POST /Library/VirtualFolders/Name is the odd one out in that controller --
most of /Library/VirtualFolders/* takes an id, and passing one here returns
a bare "HTTP 400: Error processing request." that says nothing about why.
Verified against 10.11.4: name -> 204.
Also records that renaming re-ids the library, because Jellyfin derives the
ItemId from the name. ytstream is unaffected because find_library matches on
path -- confirmed by a full refresh-metadata over 257 NFOs with 0 proxy
requests straight after the rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Matches libraries by path, never by name, and reads the name to delete back
from the API rather than assuming it: the endpoint takes a name, matches
loosely on some versions, and "YouTube" is a prefix of "YouTube (stream)".
Refuses to retire the old library unless ytstream's has episodes, so a
broken replacement cannot leave the server with no YouTube library. Dry run
by default.
Also records that /opt/youtube-automate is verified fully pushed to its bare
repo -- clean tree, specs.md tracked -- so it is safe to delete, while
/var/lib/youtube-automate holds subs.db, which is state and not in the repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cron now calls `ytstream run` hourly and ytstream's update-ytdlp.sh weekly.
Both inherit youtube-automate's healthchecks UUIDs and keep its schedules
unchanged: a check may be configured with a cron expression rather than a
simple period, so moving to the :23/04:50 slots the fragment proposed could
have alerted on a job that ran fine. Inheriting also means the placeholder
UUIDs never needed filling in.
The old service turned out to track only 2 channels, and one of them --
The Pyramid Podcast -- was sitting unresolved in ytstream's approval queue.
Decommissioning without checking would have silently dropped half of what
the old service existed to follow. Approved and backfilled.
decommission.sh does the two steps needing root (nginx repoint, disable the
unit) and refuses until an admin password is set, because the UI fails
closed and the hostname would otherwise serve a login nobody can pass.
Deleting the 2.0 GB of old downloads and touching the Jellyfin libraries
are left out on purpose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The session map is memory-only, so session directories surviving a restart
can never be served and never be evicted -- and the work root is a tmpfs,
so that is leaked RAM until reboot. The restart that ships the TTFB fix
would have stranded 1.56 GB.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Playback failed in Jellyfin the day after deployment, for uncached videos
only. The handler blocked on the full download-and-mux before sending
anything at all -- not even response headers -- so a 46-minute upload sat
silent for 79 seconds and the client gave up. The proxy counted it a
success, which is why /healthz and doctor both looked fine.
Yesterday's "DirectPlay verified" only ever ran against videos already
pulled during testing, so the first-play path was never exercised.
The output is already a fragmented MP4, so it is readable while being
written; a finished file only buys a correct duration and working seeks.
Cap the wait at --first-byte-grace (12s, explicit in the unit) and stream
whatever has not muxed by then. Measured: 156s -> 12.0s TTFB on a
66-minute upload, with ranges on a complete file unchanged.
The cost is a first play with no seek bar when the grace is missed. Every
later play of that video is perfect.
Also fixes a hang found while testing this: the streaming loop waited on
"finished and good" rather than "finished", so a producer that died after
writing some bytes held the connection for the full 45s stall timeout and
then dropped it.
The stale yt-dlp warning pointed at youtube-automate's venv, the tree we
are decommissioning; it now names ytstream's. /healthz reports the serving
mode and grace. Eight new proxy tests, 353 passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>