Compare commits

...

5 commits

Author SHA1 Message Date
7b49ec9d28 +0.0.1 2024-12-23 23:26:37 +01:00
d927d7811a fix break when missing enstimate time 2024-12-23 23:24:52 +01:00
djkato
000596424f version bump 2024-07-27 19:16:12 +02:00
djkato
0083da10a0 hide ffmpeg command output in release mode 2024-07-27 19:13:40 +02:00
a05dba3518 supplement video data from video stream metadata 2024-06-26 00:36:55 +02:00
7 changed files with 104 additions and 97 deletions

4
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -252,7 +252,7 @@ dependencies = [
[[package]]
name = "n-mb"
version = "1.1.0"
version = "1.1.3"
dependencies = [
"anyhow",
"clap",

View file

@ -1,7 +1,7 @@
[package]
name = "n-mb"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
version = "1.1.1"
version = "1.1.3"
edition = "2021"
description = "Simple ffmpeg wrapper to parse files to the most efficient formats within a set size"
license = "MIT OR Apache-2.0"

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -267,8 +267,10 @@ impl FFMPEGCommand {
.to_str()
.context("missing or bad path")?,
]);
// dbg!(&command);
// dbg!(&command2);
#[cfg(debug_assertions)]
dbg!(&command);
#[cfg(debug_assertions)]
dbg!(&command2);
Ok(FFMPEGCommand {
file_name: path.file_name().unwrap().to_str().unwrap().to_owned(),
resolution: None,
@ -341,8 +343,7 @@ pub enum EncodingStatus {
}
async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
let ffprobe = Command::new("ffprobe")
.args([
let args = [
"-v",
"error",
"-select_streams",
@ -351,7 +352,10 @@ async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
"stream=width,height,duration,bit_rate",
"-of",
"csv=s=,:p=0",
])
];
let ffprobe = Command::new("ffprobe")
.args(args)
.arg(path)
.stderr(Stdio::piped())
.output()
@ -367,27 +371,62 @@ async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
let width = mem.first().and_then(|v| v.parse::<u16>().ok());
let height = mem.get(1).and_then(|v| v.parse::<u16>().ok());
let duration = mem
.get(2)
.and_then(|v| v.parse::<f32>().ok())
.context("missing duration")?;
let duration = match mem.get(2).and_then(|v| v.parse::<f32>().ok()) {
Some(d) => d,
None => {
let metadata = get_attribute_from_meta("duration", path)
.await
.context("can't find duration anywhere")?;
let res = metadata.parse::<f32>();
//see if metadatat had seconds directly
if let Ok(d) = res {
d
} else {
// try to convert 00:00:00:00 to 0.000s
if !metadata.contains(":") {
return Err(anyhow::anyhow!("can't find duration of media anywhere"));
} else {
let mut res = 0.;
let mut iter = metadata.split(':').rev();
let secs = iter.next().map(|n| n.parse::<f32>().ok()).flatten();
let mins = iter
.next()
.map(|n| n.parse::<f32>().ok().map(|m| m * 60.))
.flatten();
let hrs = iter
.next()
.map(|n| n.parse::<f32>().ok().map(|h| h * 3600.))
.flatten();
let days = iter
.next()
.map(|n| n.parse::<f32>().ok().map(|d| d * 24. * 3600.))
.flatten();
if let Some(s) = secs {
res = res + s
};
if let Some(m) = mins {
res = res + m
};
if let Some(h) = hrs {
res = res + h
};
if let Some(d) = days {
res = res + d
};
res
}
}
}
};
dbg!(&duration);
let old_kbit_rate = mem
.get(3)
.and_then(|v| v.parse::<u32>().ok().map(|v| v / 1000));
let resolution = width.zip(height);
// if let Ok(dur) = parse_duration(text) {
// duration = dur
// } else {
// bail!("FFProbe missing duration in media. Is file corrupted or non-existent?")
// }
// let old_kbit_rate = parse_bitrate(text).ok();
//
// let mut resolution = None;
// if text.contains("Stream") {
// resolution = parse_resolution(text).ok();
// }
//
Ok(MediaData {
duration,
resolution,
@ -395,67 +434,26 @@ async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
})
}
fn parse_duration(text: &str) -> anyhow::Result<f32> {
let text = text[text.find("Duration").unwrap()..].to_owned();
let dur_text = text[text
.find(':')
.context("something wrong with the ffprobe output")?
+ 2
..text
.find(',')
.context("something wrong with the ffprobe output")?]
.to_owned();
let durs_text: Vec<&str> = dur_text.split(':').collect();
let mut durs_text_iter = durs_text.into_iter();
let h = durs_text_iter
.next()
.context("something wrong with the ffprobe output")?
.parse::<f32>()?;
let m = durs_text_iter
.next()
.context("something wrong with the ffprobe output")?
.parse::<f32>()?;
let s = durs_text_iter
.next()
.context("something wrong with the ffprobe output")?
.parse::<f32>()?;
Ok(h * 60. * 60. + m * 60. + s)
}
async fn get_attribute_from_meta(attr: &str, path: &PathBuf) -> Option<String> {
let ffprobe = Command::new("ffprobe")
.args([
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
&format!("stream={attr}:stream_args={attr}"),
"-of",
"csv=s=,:p=0",
])
.arg(path)
.stderr(Stdio::piped())
.output()
.await
.ok()?;
ffprobe.status.exit_ok().ok()?;
// fn parse_bitrate(text: &str) -> anyhow::Result<u16> {
// let text = text[text.find("bitrate").unwrap()..].to_owned();
// let bitrate_text = text[9..text.find('/').unwrap() - 2].to_owned();
//
// Ok(bitrate_text.parse::<u16>()?)
// }
//
// fn parse_resolution(text: &str) -> anyhow::Result<(u16, u16)> {
// let text = text[text.find("Stream").unwrap()..].to_owned();
// let sar_i = text
// .find("[SAR ")
// .context("something wrong with the ffprobe output")?
// - 1;
//
// let rb_b4_sar_i = text[..sar_i]
// .rfind(',')
// .context("something wrong with the ffprobe output")?
// + 1;
//
// let res_text = text[rb_b4_sar_i..sar_i].to_owned();
// let res_text = res_text.trim().to_owned();
//
// let width = res_text[..res_text
// .find('x')
// .context("something wrong with ffprobe output")?]
// .to_owned()
// .parse::<u16>()?;
//
// let height = res_text[res_text
// .find('x')
// .context("something wrong with ffprobe output")?
// + 1..]
// .to_owned()
// .parse::<u16>()?;
//
// Ok((width, height))
// }
std::str::from_utf8(&ffprobe.stdout)
.ok()
.map(|v| v.to_string())
}

View file

@ -150,13 +150,14 @@ async fn main() -> anyhow::Result<()> {
use std::time::Duration;
use tokio::time::interval;
let commands_ref = commands.clone();
let mut intv = interval(Duration::from_millis(50));
let mut intv = interval(Duration::from_millis(10));
command_spawns.push(tokio::spawn(async move {
intv.tick().await;
'line: while let Ok(Some(line)) = buff_reader.1.next_line().await {
// dbg!(&line);
#[cfg(debug_assertions)]
dbg!(&line);
if let Some(time_start) = line.find("out_time=") {
let time: Vec<String> = line[time_start + 10..]
.split(':')
@ -169,9 +170,13 @@ async fn main() -> anyhow::Result<()> {
if let Ok(number) = part.parse::<f32>() {
parsed_time.push(number)
} else {
break 'line;
// parsed_time.push(0.);
// break 'line;
}
}
if parsed_time.len() == 0 {
parsed_time.append(&mut vec![0., 0., 0.]);
}
let time = parsed_time[0] * 3600. + parsed_time[1] * 60. + parsed_time[2];
let mut command = commands_ref.lock().await;
@ -181,6 +186,8 @@ async fn main() -> anyhow::Result<()> {
command.progressed_time = time;
}
if let Some(progress_i) = line.find("progress=") {
#[cfg(debug_assertions)]
println!("found progress!, {}", &line[progress_i + 9..]);
let mut command = commands_ref.lock().await;
let command = command.get_mut(buff_reader.0).unwrap();

BIN
test.avi Normal file

Binary file not shown.

BIN
test.mkv Normal file

Binary file not shown.