feat: 后端支持配置下载文件已存在时的行为

This commit is contained in:
lanyeeee
2026-01-21 05:24:44 +08:00
parent d76130a71f
commit 91a81fb1ba
12 changed files with 298 additions and 120 deletions
+9
View File
@@ -42,6 +42,7 @@ pub struct Config {
pub chunk_concurrency: usize,
pub chunk_download_interval_sec: u64,
pub danmaku_config: CanvasConfig,
pub file_exist_action: FileExistAction,
}
impl Config {
@@ -151,6 +152,7 @@ impl Config {
chunk_concurrency: 16,
chunk_download_interval_sec: 0,
danmaku_config: CanvasConfig::default(),
file_exist_action: FileExistAction::Overwrite,
}
}
}
@@ -162,3 +164,10 @@ pub enum ProxyMode {
System,
Custom,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Type)]
pub enum FileExistAction {
#[default]
Overwrite,
Skip,
}
+10 -6
View File
@@ -330,12 +330,12 @@ impl DownloadProgress {
pub fn mark_uncompleted(&mut self) {
self.video_task.mark_uncompleted();
self.audio_task.mark_uncompleted();
self.video_process_task.completed = false;
self.danmaku_task.completed = false;
self.subtitle_task.completed = false;
self.cover_task.completed = false;
self.nfo_task.completed = false;
self.json_task.completed = false;
self.video_process_task.mark_uncompleted();
self.danmaku_task.mark_uncompleted();
self.subtitle_task.mark_uncompleted();
self.cover_task.mark_uncompleted();
self.nfo_task.mark_uncompleted();
self.json_task.mark_uncompleted();
}
pub fn get_ids_string(&self) -> String {
@@ -656,6 +656,7 @@ impl Tasks {
content_length: 0,
chunks: Vec::new(),
completed: false,
skipped: false,
};
let audio = AudioTask {
@@ -665,6 +666,7 @@ impl Tasks {
content_length: 0,
chunks: Vec::new(),
completed: false,
skipped: false,
};
let video_process = VideoProcessTask {
@@ -679,6 +681,7 @@ impl Tasks {
ass_selected: config.download_ass_danmaku,
json_selected: config.download_json_danmaku,
completed: false,
skipped: false,
};
let subtitle = SubtitleTask {
@@ -695,6 +698,7 @@ impl Tasks {
let nfo = NfoTask {
selected: config.download_nfo,
completed: false,
skipped: false,
};
let json = JsonTask {
+8 -8
View File
@@ -292,7 +292,7 @@ impl DownloadTask {
.process(self, &progress)
.await
.context(format!("{ids_string} `{filename}`下载视频文件失败"))?;
tracing::debug!("{ids_string} `{filename}`视频下载完成");
tracing::debug!("{ids_string} `{filename}`视频下载任务完成");
}
if !audio_task.is_completed() && audio_task.content_length != 0 {
@@ -300,7 +300,7 @@ impl DownloadTask {
.process(self, &progress)
.await
.context(format!("{ids_string} `{filename}`下载音频文件失败"))?;
tracing::debug!("{ids_string} `{filename}`音频下载完成");
tracing::debug!("{ids_string} `{filename}`音频下载任务完成");
}
if !video_process_task.is_completed() {
@@ -308,7 +308,7 @@ impl DownloadTask {
.process(self, &progress, &mut player_info)
.await
.context(format!("{ids_string} `{filename}`视频处理失败"))?;
tracing::debug!("{ids_string} `{filename}`视频处理完成");
tracing::debug!("{ids_string} `{filename}`视频处理任务完成");
}
if !danmaku_task.is_completed() {
@@ -316,7 +316,7 @@ impl DownloadTask {
.process(self, &progress)
.await
.context(format!("{ids_string} `{filename}`下载弹幕失败"))?;
tracing::debug!("{ids_string} `{filename}`弹幕下载完成");
tracing::debug!("{ids_string} `{filename}`弹幕下载任务完成");
}
if !subtitle_task.is_completed() {
@@ -324,7 +324,7 @@ impl DownloadTask {
.process(self, &progress, &mut player_info)
.await
.context(format!("{ids_string} `{filename}`下载字幕失败"))?;
tracing::debug!("{ids_string} `{filename}`字幕下载完成");
tracing::debug!("{ids_string} `{filename}`字幕下载任务完成");
}
if !cover_task.is_completed() {
@@ -332,7 +332,7 @@ impl DownloadTask {
.process(self, &progress)
.await
.context(format!("{ids_string} `{filename}`下载封面失败"))?;
tracing::debug!("{ids_string} `{filename}`封面下载完成");
tracing::debug!("{ids_string} `{filename}`封面下载任务完成");
}
if !nfo_task.is_completed() {
@@ -340,7 +340,7 @@ impl DownloadTask {
.process(self, &progress, &mut episode_info)
.await
.context(format!("{ids_string} `{filename}`下载NFO失败"))?;
tracing::debug!("{ids_string} `{filename}`NFO下载完成");
tracing::debug!("{ids_string} `{filename}`NFO下载任务完成");
}
if !json_task.is_completed() {
@@ -348,7 +348,7 @@ impl DownloadTask {
.process(self, &progress, &mut episode_info)
.await
.context(format!("{ids_string} `{filename}`下载JSON元数据失败"))?;
tracing::debug!("{ids_string} `{filename}`JSON元数据下载完成");
tracing::debug!("{ids_string} `{filename}`JSON元数据下载任务完成");
}
let completed_ts = SystemTime::now()
+19 -5
View File
@@ -13,6 +13,7 @@ use tauri::AppHandle;
use tokio::task::JoinSet;
use crate::{
config::FileExistAction,
downloader::{
download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress,
download_task::DownloadTask, media_chunk::MediaChunk,
@@ -28,6 +29,7 @@ use crate::{
const CHUNK_SIZE: u64 = 2 * 1024 * 1024; // 2MB
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct AudioTask {
pub selected: bool,
pub url: String,
@@ -35,6 +37,7 @@ pub struct AudioTask {
pub content_length: u64,
pub chunks: Vec<MediaChunk>,
pub completed: bool,
pub skipped: bool,
}
impl AudioTask {
@@ -286,6 +289,7 @@ impl AudioTask {
self.chunks.iter_mut().for_each(|chunk| {
chunk.completed = false;
});
self.skipped = false;
}
pub fn is_completed(&self) -> bool {
@@ -298,10 +302,6 @@ impl AudioTask {
progress: &DownloadProgress,
) -> anyhow::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let temp_file_path = episode_dir.join(format!(
"{filename}.m4a.com.lanyeeee.bilibili-video-downloader"
));
let (audio_task, episode_title, ids_string) = {
(
progress.audio_task.clone(),
@@ -310,6 +310,21 @@ impl AudioTask {
)
};
let m4a_path = episode_dir.join(format!("{filename}.m4a"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && m4a_path.exists() {
tracing::debug!("{ids_string} `{filename}`音频文件已存在,跳过下载");
download_task.update_progress(|p| {
p.audio_task.skipped = true;
p.audio_task.completed = true;
});
return Ok(());
}
let temp_file_path = episode_dir.join(format!(
"{filename}.m4a.com.lanyeeee.bilibili-video-downloader"
));
let file = if temp_file_path.exists() {
// 如果文件已存在,则打开它
OpenOptions::new()
@@ -389,7 +404,6 @@ impl AudioTask {
}
// 重命名临时文件
let m4a_path = episode_dir.join(format!("{filename}.m4a"));
if m4a_path.exists() {
std::fs::remove_file(&m4a_path)
.context(format!("删除已存在的音频文件`{}`失败", m4a_path.display()))?;
@@ -10,6 +10,7 @@ use crate::{
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct CoverTask {
pub selected: bool,
pub url: String,
@@ -17,6 +18,10 @@ pub struct CoverTask {
}
impl CoverTask {
pub fn mark_uncompleted(&mut self) {
self.completed = false;
}
pub fn is_completed(&self) -> bool {
!self.selected || self.completed
}
+29 -3
View File
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use specta::Type;
use crate::{
config::FileExistAction,
danmaku_xml_to_ass::xml_to_ass,
downloader::{download_progress::DownloadProgress, download_task::DownloadTask},
extensions::AppHandleExt,
@@ -12,15 +13,22 @@ use crate::{
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
#[allow(clippy::struct_excessive_bools)]
pub struct DanmakuTask {
pub xml_selected: bool,
pub ass_selected: bool,
pub json_selected: bool,
pub completed: bool,
pub skipped: bool,
}
impl DanmakuTask {
pub fn mark_uncompleted(&mut self) {
self.completed = false;
self.skipped = false;
}
pub fn is_completed(&self) -> bool {
!self.xml_selected && !self.ass_selected && !self.json_selected || self.completed
}
@@ -32,6 +40,27 @@ impl DanmakuTask {
) -> anyhow::Result<()> {
let danmaku_task = &progress.danmaku_task;
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let xml_path = episode_dir.join(format!("{filename}.弹幕.xml"));
let ass_path = episode_dir.join(format!("{filename}.弹幕.ass"));
let json_path = episode_dir.join(format!("{filename}.弹幕.json"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip {
let skip_xml = !danmaku_task.xml_selected || xml_path.exists();
let skip_ass = !danmaku_task.ass_selected || ass_path.exists();
let skip_json = !danmaku_task.json_selected || json_path.exists();
if skip_xml && skip_ass && skip_json {
tracing::debug!("{ids_string} `{filename}`弹幕文件已存在,跳过下载");
download_task.update_progress(|p| {
p.danmaku_task.skipped = true;
p.danmaku_task.completed = true;
});
return Ok(());
}
}
let bili_client = download_task.app.get_bili_client();
let replies = bili_client
@@ -44,14 +73,12 @@ impl DanmakuTask {
.context("将弹幕转换为XML失败")?;
if danmaku_task.xml_selected {
let xml_path = episode_dir.join(format!("{filename}.弹幕.xml"));
std::fs::write(&xml_path, &xml)
.context(format!("保存弹幕XML到`{}`失败", xml_path.display()))?;
}
if danmaku_task.ass_selected {
let config = download_task.app.get_config().read().danmaku_config.clone();
let ass_path = episode_dir.join(format!("{filename}.弹幕.ass"));
let ass_file = File::create(&ass_path)
.context(format!("创建弹幕ASS文件`{}`失败", ass_path.display()))?;
let title = filename.to_string();
@@ -59,7 +86,6 @@ impl DanmakuTask {
}
if danmaku_task.json_selected {
let json_path = episode_dir.join(format!("{filename}.弹幕.json"));
let json_string = serde_json::to_string(&replies).context("将弹幕转换为JSON失败")?;
std::fs::write(&json_path, json_string)
.context(format!("保存弹幕JSON到`{}`失败", json_path.display()))?;
@@ -11,12 +11,17 @@ use crate::downloader::{
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct JsonTask {
pub selected: bool,
pub completed: bool,
}
impl JsonTask {
pub fn mark_uncompleted(&mut self) {
self.completed = false;
}
pub fn is_completed(&self) -> bool {
!self.selected || self.completed
}
+177 -86
View File
@@ -7,6 +7,7 @@ use specta::Type;
use yaserde::{YaDeserialize, YaSerialize};
use crate::{
config::FileExistAction,
downloader::{
download_progress::DownloadProgress,
download_task::DownloadTask,
@@ -19,12 +20,19 @@ use crate::{
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct NfoTask {
pub selected: bool,
pub completed: bool,
pub skipped: bool,
}
impl NfoTask {
pub fn mark_uncompleted(&mut self) {
self.completed = false;
self.skipped = false;
}
pub fn is_completed(&self) -> bool {
!self.selected || self.completed
}
@@ -35,105 +43,188 @@ impl NfoTask {
progress: &DownloadProgress,
episode_info: &mut Option<EpisodeInfo>,
) -> anyhow::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let episode_info = episode_info
.get_or_init(&download_task.app, progress)
.await?;
let bili_client = download_task.app.get_bili_client();
match episode_info {
EpisodeInfo::Normal(info) => {
let tags = bili_client
.get_tags(progress.aid)
.await
.context("获取视频标签失败")?;
let movie_nfo = info
.to_movie_nfo(tags)
.context("将普通视频信息转换为movie NFO失败")?;
let nfo_path = episode_dir.join(format!("{filename}.nfo"));
std::fs::write(&nfo_path, movie_nfo)
.context(format!("保存普通视频NFO到`{}`失败", nfo_path.display()))?;
if let Some(ugc_season) = &info.ugc_season {
let collection_cover = &ugc_season.cover;
let (cover_data, ext) = bili_client
.get_cover_data_and_ext(collection_cover)
.await
.context("获取普通视频合集封面失败")?;
let cover_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&cover_path, cover_data).context(format!(
"保存普通视频合集封面到`{}`失败",
cover_path.display()
))?;
}
self.process_normal(download_task, progress, info).await?;
}
EpisodeInfo::Bangumi(info, ep_id) => {
let tvshow_nfo = info
.to_tvshow_nfo()
.context("将番剧信息转换为tvshow NFO失败")?;
let tvshow_nfo_path = episode_dir.join("tvshow.nfo");
std::fs::write(&tvshow_nfo_path, tvshow_nfo)
.context(format!("保存番剧NFO到`{}`失败", tvshow_nfo_path.display()))?;
let episode_details_nfo = info
.to_episode_details_nfo(*ep_id)
.context("将番剧信息转换为episodedetail NFO失败")?;
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!(
"保存番剧NFO到`{}`失败",
episode_details_nfo_path.display()
))?;
let poster_url = &info.cover;
let (poster_data, ext) = bili_client
.get_cover_data_and_ext(poster_url)
.await
.context("获取番剧封面失败")?;
let poster_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&poster_path, poster_data)
.context(format!("保存番剧封面到`{}`失败", poster_path.display()))?;
let fanart_url = &info.bkg_cover;
if !fanart_url.is_empty() {
let (fanart_data, ext) = bili_client
.get_cover_data_and_ext(fanart_url)
.await
.context("获取番剧封面失败")?;
let fanart_path = episode_dir.join(format!("fanart.{ext}"));
std::fs::write(&fanart_path, fanart_data)
.context(format!("保存番剧封面到`{}`失败", fanart_path.display()))?;
}
self.process_bangumi(download_task, progress, info, ep_id)
.await?;
}
EpisodeInfo::Cheese(info, ep_id) => {
let tvshow_nfo = info
.to_tvshow_nfo()
.context("将课程信息转换为tvshow NFO失败")?;
let tvshow_nfo_path = episode_dir.join("tvshow.nfo");
std::fs::write(&tvshow_nfo_path, tvshow_nfo)
.context(format!("保存课程NFO到`{}`失败", tvshow_nfo_path.display()))?;
let episode_details_nfo = info
.to_episode_details_nfo(*ep_id)
.context("将课程信息转换为episodedetail NFO失败")?;
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!(
"保存课程NFO到`{}`失败",
episode_details_nfo_path.display()
))?;
let poster_url = &info.cover;
let (poster_data, ext) = bili_client
.get_cover_data_and_ext(poster_url)
.await
.context("获取课程封面失败")?;
let poster_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&poster_path, poster_data)
.context(format!("保存课程封面到`{}`失败", poster_path.display()))?;
self.process_cheese(download_task, progress, info, ep_id)
.await?;
}
}
Ok(())
}
async fn process_normal(
&self,
download_task: &Arc<DownloadTask>,
progress: &DownloadProgress,
info: &NormalInfo,
) -> anyhow::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let nfo_path = episode_dir.join(format!("{filename}.nfo"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && nfo_path.exists() {
tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载");
download_task.update_progress(|p| {
p.nfo_task.skipped = true;
p.nfo_task.completed = true;
});
return Ok(());
}
let bili_client = download_task.app.get_bili_client();
let tags = bili_client
.get_tags(progress.aid)
.await
.context("获取视频标签失败")?;
let movie_nfo = info
.to_movie_nfo(tags)
.context("将普通视频信息转换为movie NFO失败")?;
std::fs::write(&nfo_path, movie_nfo)
.context(format!("保存普通视频NFO到`{}`失败", nfo_path.display()))?;
if let Some(ugc_season) = &info.ugc_season {
let collection_cover = &ugc_season.cover;
let (cover_data, ext) = bili_client
.get_cover_data_and_ext(collection_cover)
.await
.context("获取普通视频合集封面失败")?;
let cover_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&cover_path, cover_data).context(format!(
"保存普通视频合集封面到`{}`失败",
cover_path.display()
))?;
}
download_task.update_progress(|p| p.nfo_task.completed = true);
Ok(())
}
async fn process_bangumi(
&self,
download_task: &Arc<DownloadTask>,
progress: &DownloadProgress,
info: &BangumiInfo,
ep_id: &i64,
) -> anyhow::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && episode_details_nfo_path.exists() {
tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载");
download_task.update_progress(|p| {
p.nfo_task.skipped = true;
p.nfo_task.completed = true;
});
return Ok(());
}
let bili_client = download_task.app.get_bili_client();
let tvshow_nfo = info
.to_tvshow_nfo()
.context("将番剧信息转换为tvshow NFO失败")?;
let tvshow_nfo_path = episode_dir.join("tvshow.nfo");
std::fs::write(&tvshow_nfo_path, tvshow_nfo)
.context(format!("保存番剧NFO到`{}`失败", tvshow_nfo_path.display()))?;
let episode_details_nfo = info
.to_episode_details_nfo(*ep_id)
.context("将番剧信息转换为episodedetail NFO失败")?;
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!(
"保存番剧NFO到`{}`失败",
episode_details_nfo_path.display()
))?;
let poster_url = &info.cover;
let (poster_data, ext) = bili_client
.get_cover_data_and_ext(poster_url)
.await
.context("获取番剧封面失败")?;
let poster_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&poster_path, poster_data)
.context(format!("保存番剧封面到`{}`失败", poster_path.display()))?;
let fanart_url = &info.bkg_cover;
if !fanart_url.is_empty() {
let (fanart_data, ext) = bili_client
.get_cover_data_and_ext(fanart_url)
.await
.context("获取番剧封面失败")?;
let fanart_path = episode_dir.join(format!("fanart.{ext}"));
std::fs::write(&fanart_path, fanart_data)
.context(format!("保存番剧封面到`{}`失败", fanart_path.display()))?;
}
download_task.update_progress(|p| p.nfo_task.completed = true);
Ok(())
}
async fn process_cheese(
&self,
download_task: &Arc<DownloadTask>,
progress: &DownloadProgress,
info: &CheeseInfo,
ep_id: &i64,
) -> anyhow::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && episode_details_nfo_path.exists() {
tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载");
download_task.update_progress(|p| {
p.nfo_task.skipped = true;
p.nfo_task.completed = true;
});
return Ok(());
}
let bili_client = download_task.app.get_bili_client();
let tvshow_nfo = info
.to_tvshow_nfo()
.context("将课程信息转换为tvshow NFO失败")?;
let tvshow_nfo_path = episode_dir.join("tvshow.nfo");
std::fs::write(&tvshow_nfo_path, tvshow_nfo)
.context(format!("保存课程NFO到`{}`失败", tvshow_nfo_path.display()))?;
let episode_details_nfo = info
.to_episode_details_nfo(*ep_id)
.context("将课程信息转换为episodedetail NFO失败")?;
std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!(
"保存课程NFO到`{}`失败",
episode_details_nfo_path.display()
))?;
let poster_url = &info.cover;
let (poster_data, ext) = bili_client
.get_cover_data_and_ext(poster_url)
.await
.context("获取课程封面失败")?;
let poster_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&poster_path, poster_data)
.context(format!("保存课程封面到`{}`失败", poster_path.display()))?;
download_task.update_progress(|p| p.nfo_task.completed = true);
Ok(())
@@ -12,12 +12,17 @@ use crate::{
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct SubtitleTask {
pub selected: bool,
pub completed: bool,
}
impl SubtitleTask {
pub fn mark_uncompleted(&mut self) {
self.completed = false;
}
pub fn is_completed(&self) -> bool {
!self.selected || self.completed
}
@@ -17,6 +17,7 @@ use crate::{
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
#[allow(clippy::struct_excessive_bools)]
pub struct VideoProcessTask {
pub merge_selected: bool,
@@ -26,6 +27,10 @@ pub struct VideoProcessTask {
}
impl VideoProcessTask {
pub fn mark_uncompleted(&mut self) {
self.completed = false;
}
pub fn is_completed(&self) -> bool {
!self.merge_selected && !self.embed_chapter_selected && !self.embed_skip_selected
|| self.completed
@@ -44,7 +49,6 @@ impl VideoProcessTask {
.await
.context("自动合并+嵌入章节元数据失败")?;
} else if self.merge_selected {
println!("merge1");
self.merge(download_task, progress)
.await
.context("自动合并失败")?;
+20 -6
View File
@@ -13,6 +13,7 @@ use tauri::AppHandle;
use tokio::task::JoinSet;
use crate::{
config::FileExistAction,
downloader::{
download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress,
download_task::DownloadTask, media_chunk::MediaChunk,
@@ -28,6 +29,7 @@ use crate::{
const CHUNK_SIZE: u64 = 2 * 1024 * 1024; // 2MB
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
pub struct VideoTask {
pub selected: bool,
pub url: String,
@@ -36,6 +38,7 @@ pub struct VideoTask {
pub content_length: u64,
pub chunks: Vec<MediaChunk>,
pub completed: bool,
pub skipped: bool,
}
impl VideoTask {
@@ -309,23 +312,20 @@ impl VideoTask {
self.chunks.iter_mut().for_each(|chunk| {
chunk.completed = false;
});
self.skipped = false;
}
pub fn is_completed(&self) -> bool {
!self.selected || self.completed
}
#[allow(clippy::too_many_lines)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
progress: &DownloadProgress,
) -> anyhow::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let temp_file_path = episode_dir.join(format!(
"{filename}.mp4.com.lanyeeee.bilibili-video-downloader"
));
let (video_task, episode_title, ids_string) = {
let progress = download_task.progress.read();
(
@@ -335,6 +335,21 @@ impl VideoTask {
)
};
let mp4_path = episode_dir.join(format!("{filename}.mp4"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && mp4_path.exists() {
tracing::debug!("{ids_string} `{filename}`视频文件已存在,跳过下载");
download_task.update_progress(|p| {
p.video_task.skipped = true;
p.video_task.completed = true;
});
return Ok(());
}
let temp_file_path = episode_dir.join(format!(
"{filename}.mp4.com.lanyeeee.bilibili-video-downloader"
));
let file = if temp_file_path.exists() {
// 如果临时文件已存在,则打开它
OpenOptions::new()
@@ -416,7 +431,6 @@ impl VideoTask {
}
// 重命名临时文件
let mp4_path = episode_dir.join(format!("{filename}.mp4"));
if mp4_path.exists() {
std::fs::remove_file(&mp4_path)
.context(format!("删除已存在的视频文件`{}`失败", mp4_path.display()))?;