Skip to content

0.4.0: subtitles from OpenSubtitles on films and episodes - #26

Merged
ralyodio merged 2 commits into
mainfrom
feat/opensubtitles
Oct 14, 2026
Merged

ralyodio merged 2 commits into
mainfrom
feat/opensubtitles

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Adds subtitles to Movies & Shows, fetched from OpenSubtitles.

In the player: a CC button on the controls (or c on a keyboard/remote) on any film or episode opens a panel: a language picker, then what exists, best match first (THIS FILE / TRUSTED / CC tags, download counts). Pick one and it plays as a <track>. You can adjust timing with −½s / +½s (◀ / ▶ in the panel), and the pick plus its timing are remembered per title.

Matching: the provider's TMDB id when it gives one (now carried on Xtream movies and shows), else the cleaned-up title and year (EN - The Matrix (1999) 4K → the matrix, 1999). An episode is matched by its show + season + episode. The server also computes the OpenSubtitles moviehash of the file itself with two range requests, so subtitles made for that exact rip come first.

Quota: a free key gets 5 downloads/IP/day, and an account gets its own allowance. Every file is saved to disk by file id, so only the first viewer of a title spends a download. On hdtilt.com each account may spend HDTILT_SUBS_PER_DAY (default 10) new downloads a day.

Converted films: an ffmpeg session starts at offset, so /subs/<id>.vtt?shift= moves the cues server-side. After a seek, the client just refetches the cached file with the new shift. While the controls are up, cues are lifted (cue.line) so the banner does not cover them.

Surfaces: POST /api/subtitles, GET /subs/<id>.vtt, hdtilt subs CLI (--key saves a key to the config), MCP find_subtitles / fetch_subtitle (fetch is local only).

Config: OPENSUBTITLES_API_KEY turns it on; OPENSUBTITLES_USERNAME / OPENSUBTITLES_PASSWORD are optional. With no key, the CC button stays hidden and the API answers 501.

Tests

  • test/subtitles.test.js (22): title cleanup, sorted query params, ranking, SRT→VTT, shift, moviehash math and range reads, client caching/login/quota, server endpoints and the per-viewer cap.
  • Full suite 109 pass / 0 fail; biome clean.
  • Browser e2e (puppeteer, real Chrome, stub OpenSubtitles backend, local mp4 under /movie/): 17/17. Covers the CC button, the real moviehash sent, the panel, the track showing with cues on screen, lift/unlift, ◀◀ = shift=-1, Esc, remembered after reload, live TV dropping the track and the button.

🤖 Generated with Claude Code

ralyodio and others added 2 commits October 14, 2026 23:20
A CC button (or `c`) on any film or episode lists what OpenSubtitles has in
your language, best match first, and the pick plays as a <track>.

- core/subtitles.js: search (TMDB id when the panel gives one, else the
  cleaned-up title + year; an episode by its show, season, episode), a
  moviehash of the file by two range reads so subtitles made for that rip
  come first, SRT -> WebVTT, cue shifting, login for an account's quota.
  Every file is kept on disk by file id: only the first viewer spends a
  download.
- Server: POST /api/subtitles (gated like /api/vod on hdtilt.com),
  GET /subs/<id>.vtt?shift= (media cookie), HDTILT_SUBS_PER_DAY new
  downloads per viewer (10 on a public server), health says subtitles.
- Web: panel with language, sync -1/2s +1/2s, remembered per title; cues
  shifted for a converted film's offset, lifted above the controls.
- CLI `hdtilt subs`, MCP find_subtitles / fetch_subtitle (local only).
- Xtream movies and shows carry tmdbId.

Turned on by OPENSUBTITLES_API_KEY (+ _USERNAME / _PASSWORD).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The real API answered "Breaking Bad" S01E01 with The Bad Guys: Breaking In
on top. Asked by title (no TMDB id), results now rank the same name first,
then names with every word, then the year asked for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 377d200 into main Oct 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant