Compare commits

..

No commits in common. "master" and "v1.0.1" have entirely different histories.

10 changed files with 184 additions and 163 deletions

1
.gitignore vendored
View file

@ -1,5 +1,4 @@
/target /target
*.log
*.webp *.webp
*.webm *.webm
*.ogg *.ogg

104
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 3
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -71,6 +71,12 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"
@ -87,10 +93,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "bytes" name = "bitflags"
version = "1.6.0" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]] [[package]]
name = "cc" name = "cc"
@ -186,9 +198,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]] [[package]]
name = "indicatif" name = "indicatif"
@ -224,6 +236,16 @@ version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.3" version = "2.6.3"
@ -252,7 +274,7 @@ dependencies = [
[[package]] [[package]]
name = "n-mb" name = "n-mb"
version = "1.1.3" version = "1.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -286,6 +308,29 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "pbr" name = "pbr"
version = "1.1.1" version = "1.1.1"
@ -311,28 +356,43 @@ checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.81" version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -342,6 +402,22 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "socket2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -350,9 +426,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.60" version = "2.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -370,8 +446,10 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"num_cpus", "num_cpus",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]

View file

@ -1,7 +1,7 @@
[package] [package]
name = "n-mb" name = "n-mb"
authors = ["Djkáťo <djkatovfx@gmail.com>"] authors = ["Djkáťo <djkatovfx@gmail.com>"]
version = "1.1.3" version = "1.0.0"
edition = "2021" edition = "2021"
description = "Simple ffmpeg wrapper to parse files to the most efficient formats within a set size" description = "Simple ffmpeg wrapper to parse files to the most efficient formats within a set size"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@ -10,30 +10,14 @@ repository = "https://github.com/djkato/n-mb"
readme = "README.md" readme = "README.md"
keywords = ["media", "ffmpeg", "cli"] keywords = ["media", "ffmpeg", "cli"]
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
exclude = ["*.mp4", "*.mp3", "*.jpg", "*.ogg", "*.webp", "*.webm", "*.log"]
[dependencies] [dependencies]
anyhow = "1.0.75" anyhow = "1.0.75"
clap = { version = "4.4.4", features = ["cargo"] } clap = { version = "4.4.4", features = ["cargo"] }
indicatif = "0.17.7" indicatif = "0.17.7"
pbr = "1.1.1" pbr = "1.1.1"
tokio = { version = "1.32.0", features = [ tokio = { version = "1.32.0", features = ["full"] }
"macros",
"rt-multi-thread",
"process",
"io-std",
"sync",
"time",
"io-util",
] }
[[bin]] [[bin]]
name = "nmb" name="nmb"
path = "src/main.rs" path="src/main.rs"
[profile.release]
opt-level = 'z' # Optimize for size
lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
panic = 'abort' # Abort on panic
# strip = true # Strip symbols from binary*

View file

@ -1 +0,0 @@
ko_fi: djkato

View file

@ -1,21 +1,17 @@
<a href='https://ko-fi.com/A0A8Q3SVZ' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
# Automatically converts any media file and makes sure its under your limit! # Automatically converts any media file and makes sure its under your limit!
For all those who want to post memes that are just too big and surpass the 25mb free upload limit on discord, this is the app for you! For all those who want to post memes that are just too big and surpass the 25mb free upload limit on discord, this is the app for you!
![nbm usage](https://github.com/djkato/n-mb/assets/25299243/b2531d88-5de1-465f-9bef-d0ad225f06b4) ![nbm usage](https://github.com/djkato/n-mb/assets/25299243/b2531d88-5de1-465f-9bef-d0ad225f06b4)
## This program outputs to following formats: ## This program outputs to following formats:
- audio codec: opus .ogg - audio codec: opus .ogg
- video codec: vp9 + opus .webm - video codec: vp9 + opus .webm
- image codec: vp8 .webp (for gifs too) - image codec: vp8 .webp (for gifs too)
## ~~How to install Binary(Windows, Linux):~~ ## How to install Binary(Windows, Linux):
1. Download binary from [Releases](https://github.com/djkato/n-mb/releases), put into $PATH
**Releases are no longer being kept uptodate, please follow [Source installation](#how-to-install-from-sourcewindows-linux-macos)** 2. get ffmpeg for your platform [here](https://ffmpeg.org/download.html), put into $PATH
3. execute anywhere using the `nmb --size/-s <SIZE IN MB> --codec/-c <WEBM/HEVC> --files/-f=<FILE 1>,<FILE 2> . . .` command!
1. ~~Download binary from [Releases](https://github.com/djkato/n-mb/releases), put into $PATH~~
2. ~~get ffmpeg for your platform [here](https://ffmpeg.org/download.html), put into $PATH~~
3. ~~execute anywhere using the `nmb --size/-s <SIZE IN MB> --codec/-c <WEBM/HEVC> --files/-f=<FILE 1>,<FILE 2> . . .` command!~~
## How to install From Source(Windows, Linux, MacOS): ## How to install From Source(Windows, Linux, MacOS):
1. get rustup (cargo, rustc etc) from [here](https://www.rust-lang.org/tools/install) 1. get rustup (cargo, rustc etc) from [here](https://www.rust-lang.org/tools/install)

View file

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

View file

@ -28,7 +28,7 @@ pub struct FFMPEGCommand {
struct MediaData { struct MediaData {
resolution: Option<(u16, u16)>, resolution: Option<(u16, u16)>,
duration: f32, duration: f32,
old_kbit_rate: Option<u32>, old_kbit_rate: Option<u16>,
} }
impl FFMPEGCommand { impl FFMPEGCommand {
@ -234,8 +234,6 @@ impl FFMPEGCommand {
"6", "6",
"-qmax", "-qmax",
"60", "60",
"-qmin",
"1",
"-g", "-g",
"240", "240",
"-passlogfile", "-passlogfile",
@ -251,7 +249,7 @@ impl FFMPEGCommand {
"-pass", "-pass",
"1", "1",
"-f", "-f",
new_path.extension().unwrap().to_str().unwrap(), path.extension().unwrap().to_str().unwrap(),
]); ]);
if cfg!(windows) { if cfg!(windows) {
command.arg("NUL"); command.arg("NUL");
@ -267,10 +265,6 @@ impl FFMPEGCommand {
.to_str() .to_str()
.context("missing or bad path")?, .context("missing or bad path")?,
]); ]);
#[cfg(debug_assertions)]
dbg!(&command);
#[cfg(debug_assertions)]
dbg!(&command2);
Ok(FFMPEGCommand { Ok(FFMPEGCommand {
file_name: path.file_name().unwrap().to_str().unwrap().to_owned(), file_name: path.file_name().unwrap().to_str().unwrap().to_owned(),
resolution: None, resolution: None,
@ -343,19 +337,7 @@ pub enum EncodingStatus {
} }
async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> { async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
let args = [
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=width,height,duration,bit_rate",
"-of",
"csv=s=,:p=0",
];
let ffprobe = Command::new("ffprobe") let ffprobe = Command::new("ffprobe")
.args(args)
.arg(path) .arg(path)
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.output() .output()
@ -365,68 +347,20 @@ async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
.exit_ok() .exit_ok()
.context("Failed to run ffprobe. Make sure ffprobe is installed and file exists")?; .context("Failed to run ffprobe. Make sure ffprobe is installed and file exists")?;
let text = std::str::from_utf8(&ffprobe.stdout)?; let text = std::str::from_utf8(&ffprobe.stderr)?;
let mem = text.split(',').collect::<Vec<_>>(); let duration;
if let Ok(dur) = parse_duration(text) {
let width = mem.first().and_then(|v| v.parse::<u16>().ok()); duration = dur
let height = mem.get(1).and_then(|v| v.parse::<u16>().ok()); } else {
bail!("FFProbe missing duration in media. Is file corrupted or non-existent?")
let duration = match mem.get(2).and_then(|v| v.parse::<f32>().ok()) { }
Some(d) => d, let old_kbit_rate = parse_bitrate(text).ok();
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);
let mut resolution = None;
if text.contains("Stream") {
resolution = parse_resolution(text).ok();
}
Ok(MediaData { Ok(MediaData {
duration, duration,
resolution, resolution,
@ -434,26 +368,67 @@ async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<MediaData> {
}) })
} }
async fn get_attribute_from_meta(attr: &str, path: &PathBuf) -> Option<String> { fn parse_duration(text: &str) -> anyhow::Result<f32> {
let ffprobe = Command::new("ffprobe") let text = text[text.find("Duration").unwrap()..].to_owned();
.args([ let dur_text = text[text
"-v", .find(':')
"error", .context("something wrong with the ffprobe output")?
"-select_streams", + 2
"v:0", ..text
"-show_entries", .find(',')
&format!("stream={attr}:stream_args={attr}"), .context("something wrong with the ffprobe output")?]
"-of", .to_owned();
"csv=s=,:p=0", let durs_text: Vec<&str> = dur_text.split(':').collect();
]) let mut durs_text_iter = durs_text.into_iter();
.arg(path) let h = durs_text_iter
.stderr(Stdio::piped()) .next()
.output() .context("something wrong with the ffprobe output")?
.await .parse::<f32>()?;
.ok()?; let m = durs_text_iter
ffprobe.status.exit_ok().ok()?; .next()
.context("something wrong with the ffprobe output")?
std::str::from_utf8(&ffprobe.stdout) .parse::<f32>()?;
.ok() let s = durs_text_iter
.map(|v| v.to_string()) .next()
.context("something wrong with the ffprobe output")?
.parse::<f32>()?;
Ok(h * 60. * 60. + m * 60. + s)
}
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))
} }

View file

@ -110,13 +110,15 @@ async fn main() -> anyhow::Result<()> {
} }
command.command.0.stdout(Stdio::piped()); command.command.0.stdout(Stdio::piped());
command.command.0.stderr(Stdio::piped()); command.command.0.stderr(Stdio::null());
command.command.0.stdin(Stdio::null()); command.command.0.stdin(Stdio::null());
command.command.0.kill_on_drop(true);
if command.media_type == MediaType::Video { if command.media_type == MediaType::Video {
let mut pass2 = command.command.1.unwrap(); let mut pass2 = command.command.1.unwrap();
pass2.stdout(Stdio::piped()); pass2.stdout(Stdio::piped());
pass2.stderr(Stdio::piped()); pass2.stderr(Stdio::null());
pass2.stdin(Stdio::null()); pass2.stdin(Stdio::null());
pass2.kill_on_drop(true);
command.command.1 = Some(pass2) command.command.1 = Some(pass2)
} }
@ -150,14 +152,12 @@ async fn main() -> anyhow::Result<()> {
use std::time::Duration; use std::time::Duration;
use tokio::time::interval; use tokio::time::interval;
let commands_ref = commands.clone(); let commands_ref = commands.clone();
let mut intv = interval(Duration::from_millis(10)); let mut intv = interval(Duration::from_millis(50));
command_spawns.push(tokio::spawn(async move { command_spawns.push(tokio::spawn(async move {
intv.tick().await; intv.tick().await;
'line: while let Ok(Some(line)) = buff_reader.1.next_line().await { 'line: while let Ok(Some(line)) = buff_reader.1.next_line().await {
#[cfg(debug_assertions)]
dbg!(&line);
if let Some(time_start) = line.find("out_time=") { if let Some(time_start) = line.find("out_time=") {
let time: Vec<String> = line[time_start + 10..] let time: Vec<String> = line[time_start + 10..]
.split(':') .split(':')
@ -170,13 +170,9 @@ async fn main() -> anyhow::Result<()> {
if let Ok(number) = part.parse::<f32>() { if let Ok(number) = part.parse::<f32>() {
parsed_time.push(number) parsed_time.push(number)
} else { } else {
// parsed_time.push(0.); break 'line;
// 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 time = parsed_time[0] * 3600. + parsed_time[1] * 60. + parsed_time[2];
let mut command = commands_ref.lock().await; let mut command = commands_ref.lock().await;
@ -186,8 +182,6 @@ async fn main() -> anyhow::Result<()> {
command.progressed_time = time; command.progressed_time = time;
} }
if let Some(progress_i) = line.find("progress=") { 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 mut command = commands_ref.lock().await;
let command = command.get_mut(buff_reader.0).unwrap(); let command = command.get_mut(buff_reader.0).unwrap();
@ -211,9 +205,7 @@ async fn main() -> anyhow::Result<()> {
command.passed_pass_1 = true; command.passed_pass_1 = true;
} }
}, },
_ => { _ => command.status = EncodingStatus::Finished,
command.status = EncodingStatus::Finished;
}
}, },
"continue" => command.status = EncodingStatus::InProgress, "continue" => command.status = EncodingStatus::InProgress,
_ => (), _ => (),

BIN
test.avi

Binary file not shown.

BIN
test.mkv

Binary file not shown.