diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 30ddccb..2a6f492 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -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 { + let bili_client = app.get_bili_client(); + let result = match params { + GetAvailableMediaFormatsParams::Normal(params) => { + let media_url = bili_client + .get_normal_url(¶ms.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) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fc1aee3..296b696 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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]); diff --git a/src-tauri/src/types/audio_quality.rs b/src-tauri/src/types/audio_quality.rs index 09c0af6..182edc2 100644 --- a/src-tauri/src/types/audio_quality.rs +++ b/src-tauri/src/types/audio_quality.rs @@ -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, diff --git a/src-tauri/src/types/available_media_formats.rs b/src-tauri/src/types/available_media_formats.rs new file mode 100644 index 0000000..080028f --- /dev/null +++ b/src-tauri/src/types/available_media_formats.rs @@ -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, + pub audio_qualities: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct VideoQualityAndCodecType { + pub video_quality: VideoQuality, + pub codec_type: CodecType, +} diff --git a/src-tauri/src/types/bangumi_media_url.rs b/src-tauri/src/types/bangumi_media_url.rs index ec41891..35c83f8 100644 --- a/src-tauri/src/types/bangumi_media_url.rs +++ b/src-tauri/src/types/bangumi_media_url.rs @@ -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 = Vec::new(); + let mut audio_qualities: Vec = 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, + } + } +} diff --git a/src-tauri/src/types/cheese_media_url.rs b/src-tauri/src/types/cheese_media_url.rs index f58666f..0ccd087 100644 --- a/src-tauri/src/types/cheese_media_url.rs +++ b/src-tauri/src/types/cheese_media_url.rs @@ -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 = Vec::new(); + let mut audio_qualities: Vec = 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, + } + } +} diff --git a/src-tauri/src/types/get_available_media_formats_params.rs b/src-tauri/src/types/get_available_media_formats_params.rs new file mode 100644 index 0000000..0f58173 --- /dev/null +++ b/src-tauri/src/types/get_available_media_formats_params.rs @@ -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, +} diff --git a/src-tauri/src/types/mod.rs b/src-tauri/src/types/mod.rs index a7e157f..6f2281d 100644 --- a/src-tauri/src/types/mod.rs +++ b/src-tauri/src/types/mod.rs @@ -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; diff --git a/src-tauri/src/types/normal_media_url.rs b/src-tauri/src/types/normal_media_url.rs index a8dbcf9..34598c6 100644 --- a/src-tauri/src/types/normal_media_url.rs +++ b/src-tauri/src/types/normal_media_url.rs @@ -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 = Vec::new(); + let mut audio_qualities: Vec = 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, + } + } +} diff --git a/src/bindings.ts b/src/bindings.ts index 58115f5..7de6b4e 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -161,6 +161,14 @@ async getSkipSegments(bvid: string, cid: number | null) : Promise> { + 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 }