Fullfører oppgave 17.3: Fade/silence-logikk

Tre fikser i audio.rs:

1. Fade-out start clampes til 0 i stedet for å hoppes over
   når effective_duration < fade_duration. Tidligere ble faden
   stille droppet — nå starter den alltid fra minst 0.

2. Adaptiv silence-margin: margin (200ms) begrenses til maks
   halve regionens varighet. Korte stillhetsregioner (<400ms)
   fikk tidligere hele marginen spist opp og ble aldri kuttet.

3. Ny validate_fade_durations() gir feilmelding når fade-varighet
   overstiger lydens totale varighet. Kalles fra process_audio
   etter at vi kjenner faktisk varighet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-18 05:47:07 +00:00
parent 4ea0229935
commit 1d1a316a1c
2 changed files with 45 additions and 8 deletions

View file

@ -387,6 +387,41 @@ pub fn validate_operations(ops: &[EdlOperation]) -> Result<(), String> {
} }
} }
/// Valider fade-varigheter mot faktisk lydvarighet.
/// Kalles fra process_audio etter at vi kjenner varigheten.
pub fn validate_fade_durations(ops: &[EdlOperation], duration_ms: i64) -> Result<(), String> {
let mut errors: Vec<String> = Vec::new();
for (i, op) in ops.iter().enumerate() {
let idx = i + 1;
match op {
EdlOperation::FadeIn { duration_ms: fade_ms } => {
if *fade_ms as i64 > duration_ms {
errors.push(format!(
"Operasjon {idx} (fade_in): varighet ({fade_ms} ms) \
overstiger lydens varighet ({duration_ms} ms)"
));
}
}
EdlOperation::FadeOut { duration_ms: fade_ms } => {
if *fade_ms as i64 > duration_ms {
errors.push(format!(
"Operasjon {idx} (fade_out): varighet ({fade_ms} ms) \
overstiger lydens varighet ({duration_ms} ms)"
));
}
}
_ => {}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(format!("Ugyldig fade-varighet:\n- {}", errors.join("\n- ")))
}
}
// ─── EDL → FFmpeg filtergraf ────────────────────────────────────── // ─── EDL → FFmpeg filtergraf ──────────────────────────────────────
/// Bygg ffmpeg-filtergraf fra EDL-operasjoner. /// Bygg ffmpeg-filtergraf fra EDL-operasjoner.
@ -509,10 +544,8 @@ pub fn build_filter_chain(
} }
EdlOperation::FadeOut { duration_ms: dur } => { EdlOperation::FadeOut { duration_ms: dur } => {
let d = *dur as f64 / 1000.0; let d = *dur as f64 / 1000.0;
let start = (effective_duration_ms as f64 / 1000.0) - d; let start = ((effective_duration_ms as f64 / 1000.0) - d).max(0.0);
if start > 0.0 { filters.push(format!("afade=t=out:st={start:.3}:d={d:.3}"));
filters.push(format!("afade=t=out:st={start:.3}:d={d:.3}"));
}
} }
_ => {} _ => {}
} }
@ -540,6 +573,9 @@ pub async fn process_audio(
// Hent info for fade-out beregning // Hent info for fade-out beregning
let info = get_audio_info(cas, &edl.source_hash).await?; let info = get_audio_info(cas, &edl.source_hash).await?;
// Valider fade-varigheter mot faktisk lydvarighet
validate_fade_durations(&edl.operations, info.duration_ms)?;
// Sjekk om vi trenger to-pass loudnorm // Sjekk om vi trenger to-pass loudnorm
let has_normalize = edl.operations.iter().any(|op| matches!(op, EdlOperation::Normalize { .. })); let has_normalize = edl.operations.iter().any(|op| matches!(op, EdlOperation::Normalize { .. }));
@ -703,8 +739,10 @@ async fn resolve_silence_cuts(
{ {
let regions = detect_silence(cas, &edl.source_hash, *threshold_db, *min_duration_ms).await?; let regions = detect_silence(cas, &edl.source_hash, *threshold_db, *min_duration_ms).await?;
for region in regions { for region in regions {
// Behold 200ms stillhet på hver side for naturlig lyd // Behold 200ms stillhet på hver side for naturlig lyd,
let margin_ms = 200; // men aldri mer enn halve regionens varighet
let region_duration = region.end_ms - region.start_ms;
let margin_ms = 200i64.min(region_duration / 2);
let start = region.start_ms + margin_ms; let start = region.start_ms + margin_ms;
let end = region.end_ms - margin_ms; let end = region.end_ms - margin_ms;
if end > start { if end > start {

View file

@ -191,8 +191,7 @@ Ref: Kodegjennomgang av `b4c4bb8` (Lydstudio: lydredigering via FFmpeg).
- [x] 17.1 Responsivt studio-layout: `/studio/[id]` sidebar stacker under waveform på mobil. Verktøypanel som modal/sheet på små skjermer. Ref: feedback om at alt UI skal være responsivt uten unntak. - [x] 17.1 Responsivt studio-layout: `/studio/[id]` sidebar stacker under waveform på mobil. Verktøypanel som modal/sheet på små skjermer. Ref: feedback om at alt UI skal være responsivt uten unntak.
- [x] 17.2 FFmpeg-parametervalidering: valider at alle numeriske verdier (threshold, gain, ratio, frekvenser) er innenfor sikre grenser i `audio.rs` før de interpoleres i filterstrenger. Avvis ugyldige verdier med feilmelding. - [x] 17.2 FFmpeg-parametervalidering: valider at alle numeriske verdier (threshold, gain, ratio, frekvenser) er innenfor sikre grenser i `audio.rs` før de interpoleres i filterstrenger. Avvis ugyldige verdier med feilmelding.
- [~] 17.3 Fade/silence-logikk: fiks negativ fade-out start (clamp til 0), og adaptiv silence-margin (margin skal ikke overstige halve regionens varighet). Gi feilmelding ved ugyldige fade-varigheter. - [x] 17.3 Fade/silence-logikk: fiks negativ fade-out start (clamp til 0), og adaptiv silence-margin (margin skal ikke overstige halve regionens varighet). Gi feilmelding ved ugyldige fade-varigheter.
> Påbegynt: 2026-03-18T05:44
- [ ] 17.4 Frontend input-begrensninger: legg til `min`/`max` på alle tallfelter i OperationPanel (silenceThreshold, fadeMs, normTarget, compRatio). Hindre ugyldig input. - [ ] 17.4 Frontend input-begrensninger: legg til `min`/`max` på alle tallfelter i OperationPanel (silenceThreshold, fadeMs, normTarget, compRatio). Hindre ugyldig input.
- [ ] 17.5 Job-polling opprydding: rydd opp interval/timeout ved navigering bort fra studio-siden. Vis feilmelding etter N mislykkede polling-forsøk. Wrap metadata JSON.parse i try/catch. - [ ] 17.5 Job-polling opprydding: rydd opp interval/timeout ved navigering bort fra studio-siden. Vis feilmelding etter N mislykkede polling-forsøk. Wrap metadata JSON.parse i try/catch.
- [ ] 17.6 Temp-fil opprydding: legg til periodisk jobb i maskinrommet som sletter gamle temp-filer i CAS tmp-katalog. Bruk `/tmp` eller sett TTL. - [ ] 17.6 Temp-fil opprydding: legg til periodisk jobb i maskinrommet som sletter gamle temp-filer i CAS tmp-katalog. Bruk `/tmp` eller sett TTL.