Fix transcription reliability: whisper deploy, Parakeet batch resilience, and FluidAudio build - #85
Conversation
The SwiftUI patcher (PatcherModel.swift) staged and deployed only silence-detector and parakeet-transcriber. whisper-transcriber ships in the patcher app bundle (Contents/Resources/tools/whisper-transcriber) and the v3.3.8 release notes state it is included so the social-captions Whisper large-v3 / large-v3-turbo engines "actually work" — but this code path never copied it into ~/Applications/SpliceKit/tools/. Result: a clean patch reports success, yet the captions panel fails with "Whisper large-v3 transcriber not found. Re-run the SpliceKit patcher, or pick a different engine." Re-running never helps, because every run repeats the same incomplete tool set. (Note: the other, separate patcher target patcher/SpliceKitPatcher/main.swift already copies whisper correctly; only PatcherModel.swift — the shipped SwiftUI app — was missing it.) Fix: - Stage whisper-transcriber from the app bundle in both the full-patch and update code paths (mirrors the existing parakeet/silence staging). - Add a whisperBin parameter to deployTools() and copy it into the tools dir alongside the other transcribers. - Emit a loud WARNING when whisper-transcriber is absent, so this failure is visible in the patch log instead of silently degrading transcription. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…udio In batch mode the Phase 1 loop transcribed each file with a bare `try await manager.transcribe(...)` and no per-file error handling. When a single entry could not be decoded as audio — a still image, a PDF, or a video/clip with no audio track — AVFoundation throws (kAudioFileInvalidFileError 'dta?' / kAudioFileUnsupportedFileTypeError 'typ?', surfaced as com.apple.coreaudio.avfaudio error 1685348671 / 1954115647). That error propagated out of the loop to the outer catch, which set exitCode=1 and produced zero words for the ENTIRE batch. Timelines routinely mix stills/graphics/PDFs with audio clips, so one non-audio item silently broke transcription for the whole timeline. See issue elliotttate#78 (both avfaudio subcodes are the same root cause). Fix: wrap the per-file transcribe in do/catch (mirroring the diarization phase, which already tolerates per-file failures). A failing file is recorded, logged to stderr ("Skipping <file>: <reason>"), and skipped so the remaining files still transcribe. Batch results now report each skipped file's real error instead of a blanket "File not found". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… migration)
parakeet-transcriber did not compile against any published FluidAudio
release: Sources/main.swift used `.tdtCtc110m` (added in FluidAudio 0.13.7)
together with `AsrManager.transcribe(_:source:)` (removed after 0.13.0), so
no single version satisfied both. The floating `from: "0.12.0"` dependency
masked this by resolving to whatever 0.x was newest.
- Package.swift: pin FluidAudio to .upToNextMinor(from: "0.15.6") so the
pre-1.0 dependency can't silently pull a version with breaking API changes.
- main.swift: migrate the one incompatible call from
`transcribe(fileURL, source: .system)` to the current
`transcribe(fileURL, decoderState: &state)` API, using a fresh
TdtDecoderState per batch entry (independent clips, no streaming context).
Verified against FluidAudio 0.15.6: release build succeeds, and a batch of
[non-audio .png, spoken .aiff] correctly skips the .png (avfaudio 1954115647,
per the batch-resilience fix) while transcribing the audio to accurate
word-level output ("The quick brown fox jumps over the lazy dog").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
3 issues found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tools/parakeet-transcriber/Sources/main.swift">
<violation number="1" location="tools/parakeet-transcriber/Sources/main.swift:310">
P1: When single-file transcription fails, this catch swallows the error and the process exits successfully with an empty JSON array. Re-throw failures when `batchMode` is false so the existing outer handler reports the error and returns a nonzero exit status.</violation>
</file>
<file name="patcher/SpliceKit/Models/PatcherModel.swift">
<violation number="1" location="patcher/SpliceKit/Models/PatcherModel.swift:903">
P2: After a previous run leaves `whisper-transcriber` in the shared temporary build directory, a later run with no bundled Whisper silently redeploys that obsolete binary. Remove the Whisper artifact before the conditional copy in both full-patch and update paths.</violation>
<violation number="2" location="patcher/SpliceKit/Models/PatcherModel.swift:904">
P1: When the app bundle contains the source fallback, this `cp` fails because `bundledWhisper` is a directory, so full installs and updates do not stage Whisper. Build the binary before copying it, or package only an executable for this patcher path.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| var decoderState = try TdtDecoderState() | ||
| let result = try await manager.transcribe(fileURL, decoderState: &decoderState) | ||
| asrResults.append((index: index, file: entry.file, result: result)) | ||
| } catch { |
There was a problem hiding this comment.
P1: When single-file transcription fails, this catch swallows the error and the process exits successfully with an empty JSON array. Re-throw failures when batchMode is false so the existing outer handler reports the error and returns a nonzero exit status.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/parakeet-transcriber/Sources/main.swift, line 310:
<comment>When single-file transcription fails, this catch swallows the error and the process exits successfully with an empty JSON array. Re-throw failures when `batchMode` is false so the existing outer handler reports the error and returns a nonzero exit status.</comment>
<file context>
@@ -299,8 +300,22 @@ Task {
+ var decoderState = try TdtDecoderState()
+ let result = try await manager.transcribe(fileURL, decoderState: &decoderState)
+ asrResults.append((index: index, file: entry.file, result: result))
+ } catch {
+ // A single undecodable input (a still image, PDF, or a clip with no
+ // audio track — e.g. AVFoundation kAudioFileInvalidFileError /
</file context>
| } catch { | |
| } catch { | |
| if !batchMode { throw error } |
| let whisperBin = buildDir + "/whisper-transcriber" | ||
| let bundledWhisper = (Bundle.main.resourcePath ?? "") + "/tools/whisper-transcriber" | ||
| if FileManager.default.fileExists(atPath: bundledWhisper) { | ||
| shell("cp '\(bundledWhisper)' '\(whisperBin)'") |
There was a problem hiding this comment.
P1: When the app bundle contains the source fallback, this cp fails because bundledWhisper is a directory, so full installs and updates do not stage Whisper. Build the binary before copying it, or package only an executable for this patcher path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At patcher/SpliceKit/Models/PatcherModel.swift, line 904:
<comment>When the app bundle contains the source fallback, this `cp` fails because `bundledWhisper` is a directory, so full installs and updates do not stage Whisper. Build the binary before copying it, or package only an executable for this patcher path.</comment>
<file context>
@@ -889,6 +898,12 @@ class PatcherModel: ObservableObject {
+ let whisperBin = buildDir + "/whisper-transcriber"
+ let bundledWhisper = (Bundle.main.resourcePath ?? "") + "/tools/whisper-transcriber"
+ if FileManager.default.fileExists(atPath: bundledWhisper) {
+ shell("cp '\(bundledWhisper)' '\(whisperBin)'")
+ }
+
</file context>
|
|
||
| let whisperBin = buildDir + "/whisper-transcriber" | ||
| let bundledWhisper = (Bundle.main.resourcePath ?? "") + "/tools/whisper-transcriber" | ||
| if FileManager.default.fileExists(atPath: bundledWhisper) { |
There was a problem hiding this comment.
P2: After a previous run leaves whisper-transcriber in the shared temporary build directory, a later run with no bundled Whisper silently redeploys that obsolete binary. Remove the Whisper artifact before the conditional copy in both full-patch and update paths.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At patcher/SpliceKit/Models/PatcherModel.swift, line 903:
<comment>After a previous run leaves `whisper-transcriber` in the shared temporary build directory, a later run with no bundled Whisper silently redeploys that obsolete binary. Remove the Whisper artifact before the conditional copy in both full-patch and update paths.</comment>
<file context>
@@ -889,6 +898,12 @@ class PatcherModel: ObservableObject {
+ let whisperBin = buildDir + "/whisper-transcriber"
+ let bundledWhisper = (Bundle.main.resourcePath ?? "") + "/tools/whisper-transcriber"
+ if FileManager.default.fileExists(atPath: bundledWhisper) {
+ shell("cp '\(bundledWhisper)' '\(whisperBin)'")
+ }
</file context>
Fix transcription reliability: whisper deploy, Parakeet batch resilience, and FluidAudio build
Summary
Three related fixes that make on-device transcription work end-to-end again. Each is an independent, reviewable commit.
whisper-transcriber→ the social-captions Whisper engines report "not found".parakeet-transcriberno longer compiles against any published FluidAudio release → the tool can't be built from a clean checkout.All three were verified locally on FCP 11.1 / SpliceKit 3.3.8 (Apple Silicon, macOS 15.6).
1. Patcher deploys
whisper-transcriber(104fd0d)patcher/SpliceKit/Models/PatcherModel.swiftstaged and deployed onlysilence-detectorandparakeet-transcriber;deployTools()had nowhisperBinparameter.whisper-transcriberships in the patcher app bundle and the v3.3.8 notes say it's included so the Whisper large-v3 / large-v3-turbo caption engines "actually work" — but the deploy path never copied it into~/Applications/SpliceKit/tools/.Result: a clean patch reports success, yet the captions panel fails with:
Re-running never helps — every run repeats the same incomplete tool set.
Fix: stage
whisper-transcriberin both the full-patch and update paths (mirroring the existing parakeet/silence staging), add awhisperBinparameter todeployTools(), and emit a loudWARNINGwhen it's absent so the failure is visible in the patch log instead of silently degrading transcription.2. Parakeet batch resilience (
d0900d1) — fixes #78In
tools/parakeet-transcriber/Sources/main.swift, the Phase-1 batch loop transcribed each file with a baretry await manager.transcribe(...)and no per-file error handling. When one entry can't be decoded as audio — a still image, a PDF, or a clip with no audio track — AVFoundation throwskAudioFileInvalidFileError/kAudioFileUnsupportedFileTypeError(surfaced ascom.apple.coreaudio.avfaudio error 1685348671/1954115647). That error propagated out of the loop to the outercatch, setexitCode = 1, and produced zero words for the entire batch.Timelines routinely mix stills/graphics with audio clips, so a single non-audio item silently broke transcription for the whole timeline. This is issue #78 — both avfaudio subcodes are the same root cause.
Fix: wrap the per-file transcribe in
do/catch(mirroring the diarization phase, which already tolerates per-file failures). A failing file is recorded, logged (Skipping <file>: <reason>), and skipped so the remaining files still transcribe. Batch results report each skipped file's real error instead of a blanket "File not found".3. Build against current FluidAudio (
57b1ef0)parakeet-transcriberdid not compile against any published FluidAudio release:main.swiftuses.tdtCtc110m(added in FluidAudio 0.13.7) together withAsrManager.transcribe(_:source:)(removed after 0.13.0), so no single version satisfies both. The floatingfrom: "0.12.0"masked this by resolving to whatever 0.x was newest.Fix:
Package.swift: pin FluidAudio to.upToNextMinor(from: "0.15.6")so the pre-1.0 dependency can't silently pull a version with breaking API changes.main.swift: migrate the one incompatible call fromtranscribe(fileURL, source: .system)to the currenttranscribe(fileURL, decoderState: &state)API, using a freshTdtDecoderStateper batch entry (independent clips, no streaming context).(0.15.6 is compatible with the entire file —
.tdtCtc110m,.v2/.v3, model loading, diarizer — except that single call. No features dropped.)Verification
swiftc -typecheckof all patcher app sources against the built Sentry/Sparkle frameworks → 0 errors.swift build -c releaseagainst FluidAudio 0.15.6 → succeeds. Running a batch of[non-audio .png, spoken .aiff]:.pngthrowsavfaudio error 1954115647and is skipped (logged, recorded), instead of aborting the batch;Notes for reviewers
Package.resolvedis gitignored per repo convention, so it's not included.Summary by cubic
Restores end-to-end on-device transcription by deploying Whisper, making Parakeet batch-safe, and pinning
FluidAudiofor a reliable build. Previously the patcher omittedwhisper-transcriber, Parakeet aborted on a single non-audio file, andparakeet-transcribercould not compile; now Whisper is deployed with a clear warning if missing, Parakeet skips and logs bad files, and the tool builds againstFluidAudio0.15.6 using the decoder-state API.Patcher: stage and deploy
whisper-transcriberin both full and update paths; addwhisperBintodeployTools(); log a WARNING when Whisper is absent.Parakeet: wrap per-file transcribe in do/catch, log “Skipping : ”, and include the real per-file error in batch results instead of aborting the batch (fixes Splicekit ERROR COREAUDIO - M3MAX #78).
Build: pin
FluidAudioto.upToNextMinor(from: "0.15.6")and migrate totranscribe(fileURL, decoderState:&state)with a freshTdtDecoderStateper file.Rollout: Users must re-run the SwiftUI patcher once to deploy
whisper-transcriberinto ~/Applications/SpliceKit/tools/. No config or data migrations.Written for commit 57b1ef0. Summary will update on new commits.