Add option --embed-info-json to embed info-json in mkv

Closes #1644
This commit is contained in:
pukkandan 2021-11-15 04:03:41 +05:30
parent f279aaee8e
commit dac5df5a98
No known key found for this signature in database
GPG Key ID: 0F00D95A001F4698
4 changed files with 60 additions and 17 deletions

View File

@ -431,7 +431,7 @@ class YoutubeDL(object):
compat_opts: Compatibility options. See "Differences in default behavior". compat_opts: Compatibility options. See "Differences in default behavior".
The following options do not work when used through the API: The following options do not work when used through the API:
filename, abort-on-error, multistreams, no-live-chat, format-sort filename, abort-on-error, multistreams, no-live-chat, format-sort
no-clean-infojson, no-playlist-metafiles, no-keep-subs. no-clean-infojson, no-playlist-metafiles, no-keep-subs, no-attach-info-json.
Refer __init__.py for their implementation Refer __init__.py for their implementation
progress_template: Dictionary of templates for progress outputs. progress_template: Dictionary of templates for progress outputs.
Allowed keys are 'download', 'postprocess', Allowed keys are 'download', 'postprocess',
@ -2654,6 +2654,8 @@ def process_info(self, info_dict):
infofn = self.prepare_filename(info_dict, 'infojson') infofn = self.prepare_filename(info_dict, 'infojson')
_infojson_written = self._write_info_json('video', info_dict, infofn) _infojson_written = self._write_info_json('video', info_dict, infofn)
if _infojson_written: if _infojson_written:
info_dict['infojson_filename'] = infofn
# For backward compatability, even though it was a private field
info_dict['__infojson_filename'] = infofn info_dict['__infojson_filename'] = infofn
elif _infojson_written is None: elif _infojson_written is None:
return return
@ -3012,8 +3014,8 @@ def sanitize_info(info_dict, remove_private_keys=False):
keep_keys = ['_type'] # Always keep this to facilitate load-info-json keep_keys = ['_type'] # Always keep this to facilitate load-info-json
if remove_private_keys: if remove_private_keys:
remove_keys |= { remove_keys |= {
'requested_formats', 'requested_subtitles', 'requested_entries', 'requested_formats', 'requested_subtitles', 'requested_entries', 'entries',
'filepath', 'entries', 'original_url', 'playlist_autonumber', 'filepath', 'infojson_filename', 'original_url', 'playlist_autonumber',
} }
empty_values = (None, {}, [], set(), tuple()) empty_values = (None, {}, [], set(), tuple())
reject = lambda k, v: k not in keep_keys and ( reject = lambda k, v: k not in keep_keys and (

View File

@ -290,6 +290,11 @@ def set_default_compat(compat_name, opt_name, default=True, remove_compat=True):
set_default_compat('abort-on-error', 'ignoreerrors', 'only_download') set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
set_default_compat('no-playlist-metafiles', 'allow_playlist_files') set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
set_default_compat('no-clean-infojson', 'clean_infojson') set_default_compat('no-clean-infojson', 'clean_infojson')
if 'no-attach-info-json' in compat_opts:
if opts.embed_infojson:
_unused_compat_opt('no-attach-info-json')
else:
opts.embed_infojson = False
if 'format-sort' in compat_opts: if 'format-sort' in compat_opts:
opts.format_sort.extend(InfoExtractor.FormatSort.ytdl_default) opts.format_sort.extend(InfoExtractor.FormatSort.ytdl_default)
_video_multistreams_set = set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat=False) _video_multistreams_set = set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat=False)
@ -526,11 +531,14 @@ def metadataparser_actions(f):
# By default ffmpeg preserves metadata applicable for both # By default ffmpeg preserves metadata applicable for both
# source and target containers. From this point the container won't change, # source and target containers. From this point the container won't change,
# so metadata can be added here. # so metadata can be added here.
if opts.addmetadata or opts.addchapters: if opts.addmetadata or opts.addchapters or opts.embed_infojson:
if opts.embed_infojson is None:
opts.embed_infojson = 'if_exists'
postprocessors.append({ postprocessors.append({
'key': 'FFmpegMetadata', 'key': 'FFmpegMetadata',
'add_chapters': opts.addchapters, 'add_chapters': opts.addchapters,
'add_metadata': opts.addmetadata, 'add_metadata': opts.addmetadata,
'add_infojson': opts.embed_infojson,
}) })
# Note: Deprecated # Note: Deprecated
# This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment

View File

@ -1287,7 +1287,9 @@ def _dict_from_options_callback(
postproc.add_option( postproc.add_option(
'--embed-metadata', '--add-metadata', '--embed-metadata', '--add-metadata',
action='store_true', dest='addmetadata', default=False, action='store_true', dest='addmetadata', default=False,
help='Embed metadata to the video file. Also adds chapters to file unless --no-add-chapters is used (Alias: --add-metadata)') help=(
'Embed metadata to the video file. Also embeds chapters/infojson if present '
'unless --no-embed-chapters/--no-embed-info-json are used (Alias: --add-metadata)'))
postproc.add_option( postproc.add_option(
'--no-embed-metadata', '--no-add-metadata', '--no-embed-metadata', '--no-add-metadata',
action='store_false', dest='addmetadata', action='store_false', dest='addmetadata',
@ -1300,6 +1302,14 @@ def _dict_from_options_callback(
'--no-embed-chapters', '--no-add-chapters', '--no-embed-chapters', '--no-add-chapters',
action='store_false', dest='addchapters', action='store_false', dest='addchapters',
help='Do not add chapter markers (default) (Alias: --no-add-chapters)') help='Do not add chapter markers (default) (Alias: --no-add-chapters)')
postproc.add_option(
'--embed-info-json',
action='store_true', dest='embed_infojson', default=None,
help='Embed the infojson as an attachment to mkv/mka video files')
postproc.add_option(
'--no-embed-info-json',
action='store_false', dest='embed_infojson',
help='Do not embed the infojson as an attachment to the video file')
postproc.add_option( postproc.add_option(
'--metadata-from-title', '--metadata-from-title',
metavar='FORMAT', dest='metafromtitle', metavar='FORMAT', dest='metafromtitle',

View File

@ -28,6 +28,7 @@
shell_quote, shell_quote,
traverse_obj, traverse_obj,
variadic, variadic,
write_json_file,
) )
@ -636,10 +637,11 @@ def run(self, information):
class FFmpegMetadataPP(FFmpegPostProcessor): class FFmpegMetadataPP(FFmpegPostProcessor):
def __init__(self, downloader, add_metadata=True, add_chapters=True): def __init__(self, downloader, add_metadata=True, add_chapters=True, add_infojson='if_exists'):
FFmpegPostProcessor.__init__(self, downloader) FFmpegPostProcessor.__init__(self, downloader)
self._add_metadata = add_metadata self._add_metadata = add_metadata
self._add_chapters = add_chapters self._add_chapters = add_chapters
self._add_infojson = add_infojson
@staticmethod @staticmethod
def _options(target_ext): def _options(target_ext):
@ -652,13 +654,23 @@ def _options(target_ext):
@PostProcessor._restrict_to(images=False) @PostProcessor._restrict_to(images=False)
def run(self, info): def run(self, info):
filename, metadata_filename = info['filepath'], None filename, metadata_filename = info['filepath'], None
options = [] files_to_delete, options = [], []
if self._add_chapters and info.get('chapters'): if self._add_chapters and info.get('chapters'):
metadata_filename = replace_extension(filename, 'meta') metadata_filename = replace_extension(filename, 'meta')
options.extend(self._get_chapter_opts(info['chapters'], metadata_filename)) options.extend(self._get_chapter_opts(info['chapters'], metadata_filename))
files_to_delete.append(metadata_filename)
if self._add_metadata: if self._add_metadata:
options.extend(self._get_metadata_opts(info)) options.extend(self._get_metadata_opts(info))
if self._add_infojson:
if info['ext'] in ('mkv', 'mka'):
infojson_filename = info.get('infojson_filename')
options.extend(self._get_infojson_opts(info, infojson_filename))
if not infojson_filename:
files_to_delete.append(info.get('infojson_filename'))
elif self._add_infojson is True:
self.to_screen('The info-json can only be attached to mkv/mka files')
if not options: if not options:
self.to_screen('There isn\'t any metadata to add') self.to_screen('There isn\'t any metadata to add')
return [], info return [], info
@ -668,8 +680,8 @@ def run(self, info):
self.run_ffmpeg_multiple_files( self.run_ffmpeg_multiple_files(
(filename, metadata_filename), temp_filename, (filename, metadata_filename), temp_filename,
itertools.chain(self._options(info['ext']), *options)) itertools.chain(self._options(info['ext']), *options))
if metadata_filename: for file in filter(None, files_to_delete):
os.remove(metadata_filename) os.remove(file) # Don't obey --keep-files
os.replace(temp_filename, filename) os.replace(temp_filename, filename)
return [], info return [], info
@ -741,15 +753,26 @@ def add(meta_list, info_list=None):
yield ('-metadata:s:%d' % (stream_idx + i), 'language=%s' % lang) yield ('-metadata:s:%d' % (stream_idx + i), 'language=%s' % lang)
stream_idx += stream_count stream_idx += stream_count
if ('no-attach-info-json' not in self.get_param('compat_opts', []) def _get_infojson_opts(self, info, infofn):
and '__infojson_filename' in info and info['ext'] in ('mkv', 'mka')): if not infofn or not os.path.exists(infofn):
old_stream, new_stream = self.get_stream_number(info['filepath'], ('tags', 'mimetype'), 'application/json') if self._add_infojson is not True:
if old_stream is not None: return
yield ('-map', '-0:%d' % old_stream) infofn = infofn or '%s.temp' % (
new_stream -= 1 self._downloader.prepare_filename(info, 'infojson')
or replace_extension(self._downloader.prepare_filename(info), 'info.json', info['ext']))
if not self._downloader._ensure_dir_exists(infofn):
return
self.write_debug(f'Writing info-json to: {infofn}')
write_json_file(self._downloader.sanitize_info(info, self.get_param('clean_infojson', True)), infofn)
info['infojson_filename'] = infofn
yield ('-attach', info['__infojson_filename'], old_stream, new_stream = self.get_stream_number(info['filepath'], ('tags', 'mimetype'), 'application/json')
'-metadata:s:%d' % new_stream, 'mimetype=application/json') if old_stream is not None:
yield ('-map', '-0:%d' % old_stream)
new_stream -= 1
yield ('-attach', infofn,
'-metadata:s:%d' % new_stream, 'mimetype=application/json')
class FFmpegMergerPP(FFmpegPostProcessor): class FFmpegMergerPP(FFmpegPostProcessor):