feat: 支持跳过视频的某些处理部分 (#492)
This commit is contained in:
@@ -9,7 +9,7 @@ use validator::Validate;
|
||||
use crate::bilibili::{Credential, DanmakuOption, FilterOption};
|
||||
use crate::config::LegacyConfig;
|
||||
use crate::config::default::{default_auth_token, default_bind_address, default_time_format};
|
||||
use crate::config::item::{ConcurrentLimit, NFOTimeType};
|
||||
use crate::config::item::{ConcurrentLimit, NFOTimeType, SkipOption};
|
||||
use crate::utils::model::{load_db_config, save_db_config};
|
||||
|
||||
pub static CONFIG_DIR: LazyLock<PathBuf> =
|
||||
@@ -22,6 +22,8 @@ pub struct Config {
|
||||
pub credential: Credential,
|
||||
pub filter_option: FilterOption,
|
||||
pub danmaku_option: DanmakuOption,
|
||||
#[serde(default)]
|
||||
pub skip_option: SkipOption,
|
||||
pub video_name: String,
|
||||
pub page_name: String,
|
||||
pub interval: u64,
|
||||
@@ -94,6 +96,7 @@ impl Default for Config {
|
||||
credential: Credential::default(),
|
||||
filter_option: FilterOption::default(),
|
||||
danmaku_option: DanmakuOption::default(),
|
||||
skip_option: SkipOption::default(),
|
||||
video_name: "{{title}}".to_owned(),
|
||||
page_name: "{{bvid}}".to_owned(),
|
||||
interval: 1200,
|
||||
@@ -115,6 +118,7 @@ impl From<LegacyConfig> for Config {
|
||||
credential: legacy.credential,
|
||||
filter_option: legacy.filter_option,
|
||||
danmaku_option: legacy.danmaku_option,
|
||||
skip_option: SkipOption::default(),
|
||||
video_name: legacy.video_name,
|
||||
page_name: legacy.page_name,
|
||||
interval: legacy.interval,
|
||||
|
||||
@@ -69,6 +69,15 @@ impl Default for ConcurrentLimit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
pub struct SkipOption {
|
||||
pub no_poster: bool,
|
||||
pub no_video_nfo: bool,
|
||||
pub no_upper: bool,
|
||||
pub no_danmaku: bool,
|
||||
pub no_subtitle: bool,
|
||||
}
|
||||
|
||||
pub trait PathSafeTemplate {
|
||||
fn path_safe_register(&mut self, name: &'static str, template: impl Into<String>) -> Result<()>;
|
||||
fn path_safe_render(&self, name: &'static str, data: &serde_json::Value) -> Result<String>;
|
||||
|
||||
@@ -10,7 +10,7 @@ mod versioned_config;
|
||||
pub use crate::config::args::{ARGS, version};
|
||||
pub use crate::config::current::{CONFIG_DIR, Config};
|
||||
pub use crate::config::handlebar::TEMPLATE;
|
||||
pub use crate::config::item::{NFOTimeType, PathSafeTemplate, RateLimit};
|
||||
pub use crate::config::item::{NFOTimeType, PathSafeTemplate, RateLimit, SkipOption};
|
||||
pub use crate::config::legacy::LegacyConfig;
|
||||
pub use crate::config::versioned_cache::VersionedCache;
|
||||
pub use crate::config::versioned_config::VersionedConfig;
|
||||
|
||||
@@ -14,7 +14,7 @@ use tokio::sync::Semaphore;
|
||||
|
||||
use crate::adapter::{VideoSource, VideoSourceEnum};
|
||||
use crate::bilibili::{BestStream, BiliClient, BiliError, Dimension, PageInfo, Video, VideoInfo};
|
||||
use crate::config::{ARGS, PathSafeTemplate, TEMPLATE, VersionedConfig};
|
||||
use crate::config::{ARGS, PathSafeTemplate, SkipOption, TEMPLATE, VersionedConfig};
|
||||
use crate::downloader::Downloader;
|
||||
use crate::error::{DownloadAbortError, ExecutionStatus, ProcessPageError};
|
||||
use crate::utils::format_arg::{page_format_args, video_format_args};
|
||||
@@ -163,6 +163,8 @@ pub async fn download_unprocessed_videos(
|
||||
video_source.log_download_video_start();
|
||||
let semaphore = Semaphore::new(VersionedConfig::get().load().concurrent_limit.video);
|
||||
let downloader = Downloader::new(bili_client.client.clone());
|
||||
// 提前获取 skip_option,保证单个视频源在整个下载过程中配置不变
|
||||
let skip_option = &VersionedConfig::get().load().skip_option;
|
||||
let unhandled_videos_pages = filter_unhandled_video_pages(video_source.filter_expr(), connection).await?;
|
||||
let mut assigned_upper = HashSet::new();
|
||||
let tasks = unhandled_videos_pages
|
||||
@@ -179,6 +181,7 @@ pub async fn download_unprocessed_videos(
|
||||
&semaphore,
|
||||
&downloader,
|
||||
should_download_upper,
|
||||
skip_option,
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
@@ -218,6 +221,7 @@ pub async fn download_video_pages(
|
||||
semaphore: &Semaphore,
|
||||
downloader: &Downloader,
|
||||
should_download_upper: bool,
|
||||
skip_option: &SkipOption,
|
||||
) -> Result<video::ActiveModel> {
|
||||
let _permit = semaphore.acquire().await.context("acquire semaphore failed")?;
|
||||
let mut status = VideoStatus::from(video_model.download_status);
|
||||
@@ -239,7 +243,7 @@ pub async fn download_video_pages(
|
||||
let (res_1, res_2, res_3, res_4, res_5) = tokio::join!(
|
||||
// 下载视频封面
|
||||
fetch_video_poster(
|
||||
separate_status[0] && !is_single_page,
|
||||
separate_status[0] && !is_single_page && !skip_option.no_poster,
|
||||
&video_model,
|
||||
downloader,
|
||||
base_path.join("poster.jpg"),
|
||||
@@ -247,20 +251,20 @@ pub async fn download_video_pages(
|
||||
),
|
||||
// 生成视频信息的 nfo
|
||||
generate_video_nfo(
|
||||
separate_status[1] && !is_single_page,
|
||||
separate_status[1] && !is_single_page && !skip_option.no_video_nfo,
|
||||
&video_model,
|
||||
base_path.join("tvshow.nfo"),
|
||||
),
|
||||
// 下载 Up 主头像
|
||||
fetch_upper_face(
|
||||
separate_status[2] && should_download_upper,
|
||||
separate_status[2] && should_download_upper && !skip_option.no_upper,
|
||||
&video_model,
|
||||
downloader,
|
||||
base_upper_path.join("folder.jpg"),
|
||||
),
|
||||
// 生成 Up 主信息的 nfo
|
||||
generate_upper_nfo(
|
||||
separate_status[3] && should_download_upper,
|
||||
separate_status[3] && should_download_upper && !skip_option.no_upper,
|
||||
&video_model,
|
||||
base_upper_path.join("person.nfo"),
|
||||
),
|
||||
@@ -272,7 +276,8 @@ pub async fn download_video_pages(
|
||||
pages,
|
||||
connection,
|
||||
downloader,
|
||||
&base_path
|
||||
&base_path,
|
||||
skip_option,
|
||||
)
|
||||
);
|
||||
let results = [res_1, res_2, res_3, res_4, res_5]
|
||||
@@ -309,6 +314,7 @@ pub async fn download_video_pages(
|
||||
}
|
||||
|
||||
/// 分发并执行分页下载任务,当且仅当所有分页成功下载或达到最大重试次数时返回 Ok,否则根据失败原因返回对应的错误
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn dispatch_download_page(
|
||||
should_run: bool,
|
||||
bili_client: &BiliClient,
|
||||
@@ -317,6 +323,7 @@ pub async fn dispatch_download_page(
|
||||
connection: &DatabaseConnection,
|
||||
downloader: &Downloader,
|
||||
base_path: &Path,
|
||||
skip_option: &SkipOption,
|
||||
) -> Result<ExecutionStatus> {
|
||||
if !should_run {
|
||||
return Ok(ExecutionStatus::Skipped);
|
||||
@@ -332,6 +339,7 @@ pub async fn dispatch_download_page(
|
||||
&child_semaphore,
|
||||
downloader,
|
||||
base_path,
|
||||
skip_option,
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
@@ -382,6 +390,7 @@ pub async fn download_page(
|
||||
semaphore: &Semaphore,
|
||||
downloader: &Downloader,
|
||||
base_path: &Path,
|
||||
skip_option: &SkipOption,
|
||||
) -> Result<page::ActiveModel> {
|
||||
let _permit = semaphore.acquire().await.context("acquire semaphore failed")?;
|
||||
let mut status = PageStatus::from(page_model.download_status);
|
||||
@@ -437,7 +446,7 @@ pub async fn download_page(
|
||||
let (res_1, res_2, res_3, res_4, res_5) = tokio::join!(
|
||||
// 下载分页封面
|
||||
fetch_page_poster(
|
||||
separate_status[0],
|
||||
separate_status[0] && !skip_option.no_poster,
|
||||
video_model,
|
||||
&page_model,
|
||||
downloader,
|
||||
@@ -454,11 +463,28 @@ pub async fn download_page(
|
||||
&video_path
|
||||
),
|
||||
// 生成分页视频信息的 nfo
|
||||
generate_page_nfo(separate_status[2], video_model, &page_model, nfo_path),
|
||||
generate_page_nfo(
|
||||
separate_status[2] && !skip_option.no_video_nfo,
|
||||
video_model,
|
||||
&page_model,
|
||||
nfo_path
|
||||
),
|
||||
// 下载分页弹幕
|
||||
fetch_page_danmaku(separate_status[3], bili_client, video_model, &page_info, danmaku_path),
|
||||
fetch_page_danmaku(
|
||||
separate_status[3] && !skip_option.no_danmaku,
|
||||
bili_client,
|
||||
video_model,
|
||||
&page_info,
|
||||
danmaku_path
|
||||
),
|
||||
// 下载分页字幕
|
||||
fetch_page_subtitle(separate_status[4], bili_client, video_model, &page_info, &subtitle_path)
|
||||
fetch_page_subtitle(
|
||||
separate_status[4] && !skip_option.no_subtitle,
|
||||
bili_client,
|
||||
video_model,
|
||||
&page_info,
|
||||
&subtitle_path
|
||||
)
|
||||
);
|
||||
let results = [res_1, res_2, res_3, res_4, res_5]
|
||||
.into_iter()
|
||||
|
||||
@@ -246,6 +246,14 @@ export interface DanmakuOption {
|
||||
time_offset: number;
|
||||
}
|
||||
|
||||
export interface SkipOption {
|
||||
no_poster: boolean;
|
||||
no_video_nfo: boolean;
|
||||
no_upper: boolean;
|
||||
no_danmaku: boolean;
|
||||
no_subtitle: boolean;
|
||||
}
|
||||
|
||||
export interface RateLimit {
|
||||
limit: number;
|
||||
duration: number;
|
||||
@@ -270,6 +278,7 @@ export interface Config {
|
||||
credential: Credential;
|
||||
filter_option: FilterOption;
|
||||
danmaku_option: DanmakuOption;
|
||||
skip_option: SkipOption;
|
||||
video_name: string;
|
||||
page_name: string;
|
||||
interval: number;
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
<Tabs.List class="grid w-full grid-cols-5">
|
||||
<Tabs.Trigger value="basic">基本设置</Tabs.Trigger>
|
||||
<Tabs.Trigger value="auth">B站认证</Tabs.Trigger>
|
||||
<Tabs.Trigger value="filter">视频质量</Tabs.Trigger>
|
||||
<Tabs.Trigger value="filter">视频处理</Tabs.Trigger>
|
||||
<Tabs.Trigger value="danmaku">弹幕渲染</Tabs.Trigger>
|
||||
<Tabs.Trigger value="advanced">高级设置</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
@@ -431,6 +431,8 @@
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<Label>特殊流排除选项</Label>
|
||||
<p class="text-muted-foreground text-sm">排除某些类型的特殊流</p>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-dolby-video" bind:checked={formData.filter_option.no_dolby_video} />
|
||||
<Label for="no-dolby-video">排除杜比视界视频</Label>
|
||||
@@ -448,6 +450,33 @@
|
||||
<Label for="no-hires">排除Hi-RES音频</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<Label>处理跳过选项</Label>
|
||||
<p class="text-muted-foreground text-sm">在视频处理部分跳过某些执行环节</p>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-dolby-video" bind:checked={formData.skip_option.no_poster} />
|
||||
<Label for="no-dolby-video">跳过视频封面</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-dolby-audio" bind:checked={formData.skip_option.no_video_nfo} />
|
||||
<Label for="no-dolby-audio">跳过视频 NFO</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-hdr" bind:checked={formData.skip_option.no_upper} />
|
||||
<Label for="no-hdr">跳过 Up 主头像、信息</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-hires" bind:checked={formData.skip_option.no_danmaku} />
|
||||
<Label for="no-hires">跳过弹幕</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="no-hires" bind:checked={formData.skip_option.no_subtitle} />
|
||||
<Label for="no-hires">跳过字幕</Label>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- 弹幕设置 -->
|
||||
|
||||
Reference in New Issue
Block a user