feat: 支持设置 画质、音质、编码 的优先级

This commit is contained in:
lanyeeee
2025-09-05 05:06:38 +08:00
parent ae73e474c7
commit 5ed41ef7b6
10 changed files with 219 additions and 277 deletions
+33 -104
View File
@@ -1,11 +1,13 @@
use std::path::{Path, PathBuf};
use num_enum::{FromPrimitive, IntoPrimitive};
use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::{AppHandle, Manager};
use crate::danmaku_xml_to_ass::canvas::CanvasConfig;
use crate::{
danmaku_xml_to_ass::canvas::CanvasConfig,
types::{audio_quality::AudioQuality, codec_type::CodecType, video_quality::VideoQuality},
};
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
#[allow(clippy::struct_excessive_bools)]
@@ -14,9 +16,9 @@ pub struct Config {
pub download_dir: PathBuf,
pub enable_file_logger: bool,
pub sessdata: String,
pub prefer_video_quality: PreferVideoQuality,
pub prefer_codec_type: PreferCodecType,
pub prefer_audio_quality: PreferAudioQuality,
pub video_quality_priority: Vec<VideoQuality>,
pub codec_type_priority: Vec<CodecType>,
pub audio_quality_priority: Vec<AudioQuality>,
pub download_video: bool,
pub download_audio: bool,
pub auto_merge: bool,
@@ -96,13 +98,36 @@ impl Config {
fn default(app_data_dir: &Path) -> Config {
const DEFAULT_FMT_FOR_PART: &str =
"{collection_title}/{episode_title}/{episode_title}-P{part_order} {part_title}";
let default_video_quality_priority = vec![
VideoQuality::Video8K,
VideoQuality::VideoDolby,
VideoQuality::VideoHDR,
VideoQuality::Video4K,
VideoQuality::Video1080P60,
VideoQuality::Video1080PPlus,
VideoQuality::Video1080P,
VideoQuality::VideoAiRepair,
VideoQuality::Video720P60,
VideoQuality::Video720P,
VideoQuality::Video480P,
VideoQuality::Video360P,
VideoQuality::Video240P,
];
let default_audio_quality_priority = vec![
AudioQuality::AudioHiRes,
AudioQuality::AudioDolby,
AudioQuality::Audio192K,
AudioQuality::Audio132K,
AudioQuality::Audio64K,
];
Config {
download_dir: app_data_dir.join("视频下载"),
enable_file_logger: true,
sessdata: String::new(),
prefer_video_quality: PreferVideoQuality::Best,
prefer_codec_type: PreferCodecType::AVC,
prefer_audio_quality: PreferAudioQuality::Best,
video_quality_priority: default_video_quality_priority,
codec_type_priority: vec![CodecType::AVC, CodecType::HEVC, CodecType::AV1],
audio_quality_priority: default_audio_quality_priority,
download_video: true,
download_audio: true,
auto_merge: true,
@@ -130,102 +155,6 @@ impl Config {
}
}
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Serialize,
Deserialize,
Type,
IntoPrimitive,
FromPrimitive,
)]
#[repr(i64)]
pub enum PreferVideoQuality {
#[default]
Best = -1,
#[serde(rename = "240P")]
Video240P = 6,
#[serde(rename = "360P")]
Video360P = 16,
#[serde(rename = "480P")]
Video480P = 32,
#[serde(rename = "720P")]
Video720P = 64,
#[serde(rename = "720P60")]
Video720P60 = 74,
#[serde(rename = "1080P")]
Video1080P = 80,
#[serde(rename = "AiRepair")]
VideoAiRepair = 100,
#[serde(rename = "1080P+")]
Video1080PPlus = 112,
#[serde(rename = "1080P60")]
Video1080P60 = 116,
#[serde(rename = "4K")]
Video4K = 120,
#[serde(rename = "HDR")]
VideoHDR = 125,
#[serde(rename = "Dolby")]
VideoDolby = 126,
#[serde(rename = "8K")]
Video8K = 127,
}
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Serialize,
Deserialize,
Type,
IntoPrimitive,
FromPrimitive,
)]
#[repr(i64)]
#[allow(clippy::upper_case_acronyms)]
pub enum PreferCodecType {
#[default]
Unknown = -1,
AVC = 7,
HEVC = 12,
AV1 = 13,
}
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Serialize,
Deserialize,
Type,
IntoPrimitive,
FromPrimitive,
)]
#[repr(i64)]
pub enum PreferAudioQuality {
#[default]
Best = -1,
#[serde(rename = "64K")]
Audio64K = 30216,
#[serde(rename = "132K")]
Audio132K = 30232,
#[serde(rename = "192K")]
Audio192K = 30280,
#[serde(rename = "Dolby")]
AudioDolby = 30250,
#[serde(rename = "HiRes")]
AudioHiRes = 30251,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Type)]
pub enum ProxyMode {
#[default]
+21 -63
View File
@@ -1,5 +1,5 @@
use std::{
cmp::Reverse,
collections::HashMap,
fs::{File, OpenOptions},
sync::Arc,
};
@@ -114,11 +114,7 @@ impl AudioTask {
}
}
if medias.is_empty() {
return Err(anyhow!("获取音频地址失败"));
}
self.prepare(app, medias);
self.prepare(app, medias)?;
Ok(())
}
@@ -174,11 +170,7 @@ impl AudioTask {
}
}
if medias.is_empty() {
return Err(anyhow!("获取音频地址失败"));
}
self.prepare(app, medias);
self.prepare(app, medias)?;
Ok(())
}
@@ -234,37 +226,28 @@ impl AudioTask {
}
}
if medias.is_empty() {
return Err(anyhow!("获取音频地址失败"));
}
self.prepare(app, medias);
self.prepare(app, medias)?;
Ok(())
}
fn prepare(&mut self, app: &AppHandle, mut medias: Vec<MediaForPrepare>) {
medias.sort_by_key(|m| Reverse(m.id.to_audio_quality_for_prepare()));
let best_quality_id = medias[0].id;
fn prepare(&mut self, app: &AppHandle, mut medias: Vec<MediaForPrepare>) -> anyhow::Result<()> {
if medias.is_empty() {
return Err(anyhow!("获取音频地址失败"));
}
let prefer_quality = app.get_config().read().prefer_audio_quality;
let prefer_quality_id: i64 = prefer_quality.into();
let prefer_quality_found = medias.iter().any(|m| m.id == prefer_quality_id);
let quality_filtered_medias: Vec<MediaForPrepare> = if prefer_quality_found {
// 如果用户指定质量存在,则使用用户指定的质量
medias
.into_iter()
.filter(|m| m.id == prefer_quality_id)
.collect()
} else {
// 否则使用最高质量
medias
.into_iter()
.filter(|m| m.id == best_quality_id)
.collect()
};
let quality_priority = app.get_config().read().audio_quality_priority.clone();
let priority_map: HashMap<&AudioQuality, usize> = quality_priority
.iter()
.enumerate()
.map(|(index, quality)| (quality, index))
.collect();
medias.sort_by_key(|media| {
let quality: AudioQuality = media.id.into();
priority_map.get(&quality).unwrap_or(&usize::MAX)
});
let media = &quality_filtered_medias[0];
let media = &medias[0];
self.audio_quality = media.id.into();
@@ -295,6 +278,8 @@ impl AudioTask {
self.content_length = content_length;
self.chunks = chunks;
}
Ok(())
}
pub fn mark_uncompleted(&mut self) {
@@ -427,30 +412,3 @@ struct MediaForPrepare {
pub id: i64,
pub url_with_content_length: Vec<(String, u64)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum AudioQualityForPrepare {
Audio64K,
Audio132K,
Audio192K,
AudioDolby,
AudioHiRes,
}
trait ToAudioQualityForPrepare {
fn to_audio_quality_for_prepare(self) -> Option<AudioQualityForPrepare>;
}
impl ToAudioQualityForPrepare for i64 {
fn to_audio_quality_for_prepare(self) -> Option<AudioQualityForPrepare> {
let audio_quality: AudioQuality = self.into();
match audio_quality {
AudioQuality::Audio64K => Some(AudioQualityForPrepare::Audio64K),
AudioQuality::Audio132K => Some(AudioQualityForPrepare::Audio132K),
AudioQuality::Audio192K => Some(AudioQualityForPrepare::Audio192K),
AudioQuality::AudioDolby => Some(AudioQualityForPrepare::AudioDolby),
AudioQuality::AudioHiRes => Some(AudioQualityForPrepare::AudioHiRes),
AudioQuality::Unknown => None,
}
}
}
+39 -43
View File
@@ -1,5 +1,5 @@
use std::{
cmp::Reverse,
collections::HashMap,
fs::{File, OpenOptions},
sync::Arc,
};
@@ -74,11 +74,7 @@ impl VideoTask {
}
}
if medias.is_empty() {
return Err(anyhow!("获取视频地址失败"));
}
self.prepare(app, medias);
self.prepare(app, medias)?;
Ok(())
}
@@ -144,11 +140,7 @@ impl VideoTask {
}
}
if medias.is_empty() {
return Err(anyhow!("获取视频地址失败"));
}
self.prepare(app, medias);
self.prepare(app, medias)?;
Ok(())
}
@@ -214,47 +206,49 @@ impl VideoTask {
}
}
if medias.is_empty() {
return Err(anyhow!("获取视频地址失败"));
}
self.prepare(app, medias);
self.prepare(app, medias)?;
Ok(())
}
fn prepare(&mut self, app: &AppHandle, mut medias: Vec<MediaForPrepare>) {
medias.sort_by_key(|m| Reverse(m.id));
let best_quality_id = medias[0].id;
fn prepare(&mut self, app: &AppHandle, mut medias: Vec<MediaForPrepare>) -> anyhow::Result<()> {
if medias.is_empty() {
return Err(anyhow!("获取音频地址失败"));
}
let (prefer_quality, prefer_codec_type) = {
let (video_quality_priority, codec_type_priority) = {
let config = app.get_config().inner().read();
(config.prefer_video_quality, config.prefer_codec_type)
(
config.video_quality_priority.clone(),
config.codec_type_priority.clone(),
)
};
let prefer_quality_id: i64 = prefer_quality.into();
let prefer_codec_id: i64 = prefer_codec_type.into();
let prefer_quality_found = medias.iter().any(|m| m.id == prefer_quality_id);
let mut quality_filtered_medias: Vec<MediaForPrepare> = if prefer_quality_found {
// 如果用户指定质量存在,则使用用户指定的质量
medias
.into_iter()
.filter(|m| m.id == prefer_quality_id)
.collect()
} else {
// 否则使用最高质量
medias
.into_iter()
.filter(|m| m.id == best_quality_id)
.collect()
};
// 按照 AVC > HEVC > AV1 的顺序排列
quality_filtered_medias.sort_by_key(|m| m.codecid);
let media = quality_filtered_medias
let video_priority_map: HashMap<&VideoQuality, usize> = video_quality_priority
.iter()
.find(|m| m.codecid == prefer_codec_id)
.unwrap_or(&quality_filtered_medias[0]);
.enumerate()
.map(|(index, quality)| (quality, index))
.collect();
medias.sort_by_key(|media| {
let quality: VideoQuality = media.id.into();
video_priority_map.get(&quality).unwrap_or(&usize::MAX)
});
let retain_id = medias[0].id;
medias.retain(|m| m.id == retain_id);
let codec_priority_map: HashMap<&CodecType, usize> = codec_type_priority
.iter()
.enumerate()
.map(|(index, codec_type)| (codec_type, index))
.collect();
medias.sort_by_key(|m| {
let codec_type: CodecType = m.codecid.into();
codec_priority_map.get(&codec_type).unwrap_or(&usize::MAX)
});
let media = &medias[0];
self.video_quality = media.id.into();
self.codec_type = media.codecid.into();
@@ -286,6 +280,8 @@ impl VideoTask {
self.content_length = content_length;
self.chunks = chunks;
}
Ok(())
}
pub fn mark_uncompleted(&mut self) {
+2
View File
@@ -7,6 +7,8 @@ use specta::Type;
Debug,
Clone,
Copy,
Hash,
Eq,
PartialEq,
Serialize,
Deserialize,
+3
View File
@@ -7,6 +7,8 @@ use specta::Type;
Debug,
Clone,
Copy,
Hash,
Eq,
PartialEq,
Serialize,
Deserialize,
@@ -19,6 +21,7 @@ use specta::Type;
pub enum CodecType {
#[default]
Unknown = -1,
Audio = 0,
AVC = 7,
HEVC = 12,
+2
View File
@@ -7,6 +7,8 @@ use specta::Type;
Debug,
Clone,
Copy,
Hash,
Eq,
PartialEq,
Serialize,
Deserialize,