diff options
Diffstat (limited to '')
-rw-r--r-- | yt_dlp/postprocessor/__init__.py | 2 | ||||
-rw-r--r-- | yt_dlp/postprocessor/common.py | 6 | ||||
-rw-r--r-- | yt_dlp/postprocessor/embedthumbnail.py | 37 | ||||
-rw-r--r-- | yt_dlp/postprocessor/exec.py | 5 | ||||
-rw-r--r-- | yt_dlp/postprocessor/ffmpeg.py | 60 | ||||
-rw-r--r-- | yt_dlp/postprocessor/modify_chapters.py | 2 | ||||
-rw-r--r-- | yt_dlp/postprocessor/movefilesafterdownload.py | 7 | ||||
-rw-r--r-- | yt_dlp/postprocessor/sponskrub.py | 4 | ||||
-rw-r--r-- | yt_dlp/postprocessor/sponsorblock.py | 10 |
9 files changed, 69 insertions, 64 deletions
diff --git a/yt_dlp/postprocessor/__init__.py b/yt_dlp/postprocessor/__init__.py index bfe9df7..164540b 100644 --- a/yt_dlp/postprocessor/__init__.py +++ b/yt_dlp/postprocessor/__init__.py @@ -43,5 +43,5 @@ def get_postprocessor(key): globals().update(_PLUGIN_CLASSES) -__all__ = [name for name in globals().keys() if name.endswith('PP')] +__all__ = [name for name in globals() if name.endswith('PP')] __all__.extend(('PostProcessor', 'FFmpegPostProcessor')) diff --git a/yt_dlp/postprocessor/common.py b/yt_dlp/postprocessor/common.py index 8cef86c..eeeece8 100644 --- a/yt_dlp/postprocessor/common.py +++ b/yt_dlp/postprocessor/common.py @@ -65,7 +65,7 @@ class PostProcessor(metaclass=PostProcessorMetaClass): def to_screen(self, text, prefix=True, *args, **kwargs): if self._downloader: - tag = '[%s] ' % self.PP_NAME if prefix else '' + tag = f'[{self.PP_NAME}] ' if prefix else '' return self._downloader.to_screen(f'{tag}{text}', *args, **kwargs) def report_warning(self, text, *args, **kwargs): @@ -127,7 +127,7 @@ class PostProcessor(metaclass=PostProcessorMetaClass): if allowed[format_type]: return func(self, info) else: - self.to_screen('Skipping %s' % format_type) + self.to_screen(f'Skipping {format_type}') return [], info return wrapper return decorator @@ -174,7 +174,7 @@ class PostProcessor(metaclass=PostProcessorMetaClass): self._progress_hooks.append(ph) def report_progress(self, s): - s['_default_template'] = '%(postprocessor)s %(status)s' % s + s['_default_template'] = '%(postprocessor)s %(status)s' % s # noqa: UP031 if not self._downloader: return diff --git a/yt_dlp/postprocessor/embedthumbnail.py b/yt_dlp/postprocessor/embedthumbnail.py index 9c53729..f2228ac 100644 --- a/yt_dlp/postprocessor/embedthumbnail.py +++ b/yt_dlp/postprocessor/embedthumbnail.py @@ -13,7 +13,6 @@ from ..utils import ( check_executable, encodeArgument, encodeFilename, - error_to_compat_str, prepend_extension, shell_quote, ) @@ -48,7 +47,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor): if mobj is None: return guess() except PostProcessingError as err: - self.report_warning('unable to find the thumbnail resolution; %s' % error_to_compat_str(err)) + self.report_warning(f'unable to find the thumbnail resolution; {err}') return guess() return int(mobj.group('w')), int(mobj.group('h')) @@ -104,12 +103,12 @@ class EmbedThumbnailPP(FFmpegPostProcessor): old_stream, new_stream = self.get_stream_number( filename, ('tags', 'mimetype'), mimetype) if old_stream is not None: - options.extend(['-map', '-0:%d' % old_stream]) + options.extend(['-map', f'-0:{old_stream}']) new_stream -= 1 options.extend([ '-attach', self._ffmpeg_filename_argument(thumbnail_filename), - '-metadata:s:%d' % new_stream, 'mimetype=%s' % mimetype, - '-metadata:s:%d' % new_stream, 'filename=cover.%s' % thumbnail_ext]) + f'-metadata:s:{new_stream}', f'mimetype={mimetype}', + f'-metadata:s:{new_stream}', f'filename=cover.{thumbnail_ext}']) self._report_run('ffmpeg', filename) self.run_ffmpeg(filename, temp_filename, options) @@ -120,19 +119,26 @@ class EmbedThumbnailPP(FFmpegPostProcessor): if not mutagen or prefer_atomicparsley: success = False else: + self._report_run('mutagen', filename) + f = {'jpeg': MP4Cover.FORMAT_JPEG, 'png': MP4Cover.FORMAT_PNG} try: - self._report_run('mutagen', filename) + with open(thumbnail_filename, 'rb') as thumbfile: + thumb_data = thumbfile.read() + + type_ = imghdr.what(h=thumb_data) + if not type_: + raise ValueError('could not determine image type') + elif type_ not in f: + raise ValueError(f'incompatible image type: {type_}') + meta = MP4(filename) # NOTE: the 'covr' atom is a non-standard MPEG-4 atom, # Apple iTunes 'M4A' files include the 'moov.udta.meta.ilst' atom. - f = {'jpeg': MP4Cover.FORMAT_JPEG, 'png': MP4Cover.FORMAT_PNG}[imghdr.what(thumbnail_filename)] - with open(thumbnail_filename, 'rb') as thumbfile: - thumb_data = thumbfile.read() meta.tags['covr'] = [MP4Cover(data=thumb_data, imageformat=f)] meta.save() temp_filename = filename except Exception as err: - self.report_warning('unable to embed using mutagen; %s' % error_to_compat_str(err)) + self.report_warning(f'unable to embed using mutagen; {err}') success = False # Method 2: Use AtomicParsley @@ -157,13 +163,14 @@ class EmbedThumbnailPP(FFmpegPostProcessor): cmd += [encodeArgument(o) for o in self._configuration_args('AtomicParsley')] self._report_run('atomicparsley', filename) - self.write_debug('AtomicParsley command line: %s' % shell_quote(cmd)) + self.write_debug(f'AtomicParsley command line: {shell_quote(cmd)}') stdout, stderr, returncode = Popen.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if returncode: self.report_warning(f'Unable to embed thumbnails using AtomicParsley; {stderr.strip()}') + success = False # for formats that don't support thumbnails (like 3gp) AtomicParsley # won't create to the temporary file - if 'No changes' in stdout: + elif 'No changes' in stdout: self.report_warning('The file format doesn\'t support embedding a thumbnail') success = False @@ -178,9 +185,9 @@ class EmbedThumbnailPP(FFmpegPostProcessor): old_stream, new_stream = self.get_stream_number( filename, ('disposition', 'attached_pic'), 1) if old_stream is not None: - options.extend(['-map', '-0:%d' % old_stream]) + options.extend(['-map', f'-0:{old_stream}']) new_stream -= 1 - options.extend(['-disposition:%s' % new_stream, 'attached_pic']) + options.extend([f'-disposition:{new_stream}', 'attached_pic']) self._report_run('ffmpeg', filename) self.run_ffmpeg_multiple_files([filename, thumbnail_filename], temp_filename, options) @@ -196,7 +203,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor): f = {'opus': OggOpus, 'flac': FLAC, 'ogg': OggVorbis}[info['ext']](filename) pic = Picture() - pic.mime = 'image/%s' % imghdr.what(thumbnail_filename) + pic.mime = f'image/{imghdr.what(thumbnail_filename)}' with open(thumbnail_filename, 'rb') as thumbfile: pic.data = thumbfile.read() pic.type = 3 # front cover diff --git a/yt_dlp/postprocessor/exec.py b/yt_dlp/postprocessor/exec.py index c2e73fb..1f0a001 100644 --- a/yt_dlp/postprocessor/exec.py +++ b/yt_dlp/postprocessor/exec.py @@ -1,6 +1,5 @@ from .common import PostProcessor -from ..compat import compat_shlex_quote -from ..utils import Popen, PostProcessingError, variadic +from ..utils import Popen, PostProcessingError, shell_quote, variadic class ExecPP(PostProcessor): @@ -19,7 +18,7 @@ class ExecPP(PostProcessor): if filepath: if '{}' not in cmd: cmd += ' {}' - cmd = cmd.replace('{}', compat_shlex_quote(filepath)) + cmd = cmd.replace('{}', shell_quote(filepath)) return cmd def run(self, info): diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py index 7d7f3f0..164c46d 100644 --- a/yt_dlp/postprocessor/ffmpeg.py +++ b/yt_dlp/postprocessor/ffmpeg.py @@ -1,5 +1,6 @@ import collections import contextvars +import functools import itertools import json import os @@ -8,7 +9,7 @@ import subprocess import time from .common import PostProcessor -from ..compat import functools, imghdr +from ..compat import imghdr from ..utils import ( MEDIA_EXTENSIONS, ISO639Utils, @@ -61,7 +62,7 @@ ACODECS = { def create_mapping_re(supported): - return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:%s)\s*' % '|'.join(supported))) + return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:{})\s*'.format('|'.join(supported)))) def resolve_mapping(source, mapping): @@ -119,7 +120,7 @@ class FFmpegPostProcessor(PostProcessor): filename = os.path.basename(location) basename = next((p for p in programs if p in filename), 'ffmpeg') dirname = os.path.dirname(os.path.abspath(location)) - if basename in self._ffmpeg_to_avconv.keys(): + if basename in self._ffmpeg_to_avconv: self._prefer_ffmpeg = True paths = {p: os.path.join(dirname, p) for p in programs} @@ -169,12 +170,12 @@ class FFmpegPostProcessor(PostProcessor): @functools.cached_property def basename(self): - self._version # run property + _ = self._version # run property return self.basename @functools.cached_property def probe_basename(self): - self._probe_version # run property + _ = self._probe_version # run property return self.probe_basename def _get_version(self, kind): @@ -342,7 +343,7 @@ class FFmpegPostProcessor(PostProcessor): cmd += [encodeArgument('-loglevel'), encodeArgument('repeat+info')] def make_args(file, args, name, number): - keys = ['_%s%d' % (name, number), '_%s' % name] + keys = [f'_{name}{number}', f'_{name}'] if name == 'o': args += ['-movflags', '+faststart'] if number == 1: @@ -359,7 +360,7 @@ class FFmpegPostProcessor(PostProcessor): make_args(path, list(opts), arg_type, i + 1) for i, (path, opts) in enumerate(path_opts) if path) - self.write_debug('ffmpeg command line: %s' % shell_quote(cmd)) + self.write_debug(f'ffmpeg command line: {shell_quote(cmd)}') _, stderr, returncode = Popen.run( cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) if returncode not in variadic(expected_retcodes): @@ -437,7 +438,7 @@ class FFmpegPostProcessor(PostProcessor): class FFmpegExtractAudioPP(FFmpegPostProcessor): - COMMON_AUDIO_EXTS = MEDIA_EXTENSIONS.common_audio + ('wma', ) + COMMON_AUDIO_EXTS = (*MEDIA_EXTENSIONS.common_audio, 'wma') SUPPORTED_EXTS = tuple(ACODECS.keys()) FORMAT_RE = create_mapping_re(('best', *SUPPORTED_EXTS)) @@ -474,7 +475,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): acodec_opts = [] else: acodec_opts = ['-acodec', codec] - opts = ['-vn'] + acodec_opts + more_opts + opts = ['-vn', *acodec_opts, *more_opts] try: FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts) except FFmpegPostProcessorError as err: @@ -523,7 +524,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): temp_path = prepend_extension(path, 'temp') if (self._nopostoverwrites and os.path.exists(encodeFilename(new_path)) and os.path.exists(encodeFilename(orig_path))): - self.to_screen('Post-process file %s exists, skipping' % new_path) + self.to_screen(f'Post-process file {new_path} exists, skipping') return [], information self.to_screen(f'Destination: {new_path}') @@ -641,7 +642,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor): if not sub_langs: return [], info - input_files = [filename] + sub_filenames + input_files = [filename, *sub_filenames] opts = [ *self.stream_copy_opts(ext=info['ext']), @@ -650,15 +651,15 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor): '-map', '-0:s', ] for i, (lang, name) in enumerate(zip(sub_langs, sub_names)): - opts.extend(['-map', '%d:0' % (i + 1)]) + opts.extend(['-map', f'{i + 1}:0']) lang_code = ISO639Utils.short2long(lang) or lang - opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code]) + opts.extend([f'-metadata:s:s:{i}', f'language={lang_code}']) if name: - opts.extend(['-metadata:s:s:%d' % i, 'handler_name=%s' % name, - '-metadata:s:s:%d' % i, 'title=%s' % name]) + opts.extend([f'-metadata:s:s:{i}', f'handler_name={name}', + f'-metadata:s:s:{i}', f'title={name}']) temp_filename = prepend_extension(filename, 'temp') - self.to_screen('Embedding subtitles in "%s"' % filename) + self.to_screen(f'Embedding subtitles in "{filename}"') self.run_ffmpeg_multiple_files(input_files, temp_filename, opts) os.replace(temp_filename, filename) @@ -707,7 +708,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor): return [], info temp_filename = prepend_extension(filename, 'temp') - self.to_screen('Adding metadata to "%s"' % filename) + self.to_screen(f'Adding metadata to "{filename}"') self.run_ffmpeg_multiple_files( (filename, metadata_filename), temp_filename, itertools.chain(self._options(info['ext']), *options)) @@ -728,7 +729,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor): metadata_file_content += 'END=%d\n' % (chapter['end_time'] * 1000) chapter_title = chapter.get('title') if chapter_title: - metadata_file_content += 'title=%s\n' % ffmpeg_escape(chapter_title) + metadata_file_content += f'title={ffmpeg_escape(chapter_title)}\n' f.write(metadata_file_content) yield ('-map_metadata', '1') @@ -738,7 +739,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor): def add(meta_list, info_list=None): value = next(( - info[key] for key in [f'{meta_prefix}_'] + list(variadic(info_list or meta_list)) + info[key] for key in [f'{meta_prefix}_', *variadic(info_list or meta_list)] if info.get(key) is not None), None) if value not in ('', None): value = ', '.join(map(str, variadic(value))) @@ -807,7 +808,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor): old_stream, new_stream = self.get_stream_number(info['filepath'], ('tags', 'mimetype'), 'application/json') if old_stream is not None: - yield ('-map', '-0:%d' % old_stream) + yield ('-map', f'-0:{old_stream}') new_stream -= 1 yield ( @@ -834,8 +835,8 @@ class FFmpegMergerPP(FFmpegPostProcessor): args.extend([f'-bsf:a:{audio_streams}', 'aac_adtstoasc']) audio_streams += 1 if fmt.get('vcodec') != 'none': - args.extend(['-map', '%u:v:0' % (i)]) - self.to_screen('Merging formats into "%s"' % filename) + args.extend(['-map', f'{i}:v:0']) + self.to_screen(f'Merging formats into "{filename}"') self.run_ffmpeg_multiple_files(info['__files_to_merge'], temp_filename, args) os.rename(encodeFilename(temp_filename), encodeFilename(filename)) return info['__files_to_merge'], info @@ -848,10 +849,9 @@ class FFmpegMergerPP(FFmpegPostProcessor): required_version = '10-0' if is_outdated_version( self._versions[self.basename], required_version): - warning = ('Your copy of %s is outdated and unable to properly mux separate video and audio files, ' + warning = (f'Your copy of {self.basename} is outdated and unable to properly mux separate video and audio files, ' 'yt-dlp will download single file media. ' - 'Update %s to version %s or newer to fix this.') % ( - self.basename, self.basename, required_version) + f'Update {self.basename} to version {required_version} or newer to fix this.') self.report_warning(warning) return False return True @@ -873,7 +873,7 @@ class FFmpegFixupStretchedPP(FFmpegFixupPostProcessor): stretched_ratio = info.get('stretched_ratio') if stretched_ratio not in (None, 1): self._fixup('Fixing aspect ratio', info['filepath'], [ - *self.stream_copy_opts(), '-aspect', '%f' % stretched_ratio]) + *self.stream_copy_opts(), '-aspect', f'{stretched_ratio:f}']) return [], info @@ -925,7 +925,7 @@ class FFmpegFixupTimestampPP(FFmpegFixupPostProcessor): opts = ['-vf', 'setpts=PTS-STARTPTS'] else: opts = ['-c', 'copy', '-bsf', 'setts=ts=TS-STARTPTS'] - self._fixup('Fixing frame timestamp', info['filepath'], opts + [*self.stream_copy_opts(False), '-ss', self.trim]) + self._fixup('Fixing frame timestamp', info['filepath'], [*opts, *self.stream_copy_opts(False), '-ss', self.trim]) return [], info @@ -970,7 +970,7 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor): continue ext = sub['ext'] if ext == new_ext: - self.to_screen('Subtitle file for %s is already in the requested format' % new_ext) + self.to_screen(f'Subtitle file for {new_ext} is already in the requested format') continue elif ext == 'json': self.to_screen( @@ -1060,7 +1060,7 @@ class FFmpegSplitChaptersPP(FFmpegPostProcessor): in_file = info['filepath'] if self._force_keyframes and len(chapters) > 1: in_file = self.force_keyframes(in_file, (c['start_time'] for c in chapters)) - self.to_screen('Splitting video by chapters; %d chapters found' % len(chapters)) + self.to_screen(f'Splitting video by chapters; {len(chapters)} chapters found') for idx, chapter in enumerate(chapters): destination, opts = self._ffmpeg_args_for_chapter(idx + 1, chapter, info) self.real_run_ffmpeg([(in_file, opts)], [(destination, self.stream_copy_opts())]) @@ -1087,7 +1087,7 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor): _, thumbnail_ext = os.path.splitext(thumbnail_filename) if thumbnail_ext: if thumbnail_ext.lower() != '.webp' and imghdr.what(thumbnail_filename) == 'webp': - self.to_screen('Correcting thumbnail "%s" extension to webp' % thumbnail_filename) + self.to_screen(f'Correcting thumbnail "{thumbnail_filename}" extension to webp') webp_filename = replace_extension(thumbnail_filename, 'webp') os.replace(thumbnail_filename, webp_filename) info['thumbnails'][idx]['filepath'] = webp_filename diff --git a/yt_dlp/postprocessor/modify_chapters.py b/yt_dlp/postprocessor/modify_chapters.py index f521986..d82685e 100644 --- a/yt_dlp/postprocessor/modify_chapters.py +++ b/yt_dlp/postprocessor/modify_chapters.py @@ -54,7 +54,7 @@ class ModifyChaptersPP(FFmpegPostProcessor): self.write_debug('Expected and actual durations mismatch') concat_opts = self._make_concat_opts(cuts, real_duration) - self.write_debug('Concat spec = %s' % ', '.join(f'{c.get("inpoint", 0.0)}-{c.get("outpoint", "inf")}' for c in concat_opts)) + self.write_debug('Concat spec = {}'.format(', '.join(f'{c.get("inpoint", 0.0)}-{c.get("outpoint", "inf")}' for c in concat_opts))) def remove_chapters(file, is_sub): return file, self.remove_chapters(file, cuts, concat_opts, self._force_keyframes and not is_sub) diff --git a/yt_dlp/postprocessor/movefilesafterdownload.py b/yt_dlp/postprocessor/movefilesafterdownload.py index 23b0924..35e8705 100644 --- a/yt_dlp/postprocessor/movefilesafterdownload.py +++ b/yt_dlp/postprocessor/movefilesafterdownload.py @@ -34,16 +34,15 @@ class MoveFilesAfterDownloadPP(PostProcessor): if os.path.abspath(encodeFilename(oldfile)) == os.path.abspath(encodeFilename(newfile)): continue if not os.path.exists(encodeFilename(oldfile)): - self.report_warning('File "%s" cannot be found' % oldfile) + self.report_warning(f'File "{oldfile}" cannot be found') continue if os.path.exists(encodeFilename(newfile)): if self.get_param('overwrites', True): - self.report_warning('Replacing existing file "%s"' % newfile) + self.report_warning(f'Replacing existing file "{newfile}"') os.remove(encodeFilename(newfile)) else: self.report_warning( - 'Cannot move file "%s" out of temporary directory since "%s" already exists. ' - % (oldfile, newfile)) + f'Cannot move file "{oldfile}" out of temporary directory since "{newfile}" already exists. ') continue make_dir(newfile, PostProcessingError) self.to_screen(f'Moving file "{oldfile}" to "{newfile}"') diff --git a/yt_dlp/postprocessor/sponskrub.py b/yt_dlp/postprocessor/sponskrub.py index ff50d5b..525b639 100644 --- a/yt_dlp/postprocessor/sponskrub.py +++ b/yt_dlp/postprocessor/sponskrub.py @@ -35,7 +35,7 @@ class SponSkrubPP(PostProcessor): if not ignoreerror and self.path is None: if path: - raise PostProcessingError('sponskrub not found in "%s"' % path) + raise PostProcessingError(f'sponskrub not found in "{path}"') else: raise PostProcessingError('sponskrub not found. Please install or provide the path using --sponskrub-path') @@ -83,7 +83,7 @@ class SponSkrubPP(PostProcessor): cmd += ['--', information['id'], filename, temp_filename] cmd = [encodeArgument(i) for i in cmd] - self.write_debug('sponskrub command line: %s' % shell_quote(cmd)) + self.write_debug(f'sponskrub command line: {shell_quote(cmd)}') stdout, _, returncode = Popen.run(cmd, text=True, stdout=None if self.get_param('verbose') else subprocess.PIPE) if not returncode: diff --git a/yt_dlp/postprocessor/sponsorblock.py b/yt_dlp/postprocessor/sponsorblock.py index 6ba87cd..6cf9ab6 100644 --- a/yt_dlp/postprocessor/sponsorblock.py +++ b/yt_dlp/postprocessor/sponsorblock.py @@ -27,7 +27,7 @@ class SponsorBlockPP(FFmpegPostProcessor): 'filler': 'Filler Tangent', 'interaction': 'Interaction Reminder', 'music_offtopic': 'Non-Music Section', - **NON_SKIPPABLE_CATEGORIES + **NON_SKIPPABLE_CATEGORIES, } def __init__(self, downloader, categories=None, api='https://sponsor.ajay.app'): @@ -57,7 +57,7 @@ class SponsorBlockPP(FFmpegPostProcessor): if start_end[0] <= 1: start_end[0] = 0 # Make POI chapters 1 sec so that we can properly mark them - if s['category'] in self.POI_CATEGORIES.keys(): + if s['category'] in self.POI_CATEGORIES: start_end[1] += 1 # Ignore milliseconds difference at the end. # Never allow the segment to exceed the video. @@ -91,12 +91,12 @@ class SponsorBlockPP(FFmpegPostProcessor): return sponsor_chapters def _get_sponsor_segments(self, video_id, service): - hash = hashlib.sha256(video_id.encode('ascii')).hexdigest() + video_hash = hashlib.sha256(video_id.encode('ascii')).hexdigest() # SponsorBlock API recommends using first 4 hash characters. - url = f'{self._API_URL}/api/skipSegments/{hash[:4]}?' + urllib.parse.urlencode({ + url = f'{self._API_URL}/api/skipSegments/{video_hash[:4]}?' + urllib.parse.urlencode({ 'service': service, 'categories': json.dumps(self._categories), - 'actionTypes': json.dumps(['skip', 'poi', 'chapter']) + 'actionTypes': json.dumps(['skip', 'poi', 'chapter']), }) for d in self._download_json(url) or []: if d['videoID'] == video_id: |