fix: time out a stalled portal and sample motion across segments
Rust checks / windows-cross-check (push) Successful in 41s
Rust checks / linux (push) Failing after 5m39s

Hyprland's portal sometimes creates the session and never answers source
selection, before any picker appears. Recording now fails if the portal has
not reached its picker within 10 s; time in the picker is not limited. The
motion check uses the segment with the most motion, since capture can start
late.
This commit is contained in:
2026-10-08 15:16:52 +02:00
parent 06377de707
commit afc54b363e
4 changed files with 135 additions and 20 deletions
+40 -16
View File
@@ -470,24 +470,24 @@ fn check(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
}
// A black or frozen capture still produces valid segments. When the
// captured content was moving, the middle segment must show it.
// captured content was moving, some segment must show it (capture may
// start late, after the user answers the picker).
if options.expect_motion {
let middle = &segments[segments.len() / 2];
let distinct = Command::new("ffmpeg")
.args(["-v", "error", "-i"])
.arg(middle)
.args(["-map", "0:v:0", "-vf", "mpdecimate=max=0", "-f", "null", "-", "-stats_period", "100"])
.args(["-progress", "pipe:1"])
.output()?;
let frames = String::from_utf8_lossy(&distinct.stdout)
.lines()
.filter_map(|line| line.strip_prefix("frame="))
.filter_map(|frames| frames.parse::<u64>().ok())
.last()
.unwrap_or(0);
let moving = frames > 1;
let mut most = (0, None);
for segment in &segments {
let frames = distinct_frames(segment)?;
if frames > most.0 {
most = (frames, Some(segment));
}
}
let moving = most.0 > 1;
ok &= moving;
println!("motion ok={moving} distinct_frames={frames} segment={}", middle.display());
println!(
"motion ok={moving} distinct_frames={} segment={}",
most.0,
most.1
.map_or(String::new(), |segment| segment.display().to_string())
);
}
// A clip spanning the boundaries between the first three segments.
@@ -540,6 +540,30 @@ fn check(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
Ok(ok)
}
/// Frames in a segment's video that differ from the previous one.
fn distinct_frames(segment: &Path) -> Result<u64, Box<dyn std::error::Error>> {
let output = Command::new("ffmpeg")
.args(["-v", "error", "-i"])
.arg(segment)
.args([
"-map",
"0:v:0",
"-vf",
"mpdecimate=max=0",
"-f",
"null",
"-",
])
.args(["-progress", "pipe:1"])
.output()?;
Ok(String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| line.strip_prefix("frame="))
.filter_map(|frames| frames.parse().ok())
.next_back()
.unwrap_or(0))
}
fn ffprobe(path: &Path, query: &[&str]) -> Result<String, Box<dyn std::error::Error>> {
let output = Command::new("ffprobe")
.args(["-v", "error"])