From 1ca60f921fd986cd977a5fd26879fe2f632a9a94 Mon Sep 17 00:00:00 2001 From: MrS0m30n3 Date: Sat, 1 Mar 2014 19:12:37 +0200 Subject: [PATCH] first commit --- LICENSE | 25 + README | 44 ++ SIGNALS.txt | 40 ++ icons/ytube.png | Bin 0 -> 5430 bytes setup.py | 11 + youtube_dl_gui/DownloadThread.py | 148 ++++++ youtube_dl_gui/OptionsHandler.py | 129 ++++++ youtube_dl_gui/UpdateThread.py | 32 ++ youtube_dl_gui/YoutubeDLGUI.py | 616 +++++++++++++++++++++++++ youtube_dl_gui/YoutubeDLInterpreter.py | 156 +++++++ youtube_dl_gui/__init__.py | 19 + youtube_dl_gui/__main__.py | 14 + youtube_dl_gui/version.py | 1 + 13 files changed, 1235 insertions(+) create mode 100644 LICENSE create mode 100644 README create mode 100644 SIGNALS.txt create mode 100644 icons/ytube.png create mode 100644 setup.py create mode 100644 youtube_dl_gui/DownloadThread.py create mode 100644 youtube_dl_gui/OptionsHandler.py create mode 100644 youtube_dl_gui/UpdateThread.py create mode 100644 youtube_dl_gui/YoutubeDLGUI.py create mode 100644 youtube_dl_gui/YoutubeDLInterpreter.py create mode 100644 youtube_dl_gui/__init__.py create mode 100644 youtube_dl_gui/__main__.py create mode 100644 youtube_dl_gui/version.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..471f09f --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + diff --git a/README b/README new file mode 100644 index 0000000..481485c --- /dev/null +++ b/README @@ -0,0 +1,44 @@ +NAME +==== + +youtube-dlG - GUI for youtube-dl + + +DESCRIPTION +=========== + +A cross platform front-end GUI of the popular youtube-dl +written in Python. + + +REQUIREMENTS +============ + +Python (version 2.7 or 3.3+) +http://www.python.org/ + +wxPython +http://wxpython.org + +** Optional (In order to convert video files to audio-only files) +FFMPEG & FFPROBE +http://www.ffmpeg.org + + +PROJECT HOMEPAGE +================ + +Youtube-dlG: https://github.com/MrS0m30n3/youtube-dl-gui +Youtube-dl: http://rg3.github.io/youtube-dl/ + + +AUTHOR +====== + +Sotiris Papadopoulos + + +THANKS +====== + +Thanks to youtube-dl authors for creating such and amazing tool. diff --git a/SIGNALS.txt b/SIGNALS.txt new file mode 100644 index 0000000..27a543a --- /dev/null +++ b/SIGNALS.txt @@ -0,0 +1,40 @@ + +Signals list, that DownloadManager thread sends (DownloadThread.py) + +HANDLER +======= + +YoutubeDLGUI.py + def download_handler(self, msg): + ... + +SIGNALS +======= + +['finish', -1]: All downloads have finished + +['finish', index]: Download of url in index has finished + +['close', -1]: Closing down DownloadManager thread + +['close', index]: Closing down url in index + +['error', index]: Error occured url, index + +['[youtube]', ..., index]: Pre-Processing for url, index + +['[download]', ..., index]: Downloading url, index + +['[ffmpeg]', ..., index]: Post-Processing for url, index + +EXAMPLES +======== + +['[youtube]', 'Setting', 'language', 0] + +['[youtube]', 'RBumgq5yVrA:', 'Downloading', 'webpage', 0] + +['[download]', '0.0%', 'of', '4.42MiB', 'at', '24.48KiB/s', 'ETA', '03:04', 0] + +['[download]', '0.1%', 'of', '4.42MiB', 'at', '63.81KiB/s', 'ETA', '01:10', 0] + diff --git a/icons/ytube.png b/icons/ytube.png new file mode 100644 index 0000000000000000000000000000000000000000..26892d6a73e63e90f7b437dd1190a1620ae33d09 GIT binary patch literal 5430 zcmeHLYiv|S6rQER5`GApXbeAKLkO4<11N2Gx9vXq+&-bDKudujgVW{Sc3X+jgquue z?wNDWcjnBQGv{6*EMl6NGe@AeiJHfRSR{n7*+$<_fqoRUXFqLVsU5ayHisF*OFi8jIyn`*QX{2Za~l8y1GG& zk)9JLezl~hX`e_rvp(IawHxUG?oP@$QUgEa<%Sj)6?QMp%L_U4a=V-uFUiRc<~r>G zKY3_}YQxs*z8SFjH_m0GX{}C&=F3ReTHP-Fgx8}Vf)6?he4$T)v7bC#OI79H{s3PV z>8iXdU2o0w1Y10A{Wx@;^tkj3Ql5PDsk8xpj@eE+)(3vQHajc0mGhb2V8!R3UA4Sk zH)Mrv2PF>oMao0wB}q?B?!4)+2U_eYfyOZ{HE;-Pyej#)w^APVdnH}=Ia$FEeR*B= z&|SZ2!%vlxE_>LYFXd}?^iK!Yecj0`I`3v@249B!QTS?0Nl|yLlw;5M*tns0rj%z7 z8@5Rvdurek_H7&1@ng!0&H?nAoMX=zQ)M~sICD+FKOb-&GQ%m!oxj75KUF!78Doso zczLOVc8pmIWGKh@V2m+Njbv3C{Q+Jt_l0}Q_^95}H$&xlSmS6^h>`h1M65!bPa17a zu#m0!UN7G{A3+-iFJ&mpJYz%ipfMlC9wweg&;Ql~$lnVxz4|`HpC5Um4>5Qh;~U6- z{fPf>yl#Cz^7-P2y};FF;bq8o9XNY;<++pjpYQ4zXEcg)ac*^K*#1xLz!1Q>Ju2-#jol3u```D4rRgJ43th%I^Nzlko*|Q4;Qz4l|vTrPGGV z_yIm|RxuD;-39p}S5Z;emQX7QEk_KD@g4Uf5g)8w)3XFQ`L>FW_>0Ao*AsM?t_&~7 z{ZjyYsyyysu?IBVDLc_hDl2;CAwM6<_jS21Ge=mAOW- z9*+-Qe%!sAB|gVEgF0+#!}$I66}=6_h{xyIM15nLAF{l`8r5DoZf;c8?+fwHe*hn} z7(RUQ5!QLd2%q|xqs-Qid;X-vRQvnuw~zWC_qc!6%J62yWz7GX9$mxuEU>pD9}eK` z_|5Q{C(ZD&mzPvrIVL{*x(xN+x4`&fV^!}$se}7pTofutEjOLCx_v{>G&T<6Hh$3m z)aP^r`Z;Ixz_!a0Q|&7&dY3xwnh&j&tuQ~7BIV)h(vqUkbMjsG2lijK;$FC=>fxC8 zh2)9vF*g*?&v)dH^V_QX=f(4zz3<&K_(WD_(6HtQ#&t6}+-Db!@MAQ@F7`d4hWap8 z+ZpNNy=jII|F;>*sI>C3@OFGV^dROwu6=c25zgFOSku>AUhK<$r{$MymDgwt-dJoJ z*#KQzjbv0Ba@#88D80Bav>D$L0r>2B%=6o(?eL+WGgMxqG3y5-Y|>EQK8@V_Ky9t+ zDJ%#TBIYhK=Kx>F+%mLQhh1xHYK(YP-uN+dFuo5KnBkL-{LmgRC*Nt?zSjny0{&t6 zp*r6e`Vu~KV15+8VSIenFymo!Zr$3Rg^05eiK+HD|L3s&9c#*aTsX%Xu-9QfzXQ)1 z*!KO(lJ0tyUmY{AvyPewpYy)lt~XR!IY!LwVxB8q)$Ig+?7LRZP1LiFN(7rc_~x=8 z#!toTM66xFI&bm24L(X3pLG;#xkT~F_wJF%3?m!jZJOwvbrfs4$zV%f`hk0477uFO z8*c~vbQ}LBnpw+9eN*ijlki15>=^fK(gwyE)_j_E)Ku}z= MAX_DOWNLOAD_THREADS: + proc = self.check_queue(0.5) + if proc != None: + self.procList.remove(proc) + self.procNo -= 1 + # If we still running create new ProcessWrapper thread + if self.running: + self.procList.append(ProcessWrapper(self.options, url, index)) + self.procNo += 1 + else: + # Return True if at least one process is alive else return False + if not self.downloading(): + self.running = False + else: + sleep(1) + + # If we reach here close down all child threads + self.terminate_all() + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['finish', -1]) + + def add_download_item(self, downloadItem): + self.downloadlist.append(downloadItem) + + def extract_data(self): + data = self.downloadlist.pop(0) + url = data['url'] + index = data['index'] + return url, index + + def terminate_all(self): + for proc in self.procList: + if proc.isAlive(): + proc.close() + proc.join() + + def downloading(self): + for proc in self.procList: + if proc.isAlive(): + return True + return False + + def check_queue(self, t=1): + for proc in self.procList: + if not self.running: + break + if not proc.isAlive(): + return proc + sleep(t) + return None + + def close(self): + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['close', -1]) + self.running = False + self.procNo = 0 + +class ProcessWrapper(Thread): + + def __init__(self, options, url, index): + super(ProcessWrapper, self).__init__() + self.options = options + self.index = index + self.url = url + self.proc = None + self.info = None + self.err = False + self.stopped = False + self.set_process_info() + self.start() + + def run(self): + self.proc = subprocess.Popen(self.options + [self.url], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=self.info) + # while subprocess is alive and NOT the current thread + while self.proc_is_alive(): + # read output + output = self.read() + if self.err: + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['error', self.index]) + else: + if output != '': + data = self.proc_output(output) + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, data) + + if not self.err and not self.stopped: + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['finish', self.index]) + + def close(self): + self.proc.kill() + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['close', self.index]) + self.stopped = True + + def proc_is_alive(self): + return self.proc.poll() == None + + def read(self): + output = self.proc.stdout.readline() + if output == '': + output = self.proc.stderr.readline() + if output != '': + self.err = True + return output.rstrip() + + def proc_output(self, output): + data = self.remove_spaces(self.string_to_array(output)) + data.append(self.index) + return data + + def string_to_array(self, string): + return string.split(' ') + + def remove_spaces(self, array): + return [x for x in array if x != ''] + + def set_process_info(self): + if OS_TYPE == 'nt': + self.info = subprocess.STARTUPINFO() + self.info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + \ No newline at end of file diff --git a/youtube_dl_gui/OptionsHandler.py b/youtube_dl_gui/OptionsHandler.py new file mode 100644 index 0000000..179a3e2 --- /dev/null +++ b/youtube_dl_gui/OptionsHandler.py @@ -0,0 +1,129 @@ +#! /usr/bin/env python + +import os + +OS_TYPE = os.name +SETTINGS_FILENAME = 'settings' + +class OptionsHandler(): + + settings_abs_path = '' + + def __init__(self): + self.load_default() + self.set_settings_path() + if self.settings_file_exist(): + self.load_from_file() + + def load_default(self): + self.ignoreErrors = True + self.idAsName = False + self.toAudio = False + self.audioFormat = "mp3" + self.keepVideo = False + self.audioQuality = 5 + self.proxy = "" + self.savePath = "" + self.autoUpdate = False + self.videoFormat = "highest available" + self.userAgent = "" + self.referer = "" + self.username = "" + self.startTrack = "1" + self.endTrack = "0" + self.maxDownloads = "0" + self.rateLimit = "0" + self.retries = "10" + self.writeDescription = False + self.writeInfo = False + self.writeThumbnail = False + self.minFileSize = "0" + self.maxFileSize = "0" + self.writeSubs = False + self.writeAllSubs = False + self.subsLang = "English" + self.writeAutoSubs = False + self.cmdArgs = "" + + def set_settings_path(self): + self.settings_abs_path = os.path.expanduser('~') + if OS_TYPE == 'nt': + self.settings_abs_path += '\\' + else: + self.settings_abs_path += '/.config/' + self.settings_abs_path += SETTINGS_FILENAME + + def settings_file_exist(self): + return os.path.exists(self.settings_abs_path) + + def read_from_file(self): + f = open(self.settings_abs_path, 'r') + options = f.readlines() + f.close() + return options + + def load_from_file(self): + opts = [] + for option in self.read_from_file(): + opts.append(option.split('=')[1].rstrip('\n')) + self.ignoreErrors = opts[0] in ['True'] + self.idAsName = opts[1] in ['True'] + self.toAudio = opts[2] in ['True'] + self.audioFormat = opts[3] + self.keepVideo = opts[4] in ['True'] + self.audioQuality = int(opts[5]) + self.proxy = opts[6] + self.savePath = opts[7] + self.autoUpdate = opts[8] in ['True'] + self.videoFormat = opts[9] + self.userAgent = opts[10] + self.referer = opts[11] + self.username = opts[12] + self.startTrack = opts[13] + self.endTrack = opts[14] + self.maxDownloads = opts[15] + self.rateLimit = opts[16] + self.retries = opts[17] + self.writeDescription = opts[18] in ['True'] + self.writeInfo = opts[19] in ['True'] + self.writeThumbnail = opts[20] in ['True'] + self.minFileSize = opts[21] + self.maxFileSize = opts[22] + self.writeSubs = opts[23] in ['True'] + self.writeAllSubs = opts[24] in ['True'] + self.subsLang = opts[25] + self.writeAutoSubs = opts[26] in ['True'] + self.cmdArgs = opts[27] + + def save_to_file(self): + f = open(self.settings_abs_path, 'w') + f.write('IgnoreErrors='+str(self.ignoreErrors)+'\n') + f.write('IdAsName='+str(self.idAsName)+'\n') + f.write('ToAudio='+str(self.toAudio)+'\n') + f.write('AudioFormat='+str(self.audioFormat)+'\n') + f.write('KeepVideo='+str(self.keepVideo)+'\n') + f.write('AudioQuality='+str(self.audioQuality)+'\n') + f.write('Proxy='+str(self.proxy)+'\n') + f.write('SavePath='+str(self.savePath)+'\n') + f.write('AutoUpdate='+str(self.autoUpdate)+'\n') + f.write('VideoFormat='+str(self.videoFormat)+'\n') + f.write('UserAgent='+str(self.userAgent)+'\n') + f.write('Referer='+str(self.referer)+'\n') + f.write('Username='+str(self.username)+'\n') + f.write('StartTrack='+str(self.startTrack)+'\n') + f.write('EndTrack='+str(self.endTrack)+'\n') + f.write('MaxDownloads='+str(self.maxDownloads)+'\n') + f.write('RateLimit='+str(self.rateLimit)+'\n') + f.write('Retries='+str(self.retries)+'\n') + f.write('WriteDescription='+str(self.writeDescription)+'\n') + f.write('WriteInfo='+str(self.writeInfo)+'\n') + f.write('WriteThumbnail='+str(self.writeThumbnail)+'\n') + f.write('MinFileSize='+str(self.minFileSize)+'\n') + f.write('MaxFileSize='+str(self.maxFileSize)+'\n') + f.write('WriteSubtitles='+str(self.writeSubs)+'\n') + f.write('WriteAllSubtitles='+str(self.writeAllSubs)+'\n') + f.write('SubtitlesLanguage='+str(self.subsLang)+'\n') + f.write('WriteAutoSubtitles='+str(self.writeAutoSubs)+'\n') + f.write('CmdArgs='+str(self.cmdArgs)+'\n') + f.close() + \ No newline at end of file diff --git a/youtube_dl_gui/UpdateThread.py b/youtube_dl_gui/UpdateThread.py new file mode 100644 index 0000000..bc6075a --- /dev/null +++ b/youtube_dl_gui/UpdateThread.py @@ -0,0 +1,32 @@ +#! /usr/bin/env python + +from threading import Thread +from wx import CallAfter +from wx.lib.pubsub import setuparg1 +from wx.lib.pubsub import pub as Publisher +from urllib2 import urlopen, URLError, HTTPError + +LATEST_YOUTUBE_DL = 'https://yt-dl.org/latest/' +PUBLISHER_TOPIC = 'update' +DOWNLOAD_TIMEOUT = 10 + +class UpdateThread(Thread): + + def __init__(self, youtubeDLFile): + super(UpdateThread, self).__init__() + self.youtubeDLFile = youtubeDLFile + self.url = LATEST_YOUTUBE_DL + youtubeDLFile + self.start() + + def run(self): + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, "Downloading latest youtube-dl...") + try: + f = urlopen(self.url, timeout=DOWNLOAD_TIMEOUT) + with open(self.youtubeDLFile, 'wb') as lf: + lf.write(f.read()) + msg = 'Youtube-dl downloaded correctly' + except (HTTPError, URLError, IOError) as e: + msg = 'Youtube-dl download failed ' + str(e) + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, msg) + CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, 'finish') + \ No newline at end of file diff --git a/youtube_dl_gui/YoutubeDLGUI.py b/youtube_dl_gui/YoutubeDLGUI.py new file mode 100644 index 0000000..bfa3fb7 --- /dev/null +++ b/youtube_dl_gui/YoutubeDLGUI.py @@ -0,0 +1,616 @@ +#! /usr/bin/env python + +''' +This file contains all gui classes +MainFrame +Custom wx.ListCtrl class +OptionsFrame + ConnectionPanel + AudioPanel + videoPanel + DownloadPanel + SubtitlesPanel + GeneralPanel + OtherPanel +''' + +import os +import wx +from wx.lib.pubsub import setuparg1 +from wx.lib.pubsub import pub as Publisher + +from .version import __version__ +from .UpdateThread import UpdateThread +from .DownloadThread import DownloadManager +from .OptionsHandler import OptionsHandler +from .YoutubeDLInterpreter import YoutubeDLInterpreter + +if os.name == 'nt': + YOUTUBE_DL_FILENAME = 'youtube-dl.exe' +else: + YOUTUBE_DL_FILENAME = 'youtube-dl' + +TITLE = 'Youtube-dlG' +AUDIOFORMATS = ["mp3", "wav", "aac", "m4a"] +VIDEOFORMATS = ["highest available", + "mp4 [1280x720]", + "mp4 [640x360]", + "webm [640x360]", + "flv [400x240]", + "3gp [320x240]", + "mp4 1080p(DASH)", + "mp4 720p(DASH)", + "mp4 480p(DASH)", + "mp4 360p(DASH)"] +LANGUAGES = ["English", + "Greek", + "Portuguese", + "French", + "Italian", + "Russian", + "Spanish", + "German"] + +class MainFrame(wx.Frame): + + def __init__(self, parent=None, id=-1): + wx.Frame.__init__(self, parent, id, TITLE+' '+__version__, size=(600, 410), style = wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER) + + # set sizers for status box (Windows & Linux) + if os.name == 'nt': + statusListSizer = (580, 165) + statusBarSizer = (15, 365) + else: + statusListSizer = (580, 195) + statusBarSizer = (15, 390) + + # create panel, trackList, statusBox using global statusBoxSizer + self.panel = wx.Panel(self) + wx.StaticText(self.panel, -1, "URLs", (15, 10)) + self.trackList = wx.TextCtrl(self.panel, -1, pos=(10, 25), size=(580, 110), style = wx.TE_MULTILINE | wx.TE_DONTWRAP) + self.statusList = ListCtrl(self.panel, -1, pos=(10, 190), size=statusListSizer, style = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) + self.statusBar = wx.StaticText(self.panel, -1, 'Author: Sotiris Papadopoulos', pos=statusBarSizer) + + # create buttons + self.downloadButton = wx.Button(self.panel, label="Download", pos=(100, 145), size=(-1, 30)) + self.updateButton = wx.Button(self.panel, label="Update", pos=(250, 145), size=(-1, 30)) + self.optionsButton = wx.Button(self.panel, label="Options", pos=(390, 145), size=(-1, 30)) + + # bind events + self.Bind(wx.EVT_BUTTON, self.OnDownload, self.downloadButton) + self.Bind(wx.EVT_BUTTON, self.OnUpdate, self.updateButton) + self.Bind(wx.EVT_BUTTON, self.OnOptions, self.optionsButton) + self.Bind(wx.EVT_TEXT, self.OnTrackListChange, self.trackList) + self.Bind(wx.EVT_CLOSE, self.OnClose) + + # set app icon + icon = wx.Icon('../icons/ytube.png', wx.BITMAP_TYPE_ICO) + self.SetIcon(icon) + + # set publisher for update thread + Publisher.subscribe(self.update_handler, "update") + + # set publisher for download thread + Publisher.subscribe(self.download_handler, "download") + + # init options object + self.optionsList = OptionsHandler() + + # init some thread variables + self.downloadThread = None + self.updateThread = None + + # init urlList for evt_text on self.trackList + self.urlList = [] + + # check & update libraries (youtube-dl) + self.check_if_youtube_dl_exist() + if (self.optionsList.autoUpdate): + self.status_bar_write("Auto update enable") + self.update_youtube_dl() + + def check_if_youtube_dl_exist(self): + if not os.path.exists(YOUTUBE_DL_FILENAME): + self.status_bar_write("Youtube-dl is missing, trying to download it...") + self.update_youtube_dl() + + def update_youtube_dl(self): + self.downloadButton.Disable() + self.updateThread = UpdateThread(YOUTUBE_DL_FILENAME) + + def status_bar_write(self, msg): + self.statusBar.SetLabel(msg) + + def download_handler(self, msg): + ''' Handles msg base into signals see SIGNALS.txt for more info''' + if msg.data[0] == 'finish': + index = msg.data.pop() + if index == -1: + self.status_bar_write('Done') + self.downloadButton.SetLabel('Download') + self.updateButton.Enable() + self.downloadThread = None + self.urlList = [] + else: + self.statusList._write_data(index, 4, '') + self.statusList._write_data(index, 5, 'Finished') + elif msg.data[0] == 'close': + index = msg.data.pop() + if index == -1: + self.status_bar_write('Stoping downloads') + else: + self.statusList._write_data(index, 3, '') + self.statusList._write_data(index, 4, '') + self.statusList._write_data(index, 5, 'Stopped') + elif msg.data[0] == 'error': + index = msg.data.pop() + self.statusList._write_data(index, 3, '') + self.statusList._write_data(index, 4, '') + self.statusList._write_data(index, 5, 'Error') + elif msg.data[0] == '[youtube]': + index = msg.data.pop() + self.statusList._write_data(index, 5, 'Pre-Processing') + elif msg.data[0] == '[download]': + index = msg.data.pop() + if (len(msg.data[1]) <= 6 and msg.data[1] != '100%'): + self.statusList._write_data(index, 1, msg.data[3]) + self.statusList._write_data(index, 2, msg.data[1]) + self.statusList._write_data(index, 3, msg.data[7]) + self.statusList._write_data(index, 4, msg.data[5]) + self.statusList._write_data(index, 5, 'Downloading') + elif msg.data[0] == '[ffmpeg]': + index = msg.data.pop() + self.statusList._write_data(index, 4, '') + self.statusList._write_data(index, 5, 'Converting to Audio') + + def update_handler(self, msg): + if msg.data == 'finish': + self.downloadButton.Enable() + self.updateThread = None + else: + self.status_bar_write(msg.data) + + def stop_download(self): + self.downloadThread.close() + self.downloadThread.join() + self.downloadThread = None + self.urlList = [] + + def start_download(self, trackList): + self.statusList._clear_list() + for url in trackList: + if url != '': + self.urlList.append(url) + self.statusList._add_item(url) + if not self.statusList._is_empty(): + options = YoutubeDLInterpreter(self.optionsList, YOUTUBE_DL_FILENAME).get_options() + self.status_bar_write('Download started') + self.downloadThread = DownloadManager(options, self.statusList._get_items()) + self.downloadButton.SetLabel('Stop') + self.updateButton.Disable() + + def save_options(self): + self.optionsList.save_to_file() + + def OnTrackListChange(self, event): + if self.downloadThread != None: + ''' Get current url list from trackList textCtrl ''' + curList = self.trackList.GetValue().split('\n') + ''' For each url in current url list ''' + for url in curList: + ''' If url is not in self.urlList (original downloads list) and url is not empty ''' + if url not in self.urlList and url != '': + ''' Add url into original download list ''' + self.urlList.append(url) + ''' Add url into statusList ''' + index = self.statusList._add_item(url) + ''' Retrieve last item as {url:url, index:indexNo} ''' + item = self.statusList._get_last_item() + ''' Pass that item into downloadThread ''' + self.downloadThread.add_download_item(item) + + def OnDownload(self, event): + if self.downloadThread != None: + self.stop_download() + else: + self.start_download(self.trackList.GetValue().split('\n')) + + def OnUpdate(self, event): + if (self.downloadThread == None and self.updateThread == None): + self.status_bar_write("Updating youtube-dl...") + self.update_youtube_dl() + + def OnOptions(self, event): + optionsFrame = OptionsFrame(self.optionsList) + optionsFrame.Show() + + def OnClose(self, event): + self.save_options() + self.Destroy() + +class ListCtrl(wx.ListCtrl): + ''' Custom ListCtrl class ''' + def __init__(self, parent, id, pos, size, style): + wx.ListCtrl.__init__(self, parent, id, pos, size, style) + self.InsertColumn(0, 'URL', width=150) + self.InsertColumn(1, 'Size', width=90) + self.InsertColumn(2, 'Percent', width=80) + self.InsertColumn(3, 'ETA', width=50) + self.InsertColumn(4, 'Speed', width=90) + self.InsertColumn(5, 'Status', width=120) + self.ListIndex = 0 + + ''' Add single item on list ''' + def _add_item(self, item): + self.InsertStringItem(self.ListIndex, item) + self.ListIndex += 1 + return self.ListIndex + + ''' Write data on index, column ''' + def _write_data(self, index, column, data): + self.SetStringItem(index, column, data) + + ''' Clear list and set index to 0''' + def _clear_list(self): + self.DeleteAllItems() + self.ListIndex = 0 + + ''' Return True if list is empty ''' + def _is_empty(self): + if self.ListIndex == 0: + return True + else: + return False + + ''' Get last item inserted, Returns dictionary ''' + def _get_last_item(self): + data = {} + last_item = self.GetItem(itemId=self.ListIndex-1, col=0) + data['url'] = last_item.GetText() + data['index'] = self.ListIndex-1 + return data + + ''' Retrieve all items [start, self.ListIndex), Returns list ''' + def _get_items(self, start=0): + items = [] + for row in range(start, self.ListIndex): + item = self.GetItem(itemId=row, col=0) + data = {} + data['url'] = item.GetText() + data['index'] = row + items.append(data) + return items + +class ConnectionPanel(wx.Panel): + + def __init__(self, parent, optList): + self.optList = optList + + wx.Panel.__init__(self, parent) + wx.StaticText(self, -1, 'User Agent', (15, 10)) + self.userAgentBox = wx.TextCtrl(self, -1, pos=(10, 30), size=(230, -1)) + wx.StaticText(self, -1, 'Referer', (270, 10)) + self.refererBox = wx.TextCtrl(self, -1, pos=(265, 30), size=(230, -1)) + wx.StaticText(self, -1, 'Username', (15, 60)) + self.usernameBox = wx.TextCtrl(self, -1, pos=(10, 80), size=(230, -1)) + wx.StaticText(self, -1, 'Password', (270, 60)) + self.passwordBox = wx.TextCtrl(self, -1, pos=(265, 80), size=(230, -1), style = wx.TE_PASSWORD) + wx.StaticText(self, -1, 'Proxy', (15, 110)) + self.proxyBox = wx.TextCtrl(self, -1, pos=(10, 130), size=(350, -1)) + + def load_options(self): + self.userAgentBox.SetValue(self.optList.userAgent) + self.refererBox.SetValue(self.optList.referer) + self.usernameBox.SetValue(self.optList.username) + self.proxyBox.SetValue(self.optList.proxy) + + def save_options(self): + self.optList.userAgent = self.userAgentBox.GetValue() + self.optList.referer = self.refererBox.GetValue() + self.optList.username = self.usernameBox.GetValue() + self.optList.proxy = self.proxyBox.GetValue() + +class AudioPanel(wx.Panel): + + def __init__(self, parent, optList): + self.optList = optList + + wx.Panel.__init__(self, parent) + self.toAudioChk = wx.CheckBox(self, -1, 'Convert to Audio', (10, 10)) + self.keepVideoChk = wx.CheckBox(self, -1, 'Keep Video', (30, 40)) + wx.StaticText(self, -1, 'Audio Format', (35, 80)) + self.audioFormatCombo = wx.ComboBox(self, choices=AUDIOFORMATS, pos=(30, 100), size=(95, -1)) + wx.StaticText(self, -1, "Audio Quality 0 (best) 9 (worst)", (35, 130)) + self.audioQualitySpnr = wx.SpinCtrl(self, -1, "", (30, 150)) + self.audioQualitySpnr.SetRange(0, 9) + + self.Bind(wx.EVT_CHECKBOX, self.OnAudioCheck, self.toAudioChk) + + def OnAudioCheck(self, event): + if (self.toAudioChk.GetValue()): + self.keepVideoChk.Enable() + self.audioFormatCombo.Enable() + self.audioQualitySpnr.Enable() + else: + self.keepVideoChk.Disable() + self.audioFormatCombo.Disable() + self.audioQualitySpnr.Disable() + + def load_options(self): + self.toAudioChk.SetValue(self.optList.toAudio) + self.keepVideoChk.SetValue(self.optList.keepVideo) + self.audioFormatCombo.SetValue(self.optList.audioFormat) + self.audioQualitySpnr.SetValue(self.optList.audioQuality) + if (self.optList.toAudio == False): + self.keepVideoChk.Disable() + self.audioFormatCombo.Disable() + self.audioQualitySpnr.Disable() + + def save_options(self): + self.optList.toAudio = self.toAudioChk.GetValue() + self.optList.keepVideo = self.keepVideoChk.GetValue() + self.optList.audioFormat = self.audioFormatCombo.GetValue() + self.optList.audioQuality = self.audioQualitySpnr.GetValue() + +class VideoPanel(wx.Panel): + + def __init__(self, parent, optList): + self.optList = optList + + wx.Panel.__init__(self, parent) + wx.StaticText(self, -1, 'Video Format', (15, 10)) + self.videoFormatCombo = wx.ComboBox(self, choices=VIDEOFORMATS, pos=(10, 30), size=(160, 30)) + wx.StaticText(self, -1, 'Playlist Options', (300, 30)) + wx.StaticText(self, -1, 'Start', (250, 60)) + self.startBox = wx.TextCtrl(self, -1, pos=(320, 55), size=(50, -1)) + wx.StaticText(self, -1, 'Stop', (250, 100)) + self.stopBox = wx.TextCtrl(self, -1, pos=(320, 95), size=(50, -1)) + wx.StaticText(self, -1, 'Max DLs', (250, 140)) + self.maxBox = wx.TextCtrl(self, -1, pos=(320, 135), size=(50, -1)) + + def load_options(self): + self.videoFormatCombo.SetValue(self.optList.videoFormat) + self.startBox.SetValue(self.optList.startTrack) + self.stopBox.SetValue(self.optList.endTrack) + self.maxBox.SetValue(self.optList.maxDownloads) + + def save_options(self): + self.optList.videoFormat = self.videoFormatCombo.GetValue() + self.optList.startTrack = self.startBox.GetValue() + self.optList.endTrack = self.stopBox.GetValue() + self.optList.maxDownloads = self.maxBox.GetValue() + +class DownloadPanel(wx.Panel): + + def __init__(self, parent, optList): + self.optList = optList + + wx.Panel.__init__(self, parent) + wx.StaticText(self, -1, 'Rate Limit (e.g. 50k or 44.6m)', (250, 15)) + self.limitBox = wx.TextCtrl(self, -1, pos=(245, 35), size=(80, -1)) + wx.StaticText(self, -1, 'Retries', (15, 15)) + self.retriesBox = wx.TextCtrl(self, -1, pos=(10, 35), size=(50, -1)) + self.writeDescriptionChk = wx.CheckBox(self, -1, 'Write description to file', (10, 60)) + self.writeInfoChk = wx.CheckBox(self, -1, 'Write info to (.json) file', (10, 85)) + self.writeThumbnailChk = wx.CheckBox(self, -1, 'Write thumbnail to disk', (10, 110)) + self.ignoreErrorsChk = wx.CheckBox(self, -1, 'Ignore Errors', (10, 135)) + wx.StaticText(self, -1, 'Min Filesize (e.g. 50k or 44.6m)', (250, 65)) + self.minFilesizeBox = wx.TextCtrl(self, -1, pos=(245, 85), size=(80, -1)) + wx.StaticText(self, -1, 'Max Filesize (e.g. 50k or 44.6m)', (250, 115)) + self.maxFilesizeBox = wx.TextCtrl(self, -1, pos=(245, 135), size=(80, -1)) + + def load_options(self): + self.limitBox.SetValue(self.optList.rateLimit) + self.retriesBox.SetValue(self.optList.retries) + self.writeDescriptionChk.SetValue(self.optList.writeDescription) + self.writeInfoChk.SetValue(self.optList.writeInfo) + self.writeThumbnailChk.SetValue(self.optList.writeThumbnail) + self.ignoreErrorsChk.SetValue(self.optList.ignoreErrors) + self.minFilesizeBox.SetValue(self.optList.minFileSize) + self.maxFilesizeBox.SetValue(self.optList.maxFileSize) + + def save_options(self): + self.optList.rateLimit = self.limitBox.GetValue() + self.optList.retries = self.retriesBox.GetValue() + self.optList.writeDescription = self.writeDescriptionChk.GetValue() + self.optList.writeInfo = self.writeInfoChk.GetValue() + self.optList.writeThumbnail = self.writeThumbnailChk.GetValue() + self.optList.ignoreErrors = self.ignoreErrorsChk.GetValue() + self.optList.minFileSize = self.minFilesizeBox.GetValue() + self.optList.maxFileSize = self.maxFilesizeBox.GetValue() + +class SubtitlesPanel(wx.Panel): + + def __init__(self, parent, optList): + self.optList = optList + + wx.Panel.__init__(self, parent) + self.writeSubsChk = wx.CheckBox(self, -1, 'Write subtitle file', (10, 10)) + self.writeAllSubsChk = wx.CheckBox(self, -1, 'Download all available subtitles', (10, 40)) + self.writeAutoSubsChk = wx.CheckBox(self, -1, 'Write automatic subtitle file (YOUTUBE ONLY)', (10, 70)) + wx.StaticText(self, -1, 'Subtitles Language', (15, 105)) + self.subsLangCombo = wx.ComboBox(self, choices=LANGUAGES, pos=(10, 125), size=(140, 30)) + + self.Bind(wx.EVT_CHECKBOX, self.OnWriteSubsChk, self.writeSubsChk) + self.Bind(wx.EVT_CHECKBOX, self.OnWriteAllSubsChk, self.writeAllSubsChk) + self.Bind(wx.EVT_CHECKBOX, self.OnWriteAutoSubsChk, self.writeAutoSubsChk) + + def OnWriteAutoSubsChk(self, event): + if (self.writeAutoSubsChk.GetValue()): + self.writeAllSubsChk.Disable() + self.writeSubsChk.Disable() + self.subsLangCombo.Disable() + else: + self.writeAllSubsChk.Enable() + self.writeSubsChk.Enable() + self.subsLangCombo.Enable() + + def OnWriteSubsChk(self, event): + if (self.writeSubsChk.GetValue()): + self.writeAllSubsChk.Disable() + self.writeAutoSubsChk.Disable() + else: + self.writeAllSubsChk.Enable() + self.writeAutoSubsChk.Enable() + + def OnWriteAllSubsChk(self, event): + if (self.writeAllSubsChk.GetValue()): + self.writeSubsChk.Disable() + self.subsLangCombo.Disable() + self.writeAutoSubsChk.Disable() + else: + self.writeSubsChk.Enable() + self.subsLangCombo.Enable() + self.writeAutoSubsChk.Enable() + + def load_options(self): + self.writeSubsChk.Enable() + self.subsLangCombo.Enable() + self.writeAllSubsChk.Enable() + self.writeAutoSubsChk.Enable() + self.writeSubsChk.SetValue(self.optList.writeSubs) + self.writeAllSubsChk.SetValue(self.optList.writeAllSubs) + self.subsLangCombo.SetValue(self.optList.subsLang) + self.writeAutoSubsChk.SetValue(self.optList.writeAutoSubs) + if (self.writeSubsChk.GetValue()): + self.writeAllSubsChk.Disable() + self.writeAllSubsChk.SetValue(False) + self.writeAutoSubsChk.Disable() + self.writeAutoSubsChk.SetValue(False) + if (self.writeAllSubsChk.GetValue()): + self.writeSubsChk.Disable() + self.writeSubsChk.SetValue(False) + self.subsLangCombo.Disable() + self.writeAutoSubsChk.Disable() + self.writeAutoSubsChk.SetValue(False) + if (self.writeAutoSubsChk.GetValue()): + self.writeAllSubsChk.Disable() + self.writeAllSubsChk.SetValue(False) + self.writeSubsChk.Disable() + self.writeSubsChk.SetValue(False) + self.subsLangCombo.Disable() + + def save_options(self): + self.optList.writeSubs = self.writeSubsChk.GetValue() + self.optList.writeAllSubs = self.writeAllSubsChk.GetValue() + self.optList.subsLang = self.subsLangCombo.GetValue() + self.optList.writeAutoSubs = self.writeAutoSubsChk.GetValue() + +class GeneralPanel(wx.Panel): + + def __init__(self, parent, optList, controlParent): + self.optList = optList + self.parent = controlParent + + wx.Panel.__init__(self, parent) + wx.StaticText(self, -1, "Save Path", (15, 10)) + self.savePathBox = wx.TextCtrl(self, -1, pos=(10, 30), size=(350, -1)) + self.idAsNameChk = wx.CheckBox(self, -1, 'ID as Name', (10, 70)) + self.autoUpdateChk = wx.CheckBox(self, -1, 'Auto Update', (160, 70)) + self.aboutButton = wx.Button(self, label="About", pos=(380, 80), size=(100, 40)) + self.openButton = wx.Button(self, label="Open", pos=(380, 20), size=(100, 40)) + self.resetButton = wx.Button(self, label="Reset", pos=(380, 140), size=(100, 40)) + wx.StaticText(self, -1, "Settings: " + self.optList.settings_abs_path, (20, 155)) + + self.Bind(wx.EVT_BUTTON, self.OnAbout, self.aboutButton) + self.Bind(wx.EVT_BUTTON, self.OnOpen, self.openButton) + self.Bind(wx.EVT_BUTTON, self.OnReset, self.resetButton) + + def OnReset(self, event): + self.parent.reset() + + def OnOpen(self, event): + dlg = wx.DirDialog(None, "Choose directory") + if dlg.ShowModal() == wx.ID_OK: + self.savePathBox.SetValue(dlg.GetPath()) + dlg.Destroy() + + def OnAbout(self, event): + msg = TITLE + ' (GUI)'+ '\nVersion: ' + __version__ + '\nCreated by: Sotiris Papadopoulos' + wx.MessageBox(msg, 'About', wx.OK | wx.ICON_INFORMATION) + + def load_options(self): + self.savePathBox.SetValue(self.optList.savePath) + self.idAsNameChk.SetValue(self.optList.idAsName) + self.autoUpdateChk.SetValue(self.optList.autoUpdate) + + def save_options(self): + self.optList.savePath = self.savePathBox.GetValue() + self.optList.idAsName = self.idAsNameChk.GetValue() + self.optList.autoUpdate = self.autoUpdateChk.GetValue() + +class OtherPanel(wx.Panel): + + def __init__(self, parent, optList): + self.optList = optList + + wx.Panel.__init__(self, parent) + wx.StaticText(self, -1, 'Command line arguments (e.g. --help)', (25, 20)) + self.cmdArgsBox = wx.TextCtrl(self, -1, pos=(20, 40), size=(450, -1)) + + def load_options(self): + self.cmdArgsBox.SetValue(self.optList.cmdArgs) + + def save_options(self): + self.optList.cmdArgs = self.cmdArgsBox.GetValue() + +class OptionsFrame(wx.Frame): + + def __init__(self, optionsList, parent=None, id=-1): + wx.Frame.__init__(self, parent, id, "Options", size=(540, 250)) + + self.optionsList = optionsList + + panel = wx.Panel(self) + notebook = wx.Notebook(panel) + + self.generalTab = GeneralPanel(notebook, self.optionsList, self) + self.audioTab = AudioPanel(notebook, self.optionsList) + self.connectionTab = ConnectionPanel(notebook, self.optionsList) + self.videoTab = VideoPanel(notebook, self.optionsList) + self.downloadTab = DownloadPanel(notebook, self.optionsList) + self.subtitlesTab = SubtitlesPanel(notebook, self.optionsList) + self.otherTab = OtherPanel(notebook, self.optionsList) + + notebook.AddPage(self.generalTab, "General") + notebook.AddPage(self.audioTab, "Audio") + notebook.AddPage(self.videoTab, "Video") + notebook.AddPage(self.subtitlesTab, "Subtitles") + notebook.AddPage(self.downloadTab, "Download") + notebook.AddPage(self.connectionTab, "Connection") + notebook.AddPage(self.otherTab, "Commands") + + sizer = wx.BoxSizer() + sizer.Add(notebook, 1, wx.EXPAND) + panel.SetSizer(sizer) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + self.load_all_options() + + def OnClose(self, event): + self.save_all_options() + self.Destroy() + + def reset(self): + self.optionsList.load_default() + self.load_all_options() + + def load_all_options(self): + self.generalTab.load_options() + self.audioTab.load_options() + self.connectionTab.load_options() + self.videoTab.load_options() + self.downloadTab.load_options() + self.subtitlesTab.load_options() + self.otherTab.load_options() + + def save_all_options(self): + self.generalTab.save_options() + self.audioTab.save_options() + self.connectionTab.save_options() + self.videoTab.save_options() + self.downloadTab.save_options() + self.subtitlesTab.save_options() + self.otherTab.save_options() + diff --git a/youtube_dl_gui/YoutubeDLInterpreter.py b/youtube_dl_gui/YoutubeDLInterpreter.py new file mode 100644 index 0000000..3250d9d --- /dev/null +++ b/youtube_dl_gui/YoutubeDLInterpreter.py @@ -0,0 +1,156 @@ +#! /usr/bin/env python + +''' +Parse OptionHandler object into list +and call youtube_dl.main(list) using +subprocess (we use this method to let +youtube_dl.main() handle all the hard +work) +''' + +from os import name + +OS_TYPE = name + +LANGUAGES = {"English":"en", + "Greek":"gr", + "Portuguese":"pt", + "French":"fr", + "Italian":"it", + "Russian":"ru", + "Spanish":"es", + "German":"de"} + +VIDEOFORMATS = {"highest available":"auto", + "mp4 [1280x720]":"22", + "mp4 [640x360]":"18", + "webm [640x360]":"43", + "flv [400x240]":"5", + "3gp [320x240]":"36", + "mp4 1080p(DASH)":"137", + "mp4 720p(DASH)":"136", + "mp4 480p(DASH)":"135", + "mp4 360p(DASH)":"134"} + +class YoutubeDLInterpreter(): + + def __init__(self, optionsList, youtubeDLFile): + self.youtubeDLFile = youtubeDLFile + self.optionsList = optionsList + self.opts = [] + self.set_os() + self.set_progress_opts() + self.set_download_opts() + self.set_connection_opts() + self.set_video_opts() + self.set_playlist_opts() + self.set_subtitles_opts() + self.set_output_opts() + self.set_audio_opts() + self.set_other_opts() + + def get_options(self): + return self.opts + + def set_os(self): + if OS_TYPE == 'nt': + self.opts = [self.youtubeDLFile] + else: + self.opts = ['python', self.youtubeDLFile] + + def set_download_opts(self): + if (self.optionsList.rateLimit != '0' and self.optionsList.rateLimit != ''): + self.opts.append('-r') + self.opts.append(self.optionsList.rateLimit) + if (self.optionsList.retries != '10' and self.optionsList.retries != ''): + self.opts.append('-R') + self.opts.append(self.optionsList.retries) + if (self.optionsList.minFileSize != '0' and self.optionsList.minFileSize != ''): + self.opts.append('--min-filesize') + self.opts.append(self.optionsList.minFileSize) + if (self.optionsList.maxFileSize != '0' and self.optionsList.maxFileSize != ''): + self.opts.append('--max-filesize') + self.opts.append(self.optionsList.maxFileSize) + if self.optionsList.writeDescription: + self.opts.append('--write-description') + if self.optionsList.writeInfo: + self.opts.append('--write-info-json') + if self.optionsList.writeThumbnail: + self.opts.append('--write-thumbnail') + + def set_progress_opts(self): + self.opts.append('--newline') + + def set_connection_opts(self): + if self.optionsList.proxy != '': + self.opts.append('--proxy') + self.opts.append(self.optionsList.proxy) + if self.optionsList.username != '': + self.opts.append('--username') + self.opts.append(self.optionsList.username) + if self.optionsList.userAgent != '': + self.opts.append('--user-agent') + self.opts.append(self.optionsList.userAgent) + if self.optionsList.referer != '': + self.opts.append('--referer') + self.opts.append(self.optionsList.referer) + + def set_video_opts(self): + if self.optionsList.videoFormat != 'highest available': + self.opts.append('-f') + self.opts.append(VIDEOFORMATS[self.optionsList.videoFormat]) + + def set_playlist_opts(self): + if (self.optionsList.startTrack != '1' and self.optionsList.startTrack != ''): + self.opts.append('--playlist-start') + self.opts.append(self.optionsList.startTrack) + if (self.optionsList.endTrack != '0' and self.optionsList.endTrack != ''): + self.opts.append('--playlist-end') + self.opts.append(self.optionsList.endTrack) + if (self.optionsList.maxDownloads != '0' and self.optionsList.maxDownloads != ''): + self.opts.append('--max-downloads') + self.opts.append(self.optionsList.maxDownloads) + + def set_subtitles_opts(self): + if self.optionsList.writeAllSubs: + self.opts.append('--all-subs') + if (self.optionsList.writeAutoSubs): + self.opts.append('--write-auto-sub') + if self.optionsList.writeSubs: + self.opts.append('--write-subs') + if self.optionsList.subsLang != 'English': + self.opts.append('--sub-lang') + self.opts.append(LANGUAGES[self.optionsList.subsLang]) + + def set_output_opts(self): + fullP = '' + if self.optionsList.savePath != '': + if OS_TYPE == 'nt': + fullP = self.optionsList.savePath + '\\' + else: + fullP = self.optionsList.savePath + '/' + self.opts.append('-o') + if self.optionsList.idAsName: + self.opts.append(fullP + '%(id)s.%(ext)s') + else: + self.opts.append(fullP + '%(title)s.%(ext)s') + + def set_audio_opts(self): + if self.optionsList.toAudio: + self.opts.append('-x') + self.opts.append('--audio-format') + self.opts.append(self.optionsList.audioFormat) + if self.optionsList.audioQuality != 5: + self.opts.append('--audio-quality') + self.opts.append(str(self.optionsList.audioQuality)) + if self.optionsList.keepVideo: + self.opts.append('-k') + + def set_other_opts(self): + if self.optionsList.ignoreErrors: + self.opts.append('-i') + if self.optionsList.cmdArgs != '': + for option in self.optionsList.cmdArgs.split(): + self.opts.append(option) + + \ No newline at end of file diff --git a/youtube_dl_gui/__init__.py b/youtube_dl_gui/__init__.py new file mode 100644 index 0000000..5fe23c3 --- /dev/null +++ b/youtube_dl_gui/__init__.py @@ -0,0 +1,19 @@ +#! /usr/bin/env python + +from sys import exit + +try: + import wx +except ImportError, e: + print '[ERROR]', e + print 'Please install latest wx.Python' + exit(1) + +from .YoutubeDLGUI import MainFrame + +def main(): + app = wx.App() + frame = MainFrame() + frame.Centre() + frame.Show() + app.MainLoop() \ No newline at end of file diff --git a/youtube_dl_gui/__main__.py b/youtube_dl_gui/__main__.py new file mode 100644 index 0000000..3e5bb0b --- /dev/null +++ b/youtube_dl_gui/__main__.py @@ -0,0 +1,14 @@ +#! /usr/bin/env python + +import sys + +if __package__ is None and not hasattr(sys, "frozen"): + # direct call of __main__.py + import os.path + path = os.path.realpath(os.path.abspath(__file__)) + sys.path.append(os.path.dirname(os.path.dirname(path))) + +import youtube_dl_gui + +if __name__ == '__main__': + youtube_dl_gui.main() \ No newline at end of file diff --git a/youtube_dl_gui/version.py b/youtube_dl_gui/version.py new file mode 100644 index 0000000..b650ceb --- /dev/null +++ b/youtube_dl_gui/version.py @@ -0,0 +1 @@ +__version__ = '0.2'