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 0000000..3383260 Binary files /dev/null and b/public/placeholder.png differ diff --git a/public/tauri.svg b/public/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/public/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - 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})` : '' }}