Conversation
AVAudioFile(forReading:) cannot open many video containers directly
even though .mov/.mp4 are advertised as supported formats. The
buffered/chunked transcription path used for video containers was
opening the raw video file URL with AVAudioFile, which throws
com.apple.coreaudio.avfaudio error -54 ("Could not open audio file").
Extract the audio track to a standalone .m4a via AVAssetExportSession
before handing it to AVAudioFile when the source is a video container,
and clean up the temporary file afterward.
Fixes altic-dev#982
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The PR Policy check is blocking this PR because required template information is missing. Please update the PR description with:
Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template. If this remains incomplete for 48 hours after opening, the PR may be closed. |
|
| let outputURL = FileManager.default.temporaryDirectory | ||
| .appendingPathComponent(UUID().uuidString) | ||
| .appendingPathExtension("m4a") | ||
|
|
||
| exportSession.outputURL = outputURL | ||
| exportSession.outputFileType = .m4a | ||
|
|
||
| await exportSession.export() | ||
|
|
||
| if let error = exportSession.error { | ||
| throw error | ||
| } | ||
| guard exportSession.status == .completed else { | ||
| throw TranscriptionError | ||
| .audioConversionFailed("Audio export did not complete (status: \(exportSession.status.rawValue))") | ||
| } | ||
|
|
||
| return outputURL |
There was a problem hiding this comment.
Failed exports leave temporary files
If an export creates its destination before failing or being cancelled, extractAudioTrack throws without removing that file. The caller cannot clean it up because it receives the URL only after a successful return, so repeated failures can accumulate temporary .m4a files. Register cleanup inside this helper and preserve the file only after export completes.
| let outputURL = FileManager.default.temporaryDirectory | |
| .appendingPathComponent(UUID().uuidString) | |
| .appendingPathExtension("m4a") | |
| exportSession.outputURL = outputURL | |
| exportSession.outputFileType = .m4a | |
| await exportSession.export() | |
| if let error = exportSession.error { | |
| throw error | |
| } | |
| guard exportSession.status == .completed else { | |
| throw TranscriptionError | |
| .audioConversionFailed("Audio export did not complete (status: \(exportSession.status.rawValue))") | |
| } | |
| return outputURL | |
| let outputURL = FileManager.default.temporaryDirectory | |
| .appendingPathComponent(UUID().uuidString) | |
| .appendingPathExtension("m4a") | |
| var shouldRemoveOutput = true | |
| defer { | |
| if shouldRemoveOutput { | |
| try? FileManager.default.removeItem(at: outputURL) | |
| } | |
| } | |
| exportSession.outputURL = outputURL | |
| exportSession.outputFileType = .m4a | |
| await exportSession.export() | |
| if let error = exportSession.error { | |
| throw error | |
| } | |
| guard exportSession.status == .completed else { | |
| throw TranscriptionError | |
| .audioConversionFailed("Audio export did not complete (status: \(exportSession.status.rawValue))") | |
| } | |
| shouldRemoveOutput = false | |
| return outputURL |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/Fluid/Services/MeetingTranscriptionService.swift
Line: 681-698
Comment:
**Failed exports leave temporary files**
If an export creates its destination before failing or being cancelled, `extractAudioTrack` throws without removing that file. The caller cannot clean it up because it receives the URL only after a successful return, so repeated failures can accumulate temporary `.m4a` files. Register cleanup inside this helper and preserve the file only after export completes.
```suggestion
let outputURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("m4a")
var shouldRemoveOutput = true
defer {
if shouldRemoveOutput {
try? FileManager.default.removeItem(at: outputURL)
}
}
exportSession.outputURL = outputURL
exportSession.outputFileType = .m4a
await exportSession.export()
if let error = exportSession.error {
throw error
}
guard exportSession.status == .completed else {
throw TranscriptionError
.audioConversionFailed("Audio export did not complete (status: \(exportSession.status.rawValue))")
}
shouldRemoveOutput = false
return outputURL
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Description
Fixes file transcription failing on
.MOVuploads withFailed to convert audio; Could not open audio file: ... (com.apple.coreaudio.avfaudio error -54.), even though.movis advertised as a supported format.Root cause: in
MeetingTranscriptionService.transcribeFile(_:), the chunked/buffered transcription path (the path used for video containers) opened the raw video file URL directly withAVAudioFile(forReading:).AVAudioFileexpects a standalone audio file and can't open many video containers this way, which throws CoreAudio error -54.Fix: when the source is a video container (
isVideoContainer), first extract its audio track to a temporary standalone.m4afile viaAVAssetExportSession(AVAssetExportPresetAppleM4A), then open that withAVAudioFile. The temporary file is removed viadeferonce transcription finishes, whether it succeeds or throws.Scope note: this only touches the buffered transcription path taken for video containers. The path for native audio files (wav/mp3/m4a/etc.) and the speaker-diarization path (which already skips video containers) are unchanged.
Type of Change
Related Issue or Discussion
Fixes #982
Testing
swiftlint --strict --config .swiftlint.yml Sources Tests Package.swiftswiftformat --config .swiftformat Sources—0/1 files require formattingfor the changed file.Not yet verified (see Notes): this environment has only Xcode Command Line Tools, not full Xcode, so I could not run
xcodebuild, the app itself, orswiftlint(which needs sourcekitd from a full Xcode install). No.movfile has been run through this code path yet. Opening as a draft for that reason — please do not merge until the manual testing below is done.Screenshots / Video
Notes
What still needs to happen before this is ready for review:
./build.sh unsignedor the normal Xcode build) and confirm it compiles — this change has only been reviewed by eye and passed throughswiftformat, not compiled..movfile (ideally the same file/export settings that originally triggered error -54) to file transcription and confirm it now transcribes successfully instead of erroring..m4a(written toFileManager.default.temporaryDirectory) is actually removed after a run, including after a failed/cancelled transcription..movwith an unusual audio codec/no audio track at all, to confirm the new error path (Could not extract audio track from video: ...) surfaces a sane message instead of a crash.Will move this out of draft once the above has been done.