mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-08 09:06:53 +08:00
feat: 后端支持json元数据下载
This commit is contained in:
@@ -26,6 +26,7 @@ pub struct Config {
|
|||||||
pub download_subtitle: bool,
|
pub download_subtitle: bool,
|
||||||
pub download_cover: bool,
|
pub download_cover: bool,
|
||||||
pub download_nfo: bool,
|
pub download_nfo: bool,
|
||||||
|
pub download_json: bool,
|
||||||
pub dir_fmt: String,
|
pub dir_fmt: String,
|
||||||
pub dir_fmt_for_part: String,
|
pub dir_fmt_for_part: String,
|
||||||
pub time_fmt: String,
|
pub time_fmt: String,
|
||||||
@@ -106,6 +107,7 @@ impl Config {
|
|||||||
download_subtitle: true,
|
download_subtitle: true,
|
||||||
download_cover: true,
|
download_cover: true,
|
||||||
download_nfo: true,
|
download_nfo: true,
|
||||||
|
download_json: true,
|
||||||
dir_fmt: "{collection_title}/{episode_title}".to_string(),
|
dir_fmt: "{collection_title}/{episode_title}".to_string(),
|
||||||
dir_fmt_for_part: DEFAULT_FMT_FOR_PART.to_string(),
|
dir_fmt_for_part: DEFAULT_FMT_FOR_PART.to_string(),
|
||||||
time_fmt: "%Y-%m-%d_%H-%M-%S".to_string(),
|
time_fmt: "%Y-%m-%d_%H-%M-%S".to_string(),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
config::Config,
|
config::Config,
|
||||||
downloader::tasks::{
|
downloader::tasks::{
|
||||||
audio_task::AudioTask, cover_task::CoverTask, danmaku_task::DanmakuTask,
|
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,
|
video_task::VideoTask,
|
||||||
},
|
},
|
||||||
extensions::AppHandleExt,
|
extensions::AppHandleExt,
|
||||||
@@ -57,6 +57,7 @@ pub struct DownloadProgress {
|
|||||||
pub danmaku_task: DanmakuTask,
|
pub danmaku_task: DanmakuTask,
|
||||||
pub cover_task: CoverTask,
|
pub cover_task: CoverTask,
|
||||||
pub nfo_task: NfoTask,
|
pub nfo_task: NfoTask,
|
||||||
|
pub json_task: JsonTask,
|
||||||
pub create_ts: u64,
|
pub create_ts: u64,
|
||||||
pub completed_ts: Option<u64>,
|
pub completed_ts: Option<u64>,
|
||||||
}
|
}
|
||||||
@@ -128,6 +129,7 @@ impl DownloadProgress {
|
|||||||
subtitle_task: tasks.subtitle,
|
subtitle_task: tasks.subtitle,
|
||||||
cover_task: tasks.cover,
|
cover_task: tasks.cover,
|
||||||
nfo_task: tasks.nfo,
|
nfo_task: tasks.nfo,
|
||||||
|
json_task: tasks.json,
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -178,6 +180,7 @@ impl DownloadProgress {
|
|||||||
subtitle_task: tasks.subtitle,
|
subtitle_task: tasks.subtitle,
|
||||||
cover_task: tasks.cover,
|
cover_task: tasks.cover,
|
||||||
nfo_task: tasks.nfo,
|
nfo_task: tasks.nfo,
|
||||||
|
json_task: tasks.json,
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -317,6 +320,7 @@ impl DownloadProgress {
|
|||||||
&& self.subtitle_task.is_completed()
|
&& self.subtitle_task.is_completed()
|
||||||
&& self.cover_task.is_completed()
|
&& self.cover_task.is_completed()
|
||||||
&& self.nfo_task.is_completed()
|
&& self.nfo_task.is_completed()
|
||||||
|
&& self.json_task.is_completed()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_uncompleted(&mut self) {
|
pub fn mark_uncompleted(&mut self) {
|
||||||
@@ -327,6 +331,7 @@ impl DownloadProgress {
|
|||||||
self.subtitle_task.completed = false;
|
self.subtitle_task.completed = false;
|
||||||
self.cover_task.completed = false;
|
self.cover_task.completed = false;
|
||||||
self.nfo_task.completed = false;
|
self.nfo_task.completed = false;
|
||||||
|
self.json_task.completed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ids_string(&self) -> String {
|
pub fn get_ids_string(&self) -> String {
|
||||||
@@ -379,6 +384,7 @@ fn create_normal_progresses_for_single(
|
|||||||
subtitle_task: tasks.subtitle,
|
subtitle_task: tasks.subtitle,
|
||||||
cover_task: tasks.cover,
|
cover_task: tasks.cover,
|
||||||
nfo_task: tasks.nfo,
|
nfo_task: tasks.nfo,
|
||||||
|
json_task: tasks.json,
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -418,6 +424,7 @@ fn create_normal_progresses_for_single(
|
|||||||
subtitle_task: tasks.subtitle,
|
subtitle_task: tasks.subtitle,
|
||||||
cover_task: tasks.cover,
|
cover_task: tasks.cover,
|
||||||
nfo_task: tasks.nfo,
|
nfo_task: tasks.nfo,
|
||||||
|
json_task: tasks.json,
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -457,6 +464,7 @@ fn create_normal_progresses_for_single(
|
|||||||
subtitle_task: tasks.subtitle.clone(),
|
subtitle_task: tasks.subtitle.clone(),
|
||||||
cover_task: tasks.cover.clone(),
|
cover_task: tasks.cover.clone(),
|
||||||
nfo_task: tasks.nfo.clone(),
|
nfo_task: tasks.nfo.clone(),
|
||||||
|
json_task: tasks.json.clone(),
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -528,6 +536,7 @@ fn create_normal_progresses_for_season(
|
|||||||
subtitle_task: tasks.subtitle,
|
subtitle_task: tasks.subtitle,
|
||||||
cover_task: tasks.cover,
|
cover_task: tasks.cover,
|
||||||
nfo_task: tasks.nfo,
|
nfo_task: tasks.nfo,
|
||||||
|
json_task: tasks.json,
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -567,6 +576,7 @@ fn create_normal_progresses_for_season(
|
|||||||
subtitle_task: tasks.subtitle,
|
subtitle_task: tasks.subtitle,
|
||||||
cover_task: tasks.cover,
|
cover_task: tasks.cover,
|
||||||
nfo_task: tasks.nfo,
|
nfo_task: tasks.nfo,
|
||||||
|
json_task: tasks.json,
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -607,6 +617,7 @@ fn create_normal_progresses_for_season(
|
|||||||
subtitle_task: tasks.subtitle.clone(),
|
subtitle_task: tasks.subtitle.clone(),
|
||||||
cover_task: tasks.cover.clone(),
|
cover_task: tasks.cover.clone(),
|
||||||
nfo_task: tasks.nfo.clone(),
|
nfo_task: tasks.nfo.clone(),
|
||||||
|
json_task: tasks.json.clone(),
|
||||||
create_ts,
|
create_ts,
|
||||||
completed_ts: None,
|
completed_ts: None,
|
||||||
};
|
};
|
||||||
@@ -628,6 +639,7 @@ struct Tasks {
|
|||||||
subtitle: SubtitleTask,
|
subtitle: SubtitleTask,
|
||||||
cover: CoverTask,
|
cover: CoverTask,
|
||||||
nfo: NfoTask,
|
nfo: NfoTask,
|
||||||
|
json: JsonTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tasks {
|
impl Tasks {
|
||||||
@@ -679,6 +691,11 @@ impl Tasks {
|
|||||||
completed: false,
|
completed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let json = JsonTask {
|
||||||
|
selected: config.download_json,
|
||||||
|
completed: false,
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
video,
|
video,
|
||||||
audio,
|
audio,
|
||||||
@@ -687,6 +704,7 @@ impl Tasks {
|
|||||||
subtitle,
|
subtitle,
|
||||||
cover,
|
cover,
|
||||||
nfo,
|
nfo,
|
||||||
|
json,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,6 +341,13 @@ impl DownloadTask {
|
|||||||
tracing::debug!("{ids_string} `{filename}`NFO下载完成");
|
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()
|
let completed_ts = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.map(|d| d.as_secs())
|
.map(|d| d.as_secs())
|
||||||
@@ -872,6 +879,40 @@ impl DownloadTask {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn download_json(
|
||||||
|
&self,
|
||||||
|
progress: &DownloadProgress,
|
||||||
|
episode_info: &mut Option<EpisodeInfo>,
|
||||||
|
) -> 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) {
|
async fn sleep_between_task(&self) {
|
||||||
let task_id = &self.task_id;
|
let task_id = &self.task_id;
|
||||||
let mut remaining_sec = self.app.get_config().read().task_download_interval_sec;
|
let mut remaining_sec = self.app.get_config().read().task_download_interval_sec;
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod audio_task;
|
pub mod audio_task;
|
||||||
pub mod cover_task;
|
pub mod cover_task;
|
||||||
pub mod danmaku_task;
|
pub mod danmaku_task;
|
||||||
|
pub mod json_task;
|
||||||
pub mod merge_task;
|
pub mod merge_task;
|
||||||
pub mod nfo_task;
|
pub mod nfo_task;
|
||||||
pub mod subtitle_task;
|
pub mod subtitle_task;
|
||||||
|
|||||||
+3
-2
@@ -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 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 CodecType = "Unknown" | "Audio" | "AVC" | "HEVC" | "AV1"
|
||||||
export type CommandError = { err_title: string; err_message: string }
|
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 Consulting = { consulting_flag: boolean; consulting_url: string }
|
||||||
export type ContentList = { bold: boolean; content: string; number: string }
|
export type ContentList = { bold: boolean; content: string; number: string }
|
||||||
export type Cooperation = { link: 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 DimensionInWatchLater = { width: number; height: number; rotate: number }
|
||||||
export type Dolby = { type: number; audio: MediaInNormal[] | null }
|
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 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 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 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 }
|
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<string, never>
|
|||||||
export type Img = { aspect_ratio: number; url: string }
|
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 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 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 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 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 }
|
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 }
|
||||||
|
|||||||
Reference in New Issue
Block a user