mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-05 15:38:52 +08:00
feat: 支持设置 画质、音质、编码 的优先级
This commit is contained in:
+2
-1
@@ -21,7 +21,8 @@
|
||||
"pinia": "^3.0.3",
|
||||
"unplugin-auto-import": "^19.3.0",
|
||||
"unplugin-vue-components": "^28.8.0",
|
||||
"vue": "^3.5.13"
|
||||
"vue": "^3.5.13",
|
||||
"vue-draggable-plus": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.1",
|
||||
|
||||
Generated
+21
@@ -44,6 +44,9 @@ importers:
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.17(typescript@5.6.3)
|
||||
vue-draggable-plus:
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0(@types/sortablejs@1.15.8)
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.30.1
|
||||
@@ -738,6 +741,9 @@ packages:
|
||||
'@types/lodash@4.17.20':
|
||||
resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
|
||||
|
||||
'@types/sortablejs@1.15.8':
|
||||
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.36.0':
|
||||
resolution: {integrity: sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1963,6 +1969,15 @@ packages:
|
||||
vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||
|
||||
vue-draggable-plus@0.6.0:
|
||||
resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==}
|
||||
peerDependencies:
|
||||
'@types/sortablejs': ^1.15.0
|
||||
'@vue/composition-api': '*'
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
vue-eslint-parser@10.2.0:
|
||||
resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -2576,6 +2591,8 @@ snapshots:
|
||||
|
||||
'@types/lodash@4.17.20': {}
|
||||
|
||||
'@types/sortablejs@1.15.8': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.6.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.6.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@@ -3966,6 +3983,10 @@ snapshots:
|
||||
|
||||
vscode-uri@3.1.0: {}
|
||||
|
||||
vue-draggable-plus@0.6.0(@types/sortablejs@1.15.8):
|
||||
dependencies:
|
||||
'@types/sortablejs': 1.15.8
|
||||
|
||||
vue-eslint-parser@10.2.0(eslint@9.30.1(jiti@2.4.2)):
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
|
||||
+33
-104
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -7,6 +7,8 @@ use specta::Type;
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -7,6 +7,8 @@ use specta::Type;
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
|
||||
+1
-4
@@ -247,7 +247,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; embed_chapter: boolean; embed_skip: 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; proxy_mode: ProxyMode; proxy_host: string; proxy_port: number; 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; video_quality_priority: VideoQuality[]; codec_type_priority: CodecType[]; audio_quality_priority: AudioQuality[]; download_video: boolean; download_audio: boolean; auto_merge: boolean; embed_chapter: boolean; embed_skip: 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; proxy_mode: ProxyMode; proxy_host: string; proxy_port: number; 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 ContentAttr = { text: string; bg_color: string; bg_color_night: string; img: string; multi_img: MultiImg }
|
||||
export type ContentList = { bold: boolean; content: string; number: string }
|
||||
@@ -331,9 +331,6 @@ export type PendantInCheese = { image: string; name: string; pid: number }
|
||||
export type PendantInUserInfo = { pid: number; name: string; image: string; expire: number; image_enhance: string; image_enhance_frame: string; n_pid: number }
|
||||
export type PlayStrategy = { strategies: string[] }
|
||||
export type Positive = { id: number; title: string }
|
||||
export type PreferAudioQuality = "Best" | "64K" | "132K" | "192K" | "Dolby" | "HiRes"
|
||||
export type PreferCodecType = "Unknown" | "AVC" | "HEVC" | "AV1"
|
||||
export type PreferVideoQuality = "Best" | "240P" | "360P" | "480P" | "720P" | "720P60" | "1080P" | "AiRepair" | "1080P+" | "1080P60" | "4K" | "HDR" | "Dolby" | "8K"
|
||||
export type PreviewedPurchaseNote = { long_watch_text: string; pay_text: string; price_format: string; watch_text: string; watching_text: string }
|
||||
export type Producer = { mid: number; type: number; is_contribute: number | null; title: string }
|
||||
export type ProxyMode = "NoProxy" | "System" | "Custom"
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { SelectBaseOption } from 'naive-ui/es/select/src/interface'
|
||||
import { PreferAudioQuality, PreferVideoQuality } from '../../../bindings.ts'
|
||||
import { AudioQuality, VideoQuality, CodecType } from '../../../bindings.ts'
|
||||
import { useStore } from '../../../store.ts'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import ColorfulTag from '../../../components/ColorfulTag.vue'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const videoQualitySelectOptions: SelectBaseOption<PreferVideoQuality>[] = [
|
||||
{ label: '最高', value: 'Best' },
|
||||
{ label: '240P', value: '240P' },
|
||||
{ label: '360P', value: '360P' },
|
||||
{ label: '480P', value: '480P' },
|
||||
{ label: '720P', value: '720P' },
|
||||
{ label: '720P 60帧', value: '720P60' },
|
||||
{ label: '1080P', value: '1080P' },
|
||||
{ label: '智能修复', value: 'AiRepair' },
|
||||
{ label: '1080P 高码率', value: '1080P+' },
|
||||
{ label: '1080P 60帧', value: '1080P60' },
|
||||
{ label: '4K', value: '4K' },
|
||||
{ label: 'HDR', value: 'HDR' },
|
||||
{ label: '杜比视界', value: 'Dolby' },
|
||||
{ label: '8K', value: '8K' },
|
||||
]
|
||||
const audioQualitySelectOptions: SelectBaseOption<PreferAudioQuality>[] = [
|
||||
{ label: '最高', value: 'Best' },
|
||||
{ label: '64K', value: '64K' },
|
||||
{ label: '132K', value: '132K' },
|
||||
{ label: '192K', value: '192K' },
|
||||
{ label: '杜比全景声', value: 'Dolby' },
|
||||
{ label: 'Hi-Res', value: 'HiRes' },
|
||||
]
|
||||
const videoQualityNameMap: Map<VideoQuality, string> = new Map([
|
||||
['240P', '240P 极速'],
|
||||
['360P', '360P 流畅'],
|
||||
['480P', '480P 标清'],
|
||||
['720P', '720P 准高清'],
|
||||
['720P60', '720P 60帧'],
|
||||
['1080P', '1080P 高清'],
|
||||
['AiRepair', 'AI智能修复'],
|
||||
['1080P+', '1080P 高码率'],
|
||||
['1080P60', '1080P 60帧'],
|
||||
['4K', '4K 超高清'],
|
||||
['HDR', 'HDR 真彩色'],
|
||||
['Dolby', '杜比视界'],
|
||||
['8K', '8K 超高清'],
|
||||
])
|
||||
|
||||
const audioQualityNameMap: Map<AudioQuality, string> = new Map([
|
||||
['64K', '64K'],
|
||||
['132K', '132K'],
|
||||
['192K', '192K'],
|
||||
['Dolby', '杜比全景声'],
|
||||
['HiRes', 'Hi-Res 无损'],
|
||||
])
|
||||
|
||||
const codecTypeNameMap: Map<CodecType, string> = new Map([
|
||||
['AVC', 'AVC (H.264)'],
|
||||
['HEVC', 'HEVC (H.265)'],
|
||||
['AV1', 'AV1'],
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -87,44 +93,71 @@ const audioQualitySelectOptions: SelectBaseOption<PreferAudioQuality>[] = [
|
||||
<n-checkbox class="w-22" v-model:checked="store.config.download_json">json刮削</n-checkbox>
|
||||
</div>
|
||||
|
||||
<n-tooltip placement="left" trigger="hover" class="w-20vw">
|
||||
<div>如果视频有对应的画质,则使用对应的画质,否则选择最高画质</div>
|
||||
<template #trigger>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 whitespace-nowrap font-bold">优先画质</span>
|
||||
<n-select
|
||||
size="small"
|
||||
v-model:value="store.config.prefer_video_quality"
|
||||
:options="videoQualitySelectOptions" />
|
||||
<div class="flex flex-justify-between">
|
||||
<div>
|
||||
<span class="whitespace-nowrap font-bold">画质优先级</span>
|
||||
<div class="overflow-auto overflow-x-hidden h-36">
|
||||
<VueDraggable
|
||||
:force-fallback="true"
|
||||
:fallback-on-body="true"
|
||||
:animation="300"
|
||||
v-model="store.config.video_quality_priority"
|
||||
ghostClass="draggable-ghost"
|
||||
class="select-none flex flex-col gap-2">
|
||||
<ColorfulTag
|
||||
class="whitespace-nowrap cursor-move"
|
||||
color="blue"
|
||||
v-for="videoQuality in store.config.video_quality_priority"
|
||||
:key="videoQuality">
|
||||
{{ videoQualityNameMap.get(videoQuality) || videoQuality }}
|
||||
</ColorfulTag>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</template>
|
||||
</n-tooltip>
|
||||
</div>
|
||||
|
||||
<n-tooltip placement="left" trigger="hover" class="w-20vw">
|
||||
<div>如果视频有对应的音质,则使用对应的音质,否则选择最高音质</div>
|
||||
<template #trigger>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 whitespace-nowrap font-bold">优先音质</span>
|
||||
<n-select
|
||||
size="small"
|
||||
v-model:value="store.config.prefer_audio_quality"
|
||||
:options="audioQualitySelectOptions" />
|
||||
</div>
|
||||
</template>
|
||||
</n-tooltip>
|
||||
<div>
|
||||
<span class="whitespace-nowrap font-bold">音质优先级</span>
|
||||
<VueDraggable
|
||||
:force-fallback="true"
|
||||
:fallback-on-body="true"
|
||||
v-model="store.config.audio_quality_priority"
|
||||
:animation="300"
|
||||
ghostClass="draggable-ghost"
|
||||
class="select-none flex flex-col gap-2">
|
||||
<ColorfulTag
|
||||
class="whitespace-nowrap cursor-move"
|
||||
color="blue"
|
||||
v-for="audioQuality in store.config.audio_quality_priority"
|
||||
:key="audioQuality">
|
||||
{{ audioQualityNameMap.get(audioQuality) || audioQuality }}
|
||||
</ColorfulTag>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
|
||||
<n-tooltip placement="left" trigger="hover" class="w-20vw">
|
||||
<div>如果视频有对应的编码,则使用对应的编码,否则按照 AVC > HEVC > AV1 的顺序选择编码</div>
|
||||
<template #trigger>
|
||||
<div>
|
||||
<span class="mr-2 font-bold">优先编码</span>
|
||||
<n-radio-group v-model:value="store.config.prefer_codec_type" size="small">
|
||||
<n-radio-button value="AVC">AVC</n-radio-button>
|
||||
<n-radio-button value="HEVC">HEVC</n-radio-button>
|
||||
<n-radio-button value="AV1">AV1</n-radio-button>
|
||||
</n-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
</n-tooltip>
|
||||
<div>
|
||||
<span class="whitespace-nowrap font-bold">编码优先级</span>
|
||||
<VueDraggable
|
||||
:force-fallback="true"
|
||||
:fallback-on-body="true"
|
||||
v-model="store.config.codec_type_priority"
|
||||
:animation="300"
|
||||
ghostClass="draggable-ghost"
|
||||
class="select-none flex flex-col gap-2">
|
||||
<ColorfulTag
|
||||
class="whitespace-nowrap cursor-move"
|
||||
color="blue"
|
||||
v-for="codecType in store.config.codec_type_priority"
|
||||
:key="codecType">
|
||||
{{ codecTypeNameMap.get(codecType) || codecType }}
|
||||
</ColorfulTag>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.draggable-ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user