# Changelog All notable changes to Engram will be documented in this file. ## [Unreleased] ## [0.43.1] - 2026-10-22 _Highlights: fixes a data-loss bug in the Windows in-app updater, which permanently deleted any folder kept inside the Engram folder (such as a library at `D:\engram\tv`). Windows users must install this release by hand, once; the steps are below._ ### Fixed - **In-app updates on Windows could permanently delete folders kept inside the Engram folder.** If your TV or movie library, staging folder or backups sat inside the folder Engram runs from (for example `D:\engram\tv`, with Engram in `D:\engram`), a successful in-app update from any version between 0.15.3 and 0.43.0 deleted them together with the old build. The delete bypassed the Recycle Bin. The updater now removes only the previous build's own files, keeps the old folder (and says where) if anything else is in it, never clears an existing `.old` folder, and refuses to update while the Engram folder holds anything it didn't install, naming what is in the way. Folders elsewhere on the drive were never at risk. (#825) **An update is carried out by the version you already have, so on Windows install this release by hand:** close Engram, move any library, staging or backup folders out of the Engram folder, then extract `engram-win-x64.zip` to a new folder and run `engram.exe` from there. Your settings and database live in `C:\Users\\.engram` and carry over. The Windows download is now named `engram-win-x64.zip`, a name older versions don't recognise, so they cannot apply this release themselves; they report that no release asset was found. - Review-page keyboard shortcuts no longer drop a key pressed the moment the page finishes loading. The shortcut listener used to start a moment after the page appeared, and the first track selection could overwrite an early `j`/`k`. This also ends the random review-shortcut failures in the frontend unit tests. (#716) ## [0.43.0] - 2026-10-22 _Highlights: segment-format cartoons (Hey Arnold!, Dexter's Laboratory, SpongeBob) now filed under their TMDB segments, a **Test login** button for OpenSubtitles, settings that need a restart now say so with a **Restart now** button, and a shorter first-run Preferences step._ ### Added - A **Test login** button next to the OpenSubtitles fields, in setup and in Settings. It logs in with the entered (or saved) credentials and says which part failed: the username or password, the API key, a rate limit or a used-up daily quota, or no connection. On success it shows how many downloads are left today. The password and key are never echoed back or logged. (#723) - Settings that only apply after a restart now say so. After saving one (concurrent matches, GPU acceleration, staging cleanup, the stuck-job watchdog, LAN access), Settings offers **Restart now** on the packaged build, relaunching Engram and reconnecting the dashboard. The button is disabled with a reason while a disc operation is running, and a source or Docker run gets instructions instead. The GPU acceleration control offers it too once the GPU is enabled. Engram never restarts without that click. (#722) ### Changed - The first-run **Preferences** step is now short. It asks only how to name your library and how to handle extras, and says the rest uses recommended defaults, changeable later in Settings. It fits on one screen without scrolling at 1366x768. Every other option (matching, cleanup, watchdog, notifications and the rest) is still in Settings, under Preferences, and no default value changed. The setup summary of paths and keys moved out of this step; Settings still shows it. (#726) ### Fixed - Segment-format cartoons (Hey Arnold!, Dexter's Laboratory, SpongeBob and similar) are filed under the TMDB segments each disc track holds, such as `S02E11-E12`, instead of under a number that named an unrelated episode. The shared subtitle pack numbers these seasons by broadcast half-hour while TMDB lists every segment separately, so Hey Arnold! S2 Disc 2 was filed as `S02E06` to `S02E10` when it holds `S02E11-E12`, `S02E13`, `S02E14-E15`, `S02E16-E17` and `S02E18-E19`. The subtitle pack now records which segments each half-hour holds, and Engram files tracks by that. A season the pack cannot yet map is held for review with an explanation instead of being filed under the wrong number. Full effect needs the next nightly subtitle-pack update. (#760) ## [0.42.1] - 2026-10-22 _Highlights: fixes for movie bonus discs (the wrong track filed as the movie, deleted titles still offered, a new **File all as extras** action) and for TV episodes misfiled as extras when TMDB lists the wrong runtime._ ### Fixed - A movie disc whose tracks run about the same length could file a bonus track as the movie and the real pick into `Extras/`. The organizer took the biggest file in staging rather than the title Engram had chosen, and recorded every unmatched title at the movie's path, so a track still flagged as an extra showed as filed there. It now files exactly the chosen title. Choosing a bonus track with **Select** on the review page also clears its extra flag, so the job's record matches what was filed. (#814, thanks @katelovescode!) - The movie review page no longer offers **Select** on titles whose files were deleted when another title was selected. With nothing left to pick it says so, instead of asking for one of several feature-length titles, and the server refuses such a selection rather than trying to re-rip a disc that has left the drive. (#812, thanks @katelovescode!) - A bonus disc that identifies as the movie itself can now be finished. When even its longest title is far shorter than the movie's runtime, Engram holds the disc for review instead of filing a featurette as the film, and the movie review page has a new **File all as extras** action that files every title you kept into the movie's `Extras/` folder and completes the job. Extras added this way (or from a second movie disc) are numbered after the ones already there instead of replacing them. (#813, thanks @katelovescode!) - TV episodes are no longer filed as extras when TMDB lists the wrong episode length for the show. TMDB lists every MXC episode at 30 minutes while the DVD episodes run about 20, so every track on the disc missed the runtime check and went to Extras without being matched. Engram now also checks the disc itself: when no group of similar-length tracks on the disc fits TMDB's runtime, the tracks in the disc's episode-length group go on to matching, and one that can't be matched goes to review instead of Extras. Discs whose episodes do fit TMDB keep the existing check, so their featurettes are still filed as extras. (#801) ## [0.42.0] - 2026-10-21 _Highlights: settings export and import, a database backup before every upgrade, History search, review-page shortcuts, a minimum title length setting, and fixes for mounted ISOs, the Windows updater, movie extras and non-English discs._ ### Added - **Minimum Title Length** in Settings sets MakeMKV's minimum title length (`--minlength`, 120 seconds by default, MakeMKV's own default). Lower it for discs whose specials run under two minutes, which MakeMKV otherwise hides from Engram entirely. The same value is used for every scan, rip and backup so title numbers stay consistent. (#771, thanks @tzimpel!) - Engram now saves a copy of its database before upgrading it. When it starts on a new version, or finds the database's layout needs updating, it first writes `engram.db.bak--` next to `engram.db`, keeping the three newest copies, so a bad upgrade can no longer cost you your API keys, settings or job history. The copy is made with SQLite's own backup, so it includes writes not yet folded into the main file, and a startup that crashes partway through an upgrade keeps its original copy rather than replacing it on every retry. If the copy can't be written, Engram logs a warning and starts anyway. The Troubleshooting guide explains where to find the copies and how to restore one. (#730) - Keyboard shortcuts for the review page. `J`/`K` (or the arrow keys) move between tracks, `1`, `2` and `3` accept the ranked candidate, `E` marks a track as an extra, `S` skips it, `/` jumps to the episode picker and `?` lists them all. Shortcuts pause while you type in a field or a dialog is open, never take Ctrl/Cmd/Alt chords, and the selected track now has a visible focus ring. (#716) - **Search on the History page.** A search box matches the volume label, detected or TMDB title, matched episode codes and error text, case-insensitively and across all pages. The query is kept in the URL (`/history?q=...`) so a search can be bookmarked, and the summary tiles follow it. (#727) - Settings can be exported to a file and imported on another install, from **Settings > Import & Export** or the first step of setup. Keys and passwords stay out of the file unless you tick **Include keys and passwords**. Before anything changes, an import shows every setting it would apply, with paths missing on this machine unticked and invalid values explained, and it goes through the same checks as saving in Settings. Setup status, LAN access and fingerprint-sharing consent are never carried over. (#731) ### Changed - **Force advance** now only appears beside Cancel and Eject once a job looks stuck, meaning it has made no progress for three minutes. The backend reports this as `looks_stuck` / `stalled_for_seconds` on the job (REST and WebSocket), taken from the same activity clock the stale-job watchdog uses, and the button's tooltip says how long the job has been idle. On a healthy job Force stays one click away in a **More actions** menu. (#718) - The dashboard footer no longer shows made-up telemetry (`UNIT 07`, `BUFFER NOMINAL`, `CPU IDLE`...) or a platform-based `DRIVE READY`. The drive label now reflects the optical drives Engram detects (`2 DRIVES`, `1 DRIVE · BUSY` while a disc is being read, `IMPORT ONLY` when there are none), and the scrolling band reports the real ASR device with whether transcription is running, jobs per phase, and discs awaiting review. (#720) ### Fixed - Unmounting an ISO and mounting another one on the same drive letter now starts a new job right away; before, Engram did not notice the new disc until it was restarted. Unmounting removes the virtual drive letter outright, and Engram stopped checking a letter once it disappeared, so it still believed the first disc was in the drive. A drive letter that disappears now counts as an empty drive, the same as taking a disc out. (#769) - **Reassign** in History now lists every episode of the season. The picker was built only from episodes other tracks on the disc already held, so the one episode the disc was missing, the one a mis-filed track usually is, could never be chosen. It now reads the season's episode list, with episode titles, and falls back to a full numbered range when that list can't be loaded. (#770) - An episode track is no longer filed as an extra just because it is shorter than the show's listed runtime when other tracks of the same length on the disc already matched as episodes. Some shows list a 30-minute broadcast slot while the disc's episodes run 20 minutes, so a track that matching couldn't place on the first pass was then filed under Extras next to its matched siblings. Engram now keeps matching such a track, and sends it to review if it still can't be placed. (#770) - Settings no longer throws away unsaved edits when you click outside it, and Escape now closes it. The close button, Escape and an outside click all go through one path: Settings closes at once when nothing has changed, and otherwise asks **Discard unsaved changes?** with **Keep editing** selected. The first-run setup wizard ignores Escape and outside clicks entirely. (#711) - The AI provider, API key and model fields in **Settings > Data Sharing > AI assistance** now show when either **AI-Powered Title Resolution** or **AI-Powered Episode Matching (TV)** is on. Before, a user who wanted only LLM episode matching could not reach them without also enabling title resolution. The episode-matching toggle now sits beside the title-resolution toggle, and the key hint names both features. (#546) - **Restart to Update** on Windows no longer fails at `move_install` on every update. Programs Engram starts inherit its working folder, which was the install folder, so a browser that Engram opened kept the install folder in use after Engram exited, and Windows refused to move it aside for the new version. Engram now moves off the install folder at startup, and the update helper retries a busy folder for up to 30 seconds before giving up. When it does give up, the notice names the folder and the reason Windows gave, the extra `engram.new-` copy is no longer left beside the install, and copies left by earlier failed updates are removed on the next start. This takes effect from the update after the one that ships it: a version that is already failing still has to be replaced by hand once, and closing your browser first lets that go through. (#791) - The review page no longer opens straight into episode assignment for a disc whose show is unknown. It now leads with **Identify this disc** (the same dialog as **Wrong title?**) and holds the episode tools back until the show is confirmed, then unlocks without leaving the page. In heuristic-only mode (no TMDB token) a typed show name counts as confirmed, so those discs keep their **Review** button instead of being stranded. (#715) - The History summary tiles now count the same jobs as the table. They used to include jobs still in review or in progress, so the default view could read 5 total jobs above a 3-row table. The tiles follow the type and state filters. (#728) - The **Skip extras** setting now works on movie discs. It used to apply only to TV, so a movie disc ripped every bonus track and filed them all under `Extras/`. Now, when the disc is identified as a movie, titles that cannot be the feature are skipped before the rip (shown on the dashboard card with the reason, and UN-SKIP still works), and any that slip through are discarded rather than filed. Every feature-length cut is still ripped. **Ask me** now covers movies as well: the disc waits in review, where you discard the extras you don't want and keep the rest. (#773) - Re-identifying or naming a TV disc whose episodes were all filed no longer starts a second rip. Engram decided whether the disc had been ripped by looking for files in staging, which is empty once everything is filed, so the job went back to ripping a disc that had already left the drive. It now also checks the disc's tracks, and re-matches instead: filed episodes stay where they are, as with any re-match. (#774) - Force on a job stuck in Matching now clears a track left queued with no match running behind it. Force used to skip queued tracks, since they are normally waiting for a free match slot, so a track whose match had died kept the job in Matching for good. Tracks still waiting for a slot, or held until you answer a name or show question, are left alone, and the automatic stall check is unchanged. (#776) - **Re-match all** is refused for movie discs instead of running TV episode matching on the movie's tracks. The review page only offers it for TV, so this affected direct API calls. (#775) - Two discs that start at the same moment no longer get mixed up. With several ISOs mounted when Engram starts, each virtual drive's disc began in the same second, and both jobs were given the same staging folder because its name only went down to the second. Both rips wrote into that one folder, so each job matched and filed the other disc's episodes (a Season 1 disc could come out labelled as Season 2). Every job now gets a staging folder of its own, named after the job. (#768) - A ripped track with no audio no longer fills the log with "FFmpeg failed with return code 4294967274" errors. MakeMKV can write a title without its only audio track (for example a lone Japanese track on an English-language disc), and every attempt to transcribe it failed with an error that showed only FFmpeg's version banner. Engram now notices the missing audio after the first attempt, logs one warning naming the track, and skips audio matching for it instead of trying ten more times. Other FFmpeg failures now log FFmpeg's actual error message instead of its banner. (#793) - TV tracks whose only audio is another language are no longer matched to a random episode. Engram used to transcribe them as if they were English and file or suggest whatever episode the noise landed on. It now checks each ripped file's audio tracks first: a track with no English audio goes to review with a note naming the language (for example "This track's only audio is Japanese"), and the job's review reason counts them. When the disc itself had an English track that MakeMKV's language settings left out of the rip, the note says so. On discs with several audio tracks, Engram now always transcribes the English one instead of whichever track the disc marks as default. Matching in a show's own language is planned next. (#241, thanks @wintheriscomming!) ## [0.41.0] - 2026-10-20 _Highlights: recover failed jobs from staging, Play-All tracks detected from the disc's own structure, import of MP4/TS files, and an external player for movie review._ ### Added - Failed jobs can be recovered without hunting through **Import**. When the staging folder still holds ripped files, the dashboard card and the History detail panel offer **Retry from staging**, which runs those files through import as a new job with the failed job's title and season (the disc is not needed, and the partial file of a track that was mid-rip is left out). History also offers **Open folder** (on the machine running Engram) and **Copy path**, and explains why retry is unavailable when the folder has been cleaned up. Tracks a failed job never ripped now read **Not ripped** instead of **Pending**. (#713) - Manual import now accepts `.mp4`, `.m4v`, `.ts` and `.m2ts` files as well as `.mkv`. Files are filed unchanged and keep their own extension in the library (an MP4 lands as `Show - S01E01.mp4`), and an existing copy of the same episode in another container counts as a conflict instead of becoming a duplicate. Picking a `BDMV` folder, its `STREAM` folder or a single stream clip imports the whole disc backup through MakeMKV, because those clips are fragments, not titles. - Movie review now has the same **Open in player** and **Copy stream URL** buttons as TV review, so you can watch each feature-length title in VLC or MPV before picking which cut to keep. - A **Getting Help & Reporting Issues** guide covering Trace, the support bot in the Engram Discord: run `/bug` or `/feature` and it gathers the details, checks for an existing issue, and drafts a GitHub issue you file with one click; attach a job's diagnostics bundle and it reads the logs for you. The README, Troubleshooting and Contributing pages, and GitHub's new-issue page, now point there. ### Fixed - Re-matching a disc whose episodes were already filed no longer leaves the job stuck in Matching. Filed episodes stay where they are, and the disc goes to review with a note saying where they were filed. (#757) - TV discs no longer rip the "Play All" track. Engram used to spot it only when its length came within 20% of a tight group of similar-length episodes, which missed discs whose episodes vary by a few minutes or include a double-length episode, so a multi-hour duplicate was ripped and matched. Engram now checks the disc against itself: on a Blu-ray, a playlist built from the clips of two or more episodes is the Play-All; on a DVD, a track whose length and size equal the sum of two or more episode tracks is. The track is left out before the rip starts. The old length check remains for discs where neither applies, and extras reels and single-title discs are not flagged. (#634) - A track you skip once ripping has started is no longer ripped in full and then deleted. When every track on a disc is selected, Engram rips the whole disc in one MakeMKV pass, and a skip during that pass was only honored by deleting the finished file, so skipping the first few tracks while MakeMKV was still opening the disc could waste half an hour. The pass now stops as soon as MakeMKV reaches a skipped track, and the tracks still wanted are ripped one at a time. A track that is already being written when you skip it still finishes and is then discarded, so a skip never cuts off the track in progress. (#765) - A "Play All" track that Engram has already set aside no longer shows as queued with a SKIP button that does nothing. The dashboard was told every track on a newly identified disc was pending, including the full-disc Play All track that is never ripped, so pressing SKIP on it was refused by the server and the card stayed put. Tracks now arrive with their real state, and if the server ever refuses a skip or un-skip, the dashboard says why and refreshes the card. (#754) ## [0.40.0] - 2026-10-17 _Highlights: TheTVDB episode numbering for shows whose discs follow it, plus fixes for narrow windows, MakeMKV licence errors, post-rip review, GPU downloads on Windows and the Custom naming convention._ ### Added - TheTVDB episode numbering for shows whose discs follow it. When TheTVDB numbers a season differently from TMDB (Justice League's three-part pilot is one TMDB episode), the review page offers to switch the show; Engram then re-downloads subtitles and re-matches in TheTVDB numbering, without re-ripping, and files episodes under TheTVDB's numbers. Release builds include a TheTVDB key; Docker and source installs set `TVDB_API_KEY` or enter a key in Settings. ### Fixed - The top bar no longer pushes the Settings button off-screen in narrow windows. Below 1100px the MANUAL and IMPORT buttons collapse to icons and the wordmark tagline is trimmed to a single line; below 900px the wordmark yields to the mark, so every control fits down to 768px (#712). - A job that fails because MakeMKV's licence has lapsed now says so. When the beta key has expired or the MakeMKV beta build is too old to run, the job's error (on the dashboard and in History) names the cause and the fix (copy the current key from the MakeMKV forum into Settings, or update MakeMKV) instead of the generic "No titles found on disc" or a raw rip error. (#714) - A failed `faster-whisper` import no longer reports "faster-whisper not installed" unless the package is really missing. A CUDA, ctranslate2 or av import problem now surfaces its own error type and message in the log and in the ASR status, so bug reports point at the real cause. (#760) - The review page no longer offers **Start rip** for a disc that has already been ripped. On a post-rip review (low-confidence matches, file conflicts) the button started a second full rip against a drive that was usually empty, failing the job, or holding a different disc, which was then ripped under this job's name. The button now appears only while the disc is still in the drive (nothing ripped yet, and no completed backup), and the server refuses such a start with a clear message. Partially ripped tracks still have their per-track re-rip. (#710) - Choosing **Custom** under Settings → Naming Convention works again. The dropdown ignored the click and stayed on Plex, Kodi or Minimal, so the season-folder and episode-filename fields never appeared. Those fields now also show for a saved custom episode format whose season folder matches a preset; before, the dropdown read "Custom" but the fields stayed hidden. (#777) - **Download & enable** for GPU acceleration works on Windows machines where it failed with `CERTIFICATE_VERIFY_FAILED`. The CUDA download was the one connection that checked certificates against the Windows store instead of the certificates Engram ships with, and Windows can be missing the root that files.pythonhosted.org uses. (#694) - A `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE` or `SSL_CERT_FILE` environment variable pointing at a file that does not exist no longer breaks TMDB validation and every other online lookup with "Could not find a suitable TLS CA certificate bundle". Engram logs a warning and uses its own certificates. A bundle that exists is still honoured. - When the CUDA libraries load at startup but the GPU model then fails to run, the ASR badge and the GPU settings now say it fell back to CPU, and the log records the error. Before, the badge kept showing "CUDA float16" while transcription ran on the CPU. ## [0.39.0] - 2026-10-09 _Highlights: "Skip extras" now skips short TV tracks before ripping instead of after._ ### Changed - **"Skip extras" now skips short TV tracks before ripping.** With the extras policy set to *Skip*, bonus featurettes on a TV disc were ripped and only discarded afterwards, so skipping them meant watching the dashboard and pressing SKIP on each one before the rip reached it. Engram now skips tracks that are clearly too short to be episodes before the rip starts. It compares each track with the season's TMDB episode runtimes and with the disc's own episode-length tracks, and skips only when both agree, so there is no fixed minute cutoff for short-episode shows to trip over. Skipped tracks stay on the card with the reason, and UN-SKIP puts any of them back in the rip queue. ## [0.38.1] - 2026-10-03 _Highlights: episode matching works again on the Windows, macOS and Linux downloads, which in v0.38.0 sent every TV disc to manual review._ ### Fixed - **Episode matching on the standalone builds.** Every v0.38.0 download (Windows, macOS and Linux) shipped a newer audio library that the speech recognizer could not open files with, so every transcription chunk failed and every TV disc fell back to manual review. Engram now decodes the audio itself and hands the recognizer the samples directly, so matching works again regardless of that library's version. The Docker image was not affected. (#709, #735) ## [0.38.0] - 2026-10-01 _Highlights: a bigger English-only subtitle cache (about 1,380 shows), one-click GPU acceleration, the dashboard on React 19, and a fix for databases stuck partway through schema migrations._ ### Removed - **Unused frontend dependencies.** 39 packages left over from the original UI template were never imported by the dashboard and have been dropped: recharts, react-hook-form, react-day-picker, date-fns, react-slick, react-responsive-masonry, react-dnd (+ html5 backend), embla-carousel-react, input-otp, vaul, cmdk, react-resizable-panels, next-themes, class-variance-authority, and 24 unused `@radix-ui/*` packages (only tooltip and select remain). No user-facing change; smaller installs and fewer dependency-update PRs. ### Changed - **Bigger, English-only subtitle cache.** The show list the nightly cache build walks was exhausted, so the published cache had stopped growing at 524 shows. It is now English-only (by original language, which catches Spanish-language shows from US networks) and extended to about 1,380 shows, ordered so the shows most likely to be ripped from disc are harvested first. Shows already in the cache stay in it. Episode matching for newly covered shows works without a live subtitle download. - **The dashboard now runs on React 19.** No visible change is intended; this keeps the frontend on a supported React line and unblocks dependency updates that already require it. Two unused packages (`react-popper`, `@popperjs/core`) that do not support React 19 were removed. ### Fixed - **Enabling GPU acceleration no longer looks like it silently resets.** The settings panel had a separate NVIDIA EULA checkbox that read as "turn CUDA on", but ticking it saved nothing and started no download. The "Download & enable" button is now the only control. The panel and the dashboard badge also tell "enabled, restart to apply" and "enabled, but the CUDA libraries failed to load" apart from "not enabled", where before all three offered "Enable" again. The backend now logs when a download starts, so a log shows whether it ever began. (#694) - **Long-upgraded databases no longer get stuck partway through their schema migrations.** On a database first created by an older release, one migration tried to drop a column that Engram's startup had already removed, failed, and left every later migration unapplied on every launch (logged as `Alembic migration failed (non-fatal): 'is_transcoding_enabled'`). All migrations now check the live schema first and skip work that is already done, so these databases catch up to the current schema on the next start. A migration that does fail now logs which revision is stuck and how many are blocked behind it. ## [0.37.0] - 2026-09-24 _Highlights: a movie already in your library can be replaced or kept alongside from the review page instead of looping, imported movies and season-named TV folders are filed correctly, and the published subtitle cache records each season's numbering scheme._ ### Added - **The published subtitle cache now records how each season is numbered.** Some shows are catalogued by TMDB as individual short segments while the subtitle sources number the longer broadcast episodes those segments were assembled into, and until now nothing in the published cache said which of the two a given season used. Every harvested season now carries that answer, along with the episode count it was measured against, and Engram reads it instead of guessing from how many reference subtitles a season happens to have. A track matched as the first episode of a season is no longer at risk of being filed under an unrelated episode number, and a season whose subtitles were topped up after publication is no longer mistaken for one of these shows. Caches published before this change keep working exactly as they did. ### Fixed - **A movie that is already in your library no longer gets stuck in review.** If the file Engram was about to create already existed, choosing a version and pressing Select just sent the disc back to review, over and over, with a fresh "needs review" notification each time. The review page now says which file is in the way and offers **Replace existing** or **Keep both** (the new rip is filed alongside as `(v2)`); Discard still keeps the library copy and drops the rip. The page also stays open when a save lands back in review, instead of returning to the dashboard with no explanation. The **Default Conflict Resolution** setting now takes effect for these movie reviews; until now it was saved but never used. The same conflict on a movie that never needed a review used to fail the disc outright; it now follows the setting too, and with the default of asking it opens the same review. For a TV disc the setting deliberately does not apply, because a TV episode that is already in the library usually means a duplicate or mis-matched track, and replacing it automatically could overwrite the right episode with the wrong one. (#685, thanks @katelovescode!) - **Imported folders named like "Psych Season 3" are now filed under the show's real name.** The import took the folder name as the show name, so a season rip named that way matched every episode correctly and still landed in `TV/Psych Season 3/Season 03/`. The import now reads the season out of names such as `Psych Season 3`, `Psych - S03` or `PSYCH_S3_D1`, and once TMDB has identified the show, the library folder uses TMDB's name for it. Titles that simply end in a number, like *Babylon 5*, are left alone. (#667) - **Imported movies are moved into the library again.** Importing an existing movie MKV with "Organize into library" identified the film correctly and then reported the job as completed without moving anything, leaving the file in the import folder. The movie is now renamed and filed under your Movies library using your naming format, and "organize in place" imports land beside the folder you picked. Only the files the import listed are moved: other MKVs in the same folder are left alone, and choosing between two imported cuts in review no longer deletes the one you didn't pick. (#676) ## [0.36.2] - 2026-09-20 _Highlights: box-set discs whose season label differs from TMDB now re-match across every season, segment-format shows are filed under the right episode number, and matched discs stop asking for a manual confirmation on every track._ ### Fixed - **A disc whose season label doesn't match the show's TMDB seasons is now re-matched across every season instead of failing outright.** Box sets split a show into their own "seasons": the Dexter's Laboratory DVD season 3 is canonically season 2. Engram took the disc label as the TMDB season, matched against a corpus of entirely different episodes, and handed the whole disc to manual review. When every track on a disc fails to match its labelled season, Engram now distrusts the label and searches all seasons, which is what it already did for a disc with no season label at all. - **Episodes are no longer filed under a shifted number when a non-aired ordering is selected.** For shows where TMDB catalogues each ~7-minute segment separately (38 entries for Dexter's Laboratory season 1) but the reference subtitles are per 22-minute broadcast (13), a track matched as S01E01 was filed as `S01E19.mkv`: the DVD-ordering projection read the matched number as a segment index it never was. Engram now detects that the reference corpus and the TMDB roster disagree on the season's size and keeps the matched number in the filename, so the file on disk matches what the review page showed. - **Segment-format discs no longer send every track to review.** The same size disagreement made the "this track looks like two joined episodes" check fire on every track of a disc while making its confirmation impossible, so a disc that matched perfectly still asked for 13 manual confirmations. The check now stays silent when the reference subtitles could never have confirmed it. A track whose audio genuinely shows two episodes is still held for review. ## [0.36.1] - 2026-09-19 _Highlights: two drives can rip at once, later discs in a box set no longer collide with disc 1, and OpenSubtitles failures are named instead of blamed on your API key._ ### Fixed - **A season refused for unreadable reference subtitles now recovers without a restart.** When a reference subtitle file was damaged, Engram rejected it and downloaded a fresh copy to the same path, but kept serving the old unreadable version from memory, so the season stayed refused with `references_unreadable` until the backend restarted. A replaced file is now re-read as soon as it changes. (#661) - **OpenSubtitles downloads work in the Docker image again.** Every download was saved into the app's install folder, which the container user cannot write to, so each one failed and the episode fell back to the scrapers (or got no reference subtitle at all). Downloads are now saved to the system temp folder before moving into the subtitle cache. (#653) - **A season with no subtitles now says why when OpenSubtitles failed.** When the daily OpenSubtitles download quota ran out, the job told you to add an API key even though yours was fine. The message now quotes the OpenSubtitles error and suggests Re-match all in Review once the quota resets. A quota that was already spent when Engram logged in also no longer switches OpenSubtitles off until the server restarts; Engram checks it again after an hour. (#654) - **Later discs in a box set no longer collide with disc 1 when the disc has no volume label.** Some drives (for example LibreDrive-flashed ones) report an empty label, so every disc was treated as disc 1: its extras took disc 1's filenames and organizing stopped on a "file exists" conflict. Engram now reads the disc number from the disc name MakeMKV reports (such as "Season 3 - Disc 2") when the label has none. (#655, thanks @wintheriscomming!) - **Two drives can rip at the same time.** With two drives connected, the rip that started second stayed at 0.0x until the stall timeout ejected its disc, because every MakeMKV call also checked the disc in the other drive and waited behind the running rip. Engram now tells MakeMKV to leave other drives alone, so both discs rip at once. The MakeMKV version check in Settings no longer times out while a rip is running, for the same reason. (#652, thanks @wintheriscomming!) ## [0.36.0] - 2026-09-16 _Highlights: a track holding several episodes can now be filed as one combined episode, and the shared subtitle cache is rebuilt and republished nightly by a server instead of by hand._ ### Added - **The shared subtitle cache can now be rebuilt and republished nightly by a server instead of by hand.** The cache Engram downloads to speed up episode matching is refreshed by a scheduled job, with a guard that refuses to replace the published cache with a materially smaller one, so a bad build cannot quietly shrink what everyone downloads. Operator runbook: `docs/development/subtitle-cache-server.md`. - **A track that holds several episodes can be filed as one combined episode.** Cartoon DVDs often put two or three short segments in a single track. When Engram confirms a track holds several episodes, review now opens with a combined assignment such as `S01E01-E03`, and confirming it names the file `Show - S01E01-E03.mkv`, the form Plex and Jellyfin read as every episode in the file. You can also type a combined code yourself. The season roster, conflict warnings, Discord summaries, the dashboard and TheDiscDB exports all count a combined track as each episode it holds, and it is never published to the fingerprint network, where a row names a single episode. (builds on #614, thanks @raiju!) ### Security - **The developer-only matcher endpoints are no longer reachable in a normal build.** Engram shipped a set of `/api/test/*` endpoints used while developing the episode matcher. They accepted a file path from the caller and ran ffmpeg and speech recognition on it, and nothing restricted who could call them, so anyone who could reach the Engram port could have had the server read and transcribe a file outside the media library. They now answer only when debug mode is on, and only from the host machine unless LAN access is enabled, the same rules the simulation endpoints already followed. Nothing in the dashboard used them, so there is no change to normal use. ### Fixed - **Every track on a disc no longer matches the same episode when a season's subtitles are damaged.** Engram compares each track's dialogue against a reference subtitle for every episode, and several common kinds of subtitle file were being read as containing no dialogue at all while still counting as valid, so they were cached and reused on every retry. On a Dexter's Laboratory set, 36 of the season's 37 references read as empty and every track matched the one episode that had text, at full confidence. Subtitles with doubled line breaks, missing cue numbers, timings written with one or three digits, or UTF-16 files with a stray trailing byte now read correctly. Files that genuinely hold no dialogue, such as blank cues or a placeholder that only repeats a download link, are now downloaded again instead of reused, and subtitles imported by hand on Windows keep their line breaks intact. - **Replacing a damaged subtitle no longer needs a restart to take effect.** When Engram re-downloads a reference subtitle that could not be read, the new file lands under the same name as the old one. The matcher was remembering subtitles by filename alone, so it kept using the unreadable version for the rest of the session and the season stayed stuck in review reporting that its references could not be read. Remembered subtitles and the search index built from them now also track when each file was last written, so a replaced file is picked up straight away. - **A season with too few usable reference subtitles now goes to review with an explanation instead of a confident wrong guess.** When fewer than two reference subtitles for a season contain dialogue, Engram no longer tries to pick an episode from what is left. Unless a disc mapping already identifies them, the tracks are held for review, the review screen says whether the season simply had too few subtitles or had subtitles that could not be read, and Engram stops re-running the match automatically. Review now also shows the explanation for a track that appears to hold several episodes, which was previously recorded but never displayed. - **Cartoon discs with three segments per track are recognised as combined.** Shows like Dexter's Laboratory and Looney Tunes put three short segments in one track. Engram could confirm a combined track of two segments but never three, because it sampled too few points in the file; it now samples more densely when a track's length suggests several segments. Combined tracks still go to review for you to assign. ## [0.35.1] - 2026-09-10 _Highlights: a corrected disc identity now renames its backup too, and a stuck file move during organizing no longer leaves a stray copy._ ### Fixed - When a misidentified disc was corrected during review, the disc backup kept the wrong show name and TMDB id even though the library output was correct. The backup folder is now moved to match the corrected identity once the job completes. (#643) - Organizing a file that another process still had open could leave a copy in the library while reporting an error, so the next save attempt failed with "file already exists" and the episode had to be discarded. The move is now retried and rolled back cleanly. (#642) ## [0.35.0] - 2026-09-07 _Highlights: back up a disc before ripping it, and import backups you already have._ ### Added - **Optionally back up a disc before ripping it.** Engram can write a full decrypted MakeMKV copy of each disc to a folder you choose and then extract from that copy instead of from the drive. The disc is read once, sequentially, rather than once per title, which is much gentler on a scratched disc that has no in-print replacement; the disc is released as soon as the copy finishes rather than when the whole rip does; extraction from local storage is faster and survives a marginal read; and the copy is kept for preservation, since Engram never deletes one. Backups mirror your library layout and honour your own naming settings. If anything prevents a backup, Engram rips directly from the drive as before and says why on the job card, so turning this on cannot make a disc less likely to finish. Off by default: enable it under Settings and pick a backup folder. (#638) - **Import a disc backup you already have.** The Import button now recognises a folder containing `BDMV` or `VIDEO_TS`, or an `.iso` file, and runs it through the normal scan, identify, rip, match and organize pipeline instead of filing it as finished media. Point it at a shelf of backups and it queues one job per disc. (#638) ### Fixed - **The subtitle-cache builder no longer records a provider outage as missing content.** When the daily OpenSubtitles quota ran out partway through a build, every remaining season came back empty and was written down as "0% coverage", which then suppressed those seasons from being retried for 30 days. One such run marked 664 seasons unharvestable; re-running an affected show afterwards recovered all 330 of its episodes, so the subtitles had been there the whole time. A build now stops cleanly when the quota runs low (staying under the daily cap by default), declines to record coverage it could not actually measure, and reports how many seasons were affected so a misconfigured run is obvious rather than silent. Coverage that already succeeded no longer expires on a timer either, which is what used to trigger the oversized re-harvests that exhausted the quota in the first place. Run summaries also credit OpenSubtitles correctly instead of reporting its downloads as cache hits. (#631) - **The same protection now covers the subtitle scrapers.** The fix above read only OpenSubtitles' health, so a build where OpenSubtitles was fine but both Addic7ed and TVsubtitles were unreachable still wrote those seasons down as having no subtitles and stopped retrying them for 30 days. A scraper that goes down is now told apart from one that simply has nothing, so a season nobody could reach is left unmeasured and picked up on the next run, and the run summary names the provider that never answered. (#636) - **A non-ASCII show title no longer aborts a subtitle-cache harvest.** A 448-show run died partway through on a title spelled with a macron: Windows opens both the console and a redirected pipe with the legacy ANSI codepage, so rendering that one title's progress bar raised an encoding error and took the whole multi-hour run down with it, discarding the completed work for every show that had already succeeded. Output now forces UTF-8 up front, so an unrepresentable character degrades to a placeholder instead of ending the run. (#635) ## [0.34.0] - 2026-08-31 _Highlights: run identification and episode matching entirely on your own machine with Ollama or LM Studio._ ### Added - **Local AI providers: run identification and episode matching on your own machine.** Ollama and LM Studio can now be selected as the AI provider instead of a paid hosted API, so transcripts and disc labels never leave your network. No API key is required; pick the model from a dropdown of what your server actually has installed, and Engram checks it is really there before reporting a successful connection. Leave the address blank to use the conventional port (`11434` for Ollama, `1234` for LM Studio). See [Local AI](https://jsakkos.github.io/engram/getting-started/configuration/#local-ai-ollama-and-lm-studio) for setup. (#606) - **Review every disc before it is organized.** A new setting under *Naming & extras* holds every disc in the Review Queue for confirmation, however confident the match was. The matcher still runs and pre-fills its best guess — you just get the last word before anything moves into the library. Useful for shows the matcher handles badly. (#611, thanks @raiju!) - **Discord notification when a disc finishes ripping**, fired as soon as Engram is finished with the drive rather than when the job reaches its final state, and whether or not auto-eject is on. Discs that need manual review now ping at the point the drive is free instead of staying silent until the review is done. A Status field says whether the rip completed, stopped early, or was a re-rip. Off by default; enable it under Notifications in Settings. - **One track can now be assigned several episodes.** Segment-format shows (three ~7 minute cartoon segments in one 22 minute DVD track) list each segment as its own TMDB episode, so a single track really is `S01E01-E03`. The review picker can now say so, and the file is named with the range form Plex and Jellyfin both read. - **The manual episode picker is searchable.** Type part of an episode name or its number instead of scrolling a dropdown — which matters for the 40-100 episode seasons that segment-numbered shows produce. Episode names come from the TMDB data Engram already caches. - **Settings now checks your library and staging folders.** Each path field shows whether the folder exists, whether Engram can actually write to it (a real write test, not a guess at permissions), and how much space is free — so a typo or a permissions problem is visible immediately instead of surfacing as a failed organize after a completed rip. (#612, thanks @raiju!) - **Network shares are supported as library paths.** A Windows UNC path (`\\server\share\TV`) works wherever a local path does, including the pasted `//server/share` spelling, which is normalized to one canonical form. A share that is offline while you are editing settings is flagged as a warning rather than rejected, and a Windows-style share path entered on Linux or macOS now explains that it needs mounting instead of failing obscurely. (#612, thanks @raiju!) ### Fixed - **Discs whose every track was filed as "extras" are held for review instead of quietly completing.** When TMDB's runtimes don't describe the disc, the duration pre-filter could reject every track on it, so a whole box set could file every one of its episodes into `Extras/` and report success. Such a disc now stops for confirmation, because "nothing on this disc is an episode" is far more often a failed match than a genuine bonus disc. (#611, thanks @raiju!) - **Episode-length tracks are no longer mistaken for extras on segment-format shows.** When no track on a disc matches a single TMDB runtime but tracks match whole multiples of one, Engram now recognises the disc as segment-numbered and accepts the combined tracks as episodes. If nothing fits under either rule, the runtime filter is disabled for that disc and the audio matcher — which compares content, not clocks — decides. - **Cartoon discs that pack two episodes into one track are no longer filed as Extras.** Shows like The Weekenders and Courage the Cowardly Dog put two eleven-minute segments into a single half-hour track, while TMDB lists each segment as its own episode. Engram compared the track against one episode's runtime, found no match, and quietly sorted the whole thing into Extras without ever listening to it, so an entire disc could come back with nothing organized. Engram now recognises a combined track from where the dialogue matches fall across the file, and sends it to manual review naming the episodes it found rather than discarding it. Combined tracks still need you to assign them by hand, because writing a single file out under two episode numbers is a separate change. (#622) ## [0.33.0] - 2026-08-23 _Highlights: watch a track in your own video player, straight from manual review._ ### Added - **Watch a track in your own video player, straight from manual review.** When a disc is waiting on you and the evidence is ambiguous, the fastest way to settle which episode a track is has always been to go and watch it, which meant leaving the dashboard and hunting through the staging folder for a file named `title_01.mkv`. Each track in the review inspector now offers **Open in player**, which hands the file to VLC, MPV, IINA, or whatever owns playlists on your system, and **Copy stream URL** for players that prefer an Open Network Stream. Nothing is re-encoded, so you get the original picture with every audio and subtitle track intact. This works from another machine too, though only if you have turned on LAN access. ## [0.32.0] - 2026-08-19 _Highlights: review jobs stay visible even after they age off the dashboard._ ### Changed - **CI now builds and boots the packaged app before a dependency change can merge.** Until now the only PyInstaller build happened at release time, so a library update that broke packaging looked completely healthy on the pull request and only revealed itself as a broken download days later. That is how v0.29.0 through v0.31.0 each shipped without a working build. Pull requests that touch the dependency set or the packaging config now assemble the real binary, start it, and check that it answers and that the bundled audio fingerprinter is present. (#599) ### Fixed - **Discs parked in review no longer vanish if you leave them for a few days.** The dashboard only ever showed the ten most recent jobs, and the History page only lists finished ones, so a disc waiting on review could be pushed off the dashboard by ten newer discs and then appear nowhere at all. The job was never deleted, just unreachable. Jobs that still need you (review, ripping, matching, organizing) are now exempt from the dashboard's ten-job limit, so they stay on screen until they are dealt with. The limit still applies to finished jobs, which remain in History. - History can now show jobs that have not finished. The state filter gains **Needs review** and **All jobs**, making History a complete record of every disc Engram has seen rather than only the completed and failed ones. ## [0.31.1] - 2026-08-18 _Highlights: downloadable builds work again._ ### Fixed - **Release builds produce a working binary again.** Since v0.29.0 every packaged release crashed the moment it started, with `No module named 'scipy._external.array_api_compat.numpy.fft'`, so no downloadable build shipped for v0.29.0, v0.30.0 or v0.31.0. A scipy update relocated a vendored module that PyInstaller can only find when it is named explicitly, and the bundler's built-in rule still pointed at the old location. Both locations are now declared, so the packaged app starts whichever one scipy uses. ## [0.31.0] - 2026-08-16 _Highlights: eject a disc mid-rip without losing the job, and Discord notifications now identify the disc._ ### Added - **You can eject a disc mid-rip without losing the job.** A dirty or damaged disc usually announces itself partway through a rip: read speeds collapse and the drive starts grinding. Until now the only mid-rip control was Cancel, which failed the whole job and deleted everything already staged, so rescuing a scratched disc meant throwing away the tracks that had ripped perfectly. Pulling the disc by hand was not a workaround either: a rip in progress survives the disc vanishing and then sits on MakeMKV until the stall timeout expires, twenty minutes later. The disc card now has an **Eject** button while scanning or ripping. Ripping stops within seconds, the drive opens, and the job carries on: tracks that finished ripping match and organize exactly as they always would, and the rest are parked in review marked re-rippable, so you can clean the disc, put it back in, and recover them one at a time. Ejecting during the initial scan cancels the job instead, since nothing has been produced yet. If the tray refuses to open, the rip is still stopped and the disc is safe to remove by hand; Engram says so rather than pretending it worked. - Discord notifications now identify the disc. Every notification carries structured fields for disc name and number, season, duration, track count and subtitle status, so box-set rips are distinguishable without editing a template. - Completion notifications list the episodes that landed, e.g. `S01E01-E04 (4 episodes)`. - Discord notifications when a disc parks in review, with an optional mention so the ping reaches your phone and an optional deep link straight to the review screen. - Per-event notification toggles for completed, failed and review-needed. - A "Send test message" button in Settings, so a webhook can be verified at setup time instead of on the next finished disc. - TMDB poster thumbnails on notifications. - New template variables: `volume_label`, `drive_id`, `disc_number`, `discdb_disc_slug`, `review_reason`, `episodes`, `state`. ### Changed - `{{drive}}` is now documented as a deprecated alias for `{{volume_label}}`. It keeps rendering the disc volume label, so existing templates are unaffected. ## [0.30.0] - 2026-08-12 _Highlights: the AI model is configurable per-provider, and a Gemini access error now says which models your key can actually use._ ### Added - **The AI model is configurable now.** Settings → Preferences gains a **Model** field next to the AI provider and key. Leave it blank and nothing changes: Engram uses its own default for that provider. It exists because a key is not guaranteed access to that default. A Gemini key whose Google Cloud project does not carry `gemini-2.5-flash-lite` was told, correctly, that it lacked access to the model, and then given no way whatsoever to point Engram at a model it *could* use. **Test Connection** sends the model as typed, so you can confirm a combination before saving it. ### Fixed - **"This key does not have access to the model" now says which models the key does have.** When a Gemini request comes back with the model missing, Engram asks the API which models that key can actually use and names them in the error, along with the model it asked for. The old message was accurate and completely undiagnosable: it never said which model was refused, and left no next step. Reaching this error also proves the key itself is good, since Google checks the credential before it looks up the model. - **The AI "Test Connection" button no longer fails with a bare HTTP 403 behind a reverse proxy.** The button is restricted to the machine running Engram because each press spends money on your key, and in Docker or any proxied setup the request arrives from the proxy rather than from loopback. That refusal came with an explanation, but the dashboard discarded it and showed only the status code, while the server log recorded nothing at all beyond the access line, so the whole event read as an unexplained failure of the key. The reason ("enable LAN access in Settings") is now shown in the dashboard and written to the log. ## [0.29.0] - 2026-08-09 _Highlights: movie folder and filename naming is configurable, a skipped track no longer stops the track that's currently ripping, and re-importing an already-imported folder no longer fails permanently._ ### Added - **Movie naming is configurable now, the same way TV naming already was.** Settings → Preferences → Naming & extras gains a **Movie Folder Format** and a **Movie Filename Format**, with `{title}`, `{year}`, `{tmdb_id}`, and, on the filename, `{edition}`. Two same-titled movies can finally sit side by side by adding the id tag your media server expects, Plex `{title} ({year}) {{tmdb-{tmdb_id}}}` or Jellyfin `{title} ({year}) [tmdbid-{tmdb_id}]`, exactly as TV shows could. Leave the filename format blank and it reuses your folder format, so customizing one carries through to the other instead of the two quietly drifting apart. (#576, thanks @DrAquafreshhh!) ### Fixed - **Skipping a track no longer stops the track that is currently ripping.** When every title on a disc is selected, Engram rips the whole disc in a single MakeMKV pass, and that pass cannot be told to drop one title. Clicking SKIP on a *later* track was therefore honored by killing MakeMKV outright, which threw away the track being written at that moment: 20 to 40 minutes of a Blu-ray rip, gone, with the disc then either ejecting while wanted tracks were still unripped or leaving the job stuck with no visible progress. A full-disc rip now stops only when MakeMKV opens a track you skipped *and* every track after it is skipped too, which is the one case where stopping wastes nothing. Skipping a track in the middle no longer costs you the rest of the disc; it is simply left out when its turn comes. (#580) - **A recovery rip no longer looks frozen, or gets cancelled for looking frozen.** If a single-pass rip leaves tracks behind, Engram re-rips them one at a time. That recovery pass ran with progress reporting switched off, so the dashboard showed no progress, no speed and no per-track state for its entire duration. Worse, that same reporting is what tells Engram the job is alive, so a recovery long enough to matter, which one Blu-ray track routinely is, could be cancelled by the stalled-job watchdog and marked failed while it was working perfectly well. Progress and speed now continue through the recovery pass. (#580) - **Tracks that have already finished ripping no longer offer a SKIP button.** Once a track is ripped it sits on disk waiting its turn to be matched, and the button was still shown. Pressing it there did not save any work, it discarded a rip that had already succeeded. SKIP is now offered only for tracks that have not started. (#580) - **Movie folders were missing their year.** The default movie format has always read `{title} ({year})`, but no year ever reached the organizer, so the empty parentheses were stripped and every film was filed as a bare `Blade Runner`. Movies now resolve their release year from TMDB. **This changes where new rips land:** a movie that would previously have gone to `Blade Runner/` now goes to `Blade Runner (1982)/`. Folders already in your library are not touched or moved, and setting Movie Folder Format to `{title}` restores the old layout. (#576) - **Plex edition tags were mangled.** Choosing an edition in the review queue wrote `{Edition Final Cut}` instead of `{edition-Final Cut}`, which Plex cannot read, because the tag was pasted into the movie title and then run through the disc-label cleaner. Editions are now handled separately and land on the filename, where Plex looks for them: `Blade Runner (1982) {edition-Final Cut}.mkv`. (#576) - **Movie titles from TMDB were being garbled.** The cleaner that turns a disc label like `THE_SOCIAL_NETWORK` into `The Social Network` was also being applied to titles TMDB had already returned correctly, so `Spider-Man: No Way Home` was filed as `Spider Man No Way Home`, `WALL-E` as `Wall E`, and `Se7en` as `Se7En`. Titles that come from TMDB now keep their own punctuation and capitalization, apart from the handful of characters no filesystem allows. This also applies when you correct a movie's identity from the review queue, which previously re-garbled the title you had just fixed. (#576) - **Movie discs were looked up against the TV catalog.** When working out a release year, Engram sent movie TMDB ids to the TV endpoint, which returns either nothing or an unrelated series that happens to share the number. Year lookups now follow the disc's content type. (#576) - Re-importing a folder that was already imported no longer fails with an unactionable conflict. A finished import now warns and offers "Re-import Anyway" instead of blocking permanently, so correcting a mistaken season or show no longer requires renaming the source folder, as long as the source files are still there; a completed library-mode import moves its files out of staging, so those need to be put back first (#571) - Importing a folder where only some seasons were previously imported no longer closes the dialog and silently skips them; every skipped unit is now listed with the reason (#571) - Failed jobs can now be dismissed from the dashboard. The clear action covers both finished states rather than completed jobs only (#571) ## [0.28.2] - 2026-07-31 _Highlights: a failed disc no longer claims it is still analyzing._ ### Fixed - **A failed disc no longer claims it is still analyzing.** When a job died during identification, typically because the disc was ejected mid-scan or the container could not read the drive, its card showed a pulsing amber "Analyzing" chip on the poster for good, sitting right beside its own red "Error" badge. The chip was driven by the content type alone, and a job that never got past identification never gets one. Cards that have finished, failed, or are waiting on you now show a plain "Unknown" instead, and the pulse is reserved for discs that really are being worked on. (#566) ## [0.28.1] - 2026-07-30 _Highlights: TV discs that fail to organize now say why, release notes render readably in the update modal, and the MakeMKV install retries a transient Cloudflare error partway through the download, not just at the version check._ ### Fixed - **A MakeMKV install can no longer fail partway through a large download.** The Docker container's first-run MakeMKV install already retried a transient Cloudflare or network error during the version lookup; it now retries the much larger source download that follows too, which is the longer request against the same server and so the more exposed of the two. If it still gives up, it reports whether the site was unreachable or the requested MakeMKV version is simply no longer published. (#559) - **Release notes are readable now.** The "What's new" and update modals rendered their release notes as one unbroken block: paragraphs ran together with no spacing, bullet lists lost their markers, and bold text was almost indistinguishable from the surrounding prose, so there was no way to tell where one entry ended and the next began. Entries are now laid out as a headline plus its explanation, sections are separated by labelled rules, and the highlights summary reads as a proper lede. (#564) - **A TV disc that can't be filed now tells you why.** Saving a review when Engram could not write to your TV library failed the job with a bare "Failed to organize files": no error code, no path, and nothing useful in the logs or the diagnostic bundle. The underlying reason (`Permission denied`, a full disk, a staging file that had gone missing) was read and then thrown away, and because the save ran outside the job's logging context, even the traceback was filtered out of the bundle. The real cause now reaches the job error, the affected track shows it in the review inspector, and the log lines are attributed to the job so they appear in bug reports. Movie discs already reported these errors properly; TV discs now match. Engram also checks the library folder is writable *before* it starts filing, and Settings rejects a library or staging path it cannot write to, so a permissions problem surfaces when you save it rather than after a full rip. (#563) ### Changed - **Dependency updates** — opensubtitlescom >=0.1.5 (#562), mkdocstrings >=1.0.6 (#561). ## [0.28.0] - 2026-07-29 _Highlights: contribute disc metadata to two shared identification networks (TheDiscDB and the Engram acoustic fingerprint network), a What's New summary after you update, and a Test Connection button for your AI provider — plus much clearer error messages when AI episode matching fails._ ### Added - **Engram is part of two shared identification networks, and you can now see exactly what that means.** A new [Contributing Data](https://jsakkos.github.io/engram/guide/contributing-data/) guide explains both halves. **TheDiscDB** answers "which disc is this?" by content hash, and you can now contribute disc layouts back to it so others can identify the same pressing automatically: opt in under Settings → TheDiscDB Contributions, with no account and no API key needed. The **Engram acoustic fingerprint network** answers "which episode is this track?" from chromaprint audio fingerprints, so episode identification no longer depends on finding reference subtitles for the specific show you are ripping. No audio or video ever leaves your machine, contributions are pseudonymous, and everything you have sent can be deleted. Identification now reads from the fingerprint catalog by default and falls back to the existing TMDB, AI, and heuristic paths on a miss. - **A "What's new" summary after you update.** Engram now shows the release notes for the version you are running, once per version, the first time you open the dashboard after updating. It works however you updated: in-app self-update, a fresh download, or a new Docker image. Click the version number in the top bar to read them again at any time. (#553) - **Test your AI provider connection before you rely on it.** Settings → AI Provider now has a **Test Connection** button that sends one tiny request and tells you exactly what happened: the key was rejected, the account has no API credits, the provider is rate limiting you, or the key has no access to the model Engram uses. Previously the only way to find out was to rip a disc and wait through a one-to-three-minute transcription before the request was even attempted. Leave the key field blank to test the key you already saved, without having to paste it again. (#555) ### Fixed - **Settings no longer implies you need a TheDiscDB API key to contribute.** The API key field was labelled as required for submitting directly to TheDiscDB, with an empty field meaning local-only export. That was never true: TheDiscDB accepts contributions openly and Engram submits fine without a key. The field is now marked optional. The Data Sharing step also claimed that nothing was enabled by default, which understated fingerprint contributions (on by default, and gated behind the consent dialog shown before the first upload); it now says what is actually on. - **A brief makemkv.com outage no longer fails the Docker container's first-run MakeMKV install.** Looking up the latest MakeMKV version now retries transient network and Cloudflare errors before giving up, and when it does give up it says whether the site was unreachable or its download page changed format, instead of reporting the second cause for both. (#554) - **AI episode matching now tells you why it did nothing, instead of claiming it found no match.** Clicking **Try AI match** reported "No confident AI match found" for almost every outcome, including ones where the matcher never ran at all: AI matching switched off, no API key configured, no show or season detected, or the show missing from TMDB. Each of those now says what actually happened and what to do about it. The button is also disabled outright when no API key is set, so a click can no longer spend a full transcription only to fail a check that was knowable up front. (#555) - **A failing AI provider now names the actual cause instead of showing raw JSON.** Every provider failure collapsed into one opaque message, surfaced in the review inspector as `Request failed (503 Service Unavailable): {"suggestion":null,"reason":"llm_error"}`. The provider's own explanation was discarded before anyone could read it, so an exhausted account quota, a rejected key, ordinary rate limiting, and a network outage were indistinguishable from each other in the app and in the logs. Engram now reads the provider's response and reports the specific cause. A permanently out-of-credit key also stops being retried, which previously cost four requests and several seconds of backoff on every attempt for a failure that could never succeed. (#555) - **A truncated or refused AI response no longer masquerades as "no match".** When a provider cut its reply off at the token limit, or refused the request outright, the reply was silently discarded and reported as though the model had run and simply been unsure. A refusal could also crash the request outright with an internal server error. Both now report what actually went wrong. (#555) - **Gemini failures no longer blame your API key for unrelated problems.** Google returns the same generic error code for nearly every rejected request, so a bad prompt, an unsupported parameter, or an internal schema problem would all have been reported as "the API key was rejected", sending people to re-check a key that was fine. (#555) ### Changed - **Dependency updates** — @radix-ui/react-accordion 1.2.12→1.2.20 (#550), @radix-ui/react-dialog 1.1.17→1.1.23 (#549), eslint 10.6.0→10.8.0 (#548), tailwindcss monorepo 4.3.2→4.3.3 (#543), postcss ^8.5.19→^8.5.23 (#544), autoprefixer ^10.5.2→^10.5.4 (#542), @vitejs/plugin-react ^6.0.3→^6.0.4 (#556), loguru >=0.7.0→>=0.7.3 (#545), mkdocs-material >=9.5→>=9.7.7 (#557). ## [0.27.0] - 2026-07-24 _Highlights: contribute your own disc metadata back to TheDiscDB, plus fixes for mid-rip track skipping, LAN manual import, and a mid-rip identity correction that now re-matches already-ripped titles._ ### Added - **Contribute your disc metadata back to TheDiscDB.** The contribution pipeline is now enabled: a new **Contribute** page lets you export, group multi-disc sets, enhance with UPC/ASIN/cover art, and submit disc fingerprints, scan logs, and cover images to [thediscdb.com](https://thediscdb.com) so others can identify the same discs automatically. No account or API key is required — TheDiscDB accepts contributions openly — and nothing is sent without your explicit opt-in in Settings → TheDiscDB Contributions. Sharing your rips' disc metadata helps grow the shared identification database everyone benefits from. (#532) ### Fixed - **Skipping a track during ripping now actually stops it from ripping, instead of ripping it and deleting it afterward.** When every title on a disc was selected (the usual case, and always so for movies), Engram ripped the whole disc in one MakeMKV pass that could not drop an individual title mid-stream — so clicking **Skip** only marked the track for deletion once it had already been fully ripped, wasting the entire rip time. A skip requested during that single pass now aborts it and re-rips just the remaining, un-skipped titles individually, where the skip is honored before ripping starts. Skipping a title before ripping begins already worked and is unchanged. (#538) - **The manual import feature now works in Docker and other remote setups.** Browsing for files and starting an import over the network returned `403 Forbidden` because those endpoints were locked to the host machine. They now honor the **Allow LAN access** setting: when you've enabled it (headless/Docker installs enable it automatically on first run), import is reachable from another device, while the more sensitive fingerprint and contribution endpoints stay host-only. (#524) - **Upgrading to 0.26.0 no longer breaks the app with a 500 on every settings load.** On databases created before the customizable Discord messages feature, the new `discord_template_completed` / `discord_template_failed` columns were added without a default, leaving existing rows `NULL`. Because those fields are required text, `GET /api/config` then failed validation and returned HTTP 500 — which broke config loading across the whole dashboard (Settings, Contribute, first-run wizard). The schema reconciler now backfills new non-optional text columns with their declared default, and config responses tolerate legacy `NULL` values. (#537) - **Correcting a disc's identity mid-rip now actually re-matches it.** When you used the always-available identity control to fix a show while it was still ripping, the correction updated the title but never re-ran episode matching on tracks that had already been matched under the wrong identity, so nothing changed until the rip finished and you re-identified a second time from the review prompt. A mid-rip identity change now re-matches every already-ripped track against the corrected show (and restarts the reference-subtitle download for it), matching the behavior you previously only got after the rip completed. (#540) ## [0.26.0] - 2026-07-22 _Highlights: enter your own disc details when Engram can't work them out, plus customizable Discord notification messages._ ### Added - **Enter disc details yourself when Engram can't work them out.** A new **Manual** button lets you name a disc before you insert it: pick the show or film, season, and disc number, and the next disc in that drive adopts exactly what you entered, skipping automatic identification and ripping unattended with no questions asked. It's built for the awkward cases — homemade discs, obscure releases, and labels that are just a catalog number — where the automatic guess would otherwise stop to ask. Every active disc also gains an always-available identity control, so you can correct a title mid-rip instead of waiting for Engram to ask, and a disc you identified yourself is now marked with a **Manual ID** tag so you can tell at a glance which titles were asserted rather than detected. Episode matching still runs automatically on a manually named disc; anything it can't resolve lands in the usual Review Queue. (#520) - **Customizable Discord notification messages.** The Discord embed sent when a disc completes or fails now uses a template you control, instead of a fixed message. Set separate messages for the completed and failed cases in Settings → Notifications, using `{{title}}`, `{{drive}}`, `{{duration}}`, `{{error}}`, and other job details as variables. Leave either field blank to keep the previous default message. (#508, thanks @katelovescode!) ### Fixed - **Docker deployments now run `privileged: true` by default.** Plain `devices:` passthrough wasn't always enough for full optical drive control (eject/tray), and some hosts got stuck unable to stop or restart the container without it. (#459, thanks @MadsTheEngineer!) - **Titles are no longer lost or mis-organized on discs where MakeMKV's native numbering doesn't start at "t00"** (e.g. some Blu-ray sets start at "t01"): the first title would hang and fail after ripping, and the last title's rip would be silently deleted from staging (#517). - **A stalled rip is no longer reported as "Cancelled by user", and no longer hides its own review queue.** When a disc stopped making progress (a dirty, damaged, or region-mismatched disc), the stale-job watchdog stepped in to move the job along — but the job was recorded as having been cancelled by you, and it landed in Failed even when tracks had ripped far enough to be assignable. The result was a job blaming you for something you never did, pointing at a review queue that its own Failed state locked you out of. Stalled jobs now say what actually happened (e.g. "Ripping stalled — no progress after 1200s"), and a disc with reviewable tracks correctly lands in Review Needed so you can assign them. Cancelling a job yourself is unchanged. (#506) - **A disc Engram cannot read at all now gives up in about two minutes instead of twenty-six.** When the first rip pass stalled without writing a single byte, Engram still retried every title on the disc one at a time, spending a full stall timeout on each before the phase watchdog finally stepped in. A pass that stalls with no output is now treated as evidence the disc itself is unreadable: the per-title retries are skipped and every track goes straight to the review queue, where it can still be re-ripped manually. (#506) - **A region-locked disc now says so.** When a rip stalls because the drive's region setting does not match the disc, Engram surfaces that as the reason instead of reporting that the disc may be dirty or damaged, which sent people off cleaning and re-testing a disc that was never the problem. (#506) - **Bug reports no longer show another job's errors as if they were yours.** The "Report Bug" diagnostic report ignored which job it was opened for and always pulled the last 20 global error lines — a report opened for a cancelled job with no errors of its own could show an unrelated job's failure from days earlier. Recent errors are now scoped to the job the report is for, and when a job predates per-job log tagging and there's genuinely nothing job-specific to show, the report (both the modal and the copied/GitHub text) now says so instead of silently substituting unrelated errors. (#506, #522) ### Changed - **Dependency updates** — sharp 0.34.5→0.35.3 (#528), @radix-ui/react-navigation-menu 1.2.5→1.2.19 (#530), @radix-ui/react-context-menu 2.2.16→2.3.4 (#529), @radix-ui/react-dropdown-menu 2.1.6→2.1.20 (#513), @radix-ui/react-toggle 1.1.10→1.1.14 (#512), @radix-ui/react-slider 1.4.1→1.4.3 (#510), vitest/@vitest/coverage-v8 4.1.9→4.1.10 (#518), librosa >=0.10.1→>=0.11.0 (#519), postcss 8.5.16→8.5.19 (#515), @types/node 25.9.4→25.9.5 (#514). ## [0.25.0] - 2026-07-12 _Highlights: manual bulk import of downloaded .srt files for episodes with no automated subtitle match, plus a Background Animation toggle for low-power devices._ ### Added - **Manual subtitle bulk import.** For episodes where none of Engram's 3 automated subtitle sources (OpenSubtitles, Addic7ed, TVsubtitles) found a reference, you can now upload a folder of manually downloaded `.srt` files directly from the review page's "no reference subtitle" warning banner. Files are previewed (season/episode parsed from filename, duplicates and encoding issues flagged) before a separate commit step validates and writes them into the existing subtitle cache, so no changes to the matching engine were needed. (#501) - **Background Animation toggle.** A new Settings → Preferences → Display checkbox disables the falling-code rip animation, a full-viewport canvas redrawn at 20fps that's a meaningful CPU/GPU cost on low-power devices (reported case: ARM64). Stored per-browser (not synced across devices viewing the same backend), defaults on, and seeds from the OS's reduced-motion setting when no explicit choice has been made. (#502) ### Fixed - **An unconfirmed movie identity no longer auto-accepts.** When a disc's title couldn't be confirmed by DiscDB/TMDB from its label, an AI-guessed title was sometimes matched to an unrelated, low-popularity movie — and because that guess "agreed" with the movie heuristic, the mismatch was silently accepted and ripped straight through, misfiling real content as movie extras with no way to correct it afterward (#499). The disc-identity corroboration check that already gated this for TV shows now applies to movies too: an uncorroborated TMDB/AI identity pauses for confirmation via the existing re-identify prompt instead of completing unreviewed. (#503) - **The Import folder browser now scrolls.** Browsing a directory with more subfolders than fit on screen previously grew the modal past the edges of the window, stranding you in the middle of the list with no way to scroll, no visible way to go up a level, and no way to reach the start button. The folder list is now a scrolling pane inside a window-sized modal, so the header, the up-a-level row, and the start button stay put. You can also type or paste an exact folder path instead of clicking through hundreds of folders, and going up a level scrolls the folder you came from back into view. (#500) ### Changed - **Dependency updates** — tailwindcss 4.3.1→4.3.2 (#497), httpx >=0.26.0→>=0.28.1 (#498), faster-whisper >=1.0.0→>=1.2.1 (#492), eslint 10.4.0→10.6.0 (#493), @radix-ui/react-tooltip 1.1.8→1.2.12 (#496), @radix-ui/react-collapsible 1.1.14→1.1.16 (#495), @radix-ui/react-label 2.1.10→2.1.11 (#494), postcss 8.5.15→8.5.16 (#491), @vitejs/plugin-react 6.0.2→6.0.3 (#489), autoprefixer 10.5.0→10.5.2 (#490). ## [0.24.0] - 2026-07-04 _Highlights: manual track skipping for queued/not-yet-ripped tracks, plus field-validated Jetson GPU setup fixes._ ### Added - **Manual track skipping.** Skip a queued or not-yet-ripped track (for example a bogus "play all" title that slipped past auto-detection) straight from the dashboard, so MakeMKV does not waste time ripping it. Skipped tracks show a distinct SKIPPED badge, are excluded from the library, do not count as failures, and can be un-skipped until ripping reaches them. Skipping is best-effort during an in-progress full-disc rip (the file is written then deleted) and guaranteed for per-title rips. (#486) ### Fixed - **Jetson GPU setup script now works on real hardware, not just in theory.** A community member's field validation on a Jetson Orin NX (JetPack 6.2.2 / R36.5.0) found `jetson_gpu_setup.sh` was missing two steps: it didn't move the PyInstaller-bundled `libstdc++.so.6`/`libgcc_s.so.1` aside (causing `GLIBC_2.36`/`GLIBC_2.38 not found` on JetPack's older Ubuntu 22.04 base) and didn't populate the `~/.engram/cuda/` runtime cache from JetPack's system cuDNN/cuBLAS (so `/api/asr-status` never reported `gpu_runtime_installed: true`). Both are now automated by the script; `docs/development/jetson.md` is updated to "validated," and a new `docs/development/jetson-optical-drive.md` documents building the Blu-ray/DVD kernel modules JetPack doesn't ship for USB optical drives. (#485) ## [0.23.0] - 2026-07-02 _Highlights: Discord webhook notifications for job completion, plus fixes for a Docker subtitle-cache permission error, migrations getting stuck behind duplicate columns, and modal keyboard focus order._ ### Added - **Discord webhook notifications for job completion.** Configure a webhook URL in Settings → Preferences → Notifications; Engram posts a color-coded embed when a disc job reaches COMPLETED (green) or FAILED (red). The URL is stored as a credential (redacted in API responses, SSRF-validated on write). Embed content is hard-coded for now — a future PR will add customizable message templates. (#482) ### Fixed - **Subtitle cache directory no longer fails to create with a permission-denied warning on startup.** `ensure_paths_exist()` checked `is_absolute()` before expanding `~`, so the default `subtitles_cache_path` of `~/.engram/cache` was never tilde-expanded and instead resolved to a literal `~` folder under the backend source directory, which is unwritable in Docker. `~` is now expanded before the absolute-path check, matching every other consumer of this setting. (#481) - **Database migrations no longer get stuck behind a "duplicate column name" error.** `_add_missing_columns()` (the frozen-build fallback) and Alembic both converge the schema toward the same model on every startup; if a column a pending Alembic migration wanted to add had already been added out-of-band, the migration failed and was silently swallowed, leaving `alembic_version` permanently one revision behind and blocking every later migration on every subsequent restart. Alembic upgrades now apply one revision at a time and self-heal past a revision whose only failure is a column that already exists. (#483) - **Modal dismiss button now receives keyboard focus when the modal opens, and focus returns to the triggering element when it closes.** Addresses a WCAG 2.4.3 (Focus Order) gap: keyboard and screen-reader users previously had to tab in from wherever focus last landed, and lost their place in the page when the modal closed. (#479) ## [0.22.2] - 2026-06-30 _Highlights: disc auto-detection no longer breaks after the first rip on containerized installs (TrueNAS, Docker)._ ### Fixed - **Disc auto-detection no longer breaks after the first rip on containerized installs (TrueNAS, Docker).** In some container environments `/sys/block/sr0/size` freezes at the previous disc's block count after ejection and never drops to zero. This caused two compounding bugs: a ghost FAILED job fired on every cycle (spurious "inserted" event from the frozen sysfs), and the next real disc insertion was silently ignored until the container restarted. The sentinel now verifies disc presence via `ioctl(CDROM_DRIVE_STATUS)` when sysfs reports a non-zero size, querying drive firmware directly. (#477) ## [0.22.1] - 2026-06-29 _Highlights: two UI polish fixes — the bug-report icon no longer causes a layout shift on hover, and selecting a `Season NN` folder in the import picker is now identified correctly._ ### Fixed - **Bug report icon no longer causes a layout shift on hover.** The icon was rendered inside `ActionButtons`, which appears after `StateIndicator` in the flex row, so revealing it on hover pushed all buttons rightward. It now lives before `StateIndicator` in the card, with reserved space via an opacity toggle, so the layout stays stable. (#465, thanks @katelovescode!) - **Manual import now identifies a picked `Season NN` folder correctly.** Selecting a season folder directly (e.g. `…\Seinfeld\Season 4`) created a job titled "Season 4" with no season and content type "unknown", because the scanner only handled picking a show folder or a parent-of-shows folder. It now recognizes a `Season NN` pick, taking the show name from the parent folder and the season from the folder's own name (and organizes in place beside the original show folder rather than nesting a second copy inside the season). (#474) ### Changed - **Frontend dependency updates** — @radix-ui/react-scroll-area 1.2.3→1.2.12, @radix-ui/react-collapsible 1.1.3→1.1.14, @radix-ui/react-slot 1.1.2→1.3.0, @playwright/test 1.60.0→1.61.1. (#468, #470, #471, #472) ## [0.22.0] - 2026-06-28 _Highlights: manual media import replaces the background watch folder with a user-triggered folder picker, headless Linux server variants for NAS and Raspberry Pi, Docker multi-arch (amd64 + arm64) with properly versioned GHCR tags, and a new auto-eject toggle._ ### Added - **Manual media import replaces the background watch folder.** Click **IMPORT** in the top bar, browse to a folder on the server, preview the discovered seasons and their file counts, then start one import job per (show, season). The new scanner walks `Disc/` subfolders transparently and infers the season from any `Season NN` path segment, fixing two reported bugs: `Show / Season / Disc / *.mkv` trees that previously imported nothing, and partial imports that required an app restart to re-scan. The import watch path and destination mode are repurposed as last-used defaults for the picker. (#463) - **Settings toggle to disable automatic disc ejection.** A new "Auto-Eject Disc When Ripping Completes" checkbox in Settings → Preferences → Maintenance & watchdog controls whether Engram ejects the disc when ripping finishes. Enabled by default (preserving existing behavior); disable to keep the disc loaded for manual verification or re-ripping without re-inserting. (#457) - Headless server build variants for Linux: `engram-linux-x64-headless` and `engram-linux-arm64-headless`. These skip browser auto-launch and default `allow_lan_access` to `True` on first run so the dashboard is reachable from another machine without any manual configuration. Intended for NAS, Raspberry Pi, Jellyfin server, and other headless deployments. (#460) - Docker image now publishes both `linux/amd64` and `linux/arm64` layers, and GHCR releases now carry four versioned tags (`v0.22.0`, `0.22.0`, `0.22`, and `latest`) for Renovate Bot pinning. (#460, #462) ## [0.21.12] - 2026-06-23 _Highlights: two small fixes — auto-detected extras stay visible in review so you can reassign them before they are filed, and the settings button now uses the universally-recognized gear icon instead of a sun/starburst._ ### Fixed - **Auto-detected extras are no longer filed away mid-rip and frozen in the review panel.** They now stay with the rest of the disc's tracks and are organized at the end, so when a disc goes to review you can reassign a misdetected extra (e.g. a dual-episode track) to an episode before it is filed. (#449) - **Settings button now shows a gear icon instead of a sun/starburst.** The previous `IcoSettings` icon drew evenly-spaced radial spokes — the universal symbol for brightness controls. It has been replaced with the standard Lucide gear (rounded 8-tab outer shell with a center bore), which is universally recognized as a settings trigger. (#451) ## [0.21.11] - 2026-06-23 _Highlights: disc-hash fingerprint identification is now on by default for all installs, and the Reassign Track dropdown correctly shows the disc's actual season._ ### Added - **Disc-hash fingerprint identification is now enabled by default** — the fingerprint network's disc-hash identification path (`GET /v1/identify-disc`) now runs automatically for every install, including existing ones (migrated via Alembic). A `canonical` or `confirmed` match pre-assigns episodes with high confidence; `candidate`-tier hits are not applied as an override. Previously this path was gated off while the shared catalog was being seeded. The catalog is now large enough to be useful. (#445) ### Fixed - **Reassign Track episode dropdown now uses the disc's actual season.** When reassigning a track on a completed multi-season disc from the History detail panel, the episode picker was pinned to Season 1 (`S01E01`, `S01E02`, …) because it derived the season from the track's current match instead of the job's identified season. It now uses the job's detected season, so a Season 2+ disc correctly offers `S02E…` episodes. (#447) ## [0.21.10] - 2026-06-22 _Highlights: TheDiscDB ContentHash lookups are fixed after a server-side schema change that was silently causing every hash query to fail, plus dependency maintenance and subtitle-cache refresh._ ### Fixed - **TheDiscDB ContentHash lookups no longer fail with "Unexpected Execution Error"** — TheDiscDB restructured their GraphQL schema to insert a `ReleaseDisc` junction between `Release` and `Disc`. Our filter walked `releases.some.discs.some.contentHash`, which still passed schema validation but threw an execution error because the junction's `contentHash` property is not mapped to a SQL column. The filter now traverses the junction correctly (`disc { contentHash }`). In production this caused every hash-based lookup to fall through to name/TMDB matching, which could misfire badly (an Avatar: The Last Airbender disc matched Green Book, for example). (#444) ### Changed - **Subtitle seed list pruned and refreshed** — removed 159 non-English and zero-disc shows that were consuming harvest quota with no benefit to English-language matching, and added 8 US/UK classics. (#437) - **Frontend dependency updates** — react-hook-form 7.55→7.80, react-router-dom 7.17→7.18, @radix-ui/react-checkbox 1.1→1.3, @radix-ui/react-select 2.1→2.3, @radix-ui/react-switch 1.1→1.3. (#439, #440, #441, #442, #443) ## [0.21.9] - 2026-06-22 _Highlights: completed jobs are no longer locked — you can now reassign a misfiled track to the correct episode, mark it as an extra, or discard it directly from the History detail panel, and the fingerprint network self-corrects when a contributed fingerprint is retracted and re-submitted._ ### Added - **Reassign a track on a completed job from the History detail panel.** Open any completed job in History and reassign a misfiled track to a different episode, mark it as an extra, or discard it. Engram moves the organized library file to its new home (extras and discards both land in the show's `Extras/` folder) and, when the track's audio fingerprint was already contributed to the shared network, retracts that erroneous fingerprint and re-submits the corrected one as a user-verified match. The fingerprint network heals by re-deriving the episode's consensus from the remaining contributions. Previously a completed job's assignments were locked, so a single mismatched track could block a later disc with a duplicate-file conflict, and there was no way to correct a contributed fingerprint after the fact. (#435) ## [0.21.8] - 2026-06-21 _Highlights: Engram now ships a native ARM64 build for NVIDIA Jetson and other aarch64 Linux devices, with the auto-updater delivering the correct binary per architecture._ ### Added - **ARM64 (Linux aarch64) build for NVIDIA Jetson and other aarch64 Linux devices** — releases now include `engram-linux-arm64.tar.gz`, built natively on a GitHub-hosted arm64 runner and validated by the same smoke test, TLS self-test, and bundled-`fpcalc` checks as the x86_64 build. ASR runs on the CPU out of the box (the PyPI `ctranslate2` aarch64 wheel has no CUDA build); GPU-accelerated transcription on a Jetson is an opt-in on-device step that compiles CTranslate2 against the device's JetPack CUDA toolkit (`backend/scripts/jetson_gpu_setup.sh`, documented in `docs/development/jetson.md`). The auto-updater now distinguishes x86_64 from aarch64 so each host downloads the matching bundle. (#433) ## [0.21.7] - 2026-06-21 _Highlights: TheDiscDB lookup is now on by default — Engram checks the community disc database during identification and uses its episode mappings as a low-confidence fallback, while audio matching (ASR) remains the primary and preferred signal._ ### Added - **TheDiscDB lookup is now enabled by default** — when a disc's content hash matches an entry in TheDiscDB, Engram now uses the community-contributed title-to-episode mapping as a fallback when audio-recognition confidence is low (below 0.5). ASR always runs first and wins when confident; a DiscDB mapping steps in only when the audio match is weak. Previously, a DiscDB hit would short-circuit ASR entirely and stamp 0.99 confidence using disc-order numbering — which frequently disagreed with aired order and produced worse results than the audio match. Contribution of new disc layouts remains gated off by default. (#430) ## [0.21.6] - 2026-06-19 _Highlights: two disc-handling fixes — the Identify prompt no longer pops a blocking modal over a disc that is already ripping, and box-set TV discs now pin their TMDB lookup to the TV namespace instead of picking a fuzzy movie match._ ### Fixed - **The "Identify Disc" prompt no longer pops a blocking modal over a disc that is already ripping** — when a disc rips first with an open identity question (an unreadable label, or a title TMDB couldn't match), the prompt used to auto-open as a full-screen modal whose "Start Ripping →" button and "Awaiting Input" status made it look like the rip was blocked — so it was easy to hit **Cancel Job** and kill a healthy rip. The rip now stays visible with a **Name this disc** button on the card instead; the prompt only auto-opens once the rip finishes and the job is waiting for you. When the prompt is opened, its wording now reflects reality — it saves the title rather than "starting" a rip that's already underway, shows the actual reason TMDB couldn't match (instead of always claiming the label is unreadable), and reads "Ripping" while the disc is still ripping. (#424) - **Box-set TV discs now resolve to the TV series instead of a look-alike movie** — a disc whose title carries a season or box-set subtitle (for example "Avatar: The Last Airbender Book One: Water") strips down to the series name for its TMDB lookup, but that stripped name often matches *both* the real series and a fuzzy, sometimes more-popular movie. Engram used to pick the movie, then discard its id as cross-namespace noise — leaving the disc with no `tmdb_id`, forcing an Identify prompt, and failing subtitle download. For a disc the label already says is TV, Engram now pins the lookup to the TV namespace and scores name similarity against the title it actually matched, so the canonical series wins (e.g. Avatar resolves to TMDB TV id 246), subtitles prefetch, and no prompt appears. The guard that protects against a genuine wrong-namespace movie id (the Mad Men "Two Madmen" case) is unchanged. (#425) ## [0.21.5] - 2026-06-18 _Highlights: the in-app update restart button no longer incorrectly blocks when a job is parked in Needs Review or when dismissed cards are still in the database._ ### Fixed - **Update restart no longer false-fires "disc operation in progress" for parked or dismissed jobs** — two bugs caused the restart button to refuse even when no disc I/O was happening: `REVIEW_NEEDED` was counted as a blocking state even though the disc is already out and the job is waiting for user input, and jobs dismissed from the dashboard (`cleared_at` set) were not excluded from the active-job check, so an invisible ghost card could permanently block the button. Both are now fixed. (#422) ## [0.21.4] - 2026-06-18 _Highlights: disc titles that carry a season or box-set subtitle in their AI-guessed name now resolve on TMDB correctly instead of stalling on an identity prompt._ ### Fixed - **Disc titles carrying a season or box-set subtitle now find their TMDB match** — when the AI resolved a title like "Avatar: The Last Airbender Book One: Water", the over-specified name matched nothing on TMDB, leaving the job without a `tmdb_id` and forcing an identity prompt while subtitle download failed. Engram now strips the trailing Book/Volume/Part/Season qualifier before the TMDB lookup, so the disc identifies cleanly and episode matching proceeds without interruption. (#420) ## [0.21.3] - 2026-06-16 _Highlights: two disc-handling fixes — a disc whose label Engram can't quite confirm now rips immediately instead of blocking, and re-ripping a stalled track no longer misattributes a finished track as a duplicate._ ### Fixed - **A disc Engram can't quite confirm now rips first instead of waiting on you** — when TMDB finds the show but nothing on the disc corroborates the name (for example a label like `DS9S3D2 ok`, where a stray annotation broke the match), the disc used to stop and ask you to confirm the title *before* ripping anything. It now rips immediately and carries a **Confirm title** prompt you can answer any time — or it pools into the single Needs Review visit at the end — restoring the walk-away workflow for this case. Engram also strips common trailing rip annotations (`ok`, `done`, `rip`, …) from disc labels so these discs usually identify cleanly with no prompt at all. (#414) - **Re-ripping a stalled track no longer duplicates another episode or hides the re-ripped track** — when a track stalled and you reinserted the disc (or used manual re-rip), the re-ripped track could be stamped with a *different* already-ripped track's filename. It then matched that other track's episode, conflict resolution flagged the duplicate, and the original track was bounced to Needs Review — so the dashboard showed a duplicate of the first track and the re-ripped one seemed to vanish. The re-rip now ignores the disc's other finished files sitting in the staging folder (rather than re-detecting them as freshly ripped and mis-assigning them), and a file is only ever matched to the track it actually belongs to. This also closes a latent risk where a *second* stall during a re-rip could delete the episodes you'd already ripped. (#415) ## [0.21.2] - 2026-06-15 _Highlights: subtitle-cache harvesting is now hardened against two real-world OpenSubtitles data quality problems that caused entire TV seasons to match at "no clear vote" confidence and route to Needs Review._ ### Fixed - **Subtitle providers that return one episode's dialogue under a different episode's code no longer corrupt the cache** — some providers re-time an episode's subtitle and save it under a different episode slot (e.g. DS9 S02E05 was a re-timed copy of S01E05 "Babel"). Two episodes then held byte-identical dialogue in the TF-IDF index, the matching scores scattered, and both episodes ended up in Needs Review. The harvester now hashes each downloaded subtitle's cleaned dialogue, detects cross-episode duplicates before they enter the cache, removes the contaminant file, and marks its slot as not-found so it can be re-fetched cleanly. On the affected DS9 Season 2, exactly the 12 contaminated episodes were flagged and cleared. (#407) - **OpenSubtitles results mislabeled to the wrong episode are now rejected before they reach the cache** — OpenSubtitles returns multiple candidates per episode number, and for some shows the metadata is wrong (e.g. DS9 Season 2 subtitles are tagged `season_number=1`, and within-season numbering is offset by the Emissary two-part pilot so "Episode 10 - Move Along Home" was returned for a request whose S01E10 is "The Nagus"). The harvester took the first candidate, saving the wrong episode's dialogue under the right code. Each candidate is now cross-checked against the requested episode's TMDB title (and episode numbers outside the season's real range are rejected outright); the harvester saves the first *valid* candidate per episode, falling through to the scraper sources when no valid OS result exists. On live DS9 data, the real Season 1 subtitles now harvest correctly — The Passenger at S01E08, Move Along Home at S01E09 — and that episode's audio now matches at 0.347 vs 0.058 runner-up where it previously scored ~0.067 scattered and went to review. (#407) ## [0.21.1] - 2026-06-15 _Highlights: a fixes-focused release that sharpens TV disc identification — fan-style abbreviated labels (e.g. `DS9`) now resolve to the full show name, a legitimately feature-length episode is no longer mistaken for a "Play All" track and discarded, and a disc whose identity can't be corroborated goes to Needs Review instead of completing under a guessed name._ ### Changed - **Multi-disc box sets identify a little faster** — ripping several discs of the same season no longer re-fetches identical episode-runtime data from TMDB once per disc; the runtime lookup is now cached for the life of the app, trimming redundant network calls during identification. (#404) ### Fixed - **Abbreviated TV disc labels now resolve to the right show name** — a disc whose label is a fan-style abbreviation (e.g. `DS9` for *Star Trek: Deep Space Nine*) used to keep the raw label as the show name even when TMDB had identified the series correctly, filing episodes under a name like `DS9S1D1`. Engram now matches the abbreviation against the TMDB title — including number-words, so `DS9` corroborates "Deep Space **Nine**" — and adopts the proper show name. (#403) - **Feature-length episodes are no longer discarded as a "Play All" track** — a double-length episode such as a 90-minute pilot whose runtime happened to equal the combined length of the other episodes on the disc could be mistaken for a "Play All" concatenation and skipped. Engram now checks the expected episode runtimes from TMDB before flagging a long title, so a legitimate long episode is kept and filed (and the disc still classifies as TV rather than a movie). (#403) - **Discs whose identity can't be confirmed now go to review instead of completing under a guessed name** — when TMDB's show name can't be corroborated by anything on the disc, the job is sent to Needs Review with the suggested title to confirm or correct, rather than silently organizing files under the raw disc label. (#403) ## [0.21.0] - 2026-06-13 _Highlights: the walk-away workflow — drop a disc and walk away. Ripping now starts immediately even when Engram has a question about the disc (an unreadable label, an unknown season, or two same-named shows): the question rides along on the job card as a button you can answer any time, or pools into a single Needs Review visit at the end, instead of stopping the disc before it rips. Re-matching is dramatically faster thanks to a persistent on-disk transcript cache and background pre-transcription while a disc waits in review — and, with the opt-in fingerprint network, a disc the community has already mapped is recognized instantly by its content hash, pre-assigning every episode and skipping audio matching entirely._ ### Added - **Drop a disc and walk away — ripping now starts immediately even when Engram has a question about the disc** — an unreadable label, a box-set disc that doesn't reveal its season, or two same-named shows used to stop the disc in review *before* ripping, so a headless-server user visited the dashboard twice: once at insert to answer, and again at the end for episode review (the exact friction raised in the follow-up discussion on #370). The disc now rips first, and the open question rides along on the job card as a button — **Name this disc**, **Select season**, or **Confirm title** — you can answer any time. Answer during the rip and matching picks up seamlessly with zero stops; ignore it and the disc finishes ripping, then pools the question into one Needs Review visit at the end. The season question usually answers itself: matching searches across seasons automatically, and once two episodes agree on a season the disc is pinned to it and the prompt quietly disappears. (#370) - **Episode re-matching no longer re-runs speech recognition** — Whisper transcripts are now kept in a persistent on-disk cache keyed by the exact file, audio offset, and speech-recognition model, so re-matching a track — after a review decision, a "Wrong title?" re-identification, a "Re-match", or even a full app restart — reuses the transcription work it already did instead of grinding through it again. Re-matches that used to take minutes now take seconds. The cache prunes itself, and a re-ripped file simply gets fresh entries, so stale transcripts are never served. (#397) - **Background pre-transcription while a disc waits in review** — while a job sits in Needs Review, Engram now quietly transcribes its unresolved tracks in the background (politely yielding to any live matching, and stopping the moment you act on the job), so by the time you pick an episode or fix the show identity the re-match is near-instant. On by default with a **Background Pre-Transcription** toggle in Settings → Preferences, plus an optional **Pre-Transcribe Entire Files** mode for setups that often hit the expensive full-file fallback. (#397) - **Discs the community has already mapped are recognized instantly — no audio matching needed** — when the optional fingerprint network is enabled, Engram now looks a disc up by its content hash before ripping. If enough people have already contributed the same pressed disc with the same title→episode mapping, Engram identifies it and pre-assigns every episode straight away, so the whole disc skips speech-recognition matching entirely (a network-confirmed disc still verifies its episodes by audio as usual). And, with the same opt-in consent that already covers audio-fingerprint sharing, completed discs contribute their own layout record (the disc's content hash plus how its titles map to episodes — release-level data, never filenames or anything personal, tied only to your rotating pseudonym and erasable via the existing forget control) so the next person to insert that disc gets it for free. A disc that Engram itself identified from the network is never re-contributed, so the catalog can't confirm itself. (#399) ### Changed - **Deep re-match passes now build on each other instead of starting over** — the escalating "deep re-match" scan depths are aligned to a nested grid of audio offsets, so a deeper pass re-visits exactly the chunks a shallower pass already transcribed plus new ones in between; combined with the persistent transcript cache, each escalation only pays for the new offsets. A requested scan depth now equals the realized depth, and the final escalation tier no longer runs a redundant deeper pass on typical-length episodes. (#397) ### Fixed - **Disabling GPU acceleration now fully applies to episode matching** — the matcher could still select CUDA for its transcription model after GPU ASR was turned off (or when the CUDA libraries weren't usable), because it probed the GPU hardware directly instead of honoring the device resolved at startup. It now follows the same startup-pinned device as the rest of the app — including the cached-transcript identity — so "GPU off" means matching genuinely runs on the CPU. (#397) ## [0.20.0] - 2026-06-11 _Highlights: completed disc cards now summarize their contents at a glance — TV cards show the season and matched episode range, movie cards show the year — instead of repeating the raw volume label; plus a fix for a ripping card that could visibly flicker between "ripping" and "matched" on slow or dirty discs._ ### Added - **Completed disc cards now summarize what's on the disc instead of repeating the raw volume label** — a finished card's subtitle used to show the unprocessed disc label (e.g. `TV • ARRESTED_DEVELOPMENT_S1D1`), which is hard to scan in the Done list. TV cards now show the season and matched episode range (e.g. `TV · S01 E01–E08`), listing each season separately for multi-disc sets that span seasons and appending a count when some episodes are missing from the library; movie cards show `Movie · `. The raw label still appears on its own line for traceability, and active cards (ripping, matching) are unchanged. (#382) ### Fixed - **A ripping track no longer flickers between "ripping" and "matched/idle"** — when MakeMKV paused mid-track for a few seconds (common on slow or dirty discs), Engram mistook the brief lull in file growth for the track being finished, handed the still-ripping file to the matcher, and then the progress monitor kept yanking it back to "ripping" as the rip resumed — so the card visibly bounced between the red ripping state and the green matched/idle state for the rest of the rip. A track is now only treated as finished once MakeMKV has provably moved on to the next title (or the rip process exits), so a mid-track write pause can no longer be misread as completion. (#381) ## [0.19.0] - 2026-06-11 _Highlights: Settings becomes a jump-anywhere section list with deep-linkable controls instead of a replay of the first-run wizard, alongside a broad UI/UX polish pass — full-width layouts on large screens, AA-legible secondary text, fixed transparent dropdowns, and safer modal dismissal — plus the shipped subtitle cache now heals itself when a reference episode is missing instead of silently sending the track to Needs Review._ ### Changed - **Settings is a jump-anywhere section list instead of a replay of the setup wizard** — opening Settings from the gear used to reopen the first-run wizard verbatim (titled "Setup Wizard", a linear five-step stepper that always started on step 1 with no hint the steps were clickable), so changing one preference meant knowing which step and collapsible hid it. It now opens as **Settings** with a section sidebar you can move through freely, and individual settings are deep-linkable: the "GPU available →" status badge now opens Settings directly on the GPU acceleration control — it previously dropped you on the unrelated Library Paths step — and the "TMDB not configured" warnings open straight to the TMDB token field. First-run onboarding is unchanged. (#388) ### Removed - **The Library tab has been removed** — it was a poster-grid view over the same completed-jobs data the History page already shows, so it only added navigation clutter with no unique functionality. History remains the single place to browse your finished discs, and any bookmarked `/library` link now redirects there. (#374) ### Fixed - **A TV box-set disc whose label happens to match an obscure movie is no longer misidentified — and the phantom show name is gone** — a disc labeled like `MADMEN3` fuzzy-matched an unrelated TMDB *movie* ("Two Madmen"), and because a movie's id points at a completely different show in TMDB's TV namespace, Engram both sent the disc to review every time *and* showed a baffling "No episodes found for O Hristos xanastavronetai Season 3 on TMDB" banner — which survived even after you corrected the title to Mad Men. Engram now re-resolves identity from the cleaner disc title MakeMKV reports ("Mad Men Season 3") whenever a season-labeled disc matches a movie, so these discs auto-identify with no review; it never carries a movie's id onto a TV job (a cross-namespace id that poisoned the subtitle and roster lookups); and subtitle-download errors are now recorded on a clearable field, so they no longer linger as a stale banner after a successful re-download. (#392) - **A missing reference subtitle no longer silently sends an episode to Needs Review** — when the shipped subtitle-vector cache was missing a single episode (e.g. Mad Men S02E05), Engram skipped the subtitle download *and* matched against the incomplete set without ever noticing the gap, so the affected episode dead-ended in review with no match and an unreliable AI guess. Engram now compares the cache against the canonical episode list, fetches only the missing episode(s) when they can be obtained, and grafts a fetched subtitle into matching at runtime so it actually identifies the track. Any episode it still can't get a reference for is now flagged on the Review page's season roster (a warning glyph + hatch on the slot, plus a summary line) instead of failing silently — because subtitles are the source of truth for matching. Dropping the missing `.srt` into the subtitle cache also now makes the episode matchable on the next run. - **The dashboard and Review page now actually use your screen** — a flex-layout subtlety made every page's content shrink-wrap to its cards instead of filling the intended width, leaving a narrow centered column with huge dead margins on large monitors (the Review page left the entire right half of a 1920px screen empty). Content now fills out to the designed maximum, with the recovered space going to the track grid and the review Inspector — which also takes the larger share of the review layout, since it's the page's main workspace. Below ~1100px (e.g. a window snapped to half of a 1920px monitor) the dashboard's side rail now folds away instead of crushing the job cards until titles truncated to a single letter. - **A disc already in the drive no longer auto-rips during first-run setup** — on a fresh install with a disc inserted, the pipeline fired the moment the backend started, scanning and ripping into the default (unconfirmed) staging path while the setup wizard was still on screen. The disc is now parked until setup completes: the dashboard shows a "Disc detected — finish setup to start ripping" banner, and finishing the wizard picks the disc up automatically — no eject/reinsert needed. Already-configured installs are unaffected. - **Settings dropdowns are no longer transparent** — every styled dropdown (conflict resolution, episode ordering, staging cleanup, watchdog) rendered its open menu with no background, so the options were unreadable over the text behind them. The whole Synapse color-token set was silently failing to reach hand-written CSS (a Tailwind `@theme inline` subtlety), which also left the settings modal with a white frame instead of the brand cyan; both are fixed at the token layer so all 51 affected style rules recover at once. - **Pressing Escape (or clicking outside) the "Identify Disc" prompt no longer cancels the job** — the universal dismiss gestures were wired to job cancellation, so an absent-minded Escape or a misclick on the backdrop destroyed the job (the same applied to the season prompt). Dismissing now just closes the prompt and parks the disc in review — the card's actions and the Review page remain available — and cancelling requires the explicit button, now labeled "Cancel job". A dismissed prompt also stays dismissed instead of re-opening on the next status refresh. - **The "Identify Disc" prompt no longer pops over the dashboard while you're busy** — a disc with an unreadable label (or an unknown season) used to auto-open a blocking modal the instant the status poll noticed it, stealing focus from whatever you were doing, such as watching another disc rip. The prompt now only opens on its own when that disc is the *only* active job — so inserting one disc and walking away still leaves the prompt waiting for you — and stale completed cards don't count against that. When something else is in progress, the disc's card instead shows a prominent "Name this disc…" / "Select season…" button that opens the prompt on demand (also the way back in after dismissing it). - **The REVIEW tab is disabled when nothing needs review** — it previously linked to the dashboard, silently navigating you away (and triggering React duplicate-key warnings in the nav). It now sits inert with a "No jobs awaiting review" tooltip until a review exists. - **The "TMDB not configured" warning appears once, not on every card** — the dashboard banner and an identical per-card alert rendered simultaneously (four copies with three discs on screen). Cards now only warn about job-specific TMDB degradation, such as a rejected key. - **Compact view cleanup** — rows showed raw internal state names (`review_needed`) instead of the formatted labels used everywhere else, truncated "REVIEW NEEDED", and offered no way to resolve an identity review (only Cancel). States are now formatted from the same source as the badges, and identity-review rows get a "Fix title" action. - **Setup wizard polish** — the Next button no longer jumps from bottom-left (step 1) to bottom-right (steps 2+); detected tool versions read "MakeMKV v1.18.3" / "ffmpeg 7.1" instead of raw probe output like the full FFmpeg copyright banner or "(version probe timed out)"; and the ORGANIZING dashboard badge has its own library icon instead of reusing the matching glyph. - **TMDB token onboarding no longer fails silently** — the setup wizard's TMDB field showed `eyJhbGci…` as placeholder text, which is the literal opening of *every* TMDB Read Access Token, so an empty field looked already-filled and users could finish setup having never entered a token — then spend ages debugging silent matching failures. The placeholder is now plain instructions, the token auto-validates as soon as you leave the field (inline ✓/✗ — no "Test Token" hunt), and first-run setup won't pass the TMDB step until the token validates or you explicitly choose to continue without it. The dashboard also shows a dismissible warning while no TMDB token is configured, and an active disc whose classification fell back to heuristics because the key is missing or rejected now says so on its card ("TMDB API key not configured — classification ran in heuristic-only mode") instead of silently degrading. Token validation also now tells "couldn't reach the validation service" apart from "token rejected", so a backend hiccup no longer reads as a bad token. (#243) - **Identifying a disc no longer briefly freezes the dashboard and other jobs** — disc identification (and the "Wrong title?" re-identify) made blocking TMDB network calls directly on the async event loop, so while one disc was being looked up, live progress updates, other in-flight jobs, and drive detection all stalled until the lookup returned. Those lookups now run off the event loop, keeping the UI and concurrent jobs responsive during identification. (#377, #379) - **A disc with a bad sector that keeps writing slowly no longer wedges the job** — when MakeMKV crawls through damaged sectors, writing small bursts every 30–60 seconds, the "incomplete rip" detector could never accumulate 90 consecutive seconds of file-size stability (each burst reset the counter), so the job stayed stuck until the hours-long outer timeout fired. The grace window is now measured from the *last time the file grew* rather than from consecutive stable polls, so intermittent slow writes no longer restart the clock — detection fires within 90 seconds of MakeMKV's final write, regardless of how erratically it wrote before. - **Dim secondary text is now legible, and the magenta accent is back to meaning "action"** — small grey labels and values that carry real information (timestamps, file paths, per-track details, table cells, form help and status text) used a tertiary grey that fell below the WCAG AA contrast minimum; they're now a brighter grey that passes AA, while purely decorative "telemetry" text keeps its ambient dimness. Separately, magenta — reserved for actively-ripping things and the primary action of a flow — was trimmed where it had crept in as decoration: the History "TV seasons" distribution bar and the disc-identify modals' Movie/TV toggle now use the brand cyan/amber palette, and those modals' Cancel buttons are now neutral so the primary action carries the weight. (#384) ## [0.18.0] - 2026-06-10 _Highlights: a damaged track can now be recovered without re-ripping the whole disc — clean the disc, reinsert it, and Engram re-rips just that title — and box-set discs that don't reveal their season now prompt you to pick one up front instead of dead-ending every episode in Needs Review._ ### Added - Recover a single damaged track without re-ripping the whole disc. When a title fails at the rip level (a scratch/bad-sector truncation or a ripping stall), Engram now holds it in review with a "clean the disc and reinsert to re-rip this title" prompt. Reinserting the **same** disc (verified by its content fingerprint) automatically re-rips just that track, re-matches it, and finishes the job — with a manual "Re-rip this title" button and a bounded automatic-retry cap as fallbacks. (#371) ### Changed - A disc with an unrecoverable track no longer auto-completes with that track silently failed; it now waits in review until the track is re-ripped or explicitly skipped, so a "completed" job means every title succeeded. (#371) ### Fixed - **Discs that don't reveal their season no longer dead-end in review** — box-set discs labeled by disc number only (e.g. `Eureka D3`) identified the show but not the season, and Engram then silently skipped downloading the reference subtitles that episode matching depends on. Every episode failed at 0% confidence and the whole disc landed in Needs Review. Engram now asks up front: as soon as the disc is identified, a prompt appears to pick the season — or choose "All Seasons" to let matching search every season — and everything downstream (subtitle download, matching, review) works normally. Shows with only one season skip the prompt. (#370) - **The misleading "different same-named show" suggestion no longer appears when reference subtitles never arrived** — with no subtitles to match against, zero matches says nothing about the show being wrong, but Engram suggested re-identifying as a same-named twin (e.g. *Eureka! (2022)* for a *Eureka (2006)* disc). The review reason now explains the real problem — no reference subtitles were available — and what to do about it, and Engram no longer burns multi-minute deep re-match passes against an empty subtitle library. (#370) - **The review screen gains a season picker for jobs whose season is still unknown** — manual episode assignment was locked to Season 1 codes (the dropdown in the bug report's screenshot); the picker now loads any season's episode list, with episode names, for assignment. Two smaller gaps closed along the way: naming a disc through the identify prompt now also starts the reference-subtitle download (that path previously never downloaded any), and a subtitle lookup that finds nothing is no longer cached for the rest of the run, so a retried download becomes visible to re-matches. (#370) - **Discs awaiting a "Wrong title?" correction no longer show a dead-end "Review needed" button** — when a disc matched multiple same-name shows on TMDB (e.g. "The Office"), the card showed a yellow "Review needed" action button next to an identical "REVIEW NEEDED" status badge, but that button only opened an empty episode-review queue because the show identity wasn't settled yet. The episode-review button is now hidden while a disc's identity is unconfirmed, leaving the corrective "Wrong title?" action (and an explanatory banner) as the clear next step. (An earlier attempt only covered discs with no enumerated titles, which never happens for this case since titles are listed at scan time.) - **The Re-Identify ("Wrong title?") modal now shows the release year and TMDB id** — after you pick a show from the search results (or the "Did you mean?" quick-pick), the modal confirms the exact match as `Name (year) · TMDB #id` so you can tell same-name shows apart, and it now also shows what the disc is *currently* identified as for comparison. - **The History detail panel now shows the release year alongside the show/movie name** — the job-detail drill-down in History displayed only the name (or a bare TMDB id) for a disc's committed identification, because the backend exposed `tmdb_year` on the job list/by-id response but not on the detail response. The detail panel now reads `Name (year)`, matching the Re-Identify modal, so same-name titles (e.g. Frasier 1993 vs 2023) are distinguishable in history too. (#358 follow-up) - **Processing many jobs at once no longer crashes with a database timeout** — importing several seasons while another disc is ripping could exhaust Engram's database connection pool (`QueuePool limit of size 5 overflow 10 reached, connection timed out`), which stalled episode matching and the dashboard. The connection pool is now sized for that concurrency (and waits politely for SQLite's single-writer lock instead of failing with "database is locked"), and episode matching no longer keeps a database connection open while it looks up episode runtimes over the network. The same pool sizing and polite-wait now also cover the separate synchronous database connections that background worker threads use to read settings (during matching, organizing, and subtitle downloads), so those can't exhaust their own pool under the same import load. Takes effect after a backend restart. - **The dashboard now shows "Organizing" while files are moved to your library, instead of staying stuck on "Matching"** — when all of a TV disc's episodes matched automatically, Engram moved the files from the staging drive into the library (which can take a while over a network share) while the card still read "Matching", with no sign of what was happening. The card now switches to an **Organizing** state with an "Organizing N of M" progress bar as each episode lands; a single-file movie shows an indeterminate "Organizing to library…" indicator. (Movies and the post-review path already did this — this closes the gap on the common auto-match path.) As part of the fix, the long file-move no longer holds a database connection open, the stall watchdog no longer force-advances a job mid-move, and a job interrupted by a restart while organizing is now resumed on the next launch instead of being marked failed. (#359) - **A disc with a bad sector no longer leaves the whole job stuck for hours** — when MakeMKV aborts a title partway through because of an unreadable spot on the disc (an "uncorrectable" read error, e.g. a scratch), the partly-ripped file sits on disk far smaller than expected. Engram kept waiting for that file to "finish", leaving the job stuck on **Matching** for hours before finally giving up. Engram now recognizes a ripped file that has stopped growing well below its scanned size as an incomplete rip within about 90 seconds, sends just that title to review with a clear "incomplete rip — clean the disc and re-rip" explanation, and lets the rest of the disc's episodes finish normally. While Engram waits for a freshly-ripped file to finalize, the track now shows as **Queued** instead of a misleading spinning **Ripping** badge. - **Discs with run-together volume labels are now filed under the correct name and show their cover art** — a disc whose volume label has no separators between words (e.g. `BREAKINGBADS2`) was organized into a folder like `Breakingbad` and showed no poster, even though Engram had already identified it as *Breaking Bad* on TMDB. The matched TMDB title is now treated as the authoritative name whenever it's corroborated by something on the disc itself — the volume label (compared ignoring spaces and punctuation) or MakeMKV's human-readable disc name (e.g. `Breaking Bad: Season 2: Disc 1`), which is now always consulted rather than only as a last resort — so the show is filed under its proper name and season. The dashboard's cover art is also fetched by the resolved TMDB id instead of re-searching by the (sometimes garbled) name, so the poster now appears reliably. Re-rip the disc to apply the corrected naming. - **Inserting the next disc of a season is no longer rejected as a duplicate** — every disc in a season set shares one volume label (e.g. all of Breaking Bad Season 2 read as `BREAKINGBADS2`), so inserting Disc 2 while Disc 1 was still matching made Engram treat it as the same disc lingering in the drive and silently drop it. Engram now fingerprints each disc by its content hash, so two discs that share a label are told apart: a genuinely different disc is picked up right away, while a re-read of the same disc is still correctly ignored. ## [0.17.0] - 2026-06-08 _Highlights: episode matching can now run on an NVIDIA GPU (opt-in, with the CUDA runtime downloaded on demand) for dramatically faster transcription — and the dashboard's ASR badge now reports the device transcription actually runs on instead of claiming CUDA while silently falling back to the CPU._ ### Added - **GPU acceleration for episode matching (NVIDIA, opt-in)** — Engram can now run Whisper transcription on an NVIDIA GPU instead of the CPU, which is dramatically faster for episode matching. Because the required CUDA libraries (cuDNN + cuBLAS) are ~1.2 GB, they are not bundled into the download; instead a new **GPU Acceleration** control in Settings → Matching detects your GPU and downloads the runtime on demand (one time, into `~/.engram/` so it survives app updates). Supported on Windows and Linux with an NVIDIA GPU; macOS and AMD GPUs continue to run on the CPU (the engine has no GPU path there). Takes effect after a backend restart. ### Fixed - **The ASR status badge no longer claims "CUDA" when it's actually using the CPU** — on a machine with an NVIDIA GPU, the dashboard badge showed `ASR: CUDA …` based purely on the GPU being *present*, even though the compiled build had no CUDA math libraries and silently fell back to the CPU for every match. The badge now reports the device transcription actually runs on, and when a GPU is available but unused it shows an actionable `CPU · GPU available →` chip that opens the new GPU setting. ## [0.16.3] - 2026-06-07 _Highlights: a fixes-only release — the Windows "Restart to update" now runs to completion and actually installs the update, and the review panel's AI/LLM episode-matching button works again and clearly reports what it found._ ### Changed - The review Inspector's "Try LLM match" endpoint now returns a differentiated `reason` (e.g. `ai_disabled`, `not_configured`, `no_season`, `show_not_found`, `transcription_failed`, `no_match`, `llm_error`) and uses HTTP 503 for retryable operational failures (matcher/transcription/LLM-provider errors) and 500 for unexpected errors, instead of reporting every failure as a 200. This lets the UI tell "AI matching is off / couldn't find a match" apart from "the LLM provider failed, retry". (#347 follow-up) ### Fixed - **The review panel's episode-ordering picker now offers only Aired and DVD order** — the per-show ordering selector in the review queue could show extra buttons (digital, story-arc, production, TV) whenever a show happened to have those alternative orderings on TMDB, even though the global Episode Ordering setting only ever offered Aired and DVD. The two surfaces now agree: both offer just Aired and DVD. The other orderings are still recognized internally but no longer selectable, and matching, history, and the fingerprint network remain on canonical aired numbering as before. (#348) - **The "Try LLM match" button in the review Inspector now works** — clicking it on a track in review did nothing useful: the backend failed instantly on a wrong internal import (it looked for an `episode_curator` that doesn't exist), caught the error, and returned a silent success, so the button appeared to do nothing and the failure only showed up in the logs. AI-assisted single-track matching from the review panel now runs the transcription and returns a suggestion as intended. (#347) - **The review screen's "Try AI match" button now tells you what happened** — clicking *Try AI match* on a title in review runs AI episode matching in the background (often 1–3 minutes), but the button gave no signal: while it ran nothing changed on screen, and if the AI found no confident match or hit an error the screen silently refreshed and showed nothing. The button now shows a "Matching…" state while it runs, and reports the outcome right in the Inspector — the suggested episode on success, or a clear notice otherwise ("No confident AI match found." or "AI match failed — check the server log."). (#349) - **"Restart to update" on Windows now runs to completion — for real this time** — even after the 0.16.1 fix, the in-app updater could still leave you on the old version with the app simply vanishing. The helper that swaps in the new build waits for the app to close using a command pipeline that *silently hangs forever* when run without a console window — which is exactly how the helper was launched — so it never reached the step that copies in the update. The helper now runs with a hidden console so that wait works, gives up after ~10 seconds instead of hanging indefinitely, and on any failure rolls back to your working version and tells you in the app (with a "download manually" link) rather than disappearing. On a successful update you get a brief "you're now on vX" confirmation, and restarting no longer opens a second browser tab. (#351) ## [0.16.2] - 2026-06-05 _Highlights: a fixes-only release — AI episode matching works again on Google Gemini, multi-season imports now match every episode correctly instead of dropping them into review, and tool detection no longer stalls the Settings screen on a busy optical drive._ ### Fixed - **Tool detection no longer hangs the Settings/first-run screen on a busy optical drive** — the "Detect tools" check (which runs on the dashboard and in the Config Wizard) read MakeMKV's version by briefly spinning up the drive, and on a slow or busy drive that probe could block the whole check for up to ~20 seconds — so FFmpeg and fpcalc, which were already detected, sat waiting on it before anything showed up. MakeMKV is now reported as found (with its path) without waiting on the slow version read, the version probe is given a short budget and degrades to "version probe timed out" instead of stalling, and a hard per-tool deadline guarantees one slow tool can never gate the others. Server startup and the diagnostics report get the same short budget, so neither stalls on a busy drive either. (#343) - **AI episode matching now works with Google Gemini** — when the AI provider was set to **Gemini**, AI-assisted episode matching silently never produced a suggestion: every request to Gemini was rejected because the structured-output schema Engram sent used a format Gemini's API doesn't accept (a field allowed to be "an object or nothing" was encoded in a way only some providers understand). Gemini answered every call with a "bad request" error, which Engram caught and treated as "no AI result," so affected tracks fell through to manual review with nothing to show for it — making AI matching look completely broken on Gemini. Engram now translates the schema into the form Gemini expects before sending, so Gemini returns real episode suggestions again. Other providers (Anthropic, OpenAI, OpenRouter) were unaffected. (#344) - **Releases no longer fail on a transient tool-detection timeout** — the release smoke test probed the bundled-tool detection endpoint with a single 5-second request, and on a cold Windows runner the first execution of the freshly-extracted `fpcalc`/`ffmpeg` binaries (antivirus scan + cold process start) could exceed that and fail the whole release even though the build was fine — this is what stopped the v0.16.1 binaries from publishing. The smoke test now retries the probe with a warmup and verifies the bundled `fpcalc` is present in the build tree directly, so a transient timing hiccup can no longer sink a release. (#342) - **Parallel episode matching no longer matches tracks against the wrong season's references** — the parallel transcription added in 0.16.0 let several tracks match at once, but they all shared a single matcher that held just one "current season" set of reference subtitles. When two tracks from different seasons matched concurrently — e.g. importing a multi-season **Seinfeld** collection, where each season is its own job and they run side by side — one track's references were swapped out from under it mid-match, so it compared its audio against the *other* season's episodes, found nothing, and fell back to a slow whole-file pass that also failed, dropping the track into **Needs Review** with no match and no voting feedback. The matcher now keeps a separate, reusable reference set per season, so concurrent matches can't clobber each other. In a real 7-season Seinfeld run this took episodes that were landing in review at 0 votes back to confident, correct matches (10/10 votes) — with full per-track voting restored and no needless whole-file fallback. (#345) ## [0.16.1] - 2026-06-05 _Highlights: a fixes-only release — "Restart to update" on Windows now runs to completion and actually installs the update, and importing a TV-season folder with a generic label (like `Season 3`) now resolves the show and matches its episodes instead of dropping the whole season into Needs Review._ ### Fixed - **"Restart to update" now actually applies the update on Windows** — the crash-safe swap added in 0.15.3 (copy aside, verify, atomic rename, roll back) was correct, but it never ran to completion: the helper that performs the swap was launched with your install folder as its working directory, and Windows won't rename a folder while it's any program's current directory. So the very first rename failed, the updater rolled back, and you were left on the old version with no visible error — every time. The helper now moves to a neutral working directory before the swap (and is spawned from one), so the rename succeeds and the new build is installed. The `~/.engram/update_helper.log` now also records the helper's working directory and each step's exit code, so any remaining edge case is diagnosable from a single log. (#338) - **Imported TV seasons with generic folder labels now match instead of sending every episode to review** — when you import a pre-ripped folder like `…\Seinfeld\Season 3\`, Engram reads the show and season from the folder names, but the import's disc "label" is just `SEASON_3`, so the show was never looked up on TMDB and the job had no TMDB id. Since reference subtitles are now stored per-TMDB-id, a missing id sent the matcher hunting for references in a folder that didn't exist (`…/cache/data/Seinfeld` instead of `…/cache/data/1400`), found none, and dropped every track into **Needs Review** with no real match — and, for the same reason, the review panel showed no episode list and bonus features weren't sorted as Extras. Imports (and discs you name by hand) now resolve the show's TMDB id from its name *before* matching, so references are found, episodes match, extras are tagged, and the review panel shows the full season. Genuinely ambiguous same-name shows — e.g. *Frasier* (1993) vs the 2023 revival, where the folder has no year to tell them apart — still pause for you to pick the right one. (#340) ## [0.16.0] - 2026-06-05 _Highlights: episode matching now transcribes multiple tracks in parallel — the **Max Concurrent Matches** setting finally drives real concurrency (auto-clamped to your CPU or GPU), with a live ASR backend badge on the dashboard, and the "Matching" count is now honest about which tracks are genuinely transcribing._ ### Added - **Episode matching now transcribes multiple tracks in parallel** — the dashboard could show several tracks as "Matching" at once while only one actually made progress and the CPU sat mostly idle. The speech-recognition model was running one transcription at a time no matter what, so the **Max Concurrent Matches** setting didn't really do anything. That setting now drives how many episodes are transcribed simultaneously (on CPU or GPU), automatically clamped to your hardware (CPU cores, or a GPU limit) so it can't oversubscribe and slow things down. A small **ASR badge** on the dashboard shows the active backend — e.g. `ASR: CUDA · float16 · 4w` or `ASR: CPU · int8 · 8w`. (#336) ### Changed - **The dashboard's "Matching" count is now honest** — tracks waiting for a transcription slot now show **QUEUED** and flip to **MATCHING** only when a real worker actually starts on them, so the number of tracks shown as "Matching" reflects what's genuinely transcribing rather than overstating progress. The **Max Concurrent Matches** setting takes effect after a backend restart (noted in Settings). (#336) ## [0.15.4] - 2026-06-04 _Highlights: a fixes-only release — large multi-season imports no longer dump episodes into "Needs Review" while they wait for a match slot, the review page's "Re-match" is now visible and advisory (it never silently files a track), and every ripped track now shows consistently how its episode was identified._ ### Fixed - **Large multi-season imports no longer dump most episodes into "Needs Review"** — importing a folder with many seasons at once (e.g. a 7-season **Seinfeld** collection, ~175 episodes) created one job per season and queued every episode for matching, but Engram only matches a couple at a time. Episodes waiting their turn were shown as actively "working," and after 30 minutes the stale-job watchdog mistook the whole patiently-waiting queue for a stuck job and force-advanced ~126 of them straight to **Needs Review** — even though nothing was actually wrong. Tracks now show a distinct **QUEUED** state ("waiting for a match slot") while they wait, flip to **MATCHING** only when work actually starts, and are never sent to review just for waiting in line. A single match that genuinely hangs is still recovered on its own (and frees its slot so the rest of the queue keeps draining), so big imports now finish on their own instead of needing manual cleanup. (#334) - **"Re-match" on the review page no longer runs invisibly or files a track behind your back** — clicking **Re-match** on a track in the review inspector gave no sign it was working (the spinner flashed for a fraction of a second and cleared while matching was still running in the background), and when the deep match landed on an episode that already existed in your library it quietly tried to organize the file, hit a "file already exists" conflict, and surfaced *nothing* — leaving you staring at an unchanged screen (seen re-matching a bonus track on a **Gilmore Girls** disc). A manual re-match is now **advisory**: it shows a live "Re-matching…" indicator on the track for the whole match, then surfaces the candidate in review for you to confirm or mark as an Extra — it never silently organizes anything. If the suggested episode is already present, you now get a clear "File exists — likely a duplicate or extra" warning instead of a silent no-op. (The underlying conflict that was being swallowed is now reported everywhere it can occur, not just on this path.) (#327) - **Each ripped track now shows consistently how its episode was identified** — inside a disc card, the per-track details rendered unevenly: some tracks showed a confidence percentage and a vote tally, while others showed only the matched episode with no confidence and no source chip at all, so a perfectly good match looked broken or empty (seen ripping a **Gilmore Girls** season). Tracks identified by Engram's whole-file fallback have no per-chunk "votes" by design, and the card was mistaking "no votes" for "no confidence." Every matched track now shows a normalized confidence and a provider chip — an Engram mark for audio matching (with a distinct fingerprint variant), or a DiscDB/AI/Manual chip for those sources — with the vote tally shown only when votes actually exist and a small "full-file" tag explaining the matches that have none. (#333) ## [0.15.3] - 2026-06-04 _Highlights: a fixes-only release — the Windows "restart to update" swap is now crash-safe with automatic rollback, a disc loaded while the previous one is still matching is picked up right away, and a season's longest episode is no longer misfiled as an extra._ ### Fixed - **The README download-count badges now show accurate cumulative per-OS totals** — the badges counted release assets by bare file extension, so the Linux badge swept in the macOS builds *and* the rolling subtitle-cache data pack (a `.tar.gz` whose GitHub download counter resets to zero every time the cache is rebuilt). That made the Linux number read roughly 15× too high and visibly fluctuate downward as the cache was re-published. Downloads are now tallied by exact per-OS binary name, and a separate macOS badge was added. (#324) - **A disc's longest episode could be misfiled as an "extra"** — before matching a TV track, Engram checks its length against the episode runtimes TMDB lists for the season, and a track that's too far off is set aside as bonus content without ever being matched. That length window was symmetric (±5 minutes), but DVD/Blu-ray episodes run *longer* than TMDB's listed runtime — the disc includes the "previously on" recap, full end credits, and "next time" preview — so a season's longest episode could overshoot the window and be dropped into Extras even though its shorter siblings on the same disc matched fine (seen with **Gilmore Girls** S1, where the ~50-minute "Rory's Dance" was filed as an extra next to its ~46–48-minute neighbors). The window is now lenient on the long side (up to 5 minutes short, 10 minutes over), so a long-but-real episode is still transcribed and matched. (#321) - **"Restart to update" no longer risks breaking your install on Windows** — the updater downloaded and verified the new version correctly, but the final swap copied the new files *in place over your running install* with no safety net. If Windows still held a lock on any file the instant the old app exited (antivirus or Search Indexer commonly do, for a second or two), the copy half-finished and left a mix of old and new files that wouldn't start — with no way back, so the only recovery was to download and unzip the release by hand. The Windows updater now copies the new build to a separate folder first, verifies it's complete, swaps it into place with two instant renames, and **automatically rolls back to your previous version if anything goes wrong** — and writes a step-by-step log to `~/.engram/update_helper.log` so any future failure is diagnosable. Cleanup of already-installed staged updates is also now crash-safe. (#322) - **A disc loaded while Engram was still matching the previous one wasn't picked up** — after a disc finished ripping, Engram ejected it but kept matching its episodes in the background for several minutes. If you loaded the next disc during that window, it was silently ignored — no job started — until the previous job finished and you ejected and reinserted it (and reinserting too soon, before the previous job was done, was ignored too). Engram was treating the still-matching job as if it still occupied the drive, even though the disc had already been ejected. It now recognizes that a job past the ripping stage no longer holds the drive and starts a fresh job for the new disc right away; reloading the *same* disc is still ignored, so nothing gets duplicated. (Seen binge-ripping a **Gilmore Girls** season.) (#323) ## [0.15.2] - 2026-06-04 _Highlights: the community fingerprint network moved to a stable custom domain (`api.engramfp.com`); existing installs migrate automatically on update with no interruption._ ### Changed - **The fingerprint network moved to a stable custom domain** — Engram now contributes and identifies against `https://api.engramfp.com` by default, instead of the old `*.workers.dev` address. Existing installs pick up the new address automatically on update (unless you've set a custom server URL in Settings → Data Sharing); the old address keeps serving during the transition, so nothing breaks mid-migration. (#319) ## [0.15.1] - 2026-06-03 _Highlights: a data-loss fix — importing from a watch folder no longer deletes your source folder — plus faster, more accurate episode matching, several import-reliability fixes, and a disc loaded right after an eject is now reliably picked up._ ### Fixed - **An import could delete the source folder you imported from** — when importing pre-ripped files from a watch folder, Engram treated that folder as disposable "staging" and, on a successful job, deleted it wholesale once the matched files had been moved into your library (with the `on_success`/`on_completion` cleanup policy). For a disc rip that staging folder is a throwaway temp directory, so deleting it is harmless — but for an import it is **your own source folder**, so anything still inside it was permanently removed (not sent to the Recycle Bin). This was especially destructive when the folder also held content Engram never imported — for example loose files at the top level shadowing the `Season NN` subfolders beneath them — because the un-imported episodes were deleted along with the folder. Import sources are now never deleted by staging cleanup; only disc-rip staging directories are. (#317) - **A multi-season import folder could skip its Season subfolders entirely** — when an import watch folder contained both loose top-level `.mkv` files and `Season NN` subfolders, Engram could latch onto the loose files, treat the whole folder as a single season-less "flat" import, and never scan the season subfolders at all (which file it noticed first was effectively random). Those seasons were then left un-imported — and, with the bug above, deleted. The scanner now recognizes that a folder containing season/disc subfolders is a container: it imports each season, and leaves the ambiguous loose top-level files in place (logged) rather than letting them shadow the real content. (#317) - **Correct episodes were being re-transcribed (slowly) instead of accepted** — when matching ripped or imported episodes, a confident, decisive match could still be thrown away and re-run through a much slower full-file transcription, which sometimes turned a correct match into a wrong one or a manual-review prompt (seen with some **True Detective** episodes). The matcher judged matches by a raw overlap score that is naturally tiny for speech-vs-subtitle comparisons, ignoring its own calibrated 0–100% confidence. It now trusts that calibrated confidence: a decisive, high-confidence match is accepted directly and filed automatically, so matching is both faster and more accurate. (#316) - **Import folders now reliably show "matching" in the dashboard** — an imported folder could be busy matching episodes in the background while its card stayed stuck on the scanning animation. The job now flips to the matching view the moment real matching begins, hardening the earlier fix so a missed status update can't strand the card. (#316) - **The import watch folder stopped re-importing a folder after a single failed attempt** — once any job had been created for a watched folder, that exact folder was blocked from ever being imported again, even if the job had failed (cancelled, or auto-failed when the server restarted mid-job). Because the watch folder is re-scanned on every poll and on every restart, a one-time failure silently wedged the folder: the watcher kept detecting it and immediately skipped it ("Job already exists for staging path …"), so nothing imported. Engram now dedups only against an active or review-pending job for the path, so a previously failed import is retried on the next scan instead of being stuck forever. (#311) - **A disc loaded right after Engram ejected the previous one could be ignored** — when a job finished, Engram ejected the disc but didn't tell its drive monitor the drive was now empty. If you inserted the next disc before the monitor's next poll noticed the eject, it saw "disc present" both before and after and fired no "inserted" event — so no job was created and the new disc sat unprocessed until you ejected and reinserted it (or restarted Engram). Engram now marks the drive empty the instant it ejects, so the next disc always starts a fresh job. (#289, thanks @katelovescode!) - **Clearer disc card when two shows share a name** — a disc that matches more than one same-name show (for example the 1993 vs 2023 **Frasier**) is flagged for review before ripping, but its card showed a "Review needed" button that opened an empty review screen — there's nothing to review until the disc is ripped — right next to an identical "Review needed" status badge. The card now hides that dead-end button until the disc actually has ripped tracks, emphasizes the **Wrong title?** action as the thing to click, and adds a short banner explaining the same-name ambiguity and how to resolve it. (#308) ## [0.15.0] - 2026-06-03 _Highlights: same-name shows (for example the 2023 **Frasier** vs the 1993 original) can now coexist in your TV library, each in its own year/TMDB-tagged folder; the dashboard now warns you up front when no TMDB key is configured; and FFmpeg is now a documented prerequisite with broader Windows auto-detection and inline path validation in the Config Wizard._ ### Added - **Same-name TV shows can now coexist in the library** — TV episodes were filed under the bare show name (`Frasier/Season 01/…`), so ripping both the 1993 **Frasier** and the 2023 revival collided in one folder with identical filenames, and the second rip was skipped, overwritten, or bounced to Review. A new optional **Show Folder Format** setting disambiguates the show folder with the first-air year and TMDB id, matching the layout Plex (`Frasier (1993) {tmdb-3452}`) and Jellyfin (`Frasier (1993) [tmdbid-3452]`) parse for reliable matching. It is opt-in and defaults to the current bare-name layout, so existing libraries are untouched — set a format containing `{year}`/`{tmdb_id}` (and optionally add them to the episode filename format) to enable it. (#297) - **The dashboard now warns you when TMDB isn't configured** — without a TMDB Read Access Token, discs can't be identified, but previously the only symptom was matches quietly failing. The dashboard now shows a dismissible banner — with a one-click link to open Settings — whenever no TMDB key is set, plus an inline notice on each active job card. Both clear automatically the moment a token is saved, with no page reload. (#294, thanks @katelovescode!) - FFmpeg is now documented as a prerequisite, with per-platform install steps (including `winget install Gyan.FFmpeg` on Windows) and a dedicated [Troubleshooting](https://jsakkos.github.io/engram/troubleshooting/) page led by the common "FFmpeg not detected" case. - The Config Wizard now validates a manually-entered MakeMKV or FFmpeg path against the backend and shows the detected version inline (or the specific error), so a hand-typed override is no longer saved blind. The FFmpeg "not found" card also links to the download page. ### Changed - Windows FFmpeg auto-detection now also searches the Chocolatey, scoop, winget (`Gyan.FFmpeg`), and user-home install locations, so a freshly-installed FFmpeg is found even when it isn't yet on the running process's `PATH`. The in-app install hint now names the exact winget package. ### Fixed - **A TV disc named like "Show Season 11 Disc 2" could match the wrong episodes for hours, then fail** — when a disc had no readable volume label, Engram fell back to the drive's display name (e.g. `Supernatural Season 11 Disc 2`), but the parser only recognized a season when the disc number was in parentheses (`(Disc 2)`). A space-separated `Disc 2` left the season undetected, so no subtitles were downloaded for that season and matching fell back to brute-forcing every previously-seen season's subtitles with speech recognition — a run that could churn for many hours scoring the audio against the wrong seasons before failing. Engram now reads the season from these names (with or without parentheses or a dash), so the correct season is detected, its subtitles download, and episodes match on the first pass. (#303) - **Matching a disc whose season couldn't be determined was needlessly slow** — when a TV disc's season is unknown, Engram matches the file against every candidate season in turn. Each attempt re-ran speech recognition over the *same* audio from scratch, so a show with many seasons could spend hours re-transcribing identical audio before giving up. Transcriptions are now cached and reused across season attempts, so only the first attempt does the expensive transcription work and the rest are near-instant. (#303) - **Import watch-folder jobs didn't show their tracks or matching progress** — a job created from the import watch folder (pre-ripped MKVs in a watched directory) ran to completion on the backend, but the dashboard stayed frozen on the "scanning" radar for the entire matching phase and then jumped straight to organizing/completed, never showing the track grid or live per-track matching. Because these jobs skip ripping, they advance `identifying → matching` directly — a transition the job state machine rejected, so the card never learned it had left identifying. Engram now allows that shortcut (and the movie equivalent), broadcasts each track's matching state immediately, and routes the movie import branch through the state machine, so import jobs show their tracks and matching progress just like a disc rip. (#307) ## [0.14.1] - 2026-06-02 _Highlights: a hardening fix for the in-app auto-updater — it can no longer install an incomplete or corrupted download over your working copy, and builds now always include the TLS certificate bundle whose absence silently broke all networking in some 0.14.0 installs._ ### Fixed - **The auto-updater could stage and apply an incomplete build, breaking the app** — if an update's extraction was interrupted (or files were removed afterward, e.g. by antivirus), Engram could leave a half-unpacked build that still looked "ready to install": the integrity check only validated the downloaded archive, never the unpacked files. Applying it would copy a broken build over your working install — in one case a build missing its TLS certificate bundle, which silently breaks every network request (update checks, TMDB, subtitle downloads). The updater now unpacks to a temporary location and only swaps it into place once the build is verified complete (against a per-release file manifest plus required-file sentinels), then re-checks completeness one more time immediately before applying — and if that final check fails it drops the staged update instead of leaving a dead "ready to install" offer. As extra safeguards the TLS certificate bundle is now always bundled, the build toolchain is pinned, and the release smoke test fails if a build can't complete an HTTPS request. (#296, #298) ## [0.14.0] - 2026-06-02 _Highlights: a one-click "Did you mean?" candidate picker for discs that share a name with another show (for example the 2023 **Frasier** vs the 1993 original) — pick the right show in the Re-Identify dialog without re-typing a TMDB search — plus matching fixes so a re-identified revival's episodes match and file correctly instead of being shunted to Extras or matched against the wrong show's subtitles._ ### Added - **One-click "Did you mean?" candidate picker when re-identifying a same-name disc** — when a disc is flagged for a same-name collision (for example a 2023 **Frasier** disc that was identified as the 1993 original), the Re-Identify dialog now shows the matching shows as quick-pick buttons. One click on _Frasier (2023)_ re-identifies the disc with the correct show, instead of having to re-type a TMDB search to find it. The free-text search remains as a fallback. (#291) ### Fixed - **Same-name shows could be silently identified as the wrong one** — a disc whose label has no year (e.g. `FRASIER_S1D1`) was matched to the more popular same-named show on TMDB, so a 2023 revival disc was treated as the 1993 original and every episode matched the wrong subtitles at random, landing in Review with an unhelpful "assign episodes manually" message. Engram now (1) flags a no-year disc that has a real same-name twin for review *before* ripping, suggesting which show to pick, and (2) as a backstop, when a whole TV disc matches no episodes at all and a same-name twin exists, surfaces a clear "this doesn't resemble *Show (year)* — did you mean *Show (other year)*? Re-identify to fix" review instead of the generic message — and it now reaches that review after a single full-coverage confirming match pass, instead of re-transcribing the disc three times over against the wrong show's subtitles first. Re-identifying to the correct show now reliably downloads that show's subtitles. (#287, #290) - **Re-identifying a same-name revival could still misfile its episodes as "extras"** — after correcting a no-year disc to the right show in Review (e.g. the 2023 **Frasier** revival), the length check that separates real episodes from bonus features still looked up expected episode runtimes for the *original* same-named show. The revival's episodes didn't match any of the wrong show's runtimes, so they were treated as bonus material, filed into `Extras/`, and never episode-matched. That runtime check now uses the show you re-identified to, so the correct episodes are matched instead of being shunted to `Extras/`. (#292) - **Two same-name shows shared one subtitle cache folder** — downloaded reference subtitles were stored on disk by show *name* (`…/cache/data/Frasier/`), so if both a 1993 and a 2023 *Frasier* were ever processed their episodes landed in the same folder and the matcher could read one show's subtitles while identifying the other. The runtime subtitle cache is now keyed by the show's TMDB id (`…/cache/data/3452/` vs `…/cache/data/195241/`), completing the same-name isolation already applied to the shipped reference cache — the two shows can no longer cross-contaminate. Existing name-keyed caches still work (a show with no resolved id falls back to its name) and no cache rebuild is required. (#288, #293) ## [0.13.2] - 2026-06-01 _Highlights: Engram can now tell apart two TV shows that share a name — for example **Frasier** (1993) and the 2023 revival. An ambiguous disc is sent to Review with both candidates to choose from, and once you pick one, that exact show drives subtitle download and episode matching instead of whichever same-named show happened to rank first._ ### Fixed - **A disc for a show that shares its name with another show could be mis-identified and silently fail to match** — Engram identified shows by *name*, so two different TMDB shows with the same title (for example **Frasier** from 1993 and the 2023 revival) were indistinguishable: it downloaded subtitles for, and matched against, whichever one ranked first, and a disc for the other one would score at the noise floor and land in Review with no clear reason. The resolved TMDB id is now carried as the authoritative show identity through subtitle download, episode matching, and the reference-corpus lookup. When a disc is genuinely ambiguous between two substantial same-name shows, it is routed to Review with both candidates so you can choose; your choice then drives subtitle re-download and matching, and a guard refuses a precomputed reference set that actually belongs to the other same-named show. No library, database, or cache rebuild is required. (#278) - **The fingerprint and AI episode-matching paths could still pick a same-name show by name** — the fingerprint-identification cascade and the AI (LLM) episode-matching fallback each looked the show up by name independently, so for a same-name collision they could fetch the wrong show's fingerprint data or AI context when those features are enabled. Both now use the known TMDB id when it is available, falling back to name lookup only when it is not. (#282) ## [0.13.1] - 2026-06-01 _Highlights: the in-app auto-update is fixed end to end on Windows — installed builds now actually show the **Restart now** button (it was hidden by a status flag that never reached the UI), and clicking it reliably swaps in the new version and relaunches instead of silently shutting Engram down._ ### Fixed - **The in-app "Restart now" update button never appeared on installed builds** — when a new version finished downloading in the background, the banner showed "ready to install — dev mode, manual download required" and hid the one-click restart button, even on real (frozen) installs that already had the update staged and ready. The build-type flag the button gates on was dropped from the live status push (it rode only the REST endpoint, not the WebSocket message), so the UI always read it as "dev mode." Installed builds now correctly show **Restart now**. The banner is also seeded from the authoritative status endpoint so it appears even if you open the dashboard after the update finished downloading, and stale downloaded versions are now pruned from `~/.engram/update/` instead of accumulating. - **Restarting to apply an update could shut Engram down without installing it (Windows)** — the helper that swaps in the new files after Engram exits was launched in a way that let Windows terminate it together with the closing app when Engram was running inside a process Job Object, so the app went down and the update was never applied. The helper now breaks away from the job (and uses a console-independent wait), so the restart reliably swaps in the new version and relaunches. ## [0.13.0] - 2026-06-01 _Highlights: a new opt-in AI episode-matching fallback for discs that have no reference subtitles — Engram transcribes the rip and matches it against the TMDB synopsis to suggest an episode in Review; the AI key and "AI-Powered Episode Matching" toggle now save and persist; and queued fingerprint contributions survive a sustained server outage instead of being permanently dropped._ ### Added - **AI episode-matching fallback when no subtitles are found** — for discs where no reference subtitles can be downloaded, the normal subtitle-based matcher has nothing to compare against. Engram can now transcribe the ripped file with on-device speech recognition (ASR) and match that transcript against each candidate episode's TMDB synopsis, surfacing a best-guess episode in the Review queue. It is opt-in via the **"AI-Powered Episode Matching"** setting and never auto-organizes — the suggestion is always presented for you to confirm. (#283) - **Force-delete a stuck fingerprint contribution** — a contribution retrying against a permanently unreachable server could stay queued indefinitely, and its in-flight guard blocked removal. The single-contribution delete now accepts a `force=true` option to retract such a row. It still refuses to delete anything already uploaded — use **Forget me** to recall data that has left your machine. (#280) ### Fixed - **The AI API key and "AI-Powered Episode Matching" toggle didn't persist** — the AI/Gemini API key field always rendered blank with no sign a key was saved, and the episode-matching toggle could not be enabled at all, because the underlying config field was missing from the settings API models and was silently dropped on save. Settings now shows a **"Key saved"** indicator once a key is stored, and the toggle persists across restarts. (#283) - **Queued fingerprint contributions were permanently dropped during a sustained server outage** — the uploader's retry cap was a lifetime cap, so a prolonged upstream outage (for example 503s during a bulk library bootstrap) could exhaust a contribution's attempts in a single drain and mark it permanently failed, with no automatic recovery. Transient errors (5xx, network, and rate-limit 429) now keep the contribution queued and retry it on later drains; only genuine permanent failures (4xx or undecodable data) are marked failed. (#279) ## [0.12.1] - 2026-05-31 _Highlights: fingerprint-network contributions now upload far faster — a backlog drains in back-to-back batches instead of trickling out an hour at a time, and rate-limited uploads are retried instead of dropped. Plus a LAN-access fix so a dual-stack host no longer rejects its own requests._ ### Changed - **Faster fingerprint-contribution uploads** — the contribution uploader now drains its queue in back-to-back batches instead of sending one batch and then sleeping an hour, so a backlog (for example right after a bulk library bootstrap) clears promptly rather than over many hours. Rate-limit responses (HTTP 429) are now treated as transient and retried — honoring the server's `Retry-After`, capped so a misbehaving server can't stall uploads — instead of silently dropping the contribution. The steady-state idle poll interval also dropped from 60 to 15 minutes for quicker pickup of newly ripped episodes. (#276) ### Fixed - **LAN access could reject the host's own requests on dual-stack (IPv6) binds** — with LAN access bound to all interfaces, the host's own loopback connection can arrive as the IPv4-mapped IPv6 address `::ffff:127.0.0.1`, which the localhost-only guard wrongly rejected with HTTP 403. Loopback is now classified via `ipaddress` (with an explicit IPv4-mapped fallback for Python < 3.13), so local requests are recognized correctly. (#273) ## [0.12.0] - 2026-05-30 _Highlights: a wider setup wizard with a dedicated Data Sharing tab and guided TMDB onboarding; the Import Watch Folder now handles libraries pointed straight at a show and flat folders with no season; and an ASR matcher fix that restores episode matches the new fingerprint-vector scale had started rejecting._ ### Added - **Data Sharing settings tab** — a dedicated tab in the setup/settings wizard now groups everything that sends data off your machine (the fingerprint network, AI assistance, and the gated TheDiscDB integration) in one place, separate from local Preferences. (#263) - **Guided TMDB onboarding** — the TMDB token field shows instructional text instead of a token-shaped placeholder, validates automatically when you leave the field (with inline ✓/✗ feedback), and first-run setup no longer advances past the TMDB step until you've entered a valid token or explicitly chosen to continue without one. (#243, #263) ### Changed - **Wider, collapsible config wizard** — the setup/settings modal is wider (800 → 1040px) and Preferences plus the new Data Sharing tab are grouped into collapsible sections, so the page starts compact and you expand only what you need. Inline action buttons that previously rendered as bare text ("Forget me", "Contribute from existing library") are fixed. (#263) ### Fixed - **Import Watch Folder missed shows it was pointed at directly** — pointing the watch folder straight at a single show's folder broke ingestion: `Season NN` subfolders sat one nesting level too shallow to be detected (jobs were mislabeled with no show or season), and flat folders with no season matched nothing because the matcher requires a season. Engram now recognizes `Season N` / `Season 01` folders when the watch root *is* the show, and searches every candidate season for flat imports so they match across all seasons. (#264) - **ASR episode matching rejected correct matches on known seasons** — a 30-second speech-recognition chunk scores a structurally low similarity against a full-episode reference vector, and the recent precomputed-vector migration lowered that scale further, so the old fixed similarity threshold rejected most correct chunks and returned no episode. Matching now uses a rank-and-margin vote (the top candidate must clear a low floor *and* lead the runner-up by a wide margin) and falls through to a full-file comparison when no chunk votes, restoring matches while still abstaining on out-of-corpus content. (#269) ## [0.11.0] - 2026-05-29 ### Added - **Bootstrap Library (bulk fingerprint upload)** — a one-pass tool that walks your existing organized TV library, extracts a Chromaprint acoustic fingerprint from every episode, and contributes them to the shared fingerprint network in bulk. Previously the network only grew as you ripped new discs; now shows you already own can seed matching immediately. Respects the same privacy model and opt-out as the per-rip contribution flow. (#253) - **Bundled `fpcalc`** — the Chromaprint `fpcalc` binary is now shipped inside the Windows, Linux, and macOS builds, so audio fingerprinting works out of the box with no manual Chromaprint install. Development builds fetch it on demand. (#260) - **Broadcast vs. DVD/streaming episode reordering** — episode organization now reconciles aired order with DVD/streaming order using TMDB episode groups, so shows that shipped in a different order than they aired land in the correct files. (#200, #254) - **Global episode-ordering default in the Config UI** — pick DVD or aired ordering as the library-wide default directly from Settings, instead of per-show only. (#255, #259) ### Fixed - **Startup crash `no such column: app_config.episode_ordering_preference`** — the pre-init LAN-address read queried `app_config` before the schema reconcilers ran, so a freshly migrated database crashed on launch. The read now tolerates schema drift. (#261) - **Bulk fingerprint upload silently skipped episodes with certain codecs** — the bundled Chromaprint 1.5.1 `fpcalc` can't decode DTS, TrueHD, FLAC, or E-AC-3 audio, so ~128 library episodes failed fingerprinting with no warning. Engram now falls back to an `ffmpeg` pre-decode so every track can be fingerprinted. (#261) - **"Queued contribution for title None" in the logs** — the show title is now persisted and logged, so bulk-upload progress is attributable per show. (#261) - **Dashboard not refreshing after an in-app update, and incorrect frozen-build detection** — the UI now reloads after applying an update and correctly identifies packaged builds. (#258) - **Long filenames and paths truncated in job history** — they now wrap instead of being cut off. (#256) - **Cramped MANUAL row in the review inspector** — split into two rows so the manual-assignment controls are readable. (#257) ## [0.10.0] - 2026-05-28 ### Added - **Audio fingerprint contribution network** — Engram now extracts a Chromaprint acoustic fingerprint from every confidently matched episode and (opt-out) contributes it to a shared fingerprint server so episode matching improves for everyone. Privacy-first by design: contributions are gated behind a one-time consent disclosure, identified only by a regenerable per-install pseudonym (no filenames, paths, or IP addresses are sent), and a **"Forget me"** action wipes both the remote record and the local queue. A localhost-only audit endpoint plus a `~/.engram/cache/contribution_log.jsonl` log show exactly what left the machine. Configure under Settings → Fingerprint; requires the `fpcalc` binary (auto-detected, with a validation endpoint). (#242, #244, #248) - **Bulk actions in the review queue** — select multiple review titles with checkboxes (with a select-all header and shift-click range selection) and apply one decision to all of them — **Mark as Extra · Discard · Skip · Re-Match** — then commit everything in a single Save. Built for box sets with dozens of unclassified extras that previously had to be cleared one click at a time. The batch commits in one organization pass, which also avoids the `FILE_EXISTS` collisions repeated single-title saves could hit. (#249) ### Fixed - **`/review` page rendered as a solid black screen in Safari and Firefox** — two compounding causes: the REVIEW nav tab linked to a bare `/review` route that didn't exist (React Router rendered nothing, exposing the near-black body background), and Safari composited the page to black where `mix-blend-mode` atmosphere layers bled through their stacking context. The nav now deep-links to the first review job (with a `/review` → dashboard safety redirect), an `isolation: isolate` boundary contains the blend layers, and **Firefox + WebKit are now part of the Playwright matrix** to catch engine-specific regressions. (#247) - **Movie extras showed the main feature's filename in job history** — bonus features were organized correctly on disk, but the history detail panel displayed the main movie's path for every extra. `organize_movie()` now returns a source→destination mapping so each extra shows its real `Extras/Extra N.mkv` path. (#245) - **Movie review decisions didn't record the organized path** — after confirming a movie in the review queue, `organized_from`/`organized_to` were left unset on the title (the same class of bug as the extras path issue, but in the human-review path), so job history showed no destination. Both fields are now set and broadcast on a successful review organize. (#246) - **Auto-flow finalize could mis-handle TV "extra" titles** — the automatic (non-review) TV finalize path passed the synthetic `"extra"` episode code straight to the episode organizer (which rejects it) and always cleared the `is_extra` flag, diverging from the review path. It now routes extras into the season's `Extras/` folder and preserves the flag, closing a latent inconsistency. (#250) ## [0.9.1] - 2026-05-27 ### Fixed - **Episodic TV with consistent audio never auto-organizing** — shows like *The Gilded Age* where every episode shares vocabulary (recurring characters, settings) produce a tight cosine-similarity band, so the old separation-based confidence formula scored 103/119 chunk votes at only 0.20 even though the winner was dominant. A new vote-ratio path fires when the winning episode has ≥ 3× the runner-up's chunk count and ≥ 50% consensus; it scores the match from the vote ratio directly, and the final confidence is `max(path1, path2)`. Gilded Age S03 now auto-organizes at 0.95+. (#239) - **TrackGrid showed "no confident match" even when a strong best-guess existed** — REVIEW-state tracks displayed the static string *"no confident match — assign in review queue"* regardless of whether the matcher had a high-confidence episode stored in `matched_episode`. The display is now conditional: if a best guess is available it shows *"best guess S02E04 — 56% confidence (10/25 votes) — confirm in review queue"*; otherwise the no-match message is preserved. (#239) - **Premature title-complete callback during ripping write pauses** — a brief MakeMKV write pause (buffering or disc seek) could hold a file's size constant for one 3-second polling interval, causing `_check_for_completed_files()` to fire at e.g. 96 MB of a 2 193 MB file. The title was immediately sent to MATCHING state while still being written. Now requires 3 consecutive stable polls (~9 s) before declaring a file complete mid-run; the force-complete path (called after `process.wait()`) bypasses the counter and fires once, at full size. (#237) - **Windows `.exe` shipped with PyInstaller's default feather icon** — the multi-resolution Synapse v2 `engram.ico` (16–256 px) was already generated by `npm run brand:export` and committed, but the PyInstaller spec was never updated to reference it. The spec now points at `frontend/public/brand/app-icons/windows/engram.ico` so the packaged binary carries the correct brand mark. (#238) ## [0.9.0] - 2026-05-26 ### Added - **Import Watch Folder** — point Engram at any folder of pre-ripped MKV files (ARM output, NAS share, etc.) and it ingests them through the same identification → matching → organizing pipeline as a freshly ripped disc. Configured in Settings → Import Watch Folder; the watcher polls on a configurable interval and skips files still being written. (#233) - **Auto-update** — Engram now checks for new releases at startup, silently downloads the update in the background, verifies the SHA256 checksum, and prompts with a banner to "Restart to apply." Updates can be skipped per-version. Works for frozen (packaged) builds on Windows, macOS, and Linux; no-ops in development mode. (#235) ### Fixed - **TVsubtitles show mislabeling** — the resolver previously took the first search hit without verifying the show name, so e.g. "2 Broke Girls" could be matched to Gilmore Girls, corrupting the cache. Results are now filtered by title similarity before accepting a match. Adds `audit_subtitle_cache.py` and `verify_cache_content.py` scripts for cache health checks. (#202) - **Docker image not published on release** — the `release.yml` dispatch in `tag-release.yml` was missing the `docker.yml` dispatch step, so Docker images were never pushed to GHCR on tagged releases. Also adds a manual `tag` input to `docker.yml` so a specific release can be re-published without re-running the full pipeline. (#231, #232) - **CI permission error on `tag-release.yml`** — the `actions: write` permission required to dispatch `release.yml` and `docker.yml` was missing, causing workflow dispatch to fail with a 403. (#230) ## [0.8.1] - 2026-05-26 ### Fixed - **Docker MakeMKV install failing on every container start** — the version-detection script scraped the MakeMKV download page for a Linux tarball link (`makemkv-bin-*.tar.gz`) that is no longer listed there; switched to the hash-file link (`makemkv-sha-*.txt`), which is present on every release and uses the same bare version format. Adds a `MAKEMKV_DETECT_ONLY=1` mode for CI verification and a nightly full-compile check workflow to catch future regressions. (#226) ## [0.8.0] - 2026-05-26 ### Added - **LLM episode matching (opt-in)** — when audio fingerprint matching can't confidently identify a TV episode, an LLM compares the cleaned transcript against the season's TMDB synopses and suggests an episode through the review queue. Supports Gemini, Anthropic, OpenAI, and OpenRouter providers (Gemini Flash-Lite recommended); shares the existing `ai_provider`/`ai_api_key` settings. Never auto-organizes — always requires user confirmation. (#109) - **Google Gemini provider** added to the AI provider list, usable by both AI title resolution and the new LLM episode matcher. - **Docker / Linux container support** — official Docker image with a single-volume design (`/config` holds the database, logs, caches, and HF models). MakeMKV is compiled from source on first start to avoid redistribution restrictions; the stored MakeMKV license key is automatically seeded into MakeMKV's `settings.conf`. `docker-compose.yml` and full documentation included. (#193) - **LAN access toggle** — opt-in setting in Preferences that binds the server to `0.0.0.0` so the dashboard is reachable from other devices on the local network. Settings panel shows the LAN URL, a copy button, and a QR code; a "restart to apply" notice appears until the socket is rebound. The `HOST` environment variable still takes precedence for Docker / headless deployments. (#211) ### Fixed - **TV extras tagging after organize failure** — if organizing an extras track failed (e.g. destination already exists), the `is_extra` flag was silently dropped and the UI showed the track as an ordinary completed episode instead of marking it with an EXTRA chip. (#224) - **Per-track deep re-match missing from inspector** — the v0.7 inspector redesign removed the per-track "Deep re-match" button for low-confidence titles; only the disc-level conflict re-match was preserved. Restored the per-track action and wired a `deep` flag through `RematchRequest` → `rematch_single_title` → matcher (stricter scan points and vote thresholds). (#224) - **Auto-escalation never fired for needs-review titles** — `_maybe_escalate_conflicts` only escalated episode collisions, not titles routed to REVIEW. Added `_maybe_escalate_reviews` on the same 10 → 25 → full-coverage ladder; separate pass counters for conflicts vs. reviews prevent them from clearing each other (which previously pinned the ladder at pass 1). (#224) - **Race condition on shared matcher temp files** — concurrent title threads writing `chunk_{start}_{dur}.wav` and `preprocessed_{stem}.wav` without a source-file disambiguator caused PyAV `InvalidDataError` when two threads sampled the same offset. Chunk and preprocessed paths now hash the canonical source path to keep them per-source. (#216) - **Stale precomputed-cache manifest entries** — when `manifest.json` claimed coverage for a show/season whose `.npz` was missing, the fallback warning fired once per title and the in-memory manifest was never corrected, causing repeated spurious warnings. Stale entries are now pruned from the manifest on detection. (#216) - **TF-IDF matcher reference-set reuse** — `TfidfMatcher` reused across calls with a different reference set (precomputed episode codes vs. scraped SRT paths) caused silent `KeyError` swallows that rejected every chunk. The matcher is rebuilt when its `reference_signature()` changes. (#216) - **Config dropdowns unreadable on Windows** — native `` elements with a new `EngramSelect` component built on Radix UI, which renders the popup as React DOM and respects theme tokens. ## [0.7.3] - 2026-05-25 ### Fixed - **Ripping progress detection**: MakeMKV robot-mode output (`PRGC`/`PRGV`) was misread — the leading field is a message code (not a title index) and progress is `value/65536` (not `current/total`) — producing phantom "title 5018 is ripping" states and >100% per-title progress bars. The filesystem monitor (output-file sizes) is now the single source of per-title and overall progress; the stall-watchdog heartbeat is also fed from it (#209). - **Redundant disc re-scans during ripping**: each title previously triggered its own `makemkvcon` invocation, re-opening and re-scanning the whole disc every time. Ripping now issues one `makemkvcon … all` pass for the full disc selection, falling back to individual re-rips only for any titles missing from that pass (#209). ### Changed - **macOS Intel build dropped**: `llvmlite` 0.46.0 (December 2025) no longer ships macOS x86_64 wheels; building from source fails on Python 3.13. The `engram-macos-x64` release artifact is removed. Intel Mac users should use `engram-macos-arm64.tar.gz`, which runs transparently on Intel Macs via Rosetta 2. - **Subtitle cache build speed**: seasons already covered on disk are skipped on each daily run — no TMDB, OpenSubtitles, or scraper calls — until a configurable freshness window (default 30 days) expires or `--refresh` forces a full re-harvest. Previously every season was re-attempted on each run regardless of prior coverage (#204). ## [0.7.2] - 2026-05-25 ### Fixed - **macOS frozen-build launch crash**: the packaged app now calls `multiprocessing.freeze_support()` before spawning workers, preventing an infinite fork-bomb on macOS where the spawn start method caused worker processes to re-execute the frozen entry point — opening endless browser windows and crashing immediately (#206). - **macOS Intel binary mislabeled as x64**: `macos-latest` GitHub Actions runner is Apple Silicon, so prior releases shipped an arm64 binary as `engram-macos-x64.tar.gz` (Intel Macs received "bad CPU type"). CI now builds on `macos-13` (x64) and `macos-14` (arm64) separately (#206). - **Python 3.14 incompatibility**: `requires-python` capped to `<3.14` as `onnxruntime` (via `faster-whisper`) has no cp314 wheel; backend Python pinned to 3.13 (#206). ### Added - **macOS Apple Silicon download**: `engram-macos-arm64.tar.gz` is now published as a dedicated release artifact for M1/M2/M3/M4 Macs (#206). ### Changed - **Hardened cross-platform smoke tests**: release builds assert binary architecture with `file` and a process-count guard (≤ 2 processes) catches re-spawn bugs headlessly; CI runs `uv sync` resolution across Python 3.11–3.13 on Ubuntu and macOS arm64 (#206). ## [0.7.1] - 2026-05-23 ### Fixed - **Frozen-build database upgrades**: the packaged app now drops columns removed from the model on startup, fixing a crash when inserting a disc (`NOT NULL constraint failed: disc_jobs.is_transcoding_enabled`) for users upgrading from a build that still had the removed "Enable transcoding" setting (#190). ## [0.7.0] - 2026-05-23 ### Added - **Pre-built subtitle cache**: ships a precomputed subtitle-vector cache so episode matching can run without scraping subtitle sites on every disc, falling back to live scraping only when a season isn't covered (#140). Cache builds are now resumable and log API status (#149), and the builder accepts a `--show-list` to target specific shows. - **Smarter episode matcher**: persistent on-disk caches plus a threaded provider scheduler and reworked subtitle providers (#155), a per-provider circuit breaker so a failing source no longer stalls a run, interpretable 0–1 confidence scores (#169), and automatic deep re-matching when episodes conflict (#171). Match results now surface which subtitle provider contributed (#158). - **Redesigned TV disc review**: an inspector-style layout with disc-level conflict detection, making it clearer which episodes clash before you commit (#165). - **Diagnostics improvements**: bug reports can be previewed before sending and now report real installed tool versions (#174). - **Resilient frontend**: API and WebSocket errors are handled gracefully with reconnection instead of breaking the dashboard (#180). - **Brand refresh**: the canonical Synapse v2 brand system (#156), plus an ambient ripping animation and a bottom-anchored status bar (#137). ### Fixed - **Ripping reliability**: the long-held database session in `_run_ripping` is now tightly scoped to avoid blocking other work (#185), and MakeMKV subprocesses are drained on shutdown alongside matching-lifecycle fixes (#181). - **Movies**: long bonus tracks are no longer incorrectly flagged as needing review (#175). - **Review flow**: the Process action returns to the dashboard instead of erroring (#173), and re-running a match re-matches all titles with live progress (#164). - **MakeMKV validation**: the real installed version is detected from the robot-mode banner (#177). - **Subtitle matching**: subtitle download is skipped when the precomputed cache already covers a season (#163); tvsubtitles episode resolution and candidate parsing were corrected (#159); UTF-16-encoded SRTs are now accepted; OpenSubtitles quota is reported accurately and skipped when exhausted. - **Logging**: corrected log-source attribution and now surfaces disc-event errors that were previously silent (#168). - **Security**: hardened SSRF and path-traversal sinks flagged by CodeQL (#147). ### Changed - **Subtitle cache format v2**: ~85% smaller on disk via a compact `uint16` encoding (#154). - **Documentation**: README reworked to be end-user-first with supporting docs consolidated (#162). - Codebase-wide simplification sweep for maintainability (#143). ### Removed - The unimplemented "Enable transcoding" setting (#138). - The obsolete skyline-silhouette atmosphere layer (#139). ## [0.6.0] - 2026-05-02 ### Added - **OpenSubtitles.com REST API**: subtitle downloads now use the official `opensubtitlescom` REST API as the primary path (batch-downloads a whole season in one search call). Addic7ed and OpenSubtitles.org web scrapers remain as per-episode fallbacks. Configure API key, username, and password in Settings → TMDB & Subtitles. - **Disc name identification via MakeMKV CINFO codes**: extractor now captures the disc display name from `CINFO:2` (e.g. `"Star Trek: Strange New Worlds - Season 3 (Disc 1)"`). When the volume label produces a failed TMDB lookup, the disc name is parsed and tried as a second-chance TMDB query — silently resolving merged-word labels like `STRANGENEWWORLDS_SEASON3` without any user prompt. - **TMDB-failure review gate**: if both the volume label and disc name fail TMDB lookup for a TV show, the job now enters `REVIEW_NEEDED` state with the garbled name pre-filled in the correction modal (previously the job would silently start ripping with a wrong title). - **NamePromptModal pre-fill**: when a job enters review due to an unreadable or merged-word label, the modal opens with `detected_title`, content type, and season number pre-populated — the user only needs to correct the show name. - **Disc analyst static method** `_parse_disc_name()`: parses `"Show Title - Season N (Disc N)"` MakeMKV format into `(title, season)` tuple. - 14 new unit tests in `tests/unit/test_disc_name_identification.py` covering extractor CINFO parsing, analyst disc-name parsing, identification coordinator fallback logic, and review gate behavior. ### Fixed - **CINFO vs DINFO**: extractor was reading `DINFO:6` (which doesn't exist in MakeMKV robot-mode output) instead of `CINFO:2`. This meant the disc display name was never captured, so the TMDB disc-name fallback never fired for any disc. - **Scraper timeouts**: Addic7ed and OpenSubtitles.org request timeouts reduced from 30 s to 8 s so failures are fast when those sites block requests. - **Simulation service**: `insert_disc_from_staging` no longer crashes when `staging_path` contains paths with non-standard separators. ### Changed - Subtitle download strategy: OpenSubtitles.com REST API is tried first (entire season at once); only falls back to per-episode scraping if credentials are absent or the API call fails. - `SRT` validation (`is_valid_srt_file`) now deletes and re-downloads cached files that contain HTML (Cloudflare challenge pages) rather than surfacing them as valid subtitles. - `opensubtitlescom>=0.1.0` added to backend dependencies. ## [0.5.0] - 2026-04-05 ### Changed - **JobManager decomposition**: broke up the 4,295-line `JobManager` (52 methods) into 5 focused coordinators + thin orchestrator (#58) - `IdentificationCoordinator` — disc scanning, DiscDB/TMDB/AI classification - `MatchingCoordinator` — episode matching, subtitles, file readiness - `FinalizationCoordinator` — conflict resolution, organization, review workflow - `CleanupService` — staging cleanup, timed cleanup, DiscDB export - `SimulationService` — all simulation methods for E2E testing - `JobManager` reduced from 4,295 to 1,166 lines - **Alembic for database migrations**: replaced custom `_migrate_schema()` with Alembic for versioned, reversible migrations; existing databases auto-stamped on first startup (#58) - **CORS origins configurable**: read from `CORS_ORIGINS` env var (via `Settings` model) instead of hardcoded localhost (#58) ### Added - **WebSocket heartbeat**: server sends ping every 30s to detect and clean up stale connections (#58) - **Accessibility improvements**: ARIA attributes and keyboard handlers on DiscCard, ReviewQueue, ConfigWizard, NamePromptModal (#58) ### Fixed - **Memory leak**: `_episode_runtimes` and `_discdb_mappings` per-job caches now cleared on job completion/failure (#58) - **Blocking event loop**: `DiscAnalyst` config loading switched from sync DB call to async preloading in async contexts (#58) - **Sync engine churn**: `get_config_sync()` now caches the sync SQLAlchemy engine instead of creating one per call (#58) - **O(n²) loop**: `has_selection` check in `_run_ripping` hoisted out of inner loop (#58) - **Heartbeat deadlock risk**: heartbeat closes socket directly instead of calling `disconnect()` to avoid lock contention with `broadcast()` (#58) ### Removed - Unused frontend dependencies: `@mui/material`, `@mui/icons-material`, `@emotion/react`, `@emotion/styled`, `react-router` v7 (#58) ## [0.4.5] - 2026-04-04 ### Fixed - **Multi-drive cancel isolation**: canceling one drive's rip no longer kills another drive's rip — `MakeMKVExtractor` now tracks processes per job (#64) - **Elapsed time 1-hour offset**: replaced deprecated `datetime.utcnow()` with `datetime.now(UTC)` across all backend files; frontend appends `Z` suffix to naive timestamps (#61) - **Catalog-number volume labels**: labels like `BBCDVD1550` are now detected as publisher catalog codes and trigger the name prompt when TMDB/DiscDB lookups fail (#62) ### Added - **Season selector in episode review**: users can now pick season S01–S20 in the TV review UI instead of being locked to the auto-detected season (#63) - 5 new multi-drive integration tests: concurrent ripping, cancel isolation, drive removal isolation, mixed content, dual identification (#65) - Catalog number detection unit tests ### Changed - Bumped GitHub Actions: `actions/setup-node` v4→v6, `astral-sh/setup-uv` v4→v7, `actions/setup-python` v5→v6 ## [0.1.9] - 2026-02-22 ### Fixed - Discs with generic Windows volume labels (e.g. `LOGICAL_VOLUME_ID`, `VIDEO_TS`, `BDMV`) no longer produce spurious TMDB search results and wrong detected titles - TMDB name overrides are now guarded by a Jaccard word-token similarity check (≥ 35%); completely unrelated TMDB matches are discarded and the parsed disc name is preserved - Jobs where the disc name cannot be detected now enter `REVIEW_NEEDED` state instead of attempting to rip with an unknown title ### Added - **Name Prompt Modal**: when a disc label is unreadable, a cyberpunk-styled modal prompts the user to enter the title, media type (TV/Movie), and season number before ripping begins - `POST /api/jobs/{job_id}/set-name` endpoint to resume a stalled job after the user provides a name and content type - `review_reason` field on `DiscJob` model to communicate why a job entered review state (SQLite migration: `ALTER TABLE disc_jobs ADD COLUMN review_reason TEXT`) - `backend/scripts/migrate_db.py` utility script for applying future schema migrations to an existing database - 9 new unit tests covering generic label detection and TMDB similarity guard ## [0.1.8] - 2026-02-22 ### Fixed - CI/CD failures: formatting, lock file sync, and cross-platform test compatibility ## [0.1.7] - 2026-02-22 ### Fixed - TMDB classifier bug causing incorrect content type detection ## [0.1.6] - 2026-02-22 ### Fixed - Multiple tracks showing RIPPING state simultaneously - Per-track ripping progress stuck at 0% during real disc rips - Movie review workflow, config wizard key visibility, and review page overhaul