feat: automatically record Steam game sessions

Detect games with native process exit watchers, own OBS on a background worker, and persist settings and recoverable session timelines. Place segments by split frame counts, recover capture with honest gaps, and finalise on game exit or explicit Quit.

Keep the current recording alive after GPUI closes its last window, then exit when the game ends. Add an end-to-end rig covering recording, recovery, exact packet-count offsets, disk library reload and clean Quit.
This commit is contained in:
2026-10-08 17:30:40 +02:00
parent 98fe898efb
commit 435ddf2b17
21 changed files with 1872 additions and 38 deletions
+30
View File
@@ -0,0 +1,30 @@
//! A split racing with stop can leave only a Matroska header, with no footage.
use std::{
fs::File,
io::{self, Read},
path::Path,
};
/// The pinned muxer writes its first Cluster immediately after a small header.
/// Inspect only that prefix, so checking a completed segment never reads its footage.
pub fn has_cluster(path: &Path) -> io::Result<bool> {
cluster_in(File::open(path)?)
}
fn cluster_in(reader: impl Read) -> io::Result<bool> {
let mut prefix = Vec::new();
reader.take(64 * 1024).read_to_end(&mut prefix)?;
Ok(prefix.windows(4).any(|id| id == [0x1f, 0x43, 0xb6, 0x75]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distinguishes_a_header_only_tail_from_media() {
let header = [0x1a, 0x45, 0xdf, 0xa3, 0x80, 0x18, 0x53, 0x80, 0x67, 0xff];
assert!(!cluster_in(header.as_slice()).unwrap());
let mut media = header.to_vec();
media.extend([0x1f, 0x43, 0xb6, 0x75, 0x84, 0xe7, 0x82, 0, 0]);
assert!(cluster_in(media.as_slice()).unwrap());
}
}