feat: Mark failed recordings with .m4a.failed
Add a new `failed` field to `RecordingView` and corresponding logic in the worker. When FFmpeg remuxing or Whisper transcription fails, the worker now renames the original `.m4a` file to `.m4a.failed`. This prevents the recovery scan from reprocessing these files and signals to the UI that transcription failed. The UI displays a message indicating failure and suggests renaming the file back to `.m4a` to retry. A new integration test `worker_renames_audio_to_failed_on_whisper_error` is included to verify this behavior.
This commit is contained in:
@@ -39,6 +39,7 @@ pub async fn run(mut rx: TranscribeReceiver, config: Arc<Config>, client: reqwes
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error!(audio = %audio_path.display(), error = %e, "ffmpeg remux failed");
|
||||
mark_failed(&audio_path).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -47,6 +48,7 @@ pub async fn run(mut rx: TranscribeReceiver, config: Arc<Config>, client: reqwes
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!(audio = %audio_path.display(), error = %e, "whisper call failed");
|
||||
mark_failed(&audio_path).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -70,6 +72,29 @@ pub async fn run(mut rx: TranscribeReceiver, config: Arc<Config>, client: reqwes
|
||||
warn!("Transcription worker stopped (channel closed)");
|
||||
}
|
||||
|
||||
/// Atomically rename a failed recording from `<ts>.m4a` to `<ts>.m4a.failed`
|
||||
/// so the recovery scan no longer picks it up. The UI still surfaces these
|
||||
/// files (with a "failed" flag) so the user can listen to the audio and
|
||||
/// manually decide whether to retry (by renaming back) or to delete.
|
||||
async fn mark_failed(audio_path: &Path) {
|
||||
let failed_path = audio_path.with_extension("m4a.failed");
|
||||
if let Err(e) = tokio::fs::rename(audio_path, &failed_path).await {
|
||||
// Nothing to roll back — if rename fails, the worst case is that we
|
||||
// retry the same file on next restart. Log and move on.
|
||||
error!(
|
||||
audio = %audio_path.display(),
|
||||
error = %e,
|
||||
"Failed to mark recording as failed; may be retried on restart",
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
audio = %audio_path.display(),
|
||||
failed = %failed_path.display(),
|
||||
"Recording marked as failed",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate `case_dir/oneliner.txt` from the given transcript iff the file
|
||||
/// does not already exist. Errors are non-fatal: logged and swallowed.
|
||||
async fn ensure_oneliner(
|
||||
|
||||
Reference in New Issue
Block a user