2.97.38
Security
- Bumped pyjwt to 2.15.1 to clear CVE-2026-101917 and CVE-2026-102265 through CVE-2026-102274 reported against 2.13.0. No call-site changes.
- The CI pip-audit ignores for PYSEC-2025-183 (pyjwt) and PYSEC-2024-277 (joblib) are removed. Neither advisory matches the current pins.
2.97.37
Fixed
- The replacement audio is now resampled and reformatted to the episode's sample rate and channel layout before it is joined to the episode. The filler branch no longer depends on the conversion ffmpeg would insert on its own. Fixes the libmp3lame "inadequate AVFrame plane padding" failure on ffmpeg 9 (#796) and applies to uploaded replacement audio as well as the shipped clip. The encoder also receives full-size frames, so a short trailing frame cannot trip the check.
- The local Whisper packages (faster-whisper, ctranslate2) are optional when WHISPER_BACKEND is openai-api. Selecting the local backend without them fails with an actionable error instead of a generic transcription failure, and the system status reports the missing packages (#795).
- The pass-2 reviewer now receives the same hard protection barriers as pass 1, so an adjustment that reaches into kept audio is clamped instead of held.
- Pass-2 validation uses the once-per-run false-positive snapshot; the pass-2 failure path records an outcome on held and kept markers.
- Reviewer rejects are render barriers in the full run as well as on recut.
- A user confirm inside a reviewer-rejected span is cut on the recut as well as the full run; the rest of the rejected span stays a barrier on both.
- Cross-fetch probe windows are clamped to the refetch file, and blocks at the file edges are probed at the edge offsets, so a pre-roll or post-roll present in only one fetch is classified correctly.
- The long-window sponsor gate requires commercial context for description sponsors, as it does for registry brands. The content-extension start walk checks the segment that straddles the ad start. A word-timed end edge is trimmed to the return-to-show cue, like the start edge.
- The correction origin backfill maps the oldest hold snippet to a valid hold reason and repairs rows written with the invalid value.
- An estimated pattern remainder the audio analysis measures as silence (dead air between an ad and the show) is now cut with the ad instead of held for review. The pattern match is enough evidence for silence. Absorbed silence does not count toward the ad duration limits or the per-feed cap, and the reviewer does not trim it back.
Changed
- Shared helpers replace duplicated interval merging, carving, pass-2 hold handling, end-edge inheritance and sponsor gate rules. Validator registry lookups, pattern outro checks, render probes and the detections listing do less repeated work. Marker fields already returned by the API are now documented.
- The long-window sponsor gate and the validator share one registry rule: the best-supported brand with at least two mentions and commercial context confirms the sponsor.
2.97.36
Fixed
- A feed body cut inside a CDATA section is now rejected as truncated instead of being accepted as a partial document. The stored episodes are kept and the refresh retries, as it already does for a body that ends mid-element.
Changed
- The identity retry that follows a gzip decode failure logs the body size and the length and encoding headers it received, so a repeatedly short body can be traced to the transport.
2.97.35
Fixed
- A recut no longer rewrites a split piece to another piece's bounds. A split records the first piece as a boundary adjustment over the original span. Recuts applied it to the longest piece instead, which then stopped being cut. A recut now applies an adjustment to the marker nearest its new bounds. If several markers sit under the old span and none touch the new one, it is skipped. Reported in #794.
2.97.34
Fixed
- When a pass-2 finding overlaps a pass-1 hold, the parts outside the hold are no longer discarded. They go through validation and review like any other pass-2 finding. The hold itself is still decided on the full finding. An outside part can now be cut while an estimated-pattern hold next to it stays pending, so an episode may show a partial cut beside a held marker.
- A hold with several supported reads inside it can now have each read reviewed and released separately. Before, only one span per hold was reviewed.
- Ad validation no longer auto-rejects ads whose reason contains a word ending in "no" before "ad" or "sponsor", such as "Casino ad" being read as "no ad". Contributed in #793, fixes #792.
- Pending holds now block gap merges and the end-of-file extension in the pass-1 render and in recuts. A cut before a pending hold no longer runs through the hold to the end of the file. The held audio stays in until it is reviewed.
Added
- Every pass-2 span now ends with one logged outcome: cut, held, kept, rejected, covered or dropped, with the reason and its bounds in original time. The stored run stats count each outcome; the API does not return those counts.
- Confirms filed by pass-2 auto-approval now record the hold they released (
hold_id). A hold gets its id when first held. Holds saved before this release get one derived from their bounds and reason when loaded, so repeated loads agree. The id survives a recut and other paths that start from stored markers, so releases of one hold stay grouped even if its edges move. A full re-detect creates new holds with new ids. Confirms filed against an earlier hold still group by that hold's id, and older confirms without a hold id group by exact hold bounds. - Markers that pass 2 ends with now carry
pass2_outcomein the API: cut, kept, or held with its reason. Spans pass 2 discarded are still only logged and counted in the stored run stats.
2.97.33
Fixed
- Common host-read closings now count as commercial language when a sponsor is confirmed. They are a call to action before the sponsor's domain ("learn more at acme.com"), a domain read aloud or spelled out ("acme dot com", "A-C-M-E.com"), and a thank-you that names the sponsor ("thanks to Acme for supporting the show"). The domain must match the sponsor. A written domain with no call to action, a plain "thanks to Acme", and "our friends at Acme" do not count, since news and conversation use them too.
- A span that names its own sponsor in the transcript or the sponsor registry is no longer held for lacking splice evidence. A sponsor named only in the model's reason still holds.
- The long-window sponsor check now reads the span's transcript, not just the model's reason, so a truncated reason no longer drops a real read. The transcript must name the same sponsor at least twice, and a segment at the edge of the span counts only its words inside the span. A brand known only from the sponsor registry also needs commercial language in the span, as the splice check requires. A brand named twice in conversation does not count.
Changed
- When a pass-2 hold review leaves the hold in place, the marker now records the span, the verdict and the reason as
pass2_hold_review. The episode view shows them on their own line under the hold. Pass-1 reviewer reasoning is left as it was.
2.97.32
Fixed
- Pattern learning no longer merges a self-promo intro and the sponsor read after it into one pattern with an inflated duration. Merged markers keep each member's sponsor and category. When the opening and closing reads of a span name different sponsors or categories, the span is split at a divider, and each piece takes its own read's category. With no divider, the span is not learned.
- Cross-fetch comparison no longer marks show audio as different when the refetched copy has an ad inserted where the processed copy has none. A block with no match is now probed at the offsets on both sides of it, not only at the interpolated one.
- Pass-2 auto-approval now files one confirm per span, even when two held markers cover the same audio. A reviewer hold still gets its own confirm when the other hold has a different reason, since only a confirm with a matching reason releases it.
- Deactivating a pattern through the API or by merging patterns now records when it was disabled. Deactivating it again keeps the first time. Reactivating it clears the time and the disabled reason.
Changed
- Confirms filed by pass-2 auto-approval are now stored with their origin instead of being recognized by a text prefix. Existing rows are migrated at startup. The episode view labels them "Auto-approved", and episode corrections in the API include an
originfield. These confirms no longer feed the positional prior or the pattern backfill from corrections, so neither learns from the pipeline's own output. - A marker the render removed only in part is now split into cut and uncut fragments. Each fragment records the detected span it came from in
carved_from, which replacespartial_cut_spansin the marker schema. An uncut remainder that a later pass cuts in part is split again. Episodes saved withpartial_cut_spansare split the same way when loaded, except that a marker still pending review stays whole. Carved fragments do not seed learned patterns. - Reviewer rejects are now enforced inside validation, so every recut follows one code path. Behavior is unchanged.
- Recuts now log the resolved category action map, as full runs do.
- The episode view names the hold reason on every held marker instead of a generic Held chip.
- DAI markers saved before probe windows were recorded are normalized when loaded. Probe windows are no longer inferred at each use.
Added
- The ad review list can filter pending detections by hold reason and shows how many are pending for each reason in the selected feed. The detections endpoint accepts a
holdReasonquery parameter, returnsholdReasonon each detection, and reportscounts.pendingByHoldReason.
2.97.31
Fixed
- When a feed keeps a category, a text pattern whose length was estimated now protects only the words it matched. Before, its estimated tail could overlap a precisely timed sponsor read next to it, and that part of the read stayed in the audio.
- The reviewer prompt no longer lists a coarse transcript edge as measured. An edge with no precise evidence is now shown as unmeasured.
- A pass-2 finding sent to review inside a held span is now logged as sent to review, not as dropped.
Changed
- Each run logs its resolved category action map and names the categories set by a feed override.
- Tests now pin keep-map behavior: a self-promo inside a sponsor read stays in the audio, the reviewer cannot move the read's edge into it, and the replay harness checks the merge under fixed action maps.
2.97.30
Fixed
- A recut no longer cuts audio the reviewer rejected. The recut revalidated a rejected span from scratch, accepted it and saved it as cut, and every later recut kept cutting it. Reviewer rejects now stay in the audio unless the user confirmed or adjusted that span. Pass-2 auto-approval no longer files a confirm over audio the reviewer rejected. Markers already saved in that state are repaired on the next recut.
- A recut, including the automatic approval recut that follows pass 2, no longer cuts a marker the reviewer held for review. It stays held until the user approves it or pass 2 approves the hold.
- A defined pattern in a category the feed keeps now resolves to remove in every processing step, as it already did at the cut. Before, close-ad merging, duplicate folding and pattern coverage still treated it as kept. It could block a merge with the sponsor read next to it or fail to cover a detection inside it.
- Kept audio is no longer cut out of the episode. Keeps only stopped cuts from merging across them or extending over them, so a pass-1 cut, a reviewer adjustment or a recut could still overlap a kept span and remove it. Cuts are now split around kept audio before rendering, and the render clips any cut that still reaches into it. The reviewer can no longer widen an edge into kept audio. A recut no longer re-validates kept markers, so they cannot merge into a neighboring cut.
- A pass-2 finding that only partly overlaps a kept span is now split around it, and the part outside goes on to validation, review and the cut. Before, the whole finding was held for review, and its part outside the keep stayed in the audio.
- Pass 2 now treats category-kept audio as a fixed barrier instead of a pending hold, like keeps, user trims and user rejections, and carves kept audio out of its cuts.
- Audio the user marked as not an ad is now a hard limit for every render. Pass 2 and recuts now clip a cut at it, as pass 1 already did.
- Segment action controls stay aligned when a feed override is set.
- A marker now shows as cut only when the rendered audio removed it. Rejected, held and kept markers, and requested cuts the render dropped, are saved as not cut. A marker the render removed only in part is split into cut and uncut fragments (see 2.97.32). Marker state, counts, the saved cut list, the transcript and chapters come from the same rendered cuts.
- Markers a render only partly removed now show the removed parts in the episode view.
- A failed or cancelled run, including a recut, no longer leaves new ad markers next to the old published audio. A failed render changes nothing, and markers saved before a later failure are restored. The recut publishes its audio and assets before it saves markers, and a full run saves its final markers after its assets.
- A failure after an episode's new audio is published, for example while writing history, no longer puts the old ad markers back next to it.
- A short piece left when a pass-1 cut is split around kept audio now stays cut on a recut. The saved piece lacked the mark that lets a recut keep a short trusted fragment, so the recut put that audio back.
- A pass-2 finding inside a pass-1 hold now goes to the reviewer at its own span instead of being dropped. Pass 2 narrows it to the span its evidence supports inside the hold. If the reviewer confirms that span, only that span is auto-approved. On a recut or a reprocess, the rest of the hold stays held instead of being left neither cut nor pending. A reject, an abstain, a failed review or a span that crosses other protected audio leaves the whole hold pending. A finding that overlaps a hold can no longer be resurrected into a cut.
- When an LLM detection with a word-timed end merges with a fingerprint or segment-level text pattern that runs past it, the reviewer can now trim back to that end. Before, the fingerprint's projected pattern length or the pattern's segment end counted as measured. A trim to the spoken end was pushed back out to the merged edge or held as a conflict. Detection and validation still keep the merged edge, and only a reviewer trim can move it inward. A trim into the measured part of a fingerprint is still held. An approved trim also drops fingerprint match bounds and cross-fetch regions outside the new span.
- A fingerprint match's start still counts as measured even when its projected pattern length reaches past a precise transcript end. A reviewer trim can still move the end inward to the spoken words without the start losing its protection.
GET /api/v1/patterns?scope=allno longer returns an empty list. The endpoint'sactive_only,podcast_id,network_idandsourcequery params now match the documented spec, andactivestill works as an alias foractive_only. The default listing, with no query params, now includes inactive patterns as documented.- Diagnostic export no longer stops when it reaches an oversized log line. It skips that line and keeps scanning; only reaching the byte budget stops the export.
- The legacy episode reprocess endpoint now accepts an empty or non-JSON body as a default reprocess instead of failing.
- Recorded net seconds removed no longer shows as unknown when the output duration probe fails after a render. It is now estimated from the source and replacement seconds already recorded, so the removed-time stat stays consistent with them.
Changed
- Ads removed now counts cuts in the output audio. Two markers merged into one cut count once, and a marker the render dropped does not count. The second-scan count is the number of cuts that hold a second-scan marker. Run stats add the seconds of source audio cut and the seconds of beeps inserted. Removed time stays net, so beeps count against it.
- The pipeline separates hard protection, category keeps, user trims and user rejections that no step may cross, from temporary reviewer holds that can later be released. Each run resolves the feed's category action map once and shares it across detection, validation, review and pass 2.
- The ad reviewer now sees the feed's effective category actions, nearby kept audio and user rejections as hard limits. It also sees where each piece of evidence came from, including which fingerprint spans are projected lengths and which edges were measured. The default review prompt tells the reviewer not to cross protected audio and to prefer the transcript's precise edges over projected fingerprint lengths. Customized review prompts still receive the new per-ad section.
- Text pattern matching reads the active pattern list once per processing run instead of re-reading it for every match attempt.
- Merged markers now keep a fingerprint member's pattern id, so the reviewer prompt can name the pattern behind a fingerprint member.
- User corrections are loaded once per processing run and reused by validation, the reviewer, confirmed-span restore and pass-2 approvals. A correction saved during a run applies on the next run.
- An opt-in production replay harness runs saved production episodes through the real recut, validator and pass-2 code, with no LLM calls, as a regression check against fixtures kept outside the repo.
2.97.29
Fixed
- When the reviewer ends an ad on a spoken word and the show resumes at least 0.3 s later, that boundary now holds inside the cross-fetch region. It holds even when no transcribed speech lies between it and the region edge. Before, the cut ran on to the region edge and removed show audio after the ad. A fingerprint match, cue pair, probe window or user confirmation in the released span still keeps the region edge.
- Reviewer boundaries now survive the steps that run after the review. DAI core restore, terminal start snap, tail completion, tail splice snap, trailing-ad extension and end-of-episode cut extension no longer widen an edge the reviewer set with numeric bounds. Close-ad merge and cross-pass cut joining no longer bridge a gap next to a locked edge. Touching or overlapping detections still merge, and the merged marker drops any lock it has moved past. Inward moves and user-approved bounds still apply.
Changed
- A reviewer boundary inside the cross-fetch region must now fall on a word with its own timing. A transcript segment without word timings no longer supports it, so on feeds without word timestamps the region edge stays.
- Tail completion no longer extends an end the reviewer set. A spoken call to action after that end stays in the audio.
- Terminal start snap no longer moves a reviewer-set start earlier, and tail splice snap no longer moves a reviewer-set end later. An untranscribed onset or sonic logo just outside that edge stays in the audio.
- An untranscribed ad outro, such as a jingle or music, can now stay in the audio when it plays inside the region after the reviewer's last spoken word. The region shows that an ad is present but not where it ends. Unless a probe window, fingerprint match or cue pair in the released span marks the end, the reviewer's word boundary wins.
2.97.28
Fixed
- When the ad reviewer fails, for example because the LLM provider rejects the request, an ad without independently supported bounds is now held for review as reviewer_failed. Before, it was cut unreviewed. This also covers a failure of the whole review batch. An ad whose bounds are covered by independent evidence (a measured cross-fetch region, a cue pair, template-snapped cues, a fingerprint match or a user confirmation) is still cut and flagged "Reviewer failed; bounds supported". If that support check itself errors during a batch failure, every unconfirmed ad is held.
- When the reviewer trims an ad edge with no measured support, the cut now stops at the timed word that crosses the region edge. Before, it stopped at the region edge and could clip the first words of show speech after the ad. A transcript segment without word timings still keeps the region edge, since it may hold both ad and show speech.
Changed
- The code now documents the two roles of cross-fetch evidence. The region measured as differing across fetches shows the audio is an ad. The probe windows where correlation was computed show whether an edge was measured. A failed or inconclusive review counts that region as support only if at least one probe window measured it. Older markers without recorded probes use the leading window of each region, as before.
2.97.27
Fixed
- An ad detection absorbed into a pattern marker, fully or in part, is now recorded as a measured member of that marker, so the estimated-tail split can anchor on it instead of holding the whole marker.
- Pass 2 can approve the measured part of an estimated-pattern hold when a confident re-detection lies almost entirely inside it, even if it does not cover most of the hold. The rest of the hold stays in the audio.
- A trimmed pass-2 auto-approval no longer turns the audio it left out into a protected keep range. Only a user's trim protects audio from later cuts, so later runs can still detect and cut that audio.
- Splitting an estimated pattern span now logs the cut range and the held remainder.
- The reviewer can now trim a dynamically inserted ad region to a transcript pause. The cross-fetch comparison measures only a few seconds of each inserted block, so the rest of the region is inferred and could hold show speech. A trimmed edge still stops at measured evidence: probed audio, fingerprint matches, cue pairs and user-confirmed spans. Other trims still stop at the region edge, as before.
- A saved confirm correction now cuts its interval even when no detection survives to match it. That covers no detection at all, a wider candidate the validator rejected, and a covering marker the reviewer rejected, trimmed or held. Only the uncovered part of the confirmed span is added as a cut. False-positive corrections and saved trims still win. The per-feed opening exclusion clips a saved confirm to the audio after it. A cut marker is never widened. An uncut marker on the same span is aligned to the confirmed interval, and an overlapping held marker is split around the new cut. Pass-2 auto-approvals do not restore audio.
2.97.26
Fixed
- Ad validation cuts the measured part of a detected ad and holds only the unmeasured estimated-pattern remainder for review. Previously the whole merged marker was held. Approving just the held remainder and reprocessing still cuts the measured part.
- Pass 2 can auto-approve an estimated-pattern hold when an independent pass-2 re-detection corroborates it.
- The sponsor gate for long LLM detection windows now accepts a window that names a known sponsor. A sponsor from this episode's pattern or fingerprint matches (in verification, its first-pass cuts) counts anywhere in the detection, including the quoted start or end text. Sponsors from the episode description or the sponsor registry count only in the reason or description, because names like "Calm" or "Indeed" are also common words. A long correct read is no longer dropped for lacking its own ad-language cue.
- Sponsor names extracted from an episode description now match whole words only, so words like "romance" or "factory" no longer count as sponsors.
2.97.25
Fixed
- Sponsor-cue alignment keeps the full introduction when the cue occurs mid-sentence.
2.97.24
Fixed
- Saved boundary trims keep excluded speech in the audio when a later detection spans several ads, including after reviewer adjustments and verification.
- A word-timed ad start moves past preceding show speech when a nearby explicit sponsor introduction marks the actual boundary.
2.97.23
Fixed
- Detection and review use word-timed transcript lines when available, allowing ad boundaries inside mixed speech segments. Merges and validation preserve these precise edges.
- An estimated text pattern no longer hides or widens one precise LLM detection covering its matched words.
- A renewed ad marker that extends beyond a saved confirmation cuts only the overlapping approved audio. Longer outside portions receive independent review.
- Audio rendering preserves the end of a user-confirmed cut during close-gap merging and end-of-episode trimming.
- The legacy episode reprocess URL honors the requested mode. LLM reruns keep the saved transcript, and full reruns retain it until fresh transcription begins.
2.97.22
Fixed
- Text pattern edits, disables, and deletes take effect on the next match without a worker restart.
- Auto-learned patterns no longer use weak outro text to extend cuts. Estimated pattern spans need full measured coverage before removal. Sponsor registry confirmation requires the marker's advertiser and commercial language in the audio.
- An inconclusive review holds a cut for manual review when measured evidence does not support both boundaries, in either processing pass.
2.97.21
Fixed
- Refresh system packages for each image version so cached Docker layers do not retain available updates.
2.97.20
Fixed
- Ad boundary extension uses word times to recover supported calls to action. Unclear tails stay at their reviewed boundary.
- Learned patterns use the final cut and exclude words outside it.
2.97.19
Fixed
- Estimated text-pattern spans can use a corroborating detection's boundary instead of a stored duration.
- Markers split around a conflicting action no longer repeat an excluded ad's reason or sponsor.
2.97.18
Changed
- Pattern learning uses recorded boundaries to make separate patterns from ads combined for cutting.
Fixed
- Split is disabled when no reliable boundary exists. Failed splits leave the original pattern active.
- Reviewer abstentions report missing boundary coverage. Since 2.97.22 the marker keeps its bounds only when measured evidence supports them and is otherwise held for review.
- SQLite transaction warnings distinguish elapsed time that may include a lock wait from time spent holding a write lock.
2.97.17
Changed
- Settings groups configuration and diagnostic exports in a Troubleshooting section with two cards that fit smaller screens.
- Existing cached RSS feeds re-render once after a renderer update so stored metadata catches up.
Fixed
- Served RSS now reports the processed audio duration for completed episodes.
- Reviewer abstentions on inconclusive HTTP 422 responses show a bounded reason and avoid retries or breaker failures. Since 2.97.22 the marker keeps its bounds only when measured evidence supports them and is otherwise held for review.
2.97.16
Added
- Settings can export 1, 6, or 24 hours of application event metadata without log messages or identifying content.
Changed
- Runtime, frontend, and CPU image build dependencies were updated to reviewed upstream releases.
Fixed
- Previously confirmed ads stay approved after small boundary shifts on re-detection, without cutting beyond the approved span.
- Processing history shows Skipped when normalization was disabled for that run.
- Spend date filters accept full years on desktop and provide a visible calendar button.
- SQLite diagnostics separate lock-acquisition wait time from time held after a transaction begins.