feat: add GUI mvp

This commit is contained in:
2026-05-16 22:17:01 +03:00
parent 9c9dd283a6
commit 903389d73c
20 changed files with 1814 additions and 27 deletions
+3 -17
View File
@@ -3,8 +3,6 @@
## Python-first Desktop Architecture
- **Primary GUI framework**: `PySide6` (Qt for Python).
- **Communication Layer**: A local `FastAPI` backend to separate core logic from the UI.
- **IPC Mechanism**: The GUI spawns the FastAPI server on a random high port (binding to `127.0.0.1` ONLY) and communicates via REST/WebSockets.
## Core Features to Implement
@@ -12,27 +10,15 @@
2. **Interactive Configuration**: Wizard-style setup for new playlists (URL detection, folder picker).
3. **Queue Manager**: Visual progress bars for active downloads, showing speed, ETA, and current video title.
4. **Log Viewer**: Real-time streaming of yt-dlp logs for troubleshooting.
5. **Settings Panel**: Global settings for binary paths (ffmpeg, aria2c), max parallel jobs, and Docker detection toggle.
5. **Settings Panel**: Global settings for binary paths (ffmpeg), max parallel jobs, and Docker detection toggle.
## Phase 1 Roadmap: "The Bridge"
- [ ] **Refactor `src/manager.py`**: Convert CLI-first execution to async-compatible methods for FastAPI consumption.
- [ ] **FastAPI Integration**: Create endpoints for `/playlists`, `/status`, and `/download/start`.
- [ ] **PySide6 Skeleton**: Basic window with `QWebEngine` (if hybrid) or native `QWidget` dashboard.
- [ ] **Packaging**: `pyinstaller` configuration to bundle both backend and frontend into a single `.exe`.
## Packaging & Distribution (brief)
- Bundle the backend and GUI into one distributable. The GUI should spawn the local API process (background subprocess) on startup.
- Bundle the backend and GUI into one distributable.
- Windows: use `pyinstaller` or `briefcase` to create an executable/installer. Consider creating an MSI or Inno Setup installer for a polished UX.
- Linux: provide AppImage, Snap, or distribution-specific packages (deb/rpm) — AppImage is a good starting point for single-file distribution.
- Security: bind the local API to `localhost` only, use a short-lived token or IPC for authentication between GUI and backend, and avoid exposing unnecessary ports.
## Roadmap (GUI → Web → Mobile)
1. Desktop prototype: `FastAPI` backend + `PySide6` GUI (thin client) with basic playlist add/update/download controls and status streaming.
2. Packaging: create Windows exe/installer and Linux AppImage for the prototype.
3. Web frontend: build a web SPA that consumes the same backend API (hosted or local) — this reuses business logic with minimal change.
4. Android: either a native app or cross-platform UI (Flutter/React Native) that calls the backend API; alternatively host the backend and make a thin mobile client.
If you want, I can now: scaffold a minimal `FastAPI` backend and `PySide6` desktop starter in this repo, or produce concise packaging steps for Windows and Linux. Which do you prefer?
- Linux: provide AppImage, Snap, or distribution-specific packages (deb/rpm) — AppImage is a good starting point for single-file distribution.
+121
View File
@@ -0,0 +1,121 @@
You need to separate playlist sync state from download attempts.
The goal should not be “all 400 downloaded in one run”. It should be:
All downloadable items eventually reach a final state. Temporary failures are retried later. Permanent failures are recorded clearly.
Use statuses like:
queued
downloading
completed
temporary_failed
rate_limited
verification_required
unavailable
private
geo_blocked
copyright_blocked
age_restricted
unsupported
failed_permanent
skipped_by_user
removed_from_playlist
Main logic:
1. Fetch playlist metadata
2. Create/update queue items for all playlist videos
3. Download available queued items
4. On normal temporary errors: retry later
5. On YouTube rate-limit / bot check: pause the whole sync
6. On unavailable/private/deleted videos: mark as permanent failure
7. On next sync: retry only retryable items
Important: do not delete queue records just because download failed. Keep them as sync records.
For each video, store:
video_id
playlist_id
playlist_position
wanted_format: audio | video
status
failure_type
failure_message
attempt_count
last_attempt_at
next_retry_at
is_retryable
local_file_path
Retry behavior:
temporary_failed -> retry with backoff
rate_limited -> pause playlist/app queue, retry much later
verification_required -> pause until user action
unavailable/private/deleted -> do not retry often
geo/age restricted -> do not retry unless settings/auth changed
Example backoff:
attempt 1: retry after 10 minutes
attempt 2: retry after 1 hour
attempt 3: retry after 6 hours
attempt 4: retry after 24 hours
attempt 5+: retry manually or during next scheduled sync
For the user, show a sync summary:
Playlist sync partially completed
Downloaded: 200
Queued: 0
Retry later: 80
Needs attention: 1
Unavailable: 119
The playlist is still tracked. Retryable items will be attempted again in the next sync.
Best behavior for the 400-item example:
200 downloaded
50 unavailable/private/deleted -> mark permanent
149 temporary/rate-limited -> retry later
1 bot/verification error -> pause sync and ask user
Do not cancel the whole sync as “failed”. Mark it as:
completed_with_issues
paused_needs_attention
partially_synced
In UI terms, the playlist should have a health/status:
Synced
Syncing
Partially synced
Paused - needs attention
Error
The most important rule:
Never lose the reason why an item did not download.
That lets your app eventually download everything possible without repeatedly hammering YouTube or confusing the user.
## What to change to match the plan
Fix the destructive UPSERT behavior in src/app/core/database/db.py / SyncService.sync_from_config() so scans update metadata (title/index/last_seen) but do not overwrite existing downloaded/local_filename (and later: status/failure fields).
Introduce a persistent table (or extend playlist_items) with:
status, failure_type, failure_message, attempt_count, last_attempt_at, next_retry_at, is_retryable, wanted_format, local_file_path.
Update ActionExecutor / worker layer to write transitions into DB (queued → downloading → completed / temporary_failed / rate_limited / verification_required / failed_permanent).
Change “next sync” selection to only pick queued or retryable && next_retry_at <= now, not everything each time.
Add summary/health states (partially_synced, paused_needs_attention, etc.) based on counts.