mirror of
https://github.com/xlmnxp/blue-recorder.git
synced 2025-04-03 00:04:53 +03:00
use std::process::Command
This commit is contained in:
parent
6938ed4b2e
commit
0aaaa56348
1174
Cargo.lock
generated
1174
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
cmd = []
|
||||
cmd = ["ffmpeg-sidecar"]
|
||||
gtk = ["adw", "chrono", "glib", "subprocess"]
|
||||
|
||||
[dependencies]
|
||||
adw = { version = "0.2.1", package = "libadwaita", features = ["gtk_v4_6"], optional = true }
|
||||
anyhow = "1.0.86"
|
||||
chrono = { version = "0.4.19", optional = true }
|
||||
ffmpeg-sidecar = "1.1.0"
|
||||
ffmpeg-sidecar = { version = "2.0.5", optional = true }
|
||||
glib = { version = "0.10.3", optional = true }
|
||||
open = "5.1.4"
|
||||
subprocess = {version = "0.2.6", optional = true }
|
||||
|
@ -2,21 +2,32 @@
|
||||
use adw::gtk::{CheckButton, ComboBoxText, Entry, FileChooserNative, SpinButton};
|
||||
#[cfg(feature = "gtk")]
|
||||
use adw::gtk::prelude::*;
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
#[cfg(feature = "cmd")]
|
||||
use anyhow::Error;
|
||||
#[cfg(feature = "gtk")]
|
||||
use chrono::Utc;
|
||||
#[cfg(feature = "cmd")]
|
||||
use ffmpeg_sidecar::child::FfmpegChild;
|
||||
#[cfg(feature = "cmd")]
|
||||
use ffmpeg_sidecar::command::FfmpegCommand;
|
||||
use tempfile;
|
||||
use std::{cell::RefCell, time::Instant};
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "gtk")]
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
#[cfg(feature = "gtk")]
|
||||
use std::process::Child;
|
||||
use std::rc::Rc;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
#[cfg(feature = "cmd")]
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::utils::{is_input_audio_record, is_output_audio_record, is_valide, is_video_record, RecordMode};
|
||||
use crate::utils::{is_input_audio_record, is_output_audio_record, is_video_record, RecordMode};
|
||||
#[cfg(feature = "cmd")]
|
||||
use crate::utils::is_valide;
|
||||
|
||||
#[cfg(feature = "cmd")]
|
||||
#[derive(Clone)]
|
||||
@ -53,9 +64,9 @@ pub struct Ffmpeg {
|
||||
pub temp_video_filename: String,
|
||||
pub saved_filename: String,
|
||||
pub height: Option<u16>,
|
||||
pub input_audio_process: Option<Rc<RefCell<FfmpegChild>>>,
|
||||
pub output_audio_process: Option<Rc<RefCell<FfmpegChild>>>,
|
||||
pub video_process: Option<Rc<RefCell<FfmpegChild>>>,
|
||||
pub input_audio_process: Option<Rc<RefCell<Child>>>,
|
||||
pub output_audio_process: Option<Rc<RefCell<Child>>>,
|
||||
pub video_process: Option<Rc<RefCell<Child>>>,
|
||||
pub audio_record_bitrate: SpinButton,
|
||||
pub record_delay: SpinButton,
|
||||
pub record_frames: SpinButton,
|
||||
@ -330,7 +341,7 @@ impl Ffmpeg {
|
||||
(|| anyhow!("Unable to get height value"))?);
|
||||
let ffmpeg_convert = format!("ffmpeg -i file:{} -filter_complex '{}' \
|
||||
-loop 0 {} -y", &self.temp_video_filename,filter,&self.filename);
|
||||
std::process::Command::new("sh").arg("-c").arg(&ffmpeg_convert).output()?;
|
||||
Command::new("sh").arg("-c").arg(&ffmpeg_convert).output()?;
|
||||
}
|
||||
} else if is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
// Validate audio file integrity
|
||||
@ -394,7 +405,7 @@ impl Ffmpeg {
|
||||
// Kill process
|
||||
pub fn kill(&mut self) -> Result<()> {
|
||||
if self.video_process.is_some() {
|
||||
std::process::Command::new("kill")
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.video_process
|
||||
@ -405,7 +416,7 @@ impl Ffmpeg {
|
||||
)).output()?;
|
||||
}
|
||||
if self.input_audio_process.is_some() {
|
||||
std::process::Command::new("kill")
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.input_audio_process
|
||||
@ -416,7 +427,7 @@ impl Ffmpeg {
|
||||
)).output()?;
|
||||
}
|
||||
if self.output_audio_process.is_some() {
|
||||
std::process::Command::new("kill")
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.output_audio_process
|
||||
@ -467,7 +478,7 @@ impl Ffmpeg {
|
||||
x,
|
||||
y
|
||||
);
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
let format = "x11grab";
|
||||
self.height = Some(height);
|
||||
let filename = self.saved_filename.clone();
|
||||
@ -500,14 +511,14 @@ impl Ffmpeg {
|
||||
RecordMode::Screen => {
|
||||
let width = width as f32 * 0.95;
|
||||
let height = height as f32 * 0.95;
|
||||
ffmpeg_command.size(width as u32, height as u32);
|
||||
ffmpeg_command.args(["-s", &width.to_string(), &height.to_string()]);
|
||||
},
|
||||
_=> {
|
||||
ffmpeg_command.size(width.into(), height.into());
|
||||
ffmpeg_command.args(["-s", &width.to_string(), &height.to_string()]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ffmpeg_command.size(width.into(), height.into());
|
||||
ffmpeg_command.args(["-s", &width.to_string(), &height.to_string()]);
|
||||
}
|
||||
|
||||
// Show grabbed area
|
||||
@ -533,8 +544,8 @@ impl Ffmpeg {
|
||||
}
|
||||
|
||||
// Video format && input
|
||||
ffmpeg_command.format(format)
|
||||
.input(display);
|
||||
ffmpeg_command.args(["-f", format])
|
||||
.args(["-i", &display]);
|
||||
|
||||
// Disable bitrate if value is zero
|
||||
if self.video_record_bitrate.value() as u16 > 0 {
|
||||
@ -572,7 +583,7 @@ impl Ffmpeg {
|
||||
}
|
||||
},
|
||||
]);
|
||||
ffmpeg_command.overwrite();
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// Sleep for delay
|
||||
sleep(Duration::from_secs(self.record_delay.value() as u64));
|
||||
@ -585,13 +596,23 @@ impl Ffmpeg {
|
||||
|
||||
// Stop video recording
|
||||
pub fn stop_video(&mut self) -> Result<()> {
|
||||
// Quit the process to stop recording
|
||||
// Kill the process to stop recording
|
||||
if self.video_process.is_some() {
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.video_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Failed to get video_process ID."))?
|
||||
.borrow_mut().id()
|
||||
))
|
||||
.output()?;
|
||||
|
||||
self.video_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Not exiting the video recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.quit()?;
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -604,12 +625,12 @@ impl Ffmpeg {
|
||||
.keep()?;
|
||||
self.temp_input_audio_filename = Path::new(&input_audio_tempfile.1).to_string_lossy()
|
||||
.to_string();
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("pulse")
|
||||
.input(&self.audio_input_id.active_id()
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "pulse"])
|
||||
.args(["-i", &self.audio_input_id.active_id()
|
||||
.ok_or_else(|| anyhow!("Failed to get audio input ID."))?
|
||||
)
|
||||
.format("ogg");
|
||||
])
|
||||
.args(["-f", "ogg"]);
|
||||
// Disable bitrate if value is zero
|
||||
if self.audio_record_bitrate.value() as u16 > 0 {
|
||||
ffmpeg_command.args([
|
||||
@ -620,7 +641,7 @@ impl Ffmpeg {
|
||||
// Remove metadate
|
||||
ffmpeg_command.args(["-map_metadata", "-1"]);
|
||||
ffmpeg_command.arg(&self.temp_input_audio_filename);
|
||||
ffmpeg_command.overwrite();
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// Sleep for delay
|
||||
if !is_video_record(&self.temp_video_filename) {
|
||||
@ -634,13 +655,23 @@ impl Ffmpeg {
|
||||
|
||||
// Stop audio input recording
|
||||
pub fn stop_input_audio(&mut self) -> Result<()> {
|
||||
// Quit the process to stop recording
|
||||
// Kill the process to stop recording
|
||||
if self.input_audio_process.is_some() {
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.input_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Failed to get input_audio_process ID."))?
|
||||
.borrow_mut().id()
|
||||
))
|
||||
.output()?;
|
||||
|
||||
self.input_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Not exiting the input audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.quit()?;
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -653,14 +684,14 @@ impl Ffmpeg {
|
||||
.keep()?;
|
||||
self.temp_output_audio_filename = Path::new(&output_audio_tempfile.1).to_string_lossy()
|
||||
.to_string();
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("pulse")
|
||||
.input(&self.audio_output_id)
|
||||
.format("ogg");
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "pulse"])
|
||||
.args(["-i", &self.audio_output_id])
|
||||
.args(["-f", "ogg"]);
|
||||
// Remove metadate
|
||||
ffmpeg_command.args(["-map_metadata", "-1"]);
|
||||
ffmpeg_command.arg(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.overwrite();
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// Sleep for delay
|
||||
if !is_video_record(&self.temp_video_filename) && !is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
@ -674,13 +705,23 @@ impl Ffmpeg {
|
||||
|
||||
// Stop audio output recording
|
||||
pub fn stop_output_audio(&mut self) -> Result<()> {
|
||||
// Quit the process to stop recording
|
||||
// Kill the process to stop recording
|
||||
if self.output_audio_process.is_some() {
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.output_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Failed to get output_audio_process ID."))?
|
||||
.borrow_mut().id()
|
||||
))
|
||||
.output()?;
|
||||
|
||||
self.output_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Not exiting the output audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.quit()?;
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -689,44 +730,29 @@ impl Ffmpeg {
|
||||
pub fn merge(&mut self) -> Result<()> {
|
||||
if is_video_record(&self.temp_video_filename) {
|
||||
if self.output != "gif" {
|
||||
// Validate video file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_video_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
}
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.input(&self.temp_video_filename);
|
||||
ffmpeg_command.format("ogg");
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-i", &self.temp_video_filename]);
|
||||
ffmpeg_command.args(["-f", "ogg"]);
|
||||
if is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
ffmpeg_command.input(&self.temp_input_audio_filename);
|
||||
ffmpeg_command.args(["-i", &self.temp_input_audio_filename]);
|
||||
}
|
||||
if is_output_audio_record(&self.temp_output_audio_filename) {
|
||||
ffmpeg_command.input(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.args(["-i", &self.temp_output_audio_filename]);
|
||||
}
|
||||
ffmpeg_command.args([
|
||||
"-c:a",
|
||||
"aac",
|
||||
&self.saved_filename.clone()
|
||||
]);
|
||||
ffmpeg_command.overwrite()
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
} else {
|
||||
// Validate video file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_video_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
ffmpeg_command.arg("-y");
|
||||
let program = ffmpeg_command.get_program().to_string_lossy().to_string();
|
||||
let args: Vec<String> = ffmpeg_command.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
|
||||
let mut merge_command = program;
|
||||
for arg in args {
|
||||
merge_command.push_str(&format!(" {}", arg));
|
||||
}
|
||||
adw::glib::spawn_command_line_async(merge_command)?;
|
||||
} else {
|
||||
// Convert MP4 to GIF
|
||||
let filter = format!("fps={},scale={}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
|
||||
self.record_frames.value() as u16,
|
||||
@ -736,54 +762,44 @@ impl Ffmpeg {
|
||||
-loop 0 {} -y", &self.temp_video_filename,filter,
|
||||
&self.saved_filename
|
||||
.clone());
|
||||
std::process::Command::new("sh").arg("-c").arg(&ffmpeg_convert).output()?;
|
||||
adw::glib::spawn_command_line_async(ffmpeg_convert)?;
|
||||
}
|
||||
} else if is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
// Validate audio file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_input_audio_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
}
|
||||
// If only audio is recording then convert it to chosen format
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("ogg");
|
||||
ffmpeg_command.input(&self.temp_input_audio_filename);
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "ogg"]);
|
||||
ffmpeg_command.args(["-i", &self.temp_input_audio_filename]);
|
||||
if is_output_audio_record(&self.temp_output_audio_filename) {
|
||||
ffmpeg_command.input(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.args(["-i", &self.temp_output_audio_filename]);
|
||||
}
|
||||
ffmpeg_command.args([
|
||||
"-c:a",
|
||||
"aac",
|
||||
&self.saved_filename
|
||||
.clone()
|
||||
]).overwrite()
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
} else {
|
||||
// Validate audio file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_output_audio_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
.clone(),
|
||||
"-y"
|
||||
]);
|
||||
let program = ffmpeg_command.get_program().to_string_lossy().to_string();
|
||||
let args: Vec<String> = ffmpeg_command.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
|
||||
let mut merge_command = program;
|
||||
for arg in args {
|
||||
merge_command.push_str(&format!(" {}", arg));
|
||||
}
|
||||
adw::glib::spawn_command_line_async(merge_command)?;
|
||||
} else {
|
||||
// If only output audio is recording then convert it to chosen format
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("ogg");
|
||||
ffmpeg_command.input(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.arg(&self.saved_filename
|
||||
.clone())
|
||||
.overwrite()
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "ogg"]);
|
||||
ffmpeg_command.args(["-i", &self.temp_output_audio_filename]);
|
||||
ffmpeg_command.arg(&self.saved_filename.clone());
|
||||
ffmpeg_command.arg("-y");
|
||||
let program = ffmpeg_command.get_program().to_string_lossy().to_string();
|
||||
let args: Vec<String> = ffmpeg_command.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
|
||||
let mut merge_command = program;
|
||||
for arg in args {
|
||||
merge_command.push_str(&format!(" {}", arg));
|
||||
}
|
||||
adw::glib::spawn_command_line_async(merge_command)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -802,36 +818,33 @@ impl Ffmpeg {
|
||||
// Kill process
|
||||
pub fn kill(&mut self) -> Result<()> {
|
||||
if self.video_process.is_some() {
|
||||
std::process::Command::new("kill")
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.video_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Unable to kill the video recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id()
|
||||
.borrow_mut().id()
|
||||
)).output()?;
|
||||
}
|
||||
if self.input_audio_process.is_some() {
|
||||
std::process::Command::new("kill")
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.input_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Unable to kill the intput audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id()
|
||||
.borrow_mut().id()
|
||||
)).output()?;
|
||||
}
|
||||
if self.output_audio_process.is_some() {
|
||||
std::process::Command::new("kill")
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.output_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Unable to kill the output audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id()
|
||||
.borrow_mut().id()
|
||||
)).output()?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -2,21 +2,32 @@
|
||||
use adw::gtk::{CheckButton, ComboBoxText, Entry, FileChooserNative, SpinButton};
|
||||
#[cfg(feature = "gtk")]
|
||||
use adw::gtk::prelude::*;
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
#[cfg(feature = "cmd")]
|
||||
use anyhow::Error;
|
||||
#[cfg(feature = "gtk")]
|
||||
use chrono::Utc;
|
||||
#[cfg(feature = "cmd")]
|
||||
use ffmpeg_sidecar::child::FfmpegChild;
|
||||
#[cfg(feature = "cmd")]
|
||||
use ffmpeg_sidecar::command::FfmpegCommand;
|
||||
use tempfile;
|
||||
use std::{cell::RefCell, time::Instant};
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "gtk")]
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
#[cfg(feature = "gtk")]
|
||||
use std::process::Child;
|
||||
use std::rc::Rc;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
#[cfg(feature = "cmd")]
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::utils::{is_input_audio_record, is_output_audio_record, is_valide, is_video_record, RecordMode};
|
||||
use crate::utils::{is_input_audio_record, is_output_audio_record, is_video_record, RecordMode};
|
||||
#[cfg(feature = "cmd")]
|
||||
use crate::utils::is_valide;
|
||||
|
||||
#[cfg(feature = "cmd")]
|
||||
#[derive(Clone)]
|
||||
@ -55,9 +66,9 @@ pub struct Ffmpeg {
|
||||
pub window_title: String,
|
||||
pub saved_filename: String,
|
||||
pub height: Option<u16>,
|
||||
pub input_audio_process: Option<Rc<RefCell<FfmpegChild>>>,
|
||||
pub output_audio_process: Option<Rc<RefCell<FfmpegChild>>>,
|
||||
pub video_process: Option<Rc<RefCell<FfmpegChild>>>,
|
||||
pub input_audio_process: Option<Rc<RefCell<Child>>>,
|
||||
pub output_audio_process: Option<Rc<RefCell<Child>>>,
|
||||
pub video_process: Option<Rc<RefCell<Child>>>,
|
||||
pub audio_record_bitrate: SpinButton,
|
||||
pub record_delay: SpinButton,
|
||||
pub record_frames: SpinButton,
|
||||
@ -318,7 +329,7 @@ impl Ffmpeg {
|
||||
(|| anyhow!("Unable to get height value"))?);
|
||||
let ffmpeg_convert = format!("ffmpeg -i file:{} -filter_complex '{}' \
|
||||
-loop 0 {} -y", &self.temp_video_filename,filter,&self.filename);
|
||||
std::process::Command::new("sh").arg("-c").arg(&ffmpeg_convert).output()?;
|
||||
Command::new("sh").arg("-c").arg(&ffmpeg_convert).output()?;
|
||||
}
|
||||
} else if is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
// Validate audio file integrity
|
||||
@ -387,7 +398,7 @@ impl Ffmpeg {
|
||||
.ok_or_else(|| anyhow!("Unable to kill the video recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id();
|
||||
std::process::Command::new("taskkill")
|
||||
Command::new("taskkill")
|
||||
.arg("/PID")
|
||||
.arg(pid.to_string())
|
||||
.arg("/F")
|
||||
@ -399,7 +410,7 @@ impl Ffmpeg {
|
||||
.ok_or_else(|| anyhow!("Unable to kill the input audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id();
|
||||
std::process::Command::new("taskkill")
|
||||
Command::new("taskkill")
|
||||
.arg("/PID")
|
||||
.arg(pid.to_string())
|
||||
.arg("/F")
|
||||
@ -411,7 +422,7 @@ impl Ffmpeg {
|
||||
.ok_or_else(|| anyhow!("Unable to kill the output audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id();
|
||||
std::process::Command::new("taskkill")
|
||||
Command::new("taskkill")
|
||||
.arg("/PID")
|
||||
.arg(pid.to_string())
|
||||
.arg("/F")
|
||||
@ -454,7 +465,7 @@ impl Ffmpeg {
|
||||
RecordMode::Screen => "desktop",
|
||||
RecordMode::Window => &format!("title={}", &self.window_title),
|
||||
};
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
let format = "gdigrab";
|
||||
let filename = self.saved_filename.clone();
|
||||
self.output = Path::new(&filename).extension()
|
||||
@ -480,7 +491,7 @@ impl Ffmpeg {
|
||||
.to_string();
|
||||
}
|
||||
// Video format
|
||||
ffmpeg_command.format(format);
|
||||
ffmpeg_command.arg(format);
|
||||
|
||||
// Show grabbed area
|
||||
if self.show_area.is_active() {
|
||||
@ -501,14 +512,14 @@ impl Ffmpeg {
|
||||
|
||||
// Record video with specified width and hight
|
||||
if let RecordMode::Area = mode {
|
||||
ffmpeg_command.size(width.into(), height.into()).args([
|
||||
ffmpeg_command.args(["-s", &width.to_string(), &height.to_string()]).args([
|
||||
"-offset_x", &x.to_string(),
|
||||
"-offset_y", &y.to_string()
|
||||
]);
|
||||
}
|
||||
|
||||
// input
|
||||
ffmpeg_command.input(display);
|
||||
ffmpeg_command.args(["-i", display]);
|
||||
|
||||
// Disable bitrate if value is zero
|
||||
if self.video_record_bitrate.value() as u16 > 0 {
|
||||
@ -545,7 +556,7 @@ impl Ffmpeg {
|
||||
}
|
||||
},
|
||||
]);
|
||||
ffmpeg_command.overwrite();
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// Sleep for delay
|
||||
sleep(Duration::from_secs(self.record_delay.value() as u64));
|
||||
@ -558,13 +569,23 @@ impl Ffmpeg {
|
||||
|
||||
// Stop video recording
|
||||
pub fn stop_video(&mut self) -> Result<()> {
|
||||
// Quit the process to stop recording
|
||||
// Kill the process to stop recording
|
||||
if self.video_process.is_some() {
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.video_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Failed to get video_process ID."))?
|
||||
.borrow_mut().id()
|
||||
))
|
||||
.output()?;
|
||||
|
||||
self.video_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Not exiting the video recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.quit()?;
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -577,12 +598,12 @@ impl Ffmpeg {
|
||||
.keep()?;
|
||||
self.temp_input_audio_filename = Path::new(&input_audio_tempfile.1).to_string_lossy()
|
||||
.to_string();
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("dshow")
|
||||
.input(format!("audio=\"{}\"", &self.audio_input_id.active_text()
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "dshow"])
|
||||
.args(["-i", &format!("audio=\"{}\"", &self.audio_input_id.active_text()
|
||||
.ok_or_else(|| anyhow!("Failed to get audio input source."))?)
|
||||
)
|
||||
.format("ogg");
|
||||
])
|
||||
.args(["-f", "ogg"]);
|
||||
// Disable bitrate if value is zero
|
||||
if self.audio_record_bitrate.value() as u16 > 0 {
|
||||
ffmpeg_command.args([
|
||||
@ -593,7 +614,7 @@ impl Ffmpeg {
|
||||
// Remove metadate
|
||||
ffmpeg_command.args(["-map_metadata", "-1"]);
|
||||
ffmpeg_command.arg(&self.temp_input_audio_filename);
|
||||
ffmpeg_command.overwrite();
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// Sleep for delay
|
||||
if !is_video_record(&self.temp_video_filename) {
|
||||
@ -607,13 +628,23 @@ impl Ffmpeg {
|
||||
|
||||
// Stop audio input recording
|
||||
pub fn stop_input_audio(&mut self) -> Result<()> {
|
||||
// Quit the process to stop recording
|
||||
// Kill the process to stop recording
|
||||
if self.input_audio_process.is_some() {
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.input_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Failed to get input_audio_process ID."))?
|
||||
.borrow_mut().id()
|
||||
))
|
||||
.output()?;
|
||||
|
||||
self.input_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Not exiting the input audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.quit()?;
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -626,14 +657,14 @@ impl Ffmpeg {
|
||||
.keep()?;
|
||||
self.temp_output_audio_filename = Path::new(&output_audio_tempfile.1).to_string_lossy()
|
||||
.to_string();
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("dshow")
|
||||
.input(format!("audio=\"{}\"", &self.audio_output_id))
|
||||
.format("ogg");
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "dshow"])
|
||||
.args(["-i", &format!("audio=\"{}\"", &self.audio_output_id)])
|
||||
.args(["-f", "ogg"]);
|
||||
// Remove metadate
|
||||
ffmpeg_command.args(["-map_metadata", "-1"]);
|
||||
ffmpeg_command.arg(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.overwrite();
|
||||
ffmpeg_command.arg("-y");
|
||||
|
||||
// Sleep for delay
|
||||
if !is_video_record(&self.temp_video_filename) && !is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
@ -647,13 +678,23 @@ impl Ffmpeg {
|
||||
|
||||
// Stop audio output recording
|
||||
pub fn stop_output_audio(&mut self) -> Result<()> {
|
||||
// Quit the process to stop recording
|
||||
// Kill the process to stop recording
|
||||
if self.output_audio_process.is_some() {
|
||||
Command::new("kill")
|
||||
.arg(format!(
|
||||
"{}",
|
||||
self.output_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Failed to get output_audio_process ID."))?
|
||||
.borrow_mut().id()
|
||||
))
|
||||
.output()?;
|
||||
|
||||
self.output_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Not exiting the output audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.quit()?;
|
||||
.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -662,45 +703,29 @@ impl Ffmpeg {
|
||||
pub fn merge(&mut self) -> Result<()> {
|
||||
if is_video_record(&self.temp_video_filename) {
|
||||
if self.output != "gif" {
|
||||
// Validate video file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_video_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
}
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.input(&self.temp_video_filename);
|
||||
ffmpeg_command.format("ogg");
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-i", &self.temp_video_filename]);
|
||||
ffmpeg_command.args(["-f", "ogg"]);
|
||||
if is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
ffmpeg_command.input(&self.temp_input_audio_filename);
|
||||
ffmpeg_command.args(["-i", &self.temp_input_audio_filename]);
|
||||
}
|
||||
if is_output_audio_record(&self.temp_output_audio_filename) {
|
||||
ffmpeg_command.input(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.args(["-i", &self.temp_output_audio_filename]);
|
||||
}
|
||||
ffmpeg_command.args([
|
||||
"-c:a",
|
||||
"aac",
|
||||
&self.saved_filename
|
||||
.clone()
|
||||
&self.saved_filename.clone()
|
||||
]);
|
||||
ffmpeg_command.overwrite()
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
} else {
|
||||
// Validate video file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_video_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
ffmpeg_command.arg("-y");
|
||||
let program = ffmpeg_command.get_program().to_string_lossy().to_string();
|
||||
let args: Vec<String> = ffmpeg_command.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
|
||||
let mut merge_command = program;
|
||||
for arg in args {
|
||||
merge_command.push_str(&format!(" {}", arg));
|
||||
}
|
||||
adw::glib::spawn_command_line_async(merge_command)?;
|
||||
} else {
|
||||
// Convert MP4 to GIF
|
||||
let filter = format!("fps={},scale={}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
|
||||
self.record_frames.value() as u16,
|
||||
@ -710,54 +735,44 @@ impl Ffmpeg {
|
||||
-loop 0 {} -y", &self.temp_video_filename,filter,
|
||||
&self.saved_filename
|
||||
.clone());
|
||||
std::process::Command::new("sh").arg("-c").arg(&ffmpeg_convert).output()?;
|
||||
adw::glib::spawn_command_line_async(ffmpeg_convert)?;
|
||||
}
|
||||
} else if is_input_audio_record(&self.temp_input_audio_filename) {
|
||||
// Validate audio file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_input_audio_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
}
|
||||
// If only audio is recording then convert it to chosen format
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("ogg");
|
||||
ffmpeg_command.input(&self.temp_input_audio_filename);
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "ogg"]);
|
||||
ffmpeg_command.args(["-i", &self.temp_input_audio_filename]);
|
||||
if is_output_audio_record(&self.temp_output_audio_filename) {
|
||||
ffmpeg_command.input(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.args(["-i", &self.temp_output_audio_filename]);
|
||||
}
|
||||
ffmpeg_command.args([
|
||||
"-c:a",
|
||||
"aac",
|
||||
&self.saved_filename
|
||||
.clone()
|
||||
]).overwrite()
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
} else {
|
||||
// Validate audio file integrity
|
||||
let start_time = Instant::now();
|
||||
let duration = Duration::from_secs(60);
|
||||
loop {
|
||||
if is_valide(&self.temp_output_audio_filename)? {
|
||||
break;
|
||||
} else if Instant::now().duration_since(start_time) >= duration {
|
||||
return Err(Error::msg("Unable to validate tmp video file."));
|
||||
}
|
||||
.clone(),
|
||||
"-y"
|
||||
]);
|
||||
let program = ffmpeg_command.get_program().to_string_lossy().to_string();
|
||||
let args: Vec<String> = ffmpeg_command.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
|
||||
let mut merge_command = program;
|
||||
for arg in args {
|
||||
merge_command.push_str(&format!(" {}", arg));
|
||||
}
|
||||
adw::glib::spawn_command_line_async(merge_command)?;
|
||||
} else {
|
||||
// If only output audio is recording then convert it to chosen format
|
||||
let mut ffmpeg_command = FfmpegCommand::new();
|
||||
ffmpeg_command.format("ogg");
|
||||
ffmpeg_command.input(&self.temp_output_audio_filename);
|
||||
ffmpeg_command.arg(&self.saved_filename
|
||||
.clone())
|
||||
.overwrite()
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
let mut ffmpeg_command = Command::new("ffmpeg");
|
||||
ffmpeg_command.args(["-f", "ogg"]);
|
||||
ffmpeg_command.args(["-i", &self.temp_output_audio_filename]);
|
||||
ffmpeg_command.arg(&self.saved_filename.clone());
|
||||
ffmpeg_command.arg("-y");
|
||||
let program = ffmpeg_command.get_program().to_string_lossy().to_string();
|
||||
let args: Vec<String> = ffmpeg_command.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect();
|
||||
let mut merge_command = program;
|
||||
for arg in args {
|
||||
merge_command.push_str(&format!(" {}", arg));
|
||||
}
|
||||
adw::glib::spawn_command_line_async(merge_command)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -779,9 +794,8 @@ impl Ffmpeg {
|
||||
let pid = self.video_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Unable to kill the video recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id();
|
||||
std::process::Command::new("taskkill")
|
||||
.borrow_mut().id();
|
||||
Command::new("taskkill")
|
||||
.arg("/PID")
|
||||
.arg(pid.to_string())
|
||||
.arg("/F")
|
||||
@ -791,9 +805,8 @@ impl Ffmpeg {
|
||||
let pid = self.input_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Unable to kill the input audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id();
|
||||
std::process::Command::new("taskkill")
|
||||
.borrow_mut().id();
|
||||
Command::new("taskkill")
|
||||
.arg("/PID")
|
||||
.arg(pid.to_string())
|
||||
.arg("/F")
|
||||
@ -803,9 +816,8 @@ impl Ffmpeg {
|
||||
let pid = self.output_audio_process
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("Unable to kill the output audio recording process successfully."))?
|
||||
.borrow_mut()
|
||||
.as_inner().id();
|
||||
std::process::Command::new("taskkill")
|
||||
.borrow_mut().id();
|
||||
Command::new("taskkill")
|
||||
.arg("/PID")
|
||||
.arg(pid.to_string())
|
||||
.arg("/F")
|
||||
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
adw = { version = "0.2.1", package = "libadwaita", features = ["gtk_v4_6"] }
|
||||
anyhow = "1.0.86"
|
||||
async-std = {version = "1.12.0", features = ["attributes"]}
|
||||
blue-recorder-core = { path = "../core", features = ["gtk"] }
|
||||
@ -11,7 +12,6 @@ dark-light = "1.0.0"
|
||||
dirs = "4.0.0"
|
||||
fluent-bundle = "0.15.3"
|
||||
glib = "0.10.3"
|
||||
adw = { version = "0.2.1", package = "libadwaita", features = ["gtk_v4_6"] }
|
||||
regex = "1.4.3"
|
||||
rust-ini = "0.16"
|
||||
secfmt = "0.1.1"
|
||||
|
@ -994,8 +994,7 @@ fn build_ui(application: &Application, error_dialog: MessageDialog, error_messag
|
||||
if _audio_input_switch.is_active() {
|
||||
match _ffmpeg_record_interface.borrow_mut().stop_input_audio() {
|
||||
Ok(_) => {
|
||||
_play_button.set_sensitive(false);
|
||||
_play_button.set_tooltip_text(Some(&get_bundle("play-inactive-tooltip", None)));
|
||||
// Continue
|
||||
},
|
||||
Err(error) => {
|
||||
if _video_switch.is_active() {
|
||||
@ -1016,8 +1015,7 @@ fn build_ui(application: &Application, error_dialog: MessageDialog, error_messag
|
||||
if _audio_output_switch.is_active() {
|
||||
match _ffmpeg_record_interface.borrow_mut().stop_output_audio() {
|
||||
Ok(_) => {
|
||||
_play_button.set_sensitive(false);
|
||||
_play_button.set_tooltip_text(Some(&get_bundle("play-inactive-tooltip", None)));
|
||||
// Continue
|
||||
},
|
||||
Err(error) => {
|
||||
if _video_switch.is_active() {
|
||||
@ -1038,8 +1036,7 @@ fn build_ui(application: &Application, error_dialog: MessageDialog, error_messag
|
||||
if _video_switch.is_active() {
|
||||
match _ffmpeg_record_interface.borrow_mut().stop_video() {
|
||||
Ok(_) => {
|
||||
_play_button.set_sensitive(false);
|
||||
_play_button.set_tooltip_text(Some(&get_bundle("play-inactive-tooltip", None)));
|
||||
// Continue
|
||||
},
|
||||
Err(error) => {
|
||||
if _video_switch.is_active() {
|
||||
@ -1057,6 +1054,19 @@ fn build_ui(application: &Application, error_dialog: MessageDialog, error_messag
|
||||
},
|
||||
}
|
||||
}
|
||||
// Save tmp files
|
||||
match _ffmpeg_record_interface.borrow_mut().merge() {
|
||||
Ok(_) => {
|
||||
// Continue
|
||||
},
|
||||
Err(error) => {
|
||||
show_play = false;
|
||||
let text_buffer = TextBuffer::new(None);
|
||||
text_buffer.set_text(&format!("{}", error));
|
||||
_error_message.set_buffer(Some(&text_buffer));
|
||||
_error_dialog.show();
|
||||
},
|
||||
};
|
||||
if _video_switch.is_active() {
|
||||
_mouse_switch.set_sensitive(true);
|
||||
_follow_mouse_switch.set_sensitive(true);
|
||||
@ -1064,32 +1074,12 @@ fn build_ui(application: &Application, error_dialog: MessageDialog, error_messag
|
||||
enable_input_widgets(input_widgets.clone());
|
||||
_stop_button.hide();
|
||||
if show_play {
|
||||
_play_button.set_tooltip_text(Some(&get_bundle("play-tooltip", None)));
|
||||
_play_button.show();
|
||||
}
|
||||
_record_button.show();
|
||||
});
|
||||
|
||||
// Save tmp files
|
||||
let _error_dialog = error_dialog.clone();
|
||||
let _error_message = error_message.clone();
|
||||
let _play_button = play_button.clone();
|
||||
let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone();
|
||||
play_button.connect_show(move |_| {
|
||||
match _ffmpeg_record_interface.borrow_mut().merge() {
|
||||
Ok(_) => {
|
||||
_play_button.set_sensitive(true);
|
||||
_play_button.set_tooltip_text(Some(&get_bundle("play-tooltip", None)));
|
||||
},
|
||||
Err(error) => {
|
||||
_play_button.hide();
|
||||
let text_buffer = TextBuffer::new(None);
|
||||
text_buffer.set_text(&format!("{}", error));
|
||||
_error_message.set_buffer(Some(&text_buffer));
|
||||
_error_dialog.show();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// Delay window button
|
||||
let _delay_window_button = delay_window_button.clone();
|
||||
delay_window_button.connect_clicked(move |_| {});
|
||||
|
@ -173,7 +173,6 @@ frames-tooltip = خصص عدد الصور في الثانية
|
||||
hide-tooltip = اطوِ نافذة المسجل الأزرق عند بدء التسجيل
|
||||
mouse-tooltip = يظهر مؤشر الفأرة عند التسجيل
|
||||
play-tooltip = انقر لمشاهدة ما سُجِّل
|
||||
play-inactive-tooltip = ترقَّب ريثما يتم حفظ ما سُجِّل
|
||||
record-tooltip = يشرع في التسجيل
|
||||
screen-tooltip = يحدد الشاشة كاملة ليسجلها
|
||||
show-area-tooltip = يُظهر البقعة المنتقاة أثناء التسجيل
|
||||
|
@ -173,7 +173,6 @@ frames-tooltip = خصص عدد الصور في الثانية
|
||||
hide-tooltip = اطوِ نافذة المسجل الأزرق عند بدء التسجيل
|
||||
mouse-tooltip = يظهر مؤشر الفأرة عند التسجيل
|
||||
play-tooltip = انقر لمشاهدة ما سُجِّل
|
||||
play-inactive-tooltip = ترقَّب ريثما يتم حفظ ما سُجِّل
|
||||
record-tooltip = يشرع في التسجيل
|
||||
screen-tooltip = يحدد الشاشة كاملة ليسجلها
|
||||
show-area-tooltip = يُظهر البقعة المنتقاة أثناء التسجيل
|
||||
|
@ -173,7 +173,6 @@ frames-tooltip = Set frames rate
|
||||
hide-tooltip = Hide window when start recording
|
||||
mouse-tooltip = Mouse appears in video recording
|
||||
play-tooltip = Click to play video
|
||||
play-inactive-tooltip = Please wait until the file is saved
|
||||
record-tooltip = Start screen record
|
||||
screen-tooltip = Select screen to record
|
||||
show-area-tooltip = Show selected area during record
|
||||
|
@ -173,7 +173,6 @@ frames-tooltip = Set frames rate
|
||||
hide-tooltip = Hide window when start recording
|
||||
mouse-tooltip = Mouse appears in video recording
|
||||
play-tooltip = Click to play video
|
||||
play-inactive-tooltip = Please wait until the file is saved
|
||||
record-tooltip = Start screen record
|
||||
screen-tooltip = Select screen to record
|
||||
show-area-tooltip = Show selected area during record
|
||||
|
Loading…
Reference in New Issue
Block a user