blue-recorder/src/ffmpeg_interface.rs
2024-08-08 01:48:41 -07:00
Ask

353 lines
12 KiB
Rust

{54c89fb16660d33f7eb090c4ed50c6d0abab6bb6 true 11970 ffmpeg_interface.rs 0xc002b26a80}

extern crate subprocess;
use crate::utils::{is_snap, is_wayland};
use crate::wayland_record::{CursorModeTypes, RecordTypes, WaylandRecorder};
use chrono::prelude::*;
use filename::Filename;
use gettextrs::gettext;
use gtk::{prelude::*, ResponseType};
use gtk::{ButtonsType, DialogFlags, MessageDialog, MessageType};
use gtk::{CheckButton, ComboBoxText, Entry, FileChooserNative, SpinButton, Window};
use std::cell::RefCell;
use std::path::PathBuf;
use std::process::{Child, Command};
use std::rc::Rc;
use std::sync::mpsc::Sender;
use std::thread::sleep;
use std::time::Duration;
use subprocess::Exec;
#[derive(Clone)]
pub struct Ffmpeg {
pub filename: (FileChooserNative, Entry, ComboBoxText),
pub record_video: CheckButton,
pub record_audio: CheckButton,
pub audio_id: ComboBoxText,
pub record_mouse: CheckButton,
pub follow_mouse: CheckButton,
pub record_frames: SpinButton,
pub record_delay: SpinButton,
pub command: Entry,
pub video_process: Option<Rc<RefCell<Child>>>,
pub audio_process: Option<Rc<RefCell<Child>>>,
pub saved_filename: Option<String>,
pub unbound: Option<Sender<bool>>,
pub window: Window,
pub record_wayland: WaylandRecorder,
pub record_window: Rc<RefCell<bool>>,
pub main_context: gtk::glib::MainContext,
pub temp_video_filename: String,
}
impl Ffmpeg {
pub fn start_record(&mut self, x: u16, y: u16, width: u16, height: u16) -> Option<()> {
self.saved_filename = Some(
self.filename
.0
.file()
.unwrap()
.path()
.unwrap()
.join(PathBuf::from(format!(
"{}.{}",
if self.filename.1.text().to_string().trim().eq("") {
Utc::now().to_string().replace(" UTC", "").replace(' ', "-")
} else {
self.filename.1.text().to_string().trim().to_string()
},
self.filename.2.active_id().unwrap()
)))
.as_path()
.display()
.to_string(),
);
let is_file_already_exists =
std::path::Path::new(&self.saved_filename.clone().unwrap()).exists();
if is_file_already_exists {
let message_dialog = MessageDialog::new(
Some(&self.window),
DialogFlags::all(),
MessageType::Warning,
ButtonsType::YesNo,
&gettext("File already exist. Do you want to overwrite it?"),
);
let answer = self.main_context.block_on(message_dialog.run_future());
message_dialog.close();
if answer != ResponseType::Yes {
return None;
}
}
if self.record_video.is_active() && !is_wayland() {
let mut ffmpeg_command: Command = Command::new("ffmpeg");
// record video with specified width and hight
ffmpeg_command.args([
"-video_size",
format!("{}x{}", width, height).as_str(),
"-framerate",
self.record_frames.value().to_string().as_str(),
"-f",
"x11grab",
"-i",
format!(
"{}+{},{}",
std::env::var("DISPLAY")
.unwrap_or_else(|_| ":0".to_string())
.as_str(),
x,
y
)
.as_str(),
]);
// if show mouse switch is enabled, draw the mouse to video
ffmpeg_command.arg("-draw_mouse");
if self.record_mouse.is_active() {
ffmpeg_command.arg("1");
} else {
ffmpeg_command.arg("0");
}
// if follow mouse switch is enabled, follow the mouse
if self.follow_mouse.is_active() {
ffmpeg_command.args(["-follow_mouse", "centered"]);
}
let video_filename = format!(
"{}.temp.without.audio.{}",
self.saved_filename.as_ref().unwrap(),
self.filename.2.active_id().unwrap()
);
ffmpeg_command.args([
"-crf",
"1",
{
if self.record_audio.is_active() {
video_filename.as_str()
} else {
self.saved_filename.as_ref().unwrap()
}
},
"-y",
]);
// sleep for delay
sleep(Duration::from_secs(self.record_delay.value() as u64));
// start recording and return the process id
self.video_process = Some(Rc::new(RefCell::new(ffmpeg_command.spawn().unwrap())));
} else if self.record_video.is_active() && is_wayland() {
sleep(Duration::from_secs(self.record_delay.value() as u64));
let tempfile = tempfile::NamedTempFile::new()
.expect("cannot create temp file")
.keep()
.expect("cannot keep temp file");
self.temp_video_filename = tempfile
.0
.file_name()
.expect("cannot get file name")
.to_str()
.unwrap()
.to_string();
let record_window = self.record_window.take();
self.record_window.replace(record_window);
if !self.main_context.block_on(self.record_wayland.start(
self.temp_video_filename.clone(),
if record_window {
RecordTypes::Window
} else {
RecordTypes::Monitor
},
{
if self.record_mouse.is_active() {
CursorModeTypes::Show
} else {
CursorModeTypes::Hidden
}
},
)) {
println!("failed to start recording");
return None;
}
}
if self.record_audio.is_active() {
let mut ffmpeg_command = Command::new("ffmpeg");
ffmpeg_command.arg("-f");
ffmpeg_command.arg("pulse");
ffmpeg_command.arg("-i");
ffmpeg_command.arg(self.audio_id.active_id().unwrap());
ffmpeg_command.arg("-f");
ffmpeg_command.arg("ogg");
ffmpeg_command.arg(format!(
"{}.temp.audio",
self.saved_filename.as_ref().unwrap()
));
ffmpeg_command.arg("-y");
self.audio_process = Some(Rc::new(RefCell::new(ffmpeg_command.spawn().unwrap())));
}
Some(())
}
pub fn stop_record(&mut self) {
// kill the process to stop recording
if self.video_process.is_some() {
Command::new("kill")
.arg(format!(
"{}",
self.video_process.clone().unwrap().borrow_mut().id()
))
.output()
.unwrap();
self.video_process
.clone()
.unwrap()
.borrow_mut()
.wait()
.unwrap();
println!("video killed");
} else if is_wayland() {
self.main_context.block_on(self.record_wayland.stop());
}
if self.audio_process.is_some() {
Command::new("kill")
.arg(format!(
"{}",
self.audio_process.clone().unwrap().borrow_mut().id()
))
.output()
.unwrap();
self.audio_process
.clone()
.unwrap()
.borrow_mut()
.wait()
.unwrap();
println!("audio killed");
}
let video_filename = {
if is_wayland() {
self.temp_video_filename.clone()
} else {
format!(
"{}.temp.without.audio.{}",
self.saved_filename.as_ref().unwrap(),
self.filename.2.active_id().unwrap()
)
}
};
let audio_filename = format!("{}.temp.audio", self.saved_filename.as_ref().unwrap());
let is_video_record = { std::path::Path::new(video_filename.as_str()).exists() };
let is_audio_record = std::path::Path::new(audio_filename.as_str()).exists();
if is_video_record {
if is_wayland() {
// convert webm to specified format
Command::new("ffmpeg")
.args([
"-i",
self.temp_video_filename.as_str(),
"-crf",
"23", // default quality
"-c:a",
self.filename.2.active_id().unwrap().as_str(),
self.saved_filename.as_ref().unwrap(),
"-y",
])
.output()
.unwrap();
} else {
let mut move_command = Command::new("mv");
move_command.args([
self.saved_filename.as_ref().unwrap().as_str(),
if is_audio_record {
video_filename.as_str()
} else {
self.saved_filename.as_ref().unwrap()
},
]);
move_command.output().unwrap();
}
// if audio record, then merge video and audio
if is_audio_record {
Command::new("ffmpeg")
.args([
"-i",
video_filename.as_str(),
"-f",
"ogg",
"-i",
audio_filename.as_str(),
"-crf",
"23", // default quality
"-c:a",
"aac",
self.saved_filename.as_ref().unwrap(),
"-y",
])
.output()
.expect("failed to merge video and audio");
std::fs::remove_file(audio_filename).unwrap();
}
std::fs::remove_file(video_filename).unwrap();
}
// if only audio is recording then convert it to chosen format
else if is_audio_record {
Command::new("ffmpeg")
.args([
"-f",
"ogg",
"-i",
audio_filename.as_str(),
self.saved_filename.as_ref().unwrap(),
])
.output()
.expect("failed convert audio to video");
std::fs::remove_file(audio_filename).unwrap();
}
// execute command after finish recording
if self.command.text().trim() != "" {
Exec::shell(self.command.text().trim()).popen().unwrap();
}
}
pub fn play_record(self) {
if self.saved_filename.is_some() {
if is_snap() {
// open the video using snapctrl for snap package
Command::new("snapctl")
.arg("user-open")
.arg(self.saved_filename.unwrap())
.spawn()
.unwrap();
} else {
Command::new("xdg-open")
.arg(self.saved_filename.unwrap())
.spawn()
.unwrap();
}
}
}
}