feat: record at the source's size, only once capture works
Encoders and the segment output are created when the capture source first has a picture, at its own size (portrait monitors record portrait) unless a resolution is configured, so nothing is written while waiting for a source. SPEC.md records both decisions; validation docs cover how sessions should end when the game closes.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
//! clipbin-probe record [--target monitor|window|game] [--window OBS_WINDOW]
|
||||
//! [--restore-token TOKEN] [--codec h264|hevc|av1] [--encoder ID]
|
||||
//! [--seconds N] [--segment-seconds N] [--keyint N] [--bitrate KBPS]
|
||||
//! [--width W --height H --fps F] [--no-audio] [--mic]
|
||||
//! [--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] [--expect-motion] [--out DIR]
|
||||
//!
|
||||
@@ -24,7 +24,7 @@ use std::{
|
||||
|
||||
use clipbin_core::{RecordingStatus, VideoCodec};
|
||||
use clipbin_obs::{
|
||||
Engine, RecorderEvent, RecordingConfig, RuntimeLayout, StatusTracker, StopReason, VideoFormat,
|
||||
Engine, RecorderEvent, RecordingConfig, RuntimeLayout, StatusTracker, StopReason,
|
||||
capture::{self, CaptureTarget, MonitorMatch, WindowMatch},
|
||||
encoders::ALL_CODECS,
|
||||
};
|
||||
@@ -73,7 +73,10 @@ struct Options {
|
||||
segment_seconds: i64,
|
||||
keyint: i64,
|
||||
bitrate: i64,
|
||||
format: VideoFormat,
|
||||
/// The recording size; by default it follows the captured source.
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
frames_per_second: u32,
|
||||
audio: bool,
|
||||
mic: bool,
|
||||
min_free_mb: u64,
|
||||
@@ -94,11 +97,9 @@ impl Options {
|
||||
segment_seconds: 2,
|
||||
keyint: 1,
|
||||
bitrate: 20_000,
|
||||
format: VideoFormat {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
frames_per_second: 60,
|
||||
},
|
||||
width: None,
|
||||
height: None,
|
||||
frames_per_second: 60,
|
||||
audio: true,
|
||||
mic: false,
|
||||
min_free_mb: StatusTracker::DEFAULT_MIN_FREE_BYTES / 1_000_000,
|
||||
@@ -124,9 +125,9 @@ impl Options {
|
||||
"--segment-seconds" => options.segment_seconds = number(value()?)?,
|
||||
"--keyint" => options.keyint = number(value()?)?,
|
||||
"--bitrate" => options.bitrate = number(value()?)?,
|
||||
"--width" => options.format.width = number(value()?)? as u32,
|
||||
"--height" => options.format.height = number(value()?)? as u32,
|
||||
"--fps" => options.format.frames_per_second = number(value()?)? as u32,
|
||||
"--width" => options.width = Some(number(value()?)? as u32),
|
||||
"--height" => options.height = Some(number(value()?)? as u32),
|
||||
"--fps" => options.frames_per_second = number(value()?)? as u32,
|
||||
"--no-audio" => options.audio = false,
|
||||
"--mic" => options.mic = true,
|
||||
"--min-free-mb" => options.min_free_mb = number(value()?)? as u64,
|
||||
@@ -137,6 +138,14 @@ impl Options {
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
fn resolution(&self) -> Result<Option<(u32, u32)>, String> {
|
||||
match (self.width, self.height) {
|
||||
(Some(width), Some(height)) => Ok(Some((width, height))),
|
||||
(None, None) => Ok(None),
|
||||
_ => Err("--width and --height go together".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_target(&self) -> Result<CaptureTarget, String> {
|
||||
let window = WindowMatch {
|
||||
obs_window: self.window.clone(),
|
||||
@@ -180,7 +189,7 @@ fn start_engine(options: &Options) -> Result<Engine, Box<dyn std::error::Error>>
|
||||
"runtime_obs_version={}",
|
||||
layout.version().unwrap_or_default()
|
||||
);
|
||||
let engine = Engine::start(&layout, options.format)?;
|
||||
let engine = Engine::start(&layout, options.frames_per_second)?;
|
||||
println!("obs_version={}", engine.obs_version());
|
||||
for plugin in engine.plugins_failed() {
|
||||
println!("plugin_not_initialised={plugin}");
|
||||
@@ -215,6 +224,7 @@ fn encoders(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
video_source: None,
|
||||
system_audio: None,
|
||||
microphone: None,
|
||||
resolution: Some(options.resolution()?.unwrap_or((1920, 1080))),
|
||||
video_encoder: encoder.clone(),
|
||||
video_bitrate_kbps: options.bitrate,
|
||||
keyframe_interval_seconds: options.keyint,
|
||||
@@ -245,8 +255,9 @@ fn verify_encoder(engine: &Engine, config: RecordingConfig) -> Result<u64, Strin
|
||||
let recording = engine
|
||||
.start_recording(config)
|
||||
.map_err(|error| error.to_string())?;
|
||||
recording.poll().map_err(|error| error.to_string())?;
|
||||
std::thread::sleep(Duration::from_secs(3));
|
||||
let health = recording.health();
|
||||
let health = recording.poll().map_err(|error| error.to_string())?;
|
||||
recording.stop();
|
||||
let stop = wait_for_stop(&recording, Duration::from_secs(10));
|
||||
match stop {
|
||||
@@ -311,6 +322,7 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
video_source: Some(video_source),
|
||||
system_audio: options.audio.then(capture::system_audio_source),
|
||||
microphone: options.mic.then(capture::microphone_source),
|
||||
resolution: options.resolution()?,
|
||||
video_encoder: encoder,
|
||||
video_bitrate_kbps: options.bitrate,
|
||||
keyframe_interval_seconds: options.keyint,
|
||||
@@ -338,7 +350,7 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let now = Instant::now();
|
||||
if now >= next_sample {
|
||||
next_sample += Duration::from_secs(1);
|
||||
let health = recording.health();
|
||||
let health = recording.poll()?;
|
||||
println!(
|
||||
"t={} health source={}x{} pipewire={:?} active={} frames={} dropped={} lagged={} muxer_bytes={} disk_bytes={}",
|
||||
elapsed(),
|
||||
@@ -361,6 +373,9 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
recording.stop();
|
||||
}
|
||||
match recording.events().recv_timeout(Duration::from_millis(100)) {
|
||||
Ok(RecorderEvent::OutputSized { width, height }) => {
|
||||
println!("t={} recording_size={width}x{height}", elapsed())
|
||||
}
|
||||
Ok(RecorderEvent::OutputStarted) => println!("t={} output_started", elapsed()),
|
||||
Ok(RecorderEvent::SegmentStarted { path }) => {
|
||||
println!("t={} segment_started={path}", elapsed())
|
||||
|
||||
Reference in New Issue
Block a user