diff --git a/docs/plans/2026-04-06-playlist-db-design.md b/docs/plans/2026-04-06-playlist-db-design.md new file mode 100644 index 0000000..68f8b13 --- /dev/null +++ b/docs/plans/2026-04-06-playlist-db-design.md @@ -0,0 +1,80 @@ +# Playlist & Processed-Files Database Design + +## Overview + +Two new features added to the existing 8-cut tool: + +1. **Playlist** — a queue of source video files; auto-advances to the next file after each export. +2. **Processed-files database** — SQLite store of exported source filenames; warns when a newly loaded file is fuzzy-similar to one already processed. + +## Stack additions + +- `sqlite3` (Python built-in) — persistent DB at `~/.8cut.db` +- `difflib.SequenceMatcher` (Python built-in) — fuzzy filename similarity, threshold 0.75 + +## Layout + +``` +┌──────────────────────────────────────────────────────┐ +│ [Drop video files here] │ +├─────────────────┬────────────────────────────────────┤ +│ QUEUE │ │ +│ ▶ clip_a.mp4 │ mpv video preview │ +│ clip_b.mp4 │ │ +│ clip_c.mp4 ├────────────────────────────────────┤ +│ │ Timeline ──────|cursor────── │ +│ ├────────────────────────────────────┤ +│ │ [▶ Play 8s] [⏸] cursor / dur │ +├─────────────────┴────────────────────────────────────┤ +│ Name: [clip] Folder: [~] → clip_001.mp4 [Export] │ +└──────────────────────────────────────────────────────┘ +``` + +- Drop one or multiple files → all added to the queue +- Clicking a queue item loads it immediately +- `▶` marks the current file +- Auto-advances to next item after a successful export + +## Components + +### `ProcessedDB` + +Wraps `sqlite3`. DB file: `~/.8cut.db`, created on first run. + +Table: +```sql +CREATE TABLE IF NOT EXISTS processed ( + filename TEXT NOT NULL, + processed_at TEXT NOT NULL +) +``` + +Methods: +- `add(filename: str)` — inserts row with `filename` and current UTC ISO timestamp +- `find_similar(filename: str) -> str | None` — loads all filenames, iterates with `SequenceMatcher(None, a, b).ratio() >= 0.75`, returns the best-matching stored filename or `None` + +### `PlaylistWidget(QListWidget)` + +- Accepts file drops (via `dragEnterEvent` / `dropEvent`) +- Emits `file_selected = pyqtSignal(str)` when an item is activated (click or auto-advance) +- `add_files(paths: list[str])` — appends paths, skips duplicates already in the list +- `advance()` — selects the next item; if at end, does nothing (queue exhausted) +- Current item shown with a `▶ ` prefix in the display text; others show bare filename + +### `MainWindow` changes + +- Drop zone (`dragEnterEvent`/`dropEvent`) now calls `playlist.add_files(paths)` instead of loading directly +- `playlist.file_selected` connected to `_load_file` +- `_after_load`: calls `db.find_similar(os.path.basename(path))` — if match found, shows `"⚠ Similar to already processed: {match}"` in status bar +- `_on_export_done`: calls `db.add(os.path.basename(self._file_path))`, then `self._playlist.advance()` + +## Warning behavior + +- Warning is informational only — does not block export +- Shown in the existing status bar with a `⚠` prefix +- Shown each time a file is loaded (queue advance or manual click) + +## Error handling + +- DB connection errors on startup: logged to stderr, app continues without DB (warning feature silently disabled) +- Queue exhaustion (advance past last item): silently does nothing; user can add more files