chore: Clean up MP4 files and test scripts from repository
This commit is contained in:
parent
0dc63cf9ad
commit
5cb25e3ba9
32 changed files with 5 additions and 809 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1 +1,6 @@
|
|||
/target
|
||||
*.mp4
|
||||
scratch/
|
||||
*.orig
|
||||
*.obOCNTS
|
||||
*.py
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,16 +0,0 @@
|
|||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/capture/audio.rs").unwrap();
|
||||
code = code.replace(
|
||||
"pub fn new(sender: Sender<Vec<f32>>) -> Result<Self, Box<dyn std::error::Error>> {",
|
||||
"pub fn new(sender: Sender<Vec<f32>>) -> Result<(Self, u32, u16), Box<dyn std::error::Error>> {"
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
"Ok(Self { _stream: stream })",
|
||||
"Ok((Self { _stream: stream }, sample_rate, channels))"
|
||||
);
|
||||
|
||||
fs::write("src/capture/audio.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/bin/vrec-daemon.rs").unwrap();
|
||||
|
||||
code = code.replace(
|
||||
"let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);",
|
||||
"let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);\n let (audio_tx, audio_rx) = bounded::<Vec<f32>>(100);\n let audio_info = vrec::capture::audio::AudioCapture::new(audio_tx).ok();"
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
"let codec_ctx_ptr = encoder.codec_ctx() as usize;",
|
||||
r#"let codec_ctx_ptr = encoder.codec_ctx() as usize;
|
||||
|
||||
let mut audio_encoder = if let Some(ref info) = audio_info {
|
||||
vrec::encoder::AudioEncoder::new(info.1 as i32, info.2 as i32).ok()
|
||||
} else { None };
|
||||
let audio_codec_ctx_ptr = audio_encoder.as_ref().map(|e| e.codec_ctx() as usize);"#
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
"let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 60).max(60) as usize);",
|
||||
"let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 120).max(120) as usize);"
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
"let new_capacity = (config.replay_duration_sec * 60).max(60) as usize;",
|
||||
"let new_capacity = (config.replay_duration_sec * 120).max(120) as usize;"
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
"let mut muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).unwrap() };",
|
||||
"let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);\n let mut muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).unwrap() };"
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
"normal_muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).ok() };",
|
||||
"let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);\n normal_muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).ok() };"
|
||||
);
|
||||
|
||||
// Add the audio pumping logic inside the while let Ok(frame) loop
|
||||
code = code.replace(
|
||||
"if let Ok(packets) = encoder.encode_frame(&frame) {",
|
||||
r#"while let Ok(audio_chunk) = audio_rx.try_recv() {
|
||||
if let Some(enc) = audio_encoder.as_mut() {
|
||||
if let Ok(audio_packets) = enc.encode_pcm(&audio_chunk) {
|
||||
for mut pkt in audio_packets {
|
||||
pkt.set_stream_index(1);
|
||||
if config.replay_enabled {
|
||||
ring.push_overwrite(pkt.clone());
|
||||
}
|
||||
if normal_recording && !normal_waiting_keyframe {
|
||||
if let Some(muxer) = normal_muxer.as_mut() {
|
||||
let _ = muxer.write_packet(&pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(packets) = encoder.encode_frame(&frame) {"#
|
||||
);
|
||||
|
||||
// For video packets, set stream index to 0
|
||||
code = code.replace(
|
||||
"for pkt in packets {",
|
||||
"for mut pkt in packets {\n pkt.set_stream_index(0);"
|
||||
);
|
||||
|
||||
fs::write("src/bin/vrec-daemon.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
--- src/capture/audio.rs
|
||||
+++ src/capture/audio.rs
|
||||
@@ -12,7 +12,7 @@
|
||||
let device = host.default_input_device().ok_or("No audio input device available")?;
|
||||
|
||||
let config = device.default_input_config()?;
|
||||
- let sample_rate = config.sample_rate().0;
|
||||
+ let sample_rate = config.sample_rate().0; // Let's just fix the into() first
|
||||
let channels = config.channels();
|
||||
|
||||
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
|
||||
@@ -22,7 +22,7 @@
|
||||
let stream = match config.sample_format() {
|
||||
cpal::SampleFormat::F32 => {
|
||||
device.build_input_stream(
|
||||
- &config.into(),
|
||||
+ &config.clone().into(),
|
||||
move |data: &[f32], _: &_| {
|
||||
let _ = sender.try_send(data.to_vec());
|
||||
},
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
--- src/capture/audio.rs
|
||||
+++ src/capture/audio.rs
|
||||
@@ -11,7 +11,7 @@
|
||||
let device = host.default_input_device().ok_or("No audio input device available")?;
|
||||
|
||||
let config = device.default_input_config()?;
|
||||
- let sample_rate = config.sample_rate().0;
|
||||
+ let sample_rate = config.sample_rate().0; // will fix this with sed
|
||||
let channels = config.channels();
|
||||
|
||||
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
|
||||
@@ -21,7 +21,7 @@
|
||||
let stream = match config.sample_format() {
|
||||
cpal::SampleFormat::F32 => {
|
||||
device.build_input_stream(
|
||||
- &stream_config,
|
||||
+ stream_config.clone(),
|
||||
move |data: &[f32], _: &_| {
|
||||
let _ = sender.try_send(data.to_vec());
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
cpal::SampleFormat::I16 => {
|
||||
device.build_input_stream(
|
||||
- &stream_config,
|
||||
+ stream_config,
|
||||
move |data: &[i16], _: &_| {
|
||||
let f32_data: Vec<f32> = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect();
|
||||
let _ = sender.try_send(f32_data);
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
--- src/capture/audio.rs
|
||||
+++ src/capture/audio.rs
|
||||
@@ -11,7 +11,7 @@
|
||||
let device = host.default_input_device().ok_or("No audio input device available")?;
|
||||
|
||||
let config = device.default_input_config()?;
|
||||
- let sample_rate = config.sample_rate().0; // Let's just use .0 for now but wait, the compiler said u32!
|
||||
+ let sample_rate = config.sample_rate().0;
|
||||
let channels = config.channels();
|
||||
|
||||
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
|
||||
@@ -19,10 +19,10 @@
|
||||
let err_fn = |err| eprintln!("Audio stream error: {}", err);
|
||||
let stream_config: cpal::StreamConfig = config.clone().into();
|
||||
|
||||
- let stream = match stream_config.sample_rate.0 {
|
||||
- _ => {
|
||||
+ let stream = match config.sample_format() {
|
||||
+ cpal::SampleFormat::F32 => {
|
||||
device.build_input_stream(
|
||||
- &stream_config,
|
||||
+ &stream_config,
|
||||
move |data: &[f32], _: &_| {
|
||||
let _ = sender.try_send(data.to_vec());
|
||||
},
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
--- src/overlay.rs
|
||||
+++ src/overlay.rs
|
||||
@@ -16,11 +16,11 @@
|
||||
.default_height(80)
|
||||
.build();
|
||||
|
||||
- window.init_layer_shell();
|
||||
- window.set_layer(Layer::Overlay);
|
||||
- window.set_namespace("vrec-overlay");
|
||||
- window.set_margin(gtk_layer_shell::Edge::Top, 40);
|
||||
- window.set_anchor(gtk_layer_shell::Edge::Top, true);
|
||||
+ gtk_layer_shell::init_for_window(&window);
|
||||
+ gtk_layer_shell::set_layer(&window, Layer::Overlay);
|
||||
+ gtk_layer_shell::set_namespace(&window, "vrec-overlay");
|
||||
+ gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, 40);
|
||||
+ gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, true);
|
||||
|
||||
// Make it transparent and borderless
|
||||
let css_provider = CssProvider::new();
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
--- src/overlay.rs
|
||||
+++ src/overlay.rs
|
||||
@@ -16,11 +16,11 @@
|
||||
.default_height(80)
|
||||
.build();
|
||||
|
||||
- gtk_layer_shell::init_for_window(&window);
|
||||
- gtk_layer_shell::set_layer(&window, Layer::Overlay);
|
||||
- gtk_layer_shell::set_namespace(&window, "vrec-overlay");
|
||||
- gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Top, 40);
|
||||
- gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, true);
|
||||
+ window.init_layer_shell();
|
||||
+ window.set_layer(Layer::Overlay);
|
||||
+ window.set_layer_shell_namespace("vrec-overlay");
|
||||
+ window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 40);
|
||||
+ window.set_anchor(gtk_layer_shell::Edge::Top, true);
|
||||
|
||||
// Make it transparent and borderless
|
||||
let css_provider = CssProvider::new();
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
--- src/config.rs
|
||||
+++ src/config.rs
|
||||
@@ -1,6 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
+use std::env;
|
||||
+use std::os::unix::net::UnixStream;
|
||||
+use std::io::Write;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct VrecConfig {
|
||||
@@ -51,4 +54,14 @@
|
||||
fs::write(path, data)?;
|
||||
Ok(())
|
||||
}
|
||||
+
|
||||
+ pub fn notify_daemon_reload() {
|
||||
+ let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
|
||||
+ if let Ok(mut stream) = UnixStream::connect(&socket_path) {
|
||||
+ if let Ok(payload) = serde_json::to_vec(&crate::ipc::Command::ReloadConfig) {
|
||||
+ let len_buf = (payload.len() as u32).to_le_bytes();
|
||||
+ let _ = stream.write_all(&len_buf);
|
||||
+ let _ = stream.write_all(&payload);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
--- src/bin/vrec-daemon.rs
|
||||
+++ src/bin/vrec-daemon.rs
|
||||
@@ -62,6 +62,8 @@
|
||||
let mut config = vrec::config::VrecConfig::load();
|
||||
let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 60).max(60) as usize);
|
||||
let mut normal_muxer: Option<Muxer> = None;
|
||||
+ let mut normal_recording = false;
|
||||
+ let mut normal_waiting_keyframe = false;
|
||||
|
||||
thread::spawn(move || {
|
||||
while let Ok(drain) = mux_rx.recv() {
|
||||
@@ -108,16 +110,14 @@
|
||||
}
|
||||
},
|
||||
Command::StartRecording => {
|
||||
- if config.record_enabled && normal_muxer.is_none() {
|
||||
- let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
|
||||
- let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
|
||||
- let name = format!("record_{}.mp4", ts);
|
||||
- normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() };
|
||||
- println!("Started normal recording to {}", name);
|
||||
+ if config.record_enabled && !normal_recording {
|
||||
+ normal_recording = true;
|
||||
+ normal_waiting_keyframe = true;
|
||||
+ println!("StartRecording requested, waiting for keyframe...");
|
||||
}
|
||||
},
|
||||
Command::StopRecording => {
|
||||
- if let Some(mut m) = normal_muxer.take() {
|
||||
+ if let Some(mut m) = normal_muxer.take() {
|
||||
let _ = m.finalize();
|
||||
println!("Stopped normal recording.");
|
||||
}
|
||||
+ normal_recording = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
@@ -129,7 +129,19 @@
|
||||
ring.push_overwrite(pkt.clone());
|
||||
}
|
||||
- if let Some(muxer) = normal_muxer.as_mut() {
|
||||
- let _ = muxer.write_packet(&pkt);
|
||||
+ if normal_recording {
|
||||
+ if normal_waiting_keyframe {
|
||||
+ if pkt.is_keyframe() {
|
||||
+ normal_waiting_keyframe = false;
|
||||
+ let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
|
||||
+ let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
|
||||
+ let name = format!("record_{}.mp4", ts);
|
||||
+ normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() };
|
||||
+ println!("Started normal recording to {}", name);
|
||||
+ }
|
||||
+ }
|
||||
+ if !normal_waiting_keyframe {
|
||||
+ if let Some(muxer) = normal_muxer.as_mut() {
|
||||
+ let _ = muxer.write_packet(&pkt);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/muxer.rs").unwrap();
|
||||
code = code.replace(
|
||||
"pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext) -> Result<Self, String> {",
|
||||
"pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext, audio_codec_ctx: Option<*mut AVCodecContext>) -> Result<Self, String> {"
|
||||
);
|
||||
|
||||
code = code.replace(
|
||||
r#" let ret = avcodec_parameters_from_context((*stream).codecpar, codec_ctx);
|
||||
if ret < 0 {
|
||||
return Err("Failed to copy codec parameters".into());
|
||||
}"#,
|
||||
r#" (*stream).id = 0;
|
||||
let ret = avcodec_parameters_from_context((*stream).codecpar, codec_ctx);
|
||||
if ret < 0 {
|
||||
return Err("Failed to copy video codec parameters".into());
|
||||
}
|
||||
|
||||
if let Some(actx) = audio_codec_ctx {
|
||||
let astream = avformat_new_stream(fmt_ctx, std::ptr::null());
|
||||
if !astream.is_null() {
|
||||
(*astream).id = 1;
|
||||
if avcodec_parameters_from_context((*astream).codecpar, actx) < 0 {
|
||||
eprintln!("Failed to copy audio codec parameters");
|
||||
}
|
||||
}
|
||||
}"#
|
||||
);
|
||||
|
||||
fs::write("src/muxer.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
use std::fs;
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/encoder.rs").unwrap();
|
||||
code = code.replace(
|
||||
"(*codec_ctx).sample_rate = sample_rate;",
|
||||
"(*codec_ctx).sample_rate = sample_rate;\n (*codec_ctx).time_base = AVRational { num: 1, den: sample_rate };"
|
||||
);
|
||||
fs::write("src/encoder.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/encoder.rs").unwrap();
|
||||
code = code.replace(
|
||||
"(*codec_ctx).channel_layout = if channels == 2 { AV_CH_LAYOUT_STEREO } else { AV_CH_LAYOUT_MONO } as u64;",
|
||||
"av_channel_layout_default(&mut (*codec_ctx).ch_layout, channels);"
|
||||
);
|
||||
|
||||
// For SwrContext, swr_alloc_set_opts is deprecated in favor of swr_alloc_set_opts2
|
||||
// But swr_alloc_set_opts2 requires passing pointer to AVChannelLayout.
|
||||
code = code.replace(
|
||||
r#" let mut swr_ctx = swr_alloc();
|
||||
av_opt_set_int(swr_ctx as _, c"in_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
|
||||
av_opt_set_int(swr_ctx as _, c"in_sample_rate".as_ptr(), sample_rate as i64, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx as _, c"in_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLT, 0);
|
||||
|
||||
av_opt_set_int(swr_ctx as _, c"out_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
|
||||
av_opt_set_int(swr_ctx as _, c"out_sample_rate".as_ptr(), sample_rate as i64, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx as _, c"out_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLTP, 0);
|
||||
|
||||
swr_init(swr_ctx);"#,
|
||||
r#" let mut swr_ctx: *mut SwrContext = std::ptr::null_mut();
|
||||
swr_alloc_set_opts2(
|
||||
&mut swr_ctx,
|
||||
&(*codec_ctx).ch_layout,
|
||||
AVSampleFormat::AV_SAMPLE_FMT_FLTP,
|
||||
sample_rate,
|
||||
&(*codec_ctx).ch_layout,
|
||||
AVSampleFormat::AV_SAMPLE_FMT_FLT,
|
||||
sample_rate,
|
||||
0,
|
||||
std::ptr::null_mut()
|
||||
);
|
||||
swr_init(swr_ctx);"#
|
||||
);
|
||||
|
||||
code = code.replace("(*in_frame).channel_layout = (*self.codec_ctx).channel_layout;", "(*in_frame).ch_layout = (*self.codec_ctx).ch_layout;");
|
||||
code = code.replace("(*out_frame).channel_layout = (*self.codec_ctx).channel_layout;", "(*out_frame).ch_layout = (*self.codec_ctx).ch_layout;");
|
||||
code = code.replace("(*enc_frame).channel_layout = (*self.codec_ctx).channel_layout;", "(*enc_frame).ch_layout = (*self.codec_ctx).ch_layout;");
|
||||
|
||||
fs::write("src/encoder.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
use std::fs;
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/muxer.rs").unwrap();
|
||||
code = code.replace(
|
||||
"if ret < 0 {\n return Err(\"Failed to copy video codec parameters\".into());\n }",
|
||||
"if ret < 0 {\n return Err(\"Failed to copy video codec parameters\".into());\n }\n (*stream).time_base = (*codec_ctx).time_base;"
|
||||
);
|
||||
code = code.replace(
|
||||
"eprintln!(\"Failed to copy audio codec parameters\");\n }",
|
||||
"eprintln!(\"Failed to copy audio codec parameters\");\n }\n (*astream).time_base = (*actx).time_base;"
|
||||
);
|
||||
fs::write("src/muxer.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
use std::fs;
|
||||
fn main() {
|
||||
let mut code = fs::read_to_string("src/ring.rs").unwrap();
|
||||
code = code.replace(
|
||||
"pub fn is_keyframe(&self) -> bool {",
|
||||
"pub fn stream_index(&self) -> i32 {
|
||||
unsafe { (*self.ptr).stream_index }
|
||||
}
|
||||
|
||||
pub fn set_stream_index(&mut self, idx: i32) {
|
||||
unsafe { (*self.ptr).stream_index = idx; }
|
||||
}
|
||||
|
||||
pub fn is_keyframe(&self) -> bool {"
|
||||
);
|
||||
fs::write("src/ring.rs", code).unwrap();
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
fn main() { }
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
use ffmpeg_next::ffi::*;
|
||||
fn main() {
|
||||
unsafe {
|
||||
let codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_AAC);
|
||||
if codec.is_null() {
|
||||
println!("AAC encoder not found");
|
||||
} else {
|
||||
println!("AAC encoder found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
--- src/bin/vrec-ui.rs
|
||||
+++ src/bin/vrec-ui.rs
|
||||
@@ -1,18 +1,8 @@
|
||||
-use vrec::ipc::Command;
|
||||
+use vrec::ipc::{Command, send_command};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::io::Write;
|
||||
use std::env;
|
||||
use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent};
|
||||
-
|
||||
-fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
- let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
|
||||
- let mut stream = UnixStream::connect(&socket_path)?;
|
||||
- let payload = serde_json::to_vec(&cmd)?;
|
||||
- let len_buf = (payload.len() as u32).to_le_bytes();
|
||||
- stream.write_all(&len_buf)?;
|
||||
- stream.write_all(&payload)?;
|
||||
- Ok(())
|
||||
-}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
--- src/bin/vrec-ui.rs
|
||||
+++ src/bin/vrec-ui.rs
|
||||
@@ -16,6 +16,19 @@
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
+ let args: Vec<String> = env::args().collect();
|
||||
+ if args.len() > 1 {
|
||||
+ match args[1].as_str() {
|
||||
+ "--save" => {
|
||||
+ return send_command(Command::SaveReplay).map_err(|e| e.into());
|
||||
+ }
|
||||
+ "--menu" => {
|
||||
+ vrec::overlay::show_saved_overlay();
|
||||
+ return Ok(());
|
||||
+ }
|
||||
+ _ => {}
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
println!("Starting vrec UI/Hotkey process...");
|
||||
14
send_ipc.py
14
send_ipc.py
|
|
@ -1,14 +0,0 @@
|
|||
import socket
|
||||
import json
|
||||
import struct
|
||||
import os
|
||||
|
||||
def send(cmd):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock")
|
||||
payload = json.dumps(cmd).encode('utf-8')
|
||||
sock.sendall(struct.pack('<I', len(payload)))
|
||||
sock.sendall(payload)
|
||||
|
||||
import sys
|
||||
send(sys.argv[1])
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
use vrec::capture::{Frame, FrameSource};
|
||||
use vrec::encoder::VaapiEncoder;
|
||||
use vrec::ring::Packet;
|
||||
use vrec::muxer::Muxer;
|
||||
use vrec::ipc::Command;
|
||||
use ringbuf::HeapRb;
|
||||
use ringbuf::traits::{RingBuffer, Consumer, Observer};
|
||||
use crossbeam_channel::bounded;
|
||||
use std::thread;
|
||||
use std::env;
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::io::Read;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string());
|
||||
println!("Detected session type: {}", session_type);
|
||||
|
||||
let mut source: Box<dyn FrameSource> = if std::env::args().any(|a| a == "--mock") {
|
||||
println!("Initializing MOCK capture...");
|
||||
Box::new(vrec::capture::mock::MockCapture::new())
|
||||
} else if session_type.to_lowercase() == "wayland" {
|
||||
println!("Initializing Wayland capture...");
|
||||
Box::new(vrec::capture::wayland::WaylandCapture::new().await?)
|
||||
} else {
|
||||
println!("Initializing X11 capture...");
|
||||
Box::new(vrec::capture::x11::X11Capture::new()?)
|
||||
};
|
||||
|
||||
let first_frame = source.next_frame()?;
|
||||
let (width, height) = match &first_frame {
|
||||
Frame::Raw { width, height, .. } => (*width, *height),
|
||||
Frame::DmaBuf { width, height, .. } => (*width, *height),
|
||||
};
|
||||
|
||||
let (frame_tx, frame_rx) = bounded::<Frame>(5);
|
||||
let (cmd_tx, cmd_rx) = bounded::<Command>(10);
|
||||
let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);
|
||||
|
||||
let _ = frame_tx.send(first_frame);
|
||||
|
||||
let capture_tx = frame_tx.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
match source.next_frame() {
|
||||
Ok(frame) => {
|
||||
if capture_tx.send(frame).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Capture error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut encoder = VaapiEncoder::new(width, height).expect("Failed to init encoder");
|
||||
let codec_ctx_ptr = encoder.codec_ctx() as usize;
|
||||
|
||||
let mut config = vrec::config::VrecConfig::load();
|
||||
let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 60).max(60) as usize);
|
||||
let mut normal_muxer: Option<Muxer> = None;
|
||||
|
||||
thread::spawn(move || {
|
||||
while let Ok(drain) = mux_rx.recv() {
|
||||
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
|
||||
let name = format!("replay_{}.mp4", ts);
|
||||
println!("Saving replay to {}...", name);
|
||||
let mut start_idx = 0;
|
||||
for (i, p) in drain.iter().enumerate() {
|
||||
if p.is_keyframe() {
|
||||
start_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if start_idx < drain.len() {
|
||||
let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
|
||||
let mut muxer = unsafe { Muxer::new(&name, codec_ctx).unwrap() };
|
||||
for p in drain.into_iter().skip(start_idx) {
|
||||
let _ = muxer.write_packet(&p);
|
||||
}
|
||||
let _ = muxer.finalize();
|
||||
println!("Replay saved!");
|
||||
} else {
|
||||
println!("No keyframe found in buffer, skipping save.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
while let Ok(frame) = frame_rx.recv() {
|
||||
while let Ok(cmd) = cmd_rx.try_recv() {
|
||||
match cmd {
|
||||
Command::ReloadConfig => {
|
||||
config = vrec::config::VrecConfig::load();
|
||||
println!("Daemon config reloaded!");
|
||||
let new_capacity = (config.replay_duration_sec * 60).max(60) as usize;
|
||||
if ring.capacity().get() != new_capacity {
|
||||
ring = HeapRb::<Packet>::new(new_capacity);
|
||||
println!("Replay buffer resized to {} frames.", new_capacity);
|
||||
}
|
||||
},
|
||||
Command::SaveReplay => {
|
||||
if config.replay_enabled {
|
||||
let drain = ring.iter().cloned().collect::<Vec<_>>();
|
||||
let _ = mux_tx.try_send(drain);
|
||||
}
|
||||
},
|
||||
Command::StartRecording => {
|
||||
if config.record_enabled && normal_muxer.is_none() {
|
||||
let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
|
||||
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
|
||||
let name = format!("record_{}.mp4", ts);
|
||||
normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() };
|
||||
println!("Started normal recording to {}", name);
|
||||
}
|
||||
},
|
||||
Command::StopRecording => {
|
||||
if let Some(mut m) = normal_muxer.take() {
|
||||
let _ = m.finalize();
|
||||
println!("Stopped normal recording.");
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(packets) = encoder.encode_frame(&frame) {
|
||||
for pkt in packets {
|
||||
if config.replay_enabled {
|
||||
ring.push_overwrite(pkt.clone());
|
||||
}
|
||||
if let Some(muxer) = normal_muxer.as_mut() {
|
||||
let _ = muxer.write_packet(&pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
|
||||
let _ = std::fs::remove_file(&socket_path);
|
||||
let listener = UnixListener::bind(&socket_path)?;
|
||||
println!("Daemon listening on IPC socket: {}", socket_path);
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut stream) => {
|
||||
let mut len_buf = [0u8; 4];
|
||||
if stream.read_exact(&mut len_buf).is_ok() {
|
||||
let len = u32::from_le_bytes(len_buf) as usize;
|
||||
let mut payload = vec![0u8; len];
|
||||
if stream.read_exact(&mut payload).is_ok()
|
||||
&& let Ok(cmd) = serde_json::from_slice::<Command>(&payload) {
|
||||
match cmd {
|
||||
Command::StopDaemon => {
|
||||
break;
|
||||
},
|
||||
other => {
|
||||
let _ = cmd_tx.try_send(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Socket error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
use vrec::ipc::Command;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::io::Write;
|
||||
use std::env;
|
||||
use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent};
|
||||
|
||||
fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
|
||||
let mut stream = UnixStream::connect(&socket_path)?;
|
||||
let payload = serde_json::to_vec(&cmd)?;
|
||||
let len_buf = (payload.len() as u32).to_le_bytes();
|
||||
stream.write_all(&len_buf)?;
|
||||
stream.write_all(&payload)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() > 1 {
|
||||
match args[1].as_str() {
|
||||
"--save" => {
|
||||
vrec::overlay::show_notification_overlay();
|
||||
return send_command(Command::SaveReplay).map_err(|e| e.into());
|
||||
}
|
||||
"--menu" => {
|
||||
vrec::overlay::show_menu_overlay();
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Starting vrec UI/Hotkey process...");
|
||||
|
||||
let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string());
|
||||
let desktop = env::var("XDG_CURRENT_DESKTOP").unwrap_or_else(|_| "".to_string()).to_lowercase();
|
||||
|
||||
let is_gnome = desktop.contains("gnome") || desktop.contains("ubuntu");
|
||||
let is_kde = desktop.contains("kde");
|
||||
|
||||
if session_type.to_lowercase() == "wayland" {
|
||||
if is_gnome || is_kde {
|
||||
println!("WARNING: Wayland native layer shell used. Ensure your compositor allows it.");
|
||||
println!("Note: Global hotkeys may fail to register on Wayland compositors.");
|
||||
} else {
|
||||
println!("wlroots-based compositor detected.");
|
||||
}
|
||||
} else {
|
||||
println!("X11 session detected.");
|
||||
}
|
||||
|
||||
let manager = GlobalHotKeyManager::new().unwrap();
|
||||
let save_hotkey = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR);
|
||||
let menu_hotkey = HotKey::new(Some(Modifiers::ALT), Code::KeyZ);
|
||||
|
||||
if let Err(e) = manager.register(save_hotkey) {
|
||||
eprintln!("Failed to register global hotkey! Wayland compositor might be blocking it: {}", e);
|
||||
eprintln!("Fallback: running without global hotkey. Send commands via socket manually.");
|
||||
} else {
|
||||
manager.register(menu_hotkey).unwrap();
|
||||
println!("Registered global hotkeys:\n - Ctrl+Shift+R (Save Replay)\n - Alt+Z (Open GUI Menu)");
|
||||
}
|
||||
|
||||
let receiver = GlobalHotKeyEvent::receiver();
|
||||
loop {
|
||||
if let Ok(event) = receiver.recv() {
|
||||
if event.state == global_hotkey::HotKeyState::Pressed {
|
||||
if event.id == save_hotkey.id() {
|
||||
println!("Hotkey triggered! Sending SaveReplay command to daemon...");
|
||||
vrec::overlay::show_notification_overlay();
|
||||
let _ = send_command(Command::SaveReplay);
|
||||
} else if event.id == menu_hotkey.id() {
|
||||
println!("Alt+Z triggered! Opening GUI Menu Overlay...");
|
||||
vrec::overlay::show_menu_overlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
pub struct AudioCapture {
|
||||
_stream: cpal::Stream,
|
||||
}
|
||||
|
||||
impl AudioCapture {
|
||||
pub fn new(sender: Sender<Vec<f32>>) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_input_device().ok_or("No audio input device available")?;
|
||||
|
||||
let config = device.default_input_config()?;
|
||||
let sample_rate = config.sample_rate().0;
|
||||
let channels = config.channels();
|
||||
|
||||
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
|
||||
|
||||
let err_fn = |err| eprintln!("Audio stream error: {}", err);
|
||||
let stream_config: cpal::StreamConfig = config.clone().into();
|
||||
|
||||
let stream = match config.sample_format() {
|
||||
cpal::SampleFormat::F32 => {
|
||||
device.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[f32], _: &_| {
|
||||
let _ = sender.try_send(data.to_vec());
|
||||
},
|
||||
err_fn,
|
||||
None,
|
||||
)?
|
||||
}
|
||||
cpal::SampleFormat::I16 => {
|
||||
device.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[i16], _: &_| {
|
||||
let f32_data: Vec<f32> = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect();
|
||||
let _ = sender.try_send(f32_data);
|
||||
},
|
||||
err_fn,
|
||||
None,
|
||||
)?
|
||||
}
|
||||
_ => return Err("Unsupported audio format".into()),
|
||||
};
|
||||
|
||||
stream.play()?;
|
||||
|
||||
Ok(Self { _stream: stream })
|
||||
}
|
||||
}
|
||||
11
test_ipc.py
11
test_ipc.py
|
|
@ -1,11 +0,0 @@
|
|||
import socket
|
||||
import json
|
||||
import struct
|
||||
import os
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock")
|
||||
payload = b'"SaveReplay"'
|
||||
sock.sendall(struct.pack('<I', len(payload)))
|
||||
sock.sendall(payload)
|
||||
print("Sent SaveReplay")
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import socket, struct, os
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock")
|
||||
payload = b'"StartRecording"'
|
||||
sock.sendall(struct.pack('<I', len(payload)))
|
||||
sock.sendall(payload)
|
||||
print("Sent StartRecording")
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import socket, struct, os
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock")
|
||||
payload = b'"ReloadConfig"'
|
||||
sock.sendall(struct.pack('<I', len(payload)))
|
||||
sock.sendall(payload)
|
||||
print("Sent ReloadConfig")
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import socket, struct, os
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/vrec.sock")
|
||||
payload = b'"StopRecording"'
|
||||
sock.sendall(struct.pack('<I', len(payload)))
|
||||
sock.sendall(payload)
|
||||
print("Sent StopRecording")
|
||||
Loading…
Add table
Reference in a new issue