feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39

Merged
mathias merged 299 commits from feat/33-audio-upload-transcript into main 2026-10-03 14:13:37 +00:00
Showing only changes of commit f7284ccdc9 - Show all commits
+29 -14
View File
@@ -275,11 +275,18 @@ func (w *Worker) Process(ctx context.Context, job Job) {
abort(domain.UploadErrTranscriptionFailed)
return
}
if kind, bad := w.audioLengthProblem(wav, job.Duration); bad {
abort(kind)
log.Error("upload: converted audio length rejected", "kind", string(kind))
got, tooLong := w.audioLengthProblem(wav)
if tooLong {
abort(domain.UploadErrTooLong)
log.Error("upload: converted audio is over the limit", "audio_s", int(got.Seconds()), "limit_s", int(w.maxDuration.Seconds()))
return
}
if w.maxDuration > 0 && job.Duration > 0 {
if note := shortfallNote(got, job.Duration); note != "" {
log.Warn("upload: audio is much shorter than the file claimed", "audio_s", int(got.Seconds()), "claimed_s", int(job.Duration.Seconds()))
tr.Annotate("audio", note)
}
}
tr.Done("audio")
if !planned {
@@ -343,21 +350,29 @@ const (
)
// audioLengthProblem measures the converted WAV (16 kHz mono 16-bit PCM is 32,000 bytes a
// second after a 44-byte header) against the limit and against what the file claimed.
func (w *Worker) audioLengthProblem(wav string, claimed time.Duration) (domain.UploadErrorKind, bool) {
// second after a 44-byte header) against the limit. It returns the real length and whether
// that is over the limit by more than the tolerance.
func (w *Worker) audioLengthProblem(wav string) (got time.Duration, tooLong bool) {
if w.maxDuration <= 0 {
return "", false
return 0, false
}
info, err := os.Stat(wav)
if err != nil {
return "", false
return 0, false
}
got := time.Duration(float64(max(info.Size()-44, 0)) / 32000 * float64(time.Second))
switch {
case got > w.maxDuration+tooLongSlack:
return domain.UploadErrTooLong, true
case float64(got) < incompleteBelow*float64(claimed) && claimed-got > incompleteShortfall:
return domain.UploadErrIncomplete, true
got = time.Duration(float64(max(info.Size()-44, 0)) / 32000 * float64(time.Second))
return got, got > w.maxDuration+tooLongSlack
}
// shortfallNote is a remark for the audio step when the converted audio is much shorter
// than the file claimed, or "" when it is not. It is a NOTE and never a failure: a
// container header can over-estimate a complete file (a variable-bitrate MP3 without a Xing
// header claimed 79 minutes for 20 real ones), so refusing would turn away real
// recordings, while saying nothing about a truncated one would hide a partial transcript.
func shortfallNote(got, claimed time.Duration) string {
if float64(got) >= incompleteBelow*float64(claimed) || claimed-got <= incompleteShortfall {
return ""
}
return "", false
return fmt.Sprintf("The audio is %s long; the file claimed %s. If the recording was cut off, upload the complete one.",
FormatDuration(got), FormatDuration(claimed))
}