Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
61540ee
chore(release): bump to 1.8.0-rc.6 [skip ci]
github-actions[bot] Aug 1, 2026
203038e
fix(build): fall back to a mirror when ffmpeg.org refuses the runner
EtienneLescot Aug 1, 2026
babbd3d
fix(export): stop counting letterbox rows as an upscale
EtienneLescot Aug 3, 2026
eb69c65
fix(nix): make the v1.8.0 tag buildable, since the tag is what Nix us…
EtienneLescot Aug 3, 2026
58eb47a
fix(compositor): live preview holds the current frame instead of cons…
eduumach Aug 2, 2026
b82106c
fix(compositor): make the hold semantics safe on broken streams, and …
EtienneLescot Aug 3, 2026
a10558a
fix(compositor): hold the webcam at its EOF instead of restarting it …
EtienneLescot Aug 3, 2026
e4581db
fix(stt): transcribe in chunks, with progress and per-chunk retry
EtienneLescot Aug 3, 2026
df0a7e7
feat(stt): show real transcription progress instead of an endless spi…
EtienneLescot Aug 3, 2026
377f800
fix(stt): type the caption status mock for the progress fields
EtienneLescot Aug 3, 2026
44375c7
fix(stt): make the chunked run cancellable, and its failures legible
EtienneLescot Aug 3, 2026
7d001db
fix(ui): let the progress bar own its spacing, and name the model dow…
EtienneLescot Aug 3, 2026
245c88b
fix(build): bound a stalled ffmpeg fetch, and read the nix version
EtienneLescot Aug 3, 2026
b47c596
fix(ui): show the detected language where it can actually be seen
EtienneLescot Aug 3, 2026
e34633b
perf(transcript): stop re-rendering every word to move one underline
EtienneLescot Aug 3, 2026
c7e008e
feat(media): strip the Media timeline down to arranging
EtienneLescot Aug 3, 2026
63990dd
perf(waveform): share the decode; stop holding 714 MB to draw 400 bars
EtienneLescot Aug 3, 2026
925da78
perf(waveform): decode peaks natively, once per recording ever
EtienneLescot Aug 3, 2026
ebaae47
fix(timeline): drop the test's `any` and refresh the checklist's line…
EtienneLescot Aug 3, 2026
e66d213
chore(release): bump to 1.8.0-rc.7 [skip ci]
github-actions[bot] Aug 3, 2026
15802a9
fix(timeline): let a caller size a new region, and make one selection…
EtienneLescot Aug 3, 2026
c8fd6e5
fix(timeline): create pills at a readable size, and paste what was co…
EtienneLescot Aug 3, 2026
1d13bc6
chore(release): bump to 1.8.0-rc.8 [skip ci]
github-actions[bot] Aug 3, 2026
89cfb1e
fix(timeline): size a keyboard-created region by the zoom too
EtienneLescot Aug 4, 2026
fcb3558
i18n(timeline): label the annotation pill "Annotation", not "New anno…
EtienneLescot Aug 4, 2026
7c6b978
fix(timeline): create pills wide enough to read, not just to see
EtienneLescot Aug 4, 2026
f4d6b2f
feat(timeline): copy and paste trims too
EtienneLescot Aug 4, 2026
d075e85
chore(release): bump to 1.8.0-rc.9 [skip ci]
github-actions[bot] Aug 4, 2026
59f3f53
chore(release): bump to 1.8.0 [skip ci]
github-actions[bot] Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 69 additions & 7 deletions crates/compositor/src/linux_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use anyhow::{bail, Context, Result};
use std::ffi::CString;
use std::ptr;

use crate::timeline_walk::NextFrameTime;

use crate::ffi::{
av_frame_alloc, av_frame_free, av_frame_move_ref, av_frame_unref, av_packet_alloc,
av_packet_free, av_packet_unref, av_read_frame, av_seek_frame, avcodec_alloc_context3,
Expand Down Expand Up @@ -58,6 +60,11 @@ pub struct SwDecoder {
frame: *mut AVFrame,
sent_eof: bool,
cur_pts: Option<i64>,
/// Buffer de lookahead pour `peek_next_time_sec` : symétrique de
/// `pipeline_macos::Decoder::peek_frame` — cf. là-bas pour la justification.
peek_frame: *mut AVFrame,
/// `true` si `peek_frame` porte une frame décodée en attente de `commit_peek`.
has_peek: bool,
}

/// Libère toutes les ressources ffmpeg. `Drop` ne peut pas faillir ; on
Expand All @@ -78,6 +85,9 @@ impl Drop for SwDecoder {
if !self.pkt.is_null() {
av_packet_free(&mut self.pkt);
}
if !self.peek_frame.is_null() {
av_frame_free(&mut self.peek_frame);
}
}
}
}
Expand Down Expand Up @@ -177,7 +187,8 @@ impl SwDecoder {
};
let pkt = av_packet_alloc();
let frame = av_frame_alloc();
if pkt.is_null() || frame.is_null() {
let peek_frame = av_frame_alloc();
if pkt.is_null() || frame.is_null() || peek_frame.is_null() {
avcodec_free_context(&mut dec);
avformat_close_input(&mut fmt);
bail!("av_packet_alloc/av_frame_alloc (pompage sequentiel)");
Expand All @@ -192,6 +203,8 @@ impl SwDecoder {
frame,
sent_eof: false,
cur_pts: None,
peek_frame,
has_peek: false,
})
}

Expand All @@ -201,21 +214,33 @@ impl SwDecoder {
/// seek PAS : le decodeur garde son etat, donc une lecture sequentielle coute
/// UN packet par frame au lieu d'un re-parcours de demi-GOP.
pub unsafe fn next_frame(&mut self) -> Result<*mut AVFrame> {
if self.has_peek {
return self.commit_peek();
}
if !self.receive_into(self.frame)? {
return Ok(ptr::null_mut());
}
let pts = (*self.frame).best_effort_timestamp;
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
Ok(self.frame)
}

/// Décode dans `into` (buffer courant ou de lookahead) jusqu'à obtenir une frame ou
/// l'EOF — cf. `pipeline_macos::Decoder::receive_into` pour la justification.
unsafe fn receive_into(&mut self, into: *mut AVFrame) -> Result<bool> {
loop {
let r = avcodec_receive_frame(self.dec, self.frame);
let r = avcodec_receive_frame(self.dec, into);
if r == 0 {
let pts = (*self.frame).best_effort_timestamp;
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
return Ok(self.frame);
return Ok(true);
}
if r == AVERROR_EOF {
return Ok(ptr::null_mut());
return Ok(false);
}
if r != AVERROR_EAGAIN {
bail!("avcodec_receive_frame: {r}");
}
if self.sent_eof {
return Ok(ptr::null_mut());
return Ok(false);
}
let rr = av_read_frame(self.fmt, self.pkt);
if rr < 0 {
Expand All @@ -240,6 +265,41 @@ impl SwDecoder {
}
}

/// Décode la prochaine frame dans le buffer de lookahead et renvoie son temps.
/// Cf. `pipeline_macos::Decoder::peek_next_time_sec`.
pub(crate) unsafe fn peek_next_time_sec(&mut self) -> Result<NextFrameTime> {
if !self.has_peek {
if !self.receive_into(self.peek_frame)? {
return Ok(NextFrameTime::Eof);
}
self.has_peek = true;
}
let pts = (*self.peek_frame).best_effort_timestamp;
// Sans pts ni time_base exploitables on ne PEUT pas dire si la frame est due :
// `Unknown`, et non `0.0` — qui passait pour « due » à tous les coups.
Ok(if pts == i64::MIN || self.stream_timebase <= 0.0 {
NextFrameTime::Unknown
} else {
NextFrameTime::At(pts as f64 * self.stream_timebase)
})
}

/// Promeut la frame de lookahead au rang de frame courante. Cf.
/// `pipeline_macos::Decoder::commit_peek`.
pub(crate) unsafe fn commit_peek(&mut self) -> Result<*mut AVFrame> {
// `bail!` et non `debug_assert!` : compilée en release, l'assertion disparaissait
// et l'échange promouvait un `AVFrame` jamais rempli, avec un
// `best_effort_timestamp` indéterminé, jusque dans le chemin de présentation.
if !self.has_peek {
bail!("commit_peek sans peek_next_time_sec préalable");
}
std::mem::swap(&mut self.frame, &mut self.peek_frame);
self.has_peek = false;
let pts = (*self.frame).best_effort_timestamp;
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
Ok(self.frame)
}

/// Temps source (secondes) de la derniere frame rendue par `next_frame` /
/// `decode_at`, tire du pts REEL et non d'un compteur d'index.
pub fn cur_time_sec(&self) -> Option<f64> {
Expand All @@ -263,6 +323,8 @@ impl SwDecoder {
/// `AVERROR_INVALIDDATA` plutôt que de paniquer : la prochaine itération
/// lira le packet complet suivant.
pub unsafe fn decode_at(&mut self, frame_idx: u32) -> Result<*mut AVFrame> {
// Tout seek invalide un éventuel peek en attente — cf. pipeline_macos::Decoder::seek_to.
self.has_peek = false;
let fps = self.fps;
let target_ts = (frame_idx as f64 / fps) * 1_000_000.0; // AV_TIME_BASE = µs
// `AVSEEK_FLAG_BACKWARD` vaut 1, pas 4 — 4 est `AVSEEK_FLAG_ANY`. La constante
Expand Down
Loading
Loading