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, output_path: output_dir,
timestamp: chrono::Utc::now(), timestamp: chrono::Utc::now(),
status: status.to_string(), status: status.to_string(),
format: options.quality, format: options.output_format,
}; };
let _ = storage::add_history_item(&app, item); let _ = storage::add_history_item(&app, item);

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,26 @@ const qualityOptions = [
{ label: '480p', value: '480' }, { 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 // Sync default download path if not set
watch(() => settingsStore.settings.download_path, (newPath) => { watch(() => settingsStore.settings.download_path, (newPath) => {
if (newPath && !analysisStore.options.output_path) { 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() { async function analyze() {
if (!analysisStore.url) return if (!analysisStore.url) return
analysisStore.loading = true analysisStore.loading = true
@@ -217,9 +242,9 @@ async function startDownload() {
<!-- Left: Selection Controls --> <!-- Left: Selection Controls -->
<div class="flex items-center gap-2 w-full md:w-auto"> <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(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-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-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-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-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> </div>
<!-- Right: Settings --> <!-- Right: Settings -->
@@ -249,6 +274,16 @@ async function startDownload() {
/> />
</div> </div>
</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> </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> <p v-if="analysisStore.metadata.entries" class="text-blue-500 mt-1 font-medium">{{ analysisStore.metadata.entries.length }} 个视频 (播放列表)</p>
<!-- Options --> <!-- 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 --> <!-- Audio Only Toggle -->
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-zinc-800 rounded-xl"> <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> <span class="font-medium text-base">仅音频</span>
@@ -322,6 +357,14 @@ async function startDownload() {
:disabled="analysisStore.options.is_audio_only" :disabled="analysisStore.options.is_audio_only"
/> />
</div> </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> </div>
</div> </div>