Surface the existing pin (keep-from-cull) and per-file delete actions as visible inline buttons on each offline cache row instead of context-menu-only: a star toggles protection from auto-cull (and restore-if-missing), a trash culls that file early. Aligns wording/icons to the star metaphor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| test/fleet | ||
| tools | ||
| bun.lock | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
portable-net-tv
Follows VLC playback, tracks every show you watch, and prefetches the next few episodes of whatever's on into a local buffer.
One profile-less service handles all shows: it reads the currently-playing file from VLC's HTTP interface, and that file's own directory is the show's source — no per-show configuration.
The watch service
portable-net-tv watch
Runs a daemon that, every 30 s:
- Reads the currently-playing file from VLC's HTTP playlist.
- Appends episode changes to the cross-show watch log.
- Prefetches the next N episodes sitting in the playing file's directory into the buffer dir (single rsync at a time; honors a disk floor).
- Garbage-collects the buffer down to exactly that prefetch window.
Files with a sibling <episode>.broken marker are skipped at every step.
next
portable-net-tv next
Reads the watch log and prints, per show, the episode watched through and the
next one. Only genuine play/resume events count toward progress — queued
-but-unwatched episodes do not.
Watch log
Append-only JSONL at ~/.local/state/tv-anarchy/watched.jsonl (legacy
tv-anarchy-mcp path auto-migrated by the shared media package). One event per
line (see the main docs for full schema + clients + finished + durationSeconds).
ts, event ("play"|"resume"|"reset"), show, season, episode, label, path,
resumeSeconds, durationSeconds, client. The show name is parsed from the
filename (everything before the SxxEyy marker) when not supplied. "play" (or
=92 % position) advances per-show progress.
Config
~/.config/portable-net-tv/config.json:
{
"vlcHttp": { "host": "127.0.0.1", "port": 8080, "password": "<password>" },
"buffer": { "dir": "/Users/natalie/Movies/net-tv-buffer", "ahead": 3, "minFreeGB": 2 }
}
buffer is optional — it defaults to ~/Movies/net-tv-buffer, ahead 3,
minFreeGB 2.
VLC HTTP interface
The service reads VLC over HTTP (requests/playlist.json), not AppleScript —
so it works as a launchd background agent, where Apple Events to VLC are
blocked by macOS Automation (TCC). Enable it once:
defaults write org.videolan.vlc extraintf -string http
defaults write org.videolan.vlc http-host -string 127.0.0.1
defaults write org.videolan.vlc http-port -int 8080
defaults write org.videolan.vlc http-password -string <password>
If the interface or config is missing, the service treats VLC as unreachable and idles rather than failing.
Run at login
launchd keeps the service alive across reboots —
~/Library/LaunchAgents/com.quinn.portable-net-tv.watch.plist, RunAtLoad +
KeepAlive. Put Homebrew's bin first on the plist's PATH: the service
needs a modern rsync (--append-verify), not the ancient /usr/bin/rsync.
Broken media
A corrupt source file is flagged by touching a sibling marker next to it:
touch "/path/to/Show.S01E07.broken"
The watch service excludes any episode with a .broken marker — it won't
prefetch it, and episodesInDir skips it. Mark a file whenever it refuses to
play (e.g. VLC opens it but the time stays frozen at 0:00).
The TVAnarchy Mac app also offers "Mark as broken (governor skip)" in library episode context menus (right-click an episode in Library or Continue Watching). It SSHes to storage and touches the sibling marker for you. (Unmark is available via the same DevicesConfig helper if you need to re-enable a file.)
Constraints
- macOS only — reads VLC's macOS HTTP interface; paths assume the local media mount.
- VLC is the only supported player, and must be running with HTTP enabled.
Legacy
keep, play, check, and the per-show profiles under
~/.config/portable-net-tv/profiles/ predate the unified watch service and
are being retired. New use should rely on watch + next only.