feat: 后端支持获取视频的画质和音质

This commit is contained in:
lanyeeee
2026-02-01 08:45:46 +08:00
parent b4851e4143
commit bfba2a1ab5
10 changed files with 244 additions and 0 deletions
+39
View File
@@ -9,11 +9,13 @@ use crate::{
extensions::AppHandleExt,
logger,
types::{
available_media_formats::AvailableMediaFormats,
bangumi_follow_info::BangumiFollowInfo,
bangumi_info::{BangumiInfo, EpInBangumi},
create_download_task_params::CreateDownloadTaskParams,
fav_folders::FavFolders,
fav_info::FavInfo,
get_available_media_formats_params::GetAvailableMediaFormatsParams,
get_bangumi_follow_info_params::GetBangumiFollowInfoParams,
get_bangumi_info_params::GetBangumiInfoParams,
get_cheese_info_params::GetCheeseInfoParams,
@@ -402,3 +404,40 @@ pub async fn get_skip_segments(
.map_err(|err| CommandError::from("获取跳过片段失败", err))?;
Ok(skip_segments)
}
#[tauri::command(async)]
#[specta::specta]
pub async fn get_available_media_formats(
app: AppHandle,
params: GetAvailableMediaFormatsParams,
) -> CommandResult<AvailableMediaFormats> {
let bili_client = app.get_bili_client();
let result = match params {
GetAvailableMediaFormatsParams::Normal(params) => {
let media_url = bili_client
.get_normal_url(&params.bvid, params.cid)
.await
.map_err(|err| CommandError::from("获取普通视频可用格式失败", err))?;
media_url.to_get_available_media_formats_result()
}
GetAvailableMediaFormatsParams::Bangumi(params) => {
let media_url = bili_client
.get_bangumi_url(params.cid)
.await
.map_err(|err| CommandError::from("获取番剧视频可用格式失败", err))?;
media_url.to_get_available_media_formats_result()
}
GetAvailableMediaFormatsParams::Cheese(params) => {
let media_url = bili_client
.get_cheese_url(params.ep_id)
.await
.map_err(|err| CommandError::from("获取课程视频可用格式失败", err))?;
media_url.to_get_available_media_formats_result()
}
};
Ok(result)
}
+1
View File
@@ -59,6 +59,7 @@ pub fn run() {
get_logs_dir_size,
show_path_in_file_manager,
get_skip_segments,
get_available_media_formats,
])
.events(tauri_specta::collect_events![LogEvent, DownloadEvent]);
+3
View File
@@ -22,10 +22,13 @@ pub enum AudioQuality {
Unknown = -1,
#[serde(rename = "64K")]
#[num_enum(alternatives = [100008])]
Audio64K = 30216,
#[serde(rename = "132K")]
#[num_enum(alternatives = [100009])]
Audio132K = 30232,
#[serde(rename = "192K")]
#[num_enum(alternatives = [100010])]
Audio192K = 30280,
#[serde(rename = "Dolby")]
AudioDolby = 30250,
@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
use specta::Type;
use crate::types::{
audio_quality::AudioQuality, codec_type::CodecType, video_quality::VideoQuality,
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct AvailableMediaFormats {
pub video_qualities_and_codec_types: Vec<VideoQualityAndCodecType>,
pub audio_qualities: Vec<AudioQuality>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct VideoQualityAndCodecType {
pub video_quality: VideoQuality,
pub codec_type: CodecType,
}
+45
View File
@@ -1,6 +1,11 @@
use serde::{Deserialize, Serialize};
use specta::Type;
use crate::types::{
audio_quality::AudioQuality,
available_media_formats::{AvailableMediaFormats, VideoQualityAndCodecType},
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct BangumiMediaUrl {
@@ -130,3 +135,43 @@ pub struct DurlDetailInBangumi {
pub order: i64,
pub md5: String,
}
impl BangumiMediaUrl {
pub fn to_get_available_media_formats_result(&self) -> AvailableMediaFormats {
let mut video_qualities_and_codec_types: Vec<VideoQualityAndCodecType> = Vec::new();
let mut audio_qualities: Vec<AudioQuality> = Vec::new();
if let Some(dash) = &self.dash {
for media in &dash.video {
let video_qualities_and_codec_type = VideoQualityAndCodecType {
video_quality: media.id.into(),
codec_type: media.codecid.into(),
};
video_qualities_and_codec_types.push(video_qualities_and_codec_type);
}
}
for durl in &self.durls {
if !durl.durl.is_empty() {
let video_qualities_and_codec_type = VideoQualityAndCodecType {
video_quality: durl.quality.into(),
codec_type: self.video_codecid.into(),
};
video_qualities_and_codec_types.push(video_qualities_and_codec_type);
}
}
if let Some(medias) = self.dash.as_ref().and_then(|dash| dash.audio.as_ref()) {
for media in medias {
audio_qualities.push(media.id.into());
}
}
AvailableMediaFormats {
video_qualities_and_codec_types,
audio_qualities,
}
}
}
+45
View File
@@ -1,6 +1,11 @@
use serde::{Deserialize, Serialize};
use specta::Type;
use crate::types::{
audio_quality::AudioQuality,
available_media_formats::{AvailableMediaFormats, VideoQualityAndCodecType},
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct CheeseMediaUrl {
@@ -122,3 +127,43 @@ pub struct DurlDetailInCheese {
pub order: i64,
pub md5: String,
}
impl CheeseMediaUrl {
pub fn to_get_available_media_formats_result(&self) -> AvailableMediaFormats {
let mut video_qualities_and_codec_types: Vec<VideoQualityAndCodecType> = Vec::new();
let mut audio_qualities: Vec<AudioQuality> = Vec::new();
if let Some(dash) = &self.dash {
for media in &dash.video {
let video_qualities_and_codec_type = VideoQualityAndCodecType {
video_quality: media.id.into(),
codec_type: media.codecid.into(),
};
video_qualities_and_codec_types.push(video_qualities_and_codec_type);
}
}
for durl in &self.durls {
if !durl.durl.is_empty() {
let video_qualities_and_codec_type = VideoQualityAndCodecType {
video_quality: durl.quality.into(),
codec_type: self.video_codecid.into(),
};
video_qualities_and_codec_types.push(video_qualities_and_codec_type);
}
}
if let Some(medias) = self.dash.as_ref().and_then(|dash| dash.audio.as_ref()) {
for media in medias {
audio_qualities.push(media.id.into());
}
}
AvailableMediaFormats {
video_qualities_and_codec_types,
audio_qualities,
}
}
}
@@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
use specta::Type;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub enum GetAvailableMediaFormatsParams {
Normal(GetNormalAvailableMediaFormatsParams),
Bangumi(GetBangumiAvailableMediaFormatsParams),
Cheese(GetCheeseAvailableMediaFormatsParams),
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct GetNormalAvailableMediaFormatsParams {
pub bvid: String,
pub cid: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct GetBangumiAvailableMediaFormatsParams {
pub cid: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct GetCheeseAvailableMediaFormatsParams {
pub ep_id: i64,
}
+2
View File
@@ -1,4 +1,5 @@
pub mod audio_quality;
pub mod available_media_formats;
pub mod bangumi_follow_info;
pub mod bangumi_info;
pub mod bangumi_media_url;
@@ -8,6 +9,7 @@ pub mod codec_type;
pub mod create_download_task_params;
pub mod fav_folders;
pub mod fav_info;
pub mod get_available_media_formats_params;
pub mod get_bangumi_follow_info_params;
pub mod get_bangumi_info_params;
pub mod get_cheese_info_params;
+52
View File
@@ -1,6 +1,11 @@
use serde::{Deserialize, Serialize};
use specta::Type;
use crate::types::{
audio_quality::AudioQuality,
available_media_formats::{AvailableMediaFormats, VideoQualityAndCodecType},
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct NormalMediaUrl {
@@ -103,3 +108,50 @@ pub struct SupportFormatInNormal {
pub struct PlayConf {
pub is_new_description: bool,
}
impl NormalMediaUrl {
pub fn to_get_available_media_formats_result(&self) -> AvailableMediaFormats {
let mut video_qualities_and_codec_types: Vec<VideoQualityAndCodecType> = Vec::new();
let mut audio_qualities: Vec<AudioQuality> = Vec::new();
for media in &self.dash.video {
let video_qualities_and_codec = VideoQualityAndCodecType {
video_quality: media.id.into(),
codec_type: media.codecid.into(),
};
video_qualities_and_codec_types.push(video_qualities_and_codec);
}
if !self.durl.is_empty() {
let video_qualities_and_codec = VideoQualityAndCodecType {
video_quality: self.quality.into(),
codec_type: self.video_codecid.into(),
};
video_qualities_and_codec_types.push(video_qualities_and_codec);
}
if let Some(medias) = &self.dash.audio {
for media in medias {
audio_qualities.push(media.id.into());
}
}
if let Some(medias) = &self.dash.dolby.audio {
for media in medias {
audio_qualities.push(media.id.into());
}
}
let flac = self.dash.flac.as_ref();
if let Some(media) = flac.and_then(|flac| flac.audio.as_ref()) {
audio_qualities.push(media.id.into());
}
AvailableMediaFormats {
video_qualities_and_codec_types,
audio_qualities,
}
}
}
+14
View File
@@ -161,6 +161,14 @@ async getSkipSegments(bvid: string, cid: number | null) : Promise<Result<SkipSeg
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getAvailableMediaFormats(params: GetAvailableMediaFormatsParams) : Promise<Result<AvailableMediaFormats, CommandError>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_available_media_formats", { params }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
}
}
@@ -190,6 +198,7 @@ export type ArgueInfo = { argue_msg: string; argue_type: number; argue_link: str
export type AudioQuality = "Unknown" | "64K" | "132K" | "192K" | "Dolby" | "HiRes"
export type AudioTask = { selected: boolean; url: string; audio_quality: AudioQuality; content_length: number; chunks: MediaChunk[]; completed: boolean; skipped: boolean }
export type Author = { mid: number; name: string; face: string }
export type AvailableMediaFormats = { video_qualities_and_codec_types: VideoQualityAndCodecType[]; audio_qualities: AudioQuality[] }
export type BadgeInfoInBangumi = { bg_color: string; bg_color_night: string; text: string }
export type BadgeInfoInBangumiFollow = { text: string | null; bg_color: string; bg_color_night: string; img: string | null; multi_img: MultiImg }
export type BadgeInfos = { vip_or_pay: VipOrPay | null; content_attr: ContentAttr | null }
@@ -295,15 +304,19 @@ export type FavSearchResult = FavInfo
export type FileExistAction = "Overwrite" | "Skip"
export type FirstEpInfo = { id: number; cover: string; title: string; long_title: string | null; pub_time: string; duration: number }
export type Folder = { id: number; fid: number; mid: number; attr: number; title: string; fav_state: number; media_count: number }
export type GetAvailableMediaFormatsParams = { Normal: GetNormalAvailableMediaFormatsParams } | { Bangumi: GetBangumiAvailableMediaFormatsParams } | { Cheese: GetCheeseAvailableMediaFormatsParams }
export type GetBangumiAvailableMediaFormatsParams = { cid: number }
export type GetBangumiFollowInfoParams = { vmid: number;
/**
* 1: 番剧 2: 电视剧或电影
*/
type: number; pn: number; follow_status: number }
export type GetBangumiInfoParams = { EpId: number } | { SeasonId: number }
export type GetCheeseAvailableMediaFormatsParams = { ep_id: number }
export type GetCheeseInfoParams = { EpId: number } | { SeasonId: number }
export type GetFavInfoParams = { media_list_id: number; pn: number }
export type GetHistoryInfoParams = { pn: number; keyword: string; add_time_start: number; add_time_end: number; arc_max_duration: number; arc_min_duration: number; device_type: DeviceType }
export type GetNormalAvailableMediaFormatsParams = { bvid: string; cid: number }
export type GetNormalInfoParams = { Bvid: string } | { Aid: number }
export type GetUserVideoInfoParams = { pn: number; mid: number }
export type History = { oid: number; epid: number; bvid: string; page: number; cid: number; part: string; business: string; dt: number }
@@ -409,6 +422,7 @@ export type UserVideoList = { vlist: EpInUserVideo[] }
export type UserVideoSearchResult = UserVideoInfo
export type VideoProcessTask = { merge_selected: boolean; embed_chapter_selected: boolean; embed_skip_selected: boolean; completed: boolean }
export type VideoQuality = "Unknown" | "240P" | "360P" | "480P" | "720P" | "720P60" | "1080P" | "AiRepair" | "1080P+" | "1080P60" | "4K" | "HDR" | "Dolby" | "8K"
export type VideoQualityAndCodecType = { video_quality: VideoQuality; codec_type: CodecType }
export type VideoTask = { selected: boolean; url: string; video_quality: VideoQuality; codec_type: CodecType; content_length: number; chunks: MediaChunk[]; completed: boolean; skipped: boolean }
export type VipInUserInfo = { type: number; status: number; due_date: number; vip_pay_type: number; theme_type: number; label: LabelInUserInfo; avatar_subscript: number; nickname_color: string; role: number; avatar_subscript_url: string; tv_vip_status: number; tv_vip_pay_type: number; tv_due_date: number }
export type VipLabel = { path: string; text: string; label_theme: string; text_color: string; bg_style: number; bg_color: string; border_color: string; use_img_label: boolean; img_label_uri_hans: string; img_label_uri_hant: string; img_label_uri_hans_static: string; img_label_uri_hant_static: string }