From 8bc4b8a97891cf325d6a0cb97f8d27827bf7dfa4 Mon Sep 17 00:00:00 2001 From: MrS0m30n3 Date: Sat, 10 May 2014 17:03:36 +0300 Subject: [PATCH] Rerite DownloadThread.py using DownloadObject Remove OutputHandler.py Now DownloadThread talks directly on ListCtrl using write() method. Add DownloadObject.py Now DownloadObject is responsible for downloading the video and not the DownloadThread. Replaced DownloadThread.ProcessWrapper with DownloadThread.DownloadThread --- youtube_dl_gui/DownloadObject.py | 224 +++++++++++++++++++++++++ youtube_dl_gui/DownloadThread.py | 205 +++++++++++----------- youtube_dl_gui/LogManager.py | 3 + youtube_dl_gui/OptionsHandler.py | 1 - youtube_dl_gui/OutputHandler.py | 191 --------------------- youtube_dl_gui/Utils.py | 7 +- youtube_dl_gui/YoutubeDLGUI.py | 139 ++++++++------- youtube_dl_gui/YoutubeDLInterpreter.py | 24 +-- 8 files changed, 413 insertions(+), 381 deletions(-) create mode 100644 youtube_dl_gui/DownloadObject.py delete mode 100644 youtube_dl_gui/OutputHandler.py 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')