f640c064c63f48b49598aeb5a0e0f94a2a054b58
The key works. All three assumptions the plan rested on hold, so nothing in the
design had to change — but the measurements moved several numbers by a lot, and
one of them exposed a design problem the plan did not have an answer for.
Verified against the live API:
subscriptions.list?channelId= 200, 119 channels over 3 pages. An API key is
sufficient; no OAuth, no consent screen.
playlistItems.list on UULF Accepts the undocumented long-form playlist id
and returns exact videoPublishedAt. 1,249 items
against 2,724 in UU, so it excludes 54% of the
catalogue. The UU-plus-duration-filter fallback
is not needed.
videos.list durations 50 ids in one call, 50 back, 0 unparseable. Of
those 50 consecutive UU uploads, 38 were <=120s,
which is the Shorts filter earning its keep.
The account has 119 subscriptions, not the ~20 this plan assumed, so rather than
extrapolate I probed all 119 UULF feeds: 441 long-form videos in the last 30
days, median 1 per channel, and all 119 feeds polled in 7.9s on 8 threads. The
library is therefore smaller than the previous 20-channel estimate despite six
times the channels, because the distribution is severely skewed. Quota, scan cost
and poll cost are all now measured rather than guessed, and none of them is a
constraint: ~130 units to build, ~75/day steady state, under 1% of the budget.
The problem that fell out: 52 of 117 channels uploaded nothing long-form in 30
days, so they would appear in Jellyfin as empty series, and a channel uploading
every six weeks would flicker in and out as its one video crossed the retention
line. With a median of 1 upload a month that is the common case, not an edge
case. Recommended fix in §5 — retention becomes max(30 days, N most recent
videos) via min_keep_videos, default 5, which costs ~260 extra episodes and
removes the flicker entirely because a video only leaves once 5 newer ones exist.
Flagged as a decision rather than applied unilaterally.
Also recorded, because it cost three attempts to get the key working and every
failure was an indistinguishable 403 forbidden: SERVICE_DISABLED means the API is
not enabled on the project, API_KEY_SERVICE_BLOCKED means the key's own
restrictions exclude it, they are fixed on different console screens, and the
signal is in error.details[].reason rather than error.errors[].reason. The
ordering matters — the API must be enabled before the key can be restricted to
it, because it is absent from the picker until then. And after the fix, calls
still failed at 0s, 30s and 60s and succeeded at 90s; nothing was wrong except
impatience.
One more trap worth having in writing: pageInfo.totalResults reported 127 while
pagination returned 119 distinct channels, and 2 of those 119 have feeds that
HTTP-error. Terminated and private channels are still counted as subscriptions,
so anything sized off totalResults will be wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Languages
Python
96.8%
Shell
3.2%