1
0
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:
pukkandan
2021-03-15 04:32:13 +05:30
committed by GitHub
parent a1c5d2ca64
commit 7275535116
6 changed files with 79 additions and 6 deletions

View File

@@ -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({

View File

@@ -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 '

View File

@@ -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',

View File

@@ -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

View File

@@ -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',