#! /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