fix: fail on refused portal requests and catch black recordings
The portal reports refusals only in OBS's log; a denied, cancelled or failed source selection now fails with an actionable reason instead of waiting forever. check --expect-motion rejects black or frozen footage, and the hardware script checks only runs that recorded, tests restore tokens in their own step, and treats closing a window as informational: on Hyprland the stream keeps its last frame, so sessions must end on game exit.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
//! [--seconds N] [--segment-seconds N] [--keyint N] [--bitrate KBPS]
|
||||
//! [--width W --height H --fps F] [--no-audio] [--mic]
|
||||
//! [--min-free-mb MB] [--out DIR]
|
||||
//! clipbin-probe check [--codec h264|hevc|av1] [--segment-seconds N] [--out DIR]
|
||||
//! clipbin-probe check [--codec h264|hevc|av1] [--segment-seconds N] [--expect-motion] [--out DIR]
|
||||
//!
|
||||
//! `check` inspects the segments `record` wrote, using the ffprobe and ffmpeg
|
||||
//! command-line tools (install FFmpeg; on Windows, e.g. `winget install ffmpeg`).
|
||||
@@ -63,6 +63,7 @@ fn usage(error: &str) -> ExitCode {
|
||||
|
||||
struct Options {
|
||||
verify: bool,
|
||||
expect_motion: bool,
|
||||
target: String,
|
||||
window: String,
|
||||
restore_token: Option<String>,
|
||||
@@ -83,6 +84,7 @@ impl Options {
|
||||
fn parse(args: &[String]) -> Result<Self, String> {
|
||||
let mut options = Self {
|
||||
verify: false,
|
||||
expect_motion: false,
|
||||
target: "monitor".into(),
|
||||
window: String::new(),
|
||||
restore_token: None,
|
||||
@@ -112,6 +114,7 @@ impl Options {
|
||||
};
|
||||
match flag.as_str() {
|
||||
"--verify" => options.verify = true,
|
||||
"--expect-motion" => options.expect_motion = true,
|
||||
"--target" => options.target = value()?,
|
||||
"--window" => options.window = value()?,
|
||||
"--restore-token" => options.restore_token = Some(value()?),
|
||||
@@ -337,11 +340,11 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
next_sample += Duration::from_secs(1);
|
||||
let health = recording.health();
|
||||
println!(
|
||||
"t={} health source={}x{} streaming={:?} active={} frames={} dropped={} lagged={} muxer_bytes={} disk_bytes={}",
|
||||
"t={} health source={}x{} pipewire={:?} active={} frames={} dropped={} lagged={} muxer_bytes={} disk_bytes={}",
|
||||
elapsed(),
|
||||
health.source_width,
|
||||
health.source_height,
|
||||
health.source_streaming,
|
||||
health.pipewire,
|
||||
health.output_active,
|
||||
health.total_frames,
|
||||
health.dropped_frames,
|
||||
@@ -466,6 +469,27 @@ 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.
|
||||
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;
|
||||
ok &= moving;
|
||||
println!("motion ok={moving} distinct_frames={frames} segment={}", middle.display());
|
||||
}
|
||||
|
||||
// A clip spanning the boundaries between the first three segments.
|
||||
let list = options.out.join("concat.txt");
|
||||
let entries: String = segments[..3]
|
||||
|
||||
Reference in New Issue
Block a user