feat: integrate Miruro playback and episode progress

This commit is contained in:
2026-10-12 22:23:15 +02:00
parent 0fe24ff302
commit 0e909001d0
9 changed files with 1676 additions and 92 deletions
+22 -12
View File
@@ -1,13 +1,13 @@
# tubular
A modal terminal YouTube browser built with Rust and [Ratatui](https://ratatui.rs/). Search for videos, organize subscribed channels with overlapping tags, browse their feeds, and open a selected video in Terebi.
A modal terminal YouTube and Miruro browser built with Rust and [Ratatui](https://ratatui.rs/). Search for videos or anime episodes, organize subscribed YouTube channels with overlapping tags, and open a selection in Terebi.
## Requirements
- 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.
- [`yt-dlp`](https://github.com/yt-dlp/yt-dlp) on `PATH` for YouTube searches, feeds, and full descriptions; Miruro uses the native API client.
- `curl` on `PATH` for thumbnails.
- 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 **Terebi CLI** installed on `PATH`, together with its app bundle and streaming dependencies, for playback. YouTube requires `--new-instance --url … --progress-file PATH`; Miruro additionally requires `--source-file PATH` and version-2 episode progress support.
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.
@@ -34,6 +34,7 @@ cargo run --locked -- "rust programming"
```sh
tubular # Start without a query
tubular "rust programming" # Search immediately
tubular --miruro "apothecary" # Search Miruro instead
tubular --player /path/to/terebi "japanese podcast"
tubular --help
```
@@ -49,6 +50,15 @@ terebi --new-instance --url 'https://www.youtube.com/watch?v=VIDEO_ID' \
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.
### Miruro episodes
Use `--miruro` at startup, or reach the permanent **Miruro** tab with Tab / Shift-Tab. Press `i` to edit its search and Enter to submit. Autocomplete returns at most eight catalog titles, without pagination. Enter on a title loads its returned episodes; Enter on an episode resolves a fresh direct **ssub / sun** HLS source and launches Terebi with a private source document. `/` still opens YouTube Search.
Episodes support watch later, saved videos, currently watching, and watched status. Only episodes—not catalog titles—can be saved. Reopening a saved episode resolves new URLs; identity is the opaque Miruro catalog ID plus its returned episode number. Miruro entries appear in combined collections, not YouTube channel categories. Channel/tag actions and YouTube thumbnails/extraction do not apply.
Stream and external VTT subtitle headers are supplied explicitly; Terebi selects the source's declared default subtitle. Sources requiring media redirects, cross-origin HLS, proxies, iframe playback, or provider-specific playlist rewriting fail explicitly. No provider/language fallback or automatic expired-source refresh is performed.
## Controls
These are the default bindings. Overrides in `[keys]` replace an action's bindings; the live hints and `?` reference show the configured keys.
@@ -58,9 +68,9 @@ These are the default bindings. Overrides in `[keys]` replace an action's bindin
| Key | Action |
| --- | --- |
| `j` / `k`, ↓ / ↑ | Select the next / previous video |
| Enter | Open the selected video in Terebi |
| Enter | Open a Miruro catalog title's episodes, or launch the selected video/episode in Terebi |
| `g` / Home, `G` / End | Select the first / last loaded video |
| `i` | Edit the current category/combined feed's search, or general Search from other views |
| `i` | Edit the current category/combined feed or Miruro search; other views use general YouTube Search |
| `/` | Go directly to general YouTube Search |
| `F` (Shift-F) | Go directly to the subscription Feed |
| `d` | Open video info; press again for the full description, again to return to info |
@@ -205,7 +215,7 @@ 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.
- **Search**, **Feed**, and **Miruro** are permanent tabs. Feed combines all YouTube `[channels]` registrations, including untagged channels, plus channels entered directly in categories, without duplicates.
- Browsing an unpinned category, combined-category feed, channel, watch-later list, or saved-video 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.
@@ -214,7 +224,7 @@ Bulk editing refuses to clear all tags when a target has no `[channels]` registr
## 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.
Watch later is **one collection**, deduplicated by YouTube video ID or stable Miruro episode identity. Category lists are derived from saved YouTube 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`. Miruro episodes appear only in the combined list. No category memberships are stored on individual videos.
`W` opens a searchable collection selector from any video view or preview. The All categories (combined) entry comes first, followed by configured categories; it remains available even when there are no categories. Navigate with `j` / `k` or arrows in normal mode, or press `i` to filter by name and use arrows / `Ctrl-j` / `Ctrl-k` while typing. `Enter` browses the selected match; with no matches it does nothing. `Esc` returns to normal mode, then closes the selector without navigating. Reopening clears the search. `Alt-w` still opens the current category's watch later directly.
@@ -234,15 +244,15 @@ Files are siblings of `config.toml`:
- `saved-videos.toml`: independent versioned saved-video metadata.
- `currently-watching.toml`: versioned metadata for started, unfinished videos.
- `watch-state.toml`: versioned global watched video IDs.
- `playback-sessions/`: private per-launch progress streams, expected video identities, video snapshots, and durable admission markers.
- `playback-sessions/`: private per-launch progress streams, expected identities, metadata snapshots, durable admission markers, and Miruro source documents.
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.
Progress sessions survive Tubular exit so later player reports can be recovered on the next launch. Miruro source documents are `0600` files in `0700` session directories and remain unchanged, even after launcher exit or reported end/close; they contain private, expiring resource URLs and headers. Sessions are retained because CLI exit does not prove the independent GUI has finished; up to 4,096 are monitored. Close the corresponding players before removing their inactive session directories, then restart Tubular. Never remove an active player's directory.
## Saved videos
Saved videos is a separate collection from watch later, deduplicated by YouTube video ID. It uses the same offline title, creator, and publication metadata and follows **current channel category memberships**, not categories stored on individual videos.
Saved videos is a separate collection from watch later, deduplicated by YouTube video ID or stable Miruro episode identity. It uses the same offline title, creator, and publication metadata; YouTube entries follow **current channel category memberships**, not categories stored on individual videos. Miruro episodes appear in All categories (combined).
`V` opens the same searchable selector for saved videos, with **All categories (combined)** first and configured categories beneath it. `Alt-v` opens the current category's saved videos directly. `Alt-w` / `Alt-v` retain the explicit category when switching between category-scoped collection lists; without a current category, they leave the view unchanged. All four shortcuts work in normal mode and previews, not text-entry fields or modal selectors. Both collections share the existing temporary-tab and back/forward history behavior.
@@ -264,7 +274,7 @@ Use `x` for individual removal or `X` for bulk confirmation. Removal neither mar
## Collection removal
Removal controls apply **only in watch-later, saved-video, and currently-watching views**, including previews opened from those lists. Search, Feed, ordinary channel/category views, and their previews do not expose removal actions or hints. Text fields and selectors retain their normal controls.
Removal controls apply **only in watch-later, saved-video, and currently-watching views**, including previews opened from those lists. Search, Feed, Miruro catalog/episode browsing, ordinary channel/category views, and their previews do not expose removal actions or hints. Text fields and selectors retain their normal controls.
`x` immediately removes only the selected video from the active collection. `X` snapshots the current list and opens bulk confirmation: watch later uses the current category and All/Unwatched/Watched filter; saved videos uses the current category's entire saved list; Currently watching uses its entire combined list. An empty list does not open confirmation.
@@ -322,7 +332,7 @@ Video info shows a thumbnail, title, author/publication details, and a clipped d
## Development
[Miruro API notes](docs/miruro-api.md) document the native `tubular::miruro` wrapper and verified transport behavior. Miruro is not yet integrated into the TUI or Terebi playback.
[Miruro API notes](docs/miruro-api.md) document the native `tubular::miruro` wrapper, source-document projection, stable episode/progress contracts, and verification limits.
```sh
cargo fmt --check