Files
leaguerecorder/record-daemon/src/recording/capture.rs

239 lines
6.8 KiB
Rust

//! Game capture source configuration using libobs-simple.
//!
//! This module provides capture sources for recording game footage,
//! including game capture, window capture, and monitor capture.
use serde::{Deserialize, Serialize};
/// Game capture source for recording game footage.
///
/// Uses OBS game capture for efficient GPU-based capture.
#[derive(Debug, Clone)]
pub struct GameCapture {
/// Source name.
pub name: String,
/// Window title to capture (if specific window).
pub window: Option<String>,
/// Process name to capture.
pub process_name: Option<String>,
/// Capture mode.
pub mode: CaptureMode,
/// Whether to capture cursor.
pub capture_cursor: bool,
/// Window class (Windows only).
pub window_class: Option<String>,
}
impl Default for GameCapture {
fn default() -> Self {
Self {
name: "Game Capture".to_string(),
window: None,
process_name: Some("League of Legends.exe".to_string()),
mode: CaptureMode::Process,
capture_cursor: false,
window_class: Some("RiotWindowClass".to_string()),
}
}
}
impl GameCapture {
/// Create a new game capture source.
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
..Default::default()
}
}
/// Create a game capture configured for League of Legends.
pub fn for_league_of_legends() -> Self {
Self {
name: "League of Legends Capture".to_string(),
window: Some("League of Legends (TM) Client".to_string()),
process_name: Some("League of Legends.exe".to_string()),
mode: CaptureMode::Window,
capture_cursor: false,
window_class: Some("RiotWindowClass".to_string()),
}
}
/// Set the window to capture.
pub fn with_window(mut self, window: &str) -> Self {
self.window = Some(window.to_string());
self.mode = CaptureMode::Window;
self
}
/// Set the process name to capture.
pub fn with_process(mut self, process: &str) -> Self {
self.process_name = Some(process.to_string());
self.mode = CaptureMode::Process;
self
}
/// Set the window class (Windows only).
pub fn with_window_class(mut self, class: &str) -> Self {
self.window_class = Some(class.to_string());
self
}
/// Set whether to capture the cursor.
pub fn with_cursor(mut self, capture: bool) -> Self {
self.capture_cursor = capture;
self
}
/// Get the window string for OBS in the format "Title:Class:Executable".
pub fn window_string(&self) -> Option<String> {
match (&self.window, &self.window_class, &self.process_name) {
(Some(window), Some(class), Some(process)) => {
Some(format!("{}:{}:{}", window, class, process))
}
(Some(window), None, Some(process)) => Some(format!("{}::{}", window, process)),
(Some(window), Some(class), None) => Some(format!("{}:{}:", window, class)),
_ => None,
}
}
}
/// Capture mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum CaptureMode {
/// Capture any fullscreen application.
Any,
/// Capture a specific window.
Window,
/// Capture a specific process.
#[default]
Process,
}
/// Window capture source (alternative to game capture).
///
/// Uses OBS window capture which works with more applications
/// but may have slightly higher overhead than game capture.
#[derive(Debug, Clone)]
pub struct WindowCapture {
/// Source name.
pub name: String,
/// Window title.
pub window_title: String,
/// Window class (X11/Windows).
pub window_class: Option<String>,
/// Whether to capture cursor.
pub capture_cursor: bool,
}
impl WindowCapture {
/// Create a new window capture source.
pub fn new(name: &str, window_title: &str) -> Self {
Self {
name: name.to_string(),
window_title: window_title.to_string(),
window_class: None,
capture_cursor: false,
}
}
/// Set the window class (for X11/Windows).
pub fn with_class(mut self, class: &str) -> Self {
self.window_class = Some(class.to_string());
self
}
/// Set whether to capture the cursor.
pub fn with_cursor(mut self, capture: bool) -> Self {
self.capture_cursor = capture;
self
}
}
/// Monitor capture source (fallback).
///
/// Captures an entire monitor. Useful as a fallback when
/// game capture or window capture don't work.
#[derive(Debug, Clone)]
pub struct MonitorCapture {
/// Source name.
pub name: String,
/// Monitor index (0-based).
pub monitor: u32,
/// Whether to capture cursor.
pub capture_cursor: bool,
}
impl MonitorCapture {
/// Create a new monitor capture source.
pub fn new(name: &str, monitor: u32) -> Self {
Self {
name: name.to_string(),
monitor,
capture_cursor: false,
}
}
/// Set whether to capture the cursor.
pub fn with_cursor(mut self, capture: bool) -> Self {
self.capture_cursor = capture;
self
}
}
/// Capture source type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceType {
/// Game capture source.
GameCapture,
/// Window capture source.
WindowCapture,
/// Monitor capture source.
MonitorCapture,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_game_capture_creation() {
let capture = GameCapture::new("Test Capture");
assert_eq!(capture.name, "Test Capture");
assert_eq!(capture.mode, CaptureMode::Process);
}
#[test]
fn test_game_capture_with_process() {
let capture = GameCapture::new("Test").with_process("League of Legends.exe");
assert_eq!(
capture.process_name,
Some("League of Legends.exe".to_string())
);
assert_eq!(capture.mode, CaptureMode::Process);
}
#[test]
fn test_league_capture_config() {
let capture = GameCapture::for_league_of_legends();
assert_eq!(capture.name, "League of Legends Capture");
assert_eq!(
capture.process_name,
Some("League of Legends.exe".to_string())
);
assert_eq!(capture.window_class, Some("RiotWindowClass".to_string()));
}
#[test]
fn test_window_string() {
let capture = GameCapture::for_league_of_legends();
let window_str = capture.window_string();
assert!(window_str.is_some());
let s = window_str.unwrap();
assert!(s.contains("League of Legends"));
assert!(s.contains("RiotWindowClass"));
}
}