mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-12-14 23:32:33 +00:00
parent
5df1ac92bd
commit
3b603dbdf1
7 changed files with 87 additions and 12 deletions
11
README.md
11
README.md
|
@ -893,6 +893,15 @@ You can also fork the project on github and run your fork's [build workflow](.gi
|
||||||
multiple times
|
multiple times
|
||||||
--xattrs Write metadata to the video file's xattrs
|
--xattrs Write metadata to the video file's xattrs
|
||||||
(using dublin core and xdg standards)
|
(using dublin core and xdg standards)
|
||||||
|
--concat-playlist POLICY Concatenate videos in a playlist. One of
|
||||||
|
"never" (default), "always", or
|
||||||
|
"multi_video" (only when the videos form a
|
||||||
|
single show). All the video files must have
|
||||||
|
same codecs and number of streams to be
|
||||||
|
concatable. The "pl_video:" prefix can be
|
||||||
|
used with "--paths" and "--output" to set
|
||||||
|
the output filename for the split files.
|
||||||
|
See "OUTPUT TEMPLATE" for details
|
||||||
--fixup POLICY Automatically correct known faults of the
|
--fixup POLICY Automatically correct known faults of the
|
||||||
file. One of never (do nothing), warn (only
|
file. One of never (do nothing), warn (only
|
||||||
emit a warning), detect_or_warn (the
|
emit a warning), detect_or_warn (the
|
||||||
|
@ -1106,7 +1115,7 @@ To summarize, the general syntax for a field is:
|
||||||
%(name[.keys][addition][>strf][,alternate][&replacement][|default])[flags][width][.precision][length]type
|
%(name[.keys][addition][>strf][,alternate][&replacement][|default])[flags][width][.precision][length]type
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation` (deprecated), `infojson`, `link`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o "%(title)s.%(ext)s" -o "thumbnail:%(title)s\%(title)s.%(ext)s"` will put the thumbnails in a folder with the same name as the video. If any of the templates (except default) is empty, that type of file will not be written. Eg: `--write-thumbnail -o "thumbnail:"` will write thumbnails only for playlists and not for video.
|
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation` (deprecated), `infojson`, `link`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`, `pl_video`. For example, `-o "%(title)s.%(ext)s" -o "thumbnail:%(title)s\%(title)s.%(ext)s"` will put the thumbnails in a folder with the same name as the video. If any of the templates (except default) is empty, that type of file will not be written. Eg: `--write-thumbnail -o "thumbnail:"` will write thumbnails only for playlists and not for video.
|
||||||
|
|
||||||
The available fields are:
|
The available fields are:
|
||||||
|
|
||||||
|
|
|
@ -1596,6 +1596,19 @@ class YoutubeDL(object):
|
||||||
def _ensure_dir_exists(self, path):
|
def _ensure_dir_exists(self, path):
|
||||||
return make_dir(path, self.report_error)
|
return make_dir(path, self.report_error)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _playlist_infodict(ie_result, **kwargs):
|
||||||
|
return {
|
||||||
|
**ie_result,
|
||||||
|
'playlist': ie_result.get('title') or ie_result.get('id'),
|
||||||
|
'playlist_id': ie_result.get('id'),
|
||||||
|
'playlist_title': ie_result.get('title'),
|
||||||
|
'playlist_uploader': ie_result.get('uploader'),
|
||||||
|
'playlist_uploader_id': ie_result.get('uploader_id'),
|
||||||
|
'playlist_index': 0,
|
||||||
|
**kwargs,
|
||||||
|
}
|
||||||
|
|
||||||
def __process_playlist(self, ie_result, download):
|
def __process_playlist(self, ie_result, download):
|
||||||
# We process each entry in the playlist
|
# We process each entry in the playlist
|
||||||
playlist = ie_result.get('title') or ie_result.get('id')
|
playlist = ie_result.get('title') or ie_result.get('id')
|
||||||
|
@ -1695,17 +1708,7 @@ class YoutubeDL(object):
|
||||||
|
|
||||||
_infojson_written = False
|
_infojson_written = False
|
||||||
if not self.params.get('simulate') and self.params.get('allow_playlist_files', True):
|
if not self.params.get('simulate') and self.params.get('allow_playlist_files', True):
|
||||||
ie_copy = {
|
ie_copy = self._playlist_infodict(ie_result, n_entries=n_entries)
|
||||||
'playlist': playlist,
|
|
||||||
'playlist_id': ie_result.get('id'),
|
|
||||||
'playlist_title': ie_result.get('title'),
|
|
||||||
'playlist_uploader': ie_result.get('uploader'),
|
|
||||||
'playlist_uploader_id': ie_result.get('uploader_id'),
|
|
||||||
'playlist_index': 0,
|
|
||||||
'n_entries': n_entries,
|
|
||||||
}
|
|
||||||
ie_copy.update(dict(ie_result))
|
|
||||||
|
|
||||||
_infojson_written = self._write_info_json(
|
_infojson_written = self._write_info_json(
|
||||||
'playlist', ie_result, self.prepare_filename(ie_copy, 'pl_infojson'))
|
'playlist', ie_result, self.prepare_filename(ie_copy, 'pl_infojson'))
|
||||||
if _infojson_written is None:
|
if _infojson_written is None:
|
||||||
|
|
|
@ -591,6 +591,12 @@ def _real_main(argv=None):
|
||||||
# XAttrMetadataPP should be run after post-processors that may change file contents
|
# XAttrMetadataPP should be run after post-processors that may change file contents
|
||||||
if opts.xattrs:
|
if opts.xattrs:
|
||||||
postprocessors.append({'key': 'XAttrMetadata'})
|
postprocessors.append({'key': 'XAttrMetadata'})
|
||||||
|
if opts.concat_playlist != 'never':
|
||||||
|
postprocessors.append({
|
||||||
|
'key': 'FFmpegConcat',
|
||||||
|
'only_multi_video': opts.concat_playlist != 'always',
|
||||||
|
'when': 'playlist',
|
||||||
|
})
|
||||||
# Exec must be the last PP of each category
|
# Exec must be the last PP of each category
|
||||||
if opts.exec_before_dl_cmd:
|
if opts.exec_before_dl_cmd:
|
||||||
opts.exec_cmd.setdefault('before_dl', opts.exec_before_dl_cmd)
|
opts.exec_cmd.setdefault('before_dl', opts.exec_before_dl_cmd)
|
||||||
|
|
|
@ -1397,6 +1397,16 @@ def create_parser():
|
||||||
'--xattrs',
|
'--xattrs',
|
||||||
action='store_true', dest='xattrs', default=False,
|
action='store_true', dest='xattrs', default=False,
|
||||||
help='Write metadata to the video file\'s xattrs (using dublin core and xdg standards)')
|
help='Write metadata to the video file\'s xattrs (using dublin core and xdg standards)')
|
||||||
|
postproc.add_option(
|
||||||
|
'--concat-playlist',
|
||||||
|
metavar='POLICY', dest='concat_playlist', default='multi_video',
|
||||||
|
choices=('never', 'always', 'multi_video'),
|
||||||
|
help=(
|
||||||
|
'Concatenate videos in a playlist. One of "never" (default), "always", or '
|
||||||
|
'"multi_video" (only when the videos form a single show). '
|
||||||
|
'All the video files must have same codecs and number of streams to be concatable. '
|
||||||
|
'The "pl_video:" prefix can be used with "--paths" and "--output" to '
|
||||||
|
'set the output filename for the split files. See "OUTPUT TEMPLATE" for details'))
|
||||||
postproc.add_option(
|
postproc.add_option(
|
||||||
'--fixup',
|
'--fixup',
|
||||||
metavar='POLICY', dest='fixup', default=None,
|
metavar='POLICY', dest='fixup', default=None,
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .embedthumbnail import EmbedThumbnailPP
|
||||||
from .exec import ExecPP, ExecAfterDownloadPP
|
from .exec import ExecPP, ExecAfterDownloadPP
|
||||||
from .ffmpeg import (
|
from .ffmpeg import (
|
||||||
FFmpegPostProcessor,
|
FFmpegPostProcessor,
|
||||||
|
FFmpegConcatPP,
|
||||||
FFmpegEmbedSubtitlePP,
|
FFmpegEmbedSubtitlePP,
|
||||||
FFmpegExtractAudioPP,
|
FFmpegExtractAudioPP,
|
||||||
FFmpegFixupDuplicateMoovPP,
|
FFmpegFixupDuplicateMoovPP,
|
||||||
|
|
|
@ -1123,3 +1123,48 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
|
||||||
if not has_thumbnail:
|
if not has_thumbnail:
|
||||||
self.to_screen('There aren\'t any thumbnails to convert')
|
self.to_screen('There aren\'t any thumbnails to convert')
|
||||||
return files_to_delete, info
|
return files_to_delete, info
|
||||||
|
|
||||||
|
|
||||||
|
class FFmpegConcatPP(FFmpegPostProcessor):
|
||||||
|
def __init__(self, downloader, only_multi_video=False):
|
||||||
|
self._only_multi_video = only_multi_video
|
||||||
|
super().__init__(downloader)
|
||||||
|
|
||||||
|
def concat_files(self, in_files, out_file):
|
||||||
|
if len(in_files) == 1:
|
||||||
|
os.replace(in_files[0], out_file)
|
||||||
|
return
|
||||||
|
|
||||||
|
codecs = [traverse_obj(self.get_metadata_object(file), ('streams', ..., 'codec_name')) for file in in_files]
|
||||||
|
if len(set(map(tuple, codecs))) > 1:
|
||||||
|
raise PostProcessingError(
|
||||||
|
'The files have different streams/codecs and cannot be concatenated. '
|
||||||
|
'Either select different formats or --recode-video them to a common format')
|
||||||
|
super().concat_files(in_files, out_file)
|
||||||
|
|
||||||
|
@PostProcessor._restrict_to(images=False)
|
||||||
|
def run(self, info):
|
||||||
|
if not info.get('entries') or self._only_multi_video and info['_type'] != 'multi_video':
|
||||||
|
return [], info
|
||||||
|
elif None in info['entries']:
|
||||||
|
raise PostProcessingError('Aborting concatenation because some downloads failed')
|
||||||
|
elif any(len(entry) > 1 for entry in traverse_obj(info, ('entries', ..., 'requested_downloads')) or []):
|
||||||
|
raise PostProcessingError('Concatenation is not supported when downloading multiple separate formats')
|
||||||
|
|
||||||
|
in_files = traverse_obj(info, ('entries', ..., 'requested_downloads', 0, 'filepath'))
|
||||||
|
if not in_files:
|
||||||
|
self.to_screen('There are no files to concatenate')
|
||||||
|
return [], info
|
||||||
|
|
||||||
|
ie_copy = self._downloader._playlist_infodict(info)
|
||||||
|
exts = [traverse_obj(entry, ('requested_downloads', 0, 'ext'), 'ext') for entry in info['entries']]
|
||||||
|
ie_copy['ext'] = exts[0] if len(set(exts)) == 1 else 'mkv'
|
||||||
|
out_file = self._downloader.prepare_filename(ie_copy, 'pl_video')
|
||||||
|
|
||||||
|
self.concat_files(in_files, out_file)
|
||||||
|
|
||||||
|
info['requested_downloads'] = [{
|
||||||
|
'filepath': out_file,
|
||||||
|
'ext': ie_copy['ext'],
|
||||||
|
}]
|
||||||
|
return in_files, info
|
||||||
|
|
|
@ -4695,6 +4695,7 @@ OUTTMPL_TYPES = {
|
||||||
'annotation': 'annotations.xml',
|
'annotation': 'annotations.xml',
|
||||||
'infojson': 'info.json',
|
'infojson': 'info.json',
|
||||||
'link': None,
|
'link': None,
|
||||||
|
'pl_video': None,
|
||||||
'pl_thumbnail': None,
|
'pl_thumbnail': None,
|
||||||
'pl_description': 'description',
|
'pl_description': 'description',
|
||||||
'pl_infojson': 'info.json',
|
'pl_infojson': 'info.json',
|
||||||
|
|
Loading…
Reference in a new issue