add format convert support

This commit is contained in:
Julian Freeman
2025-12-08 17:48:22 -04:00
parent 77407c7e28
commit eada45bd9c
5 changed files with 63 additions and 9 deletions

View File

@@ -77,7 +77,7 @@ pub async fn start_download(app: AppHandle, url: String, options: DownloadOption
output_path: output_dir,
timestamp: chrono::Utc::now(),
status: status.to_string(),
format: options.quality,
format: options.output_format,
};
let _ = storage::add_history_item(&app, item);

View File

@@ -36,6 +36,7 @@ pub struct DownloadOptions {
pub is_audio_only: bool,
pub quality: String, // e.g., "1080", "720", "best"
pub output_path: String, // Directory
pub output_format: String, // "original", "mp4", "webm", "mkv", "m4a", "aac", "opus", "vorbis", "wav", etc.
}
#[derive(Serialize, Clone, Debug)]
@@ -188,8 +189,11 @@ pub async fn download_video(
// Formats
if options.is_audio_only {
args.push("-x".to_string());
args.push("--audio-format".to_string());
args.push("mp3".to_string());
// Only set audio format if not "original"
if options.output_format != "original" {
args.push("--audio-format".to_string());
args.push(options.output_format.clone());
}
} else {
let format_arg = if options.quality == "best" {
"bestvideo+bestaudio/best".to_string()
@@ -198,6 +202,12 @@ pub async fn download_video(
};
args.push("-f".to_string());
args.push(format_arg);
// Only set merge output format if not "original"
if options.output_format != "original" {
args.push("--merge-output-format".to_string());
args.push(options.output_format.clone());
}
}
// Progress output

View File

@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "流萤 - 视频下载",
"title": "流萤 - 视频下载 v1.0.0",
"width": 1300,
"height": 900
}

View File

@@ -15,7 +15,8 @@ export const useAnalysisStore = defineStore('analysis', () => {
const options = ref({
is_audio_only: false,
quality: 'best',
output_path: ''
output_path: '',
output_format: 'original'
})
function toggleEntry(id: string) {

View File

@@ -18,6 +18,26 @@ const qualityOptions = [
{ label: '480p', value: '480' },
]
const videoFormatOptions = [
{ label: '原格式', value: 'original' },
{ label: 'MP4', value: 'mp4' },
{ label: 'WebM', value: 'webm' },
{ label: 'Matroska (MKV)', value: 'mkv' },
{ label: 'FLV', value: 'flv' },
{ label: 'AVI', value: 'avi' },
]
const audioFormatOptions = [
{ label: '原格式', value: 'original' },
{ label: 'MP3', value: 'mp3' },
{ label: 'M4A', value: 'm4a' },
{ label: 'AAC', value: 'aac' },
{ label: 'Opus', value: 'opus' },
{ label: 'Vorbis', value: 'vorbis' },
{ label: 'WAV', value: 'wav' },
{ label: 'FLAC', value: 'flac' },
]
// Sync default download path if not set
watch(() => settingsStore.settings.download_path, (newPath) => {
if (newPath && !analysisStore.options.output_path) {
@@ -36,6 +56,11 @@ watch(() => analysisStore.url, (newUrl) => {
}
})
// Reset format to original when toggling audio-only mode
watch(() => analysisStore.options.is_audio_only, () => {
analysisStore.options.output_format = 'original'
})
async function analyze() {
if (!analysisStore.url) return
analysisStore.loading = true
@@ -217,9 +242,9 @@ async function startDownload() {
<!-- Left: Selection Controls -->
<div class="flex items-center gap-2 w-full md:w-auto">
<button @click="analysisStore.setAllEntries(true)" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">全选</button>
<button @click="analysisStore.setAllEntries(false)" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">取消全选</button>
<button @click="analysisStore.invertSelection()" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">反选</button>
<button @click="analysisStore.setAllEntries(true)" class="text-base font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">全选</button>
<button @click="analysisStore.setAllEntries(false)" class="text-base font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">取消全选</button>
<button @click="analysisStore.invertSelection()" class="text-base font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">反选</button>
</div>
<!-- Right: Settings -->
@@ -249,6 +274,16 @@ async function startDownload() {
/>
</div>
</div>
<!-- Format Dropdown -->
<div class="flex items-center gap-3">
<div class="w-48">
<AppSelect
v-model="analysisStore.options.output_format"
:options="analysisStore.options.is_audio_only ? audioFormatOptions : videoFormatOptions"
/>
</div>
</div>
</div>
</div>
</div>
@@ -298,7 +333,7 @@ async function startDownload() {
<p v-if="analysisStore.metadata.entries" class="text-blue-500 mt-1 font-medium">{{ analysisStore.metadata.entries.length }} 个视频 (播放列表)</p>
<!-- Options -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
<!-- Audio Only Toggle -->
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-zinc-800 rounded-xl">
<span class="font-medium text-base">仅音频</span>
@@ -322,6 +357,14 @@ async function startDownload() {
:disabled="analysisStore.options.is_audio_only"
/>
</div>
<!-- Format Dropdown -->
<div class="relative">
<AppSelect
v-model="analysisStore.options.output_format"
:options="analysisStore.options.is_audio_only ? audioFormatOptions : videoFormatOptions"
/>
</div>
</div>
</div>
</div>