def __init__(self, handler): self._handler = handler # See ydl_opts['forcejson'] self._on_info_dict_json = None self._allow_authentication_request = True self._skip_authentication = False self._skipped_count = 0 self.ydl_opts = { 'logger': self, 'logtostderr': True, 'no_color': True, 'progress_hooks': [self._on_progress], 'fixup': 'detect_or_warn', 'ignoreerrors': True, # handled via logger error callback 'retries': 10, 'fragment_retries': 10, 'writesubtitles': True, 'allsubtitles': True, 'subtitlesformat': 'vtt/best', 'keepvideo': True, 'postprocessors': [ {'key': 'FFmpegMetadata'}, {'key': 'FFmpegSubtitlesConvertor', 'format': 'vtt'}, {'key': 'FFmpegEmbedSubtitle'}, {'key': 'XAttrMetadata'}]} url = self._handler.get_url() download_dir = os.path.abspath(self._handler.get_download_dir()) with tempfile.TemporaryDirectory() as temp_dir: self.ydl_opts['cookiefile'] = os.path.join(temp_dir, 'cookies') # Collect info without downloading videos testplaylist_dir = os.path.join(temp_dir, 'testplaylist') noplaylist_dir = os.path.join(temp_dir, 'noplaylist') fullplaylist_dir = os.path.join(temp_dir, 'fullplaylist') for path in [testplaylist_dir, noplaylist_dir, fullplaylist_dir]: os.mkdir(path) self.ydl_opts['writeinfojson'] = True self.ydl_opts['writethumbnail'] = True self.ydl_opts['skip_download'] = True self.ydl_opts['playlistend'] = 2 self.ydl_opts['outtmpl'] = '%(autonumber)s.%(ext)s' # Test playlist info_testplaylist, skipped_testplaylist = self._load_playlist( testplaylist_dir, url) self.ydl_opts['noplaylist'] = True if len(info_testplaylist) + skipped_testplaylist > 1: info_noplaylist, skipped_noplaylist = self._load_playlist( noplaylist_dir, url) else: info_noplaylist = info_testplaylist skipped_noplaylist = skipped_testplaylist del self.ydl_opts['noplaylist'] del self.ydl_opts['playlistend'] if (len(info_testplaylist) + skipped_testplaylist > len(info_noplaylist) + skipped_noplaylist): self.ydl_opts['noplaylist'] = ( not self._handler.on_playlist_request()) if not self.ydl_opts['noplaylist']: info_playlist, _ = self._load_playlist( fullplaylist_dir, url) else: info_playlist = info_noplaylist elif len(info_testplaylist) + skipped_testplaylist > 1: info_playlist, _ = self._load_playlist(fullplaylist_dir, url) else: info_playlist = info_testplaylist # Download videos self._allow_authentication_request = False del self.ydl_opts['writeinfojson'] del self.ydl_opts['writethumbnail'] del self.ydl_opts['skip_download'] # Include id and format_id in outtmpl to prevent youtube-dl # from continuing wrong file self.ydl_opts['outtmpl'] = '%(id)s.%(format_id)s.%(ext)s' # Output info_dict as JSON handled via logger debug callback self.ydl_opts['forcejson'] = True mode = self._handler.get_mode() if mode == 'audio': resolution = MAX_RESOLUTION prefer_mpeg = False self.ydl_opts['format'] = 'bestaudio/best' self.ydl_opts['postprocessors'].insert(0, { 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192'}) self.ydl_opts['postprocessors'].insert(1, { 'key': 'EmbedThumbnail', 'already_have_thumbnail': True}) else: resolution = self._handler.get_resolution() prefer_mpeg = self._handler.get_prefer_mpeg() try: os.makedirs(download_dir, exist_ok=True) except OSError as e: traceback.print_exc(file=sys.stderr) sys.stderr.flush() self._handler.on_error( 'ERROR: Failed to create download folder: %s' % e) sys.exit(1) for i, (info_path, thumbnail_paths, subtitles) in enumerate( info_playlist): with open(info_path) as f: info = json.load(f) title = info.get('title') or info.get('id') or 'video' output_title = self._get_output_title(title) # Test subtitles # youtube-dl fails for subtitles that it can't convert or # are unsupported by ffmpeg supported_subtitles = [] for sub_path, sub_lang, sub_ext in subtitles: print('[youtube_dl_slave] Testing subtitle (%r, %r)' % (sub_lang, sub_ext), file=sys.stderr, flush=True) if sub_ext in ['dfxp', 'ttml', 'tt']: # Try to use youtube-dl's internal dfxp2srt converter with open(sub_path, 'rb') as f: sub_data = f.read() try: sub_data = dfxp2srt(sub_data) except Exception: traceback.print_exc(file=sys.stderr) sys.stderr.flush() continue ff_sub_path = sub_path + '-converted.srt' with open(ff_sub_path, 'w', encoding='utf-8') as f: f.write(sub_data) else: ff_sub_path = sub_path # Try to read and convert subtitles with ffmpeg try: subprocess.run( [FFMPEG_EXE, '-i', os.path.abspath(ff_sub_path), '-f', 'webvtt', '-'], check=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL) except FileNotFoundError: traceback.print_exc(file=sys.stderr) sys.stderr.flush() self._handler.on_error( 'ERROR: %r not found' % FFMPEG_EXE) sys.exit(1) except subprocess.CalledProcessError: traceback.print_exc(file=sys.stderr) sys.stderr.flush() continue supported_subtitles.append((sub_lang, sub_ext)) # Choose supported subtitles new_info_subtitles = {} for sub_lang, subs in (info.get('subtitles') or {}).items(): new_subs = [] for sub in subs or []: if (sub_lang, sub.get('ext')) in supported_subtitles: new_subs.append(sub) if new_subs: new_info_subtitles[sub_lang] = new_subs info['subtitles'] = new_info_subtitles thumbnail_path = thumbnail_paths[0] if thumbnail_paths else '' if thumbnail_path: # Convert thumbnail to JPEG and limit resolution print('[youtube_dl_slave] Converting thumbnail', file=sys.stderr, flush=True) new_thumbnail_path = thumbnail_path + '-converted.jpg' try: subprocess.run( [FFMPEG_EXE, '-i', os.path.abspath(thumbnail_path), '-vf', ('scale=\'min({0},iw):min({0},ih):' 'force_original_aspect_ratio=decrease\'' ).format(MAX_THUMBNAIL_RESOLUTION), os.path.abspath(new_thumbnail_path)], check=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL) except FileNotFoundError: traceback.print_exc(file=sys.stderr) sys.stderr.flush() self._handler.on_error( 'ERROR: %r not found' % FFMPEG_EXE) sys.exit(1) except subprocess.CalledProcessError: traceback.print_exc(file=sys.stderr) sys.stderr.flush() new_thumbnail_path = '' # No longer needed os.remove(thumbnail_path) thumbnail_path = new_thumbnail_path self._handler.on_progress_start(i, len(info_playlist), title, thumbnail_path) for thumbnail in info.get('thumbnails') or []: thumbnail['filename'] = thumbnail_path # Remove description, because long comments cause problems when # displayed in Nautilus and other applications. with contextlib.suppress(KeyError): del info['description'] sort_formats(info.get('formats') or [], resolution, prefer_mpeg) with open(info_path, 'w') as f: json.dump(info, f) # Check if we already got the file existing_filename = self._find_existing_download( download_dir, output_title, mode) if existing_filename is not None: self._handler.on_progress_end(existing_filename) continue # Download into separate directory because youtube-dl generates # many temporary files temp_download_dir = os.path.join( download_dir, output_title + '.part') # Lock download directory to prevent other processes from # writing to the same files temp_download_dir_cm = contextlib.ExitStack() try: temp_download_dir_cm.enter_context( self._create_and_lock_dir(temp_download_dir)) except OSError as e: traceback.print_exc(file=sys.stderr) sys.stderr.flush() self._handler.on_error( 'ERROR: Failed to lock download folder: %s' % e) sys.exit(1) with temp_download_dir_cm: # Check if the file got downloaded in the meantime existing_filename = self._find_existing_download( download_dir, output_title, mode) if existing_filename is not None: filename = existing_filename else: info_dict = None # See ydl_opts['forcejson'] def on_info_dict_json(info_dict_): nonlocal info_dict info_dict = info_dict_ self._on_info_dict_json = on_info_dict_json self._load_video(temp_download_dir, info_path) if self._on_info_dict_json: raise RuntimeError('info_dict not received') # Find the temporary filename temp_filename_root, temp_filename_ext = ( os.path.splitext(info_dict['_filename'])) if mode == 'audio': temp_filename_ext = '.mp3' else: # youtube-dl changes extension for incompatible # formats to .mkv for ext in [temp_filename_ext, '.mkv']: if os.path.exists(temp_filename_root + ext): temp_filename_ext = ext break temp_filename = temp_filename_root + temp_filename_ext filename = output_title + temp_filename_ext # Move finished download from download to target dir try: os.replace( os.path.join(temp_download_dir, temp_filename), os.path.join(download_dir, filename)) except OSError as e: traceback.print_exc(file=sys.stderr) sys.stderr.flush() self._handler.on_error(( 'ERROR: Falied to move finished download to ' 'download folder: %s') % e) sys.exit(1) # Delete download directory with contextlib.suppress(OSError): shutil.rmtree(temp_download_dir) self._handler.on_progress_end(filename)
def __init__(self): self._handler = Handler() # See ydl_opts['forcejson'] self._expect_info_dict_json = False self._info_dict = None self._allow_authentication_request = True self._skip_authentication = False self._skipped_count = 0 self.ydl_opts = { 'logger': self, 'logtostderr': True, 'no_color': True, 'progress_hooks': [self._on_progress], 'fixup': 'detect_or_warn', 'ignoreerrors': True, # handled via logger error callback 'postprocessors': [{ 'key': 'XAttrMetadata' }] } url = self._handler.get_url() target_dir = os.path.abspath(self._handler.get_target_dir()) with tempfile.TemporaryDirectory() as temp_dir: self.ydl_opts['cookiefile'] = os.path.join(temp_dir, 'cookies') # Collect info without downloading videos testplaylist_dir = os.path.join(temp_dir, 'testplaylist') noplaylist_dir = os.path.join(temp_dir, 'noplaylist') fullplaylist_dir = os.path.join(temp_dir, 'fullplaylist') for path in [testplaylist_dir, noplaylist_dir, fullplaylist_dir]: os.mkdir(path) self.ydl_opts['writeinfojson'] = True self.ydl_opts['writethumbnail'] = True self.ydl_opts['skip_download'] = True self.ydl_opts['playlistend'] = 2 self.ydl_opts['outtmpl'] = '%(autonumber)s.%(ext)s' # Test playlist info_testplaylist, skipped_testplaylist = self._load_playlist( testplaylist_dir, url) self.ydl_opts['noplaylist'] = True if len(info_testplaylist) + skipped_testplaylist > 1: info_noplaylist, skipped_noplaylist = self._load_playlist( noplaylist_dir, url) else: info_noplaylist = info_testplaylist skipped_noplaylist = skipped_testplaylist del self.ydl_opts['noplaylist'] del self.ydl_opts['playlistend'] if (len(info_testplaylist) + skipped_testplaylist > len(info_noplaylist) + skipped_noplaylist): self.ydl_opts['noplaylist'] = ( not self._handler.on_playlist_request()) if not self.ydl_opts['noplaylist']: info_playlist, skipped_playlist = self._load_playlist( fullplaylist_dir, url) else: info_playlist = info_noplaylist skipped_playlist = skipped_noplaylist elif len(info_testplaylist) + skipped_testplaylist > 1: info_playlist, skipped_playlist = self._load_playlist( fullplaylist_dir, url) else: info_playlist = info_testplaylist skipped_playlist = skipped_testplaylist # Download videos self._allow_authentication_request = False del self.ydl_opts['writeinfojson'] del self.ydl_opts['writethumbnail'] del self.ydl_opts['skip_download'] self.ydl_opts['outtmpl'] = OUTPUT_TEMPLATE # Output info_dict as JSON handled via logger debug callback self.ydl_opts['forcejson'] = True mode = self._handler.get_mode() resolution = self._handler.get_resolution() if mode == 'audio': resolution = MAX_RESOLUTION self.ydl_opts['format'] = 'bestaudio/best' self.ydl_opts['postprocessors'].insert( 0, { 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192' }) self.ydl_opts['postprocessors'].insert( 1, { 'key': 'EmbedThumbnail', 'already_have_thumbnail': True }) os.makedirs(target_dir, exist_ok=True) for i, info_path in enumerate(info_playlist): with open(info_path) as f: info = json.load(f) title = info.get('title', info.get('id', 'video')) thumbnail_paths = list( filter( lambda p: os.path.splitext(p)[1][1:] != 'json', glob.iglob( glob.escape(info_path[:-len('info.json')]) + '*'))) thumbnail_path = thumbnail_paths[0] if thumbnail_paths else '' self._handler.on_progress_start(i, len(info_playlist), title, thumbnail_path) if info.get('thumbnails'): info['thumbnails'][-1]['filename'] = thumbnail_path if info.get('formats'): sort_formats(info['formats'], resolution=resolution) with open(info_path, 'w') as f: json.dump(info, f) # See ydl_opts['forcejson'] self._expect_info_dict_json = True self._load_playlist(target_dir, info_path=info_path) if self._expect_info_dict_json: raise RuntimeError('info_dict not received') filename = self._info_dict['_filename'] if mode == 'audio': filename = os.path.splitext(filename)[0] + '.mp3' self._handler.on_progress_end(filename) self._info_dict = None
def __init__(self): self._handler = Handler() # See ydl_opts['forcejson'] self._expect_info_dict_json = False self._info_dict = None self._allow_authentication_request = True self._skip_authentication = False self._skipped_count = 0 self.ydl_opts = { 'logger': self, 'logtostderr': True, 'no_color': True, 'progress_hooks': [self._on_progress], 'fixup': 'detect_or_warn', 'ignoreerrors': True, # handled via logger error callback 'retries': 10, 'fragment_retries': 10, 'postprocessors': [{ 'key': 'XAttrMetadata' }] } url = self._handler.get_url() download_dir = os.path.abspath(self._handler.get_download_dir()) with tempfile.TemporaryDirectory() as temp_dir: self.ydl_opts['cookiefile'] = os.path.join(temp_dir, 'cookies') # Collect info without downloading videos testplaylist_dir = os.path.join(temp_dir, 'testplaylist') noplaylist_dir = os.path.join(temp_dir, 'noplaylist') fullplaylist_dir = os.path.join(temp_dir, 'fullplaylist') for path in [testplaylist_dir, noplaylist_dir, fullplaylist_dir]: os.mkdir(path) self.ydl_opts['writeinfojson'] = True self.ydl_opts['writethumbnail'] = True self.ydl_opts['skip_download'] = True self.ydl_opts['playlistend'] = 2 self.ydl_opts['outtmpl'] = '%(autonumber)s.%(ext)s' # Test playlist info_testplaylist, skipped_testplaylist = self._load_playlist( testplaylist_dir, url) self.ydl_opts['noplaylist'] = True if len(info_testplaylist) + skipped_testplaylist > 1: info_noplaylist, skipped_noplaylist = self._load_playlist( noplaylist_dir, url) else: info_noplaylist = info_testplaylist skipped_noplaylist = skipped_testplaylist del self.ydl_opts['noplaylist'] del self.ydl_opts['playlistend'] if (len(info_testplaylist) + skipped_testplaylist > len(info_noplaylist) + skipped_noplaylist): self.ydl_opts['noplaylist'] = ( not self._handler.on_playlist_request()) if not self.ydl_opts['noplaylist']: info_playlist, _ = self._load_playlist( fullplaylist_dir, url) else: info_playlist = info_noplaylist elif len(info_testplaylist) + skipped_testplaylist > 1: info_playlist, _ = self._load_playlist(fullplaylist_dir, url) else: info_playlist = info_testplaylist # Download videos self._allow_authentication_request = False del self.ydl_opts['writeinfojson'] del self.ydl_opts['writethumbnail'] del self.ydl_opts['skip_download'] # Include id and format_id in outtmpl to prevent youtube-dl # from continuing wrong file self.ydl_opts['outtmpl'] = '%(id)s.%(format_id)s.%(ext)s' # Output info_dict as JSON handled via logger debug callback self.ydl_opts['forcejson'] = True mode = self._handler.get_mode() if mode == 'audio': resolution = MAX_RESOLUTION prefer_mpeg = False self.ydl_opts['format'] = 'bestaudio/best' self.ydl_opts['postprocessors'].insert( 0, { 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192' }) self.ydl_opts['postprocessors'].insert( 1, { 'key': 'EmbedThumbnail', 'already_have_thumbnail': True }) else: resolution = self._handler.get_resolution() prefer_mpeg = self._handler.get_prefer_mpeg() try: os.makedirs(download_dir, exist_ok=True) except OSError as e: self._handler.on_error( 'ERROR: Failed to create download folder: %s' % e) raise for i, info_path in enumerate(info_playlist): with open(info_path) as f: info = json.load(f) title = info.get('title') or info.get('id') or 'video' output_title = self._get_output_title(title) thumbnail_paths = list( filter( lambda p: os.path.splitext(p)[1][1:] != 'json', glob.iglob( glob.escape(info_path[:-len('info.json')]) + '*'))) thumbnail_path = thumbnail_paths[0] if thumbnail_paths else '' if thumbnail_path: # Convert thumbnail to JPEG and limit resolution new_thumbnail_path = thumbnail_path + '-converted.jpg' try: subprocess.run([ FFMPEG_EXE, '-i', os.path.abspath(thumbnail_path), '-vf', ('scale=\'min({0},iw):min({0},ih):' 'force_original_aspect_ratio=decrease\'' ).format(MAX_THUMBNAIL_RESOLUTION), os.path.abspath(new_thumbnail_path) ], check=True, stdin=subprocess.DEVNULL, stdout=sys.stderr) except FileNotFoundError: self._handler.on_error('ERROR: %r not found' % FFMPEG_EXE) raise except subprocess.CalledProcessError: traceback.print_exc(file=sys.stderr) sys.stderr.flush() new_thumbnail_path = '' # No longer needed os.remove(thumbnail_path) thumbnail_path = new_thumbnail_path self._handler.on_progress_start(i, len(info_playlist), title, thumbnail_path) for thumbnail in info.get('thumbnails') or []: thumbnail['filename'] = thumbnail_path sort_formats( info.get('formats') or [], resolution, prefer_mpeg) with open(info_path, 'w') as f: json.dump(info, f) # Check if we already got the file existing_filename = self._find_existing_download( download_dir, output_title, mode) if existing_filename is not None: self._handler.on_progress_end(existing_filename) continue # Download into separate directory because youtube-dl generates # many temporary files temp_download_dir = os.path.join(download_dir, output_title + '.part') # Lock download directory to prevent other processes from # writing to the same files temp_download_dir_cm = contextlib.ExitStack() try: temp_download_dir_cm.enter_context( self._create_and_lock_dir(temp_download_dir)) except OSError as e: self._handler.on_error( 'ERROR: Failed to lock download folder: %s' % e) raise with temp_download_dir_cm: # Check if the file got downloaded in the meantime existing_filename = self._find_existing_download( download_dir, output_title, mode) if existing_filename is not None: filename = existing_filename else: # See ydl_opts['forcejson'] self._expect_info_dict_json = True self._load_playlist(temp_download_dir, info_path=info_path) if self._expect_info_dict_json: raise RuntimeError('info_dict not received') # Move finished download from download to target dir temp_filename = self._info_dict['_filename'] if mode == 'audio': temp_filename = ( os.path.splitext(temp_filename)[0] + '.mp3') output_ext = os.path.splitext(temp_filename)[1] filename = output_title + output_ext try: os.replace( os.path.join(temp_download_dir, temp_filename), os.path.join(download_dir, filename)) except OSError as e: self._handler.on_error( ('ERROR: Falied to move finished download to ' 'download folder: %s') % e) raise # Delete download directory with contextlib.suppress(OSError): shutil.rmtree(temp_download_dir) self._handler.on_progress_end(filename) self._info_dict = None
def __init__(self): self._handler = Handler() # See ydl_opts['forcejson'] self._expect_info_dict_json = False self._info_dict = None self._allow_authentication_request = True self._skip_authentication = False self._skipped_count = 0 self.ydl_opts = { 'logger': self, 'logtostderr': True, 'no_color': True, 'progress_hooks': [self._on_progress], 'fixup': 'detect_or_warn', 'ignoreerrors': True, # handled via logger error callback 'retries': 10, 'fragment_retries': 10, 'postprocessors': [{'key': 'XAttrMetadata'}]} url = self._handler.get_url() target_dir = os.path.abspath(self._handler.get_target_dir()) with tempfile.TemporaryDirectory() as temp_dir: self.ydl_opts['cookiefile'] = os.path.join(temp_dir, 'cookies') # Collect info without downloading videos testplaylist_dir = os.path.join(temp_dir, 'testplaylist') noplaylist_dir = os.path.join(temp_dir, 'noplaylist') fullplaylist_dir = os.path.join(temp_dir, 'fullplaylist') for path in [testplaylist_dir, noplaylist_dir, fullplaylist_dir]: os.mkdir(path) self.ydl_opts['writeinfojson'] = True self.ydl_opts['writethumbnail'] = True self.ydl_opts['skip_download'] = True self.ydl_opts['playlistend'] = 2 self.ydl_opts['outtmpl'] = '%(autonumber)s.%(ext)s' # Test playlist info_testplaylist, skipped_testplaylist = self._load_playlist( testplaylist_dir, url) self.ydl_opts['noplaylist'] = True if len(info_testplaylist) + skipped_testplaylist > 1: info_noplaylist, skipped_noplaylist = self._load_playlist( noplaylist_dir, url) else: info_noplaylist = info_testplaylist skipped_noplaylist = skipped_testplaylist del self.ydl_opts['noplaylist'] del self.ydl_opts['playlistend'] if (len(info_testplaylist) + skipped_testplaylist > len(info_noplaylist) + skipped_noplaylist): self.ydl_opts['noplaylist'] = ( not self._handler.on_playlist_request()) if not self.ydl_opts['noplaylist']: info_playlist, _ = self._load_playlist( fullplaylist_dir, url) else: info_playlist = info_noplaylist elif len(info_testplaylist) + skipped_testplaylist > 1: info_playlist, _ = self._load_playlist(fullplaylist_dir, url) else: info_playlist = info_testplaylist # Download videos self._allow_authentication_request = False del self.ydl_opts['writeinfojson'] del self.ydl_opts['writethumbnail'] del self.ydl_opts['skip_download'] self.ydl_opts['outtmpl'] = OUTPUT_TEMPLATE # Output info_dict as JSON handled via logger debug callback self.ydl_opts['forcejson'] = True mode = self._handler.get_mode() if mode == 'audio': resolution = MAX_RESOLUTION prefer_mpeg = False self.ydl_opts['format'] = 'bestaudio/best' self.ydl_opts['postprocessors'].insert(0, { 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192'}) self.ydl_opts['postprocessors'].insert(1, { 'key': 'EmbedThumbnail', 'already_have_thumbnail': True}) else: resolution = self._handler.get_resolution() prefer_mpeg = self._handler.get_prefer_mpeg() os.makedirs(target_dir, exist_ok=True) for i, info_path in enumerate(info_playlist): with open(info_path) as f: info = json.load(f) title = info.get('title', info.get('id', 'video')) thumbnail_paths = list(filter( lambda p: os.path.splitext(p)[1][1:] != 'json', glob.iglob( glob.escape(info_path[:-len('info.json')]) + '*'))) thumbnail_path = thumbnail_paths[0] if thumbnail_paths else '' if thumbnail_path: # Convert thumbnail to JPEG and limit resolution new_thumbnail_path = thumbnail_path + '-converted.jpg' try: subprocess.run( [CONVERT_EXE, '-alpha', 'remove', os.path.abspath(thumbnail_path), '-resize', '{0}>x{0}>'.format(MAX_THUMBNAIL_RESOLUTION), os.path.abspath(new_thumbnail_path)], check=True, stdin=subprocess.DEVNULL, stdout=sys.stderr) except FileNotFoundError: self._handler.on_error( 'ERROR: %r not found. Please install ImageMagick.' % CONVERT_EXE) raise except subprocess.CalledProcessError: traceback.print_exc(file=sys.stderr) new_thumbnail_path = '' thumbnail_path = new_thumbnail_path self._handler.on_progress_start(i, len(info_playlist), title, thumbnail_path) for thumbnail in info.get('thumbnails') or []: thumbnail['filename'] = thumbnail_path sort_formats(info.get('formats') or [], resolution, prefer_mpeg) # Limit length of title in output file name for i in range(len(title), -1, -1): info['output_title'] = title[:i] if i < len(title): info['output_title'] += '…' # Check length with file system encoding if (len(info['output_title'].encode( sys.getfilesystemencoding(), 'ignore')) < MAX_OUTPUT_TITLE_LENGTH): break with open(info_path, 'w') as f: json.dump(info, f) # See ydl_opts['forcejson'] self._expect_info_dict_json = True self._load_playlist(target_dir, info_path=info_path) if self._expect_info_dict_json: raise RuntimeError('info_dict not received') filename = self._info_dict['_filename'] if mode == 'audio': filename = os.path.splitext(filename)[0] + '.mp3' self._handler.on_progress_end(filename) self._info_dict = None
def __init__(self): self.handler = Handler() ydl_opts = { "logger": self, "logtostderr": True, "no_color": True, "progress_hooks": [self.on_progress], "writeinfojson": True, "skip_download": True, "playlistend": 2, "outtmpl": "%(autonumber)s.%(ext)s", "fixup": "detect_or_warn" } url = self.handler.get_url() target_dir = os.path.abspath(self.handler.get_target_dir()) with tempfile.TemporaryDirectory() as temp_dir: ydl_opts["cookiefile"] = os.path.join(temp_dir, "cookies") testplaylist_dir = os.path.join(temp_dir, "testplaylist") noplaylist_dir = os.path.join(temp_dir, "noplaylist") fullplaylist_dir = os.path.join(temp_dir, "fullplaylist") for path in [testplaylist_dir, noplaylist_dir, fullplaylist_dir]: os.mkdir(path) # Test playlist info_testplaylist = self.load_playlist(testplaylist_dir, ydl_opts, url) ydl_opts["noplaylist"] = True if len(info_testplaylist) > 1: info_noplaylist = self.load_playlist(noplaylist_dir, ydl_opts, url) else: info_noplaylist = info_testplaylist del ydl_opts["noplaylist"] del ydl_opts["playlistend"] if len(info_testplaylist) > len(info_noplaylist): ydl_opts["noplaylist"] = not self.handler.on_playlist_request() if not ydl_opts["noplaylist"]: info_playlist = self.load_playlist(fullplaylist_dir, ydl_opts, url) else: info_playlist = info_noplaylist elif len(info_testplaylist) > 1: info_playlist = self.load_playlist(fullplaylist_dir, ydl_opts, url) else: info_playlist = info_testplaylist del ydl_opts["writeinfojson"] del ydl_opts["skip_download"] del ydl_opts["outtmpl"] mode = self.handler.get_mode() resolution = self.handler.get_resolution() if mode == "audio": resolution = MAX_RESOLUTION ydl_opts.update({ "format": "bestaudio/best", "postprocessors": [{ "key": "FFmpegExtractAudio", "preferredcodec": "mp3", "preferredquality": "192", }] }) os.makedirs(target_dir, exist_ok=True) for i, info_path in enumerate(info_playlist): self.handler.on_playlist_progress(i, len(info_playlist)) with open(info_path) as f: info = json.load(f) sort_formats(info["formats"], resolution=resolution) with open(info_path, "w") as f: json.dump(info, f) self.load_playlist(target_dir, ydl_opts, info_path=info_path)