mirror of https://github.com/yutto-dev/yutto
✨ feat: add dolby atmos download support (#256)
This commit is contained in:
parent
4f2f9415ba
commit
a896de1e84
|
@ -179,8 +179,8 @@ yutto 支持一些基础参数,无论是批量下载还是单视频下载都
|
||||||
#### 指定音频码率等级
|
#### 指定音频码率等级
|
||||||
|
|
||||||
- 参数 `-aq` 或 `--audio-quality`
|
- 参数 `-aq` 或 `--audio-quality`
|
||||||
- 可选值 `30280 | 30232 | 30216`
|
- 可选值 `30251 | 30255 | 30250 | 30280 | 30232 | 30216`
|
||||||
- 默认值 `30280`
|
- 默认值 `30251`
|
||||||
|
|
||||||
码率对应关系如下
|
码率对应关系如下
|
||||||
|
|
||||||
|
@ -188,6 +188,8 @@ yutto 支持一些基础参数,无论是批量下载还是单视频下载都
|
||||||
| code | 码率 |
|
| code | 码率 |
|
||||||
| :-: | :-: |
|
| :-: | :-: |
|
||||||
| 30251 | - (Hi-Res) |
|
| 30251 | - (Hi-Res) |
|
||||||
|
| 30255 | - (杜比音效) |
|
||||||
|
| 30250 | - (杜比全景声) |
|
||||||
| 30280 | 320kbps |
|
| 30280 | 320kbps |
|
||||||
| 30232 | 128kbps |
|
| 30232 | 128kbps |
|
||||||
| 30216 | 64kbps |
|
| 30216 | 64kbps |
|
||||||
|
|
|
@ -76,7 +76,7 @@ def cli() -> argparse.ArgumentParser:
|
||||||
default=127,
|
default=127,
|
||||||
choices=video_quality_priority_default,
|
choices=video_quality_priority_default,
|
||||||
type=int,
|
type=int,
|
||||||
help="视频清晰度等级(127:8K, 126: Dolby Vision, 125:HDR, 120:4K, 116:1080P60, 112:1080P+, 80:1080P, 74:720P60, 64:720P, 32:480P, 16:360P)",
|
help="视频清晰度等级(127:8K, 126:Dolby Vision, 125:HDR, 120:4K, 116:1080P60, 112:1080P+, 80:1080P, 74:720P60, 64:720P, 32:480P, 16:360P)",
|
||||||
)
|
)
|
||||||
group_common.add_argument(
|
group_common.add_argument(
|
||||||
"-aq",
|
"-aq",
|
||||||
|
@ -84,7 +84,7 @@ def cli() -> argparse.ArgumentParser:
|
||||||
default=30251,
|
default=30251,
|
||||||
choices=audio_quality_priority_default,
|
choices=audio_quality_priority_default,
|
||||||
type=int,
|
type=int,
|
||||||
help="音频码率等级(30280:320kbps, 30232:128kbps, 30216:64kbps)",
|
help="音频码率等级(30251:Hi-Res, 30255:Dolby Audio, 30250:Dolby Atmos, 30280:320kbps, 30232:128kbps, 30216:64kbps)",
|
||||||
)
|
)
|
||||||
group_common.add_argument(
|
group_common.add_argument(
|
||||||
"--vcodec",
|
"--vcodec",
|
||||||
|
|
|
@ -177,25 +177,6 @@ async def get_ugc_video_playurl(
|
||||||
)
|
)
|
||||||
if resp_json["data"].get("dash") is None:
|
if resp_json["data"].get("dash") is None:
|
||||||
raise UnSupportedTypeError(f"该视频({format_ids(avid, cid)})尚不支持 DASH 格式")
|
raise UnSupportedTypeError(f"该视频({format_ids(avid, cid)})尚不支持 DASH 格式")
|
||||||
# TODO: 处理 resp_json["data"]["dash"]["dolby"],应当是 Dolby 的音频流
|
|
||||||
# {
|
|
||||||
# "type": 1 | 2, (1: Dolby Audio 杜比音效(例:BV1Fa41127J4),2: Dolby Atmos 杜比全景声(例:BV1eV411W7tt))
|
|
||||||
# "audio": [
|
|
||||||
# {
|
|
||||||
# "id": 30255 | 30250,(好像是只有这俩,分别对应上面两个)
|
|
||||||
# "base_url": "xxxx",
|
|
||||||
# "backup_url": ["xxxx", "xxxx"],
|
|
||||||
# "bandwidth": xxx,
|
|
||||||
# "mime_type": "audio/mp4",
|
|
||||||
# "codecs": "ec-3",
|
|
||||||
# "segment_base": {
|
|
||||||
# "initialization": "xxx",
|
|
||||||
# "index_range": "xxx",
|
|
||||||
# },
|
|
||||||
# "size": xxx,
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# }
|
|
||||||
videos: list[VideoUrlMeta] = (
|
videos: list[VideoUrlMeta] = (
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -226,13 +207,26 @@ async def get_ugc_video_playurl(
|
||||||
if resp_json["data"]["dash"]["audio"]
|
if resp_json["data"]["dash"]["audio"]
|
||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
|
if resp_json["data"]["dash"]["dolby"] is not None and resp_json["data"]["dash"]["dolby"]["audio"] is not None:
|
||||||
|
dolby_audios_json = resp_json["data"]["dash"]["dolby"]["audio"]
|
||||||
|
audios.extend(
|
||||||
|
{
|
||||||
|
"url": dolby_audio_json["base_url"],
|
||||||
|
"mirrors": dolby_audio_json["backup_url"] if dolby_audio_json["backup_url"] is not None else [],
|
||||||
|
"codec": "eac3", # TODO: 由于这里的 codecid 仍然是 0,所以无法通过 audio_codec_map 转换,暂时直接硬编码
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"quality": dolby_audio_json["id"],
|
||||||
|
}
|
||||||
|
for dolby_audio_json in dolby_audios_json
|
||||||
|
)
|
||||||
if resp_json["data"]["dash"]["flac"] is not None and resp_json["data"]["dash"]["flac"]["audio"] is not None:
|
if resp_json["data"]["dash"]["flac"] is not None and resp_json["data"]["dash"]["flac"]["audio"] is not None:
|
||||||
hi_res_audio_json = resp_json["data"]["dash"]["flac"]["audio"]
|
hi_res_audio_json = resp_json["data"]["dash"]["flac"]["audio"]
|
||||||
audios.append(
|
audios.append(
|
||||||
{
|
{
|
||||||
"url": hi_res_audio_json["base_url"],
|
"url": hi_res_audio_json["base_url"],
|
||||||
"mirrors": hi_res_audio_json["backup_url"] if hi_res_audio_json["backup_url"] is not None else [],
|
"mirrors": hi_res_audio_json["backup_url"] if hi_res_audio_json["backup_url"] is not None else [],
|
||||||
"codec": "fLaC", # TODO: 由于这里的 codecid 仍然是 0,所以无法通过 audio_codec_map 转换,暂时直接硬编码
|
"codec": "flac", # TODO: 同上,硬编码
|
||||||
"width": 0,
|
"width": 0,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"quality": hi_res_audio_json["id"],
|
"quality": hi_res_audio_json["id"],
|
||||||
|
|
|
@ -7,10 +7,10 @@ from yutto.utils.priority import gen_priority_sequence
|
||||||
VideoCodecId = Literal[7, 12, 13]
|
VideoCodecId = Literal[7, 12, 13]
|
||||||
VideoCodec = Literal["avc", "hevc", "av1"]
|
VideoCodec = Literal["avc", "hevc", "av1"]
|
||||||
AudioCodecId = Literal[0]
|
AudioCodecId = Literal[0]
|
||||||
AudioCodec = Literal["mp4a", "fLaC"]
|
AudioCodec = Literal["mp4a", "flac", "eac3"]
|
||||||
|
|
||||||
video_codec_priority_default: list[VideoCodec] = ["avc", "hevc", "av1"]
|
video_codec_priority_default: list[VideoCodec] = ["avc", "hevc", "av1"]
|
||||||
audio_codec_priority_default: list[AudioCodec] = ["mp4a", "fLaC"]
|
audio_codec_priority_default: list[AudioCodec] = ["mp4a", "flac", "eac3"]
|
||||||
|
|
||||||
video_codec_map: dict[VideoCodecId, VideoCodec] = {
|
video_codec_map: dict[VideoCodecId, VideoCodec] = {
|
||||||
7: "avc",
|
7: "avc",
|
||||||
|
|
|
@ -12,10 +12,10 @@ class Media(Enum):
|
||||||
|
|
||||||
|
|
||||||
VideoQuality = Literal[127, 126, 125, 120, 116, 112, 80, 74, 64, 32, 16]
|
VideoQuality = Literal[127, 126, 125, 120, 116, 112, 80, 74, 64, 32, 16]
|
||||||
AudioQuality = Literal[30251, 30280, 30232, 30216]
|
AudioQuality = Literal[30251, 30255, 30250, 30280, 30232, 30216]
|
||||||
|
|
||||||
video_quality_priority_default: list[VideoQuality] = [127, 126, 125, 120, 116, 112, 80, 74, 64, 32, 16]
|
video_quality_priority_default: list[VideoQuality] = [127, 126, 125, 120, 116, 112, 80, 74, 64, 32, 16]
|
||||||
audio_quality_priority_default: list[AudioQuality] = [30251, 30280, 30232, 30216]
|
audio_quality_priority_default: list[AudioQuality] = [30251, 30255, 30250, 30280, 30232, 30216]
|
||||||
|
|
||||||
video_quality_map = {
|
video_quality_map = {
|
||||||
127: {
|
127: {
|
||||||
|
@ -80,6 +80,14 @@ audio_quality_map = {
|
||||||
"description": "Hi-Res",
|
"description": "Hi-Res",
|
||||||
"bitrate": 999,
|
"bitrate": 999,
|
||||||
}, # Example: BV1eV4y1P7fc
|
}, # Example: BV1eV4y1P7fc
|
||||||
|
30255: {
|
||||||
|
"description": "杜比音效", # Dolby Audio
|
||||||
|
"bitrate": 999,
|
||||||
|
}, # Example: BV1Fa41127J4,但现在好像没了,也没找到其他的杜比音效选项
|
||||||
|
30250: {
|
||||||
|
"description": "杜比全景声", # Dolby Atmos
|
||||||
|
"bitrate": 999,
|
||||||
|
}, # Example: BV1eV411W7tt
|
||||||
30280: {
|
30280: {
|
||||||
"description": "320kbps",
|
"description": "320kbps",
|
||||||
"bitrate": 320,
|
"bitrate": 320,
|
||||||
|
|
|
@ -261,14 +261,14 @@ async def start_downloader(
|
||||||
if not will_download_video:
|
if not will_download_video:
|
||||||
if options["output_format_audio_only"] != "infer":
|
if options["output_format_audio_only"] != "infer":
|
||||||
output_format = "." + options["output_format_audio_only"]
|
output_format = "." + options["output_format_audio_only"]
|
||||||
elif will_download_audio and audio["codec"] == "fLaC": # pyright: ignore [reportOptionalSubscript]
|
elif will_download_audio and audio["codec"] == "flac": # pyright: ignore [reportOptionalSubscript]
|
||||||
output_format = ".flac"
|
output_format = ".flac"
|
||||||
else:
|
else:
|
||||||
output_format = ".aac"
|
output_format = ".aac"
|
||||||
else:
|
else:
|
||||||
if options["output_format"] != "infer":
|
if options["output_format"] != "infer":
|
||||||
output_format = "." + options["output_format"]
|
output_format = "." + options["output_format"]
|
||||||
elif will_download_audio and audio["codec"] == "fLaC": # pyright: ignore [reportOptionalSubscript]
|
elif will_download_audio and audio["codec"] == "flac": # pyright: ignore [reportOptionalSubscript]
|
||||||
output_format = ".mkv" # MP4 does not support FLAC audio
|
output_format = ".mkv" # MP4 does not support FLAC audio
|
||||||
|
|
||||||
output_path = output_dir.joinpath(filename + output_format)
|
output_path = output_dir.joinpath(filename + output_format)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from yutto.api.ugc_video import AudioUrlMeta, VideoUrlMeta
|
from yutto._typing import AudioUrlMeta, VideoUrlMeta
|
||||||
from yutto.bilibili_typing.codec import (
|
from yutto.bilibili_typing.codec import (
|
||||||
AudioCodec,
|
AudioCodec,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
|
@ -31,14 +31,12 @@ def select_video(
|
||||||
gen_vcodec_priority(video_codec) if video_download_codec_priority is None else video_download_codec_priority
|
gen_vcodec_priority(video_codec) if video_download_codec_priority is None else video_download_codec_priority
|
||||||
)
|
)
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
video_combined_priority = [
|
video_combined_priority = [
|
||||||
(vqn, vcodec)
|
(vqn, vcodec)
|
||||||
for vqn in video_quality_priority
|
for vqn in video_quality_priority
|
||||||
# TODO: Dolby Selector
|
# TODO: Dolby Selector
|
||||||
for vcodec in video_codec_priority
|
for vcodec in video_codec_priority
|
||||||
]
|
] # fmt: skip
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
for vqn, vcodec in video_combined_priority:
|
for vqn, vcodec in video_combined_priority:
|
||||||
for video in videos:
|
for video in videos:
|
||||||
|
@ -55,13 +53,11 @@ def select_audio(
|
||||||
audio_quality_priority = gen_audio_quality_priority(audio_quality)
|
audio_quality_priority = gen_audio_quality_priority(audio_quality)
|
||||||
audio_codec_priority = gen_acodec_priority(audio_codec)
|
audio_codec_priority = gen_acodec_priority(audio_codec)
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
audio_combined_priority = [
|
audio_combined_priority = [
|
||||||
(aqn, acodec)
|
(aqn, acodec)
|
||||||
for aqn in audio_quality_priority
|
for aqn in audio_quality_priority
|
||||||
for acodec in audio_codec_priority
|
for acodec in audio_codec_priority
|
||||||
]
|
] # fmt: skip
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
for aqn, acodec in audio_combined_priority:
|
for aqn, acodec in audio_combined_priority:
|
||||||
for audio in audios:
|
for audio in audios:
|
||||||
|
|
|
@ -26,7 +26,6 @@ def size_format(size: float, ndigits: int = 2, base_unit_size: Literal[1024, 100
|
||||||
|
|
||||||
def get_char_width(char: str) -> int:
|
def get_char_width(char: str) -> int:
|
||||||
"""计算单个字符的宽度"""
|
"""计算单个字符的宽度"""
|
||||||
# fmt: off
|
|
||||||
widths = [
|
widths = [
|
||||||
(126, 1), (159, 0), (687, 1), (710, 0), (711, 1),
|
(126, 1), (159, 0), (687, 1), (710, 0), (711, 1),
|
||||||
(727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0),
|
(727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0),
|
||||||
|
@ -36,8 +35,7 @@ def get_char_width(char: str) -> int:
|
||||||
(55203, 2), (63743, 1), (64106, 2), (65039, 1), (65059, 0),
|
(55203, 2), (63743, 1), (64106, 2), (65039, 1), (65059, 0),
|
||||||
(65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2),
|
(65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2),
|
||||||
(120831, 1), (262141, 2), (1114109, 1),
|
(120831, 1), (262141, 2), (1114109, 1),
|
||||||
]
|
] # fmt: skip
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
o = ord(char)
|
o = ord(char)
|
||||||
if o == 0xE or o == 0xF:
|
if o == 0xE or o == 0xF:
|
||||||
|
|
Loading…
Reference in New Issue