Commit Graph

400 Commits

Author SHA1 Message Date
Miguel Palhas d7e9f710e0 Merge #233: stop offering a forced subtitle grab
Closes #233
2026-08-25 08:47:00 +01:00
Miguel Palhas 097e081f4d fix(arr): drop the forced flag from subtitle grab
§15 gives forced tracks no sidecar name and never lets them
satisfy a want; ranking already rejects every forced candidate
(#222). A grab endpoint accepting `forced: true` had nowhere
coherent to put the result, so the flag and its stale test are
gone (#233).
2026-08-25 08:42:56 +01:00
Miguel Palhas 1126a52bbb Merge #222: one sidecar per language, enforced in the schema
Closes #222
2026-08-25 08:35:58 +01:00
Miguel Palhas 2c5cd85499 Merge #220: configure the OpenAI-compatible endpoint and model
Closes #220
2026-08-25 08:35:54 +01:00
Miguel Palhas a45d8ff86a style(arr): give the OpenAI endpoint its own row
Five fields wrapped the model onto a line of its own and clipped the base
URL placeholder. Paired under one label, the way the translator budgets
already are, and the base URL gets the width it needs.
2026-08-25 08:33:47 +01:00
Miguel Palhas abad9b9cfd fix(arr): name the field, not a reply, in the 422
The backend's Malformed Display describes an answer that came back wrong.
Nothing is sent while validating a settings write, so only the reason
belongs in the message.
2026-08-25 08:33:47 +01:00
Miguel Palhas e467bd47fb feat(arr): add the OpenAI endpoint fields to /settings
Shown only when openai is among available_engines. Placeholders say the
base URL is optional and what a local one looks like.
2026-08-25 08:29:59 +01:00
Miguel Palhas 5aa914f81c refactor(arr): retire the OpenAI bootstrap keys
#216 added translate_openai_model as an explicit stopgap; the database row
replaces it, along with translate_openai_base_url. Only the API key stays
in the environment. deny_unknown_fields makes a config file still carrying
either one a parse error, so the move is visible rather than ignored.
2026-08-25 08:29:59 +01:00
Miguel Palhas e8bc766d4b feat(arr): expose the OpenAI endpoint on /settings
Two more fields on the subtitle settings row, validated on write — a base
URL that does not parse is a 422 naming the field — and pushed into the
cell the running backend and the health lamp both read.
2026-08-25 08:29:59 +01:00
Miguel Palhas c6906bffae feat(arr): make the OpenAI endpoint a live setting
The base URL and model move into a cell the backend re-reads per request,
so an operator can repoint it without a restart. Migration 0028 adds the
two columns; DESIGN.md §15 calls both database rows. Construction never
depends on the API key — llama.cpp serves without one.
2026-08-25 08:29:45 +01:00
Miguel Palhas d358c58844 test(arr): a forced candidate writes no sidecar
Ranking already rejects one (#185); this holds the loop to it at the end
of the pipeline, where the row would appear.
2026-08-25 08:28:56 +01:00
Miguel Palhas 3122d5b0a0 feat(arr): refuse a grab for a satisfied language
The 409 on a second subtitle for one language now comes from §15's
invariant rather than from the sidecar filename, so a provider fetch is
refused even when the language is held by a `.mt.srt` the path check
cannot see. The message names the manual delete as the way to replace
it. `claim_path` stays: two `media_files` rows for one video still
derive the same name from different ids.
2026-08-25 08:28:56 +01:00
Miguel Palhas 29c31beceb feat(arr): one sidecar per language, in the schema
DESIGN.md §15 as amended: a language is satisfied by exactly one
sidecar, and no filename segment distinguishes forced from plain from
SDH. A unique index over sidecar rows says so; embedded rows keep their
own key, since several tracks for one language can legitimately coexist
inside a video.

Existing databases may hold a duplicate from a manual grab that beat the
API's path check, so the migration resolves them rather than failing: a
real subtitle beats a machine translation, and of two of the same kind
the newest wins. The files stay on disk for the manual delete to clean
up.

`record_file` no longer swallows every conflict — only the two that mean
"arr already knows this file".
2026-08-25 08:28:51 +01:00
Miguel Palhas ab001b512f docs(arr): settle the two open §15 questions
Forced and SDH get no sidecar name of their own: one language, one
sidecar, and a forced track is ignored where a plain one exists. That
makes the database constraint fall out rather than needing a scheme.

The OpenAI-compatible backend's base URL and model become database rows.
It is not "OpenAI" — it is any endpoint speaking that shape, llama.cpp
included — so which one is in use is something to try and change, not a
property of the deployment. Only the API key stays in the environment.
2026-08-25 08:17:36 +01:00
Miguel Palhas 84dc5ba27b docs(arr): sharpen the unconfigured provider lamp
Said 'not configured'; the branch is specifically about missing bootstrap
credentials, and after #215 only a provider that needs them can reach it.
2026-08-25 06:51:11 +01:00
Miguel Palhas 60257f3177 Merge #223: offer subtitle delete in the UI
Closes #223
2026-08-25 06:49:39 +01:00
Miguel Palhas b73a58d1a4 Merge #225: cache the remote-command probe
Closes #225
2026-08-25 06:49:39 +01:00
Miguel Palhas 80101e789b Merge #215: drop the unused Podnapisi credentials
Closes #215
2026-08-25 06:49:39 +01:00
Miguel Palhas 73d3f04398 fix(arr): cache the remote-command probe verdict
Reused for 5 minutes (#225) instead of running the configured
command on every /api/health poll.
2026-08-25 06:40:50 +01:00
Miguel Palhas 5f1fffdd97 feat(arr): drop unused podnapisi credentials
Podnapisi's search and download are unauthenticated (#188), so the
ARR_PODNAPISI_USERNAME/PASSWORD fields were config nothing read. §10
keeps bootstrap config an honest list.
2026-08-25 06:40:38 +01:00
Miguel Palhas 687c0c106f feat(arr): delete affordance on subtitle chips
Adds a two-click delete icon to each present-subtitle chip, naming
the sidecar in its title/aria-label. Wires the existing DELETE
/api/subtitles/{id} endpoint and reuses the panel's refresh() so
chips and the manual panel repaint after a delete (#223).
2026-08-25 06:40:26 +01:00
Miguel Palhas 5bc9022046 fix(arr): pass the podnapisi seam to the lamp probes
#200 branched before #205's ARR_PODNAPISI_URL seam was pushed, so its new
broken::SubtitleUpstreams call site was written against the three-argument
subtitle_providers. The merge was textually clean and did not build.
2026-08-25 06:31:27 +01:00
Miguel Palhas 8043ef614a Merge #200: lamp the subtitle upstreams
Closes #200
2026-08-25 06:30:34 +01:00
Miguel Palhas 228db06d83 Merge #221: give an expired candidate a typed error
Closes #221
2026-08-25 06:30:34 +01:00
Miguel Palhas 783a6ba760 Merge #224: clear subtitle attempts on language drop
Closes #224
2026-08-25 06:30:34 +01:00
Miguel Palhas a4422e26e5 style(arr): formatting 2026-08-25 06:28:20 +01:00
Miguel Palhas a836967e32 feat(web): subtitle lane under the signal chain 2026-08-25 06:24:32 +01:00
Miguel Palhas 8c6d4ca577 feat(daemon): fold subtitle lamps into broken notifications 2026-08-25 06:12:22 +01:00
Miguel Palhas be7fa87e74 feat(api): subtitle lamps in the health report 2026-08-25 06:04:30 +01:00
Miguel Palhas 8c5613b247 feat(arr): probe methods for subtitle providers and engines 2026-08-25 05:54:19 +01:00
Miguel Palhas 2357e72113 fix(api): give an expired subtitle candidate a typed error
A grab naming a stale candidate_id now fails as ApiError::SubtitleCandidateExpired (404, code candidate_expired) instead of the generic upstream 503 string the panel had to pattern-match for 'not found'.
2026-08-25 05:35:18 +01:00
Miguel Palhas 7b4def4516 style(arr): cargo fmt 2026-08-25 05:32:25 +01:00
Miguel Palhas 3cc9ab4aff fix(arr): clear subtitle attempts on language drop
Removing a language from wanted_languages left its subtitle_attempts
rows behind, resurrecting stale backoff on re-add (#224).
2026-08-25 05:32:08 +01:00
Miguel Palhas baea1dddbb Merge #204: add the subtitle section to settings
Closes #204
2026-08-25 05:26:19 +01:00
Miguel Palhas fe00220c5b Merge #205: cover the subtitle path in arr-e2e
Closes #205
2026-08-25 05:26:19 +01:00
Miguel Palhas bc43084f74 Merge #217: sync translated subtitles with alass
Closes #217
2026-08-25 05:26:19 +01:00
Miguel Palhas 71c161eff7 ci(arr): install alass for the e2e workflow
No distro package for it; cargo install alass-cli, symlinked to the
name the wrapper actually invokes. Also builds the daemon with
translate-command and triggers on arr-subs changes.
2026-08-25 05:25:37 +01:00
Miguel Palhas 6ee79519ca test(arr-e2e): cover the subtitle path end to end
Cross-process against the real arr binary, real ffmpeg and real alass:
a provider fetch synced and named under DESIGN.md §15, never
re-searched once satisfied; an embedded English track extracted and
translated into a .mt sidecar; an implausible sync kept unsynced and
surfaced in the missing-subtitles queue. Podnapisi and the translation
backend are stubbed at the HTTP/process boundary, never a live tracker
or a live translation API.
2026-08-25 05:25:33 +01:00
Miguel Palhas e47d2c920b feat(arr-e2e): extend the harness for subtitle scenarios
Daemon::spawn_with_env for extra child environment, database_path()
and media_root() accessors so a test can seed a media file directly,
the daemon built with translate-command, and the plausible/farfetched
Podnapisi zip fixtures the subtitle tests download from.
2026-08-25 05:25:28 +01:00
Miguel Palhas 5fc6854705 feat(arr): add an env-only Podnapisi URL seam
ARR_PODNAPISI_URL, same shape as the existing tmdb_url seam: a test
harness can point the provider at a wiremock fake without touching
DESIGN.md §10's config surface.
2026-08-25 05:25:25 +01:00
Miguel Palhas da1a932459 fix(arr): correct alass reference/subtitle argument order
Syncer::run passed [subtitle, video] where alass expects
<reference-file> <incorrect-sub-file>; every real invocation failed
before comparing timings, silently degrading to SyncState::NotRun.
2026-08-25 05:25:19 +01:00
Miguel Palhas 6d665e7c2f feat(arr): render subtitle settings in /settings
Adds wanted languages, translation engine (restricted to
available_engines), remote-command timeout, and an ordered
enable/disable list per provider with daily budgets, wired to
the existing GET/PUT /api/settings/subtitles from #198.
2026-08-25 05:08:58 +01:00
Miguel Palhas 9f0a7de37a fix(arr): sync translated subtitles too 2026-08-25 04:56:35 +01:00
Miguel Palhas e0e7ddf6de docs(arr): correct the subtitle lane's comment
Said translation backends are passed empty; #216 wired them and #219 made
both callers share one set.
2026-08-25 04:55:06 +01:00
Miguel Palhas 78aabe99e5 Merge #202: add the missing-subtitles queue
Closes #202
2026-08-25 04:54:12 +01:00
Miguel Palhas a62a8de8fc Merge #219: apply the remote-command timeout setting
Closes #219
2026-08-25 04:54:12 +01:00
Miguel Palhas b3cca4f692 fix(api): drop subtitle queue gaps for unwanted languages
An attempt row survives after a language leaves wanted_languages —
missing_for (#201) already bounds by the current wanted set, so the
queue reads the same way instead of showing a stale gap forever.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 03:16:54 +01:00
Miguel Palhas e338a76c66 fix(arr): wire the command timeout to the settings row 2026-08-25 03:15:33 +01:00
Miguel Palhas 47c7ef6682 fix(arr): re-read the command timeout every batch 2026-08-25 03:15:33 +01:00
Miguel Palhas a780b49ab3 feat(web): missing-subtitles queue alongside no-pt-source
A third lane in the attention queues view: one row per movie or
series with a gap chip per language and why (#186's reasons, plus a
sync alass flagged). Reuses the existing deck-group markup and
missingChipLabel wording so it reads like its two siblings; each
row opens the title's own page, where the #203 fetch/translate
panel already carries the resolving actions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 03:11:34 +01:00