SubtitleTrack now carries the codec, split text formats from bitmap
ones per DESIGN.md §15, plus the forced and SDH dispositions ffprobe
reports. Without the forced flag a file carrying only a forced track
read as satisfied for that language.
An empty season deck was three truths wearing one message, and the one
it chose to blame was wrong: a season on the per-episode lane sat on
"sweeping indexers…" for the full wait and then blamed a backoff for a
pack search that was never going to run.
`GET /api/series/{id}/seasons/{n}/pack-state` says which lane the
season takes and why, from `season_grab_reason` in arr-core, plus the
failed-pack tally and when #181's window reopens. Seasons gain
`last_pack_search_at`, written only by a season-scoped sweep, so a
pack search that ran and found nothing is a settled answer rather than
a pending one.
The deck then says the true thing in each case, and a season held off
the pack lane by a failure offers the retry that waives its window.
Refs #182
A size band describes one episode (DESIGN.md §5.5), so both the target
penalty and the floor now compare a release's size divided by the number
of episodes it covers. The caller supplies the count — arr-core has no
IO. Movies and unknown counts divide by one, so movie scoring is
unchanged and pinned by test.
Closes#180
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A failed season-pack grab held the season off the pack lane forever:
pack_hard_failed was a bare EXISTS over failed grabs, so one bad
torrent disabled pack search for good, against §6.2's "it never gives
up entirely, it goes quiet".
The guard now rides the shared backoff curve (backoff_elapsed, 1h → 6h
→ 1d → 3d, capped 7d), counting failed pack grabs as attempts and
anchoring on the latest one's grabbed_at. Both the targeted and RSS
lanes agree. A manual season search waives the window outright — the
season deck is §6.2's escape hatch.
Closes#181.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The metadata lane runs daily, so a series added a moment ago showed no
seasons for up to 24 hours and a movie had no digital release date —
the field §6.2 gates targeted search on.
AppState now carries a MetadataCommand channel alongside the movie,
episode and season ones. Both create handlers send on it after the row
is committed, and a new daemon lane drains it. Its own task rather than
an arm of manual::run: a refresh against TMDB can take a while and must
not sit in front of an operator's manual search.
The add never waits on TMDB and never fails because of it. A refresh
that fails leaves metadata_refreshed_at NULL, which is what the daily
sweep already treats as due, so the title is retried rather than lost.
A command naming a title deleted in between finds no row and does
nothing. METADATA_INTERVAL is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A series' first metadata refresh reveals its whole back catalogue, and
apply_auto_track flagged every season as new — adding Rick and Morty
tracked S01-S09 and wanted 91 episodes (#160).
Per DESIGN.md 4.1 the rule applies from the second refresh onward.
The caller passes whether metadata_refreshed_at is set; arr-core stays
IO-free and decides. Wired through the daemon's daily refresh and the
API's create-season endpoint.
TMDB reports vote_average: 0 where no rating exists (#156). Normalise
it to None at the arr-meta edge, like non_empty does for "", and let
the Option flow through the daemon refresh and API add paths so the
nullable columns from #145 do their job.
§9.6 keeps rich detail out of the database except for the three fields
pure-SQL views need. Adds poster_path, backdrop_path and vote_average to
movies and series, written by the daily metadata refresh in both lanes
and filled at add time from the TMDB response the create flows already
fetch.
One feed pass now serves films and episodes alike. A
single-episode release grabs its open episode directly; a season
pack only grabs when season_grab_mode allows packs for that
season (§14, #117), and when it wins, the singles stand down.
Blocked series keep matching RSS (§6.3) and nothing here touches
the targeted-search backoff (§6.2).
MovieCommand::Search loaded the movie through the gap filter, so an
available movie fell out and the search was a silent no-op. Split the
lane: a gap still searches and grabs, anything already satisfied
refreshes the release deck (DESIGN.md 9.3) without grabbing, and a
blocked movie still refuses.
Closes#115
Overrides #86: removing a torrent by hand in Transmission is human
intent, not a gap to refill. Clear `wanted` and mark the target
`parked` (a new movies/episodes state) rather than reopening it as
`missing`, so neither targeted search nor RSS matching pick it back
up. Blacklist stays untouched, since the release never failed policy.
Closes#108
Nothing drained AppState's movie_commands mpsc, so manual search and
one-click grab were accepted with 202 and then did nothing until the
64-slot buffer filled and the endpoint started 503ing.
A new daemon task drains it: Search resets the movie's backoff and
runs the targeted-search + grab lane immediately; Grab sends the
already-chosen release straight to Transmission.
Closes#107
Transmission has no route to Prowlarr, and Prowlarr redirects a
.torrent link to a magnet that a file fetch cannot follow. arr fetches
the link itself and sends Transmission a magnet or the torrent bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PRs #82 and #85 were each green pre-merge; #85 renamed movie states
(grabbed->downloading, imported->available) and #82's import path
still wrote the old names, so main failed the movies CHECK constraint.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>