diff --git a/youtube_dl_gui/DownloadObject.py b/youtube_dl_gui/DownloadObject.py new file mode 100644 index 0000000..9ef1879 --- /dev/null +++ b/youtube_dl_gui/DownloadObject.py @@ -0,0 +1,224 @@ +#! /usr/bin/env python + +import subprocess + +from .Utils import ( + get_encoding, + get_filename, + encode_list, + os_type +) + + +class DownloadObject(object): + + ''' + Download videos using youtube-dl & subprocess. + + Params + youtubedl_path: Absolute path of youtube-dl. + data_hook: Can be any function with one parameter, the data. + logger: Can be any logger which implements log(). + + Accessible Methods + download() + Params: URL to download + Options list e.g. ['--help'] + Return: On download error return False else True. + + stop() + Params: None + + Acessible Variables + files_list: Python list that contains all the files DownloadObject + instance has downloaded. + + Data_hook Keys + See self._init_data(). + ''' + + STDERR_IGNORE = '' # Default filter for our self._log() method + + def __init__(self, youtubedl_path, data_hook=None, logger=None): + self.youtubedl_path = youtubedl_path + self.data_hook = data_hook + self.logger = logger + self.files_list = [] + self._proc = None + self._init_data() + + def _init_data(self): + ''' Keep the __init__() clean. ''' + self._data = { + 'playlist_index': None, + 'playlist_size': None, + 'filesize': None, + 'filename': None, + 'percent': None, + 'status': None, + 'speed': None, + 'eta': None + } + + def download(self, url, options): + ''' Download url using given options list. ''' + error = False + + cmd = self._get_cmd(url, options) + cmd = self._encode_cmd(cmd) + info = self._get_process_info() + + self._proc = self._create_process(cmd, info) + + while self._proc_is_alive(): + stdout, stderr = self._read() + + data = extract_data(stdout) + synced = self._sync_data(data) + + if stderr != '': + error = True + + if self.logger is not None: + self._log(stderr) + + if self.data_hook is not None and synced: + self._hook_data() + + return (not error) + + def stop(self): + ''' Stop download process. ''' + if self._proc is not None: + self._proc.kill() + + def _sync_data(self, data): + ''' Sync data between extract_data() dictionary and self._data. + Return True if synced else return False. + ''' + synced = False + for key in data: + if key == 'filename': + # Save full file path on files_list + self._add_on_files_list(data['filename']) + # Keep only the filename not the path on data['filename'] + data['filename'] = get_filename(data['filename']) + + self._data[key] = data[key] + synced = True + + return synced + + def _add_on_files_list(self, filename): + ''' Add filename on files_list. ''' + self.files_list.append(filename) + + def _log(self, data): + if data != self.STDERR_IGNORE: + self.logger.log(data) + + def _hook_data(self): + ''' Pass self._data back to data_hook. ''' + self.data_hook(self._data) + + def _proc_is_alive(self): + ''' Return True if self._proc is alive. ''' + if self._proc is None: + return False + return self._proc.poll() is None + + def _read(self): + ''' Read subprocess stdout, stderr. ''' + stdout = self._read_stdout() + if stdout == '': + stderr = self._read_stderr() + else: + stderr = '' + return stdout, stderr + + def _read_stdout(self): + ''' Read subprocess stdout. ''' + if self._proc is None: + return '' + + stdout = self._proc.stdout.readline() + return stdout.rstrip() + + def _read_stderr(self): + ''' Read subprocess stderr. ''' + if self._proc is None: + return '' + + stderr = self._proc.stderr.readline() + return stderr.rstrip() + + def _create_process(self, cmd, info): + return subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=info) + + def _get_cmd(self, url, options): + ''' Return command for subprocess. ''' + if os_type == 'nt': + cmd = [self.youtubedl_path] + options + [url] + else: + cmd = ['python', self.youtubedl_path] + options + [url] + return cmd + + def _encode_cmd(self, cmd): + ''' Encode command for subprocess. + Refer to http://stackoverflow.com/a/9951851/35070 + ''' + encoding = get_encoding() + if encoding is not None: + cmd = encode_list(cmd, encoding) + return cmd + + def _get_process_info(self): + ''' Hide subprocess window on Windows. ''' + if os_type == 'nt': + info = subprocess.STARTUPINFO() + info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + return info + else: + return None + + +def extract_data(stdout): + ''' Extract data from youtube-dl stdout. ''' + data_dictionary = {} + + stdout = [s for s in stdout.split(' ') if s != ''] + + if len(stdout) == 0: + return data_dictionary + + header = stdout.pop(0).replace('[', '').replace(']', '') + + if header == 'download': + data_dictionary['status'] = 'download' + + if stdout[0] == 'Destination:': + data_dictionary['filename'] = ' '.join(stdout[1:]) + + elif '%' in stdout[0]: + if stdout[0] == '100%': + data_dictionary['speed'] = '' + else: + data_dictionary['percent'] = stdout[0] + data_dictionary['filesize'] = stdout[2] + data_dictionary['speed'] = stdout[4] + data_dictionary['eta'] = stdout[6] + + elif stdout[0] == 'Downloading' and stdout[1] == 'video': + data_dictionary['playlist_index'] = stdout[2] + data_dictionary['playlist_size'] = stdout[4] + + if header == 'ffmpeg': + data_dictionary['status'] = 'post_process' + + if header == 'youtube': + data_dictionary['status'] = 'pre_process' + + return data_dictionary diff --git a/youtube_dl_gui/DownloadThread.py b/youtube_dl_gui/DownloadThread.py index 382838a..62bff75 100644 --- a/youtube_dl_gui/DownloadThread.py +++ b/youtube_dl_gui/DownloadThread.py @@ -1,6 +1,5 @@ #! /usr/bin/env python -import subprocess from time import sleep from threading import Thread @@ -8,30 +7,27 @@ from wx import CallAfter from wx.lib.pubsub import setuparg1 from wx.lib.pubsub import pub as Publisher -from .OutputHandler import ( - DataPack, - OutputFormatter -) +from .YoutubeDLInterpreter import YoutubeDLInterpreter +from .DownloadObject import DownloadObject from .Utils import ( - get_encoding, - encode_list, + get_youtubedl_filename, remove_file, - os_type, - file_exist + file_exist, + fix_path ) -MAX_DOWNLOAD_THREADS = 3 -PUBLISHER_TOPIC = 'download' class DownloadManager(Thread): - def __init__(self, options, downloadlist, clear_dash_files, logmanager=None): + PUBLISHER_TOPIC = 'download_manager' + MAX_DOWNLOAD_THREADS = 3 + + def __init__(self, downloadlist, opt_manager, logmanager=None): super(DownloadManager, self).__init__() - self.clear_dash_files = clear_dash_files self.downloadlist = downloadlist + self.opt_manager = opt_manager self.logmanager = logmanager - self.options = options self.running = True self.kill = False self.procList = [] @@ -44,7 +40,7 @@ class DownloadManager(Thread): # Extract url, index from data url, index = self.extract_data() # Wait for your turn if there are not more positions in 'queue' - while self.procNo >= MAX_DOWNLOAD_THREADS: + while self.procNo >= self.MAX_DOWNLOAD_THREADS: proc = self.check_queue() if proc != None: self.procList.remove(proc) @@ -53,11 +49,10 @@ class DownloadManager(Thread): # If we still running create new ProcessWrapper thread if self.running: self.procList.append( - ProcessWrapper( - self.options, + DownloadThread( url, index, - self.clear_dash_files, + self.opt_manager, self.logmanager ) ) @@ -71,7 +66,7 @@ class DownloadManager(Thread): # If we reach here close down all child threads self.terminate_all() if not self.kill: - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('finish')) + self._callafter('finish') def downloading(self): for proc in self.procList: @@ -100,98 +95,100 @@ class DownloadManager(Thread): if not proc.isAlive(): return proc return None - + + def _callafter(self, data): + CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data) + def close(self, kill=False): self.kill = kill self.procNo = 0 self.running = False - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('close')) - -class ProcessWrapper(Thread): - - def __init__(self, options, url, index, clear_dash_files, log=None): - super(ProcessWrapper, self).__init__() - self.clear_dash_files = clear_dash_files - self.options = options + self._callafter('close') + +class DownloadThread(Thread): + + ''' + Params + url: URL to download. + index: ListCtrl index for the current DownloadThread. + opt_manager: OptionsHandler.OptionsHandler object. + log_manager: Any logger which implements log(). + + Accessible Methods + close() + Params: None + ''' + + PUBLISHER_TOPIC = 'download_thread' + + def __init__(self, url, index, opt_manager, log_manager=None): + super(DownloadThread, self).__init__() + self.log_manager = log_manager + self.opt_manager = opt_manager self.index = index - self.log = log self.url = url - self.filenames = [] - self.stopped = False - self.error = False - self.proc = None + self._dl_object = None self.start() - + def run(self): - self.proc = self.create_process(self.get_cmd(), self.get_process_info()) - # while subprocess is alive and NOT the current thread - while self.proc_is_alive(): - # read stdout, stderr from proc - stdout, stderr = self.read() - if stdout != '': - # pass stdout to output formatter - data = OutputFormatter(stdout).get_data() - if self.clear_dash_files: self.add_file(data) - # add index to data pack - data.index = self.index - # send data back to caller - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, data) - if stderr != '': - self.error = True - self.write_to_log(stderr) - if not self.stopped: - if self.clear_dash_files: - self.clear_dash() - if not self.error: - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('finish', self.index)) - else: - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('error', self.index)) - - def add_file(self, dataPack): - if dataPack.header == 'filename': - self.filenames.append(dataPack.data) - - def write_to_log(self, data): - if self.log != None: - self.log.write(data) - - def clear_dash(self): - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('remove', self.index)) - for f in self.filenames: - if file_exist(f): - remove_file(f) - - def close(self): - self.proc.kill() - self.stopped = True - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('close', self.index)) - - def proc_is_alive(self): - return self.proc.poll() == None - - def read(self): - stdout = '' - stderr = '' - stdout = self.proc.stdout.readline() - if stdout == '': - stderr = self.proc.stderr.readline() - return stdout.rstrip(), stderr.rstrip() - - def create_process(self, cmd, info): - return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=info) - - def get_cmd(self): - enc = get_encoding() - if enc != None: - cmd = encode_list(self.options + [self.url], enc) + youtubedl_path = self._get_youtubedl_path() + options = YoutubeDLInterpreter(self.opt_manager).get_options() + + self._dl_object = DownloadObject(youtubedl_path, self._data_hook, self.log_manager) + + success = self._dl_object.download(self.url, options) + + if self.opt_manager.options['clear_dash_files']: + self._clear_dash() + + if success: + self._callafter(self._get_status_pack('Finished')) else: - cmd = self.options + [self.url] - return cmd - - def get_process_info(self): - if os_type == 'nt': - info = subprocess.STARTUPINFO() - info.dwFlags |= subprocess.STARTF_USESHOWWINDOW - return info + self._callafter(self._get_status_pack('Error')) + + def close(self): + if self._dl_object is not None: + self._callafter(self._get_status_pack('Stopping')) + self._dl_object.stop() + + def _clear_dash(self): + ''' Clear DASH files after ffmpeg mux ''' + for fl in self._dl_object.files_list: + if file_exist(fl): + remove_file(fl) + + def _data_hook(self, data): + ''' Add download index and send data back to caller ''' + data['index'] = self.index + data['status'] = self._get_status(data) + self._callafter(data) + + def _callafter(self, data): + CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data) + + def _get_status_pack(self, message): + ''' Return simple status pack ''' + data = {'index': self.index, 'status': message} + return data + + def _get_status(self, data): + ''' Return download process status ''' + if data['playlist_index'] is not None: + playlist_info = '%s/%s' % (data['playlist_index'], data['playlist_size']) else: - return None + playlist_info = '' + + if data['status'] == 'pre_process': + msg = 'Pre-Processing %s' % playlist_info + elif data['status'] == 'download': + msg = 'Downloading %s' % playlist_info + elif data['status'] == 'post_process': + msg = 'Post-Processing %s' % playlist_info + + return msg + + def _get_youtubedl_path(self): + ''' Retrieve youtube-dl path ''' + path = self.opt_manager.options['youtubedl_path'] + path = fix_path(path) + get_youtubedl_filename() + return path diff --git a/youtube_dl_gui/LogManager.py b/youtube_dl_gui/LogManager.py index 35a4239..1c437eb 100644 --- a/youtube_dl_gui/LogManager.py +++ b/youtube_dl_gui/LogManager.py @@ -35,6 +35,9 @@ class LogManager(): with open(self.path, 'w') as fl: fl.write('') + def log(self, data): + self.write(data) + def write(self, data): with open(self.path, 'a') as fl: if self.add_time: diff --git a/youtube_dl_gui/OptionsHandler.py b/youtube_dl_gui/OptionsHandler.py index bdb2e87..fc32ef4 100644 --- a/youtube_dl_gui/OptionsHandler.py +++ b/youtube_dl_gui/OptionsHandler.py @@ -8,7 +8,6 @@ from .Utils import ( get_user_config_path, get_HOME, file_exist, - os_type, fix_path, check_path ) diff --git a/youtube_dl_gui/OutputHandler.py b/youtube_dl_gui/OutputHandler.py deleted file mode 100644 index 7aeb176..0000000 --- a/youtube_dl_gui/OutputHandler.py +++ /dev/null @@ -1,191 +0,0 @@ -#! /usr/bin/env python - -from .Utils import ( - remove_empty_items, - string_to_array, - get_filename -) - -class DownloadHandler(): - - def __init__(self, ListCtrl): - self.ListCtrl = ListCtrl - self.finished = False - self.closed = False - self.error = False - self.init_handlers() - - def init_handlers(self): - ''' Initialise handlers ''' - self.handlers = [None for i in range(self.ListCtrl.ListIndex)] - - def _has_closed(self): - return self.closed - - def _has_finished(self): - return self.finished - - def _has_error(self): - return self.error - - def _add_empty_handler(self): - self.handlers.append(None) - - def handle(self, msg): - ''' Handles msg base to Signals.txt ''' - pack = msg.data - if pack.index == -1: - self.global_handler(pack) - else: - self.index_handler(pack) - - def global_handler(self, pack): - ''' Manage global index = -1 ''' - if pack.header == 'close': - self.closed = True - elif pack.header == 'finish': - self.finished = True - - def index_handler(self, pack): - ''' Manage handlers base on index ''' - if self.handlers[pack.index] == None: - self.handlers[pack.index] = IndexDownloadHandler(self.ListCtrl, pack.index) - self.handlers[pack.index].handle(pack) - if self.handlers[pack.index].has_error(): - self.error = True - -class IndexDownloadHandler(): - - def __init__(self, ListCtrl, index): - self.ListCtrl = ListCtrl - self.index = index - self.err = False - self.info = '' - - def has_error(self): - return self.err - - def handle(self, pack): - ''' Handle its pack for current index ''' - if pack.header == 'finish': - self.finish() - elif pack.header == 'close': - self.close() - elif pack.header == 'error': - self.error() - elif pack.header == 'playlist': - self.playlist(pack.data) - elif pack.header == 'youtube': - self.pre_proc() - elif pack.header == 'download': - self.download(pack.data) - elif pack.header == 'ffmpeg': - self.post_proc() - elif pack.header == 'remove': - self.remove() - elif pack.header == 'filename': - self.filename(pack.data) - - def finish(self): - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Finished') - - def close(self): - self.ListCtrl._write_data(self.index, 3, '') - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Stopped') - - def error(self): - self.err = True - self.ListCtrl._write_data(self.index, 3, '') - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Error') - - def pre_proc(self): - self.ListCtrl._write_data(self.index, 5, 'Pre-Processing %s' % self.info) - - def post_proc(self): - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Post-Processing %s' % self.info) - - def download(self, data): - self.ListCtrl._write_data(self.index, 1, data[0]) - self.ListCtrl._write_data(self.index, 2, data[1]) - self.ListCtrl._write_data(self.index, 3, data[2]) - self.ListCtrl._write_data(self.index, 4, data[3]) - self.ListCtrl._write_data(self.index, 5, 'Downloading %s' % self.info) - - def playlist(self, data): - self.ListCtrl._write_data(self.index, 1, '') - self.ListCtrl._write_data(self.index, 2, '') - self.ListCtrl._write_data(self.index, 3, '') - self.ListCtrl._write_data(self.index, 4, '') - self.info = '%s/%s' % (data[0], data[1]) - - def remove(self): - self.ListCtrl._write_data(self.index, 5, 'Removing DASH Files') - - def filename(self, fl): - self.ListCtrl._write_data(self.index, 0, get_filename(fl)) - -class DataPack(): - - def __init__(self, header, index=-1, data=None): - self.header = header - self.index = index - self.data = data - -class OutputFormatter(): - - def __init__(self, stdout): - dataPack = None - - self.stdout = remove_empty_items(string_to_array(stdout)) - # extract header from stdout - header = self.extract_header() - # extract special headers filename, playlist - header = self.set_filename_header(header) - header = self.set_playlist_header(header) - # extract data base on header - data = self.extract_data(header) - # extract special ignore header base on header, data - header = self.set_ignore_header(header, data) - # create data pack - self.dataPack = DataPack(header, data=data) - - def extract_header(self): - header = self.stdout.pop(0).replace('[', '').replace(']', '') - return header - - def extract_data(self, header): - ''' Extract data base on header ''' - if header == 'download': - if '%' in self.stdout[0]: - if self.stdout[0] != '100%': - ''' size, percent, eta, speed ''' - return [self.stdout[2], self.stdout[0], self.stdout[6], self.stdout[4]] - if header == 'playlist': - return [self.stdout[2], self.stdout[4]] - if header == 'filename': - return ' '.join(self.stdout[1:]) - return None - - def set_filename_header(self, header): - if header != 'ffmpeg': - if self.stdout[0] == 'Destination:': - return 'filename' - return header - - def set_playlist_header(self, header): - if header == 'download': - if self.stdout[0] == 'Downloading' and self.stdout[1] == 'video': - return 'playlist' - return header - - def set_ignore_header(self, header, data): - if header == 'download' and data == None: - return 'ignore' - return header - - def get_data(self): - return self.dataPack diff --git a/youtube_dl_gui/Utils.py b/youtube_dl_gui/Utils.py index 7eaaba9..a53db95 100644 --- a/youtube_dl_gui/Utils.py +++ b/youtube_dl_gui/Utils.py @@ -37,7 +37,6 @@ def get_encoding(): if sys.version_info >= (3, 0): return None if sys.platform == 'win32': - # Refer to http://stackoverflow.com/a/9951851/35070 return preferredencoding() return None @@ -86,6 +85,12 @@ def check_path(path): if not file_exist(path): makedir(path) +def get_youtubedl_filename(): + youtubedl_fl = 'youtube-dl' + if os_type == 'nt': + youtubedl_fl += '.exe' + return youtubedl_fl + def get_user_config_path(): if os_type == 'nt': path = os.getenv('APPDATA') diff --git a/youtube_dl_gui/YoutubeDLGUI.py b/youtube_dl_gui/YoutubeDLGUI.py index 5ee56b9..40625e7 100644 --- a/youtube_dl_gui/YoutubeDLGUI.py +++ b/youtube_dl_gui/YoutubeDLGUI.py @@ -10,8 +10,7 @@ from .version import __version__ from .UpdateThread import UpdateThread from .DownloadThread import DownloadManager from .OptionsHandler import OptionsHandler -from .YoutubeDLInterpreter import YoutubeDLInterpreter -from .OutputHandler import DownloadHandler + from .LogManager import LogManager, LogGUI from .Utils import ( video_is_dash, @@ -88,11 +87,11 @@ class MainFrame(wx.Frame): Publisher.subscribe(self.update_handler, "update") # set publisher for download thread - Publisher.subscribe(self.download_handler, "download") + Publisher.subscribe(self.download_handler, "download_manager") + Publisher.subscribe(self.download_handler, "download_thread") - # init Options and DownloadHandler objects + # init Options self.optManager = OptionsHandler() - self.downloadHandler = None # init log manager self.logManager = None @@ -158,13 +157,11 @@ class MainFrame(wx.Frame): def status_bar_write(self, msg): self.statusBar.SetLabel(msg) - def fin_task(self, msg): - self.status_bar_write(msg) + def fin_tasks(self): self.downloadButton.SetLabel('Download') self.updateButton.Enable() self.downloadThread.join() self.downloadThread = None - self.downloadHandler = None self.urlList = [] if self.optManager.options['shutdown']: shutdown_sys(self.optManager.options['sudo_password']) @@ -177,18 +174,18 @@ class MainFrame(wx.Frame): open_dir(self.optManager.options['save_path']) def download_handler(self, msg): - self.downloadHandler.handle(msg) - if self.downloadHandler._has_closed(): - self.status_bar_write('Stoping downloads') - if self.downloadHandler._has_finished(): - if self.downloadHandler._has_error(): - if self.logManager != None: - msg = 'An error occured while downloading. See Options>Log.' - else: - msg = 'An error occured while downloading' - else: - msg = 'Done' - self.fin_task(msg) + topic = msg.topic[0] + data = msg.data + + if topic == 'download_thread': + self.statusList.write(data) + + if topic == 'download_manager': + if data == 'close': + self.status_bar_write('Stopping downloads') + elif data == 'finish': + self.status_bar_write('Done') + self.fin_tasks() def update_handler(self, msg): if msg.data == 'finish': @@ -206,20 +203,19 @@ class MainFrame(wx.Frame): url = remove_spaces(url) if url != '': self.urlList.append(url) - self.statusList._add_item(url) + self.statusList.add_item(url) def start_download(self): - self.statusList._clear_list() + self.statusList.clear_list() self.load_tracklist(self.trackList.GetValue().split('\n')) - if not self.statusList._is_empty(): - options = YoutubeDLInterpreter(self.optManager, YOUTUBE_DL_FILENAME).get_options() + if not self.statusList.is_empty(): + self.downloadThread = DownloadManager( - options, - self.statusList._get_items(), - self.optManager.options['clear_dash_files'], + self.statusList.get_items(), + self.optManager, self.logManager ) - self.downloadHandler = DownloadHandler(self.statusList) + self.status_bar_write('Download started') self.downloadButton.SetLabel('Stop') self.updateButton.Disable() @@ -257,12 +253,10 @@ class MainFrame(wx.Frame): if url not in self.urlList and url != '': ''' Add url into original download list ''' self.urlList.append(url) - ''' Add handler for url ''' - self.downloadHandler._add_empty_handler() ''' Add url into statusList ''' - self.statusList._add_item(url) + self.statusList.add_item(url) ''' Retrieve last item as {url:url, index:indexNo} ''' - item = self.statusList._get_last_item() + item = self.statusList.get_last_item() ''' Pass that item into downloadThread ''' self.downloadThread._add_download_item(item) @@ -291,7 +285,19 @@ class MainFrame(wx.Frame): self.Destroy() class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): + ''' Custom ListCtrl class ''' + + # Hold column for each data + DATA_COLUMNS = { + 'filename': 0, + 'filesize': 1, + 'percent': 2, + 'status': 5, + 'speed': 4, + 'eta': 3 + } + def __init__(self, parent=None, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): wx.ListCtrl.__init__(self, parent, id, pos, size, style) ListCtrlAutoWidthMixin.__init__(self) @@ -302,44 +308,53 @@ class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): self.InsertColumn(4, 'Speed', width=90) self.InsertColumn(5, 'Status', width=160) self.setResizeColumn(0) - self.ListIndex = 0 - - ''' Add single item on list ''' - def _add_item(self, item): - self.InsertStringItem(self.ListIndex, item) - self.ListIndex += 1 + self.list_index = 0 - ''' Write data on index, column ''' - def _write_data(self, index, column, data): - self.SetStringItem(index, column, data) + def write(self, data): + ''' Write data on ListCtrl ''' + index = data['index'] + + for key in data: + if key in self.DATA_COLUMNS: + self._write_data(data[key], index, self.DATA_COLUMNS[key]) + + def add_item(self, item): + ''' Add single item on ListCtrl ''' + self.InsertStringItem(self.list_index, item) + self.list_index += 1 - ''' Clear list and set index to 0''' - def _clear_list(self): + def clear_list(self): self.DeleteAllItems() - self.ListIndex = 0 + self.list_index = 0 - ''' Return True if list is empty ''' - def _is_empty(self): - return self.ListIndex == 0 + def is_empty(self): + return self.list_index == 0 + + def get_items(self, start_index=0): + ''' Return list of items starting from start_index ''' + items = [] + for row in range(start_index, self.list_index): + item = self._get_item(row) + items.append(item) + return items + + def get_last_item(self): + ''' Return last item of ListCtrl ''' + return self._get_item(self.list_index - 1) + + def _write_data(self, data, row, column): + ''' Write data on row, column ''' + if isinstance(data, basestring): + self.SetStringItem(row, column, data) - ''' Get last item inserted, Returns dictionary ''' - def _get_last_item(self): + def _get_item(self, index): + ''' Return single item from index ''' data = {} - last_item = self.GetItem(itemId=self.ListIndex-1, col=0) - data['url'] = last_item.GetText() - data['index'] = self.ListIndex-1 + item = self.GetItem(itemId=index, col=0) + data['url'] = item.GetText() + data['index'] = index 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 LogPanel(wx.Panel): diff --git a/youtube_dl_gui/YoutubeDLInterpreter.py b/youtube_dl_gui/YoutubeDLInterpreter.py index 9705329..42e24ff 100644 --- a/youtube_dl_gui/YoutubeDLInterpreter.py +++ b/youtube_dl_gui/YoutubeDLInterpreter.py @@ -1,18 +1,8 @@ #! /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 .Utils import ( video_is_dash, - os_type, - fix_path, - add_PATH + fix_path ) LANGUAGES = {"English":"en", @@ -45,11 +35,9 @@ AUDIO_Q = {"high":"0", class YoutubeDLInterpreter(): - def __init__(self, optManager, youtubeDLFile): - self.youtubeDLFile = youtubeDLFile + def __init__(self, optManager): self.optManager = optManager self.opts = [] - self.set_os() self.set_progress_opts() self.set_output_opts() self.set_auth_opts() @@ -64,14 +52,6 @@ class YoutubeDLInterpreter(): def get_options(self): return self.opts - def set_os(self): - if os_type == 'nt': - self.opts = [self.youtubeDLFile] - add_PATH(self.optManager.options['youtubedl_path']) - else: - path = fix_path(self.optManager.options['youtubedl_path']) - self.opts = ['python', path + self.youtubeDLFile] - def set_progress_opts(self): ''' Do NOT change this option ''' self.opts.append('--newline')