This commit is contained in:
Julian Freeman
2026-04-19 11:10:29 -04:00
parent 634c349ebb
commit 4f0c6b80ef
9 changed files with 5 additions and 409 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "stream-capture",
"private": true,
"version": "1.2.0",
"version": "1.2.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,58 +0,0 @@
# Feature Request: Real-time Logging & UI Refinement
## 1. Context & Objective
Users currently experience download failures without sufficient context or error details, making troubleshooting difficult.
The objective is to introduce a dedicated **Logs System** within the application to capture, display, and persist detailed execution logs from the `yt-dlp` process.
Additionally, the application's sidebar layout requires balancing for better UX.
## 2. Requirements
### 2.1. Backend (Rust) - Logging Infrastructure
* **Stream Capture**: The `downloader` module must capture both `STDOUT` (progress) and `STDERR` (errors/warnings) from the `yt-dlp` process.
* **Event Emission**: Emit a new Tauri event `download-log` to the frontend in real-time.
* Payload: `{ id: String, message: String, level: 'info' | 'error' | 'debug' }`
* **Persistence (Optional but Recommended)**: Ideally, write logs to a local file (e.g., `logs/{date}.log` or `logs/{task_id}.log`) for post-mortem analysis. *For this iteration, real-time event emission is priority.*
### 2.2. Frontend - Logs View
* **New Route**: `/logs`
* **UI Layout**:
* A dedicated **Logs Tab** in the main navigation.
* A console-like view displaying a stream of log messages.
* Filters: Filter by "Task ID" or "Level" (Error/Info).
* **Real-time**: The view must update automatically as new log events arrive.
* **Detail**: Failed downloads must show the specific error message returned by `yt-dlp` (e.g., "Sign in required", "Video unavailable").
### 2.3. UI/UX - Sidebar Refactoring
* **Reordering**:
* **Top Section**: Home (Downloader), History.
* **Bottom Section**: Logs, Settings.
* **Removal**: Remove the "Version Number" text from the bottom of the sidebar (it can be moved to the Settings page if needed, or just removed as requested).
## 3. Implementation Steps
### Step 1: Rust Backend Updates
* Modify `src-tauri/src/downloader.rs`:
* Update `download_video` to spawn the process with `Stdio::piped()` for both stdout and stderr.
* Use `tokio::select!` or separate tasks to read both streams concurrently.
* Emit `download-log` events for every line read.
* Ensure the final error message (if exit code != 0) is explicitly captured and returned/logged.
### Step 2: Frontend Store & Logic
* Create `src/stores/logs.ts`:
* State: `logs: LogEntry[]`.
* Action: `addLog(entry)`.
* Listener: Listen for `download-log` events globally.
### Step 3: Frontend UI
* Create `src/views/Logs.vue`:
* Display logs in a scrollable container.
* Style error logs in red, info in gray/white.
* Update `src/App.vue`:
* Add the `FileText` (or similar) icon for Logs.
* Refactor the sidebar Flexbox layout to push Logs and Settings to the bottom.
* Remove the version footer.
* Update `src/router/index.ts` to include the new route.
## 4. Success Criteria
* When a download fails, the user can go to the "Logs" tab and see the exact error output from `yt-dlp`.
* The sidebar looks balanced with navigation items split between top and bottom.

View File

@@ -1,142 +0,0 @@
# Project: StreamCapture (Tauri + Vue 3 YouTube Downloader)
## 1. Context & Objective
You are an expert Full-Stack Rust/TypeScript developer specializing in Tauri v2 application development.
Your task is to build a cross-platform (Windows & macOS) desktop application named "StreamCapture" in the current directory.
The app is a GUI wrapper for `yt-dlp`, but unlike standard sidecar implementations, it must manage the `yt-dlp` binary externally (in the user's AppData directory) to allow for frequent updates without rebuilding the app.
**Tech Stack:**
- **Core:** Tauri v2 (Rust)
- **Frontend:** Vue 3 (Composition API, `<script setup>`)
- **Build Tool:** Vite
- **Styling:** Tailwind CSS (Mobile-first, Modern UI)
- **State Management:** Pinia
- **Icons:** Lucide-vue-next
- **HTTP Client (Rust):** `reqwest` (for downloading yt-dlp)
---
## 2. Core Architecture & Workflow
### 2.1. External Binary Management (Crucial)
Since `yt-dlp` updates frequently, we **do NOT** bundle it inside the binary.
1. **Location:** The app must utilize the system's local data directory (e.g., `%APPDATA%/StreamCapture/bin` on Windows, `~/Library/Application Support/StreamCapture/bin` on macOS).
2. **Initialization:** On app launch, the Rust backend must check if `yt-dlp` exists in that directory.
3. **Auto-Download:** If missing, download the correct binary from the official GitHub releases (`yt-dlp.exe` for Win, `yt-dlp_macos` for Mac).
4. **Permissions:** On macOS, apply `chmod +x` (0o755) to the downloaded binary immediately.
5. **Execution:** Use Rust's `std::process::Command` to execute this specific binary using its absolute path.
### 2.2. Configuration Persistence
- File: `settings.json` in the app data directory.
- Fields:
- `download_path`: string (Default: System Download Dir)
- `theme`: 'light' | 'dark' | 'system' (Default: system)
- `last_updated`: timestamp (for yt-dlp check)
### 2.3. History Persistence
- File: `history.json`.
- Stores a list of completed or failed downloads (metadata, status, timestamp, file path).
---
## 3. Rust Backend Specifications (`src-tauri/src/lib.rs` & Modules)
Create specific commands to handle logic securely. Avoid exposing raw shell execution to the frontend.
### Module: `ytdlp_manager`
- **`init_ytdlp()`**: Checks existence. If missing, downloads it. Returns status.
- **`update_ytdlp()`**: Runs `yt-dlp -U`.
- **`get_version()`**: Returns current version string.
### Module: `downloader`
- **`fetch_metadata(url: String)`**:
- Runs `yt-dlp --dump-single-json --flat-playlist [url]`.
- **Logic:** If it's a playlist, return a list of video objects (id, title, thumbnail, duration). If single video, return one object.
- **Important:** Do NOT download media yet.
- **`download_video(url: String, options: DownloadOptions)`**:
- **Options Struct:** `{ is_audio_only: bool, quality: String, output_path: String }`
- **Process:** Spawns a command. Emits Tauri Events (`download-progress`) to frontend with percentage and speed.
### Module: `storage`
- Helper functions to read/write `settings.json` and `history.json`.
---
## 4. Frontend Specifications (Vue 3 + Tailwind)
### 4.1. UI Layout
- **Sidebar:** Navigation (Home/Download, History, Settings).
- **Header:** Theme toggle, App status (checking yt-dlp...).
- **Main Content:** Dynamic view based on route.
### 4.2. Views
#### A. Home View (The Downloader)
1. **Input Area:** Large, modern input box for URL. "Analyze" button.
2. **Selection Area (Conditional):**
- Appears after "Analyze" succeeds.
- Shows thumbnail(s) and title(s).
- If Playlist: Show list of items with checkboxes (Select All / Select None).
3. **Options Panel:**
- **Format:** Dropdown (Best Video+Audio, 1080p, 720p, 480p).
- **Mode:** Toggle Switch (Video / Audio Only).
4. **Action:** Big "Download" button.
5. **Progress:** Progress bars for active downloads.
#### B. History View
- Table/List of past downloads.
- Columns: Thumbnail (small), Title, Date, Format, Status (Success/Fail).
- Actions: "Open File Location", "Delete Record" (Icon), "Clear All" (Button at top).
#### C. Settings View
- **Download Path:** Input field + "Browse" button (use Tauri `dialog` API).
- **Yt-dlp:** Show current version. Button "Check for Updates".
- **Theme:** Radio buttons (Light/Dark/System).
### 4.3. State Management (Pinia stores)
- `useSettingsStore`: Loads/saves config. Handles theme applying (adding `.dark` class to `html`).
- `useQueueStore`: Manages active downloads and progress events.
---
## 5. Visual Design Guidelines (Tailwind)
- **Theme:**
- **Light Mode:** White/Gray-50 background, Zinc-900 text, Primary Blue-600.
- **Dark Mode:** Zinc-950 background, Gray-100 text, Primary Blue-500.
- **Components:**
- Rounded corners (`rounded-xl`).
- Subtle shadows (`shadow-sm`, `shadow-md`).
- Input fields with focus rings.
- Transitions for hover states and page switches.
---
## 6. Implementation Steps for Gemini
Please generate the code in the following order. **Ensure all files include `// filepath: ...` comments.**
1. **Rust Setup:**
- `Cargo.toml` (dependencies: reqwest, serde, serde_json, tokio, etc.).
- `src-tauri/src/lib.rs`: Main entry with command registration.
- `src-tauri/src/ytdlp.rs`: The download/update logic.
- `src-tauri/src/commands.rs`: The Tauri commands exposed to frontend.
2. **Frontend Setup:**
- `package.json`: deps (pinia, vue-router, lucide-vue-next, tailwindcss, autoprefixer, postcss).
- `src/style.css`: Tailwind directives.
- `src/stores/...`: Pinia stores.
- `src/components/...`: Reusable UI components (Input, Button, Card).
- `src/views/...`: The main pages.
- `src/App.vue`: Main layout.
3. **Configuration:**
- `tailwind.config.js`: Dark mode config.
- `src-tauri/capabilities/default.json`: **CRITICAL**. Configure permissions to allow accessing `app_data_dir` and the network scope.
## 7. Testing Requirements
- Create a basic Rust test in `src-tauri/src/ytdlp.rs` that verifies it can construct the correct path for the binary based on OS.
- Ensure Vue components handle "Loading" states gracefully (skeletons or spinners).
---
**IMPORTANT NOTE ON PERMISSIONS:**
Since we are using `std::process::Command` directly in Rust, we do not need to configure the strict `shell` scope in `tauri.conf.json`, but we MUST ensure the App Data directory is writable.
Start by generating the Rust backend logic first, as the frontend depends on these commands.

View File

@@ -1,67 +0,0 @@
# Feature Specification: QuickJS Runtime Integration for yt-dlp
## 1. Context & Objective
`yt-dlp` increasingly relies on a JavaScript runtime to execute complex decryption scripts (e.g., signature extraction) required by YouTube and other platforms. Without a JS runtime, downloads may fail or be throttled.
The objective is to automatically manage the **QuickJS** runtime binary (download, update, store) alongside the existing `yt-dlp` binary and configure the download process to utilize it via environment variable injection.
## 2. Technical Requirements
### 2.1. Binary Management
The application must manage the QuickJS binary lifecycle identically to how it currently handles `yt-dlp`.
* **Source Repository:** [https://github.com/quickjs-ng/quickjs](https://github.com/quickjs-ng/quickjs)
* **Storage Location:** The user's AppData `bin` directory (same as `yt-dlp`).
* Windows: `%APPDATA%\StreamCapture\bin\`
* macOS: `~/Library/Application Support/StreamCapture/bin/`
* **Unified Naming on Disk:**
Regardless of the source filename, the binary must be renamed upon saving to ensure consistent invocation:
* **Windows:** `qjs.exe`
* **macOS:** `qjs`
### 2.2. Download & Update Logic
* **Check:** On app launch (or `init_ytdlp`), check if `qjs` exists.
* **Download:** If missing, fetch the appropriate asset from the latest GitHub release of `quickjs-ng/quickjs`.
* *Note:* Logic must detect OS/Arch to pick the correct asset (e.g., `windows-x86_64`, `macos-x86_64/arm64`).
* **Permissions:** On macOS/Linux, ensure `chmod +x` is applied.
* **Update:** When `update_ytdlp` is triggered, also check/redownload the latest `qjs` binary.
### 2.3. Execution & Environment Injection
To enable `yt-dlp` to find the `qjs` binary without hardcoding absolute paths in the flags (which can be fragile), we will modify the **subprocess environment**.
* **Command Flag:** Always pass `--js-runtime qjs` to the `yt-dlp` command.
* **Environment Modification:**
Before spawning the `yt-dlp` child process in Rust, dynamically modify its `PATH` environment variable.
* **Action:** Prepend the absolute path of the application's `bin` directory to the system `PATH`.
* **Result:** When `yt-dlp` looks for `qjs`, it will find it in the injected `PATH`.
## 3. Implementation Plan
### Step 1: Refactor `src-tauri/src/ytdlp.rs`
* Rename file/module to `src-tauri/src/binary_manager.rs` (Optional, or just expand `ytdlp.rs`).
* Add constants/functions for QuickJS:
* `get_qjs_binary_name()`
* `download_qjs()`: Similar logic to `download_ytdlp` but parsing QuickJS release assets.
### Step 2: Update `src-tauri/src/commands.rs`
* Update `init_ytdlp` to initialize **both** binaries.
* Update `update_ytdlp` to update **both** binaries.
### Step 3: Update `src-tauri/src/downloader.rs`
* In `download_video` and `fetch_metadata`:
1. Resolve the absolute path to the `bin` directory.
2. Read the current system `PATH`.
3. Construct a new `PATH` string: `bin_dir + delimiter + system_path`.
4. Configure the `Command`:
```rust
Command::new(...)
.env("PATH", new_path)
.arg("--js-runtime")
.arg("qjs")
...
```
## 4. Acceptance Criteria
1. **File Existence:** `qjs.exe` (Win) or `qjs` (Mac) appears in the `bin` folder after app startup.
2. **Process Execution:** The logs show `yt-dlp` running successfully.
3. **Verification:** If a specific video requires JS interpretation (often indicated by slow downloads or errors without JS), it proceeds smoothly.
4. **Clean Logs:** No "PhantomJS not found" or "JS engine not found" warnings in the logs.

View File

@@ -1,26 +0,0 @@
# QuickJS Binary Source Update
## 1. Context
The current `quickjs-ng` runtime is proving too slow for our needs. We need to switch to the original QuickJS runtime provided by Bellard.
## 2. Source Details
* **Base URL:** `https://bellard.org/quickjs/binary_releases/`
* **Versioning:** Use `LATEST.json` at the base URL to find the latest version and filename.
* Format: `{"version":"2024-01-13","files":{"quickjs-win-x86_64.zip":"quickjs-win-x86_64-2024-01-13.zip", ...}}`
* **Target Files:**
* **Windows:** Look for key `quickjs-win-x86_64.zip`.
* **macOS:** Look for key `quickjs-cosmo-x86_64.zip` (Cosmo builds are generally portable). *Wait, need to confirm if macos specific builds exist or if cosmo is the intended one for unix-like.*
* *Correction*: Bellard's page lists `quickjs-macos-x86_64.zip`? Let's check LATEST.json first. Assuming `quickjs-cosmo` might be Linux/Universal. Let's fetch `LATEST.json` to be sure.
## 3. Implementation Changes
* **`src-tauri/src/binary_manager.rs`**:
* Update `QJS_REPO_URL` to `https://bellard.org/quickjs/binary_releases`.
* Add logic to fetch `LATEST.json` first.
* Parse the JSON to get the actual filename for the current OS.
* Download the ZIP file.
* Extract the binary (`qjs.exe` or `qjs`) from the ZIP.
* Rename it to our internal standard (`quickjs.exe` / `quickjs`).
## 4. Verification
* Re-run tests.
* Ensure `update_quickjs` flows correctly with the new logic.

View File

@@ -1,111 +0,0 @@
# Feature Request: Advanced Playlist Parsing & Mix Handling
## Context
We are upgrading the **StreamCapture** application. Currently, the app only supports parsing standard single video URLs.
We need to implement robust support for **Standard Playlists** and **YouTube Mixes (Radio)**, while solving performance issues and thumbnail loading errors.
## Problem Statement
1. **Playlists (`/playlist?list=...`)**: Fails to parse entries or hangs because it attempts to resolve full metadata for every video. Thumbnails are missing.
2. **Mixes (`/watch?v=...&list=RD...`)**: Currently fails or behaves unpredictably.
3. **UI/UX**: Users need a specific choice when encountering a "Mix" link:
* **Option A (Default):** Treat it as a single video (ignore the list).
* **Option B:** Parse the Mix as a playlist (limit to top 20 items).
## Technical Requirements
### 1. Rust Backend Refactoring (`src-tauri/src/ytdlp.rs`)
Refactor the `fetch_metadata` command to use a unified, efficient parsing strategy.
#### A. Command Construction
For **ALL** metadata fetching, use the `--flat-playlist` flag to prevent deep extraction (which causes the hang).
**Base Command:**
```bash
yt-dlp --dump-single-json --flat-playlist --no-warnings [URL]
```
#### B. Handling Different URL Types
1. Single Video:
- `yt-dlp` returns a single JSON object with `_type: "video"` (or no type).
- Action: Wrap it in a list of 1.
2. Standard Playlist:
- `yt-dlp` returns `_type: "playlist"` with an `entries` array.
- Action: Map the `entries` to our `Video` struct.
3. Mix / Radio (Infinite List):
- Condition: If the frontend flags this request as a "Mix Playlist scan".
- Modification: Add flag --playlist-end 20 to the command.
- Reason: Mixes are infinite; we must cap them.
#### C. Data Normalization & Thumbnail Fallback
When using `--flat-playlist`, the `entries` often lack full `thumbnail` URLs or return webp formats that might not render immediately.
- Logic: If the `thumbnail` field is missing or empty in an entry, construct it manually using the ID:
- https://i.ytimg.com/vi/{video_id}/mqdefault.jpg
### 2. Frontend Logic (`src/views/Home.vue` & Stores)
#### A. URL Detection & User Choice
Before sending the URL to Rust, analyze the string:
1. Regex Check: Detect if the URL contains both `v=` AND `list=` (typical for Mixes).
2. New UI Element:
- If a Mix link is detected, show a **Checkbox** or **Toggle** near the "Analyze" button.
- Label: "Scan Playlist (Max 20)"
- Default State: Unchecked (Off).
3. Submission Logic:
- If Unchecked (Default): Strip the `&list=...` parameter from the URL string before calling Rust. Treat it as a pure single video.
- If Checked: Keep the full URL and pass a flag (e.g., `is_mix: true`) to the Rust backend (or handle the logic to request the top 20).
#### B. Displaying Results
- Ensure the "Selection Area" can render a list of cards whether it contains 1 video or 20 videos.
- If it's a playlist, show a "Select All" / "Deselect All" control.
## Implementation Plan
### Step 1: Rust Structs Update
Update the Serde structs in `ytdlp.rs` to handle the `flat-playlist` JSON structure.
```rust
// Example structure hint
struct YtDlpResponse {
_type: Option<String>,
entries: Option<Vec<VideoEntry>>,
// ... fields for single video fallback
id: Option<String>,
title: Option<String>,
}
struct VideoEntry {
id: String,
title: String,
duration: Option<f64>,
thumbnail: Option<String>, // Might be missing
}
```
### Step 2: Rust Logic Update
Modify `command::fetch_metadata`.
- Accept an optional argument `parse_mix_playlist: bool`.
- If `true`, append `--playlist-end 20`.
- Implement the thumbnail fallback logic (if `thumbnail` is None, use `i.ytimg.com`).
### Step 3: Frontend Update
- Add the "Mix Detected" logic in `Home.vue`.
- Add the toggle UI.
- Update the `analyze` function to handle URL stripping vs. passing through based on the toggle.
## Deliverables
Please rewrite the necessary parts of `src-tauri/src/ytdlp.rs`, `src-tauri/src/commands.rs`, and `src/views/Home.vue` to implement this logic.

2
src-tauri/Cargo.lock generated
View File

@@ -4038,7 +4038,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stream-capture"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"anyhow",
"base64 0.22.1",

View File

@@ -1,6 +1,6 @@
[package]
name = "stream-capture"
version = "1.2.0"
version = "1.2.1"
description = "A Tauri App"
authors = ["you"]
edition = "2021"

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "StreamCapture",
"version": "1.2.0",
"version": "1.2.1",
"identifier": "top.volan.stream-capture",
"build": {
"beforeDevCommand": "pnpm dev",
@@ -13,7 +13,7 @@
"windows": [
{
"label": "main",
"title": "流萤 - 视频下载 v1.2.0",
"title": "流萤 - 视频下载 v1.2.1",
"width": 1300,
"height": 900,
"visible": false