diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 9a659fc65..ff1faaab5 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -293,6 +293,8 @@ def _real_main(argv=None): }) if not already_have_thumbnail: opts.writethumbnail = True + if opts.split_tracks: + postprocessors.append({'key': 'FFmpegSplitByTracks'}) # XAttrMetadataPP should be run after post-processors that may change file # contents if opts.xattrs: diff --git a/youtube_dl/options.py b/youtube_dl/options.py index 6d5ac62b3..0f100e14d 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -525,6 +525,11 @@ def parseOpts(overrideArguments=None): '--external-downloader-args', dest='external_downloader_args', metavar='ARGS', help='Give these arguments to the external downloader') + downloader.add_option( + '--split-tracks', + dest='split_tracks', action='store_true', default=False, + help="Split tracks based on chapters information" + ) workarounds = optparse.OptionGroup(parser, 'Workarounds') workarounds.add_option( diff --git a/youtube_dl/postprocessor/__init__.py b/youtube_dl/postprocessor/__init__.py index 3ea518399..90d36c521 100644 --- a/youtube_dl/postprocessor/__init__.py +++ b/youtube_dl/postprocessor/__init__.py @@ -12,6 +12,7 @@ from .ffmpeg import ( FFmpegMetadataPP, FFmpegVideoConvertorPP, FFmpegSubtitlesConvertorPP, + FFmpegSplitByTracksPP, ) from .xattrpp import XAttrMetadataPP from .execafterdownload import ExecAfterDownloadPP @@ -27,6 +28,7 @@ __all__ = [ 'ExecAfterDownloadPP', 'FFmpegEmbedSubtitlePP', 'FFmpegExtractAudioPP', + 'FFmpegSplitByTracksPP', 'FFmpegFixupM3u8PP', 'FFmpegFixupM4aPP', 'FFmpegFixupStretchedPP', diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py index 5f7298345..c0367333a 100644 --- a/youtube_dl/postprocessor/ffmpeg.py +++ b/youtube_dl/postprocessor/ffmpeg.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import io +import logging import os import subprocess import time @@ -655,3 +656,49 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor): } return sub_filenames, info + + +class FFmpegSplitByTracksPP(FFmpegPostProcessor): + log = logging.getLogger(__name__) + + def _ffmpeg_time_string(self, seconds): + t_minutes, t_seconds = divmod(seconds, 60) + t_hours, t_minutes = divmod(t_minutes, 60) + t_string = '{hrs:02}:{min:02}:{sec:02}'.format(hrs=t_hours, min=t_minutes, sec=t_seconds) + return t_string + + def _build_track_name(self, chapter, information): + track_title = chapter.get("title", "") + track_title = encodeFilename(track_title) + track_title = track_title.replace("/", "_") + + prefix, sep, ext = information['filepath'].rpartition('.') + track_name = "%s - %s%s%s" % (prefix, track_title, sep, ext) + + return track_name + + def _extract_track_from_chapter(self, chapter, information): + start = int(chapter['start_time']) + end = int(chapter['end_time']) + duration = end - start + + start = self._ffmpeg_time_string(start) + duration = self._ffmpeg_time_string(duration) + + destination = self._build_track_name(chapter, information) + + self.run_ffmpeg(information['filepath'], destination, ['-c', 'copy', '-ss', start, '-t', duration]) + + def run(self, information): + chapters = information.get('chapters', []) + if not isinstance(chapters, list) or len(chapters) == 0: + self.log.warning('[ffmpeg] There are no tracks to extract') + return [], information + + for idx, chapter in enumerate(chapters): + try: + self._extract_track_from_chapter(chapter, information) + except Exception as e: + self.log.error('Splitting track failed: ' + repr(e)) + + return [], information