mirror of
https://github.com/xlmnxp/blue-recorder.git
synced 2024-11-23 17:13:11 +03:00
make window selection, area selection functional
This commit is contained in:
parent
42b77654a4
commit
6d7a0f2a5b
95
Cargo.lock
generated
95
Cargo.lock
generated
@ -6,6 +6,15 @@ version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.38"
|
||||
@ -38,6 +47,12 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
@ -48,10 +63,12 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
name = "blue-recorder"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"gdk",
|
||||
"gio",
|
||||
"glib",
|
||||
"gtk",
|
||||
"regex",
|
||||
"rust-ini",
|
||||
]
|
||||
|
||||
@ -99,6 +116,19 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.2.2"
|
||||
@ -282,7 +312,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -460,6 +490,25 @@ version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.5.2"
|
||||
@ -631,6 +680,24 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.16.1"
|
||||
@ -717,6 +784,26 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
@ -756,6 +843,12 @@ version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -10,6 +10,8 @@ edition = "2018"
|
||||
gdk = "0.13.2"
|
||||
glib = "0.10.3"
|
||||
rust-ini = "0.16"
|
||||
regex = "1.4.3"
|
||||
chrono = "0.4.19"
|
||||
|
||||
[dependencies.gtk]
|
||||
version = "0.9.0"
|
||||
|
90
src/area_capture.rs
Normal file
90
src/area_capture.rs
Normal file
@ -0,0 +1,90 @@
|
||||
extern crate regex;
|
||||
use regex::Regex;
|
||||
use std::process::Command;
|
||||
|
||||
// this struct use "xwininfo" to get area x, y, width and height
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AreaCapture {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl AreaCapture {
|
||||
pub fn new() -> AreaCapture {
|
||||
AreaCapture {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_area(&mut self) -> Self {
|
||||
let coordinate = xwininfo_to_coordinate(
|
||||
String::from_utf8(Command::new("xwininfo").output().unwrap().stdout).unwrap()
|
||||
);
|
||||
self.x = coordinate.0;
|
||||
self.y = coordinate.1;
|
||||
self.width = coordinate.2;
|
||||
self.height = coordinate.3;
|
||||
*self
|
||||
}
|
||||
|
||||
pub fn get_window_by_name(&mut self, name: &str) -> Self {
|
||||
let coordinate = xwininfo_to_coordinate(
|
||||
String::from_utf8(Command::new("xwininfo").arg("-name").arg(name).output().unwrap().stdout).unwrap(),
|
||||
);
|
||||
self.x = coordinate.0;
|
||||
self.y = coordinate.1;
|
||||
self.width = coordinate.2;
|
||||
self.height = coordinate.3;
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
fn xwininfo_to_coordinate(xwininfo_output: String) -> (u16, u16, u16, u16) {
|
||||
let x: u16 = Regex::new(r"A.*X:\s+(\d+)\n")
|
||||
.unwrap()
|
||||
.captures(xwininfo_output.as_str())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string()
|
||||
.parse::<u16>()
|
||||
.unwrap();
|
||||
let y: u16 = Regex::new(r"A.*Y:\s+(\d+)\n")
|
||||
.unwrap()
|
||||
.captures(xwininfo_output.as_str())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string()
|
||||
.parse::<u16>()
|
||||
.unwrap();
|
||||
let width: u16 = Regex::new(r"Width:\s(\d+)\n")
|
||||
.unwrap()
|
||||
.captures(xwininfo_output.as_str())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string()
|
||||
.parse::<u16>()
|
||||
.unwrap();
|
||||
let height: u16 = Regex::new(r"Height:\s(\d+)\n")
|
||||
.unwrap()
|
||||
.captures(xwininfo_output.as_str())
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string()
|
||||
.parse::<u16>()
|
||||
.unwrap();
|
||||
|
||||
(x, y, width, height)
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
use gtk::{
|
||||
CheckButton, ComboBoxExt, ComboBoxText, Entry, EntryExt, FileChooser, FileChooserExt,
|
||||
SpinButton, SpinButtonExt, ToggleButtonExt,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use gtk::{CheckButton, SpinButton, ComboBoxText, FileChooser, Entry, ToggleButtonExt, SpinButtonExt, ComboBoxExt, FileChooserExt, EntryExt};
|
||||
use chrono::prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ffmpeg {
|
||||
@ -14,13 +18,17 @@ pub struct Ffmpeg {
|
||||
pub follow_mouse: CheckButton,
|
||||
pub record_frames: SpinButton,
|
||||
pub record_delay: SpinButton,
|
||||
pub process_id: Option<u32>
|
||||
pub process_id: Option<u32>,
|
||||
pub saved_filename: Option<String>,
|
||||
}
|
||||
|
||||
impl Ffmpeg {
|
||||
pub fn start_record(&mut self, x: i16, y: i16, width: i16, height: i16) -> u32 {
|
||||
pub fn start_record(&mut self, x: u16, y: u16, width: u16, height: u16) -> u32 {
|
||||
if self.process_id.is_some() {
|
||||
Command::new("kill").arg(format!("{}", self.process_id.unwrap())).output().unwrap();
|
||||
Command::new("kill")
|
||||
.arg(format!("{}", self.process_id.unwrap()))
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut ffmpeg_command: Command = Command::new("ffmpeg");
|
||||
@ -32,9 +40,11 @@ impl Ffmpeg {
|
||||
}
|
||||
|
||||
// if show mouse switch is enabled, draw the mouse to video
|
||||
ffmpeg_command.arg("-draw_mouse");
|
||||
if self.record_mouse.get_active() {
|
||||
ffmpeg_command.arg("-draw_mouse");
|
||||
ffmpeg_command.arg("1");
|
||||
} else {
|
||||
ffmpeg_command.arg("0");
|
||||
}
|
||||
|
||||
// if follow mouse switch is enabled, follow the mouse
|
||||
@ -64,23 +74,30 @@ impl Ffmpeg {
|
||||
ffmpeg_command.arg("-strict");
|
||||
ffmpeg_command.arg("-2");
|
||||
}
|
||||
|
||||
ffmpeg_command.arg("-q");
|
||||
ffmpeg_command.arg("1");
|
||||
|
||||
self.saved_filename = Some(
|
||||
self.filename
|
||||
.0
|
||||
.get_filename()
|
||||
.unwrap()
|
||||
.join(PathBuf::from(format!(
|
||||
"{}.{}",
|
||||
if self.filename.1.get_text().to_string().trim().eq("") {
|
||||
Utc::now().to_string()
|
||||
} else {
|
||||
self.filename.1.get_text().to_string().trim().to_string()
|
||||
},
|
||||
self.filename.2.get_active_id().unwrap().to_string()
|
||||
)))
|
||||
.as_path()
|
||||
.display()
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
ffmpeg_command.arg({
|
||||
self.filename.0.get_filename()
|
||||
.unwrap()
|
||||
.join(PathBuf::from(format!(
|
||||
"{}.{}",
|
||||
if self.filename.1.get_text().to_string().trim().eq("") {
|
||||
self.filename.1.get_text().to_string()
|
||||
} else {
|
||||
self.filename.1.get_text().to_string().trim().to_string()
|
||||
},
|
||||
self.filename.2.get_active_id().unwrap().to_string()
|
||||
)))
|
||||
});
|
||||
ffmpeg_command.arg(self.saved_filename.as_ref().unwrap());
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// sleep for delay
|
||||
@ -88,29 +105,23 @@ impl Ffmpeg {
|
||||
|
||||
// start recording and return the process id
|
||||
self.process_id = Some(ffmpeg_command.spawn().unwrap().id());
|
||||
println!("{}", self.process_id.unwrap());
|
||||
println!("{}", self.process_id.unwrap());
|
||||
self.process_id.unwrap()
|
||||
}
|
||||
|
||||
pub fn stop_record(self) {
|
||||
if self.process_id.is_some() {
|
||||
Command::new("kill").arg(format!("{}", self.process_id.unwrap())).output().unwrap();
|
||||
Command::new("kill")
|
||||
.arg(format!("{}", self.process_id.unwrap()))
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_record(self) {
|
||||
Command::new("xdg-open").arg({
|
||||
self.filename.0.get_filename()
|
||||
.unwrap()
|
||||
.join(PathBuf::from(format!(
|
||||
"{}.{}",
|
||||
if self.filename.1.get_text().to_string().trim().eq("") {
|
||||
self.filename.1.get_text().to_string()
|
||||
} else {
|
||||
self.filename.1.get_text().to_string().trim().to_string()
|
||||
},
|
||||
self.filename.2.get_active_id().unwrap().to_string()
|
||||
)))
|
||||
}).output().unwrap();
|
||||
if self.saved_filename.is_some() {
|
||||
Command::new("xdg-open")
|
||||
.arg(self.saved_filename.unwrap()).spawn().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
src/main.rs
38
src/main.rs
@ -3,6 +3,7 @@ extern crate gio;
|
||||
extern crate gtk;
|
||||
mod config_management;
|
||||
mod ffmpeg_interface;
|
||||
mod area_capture;
|
||||
|
||||
// use gio::prelude::*;
|
||||
use std::rc::Rc;
|
||||
@ -12,12 +13,15 @@ use gtk::prelude::*;
|
||||
use gtk::ComboBoxText;
|
||||
use gtk::{
|
||||
AboutDialog, Builder, Button, CheckButton, CssProvider, Entry, FileChooser, Label, MenuItem,
|
||||
SpinButton, Window,
|
||||
SpinButton, Window
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn main() {
|
||||
// use "GDK_BACKEND=x11" to make xwininfo work in Wayland by using XWayland
|
||||
std::env::set_var("GDK_BACKEND", "x11");
|
||||
|
||||
if gtk::init().is_err() {
|
||||
println!("Failed to initialize GTK.");
|
||||
return;
|
||||
@ -40,6 +44,7 @@ fn main() {
|
||||
let play_button: Button = builder.get_object("playbutton").unwrap();
|
||||
let window_grab_button: Button = builder.get_object("window_grab_button").unwrap();
|
||||
let area_grab_button: Button = builder.get_object("area_grab_button").unwrap();
|
||||
let area_set_button: Button = builder.get_object("area_set_button").unwrap();
|
||||
let frames_label: Label = builder.get_object("frames_label").unwrap();
|
||||
let delay_label: Label = builder.get_object("delay_label").unwrap();
|
||||
let command_label: Label = builder.get_object("command_label").unwrap();
|
||||
@ -210,11 +215,25 @@ fn main() {
|
||||
});
|
||||
|
||||
// Buttons
|
||||
let _area_chooser_window = area_chooser_window.to_owned();
|
||||
let area_capture: Rc<RefCell<area_capture::AreaCapture>> = Rc::new(RefCell::new(area_capture::AreaCapture::new()));
|
||||
let mut _area_capture = area_capture.clone();
|
||||
window_grab_button.connect_clicked(move |_| {
|
||||
_area_capture.borrow_mut().get_area();
|
||||
});
|
||||
|
||||
let _area_chooser_window = area_chooser_window.clone();
|
||||
let mut _area_capture = area_capture.clone();
|
||||
area_grab_button.connect_clicked(move |_| {
|
||||
_area_chooser_window.show();
|
||||
});
|
||||
|
||||
let _area_chooser_window = area_chooser_window.clone();
|
||||
let mut _area_capture = area_capture.clone();
|
||||
area_set_button.connect_clicked(move |_| {
|
||||
_area_capture.borrow_mut().get_window_by_name("Area Chooser");
|
||||
_area_chooser_window.hide();
|
||||
});
|
||||
|
||||
// init record struct
|
||||
let ffmpeg_record_interface: Rc<RefCell<ffmpeg_interface::Ffmpeg>> = Rc::new(RefCell::new(ffmpeg_interface::Ffmpeg {
|
||||
filename: (folder_chooser, filename_entry, format_chooser_combobox),
|
||||
@ -225,13 +244,15 @@ fn main() {
|
||||
follow_mouse: follow_mouse_switch,
|
||||
record_frames: frames_spin,
|
||||
record_delay: delay_spin,
|
||||
process_id: None
|
||||
process_id: None,
|
||||
saved_filename: None
|
||||
}));
|
||||
|
||||
let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone();
|
||||
|
||||
let mut _area_capture = area_capture.clone();
|
||||
record_button.connect_clicked(move |_| {
|
||||
_ffmpeg_record_interface.borrow_mut().start_record(0, 0, 512, 512);
|
||||
let _area_capture = _area_capture.borrow_mut().clone();
|
||||
_ffmpeg_record_interface.borrow_mut().start_record(_area_capture.x, _area_capture.y, _area_capture.width, _area_capture.height);
|
||||
});
|
||||
|
||||
let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone();
|
||||
@ -246,14 +267,17 @@ fn main() {
|
||||
|
||||
// Windows
|
||||
// hide area chooser after it deleted.
|
||||
let _area_chooser_window = area_chooser_window.to_owned();
|
||||
let _area_chooser_window = area_chooser_window.clone();
|
||||
area_chooser_window.connect_delete_event(move |_, _event: &gdk::Event| {
|
||||
_area_chooser_window.hide();
|
||||
Inhibit(true)
|
||||
});
|
||||
|
||||
// close the application when main window destroy
|
||||
main_window.connect_destroy(|_| {
|
||||
let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone();
|
||||
main_window.connect_destroy(move |_| {
|
||||
// stop recording before close the application
|
||||
_ffmpeg_record_interface.borrow_mut().clone().stop_record();
|
||||
gtk::main_quit();
|
||||
});
|
||||
|
||||
|
@ -67,13 +67,12 @@
|
||||
<property name="has_subtitle">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="areasettings">
|
||||
<object class="GtkButton" id="area_set_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">half</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="areasettings" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
@ -345,7 +344,6 @@
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<signal name="clicked" handler="selectarea" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
Loading…
Reference in New Issue
Block a user