diff --git a/cmd/tapir/uploads.go b/cmd/tapir/uploads.go index 13f7d02..bda8a19 100644 --- a/cmd/tapir/uploads.go +++ b/cmd/tapir/uploads.go @@ -55,8 +55,8 @@ func buildUploads(ctx context.Context, cfg config.Config, st uploadsStore, log * log.Warn("uploads: marked interrupted by a restart (audio is not retained; they must be re-uploaded)", "count", n) } - worker := uploads.NewWorker(st, &audio.FFmpeg{Bin: cfg.FFmpegBin}, buildTranscriber(cfg), - cfg.UploadTmpDir, cfg.TranscribeTimeout) + worker := uploads.NewWorker(st, newConverter(cfg), buildTranscriber(cfg), + cfg.UploadTmpDir, cfg.TranscribeTimeout, uploads.WithMaxDuration(cfg.UploadMaxDuration)) intake := uploads.NewIntake(st, &audio.FFprobe{Bin: cfg.FFprobeBin}, worker, uploadLimits(cfg), cfg.UploadTmpDir) return intake, worker, nil @@ -94,6 +94,12 @@ func wireUploads(app *web.App, cfg config.Config, in *uploads.Intake) { app.UploadChatModels = uploadChatModels(cfg) } +// newConverter is the ffmpeg adapter capped at the configured maximum duration, so a file +// whose header lies about its length cannot become hours of WAV (ADR-035 review). +func newConverter(cfg config.Config) *audio.FFmpeg { + return &audio.FFmpeg{Bin: cfg.FFmpegBin, MaxSeconds: int(cfg.UploadMaxDuration.Seconds())} +} + // adjudicatorTimeout bounds one adjudication call. A call takes ~2 s on gemma4-26b and ~10 s // on 31b; this is the wait before an unresponsive model is counted as an error. const adjudicatorTimeout = 2 * time.Minute