Write subtitle sidecars into the title folder #195

Closed
opened 2026-08-24 21:28:32 +01:00 by naps62-yolo · 1 comment
Owner

Write subtitles into the library, in the §7.4 title folder next to the video.

Naming, per DESIGN.md §15 (added by #183):

Dune Part Two (2024) [tmdbid-693134] - [2160p][WEB-DL][HDR10].pt-PT.srt
Dune Part Two (2024) [tmdbid-693134] - [2160p][WEB-DL][HDR10].pt-PT.mt.srt

The .mt segment marks a machine translation, keeping §7.4's property that
ls tells the truth with the app stopped.

  • Writes are atomic: write beside the target and rename, never a half-written
    sidecar that Jellyfin picks up mid-write.
  • Call Jellyfin's refresh for the affected library after writing, the same
    single call §7.5 already makes on import.
  • Record the written path on the subtitle row (#186).
  • Deletion needs no new code — sidecars live inside the title folder, which
    §7.4 deletes atomically. Confirm that holds for the season subfolders on the
    TV side.

Verify against the live Jellyfin before closing: that it reads
….pt-PT.srt as Portuguese, and that ….pt-PT.mt.srt is also read as
Portuguese rather than as a subtitle titled "mt". If the extra segment breaks
the language read, fall back to a plain filename with the machine flag living
only on the database row, and amend §15 to match — do not ship a marker
Jellyfin misreads.

Depends on: #186, #194, #212, #213

Write subtitles into the library, in the §7.4 title folder next to the video. Naming, per DESIGN.md §15 (added by #183): ``` Dune Part Two (2024) [tmdbid-693134] - [2160p][WEB-DL][HDR10].pt-PT.srt Dune Part Two (2024) [tmdbid-693134] - [2160p][WEB-DL][HDR10].pt-PT.mt.srt ``` The `.mt` segment marks a machine translation, keeping §7.4's property that `ls` tells the truth with the app stopped. - Writes are atomic: write beside the target and rename, never a half-written sidecar that Jellyfin picks up mid-write. - Call Jellyfin's refresh for the affected library after writing, the same single call §7.5 already makes on import. - Record the written path on the subtitle row (#186). - Deletion needs no new code — sidecars live inside the title folder, which §7.4 deletes atomically. Confirm that holds for the season subfolders on the TV side. **Verify against the live Jellyfin before closing:** that it reads `….pt-PT.srt` as Portuguese, and that `….pt-PT.mt.srt` is also read as Portuguese rather than as a subtitle titled "mt". If the extra segment breaks the language read, fall back to a plain filename with the machine flag living only on the database row, and amend §15 to match — do not ship a marker Jellyfin misreads. Depends on: #186, #194, #212, #213
naps62-yolo added this to the Subtitles milestone 2026-08-24 21:28:32 +01:00
naps62-yolo added the area/daemondifficulty/moderatephase/9-subtitlestype/feature labels 2026-08-24 21:28:32 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:27 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:27 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:28 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:28 +01:00
naps62-yolo added a new dependency 2026-08-24 22:37:27 +01:00
naps62-yolo added a new dependency 2026-08-24 22:37:27 +01:00
Author
Owner

Implemented the sidecar-write half of §15: naming and atomic write already existed from #199 (layout::subtitle_name, subtitles.rs's write_sidecar/claim_path/finish), so the actual gap was the Jellyfin refresh call. JellyfinClient lived in arr-daemon, a binary crate arr-api cannot depend on, so I moved it into arr-api (arr_api::jellyfin), attached an optional instance to AppState via with_jellyfin, and made the subtitle grab and translate handlers call refresh() after a successful write — same single call §7.5 already makes on import, best-effort (a refresh failure logs and never fails the write). arr-daemon now builds one JellyfinClient in main.rs and hands it to both the reconcile loop's ImportAction and the API's AppState. Added tests covering the refresh call and its failure tolerance.

Branch: subtitles/195-write-sidecars. just ci passed in full (639/639 tests, fmt/lint/deps/web-check clean).

Findings

  • Could not verify against a live Jellyfin. This worker's sandbox has no network reachability to the Jellyfin host (ICMP reaches it, TCP 8096 times out — looks firewalled from this segment). The issue asks to confirm ….pt-PT.srt reads as Portuguese and ….pt-PT.mt.srt reads as Portuguese rather than a subtitle titled "mt" before closing. That check still needs to happen from a session with real LAN access before this can be considered validated; I did not add a fallback naming scheme since I have no evidence the current one is wrong.

  • Season/episode deletion never removes subtitle sidecars from disk, and now silently orphans them. remove_library_files in arr-api/src/series.rs resolves targets from media_files rows only; for FileScope::Season/FileScope::Episode it deletes the exact video file, not the season folder (only a whole-series delete remove_dir_alls the title folder). remove_scope_files then deletes the media_files row in the same transaction, and subtitle_files.media_file_id is ON DELETE CASCADE, so the subtitle's database row disappears too — while the .srt/.mt.srt file stays on disk, now with nothing in the database pointing at it. The doc comment on remove_library_files ("Sidecars beside a removed episode therefore stay; they are not rows this service wrote") predates subtitles (#174/#175, phase/6, before #186) and is now stale — subtitle files are rows this service wrote. This is a real gap in area/api's removal path, not something #195's scope covers; worth its own issue.

Implemented the sidecar-write half of §15: naming and atomic write already existed from #199 (`layout::subtitle_name`, `subtitles.rs`'s `write_sidecar`/`claim_path`/`finish`), so the actual gap was the Jellyfin refresh call. `JellyfinClient` lived in `arr-daemon`, a binary crate arr-api cannot depend on, so I moved it into `arr-api` (`arr_api::jellyfin`), attached an optional instance to `AppState` via `with_jellyfin`, and made the subtitle grab and translate handlers call `refresh()` after a successful write — same single call §7.5 already makes on import, best-effort (a refresh failure logs and never fails the write). `arr-daemon` now builds one `JellyfinClient` in `main.rs` and hands it to both the reconcile loop's `ImportAction` and the API's `AppState`. Added tests covering the refresh call and its failure tolerance. Branch: `subtitles/195-write-sidecars`. `just ci` passed in full (639/639 tests, fmt/lint/deps/web-check clean). ## Findings - **Could not verify against a live Jellyfin.** This worker's sandbox has no network reachability to the Jellyfin host (ICMP reaches it, TCP 8096 times out — looks firewalled from this segment). The issue asks to confirm `….pt-PT.srt` reads as Portuguese and `….pt-PT.mt.srt` reads as Portuguese rather than a subtitle titled "mt" before closing. That check still needs to happen from a session with real LAN access before this can be considered validated; I did not add a fallback naming scheme since I have no evidence the current one is wrong. - **Season/episode deletion never removes subtitle sidecars from disk, and now silently orphans them.** `remove_library_files` in `arr-api/src/series.rs` resolves targets from `media_files` rows only; for `FileScope::Season`/`FileScope::Episode` it deletes the exact video file, not the season folder (only a whole-series delete `remove_dir_all`s the title folder). `remove_scope_files` then deletes the `media_files` row in the same transaction, and `subtitle_files.media_file_id` is `ON DELETE CASCADE`, so the subtitle's database row disappears too — while the `.srt`/`.mt.srt` file stays on disk, now with nothing in the database pointing at it. The doc comment on `remove_library_files` ("Sidecars beside a removed episode therefore stay; they are not rows this service wrote") predates subtitles (#174/#175, phase/6, before #186) and is now stale — subtitle files *are* rows this service wrote. This is a real gap in area/api's removal path, not something #195's scope covers; worth its own issue.
naps62-yolo added a new dependency 2026-08-25 01:50:50 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: yolo/arr#195