From 091cf65dac0a1255e329cca13d69a369884dab61 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Mon, 8 Dec 2025 08:52:49 -0400 Subject: [PATCH] fix playlist --- README.md | 2 - index.html | 3 +- public/placeholder.png | Bin 0 -> 4251 bytes public/tauri.svg | 6 -- public/vite.svg | 1 - src-tauri/tauri.conf.json | 2 +- src/assets/vue.svg | 1 - src/stores/analysis.ts | 17 +++- src/views/Home.vue | 181 ++++++++++++++++++++++++++++++-------- 9 files changed, 160 insertions(+), 53 deletions(-) create mode 100644 public/placeholder.png delete mode 100644 public/tauri.svg delete mode 100644 public/vite.svg delete mode 100644 src/assets/vue.svg diff --git a/README.md b/README.md index 56583d5..b3c306c 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,3 @@ Generated by Gemini CLI. 1. windows 上打包后运行命令会出现黑窗,而且还是会出现找不到 js-runtimes 的问题,但是 quickjs 是正常下载了 2. macos 上未测试 -3. 增加日志内容,除了下载日志,把运行日志,包括调用的完整命令也输出一下 -4. 解析多视频还是有问题,不显示视频列表,缩略图也不对 diff --git a/index.html b/index.html index 99f203f..b5cb5a7 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,8 @@ - - Tauri + Vue + Typescript App + Stream Capture diff --git a/public/placeholder.png b/public/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..3383260e1671a10aae93204b11aafda460ff8cba GIT binary patch literal 4251 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 - - - - - diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3943834..ccd2dd7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -12,7 +12,7 @@ "app": { "windows": [ { - "title": "stream-capture", + "title": "Stream Capture", "width": 1300, "height": 850 } diff --git a/src/assets/vue.svg b/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/stores/analysis.ts b/src/stores/analysis.ts index 11afe76..fca60c5 100644 --- a/src/stores/analysis.ts +++ b/src/stores/analysis.ts @@ -18,6 +18,21 @@ export const useAnalysisStore = defineStore('analysis', () => { output_path: '' }) + function toggleEntry(id: string) { + if (metadata.value && metadata.value.entries) { + const entry = metadata.value.entries.find((e: any) => e.id === id) + if (entry) { + entry.selected = !entry.selected + } + } + } + + function setAllEntries(selected: boolean) { + if (metadata.value && metadata.value.entries) { + metadata.value.entries.forEach((e: any) => e.selected = selected) + } + } + function reset() { url.value = '' loading.value = false @@ -27,5 +42,5 @@ export const useAnalysisStore = defineStore('analysis', () => { scanMix.value = false } - return { url, loading, error, metadata, options, isMix, scanMix, reset } + return { url, loading, error, metadata, options, isMix, scanMix, toggleEntry, setAllEntries, reset } }) \ No newline at end of file diff --git a/src/views/Home.vue b/src/views/Home.vue index 7471522..e2903fb 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -66,7 +66,13 @@ async function analyze() { } } - const res = await invoke('fetch_metadata', { url: urlToScan, parseMixPlaylist: parseMix }) + const res = await invoke('fetch_metadata', { url: urlToScan, parseMixPlaylist: parseMix }) + + // Initialize selected state for playlist entries + if (res.entries) { + res.entries = res.entries.map((e: any) => ({ ...e, selected: true })) + } + analysisStore.metadata = res } catch (e: any) { analysisStore.error = e.toString() @@ -84,44 +90,63 @@ async function startDownload() { } try { - const metaToSend = analysisStore.metadata.entries ? - { title: analysisStore.metadata.title, thumbnail: "", id: analysisStore.metadata.id } : - analysisStore.metadata; + if (analysisStore.metadata.entries) { + // Playlist Download + const selectedEntries = analysisStore.metadata.entries.filter((e: any) => e.selected) + + if (selectedEntries.length === 0) { + analysisStore.error = "Please select at least one video to download." + return + } - // Note: We might want to pass the *cleaned* URL if it was cleaned during analyze - // But for now we pass the original URL or whatever was scanned. - // Actually, if we scanned as a single video (unchecked), we should probably download as single video. - // The user might expect the same result as analysis. - // Let's reconstruct the URL logic or just use what `analyze` used? - // Since `start_download` just takes a URL string, we should probably use the same logic. - - let urlToDownload = analysisStore.url; - if (analysisStore.isMix && !analysisStore.scanMix) { - try { - const u = new URL(urlToDownload); - u.searchParams.delete('list'); - u.searchParams.delete('index'); - u.searchParams.delete('start_radio'); - urlToDownload = u.toString(); - } catch (e) { - urlToDownload = urlToDownload.replace(/&list=[^&]+/, ''); - } - } + for (const entry of selectedEntries) { + const videoUrl = `https://www.youtube.com/watch?v=${entry.id}` + const id = await invoke('start_download', { + url: videoUrl, + options: analysisStore.options, + metadata: entry // Pass the individual video metadata + }) - const id = await invoke('start_download', { - url: urlToDownload, - options: analysisStore.options, - metadata: metaToSend - }) + queueStore.addTask({ + id, + title: entry.title, + thumbnail: entry.thumbnail, + progress: 0, + speed: 'Pending...', + status: 'pending' + }) + } + } else { + // Single Video Download + let urlToDownload = analysisStore.url; + // Clean URL if it was a mix but we didn't scan it as one + if (analysisStore.isMix && !analysisStore.scanMix) { + try { + const u = new URL(urlToDownload); + u.searchParams.delete('list'); + u.searchParams.delete('index'); + u.searchParams.delete('start_radio'); + urlToDownload = u.toString(); + } catch (e) { + urlToDownload = urlToDownload.replace(/&list=[^&]+/, ''); + } + } - queueStore.addTask({ - id, - title: metaToSend.title, - thumbnail: metaToSend.thumbnail, - progress: 0, - speed: 'Pending...', - status: 'pending' - }) + const id = await invoke('start_download', { + url: urlToDownload, + options: analysisStore.options, + metadata: analysisStore.metadata + }) + + queueStore.addTask({ + id, + title: analysisStore.metadata.title, + thumbnail: analysisStore.metadata.thumbnail, + progress: 0, + speed: 'Pending...', + status: 'pending' + }) + } // Reset state after successful download start analysisStore.reset() @@ -178,7 +203,86 @@ async function startDownload() {
-
+ + +
+
+
+

{{ analysisStore.metadata.title }}

+

{{ analysisStore.metadata.entries.length }} videos found

+
+
+ + +
+
+ + +
+ +
+ Audio Only (All) + +
+ + +
+ Quality (All) +
+ +
+
+
+
+ + +
+
+ + + + + + + +
+

{{ entry.title }}

+

+ {{ entry.duration ? Math.floor(entry.duration / 60) + ':' + String(Math.floor(entry.duration % 60)).padStart(2, '0') : '' }} + {{ entry.uploader }} +

+
+
+
+ + +
@@ -186,7 +290,6 @@ async function startDownload() {

{{ analysisStore.metadata.title }}

{{ analysisStore.metadata.uploader }}

-

{{ analysisStore.metadata.entries.length }} videos in playlist

@@ -222,7 +325,7 @@ async function startDownload() { @click="startDownload" class="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-xl font-bold transition-colors shadow-lg shadow-blue-600/20" > - Download Now + Download Now {{ analysisStore.metadata.entries ? `(${analysisStore.metadata.entries.filter((e: any) => e.selected).length})` : '' }}