|
|
#! /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: See downlaoad() return codes
Return-Codes: OK : 'Url downloaded successfully' ERROR : 'Error occured while downloading' ALREADY: 'Url is already downloaded'
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(). '''
# download() return codes OK = 0 ERROR = 1 ALREADY = -1
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._return_code = 0 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): self._return_code = self.OK
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 != '': self._return_code = self.ERROR
if self.logger is not None: self._log(stderr)
if self.data_hook is not None and synced: self._hook_data()
return self._return_code
def stop(self): 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'])
if key == 'status': # Set self._return_code to already downloaded if data[key] == 'already_downloaded': self._return_code = self.ALREADY
self._data[key] = data[key] synced = True
return synced
def _add_on_files_list(self, filename): 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): if self._proc is None: return ''
stdout = self._proc.stdout.readline() return stdout.rstrip()
def _read_stderr(self): 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'
# Get filename if stdout[0] == 'Destination:': data_dictionary['filename'] = ' '.join(stdout[1:])
# Get progress info 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]
# Get playlist info elif stdout[0] == 'Downloading' and stdout[1] == 'video': data_dictionary['playlist_index'] = stdout[2] data_dictionary['playlist_size'] = stdout[4]
# Get file already downloaded status elif stdout[-1] == 'downloaded': data_dictionary['status'] = 'already_downloaded'
if header == 'ffmpeg': data_dictionary['status'] = 'post_process'
if header == 'youtube': data_dictionary['status'] = 'pre_process'
return data_dictionary
|