n-mb/src/main.rs

185 lines
7 KiB
Rust
Raw Normal View History

2023-09-24 17:40:00 +00:00
#![feature(exit_status_error)]
use anyhow::Context;
use clap::{arg, command, value_parser};
use encoder::{FFMPEGCommand, MediaType};
use std::{path::PathBuf, process::Stdio, sync::Arc};
use tokio::{
io::{AsyncBufReadExt, BufReader},
sync::Mutex,
};
use ui::display;
use crate::encoder::EncodingStatus;
mod encoder;
mod ui;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = command!()
.about("Simple program to parse files to the most efficient formats within a set size")
.arg(
arg!(-s --size <NUMBER> "Target megabyte size. If not set, default of 25mb (Discords free limit)")
.required(false)
.default_value("25")
.value_parser(value_parser!(u16))
)
.arg(
arg!(-f --files <FILES> "Comma separated files to convert. EG: -f=<FILE>,<FILE>")
.required(true)
.value_parser(value_parser!(PathBuf))
.value_delimiter(',')
.num_args(1..=std::usize::MAX)
).get_matches();
let size = args
.get_one::<u16>("size")
.expect("Default value dissapeared from rate");
let files = args
.get_many::<PathBuf>("files")
.context("No files specified")?
.collect::<Vec<_>>();
let commands: Arc<Mutex<Vec<FFMPEGCommand>>> = Arc::new(Mutex::new(vec![]));
{
let mut commands_mut = commands.try_lock().unwrap();
for file in files {
let mut command: FFMPEGCommand;
let extension = file
.extension()
.context("File doesn't have extension - is folder or is invalid file")?;
match extension
.to_str()
.expect("Somehow Extension contains charcters we can't decode lol")
.to_lowercase()
.as_str()
{
"webm" | "mp4" | "mov" | "avi" | "mpeg" | "mkv" => {
command = FFMPEGCommand::new(MediaType::Video, file, size.clone()).await?;
}
"mp3" | "wav" | "ogg" | "opus" | "flac" | "aiff" => {
command = FFMPEGCommand::new(MediaType::Audio, file, size.clone()).await?;
}
"jpg" | "png" | "webp" | "exr" | "jpeg" | "tiff" | "bpm" | "raw" | "tif" => {
command = FFMPEGCommand::new(MediaType::Image, file, size.clone()).await?;
}
"gif" => {
command =
FFMPEGCommand::new(MediaType::AnimatedImage, file, size.clone()).await?;
}
_ => break,
}
//dbg!(&command.command.0);
command.command.0.stdout(Stdio::piped());
command.command.0.stderr(Stdio::null());
command.command.0.stdin(Stdio::null());
command.command.0.kill_on_drop(true);
if command.media_type == MediaType::Video {
let mut pass2 = command.command.1.unwrap();
pass2.stdout(Stdio::piped());
pass2.stderr(Stdio::null());
pass2.stdin(Stdio::null());
pass2.kill_on_drop(true);
command.command.1 = Some(pass2)
}
command.exec_handle = Some(command.command.0.spawn()?);
command.buff_reader = Some(
BufReader::new(
command
.exec_handle
.as_mut()
.unwrap()
.stdout
.take()
.expect("encoder stdout missing - exited early or unavailable"),
)
.lines(),
);
commands_mut.push(command);
}
}
let mut command_spawns = vec![];
let mut buff_readers = vec![];
let ui = tokio::spawn(display(commands.clone()));
{
for (i, command) in commands.lock().await.iter_mut().enumerate() {
buff_readers.push((i, command.buff_reader.take().unwrap()));
}
}
for mut buff_reader in buff_readers.into_iter() {
use std::time::Duration;
use tokio::time::interval;
let commands_ref = commands.clone();
let mut intv = interval(Duration::from_millis(50));
command_spawns.push(tokio::spawn(async move {
intv.tick().await;
'line: while let Ok(Some(line)) = buff_reader.1.next_line().await {
if let Some(time_start) = line.find("out_time=") {
let time: Vec<String> = line[time_start + 10..]
.split(":")
.map(|s| s.to_owned())
.collect();
let mut parsed_time = vec![];
for part in time {
if let Ok(number) = part.parse::<f32>() {
parsed_time.push(number)
} else {
break 'line;
}
}
let time = parsed_time[0] * 3600. + parsed_time[1] * 60. + parsed_time[2];
let mut command = commands_ref.lock().await;
let command = command.get_mut(buff_reader.0).unwrap();
command.status = EncodingStatus::InProgress;
command.progressed_time = time;
}
if let Some(progress_i) = line.find("progress=") {
let mut command = commands_ref.lock().await;
let command = command.get_mut(buff_reader.0).unwrap();
match &line[progress_i + 9..] {
"end" => match command.media_type {
//Executes 2nd pass
MediaType::Video => match command.passed_pass_1 {
true => command.status = EncodingStatus::Finished,
false => {
command.exec_handle =
Some(command.command.1.as_mut().unwrap().spawn().unwrap());
buff_reader = (
buff_reader.0,
BufReader::new(
command.exec_handle.as_mut().unwrap().stdout.take().expect(
"encoder stdout missing - exited early or unavailable",
),
)
.lines(),
);
command.passed_pass_1 = true;
}
},
_ => command.status = EncodingStatus::Finished,
},
"continue" => command.status = EncodingStatus::InProgress,
_ => (),
};
}
}
}));
}
for spawn in command_spawns {
spawn.await?;
}
ui.await?;
Ok(())
}