Skip to content

fix(encode): make every output format render the frames it claims - #151

Merged
LeadcodeDev merged 1 commit into
chantier/audit-remediationfrom
fix/encode-still-gif-raw-audio
Aug 8, 2026
Merged

fix(encode): make every output format render the frames it claims#151
LeadcodeDev merged 1 commit into
chantier/audit-remediationfrom
fix/encode-still-gif-raw-audio

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Round 3 du chantier d'audit — lot encodage, 7 constats vérifiés.

Tous partagent la même famille de défaut : une sortie qui contredit silencieusement le flux de frames, la fréquence d'échantillonnage ou la durée qu'elle annonce.

Constat Symptôme observable
#2 Désynchronisation A/V progressive : mix_audio_tracks rééchantillonne vers une constante, deux sites de mux en déclaraient une autre en dur. Le PCM était juste, la déclaration non.
#4 still --time choisissait sa frame en parcourant les scènes, l'encodeur parcourt build_frame_tasks. Dès qu'il y a une transition, le still montre une image absente de la vidéo.
#5 --format raw sortait 120 frames là où le MP4 en sort 90 (scénario 2s+2s, fondu 1s) et n'appliquait aucun post-effet.
#7 GIF trop court : 100.0 / fps arrondi une fois puis réutilisé comme délai plat — 1,8 s pour un scénario de 2,0 s.
#8 Les stills JPEG échouaient systématiquement et laissaient un fichier vide (encodeur alimenté en RGBA). --format perdait aussi face à l'extension du fichier.
#9 L'encodage incrémental accumulait toutes les frames dans un all_yuv avant de démarrer l'encodeur — la vidéo entière en mémoire.
#10 L'extraction audio ffmpeg écrivait directement sur le chemin final : un échec laissait un WAV tronqué que les runs suivants prenaient pour un cache valide.

Notes d'implémentation

  • La constante de fréquence est désormais unique (audio::OUTPUT_SAMPLE_RATE) et lue par les deux muxers ; on ne peut plus en réintroduire une en dur sans le voir.
  • still --time et --format raw passent tous deux par build_frame_tasks + render_frame_task — le même chemin que l'encodeur, donc plus de divergence possible par construction.
  • Les délais GIF sont dérivés de bornes cumulées arrondies puis différenciées, ce qui borne l'erreur au lieu de l'accumuler. Le plancher de 2cs est conservé et testé.
  • L'extraction audio écrit dans un fichier voisin et ne promeut qu'en cas de succès.

Vérification

cargo test -p rustmotion -p rustmotion-cli sur cette branche seule (sans les trois autres lots du round 3) : 124 + 126 tests, 0 échec.

Seven confirmed audit findings, all in the same failure family: an output
path that quietly disagreed with the frame stream, the sample rate, or the
duration it advertised.

- Audio muxed at the wrong rate. `mix_audio_tracks` resamples every track
  to one constant; two muxer call sites hardcoded a *different* rate when
  declaring the track. The PCM was correct, the declaration was not, so
  playback drifted. Both sites now read `audio::OUTPUT_SAMPLE_RATE`, and
  hardcoding a rate next to it is no longer possible without noticing.
- `still --time` picked its frame by walking scenes directly, while the
  encoder walks `build_frame_tasks`. Any scenario with a transition made
  the two diverge: the still showed a frame the video never contains. It
  now goes through the same task stream.
- `--format raw` emitted 120 frames for a 2s+2s scenario with a 1s fade
  where the MP4 emits 90, and skipped post-effects entirely.
- GIF playback ran short: `100.0 / fps` was rounded once and reused as a
  flat per-frame delay, so the rounding error accumulated (1.8s for a 2.0s
  scenario). Delays are now derived from cumulative rounded boundaries,
  with the 2cs floor preserved.
- JPEG stills failed unconditionally and left a zero-byte file behind:
  the encoder was handed RGBA, which it cannot take. Now flattened onto an
  opaque background first. `--format` also no longer loses to the output
  file's extension.
- Incremental encoding rendered every dirty frame into one `all_yuv`
  buffer before starting the encoder, holding the whole video in memory.
  Rendering and encoding now interleave.
- `ffmpeg` audio extraction wrote straight to the destination path, so a
  failed run left a truncated WAV that later runs treated as a valid
  cache. It now writes to a sibling scratch file and promotes on success.

Tests: 124 + 126 pass on this branch alone, without the other round-3 lots.
@LeadcodeDev LeadcodeDev added the bug Something isn't working label Aug 8, 2026
@LeadcodeDev LeadcodeDev self-assigned this Aug 8, 2026
@LeadcodeDev
LeadcodeDev merged commit d174819 into chantier/audit-remediation Aug 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant