feat: add watch-later views and Terebi watched-state tracking
This commit is contained in:
@@ -7,7 +7,7 @@ A modal terminal YouTube browser built with Rust and [Ratatui](https://ratatui.r
|
||||
- A recent stable Rust toolchain, including Cargo, to build and install.
|
||||
- [`yt-dlp`](https://github.com/yt-dlp/yt-dlp) on `PATH` for searches, feeds, and full descriptions.
|
||||
- `curl` on `PATH` for thumbnails.
|
||||
- The **Terebi CLI** installed on `PATH`, together with its app bundle and streaming dependencies, for playback.
|
||||
- The **Terebi CLI** installed on `PATH`, together with its app bundle and streaming dependencies, for playback. It must support `--new-instance --url … --progress-file PATH`.
|
||||
|
||||
The default playback setup requires **macOS 15+**, since Terebi is a native macOS player. Install Terebi separately and follow its dependency/setup instructions; installing Tubular does not install the player.
|
||||
|
||||
@@ -43,10 +43,11 @@ Tubular starts in **normal mode**, with or without a command-line query. Press `
|
||||
Selecting a video launches:
|
||||
|
||||
```sh
|
||||
terebi --new-instance --url 'https://www.youtube.com/watch?v=VIDEO_ID'
|
||||
terebi --new-instance --url 'https://www.youtube.com/watch?v=VIDEO_ID' \
|
||||
--progress-file '/private/session/progress.jsonl'
|
||||
```
|
||||
|
||||
Each selection opens an independent player instance without replacing existing playback. `--player` overrides the Terebi CLI executable; it is not a generic mpv/IINA adapter. Tubular reports launcher failures, while later streaming/playback errors appear in Terebi. Quitting Tubular leaves launched players running.
|
||||
Each selection opens an independent player instance without replacing existing playback. Tubular creates a private reporting session and consumes Terebi's progress records independently of the launcher. `--player` overrides the Terebi CLI executable; it is not a generic mpv/IINA adapter. Tubular reports launcher failures, while later streaming/playback errors appear in Terebi. Quitting Tubular leaves launched players running.
|
||||
|
||||
## Controls
|
||||
|
||||
@@ -69,6 +70,10 @@ These are the default bindings. Overrides in `[keys]` replace an action's bindin
|
||||
| `U` (Shift-U) | Open the selected video's channel |
|
||||
| `s` | Edit the selected channel's tags |
|
||||
| `S` | Edit tags for all saved channels in the current category |
|
||||
| `w` | Save the selected video to watch later (also works in previews) |
|
||||
| `W` | Browse watch later for the current category, or the combined collection elsewhere |
|
||||
| `f` | In watch later, cycle All → Unwatched → Watched |
|
||||
| `D` | In watch later, confirm removal of all videos matching the current list/filter |
|
||||
| Tab / Shift-Tab | Switch between visible views |
|
||||
| `r` | Reload configuration and refresh the current view |
|
||||
| `?` | Open / close the scrollable keybinding reference |
|
||||
@@ -157,12 +162,40 @@ Category entries accept `@handles`, HTTPS YouTube channel URLs, or canonical `UC
|
||||
Video tables color channel names **cyan** when the creator belongs to at least one category. Untagged registrations keep the default text color; selected rows retain their black-on-cyan highlight. Membership is matched by channel ID or handle, not the displayed name, and updates after saving tags or reloading config.
|
||||
|
||||
- **Search** and **Feed** are permanent tabs. Feed combines all `[channels]` registrations, including untagged channels, plus channels entered directly in categories, without duplicates.
|
||||
- Browsing an unpinned category opens one temporary tab; choosing another replaces it. Pinned categories keep their own tabs, marked with a dimmed `p`.
|
||||
- Browsing an unpinned category, channel, or watch-later list uses one shared temporary tab; choosing another replaces it. Pinned categories keep their own tabs, marked with a dimmed `p`.
|
||||
- Removing a channel from a category does **not** unsubscribe it from Feed. To unsubscribe, remove its `[channels]` mappings and category references, then press `r`.
|
||||
- Temporary tabs do not survive restarting; saved categories and pins do. Hidden views retain loaded rows, selection, and pagination during the session.
|
||||
|
||||
Bulk editing refuses to clear all tags when a target has no `[channels]` registration, preventing accidental unsubscription of category-only channels. If source memberships change while a bulk dialog is open, close and reopen it before saving.
|
||||
|
||||
## Watch later
|
||||
|
||||
Watch later is **one collection**, deduplicated by YouTube video ID. Category lists are derived from the saved videos' channel identities and the **current** subscription categories: adding or removing a channel from a category changes its watch-later list after saving tags or reloading config with `r`. No category memberships are stored on individual videos.
|
||||
|
||||
Browse a category with `c`, then use `W` for its watch-later list. From Search, Feed, or a channel view, `W` opens the combined collection. Watch-later views use saved title, creator, and publication metadata without fetching video lists, so they also work offline. Their filters are independent and retained during the session.
|
||||
|
||||
Saved video titles are **magenta** in all video tables; categorized channel names remain cyan. A `✓` marks watched videos, including outside watch-later views. The selected row retains its usual highlight.
|
||||
|
||||
### Watched status
|
||||
|
||||
Terebi reports acknowledged playback positions. A video becomes watched when an eligible position reaches **95% of a known positive duration**. Seeking near the end counts; live/non-seekable, unknown-duration, and audio-only inputs do not. Launcher exit or window closure alone never marks a video watched. Watched status is global across categories and remains after removing a video from watch later.
|
||||
|
||||
### Removal
|
||||
|
||||
`D` snapshots all videos matching the current list and filter, then shows their count, category, and filter. **`y` confirms; `n` or Esc cancels.** Enter does not confirm. Removal deletes those videos from the shared collection, so they disappear from every matching category; watched status is untouched. An empty filtered list has nothing to remove.
|
||||
|
||||
### Storage
|
||||
|
||||
Files are siblings of `config.toml`:
|
||||
|
||||
- `watch-later.toml`: versioned saved-video metadata.
|
||||
- `watch-state.toml`: versioned global watched video IDs.
|
||||
- `playback-sessions/`: private per-launch progress streams and their expected video identities.
|
||||
|
||||
State writes are atomic and owner-only. Invalid state files or detected external edits are reported rather than overwritten; repair the file or restart before retrying. Each state file is limited to 8 MiB.
|
||||
|
||||
Progress sessions survive Tubular exit so later player reports can be recovered on the next launch. They are retained because the GUI may still be writing; up to 4,096 sessions are monitored. If that limit is reached, close the corresponding players, remove their inactive session directories, and restart Tubular. Never remove an active player's reporting directory.
|
||||
|
||||
## Configurable keybindings
|
||||
|
||||
Print a complete default configuration, including every action and its default bindings:
|
||||
|
||||
Reference in New Issue
Block a user