2.102.2
Changed
- An audio cue template shows Play, Threshold and a More menu on one line. Export, Promote to network, Change type, Optimize window and Delete are in the menu. Before, the seven buttons wrapped unevenly, and Optimize window and Delete sat apart at the right.
- The Threshold button of a cue template always reads "Threshold". A set value is shown as a line of text under the cue's details.
- The enable checkbox of a cue template lines up with its name.
Fixed
- Add audio to pattern also finds a pattern that matched inside a merged ad break. A stored marker names only its lead pattern, and the other patterns that matched inside it are kept as members with their own spans. The lookup now accepts a member and builds the audio from that member's span. A text pattern member records its pattern from this version on.
- Add audio to pattern searches the transcripts of retained episodes in the pattern's scope when no ad marker names the pattern. Episodes processed by an earlier version do not need reprocessing. The audio runs from the pattern's opening words to its closing words. A span that overlaps a rejected ad, or whose length does not fit the pattern, is not offered.
2.102.1
Added
- The Patterns page has an Audio filter beside Category, with All, Has audio, Audio on, Audio off and No audio. The choice is kept in the URL as
audio.
Changed
- The Audio column and the pattern cards say "No audio" for a pattern with no audio fingerprint and "No matches yet" for a fingerprint that has not matched. Both showed "-" before.
- The actions of an audio cue template (Play, Export, Change type, Threshold and Optimize window) are drawn as buttons. The Play icon no longer touches its label, and Delete stays on the same line as Optimize window.
- Outline buttons such as Delete and Refresh are the same height as the buttons beside them. They were 2 px taller.
Fixed
- Add audio to pattern finds a source for more patterns. It now also looks in retained episodes where the pattern's own ad marker was accepted, within the pattern's scope, and it resolves the origin episode of global and network patterns. Before, it was offered only for the origin episode of a podcast-scope pattern or for an episode with a correction, so a pattern matched by text alone could not get audio.
- Pattern Cleanup trims and splits, and the Split action in the pattern dialog, update the audio of patterns whose fingerprint was stored before 2.102.0. Those fingerprints have no recorded source, so the audio step was skipped without notice: a split left the new patterns with no audio and a trim kept the old span. A retained match inside the pattern's scope is now used as the source. When no source is retained, the response reports
fingerprintStored: falsewithoriginal_not_retained, and the Cleanup tab and the split dialog say that the pattern's audio was not updated. - A link to a Settings section opens that section, scrolls to it and moves focus there. The "Settings > Experiments" link on the Cleanup tab opens Pattern cleanup, and the YouTube link on Add feed now works. Before, only two sections responded to such links.
2.102.0
Breaking
- Patterns with network scope now match. The detector never told the pattern matcher which network a feed belongs to, so a network-scope pattern never matched, by text or by audio. It now matches on every feed of its network, using the feed's network override when one is set. If you have network-scope patterns, review them on the Patterns page: they begin cutting on other feeds of the same network after this upgrade. Changing a pattern's scope to network now also records the network it applies to.
PUT /api/v1/patterns/{id}rejects the change with a 400 when the pattern has no feed network and nonetworkIdis sent, and an automatic promotion to network scope is skipped for a pattern that has no network. - A path under
/api/that no route matches now answers 404 with a JSON body, and a method the path does not accept answers 405 with a JSON body and anAllowheader. Before, both returned the web app's HTML page. API clients that parsed an HTML 200 or 404 for a wrong path need to handle the JSON error instead. Paths outside/api/are unchanged. - Audio fingerprint matching now follows the pattern's scope. Before this release every stored fingerprint was matched in every feed, whatever the pattern's scope. Now a podcast-scope pattern's audio matches only in its own podcast, a network-scope pattern's in feeds of its network, and a global pattern's everywhere. An ad that was being cut on other feeds through audio alone stops being cut there. The pattern dialog's Audio tab says how many past matches came from episodes outside the pattern's scope; widen the pattern's scope to keep cutting on those feeds.
Added
- An LLM is optional. Settings > LLM provider has a new None choice, also available as
LLM_PROVIDER=noneand asllmProvideronPUT /api/v1/settings/ad-detection. It needs no API key, base URL or model, and saved keys and models are kept for when you switch back. Episodes still transcribe and run audio cues, audio fingerprints, text patterns and the cross-fetch differential. They publish as a normal completed run that is not degraded and does not count toward the retry limit. SponsorBlock still applies on YouTube feeds. Nothing calls a model. LLM detection, the verification pass, the ad reviewer, the pre-roll and post-roll heuristics, chapter generation, Pattern Cleanup, reviewer calibration, startup and failover probes and the price catalog refresh are all off, including phases assigned to the secondary provider. A Pattern Cleanup run, a reviewer calibration or a chapter regeneration that is running when you switch to None stops without recording an error. Publisher chapters and ad chapters are kept, and a chapters mode ofgeneratebehaves likeauto. In Settings the model-only sections stay visible and disabled with a note, andGET /api/v1/settingsreportsllmEnabled.POST /api/v1/feeds/{slug}/episodes/{id}/regenerate-chapters,POST /api/v1/patterns/cleanup/runandPOST /api/v1/system/model-pricing/refreshanswer 409 with the codellm_disabled. An episode with no detections publishes uncut, or waits for review when manual approval is on. A regular podcast feed cuts a new ad only after you mark it once, so on a fresh install start with Add ad. Retry ad detection and Reprocess still work and apply any pattern added since.nonecannot be the secondary or failover provider. See Running without an LLM (#994). - Each pattern's audio is now visible and controllable. The Patterns table has an Audio column ("91% match", "Audio off", "No source audio" or "-"). The pattern dialog has an Audio tab beside Details, with an on/off switch, Play, the number of matches, mean match, "Not this ad" count and last match, Edit edges and Delete audio. Below it, a Recent matches list (10 per page) shows each match's episode, position, similarity, how much of the ad it covered and whether only audio found it. Not this ad on a match sends the existing reject correction for that episode and takes the match out of the pattern's counts. Edit edges opens the source episode with the fingerprint's span as the only region and rebuilds the fingerprint from the new edges without touching the episode's own markers and cuts. Turning audio off stops audio matching only; text matching and the pattern stay as they are. Play, Edit edges and rebuilds need a retained original; without one the tab says "Source audio expired", and the fingerprint keeps matching. Audio stored by earlier versions has no recorded source, so Edit edges starts from the pattern's own marker and records the source when you save. A pattern with no audio shows Add audio to pattern when an episode still has both its original audio and the pattern's marker. The origin episode is checked first, then up to 5 episodes that have a correction for the pattern. Otherwise the button is disabled and the tab says no retained episode has the ad. A text-pattern detection on the Detected Ads tab and the episode page also shows Add audio to pattern. The routes are
GET/PUT/DELETE /api/v1/patterns/{id}/audio,POST /api/v1/patterns/{id}/audio/rebuild,GET /api/v1/patterns/{id}/audio/matchesandGET /api/v1/patterns/{id}/audio/preview. - Audio fingerprints are now stored from more places, with any LLM provider. Add ad, a confirm correction and an adjust correction store a fingerprint from the retained original (Add ad and a create correction fingerprint each sponsor over its own span; confirm only when the pattern has none; adjust rebuilds it). The response of the corrections route gains
fingerprintStoredandfingerprintSkippedReason. Audio that is too plain to ever match, such as silence or a steady tone, is refused with the reasonnot_distinctiveinstead of being stored and never matching. Once you delete a pattern's audio, automatic detection does not create it again. A split, by hand or in Pattern Cleanup, gives each new pattern its own fingerprint cut from the original's source episode. A Cleanup trim rebuilds the fingerprint from the trimmed text. Both need the episode's original audio retained and the pattern's audio on. - Settings > Audio fingerprinting has five settings: Enable audio fingerprinting (on by default), Match strictness (65%, allowed 65% to 95%), Minimum coverage (50%, 10% to 100%), Gap tolerance (3.5 s, 0 to 15 s) and Minimum ad length (5 s, 3 to 30 s). A value outside its range is rejected with a 400. Changes apply to later runs and Re-detect ads and never re-fingerprint stored ads. The section stays enabled when the provider is None. API:
fingerprintEnabled,fingerprintMatchStrictness,fingerprintMinCoverage,fingerprintGapToleranceSandfingerprintMinAdLengthSonPUT /api/v1/settings/ad-detection. - Stats has an Audio matching section that follows the date range and podcast filter, with four tiles: Patterns with audio (of the total), Audio matches, Found by audio only and Not this ad. API:
GET /api/v1/stats/audio-matching. - On the first start after the upgrade, match history is filled in from the markers still stored on episodes, so existing fingerprints show their past matches. The fill-in deletes nothing. Matches an older version made outside a pattern's scope are flagged, and the pattern's Audio tab says how many came from episodes outside its scope, so you can widen the scope on purpose (see Breaking). Markers that have been removed from episodes cannot be recovered, so the history is partial. A match recorded without similarity or coverage shows "-" for them. The step runs in the background after startup, so startup does not wait for it. While it runs, the pattern's Audio tab and the Stats Audio matching section say "Match history is still being filled in.", and the API reports
historyBackfillPending: trueon the pattern audio detail and onGET /api/v1/stats/audio-matching. A run that fails is retried after an hour. Match history is now deleted together with its episode, and rows for episodes that no longer exist are removed once on upgrade. On a synthetic database of 50,000 episodes and 300,000 stored error rows, on one 8-core machine, both background upgrade steps finished in about 20 seconds. Startup spent 0.02 s registering them, and no write held the database lock for more than 44 ms. Other hardware will differ. - Waveform peaks are cached. The ad, cue and split editors used to decode the whole retained original on every open. The peaks now live in a small file next to the retained original. The file is written during processing from a decode the splice detector already does, and an editor reads it when it opens. An episode processed before the upgrade builds its file on its first editor open, and episodes longer than 4 hours, which had no waveform before, now have one. For an original longer than 45 minutes the file is built in the background on first open. The route answers 503 with
Retry-Afteruntil it is ready, and the editor shows "Loading waveform..." and retries. For a shorter one the file is built inside the request. A replaced original is detected by its size and modification time, and its peaks are rebuilt. The file is removed with its original. The split editor now shows "Could not load waveform" like the other two editors. Credit for the design goes to Punkamonk (#995). - Settings > Security has Session length (hours) (24 by default, 1 to 8760) and Remember me length (days) (30 by default, 1 to 365), with a Save button for the two fields. The sign-in page has a Remember me on this device checkbox, unticked by default, that gives that device the longer session. Both lengths count from the session's last use, and a change applies to existing sessions on their next request with no restart. They are also read from
SESSION_LIFETIME_HOURSandSESSION_REMEMBER_DAYSon first start, and a value saved in Settings wins from then on. Both compose files now pass the two variables.POST /api/v1/auth/loginacceptsrememberMe. When sign-in is refused with a 429, whether by the rate limit or the failed-login lockout, the sign-in page says "Too many sign-in attempts. Try again in N minutes." instead of "Invalid password" (#996). - Processing order for queued episodes. Queue > Queue control has Processing order, and each feed's settings have Processing order beside Queue priority, with As queued, Oldest first and Newest first (the feed also has Use default). Oldest first and Newest first follow the publish date, with undated episodes last, so a back catalog added in one go can be processed from episode 1. The order applies within a feed among episodes of the same priority, so priorities, boosts and other feeds are not affected, and a change re-sorts episodes already waiting. The default is As queued, which is the old behavior. API:
queueOrderonPUT /api/v1/settings/ad-detectionand onPATCH /api/v1/feeds/{slug}. On the first start after the upgrade, queued episodes whose publish date is still stored in the feed's raw format are converted to ISO 8601 once (#997). POST /api/v1/system/database/checkpointaccepts a JSON body{"mode": "passive" | "truncate"}(passive by default) and answers 503 when SQLite fails to run the checkpoint.
Changed
- Audio fingerprint matching is reworked. The matcher now measures the whole ad. For each stored fingerprint in scope it finds where the ad lines up in the episode and reports the measured start and end, similarity and coverage. The old matcher compared the first 10 seconds and assumed the end from the stored length. A short dropout inside an ad is bridged, and a longer damaged stretch splits the match into pieces that each have to pass on their own. The cut now spans the measured match. Ads that start between the old scan steps are found, and unrelated audio that agrees for a few seconds is rejected by evidence checks. A match must also agree clearly better than the same stretch shifted a few seconds either way, which stops an ad that shares a music bed with the show from matching elsewhere in the episode. The old per-window fallback scan is removed. A run below the minimum coverage still counts as a partial match when it holds at least 20 seconds of matching evidence, and plain or repetitive audio counts for less. One ad from a stored break of several ads is now found on its own and only that ad is cut. A short shared tagline or disclaimer is still rejected, and a partial match never promotes the pattern's scope. If fingerprinting the whole episode fails, the episode logs one warning and continues without audio matches. The matcher has limits on heavily compressed or damaged audio, described in How It Works.
- Backend code cleanup: unused code and code used only by tests are removed, duplicated code is merged and long comments are shortened. No change in behavior is intended.
- Every rate-limited 429 from the API carries a whole-second
Retry-Afterheader andretryAfterSeconds, and rate-limited responses on routes outside the API, such as feeds, carryRetry-After. The web app retries a rate-limited read only when the wait is 5 seconds or less. GET /api/v1/settingsreportsminandmaxon the four audio fingerprint numbers and the two session lengths,stepon the fingerprint numbers, andoptionsonqueueOrder. The Settings page reads its limits from there. Configuration import rejects an out-of-range audio fingerprint or session value with a message naming the setting, for examplefingerprint_min_coverage must be a number between 0.1 and 1.PATCH /api/v1/feeds/{slug}trimsnetworkIdOverride, treats a blank value as clearing it, and answers 400 when a changed value is not a string of at most 100 characters. Existing longer values keep working. Network ids already stored on patterns and feed network overrides are trimmed once at startup, and a blank one becomes unset.- The health probes
/api/v1/health/liveand/api/v1/healthnever set cookies and keep answering when a settings read fails. - Header navigation links are 44 px tall on touch screens at wide widths.
- A signed-in session now has an idle limit. A session that was last used longer ago than the session length signs in again once after this upgrade, and a changed length applies to existing sessions. Signing out clears the session in this browser only, and a copied cookie stays usable until its idle limit. Changing the password signs out every device and every copy of a cookie.
- Deduplicating patterns moves a removed duplicate's audio fingerprint and its match history to the pattern that is kept, when the kept one is active, has no fingerprint of its own and its audio was not deleted.
- A preview play button now has the same height as the buttons beside it, in the pattern dialog, the Detected Ads rows, episode marker rows, the split dialog and the cue sections. On touch screens the play button fills its 44 px target.
- The Patterns and Sponsors pages show cards instead of a table below 1280 px wide, so nothing is clipped or scrolls sideways on phones and tablets. The cards have a Sort by control with the table's sort fields, and sponsor cards now show the category and created date. From 1280 px the Patterns table always shows the Created column.
- Settings > Security lists Agents that skip processing in a table with a Remove button on each row, like the authenticated feeds table.
- A dropdown and a text box in the same row are now the same height in every browser: 40 px with a mouse and 44 px on touch screens. Dropdowns draw their own arrow in place of the browser's.
- Parakeet no longer checks the network on each model load. Once the model files are present, a load reads the local copy. At most once every 7 days a load starts a background check for a newer revision and never waits for it. A changed revision downloads into a staging directory and replaces the local copy only when every file is present, and the new revision is used from the next load. A failed check keeps the local copy. Updates are off when the model directory is a mount point or a symlink, or the volume does not support file locks; Transcription lists the cases.
- The reviewer prompt no longer says fingerprint spans are projected lengths, because they are now measured. A stored
review_promptthat still contains the old sentence has that sentence replaced once at startup. A custom prompt is otherwise left alone, and a per-feed reviewer prompt override keeps its own wording. The reviewer also treats a fingerprint match as measured over the run that matched. - A feed source URL that still holds whitespace or a control character after trimming is rejected with a 400 on
POST /api/v1/feedsandPATCH /api/v1/feeds/{slug}, and listed underfailedby OPML import. Configuration import encodes such characters instead of refusing the backup. LOG_FORMATandLOG_LEVELare now passed through by both compose files (defaultstextandINFO) and listed in.env.example. WithLOG_FORMAT=json, gunicorn's own access, worker and error lines are JSON too, and every line carries alevelfield for a log shipper to read.- Quieter logs: the startup note that rate limits count per worker is INFO instead of WARNING, "Suppressed N upstream item(s)" logs at INFO and only when a feed's count changes, "Skipping artwork retry" logs once per backoff window per URL,
filelockdebug lines and the onnxruntime session-creation warnings no longer appear on each model load, and the GPU transcription runs on is logged once at first use with its index, name, PCI bus id and total memory.
Fixed
- Page tabs and section links wrap on narrow screens, including Patterns and Stats. Pattern details can be opened from the keyboard in both cards and tables.
- Repeated queue claims preserve other feeds' positions when a feed uses Oldest first or Newest first.
- Off-grid waveform requests stream decoded audio in bounded chunks and enforce the duration and output-size limits.
- Edit edges can use a retained match after the fingerprint's original source expires. Saving new edges clears the loaded preview so Play uses the rebuilt clip.
- A failed Delete audio request keeps its confirmation open with the error and a retry action.
- Credentials no longer reach logs, stored errors or API responses. A failed upstream fetch put the full URL, including a query string such as
key=..., in the exception text that followed the logged URL. Query strings, URL credentials and feed keys are now removed from every log line and traceback. They are also removed from error text before it is saved, returned by the API or sent in a webhook, and webhook and email alerts remove them fromerror_message,reasonandfeed_url. Error text stored by earlier versions is cleaned by a background step after startup; the original query strings in old error rows are not kept. Error text is also redacted whenever the API, the history CSV and JSON exports, webhooks or email read it, so old rows never leave unredacted. That covers the run history on the episode page and in Stats, degraded-detection reasons and feed refresh errors.LOG_DOWNLOAD_QUERY=truestill keeps the query on the download URL lines it covers. - The upstream chapters and transcript fetches retry once with the feed's user agent when a host answers 403, or use the user agent the audio download settled on, as the audio download already did. Before, they gave up on the first 403.
- The database write-ahead log stays bounded. A WAL file that grew once never shrank. Each connection now limits it to 64 MiB, and the maintenance tick runs a truncating checkpoint when no episode is processing and no backup is running, then tries again later if a reader is in the way.
- Restarting no longer rebuilds the whole search index. The time of the last full rebuild is kept in the database and the weekly rebuild keeps its schedule. The first start after this upgrade still runs one full rebuild. Retired search-index tables are emptied in short transactions before they are dropped, so startup no longer holds a long write lock for them.
- The Recents feed query uses a new index on processed status and publish date, created at startup, instead of scanning the episodes table.
- The pattern audio preview cache is capped at 64 MiB and drops expired slices. A rebuild span with an out-of-range number is rejected with a 400.
- The start of the waveform for AAC and low-bitrate MP3 is no longer drawn flat, because the decode no longer seeks to 0 and drops the decoder's lead-in.
- Image builds no longer fail on a glibc version mismatch. The patched glibc is dropped because Ubuntu's glibc 2.43-2ubuntu2.7 includes the fix for CVE-2026-18374. The image build and the image check now fail on an older glibc.
- Expired failed sign-in records are now removed by the maintenance task. Before, they stayed in the database until that address signed in.
- The From and Until boxes of the processing schedule no longer run past the edge of their card on an iPhone. Each is at most 128 px wide.
- Summary tiles keep their numbers on one line across a row when a label wraps to two lines, on Stats, Patterns, History, the feed page and the pattern dialog.
- In Audio cue templates, Delete stays at the right end of a template's actions and no longer wraps onto a line of its own.
GET /api/v1/stats/addressingno longer reads every episode's transcript to compute snap distances. The sums are kept per episode and cleared when the episode's markers change. On a 3 GB database the request went from 7.5 s to under 0.1 s with a cold cache. The first request after the upgrade is still slow once.