diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 3b0dbd6..1c41870 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -26,6 +26,7 @@ pub struct Config { pub download_subtitle: bool, pub download_cover: bool, pub download_nfo: bool, + pub download_json: bool, pub dir_fmt: String, pub dir_fmt_for_part: String, pub time_fmt: String, @@ -106,6 +107,7 @@ impl Config { download_subtitle: true, download_cover: true, download_nfo: true, + download_json: true, dir_fmt: "{collection_title}/{episode_title}".to_string(), dir_fmt_for_part: DEFAULT_FMT_FOR_PART.to_string(), time_fmt: "%Y-%m-%d_%H-%M-%S".to_string(), diff --git a/src-tauri/src/downloader/download_progress.rs b/src-tauri/src/downloader/download_progress.rs index 98db687..ad1f31e 100644 --- a/src-tauri/src/downloader/download_progress.rs +++ b/src-tauri/src/downloader/download_progress.rs @@ -13,7 +13,7 @@ use crate::{ config::Config, downloader::tasks::{ audio_task::AudioTask, cover_task::CoverTask, danmaku_task::DanmakuTask, - merge_task::MergeTask, nfo_task::NfoTask, subtitle_task::SubtitleTask, + json_task::JsonTask, merge_task::MergeTask, nfo_task::NfoTask, subtitle_task::SubtitleTask, video_task::VideoTask, }, extensions::AppHandleExt, @@ -57,6 +57,7 @@ pub struct DownloadProgress { pub danmaku_task: DanmakuTask, pub cover_task: CoverTask, pub nfo_task: NfoTask, + pub json_task: JsonTask, pub create_ts: u64, pub completed_ts: Option, } @@ -128,6 +129,7 @@ impl DownloadProgress { subtitle_task: tasks.subtitle, cover_task: tasks.cover, nfo_task: tasks.nfo, + json_task: tasks.json, create_ts, completed_ts: None, }; @@ -178,6 +180,7 @@ impl DownloadProgress { subtitle_task: tasks.subtitle, cover_task: tasks.cover, nfo_task: tasks.nfo, + json_task: tasks.json, create_ts, completed_ts: None, }; @@ -317,6 +320,7 @@ impl DownloadProgress { && self.subtitle_task.is_completed() && self.cover_task.is_completed() && self.nfo_task.is_completed() + && self.json_task.is_completed() } pub fn mark_uncompleted(&mut self) { @@ -327,6 +331,7 @@ impl DownloadProgress { self.subtitle_task.completed = false; self.cover_task.completed = false; self.nfo_task.completed = false; + self.json_task.completed = false; } pub fn get_ids_string(&self) -> String { @@ -379,6 +384,7 @@ fn create_normal_progresses_for_single( subtitle_task: tasks.subtitle, cover_task: tasks.cover, nfo_task: tasks.nfo, + json_task: tasks.json, create_ts, completed_ts: None, }; @@ -418,6 +424,7 @@ fn create_normal_progresses_for_single( subtitle_task: tasks.subtitle, cover_task: tasks.cover, nfo_task: tasks.nfo, + json_task: tasks.json, create_ts, completed_ts: None, }; @@ -457,6 +464,7 @@ fn create_normal_progresses_for_single( subtitle_task: tasks.subtitle.clone(), cover_task: tasks.cover.clone(), nfo_task: tasks.nfo.clone(), + json_task: tasks.json.clone(), create_ts, completed_ts: None, }; @@ -528,6 +536,7 @@ fn create_normal_progresses_for_season( subtitle_task: tasks.subtitle, cover_task: tasks.cover, nfo_task: tasks.nfo, + json_task: tasks.json, create_ts, completed_ts: None, }; @@ -567,6 +576,7 @@ fn create_normal_progresses_for_season( subtitle_task: tasks.subtitle, cover_task: tasks.cover, nfo_task: tasks.nfo, + json_task: tasks.json, create_ts, completed_ts: None, }; @@ -607,6 +617,7 @@ fn create_normal_progresses_for_season( subtitle_task: tasks.subtitle.clone(), cover_task: tasks.cover.clone(), nfo_task: tasks.nfo.clone(), + json_task: tasks.json.clone(), create_ts, completed_ts: None, }; @@ -628,6 +639,7 @@ struct Tasks { subtitle: SubtitleTask, cover: CoverTask, nfo: NfoTask, + json: JsonTask, } impl Tasks { @@ -679,6 +691,11 @@ impl Tasks { completed: false, }; + let json = JsonTask { + selected: config.download_json, + completed: false, + }; + Self { video, audio, @@ -687,6 +704,7 @@ impl Tasks { subtitle, cover, nfo, + json, } } } diff --git a/src-tauri/src/downloader/download_task.rs b/src-tauri/src/downloader/download_task.rs index c1a0a7d..79bf91c 100644 --- a/src-tauri/src/downloader/download_task.rs +++ b/src-tauri/src/downloader/download_task.rs @@ -341,6 +341,13 @@ impl DownloadTask { tracing::debug!("{ids_string} `{filename}`NFO下载完成"); } + if !progress.json_task.is_completed() { + self.download_json(&progress, &mut episode_info) + .await + .context(format!("{ids_string} `{filename}`下载JSON元数据失败"))?; + tracing::debug!("{ids_string} `{filename}`JSON元数据下载完成"); + } + let completed_ts = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) @@ -872,6 +879,40 @@ impl DownloadTask { Ok(()) } + async fn download_json( + &self, + progress: &DownloadProgress, + episode_info: &mut Option, + ) -> anyhow::Result<()> { + let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); + let (aid, ep_id, episode_type) = (progress.aid, progress.ep_id, progress.episode_type); + + let bili_client = self.app.get_bili_client(); + + let episode_info = episode_info + .get_or_init(&bili_client, aid, ep_id, episode_type) + .await?; + + let json_path = episode_dir.join(format!("{filename}-元数据.json")); + let json_string = match episode_info { + EpisodeInfo::Normal(info) => { + serde_json::to_string(&info).context("将普通视频信息转换为JSON失败")? + } + EpisodeInfo::Bangumi(info, _ep_id) => { + serde_json::to_string(&info).context("将番剧信息转换为JSON失败")? + } + EpisodeInfo::Cheese(info, _ep_id) => { + serde_json::to_string(&info).context("将课程信息转换为JSON失败")? + } + }; + std::fs::write(&json_path, json_string) + .context(format!("保存JSON到`{}`失败", json_path.display()))?; + + self.update_progress(|p| p.json_task.completed = true); + + Ok(()) + } + async fn sleep_between_task(&self) { let task_id = &self.task_id; let mut remaining_sec = self.app.get_config().read().task_download_interval_sec; diff --git a/src-tauri/src/downloader/tasks/json_task.rs b/src-tauri/src/downloader/tasks/json_task.rs new file mode 100644 index 0000000..1267e0b --- /dev/null +++ b/src-tauri/src/downloader/tasks/json_task.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct JsonTask { + pub selected: bool, + pub completed: bool, +} + +impl JsonTask { + pub fn is_completed(&self) -> bool { + !self.selected || self.completed + } +} diff --git a/src-tauri/src/downloader/tasks/mod.rs b/src-tauri/src/downloader/tasks/mod.rs index 048a59d..d8acc56 100644 --- a/src-tauri/src/downloader/tasks/mod.rs +++ b/src-tauri/src/downloader/tasks/mod.rs @@ -1,6 +1,7 @@ pub mod audio_task; pub mod cover_task; pub mod danmaku_task; +pub mod json_task; pub mod merge_task; pub mod nfo_task; pub mod subtitle_task; diff --git a/src/bindings.ts b/src/bindings.ts index 81fc69b..737dc7d 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -240,7 +240,7 @@ export type CntInfo = { collect: number; play: number; thumb_up: number; share: export type CntInfoInMedia = { collect: number; play: number; danmaku: number; vt: number; play_switch: number; reply: number; view_text_1: string } export type CodecType = "Unknown" | "Audio" | "AVC" | "HEVC" | "AV1" export type CommandError = { err_title: string; err_message: string } -export type Config = { download_dir: string; enable_file_logger: boolean; sessdata: string; prefer_video_quality: PreferVideoQuality; prefer_codec_type: PreferCodecType; prefer_audio_quality: PreferAudioQuality; download_video: boolean; download_audio: boolean; auto_merge: boolean; download_xml_danmaku: boolean; download_ass_danmaku: boolean; download_json_danmaku: boolean; download_subtitle: boolean; download_cover: boolean; download_nfo: boolean; dir_fmt: string; dir_fmt_for_part: string; time_fmt: string; task_concurrency: number; task_download_interval_sec: number; chunk_concurrency: number; chunk_download_interval_sec: number; danmaku_config: CanvasConfig } +export type Config = { download_dir: string; enable_file_logger: boolean; sessdata: string; prefer_video_quality: PreferVideoQuality; prefer_codec_type: PreferCodecType; prefer_audio_quality: PreferAudioQuality; download_video: boolean; download_audio: boolean; auto_merge: boolean; download_xml_danmaku: boolean; download_ass_danmaku: boolean; download_json_danmaku: boolean; download_subtitle: boolean; download_cover: boolean; download_nfo: boolean; download_json: boolean; dir_fmt: string; dir_fmt_for_part: string; time_fmt: string; task_concurrency: number; task_download_interval_sec: number; chunk_concurrency: number; chunk_download_interval_sec: number; danmaku_config: CanvasConfig } export type Consulting = { consulting_flag: boolean; consulting_url: string } export type ContentList = { bold: boolean; content: string; number: string } export type Cooperation = { link: string } @@ -259,7 +259,7 @@ export type DimensionInBangumi = { height: number; rotate: number; width: number export type DimensionInWatchLater = { width: number; height: number; rotate: number } export type Dolby = { type: number; audio: MediaInNormal[] | null } export type DownloadEvent = { event: "Speed"; data: { speed: string } } | { event: "TaskCreate"; data: { state: DownloadTaskState; progress: DownloadProgress } } | { event: "TaskStateUpdate"; data: { task_id: string; state: DownloadTaskState } } | { event: "TaskSleeping"; data: { task_id: string; remaining_sec: number } } | { event: "TaskDelete"; data: { task_id: string } } | { event: "ProgressPreparing"; data: { task_id: string } } | { event: "ProgressUpdate"; data: { progress: DownloadProgress } } -export type DownloadProgress = { task_id: string; episode_type: EpisodeType; aid: number; bvid: string | null; cid: number; ep_id: number | null; duration: number; pub_ts: number; collection_title: string; part_title: string | null; part_order: number | null; episode_title: string; episode_order: number; up_name: string | null; up_uid: number | null; up_avatar: string | null; episode_dir: string; filename: string; video_task: VideoTask; audio_task: AudioTask; merge_task: MergeTask; subtitle_task: SubtitleTask; danmaku_task: DanmakuTask; cover_task: CoverTask; nfo_task: NfoTask; create_ts: number; completed_ts: number | null } +export type DownloadProgress = { task_id: string; episode_type: EpisodeType; aid: number; bvid: string | null; cid: number; ep_id: number | null; duration: number; pub_ts: number; collection_title: string; part_title: string | null; part_order: number | null; episode_title: string; episode_order: number; up_name: string | null; up_uid: number | null; up_avatar: string | null; episode_dir: string; filename: string; video_task: VideoTask; audio_task: AudioTask; merge_task: MergeTask; subtitle_task: SubtitleTask; danmaku_task: DanmakuTask; cover_task: CoverTask; nfo_task: NfoTask; json_task: JsonTask; create_ts: number; completed_ts: number | null } export type DownloadTaskState = "Pending" | "Downloading" | "Paused" | "Completed" | "Failed" export type DurlDetailInBangumi = { size: number; ahead: string; length: number; vhead: string; backup_url: string[]; url: string; order: number; md5: string } export type DurlDetailInCheese = { size: number; ahead: string; length: number; vhead: string; backup_url: string[]; url: string; order: number; md5: string } @@ -292,6 +292,7 @@ export type IconResource = Record export type Img = { aspect_ratio: number; url: string } export type Info = { id: number; fid: number; mid: number; attr: number; title: string; cover: string; upper: Upper; cover_type: number; cnt_info: CntInfo; type: number; intro: string; ctime: number; mtime: number; state: number; fav_state: number; like_state: number; media_count: number; is_top: boolean } export type IpInfo = { ip: string; zone_ip: string; zone_id: number; country: string; province: string; city: string } +export type JsonTask = { selected: boolean; completed: boolean } export type JsonValue = null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue } export type LabelInPlayerInfo = { 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 } export type LabelInUserInfo = { 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 }