feat: add support for specify a output format (#85) (#109)

This commit is contained in:
Nyakku Shigure 2023-02-04 19:33:39 +08:00 committed by GitHub
parent a3b63ef23c
commit 86c88b2bdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 4 deletions

1
.gitignore vendored
View File

@ -123,6 +123,7 @@ dmypy.json
*.flac
*.mp4
*.mkv
*.mov
*.m4s
*.xml
*.pb

View File

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

View File

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

View File

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

View File

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

View File

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