Browse Source
Rerite DownloadThread.py using DownloadObject
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.DownloadThreaddoc-issue-template
MrS0m30n3
10 years ago
8 changed files with 413 additions and 381 deletions
Split View
Diff Options
-
224youtube_dl_gui/DownloadObject.py
-
205youtube_dl_gui/DownloadThread.py
-
3youtube_dl_gui/LogManager.py
-
1youtube_dl_gui/OptionsHandler.py
-
191youtube_dl_gui/OutputHandler.py
-
7youtube_dl_gui/Utils.py
-
139youtube_dl_gui/YoutubeDLGUI.py
-
24youtube_dl_gui/YoutubeDLInterpreter.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 |
@ -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 |
Write
Preview
Loading…
Cancel
Save