mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-10-03 11:23:45 -04:00
Split video by chapters (#158)
* New options `--split-chapters` and `--no-split-chapters` * The output/path of the split files can be given using the key `chapter` * Additional keys `section_title`, `section_number`, `section_start`, `section_end` are available in the output template * Alias `--split-tracks` for parity with animelover/youtube-dl * `--sponskrub-cut` and `--split-chapter` cannot work together Closes: https://github.com/blackjack4494/yt-dlc/issues/277 https://github.com/ytdl-org/youtube-dl/issues/28438 https://github.com/ytdl-org/youtube-dl/issues/12907 https://github.com/ytdl-org/youtube-dl/issues/6480 https://github.com/ytdl-org/youtube-dl/pull/25005 Rewritten from the implementation by: femaref and Wattux https://github.com/Wattux/youtube-dl/tree/split-at-timestamps https://github.com/ytdl-org/youtube-dl/pull/25005 https://github.com/femaref/youtube-dl/tree/split-track
This commit is contained in:
@@ -279,9 +279,14 @@ def _real_main(argv=None):
|
||||
|
||||
def report_conflict(arg1, arg2):
|
||||
write_string('WARNING: %s is ignored since %s was given\n' % (arg2, arg1), out=sys.stderr)
|
||||
|
||||
if opts.remuxvideo and opts.recodevideo:
|
||||
report_conflict('--recode-video', '--remux-video')
|
||||
opts.remuxvideo = False
|
||||
if opts.sponskrub_cut and opts.split_chapters and opts.sponskrub is not False:
|
||||
report_conflict('--split-chapter', '--sponskrub-cut')
|
||||
opts.sponskrub_cut = False
|
||||
|
||||
if opts.allow_unplayable_formats:
|
||||
if opts.extractaudio:
|
||||
report_conflict('--allow-unplayable-formats', '--extract-audio')
|
||||
@@ -371,11 +376,7 @@ def _real_main(argv=None):
|
||||
})
|
||||
if not already_have_thumbnail:
|
||||
opts.writethumbnail = True
|
||||
# XAttrMetadataPP should be run after post-processors that may change file
|
||||
# contents
|
||||
if opts.xattrs:
|
||||
postprocessors.append({'key': 'XAttrMetadata'})
|
||||
# This should be below all ffmpeg PP because it may cut parts out from the video
|
||||
# This should be below most ffmpeg PP because it may cut parts out from the video
|
||||
# If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
|
||||
if opts.sponskrub is not False:
|
||||
postprocessors.append({
|
||||
@@ -386,6 +387,11 @@ def _real_main(argv=None):
|
||||
'force': opts.sponskrub_force,
|
||||
'ignoreerror': opts.sponskrub is None,
|
||||
})
|
||||
if opts.split_chapters:
|
||||
postprocessors.append({'key': 'FFmpegSplitChapters'})
|
||||
# XAttrMetadataPP should be run after post-processors that may change file contents
|
||||
if opts.xattrs:
|
||||
postprocessors.append({'key': 'XAttrMetadata'})
|
||||
# ExecAfterDownload must be the last PP
|
||||
if opts.exec_cmd:
|
||||
postprocessors.append({
|
||||
|
@@ -1183,6 +1183,17 @@ def parseOpts(overrideArguments=None):
|
||||
'--convert-subs', '--convert-subtitles',
|
||||
metavar='FORMAT', dest='convertsubtitles', default=None,
|
||||
help='Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)')
|
||||
postproc.add_option(
|
||||
'--split-chapters', '--split-tracks',
|
||||
dest='split_chapters', action='store_true', default=False,
|
||||
help=(
|
||||
'Split video into multiple files based on internal chapters. '
|
||||
'The "chapter:" 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(
|
||||
'--no-split-chapters', '--no-split-tracks',
|
||||
dest='split_chapters', action='store_false',
|
||||
help='Do not split video based on chapters (default)')
|
||||
|
||||
sponskrub = optparse.OptionGroup(parser, 'SponSkrub (SponsorBlock) Options', description=(
|
||||
'SponSkrub (https://github.com/yt-dlp/SponSkrub) is a utility to mark/remove sponsor segments '
|
||||
|
@@ -13,6 +13,7 @@ from .ffmpeg import (
|
||||
FFmpegVideoConvertorPP,
|
||||
FFmpegVideoRemuxerPP,
|
||||
FFmpegSubtitlesConvertorPP,
|
||||
FFmpegSplitChaptersPP,
|
||||
)
|
||||
from .xattrpp import XAttrMetadataPP
|
||||
from .execafterdownload import ExecAfterDownloadPP
|
||||
@@ -31,6 +32,7 @@ __all__ = [
|
||||
'ExecAfterDownloadPP',
|
||||
'FFmpegEmbedSubtitlePP',
|
||||
'FFmpegExtractAudioPP',
|
||||
'FFmpegSplitChaptersPP',
|
||||
'FFmpegFixupM3u8PP',
|
||||
'FFmpegFixupM4aPP',
|
||||
'FFmpegFixupStretchedPP',
|
||||
|
@@ -10,6 +10,7 @@ import json
|
||||
|
||||
from .common import AudioConversionError, PostProcessor
|
||||
|
||||
from ..compat import compat_str
|
||||
from ..utils import (
|
||||
encodeArgument,
|
||||
encodeFilename,
|
||||
@@ -769,3 +770,40 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
|
||||
}
|
||||
|
||||
return sub_filenames, info
|
||||
|
||||
|
||||
class FFmpegSplitChaptersPP(FFmpegPostProcessor):
|
||||
|
||||
def _prepare_filename(self, number, chapter, info):
|
||||
info = info.copy()
|
||||
info.update({
|
||||
'section_number': number,
|
||||
'section_title': chapter.get('title'),
|
||||
'section_start': chapter.get('start_time'),
|
||||
'section_end': chapter.get('end_time'),
|
||||
})
|
||||
return self._downloader.prepare_filename(info, 'chapter')
|
||||
|
||||
def _ffmpeg_args_for_chapter(self, number, chapter, info):
|
||||
destination = self._prepare_filename(number, chapter, info)
|
||||
if not self._downloader._ensure_dir_exists(encodeFilename(destination)):
|
||||
return
|
||||
|
||||
chapter['_filename'] = destination
|
||||
self.to_screen('Chapter %03d; Destination: %s' % (number, destination))
|
||||
return (
|
||||
destination,
|
||||
['-ss', compat_str(chapter['start_time']),
|
||||
'-to', compat_str(chapter['end_time'])])
|
||||
|
||||
def run(self, info):
|
||||
chapters = info.get('chapters') or []
|
||||
if not chapters:
|
||||
self.report_warning('There are no tracks to extract')
|
||||
return [], info
|
||||
|
||||
self.to_screen('Splitting video by chapters; %d chapters found' % len(chapters))
|
||||
for idx, chapter in enumerate(chapters):
|
||||
destination, opts = self._ffmpeg_args_for_chapter(idx + 1, chapter, info)
|
||||
self.real_run_ffmpeg([(info['filepath'], opts)], [(destination, ['-c', 'copy'])])
|
||||
return [], info
|
||||
|
@@ -4182,8 +4182,10 @@ def qualities(quality_ids):
|
||||
|
||||
DEFAULT_OUTTMPL = {
|
||||
'default': '%(title)s [%(id)s].%(ext)s',
|
||||
'chapter': '%(title)s - %(section_number)03d %(section_title)s [%(id)s].%(ext)s',
|
||||
}
|
||||
OUTTMPL_TYPES = {
|
||||
'chapter': None,
|
||||
'subtitle': None,
|
||||
'thumbnail': None,
|
||||
'description': 'description',
|
||||
|
Reference in New Issue
Block a user