From 86c88b2bdf3907d0390b75bfb5b928a6b16aa110 Mon Sep 17 00:00:00 2001 From: Nyakku Shigure Date: Sat, 4 Feb 2023 19:33:39 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20support=20for=20speci?= =?UTF-8?q?fy=20a=20output=20format=20(#85)=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 26 ++++++++++++++++++++++++++ justfile | 1 + yutto/__main__.py | 11 +++++++++++ yutto/_typing.py | 2 ++ yutto/processor/downloader.py | 29 +++++++++++++++++++++++++---- 6 files changed, 66 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3817e74..2e45b78 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,7 @@ dmypy.json *.flac *.mp4 *.mkv +*.mov *.m4s *.xml *.pb diff --git a/README.md b/README.md index 5bb51ae..5a5437e 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,32 @@ yutto 支持一些基础参数,无论是批量下载还是单视频下载都 详情同视频编码。 +#### 指定输出格式 + +- 参数 `--output-format` +- 可选值 `"infer" | "mp4" | "mkv" | "mov"` +- 默认值 `"infer"` + +在至少包含视频流时所使用的输出格式,默认选值 `"infer"` 表示自动根据情况进行推导以保证输出的可用,推导规则如下: + +- 如果输出包含音频流且音频流编码为 `"fLaC"`,则输出格式为 `"mkv"`,因为 `"mp4"` 尚不支持 `"fLaC"` 编码 +- 否则为 `"mp4"` + +#### 指定在仅包含音频流时的输出格式 + +- 参数 `--output-format-audio-only` +- 可选值 `"infer" | "aac" | "flac" | "mp4" | "mkv" | "mov"` +- 默认值 `"infer"` + +在仅包含音频流时所使用的输出格式,默认选值 `"infer"` 表示自动根据情况进行推导以保证输出的可用,推导规则如下: + +- 如果音频流编码为 `"fLaC"`,则输出格式为 `"flac"` +- 否则为 `"aac"` + +> **Note** +> +> 并不是仅仅在指定 `--audio-only` 时才会仅仅包含视频流,有些视频是仅包含音频流的,此时即便不指定 `--audio-only` 选项也会按照本选项的格式进行输出。 + #### 弹幕格式选择 - 参数 `-df` 或 `--danmaku-format` diff --git a/justfile b/justfile index 11cd71d..d948206 100644 --- a/justfile +++ b/justfile @@ -41,6 +41,7 @@ clean: find . -name "*.m4s" -print0 | xargs -0 rm -f find . -name "*.mp4" -print0 | xargs -0 rm -f find . -name "*.mkv" -print0 | xargs -0 rm -f + find . -name "*.mov" -print0 | xargs -0 rm -f find . -name "*.aac" -print0 | xargs -0 rm -f find . -name "*.flac" -print0 | xargs -0 rm -f find . -name "*.srt" -print0 | xargs -0 rm -f diff --git a/yutto/__main__.py b/yutto/__main__.py index 14b7687..df27c48 100644 --- a/yutto/__main__.py +++ b/yutto/__main__.py @@ -85,6 +85,15 @@ def cli() -> argparse.ArgumentParser: group_common.add_argument( "--acodec", default="mp4a:copy", metavar="DOWNLOAD_ACODEC:SAVE_ACODEC", help="音频编码格式(<下载格式>:<生成格式>)" ) + group_common.add_argument( + "--output-format", default="infer", choices=["infer", "mp4", "mkv", "mov"], help="输出格式(infer 为自动推断)" + ) + group_common.add_argument( + "--output-format-audio-only", + default="infer", + choices=["infer", "aac", "flac", "mp4", "mkv", "mov"], + help="仅包含音频流时所使用的输出格式(infer 为自动推断)", + ) group_common.add_argument("-df", "--danmaku-format", default="ass", choices=["xml", "ass", "protobuf"], help="弹幕类型") group_common.add_argument("-bs", "--block-size", default=0.5, type=float, help="分块下载时各块大小,单位为 MiB,默认为 0.5MiB") group_common.add_argument("-w", "--overwrite", action="store_true", help="强制覆盖已下载内容") @@ -258,6 +267,8 @@ async def run(args_list: list[argparse.Namespace]): "audio_quality": args.audio_quality, "audio_download_codec": args.acodec.split(":")[0], "audio_save_codec": args.acodec.split(":")[1], + "output_format": args.output_format, + "output_format_audio_only": args.output_format_audio_only, "overwrite": args.overwrite, "block_size": int(args.block_size * 1024 * 1024), "num_workers": args.num_workers, diff --git a/yutto/_typing.py b/yutto/_typing.py index 2fb1c77..0f60b48 100644 --- a/yutto/_typing.py +++ b/yutto/_typing.py @@ -179,6 +179,8 @@ class DownloaderOptions(TypedDict): audio_quality: AudioQuality audio_download_codec: AudioCodec audio_save_codec: str + output_format: str + output_format_audio_only: str overwrite: bool block_size: int num_workers: int diff --git a/yutto/processor/downloader.py b/yutto/processor/downloader.py index 7d33141..643c3ac 100644 --- a/yutto/processor/downloader.py +++ b/yutto/processor/downloader.py @@ -147,6 +147,15 @@ def merge_video_and_audio( ffmpeg = FFmpeg() Logger.info("开始合并……") + # Using FFmpeg to Create HEVC Videos That Work on Apple Devices: + # https://aaron.cc/ffmpeg-hevc-apple-devices/ + # see also: https://github.com/yutto-dev/yutto/issues/85 + vtag: str | None = None + if options["video_save_codec"] == "hevc" or ( + options["video_save_codec"] == "copy" and video is not None and video["codec"] == "hevc" + ): + vtag = "hvc1" + if video is not None and video["codec"] == options["video_save_codec"]: options["video_save_codec"] = "copy" if audio is not None and audio["codec"] == options["audio_save_codec"]: @@ -159,6 +168,7 @@ def merge_video_and_audio( ["-acodec", options["audio_save_codec"]] if audio is not None else [], # see also: https://www.reddit.com/r/ffmpeg/comments/qe7oq1/comment/hi0bmic/?utm_source=share&utm_medium=web2x&context=3 ["-strict", "unofficial"], + ["-tag:v", vtag] if vtag is not None else [], ["-threads", str(os.cpu_count())], ["-y", str(output_path)], ] @@ -201,18 +211,26 @@ async def start_downloader( will_download_audio = audio is not None and require_audio # 显示音视频详细信息 - show_videos_info(videos, videos.index(video) if video is not None else -1) - show_audios_info(audios, audios.index(audio) if audio is not None else -1) + show_videos_info( + videos, videos.index(video) if will_download_video else -1 # pyright: ignore [reportGeneralTypeIssues] + ) + show_audios_info( + audios, audios.index(audio) if will_download_audio else -1 # pyright: ignore [reportGeneralTypeIssues] + ) output_dir.mkdir(parents=True, exist_ok=True) output_format = ".mp4" if not will_download_video: - if will_download_audio and audio["codec"] == "fLaC": # type: ignore + if options["output_format_audio_only"] != "infer": + output_format = "." + options["output_format_audio_only"] + elif will_download_audio and audio["codec"] == "fLaC": # pyright: ignore [reportOptionalSubscript] output_format = ".flac" else: output_format = ".aac" else: - if will_download_audio and audio["codec"] == "fLaC": # type: ignore + if options["output_format"] != "infer": + output_format = "." + options["output_format"] + elif will_download_audio and audio["codec"] == "fLaC": # pyright: ignore [reportOptionalSubscript] output_format = ".mkv" # MP4 does not support FLAC audio output_path = output_dir.joinpath(filename + output_format) @@ -253,6 +271,9 @@ async def start_downloader( Logger.warning("没有音视频需要下载") return + video = video if will_download_video else None + audio = audio if will_download_audio else None + # 下载视频 / 音频 await download_video_and_audio(session, video, video_path, audio, audio_path, options)