Compare commits
19 commits
Author | SHA1 | Date | |
---|---|---|---|
7b49ec9d28 | |||
d927d7811a | |||
![]() |
000596424f | ||
![]() |
0083da10a0 | ||
a05dba3518 | |||
39ec9a5372 | |||
74c41ab9e4 | |||
fcce5bd593 | |||
9e82673aca | |||
276e26d7e2 | |||
6b630a495e | |||
bf5cee22cc | |||
b226a4d36d | |||
48dba729db | |||
a508e5eb21 | |||
82d6a6ae54 | |||
d086121cea | |||
4e09c735dd | |||
5f8901d53e |
11 changed files with 324 additions and 201 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
/target
|
/target
|
||||||
|
*.log
|
||||||
*.webp
|
*.webp
|
||||||
*.webm
|
*.webm
|
||||||
*.ogg
|
*.ogg
|
||||||
|
|
102
Cargo.lock
generated
102
Cargo.lock
generated
|
@ -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 = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
|
@ -71,12 +71,6 @@ 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"
|
||||||
|
@ -92,17 +86,11 @@ dependencies = [
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.4.0"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
|
@ -198,9 +186,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.2"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indicatif"
|
name = "indicatif"
|
||||||
|
@ -236,16 +224,6 @@ 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"
|
||||||
|
@ -274,7 +252,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "n-mb"
|
name = "n-mb"
|
||||||
version = "1.0.0"
|
version = "1.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -308,29 +286,6 @@ 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"
|
||||||
|
@ -356,43 +311,28 @@ checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.67"
|
version = "1.0.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.33"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
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"
|
||||||
|
@ -402,22 +342,6 @@ 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"
|
||||||
|
@ -426,9 +350,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.37"
|
version = "2.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -446,10 +370,8 @@ 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",
|
||||||
]
|
]
|
||||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -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.0.0"
|
version = "1.1.3"
|
||||||
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,13 +10,30 @@ 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 = ["full"] }
|
tokio = { version = "1.32.0", features = [
|
||||||
|
"macros",
|
||||||
|
"rt-multi-thread",
|
||||||
|
"process",
|
||||||
|
"io-std",
|
||||||
|
"sync",
|
||||||
|
"time",
|
||||||
|
"io-util",
|
||||||
|
] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name="nmb"
|
name = "nmb"
|
||||||
|
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*
|
||||||
|
|
1
FUNDING.yml
Normal file
1
FUNDING.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ko_fi: djkato
|
17
README.md
17
README.md
|
@ -1,15 +1,26 @@
|
||||||
|
<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!
|
||||||
|
|
||||||
## 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(Windows, Linux, MacOS):
|
## ~~How to install Binary(Windows, Linux):~~
|
||||||
|
|
||||||
|
**Releases are no longer being kept uptodate, please follow [Source installation](#how-to-install-from-sourcewindows-linux-macos)**
|
||||||
|
|
||||||
|
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):
|
||||||
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)
|
||||||
2. get ffmpeg for your platform [here](https://ffmpeg.org/download.html), put into $PATH
|
2. get ffmpeg for your platform [here](https://ffmpeg.org/download.html), put into $PATH
|
||||||
3. run `cargo install n-mb` in your favourite terminal
|
3. run `cargo install n-mb` in your favourite terminal
|
||||||
4. execute anywhere using the `nmb --size/-s <SIZE IN MB> --files/-f=<FILE 1>,<FILE 2> . . .` command!
|
4. execute anywhere using the `nmb --size/-s <SIZE IN MB> --codec/-c <WEBM/HEVC> --files/-f=<FILE 1>,<FILE 2> . . .` command!
|
||||||
|
|
||||||
<sub>Thanks for an amazing read on how to optimize vp9 for file sizes deterenkelt, I recommend this read: https://codeberg.org/deterenkelt/Nadeshiko/wiki/Researches%E2%80%89%E2%80%93%E2%80%89VP9-and-overshooting</sub>
|
<sub>Thanks for an amazing read on how to optimize vp9 for file sizes deterenkelt, I recommend this read: https://codeberg.org/deterenkelt/Nadeshiko/wiki/Researches%E2%80%89%E2%80%93%E2%80%89VP9-and-overshooting</sub>
|
||||||
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
299
src/encoder.rs
299
src/encoder.rs
|
@ -2,9 +2,11 @@ use anyhow::{bail, Context};
|
||||||
use pbr::{Pipe, ProgressBar};
|
use pbr::{Pipe, ProgressBar};
|
||||||
use std::{path::PathBuf, process::Stdio};
|
use std::{path::PathBuf, process::Stdio};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, BufReader, Lines},
|
io::{BufReader, Lines},
|
||||||
process::{Child, ChildStderr, ChildStdout, Command},
|
process::{Child, ChildStdout, Command},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::VideoCodec;
|
||||||
const MAX_OPUS_BITRATE: f32 = 256.; //kbits
|
const MAX_OPUS_BITRATE: f32 = 256.; //kbits
|
||||||
const MIN_OPUS_BITRATE: f32 = 50.; //kbits
|
const MIN_OPUS_BITRATE: f32 = 50.; //kbits
|
||||||
|
|
||||||
|
@ -23,10 +25,21 @@ pub struct FFMPEGCommand {
|
||||||
pub progress_bar: Option<ProgressBar<Pipe>>,
|
pub progress_bar: Option<ProgressBar<Pipe>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MediaData {
|
||||||
|
resolution: Option<(u16, u16)>,
|
||||||
|
duration: f32,
|
||||||
|
old_kbit_rate: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
impl FFMPEGCommand {
|
impl FFMPEGCommand {
|
||||||
pub async fn new(media_type: MediaType, path: &PathBuf, size: u16) -> anyhow::Result<Self> {
|
pub async fn new(
|
||||||
|
media_type: MediaType,
|
||||||
|
path: &PathBuf,
|
||||||
|
size: u16,
|
||||||
|
codec: VideoCodec,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
match media_type {
|
match media_type {
|
||||||
MediaType::Video => Self::create_video(path, size).await,
|
MediaType::Video => Self::create_video(path, size, codec).await,
|
||||||
MediaType::Audio => Self::create_audio(path, size).await,
|
MediaType::Audio => Self::create_audio(path, size).await,
|
||||||
MediaType::Image => Self::create_image(path, size),
|
MediaType::Image => Self::create_image(path, size),
|
||||||
MediaType::AnimatedImage => Self::create_animated_image(path),
|
MediaType::AnimatedImage => Self::create_animated_image(path),
|
||||||
|
@ -35,11 +48,29 @@ impl FFMPEGCommand {
|
||||||
|
|
||||||
async fn create_audio(path: &PathBuf, size: u16) -> anyhow::Result<Self> {
|
async fn create_audio(path: &PathBuf, size: u16) -> anyhow::Result<Self> {
|
||||||
let ffprobe_out = parse_ffprobe(path).await?;
|
let ffprobe_out = parse_ffprobe(path).await?;
|
||||||
let duration = ffprobe_out.0.context("Duration missing")?;
|
let duration = ffprobe_out.duration;
|
||||||
|
let max_kbit_rate = match ffprobe_out.old_kbit_rate {
|
||||||
let bitrate = (size as f32 * 1000. / duration) * 0.95;
|
None => MAX_OPUS_BITRATE,
|
||||||
let bitrate = bitrate.clamp(MIN_OPUS_BITRATE, MAX_OPUS_BITRATE) as u16;
|
Some(r) => {
|
||||||
|
if (r as f32) < MAX_OPUS_BITRATE {
|
||||||
|
r as f32
|
||||||
|
} else {
|
||||||
|
MAX_OPUS_BITRATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bitrate = (size as f32 * 1000. / duration) * 0.85;
|
||||||
|
let bitrate = bitrate.clamp(MIN_OPUS_BITRATE, max_kbit_rate) as u16;
|
||||||
|
/*
|
||||||
|
println!(
|
||||||
|
"{} * {} ~= {} (actually is {})",
|
||||||
|
duration,
|
||||||
|
bitrate,
|
||||||
|
size * 1000,
|
||||||
|
duration * bitrate as f32
|
||||||
|
);
|
||||||
|
*/
|
||||||
let mut new_path = path.clone();
|
let mut new_path = path.clone();
|
||||||
new_path.set_extension("ogg");
|
new_path.set_extension("ogg");
|
||||||
|
|
||||||
|
@ -76,18 +107,18 @@ impl FFMPEGCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_video(path: &PathBuf, size: u16) -> anyhow::Result<Self> {
|
async fn create_video(path: &PathBuf, size: u16, codec: VideoCodec) -> anyhow::Result<Self> {
|
||||||
let ffprobe_out = parse_ffprobe(path).await?;
|
let ffprobe_out = parse_ffprobe(path).await?;
|
||||||
|
|
||||||
let duration = ffprobe_out.0.context("Duration missing")?;
|
let duration = ffprobe_out.duration;
|
||||||
let resolution = ffprobe_out.1.context("Missing resolution")?;
|
let resolution = ffprobe_out.resolution.context("Missing resolution")?;
|
||||||
|
|
||||||
let mut overflown_audio_bitrate = None;
|
let mut overflown_audio_bitrate = None;
|
||||||
let mut audio_bitrate = (size as f32 * 1000. / duration) * 0.95 * 0.1;
|
let mut audio_bitrate = size as f32 * 180. / duration;
|
||||||
let mut video_bitrate = (size as f32 * 1000. / duration) * 0.95 * 0.9;
|
let mut video_bitrate = size as f32 * 780. / duration;
|
||||||
|
|
||||||
if audio_bitrate < MIN_OPUS_BITRATE {
|
if audio_bitrate < MIN_OPUS_BITRATE {
|
||||||
overflown_audio_bitrate = Some(MIN_OPUS_BITRATE - audio_bitrate);
|
overflown_audio_bitrate = Some(audio_bitrate - MIN_OPUS_BITRATE);
|
||||||
audio_bitrate = MIN_OPUS_BITRATE;
|
audio_bitrate = MIN_OPUS_BITRATE;
|
||||||
}
|
}
|
||||||
if audio_bitrate > MAX_OPUS_BITRATE {
|
if audio_bitrate > MAX_OPUS_BITRATE {
|
||||||
|
@ -96,7 +127,16 @@ impl FFMPEGCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(overflow) = overflown_audio_bitrate {
|
if let Some(overflow) = overflown_audio_bitrate {
|
||||||
video_bitrate = video_bitrate + overflow;
|
/*
|
||||||
|
println!(
|
||||||
|
"-b:v:{}\n-b:a:{} (ovw: {})\nsum:{}/{}",
|
||||||
|
video_bitrate,
|
||||||
|
audio_bitrate,
|
||||||
|
overflow,
|
||||||
|
video_bitrate + audio_bitrate,
|
||||||
|
size
|
||||||
|
);*/
|
||||||
|
video_bitrate += overflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut height = resolution.1;
|
let mut height = resolution.1;
|
||||||
|
@ -112,25 +152,56 @@ impl FFMPEGCommand {
|
||||||
|
|
||||||
let old_path_str = path.as_os_str().to_str().context("missing or bad path")?;
|
let old_path_str = path.as_os_str().to_str().context("missing or bad path")?;
|
||||||
let mut new_path = path.clone();
|
let mut new_path = path.clone();
|
||||||
new_path.set_extension("webm");
|
|
||||||
|
|
||||||
let scale_arg = format!("scale=-1:{height}");
|
let scale_arg = format!("scale=-1:{height}");
|
||||||
let bitrate_arg = format!("{video_bitrate}k");
|
let bitrate_arg = format!("{}k", video_bitrate as u16);
|
||||||
let minrate_arg = format!("{}k", (video_bitrate * 0.5) as u16);
|
let minrate_arg = format!("{}k", (video_bitrate * 0.5) as u16);
|
||||||
let maxrate_arg = format!("{}k", (video_bitrate * 1.45) as u16);
|
let maxrate_arg = format!("{}k", (video_bitrate * 1.45) as u16);
|
||||||
let ba_arg = format!("{audio_bitrate}k");
|
let ba_arg = format!("{}k", audio_bitrate as u16);
|
||||||
|
let mut passlogfile = path.clone();
|
||||||
|
passlogfile.set_extension("");
|
||||||
let mut command = Command::new("ffmpeg");
|
let mut command = Command::new("ffmpeg");
|
||||||
let mut command2 = Command::new("ffmpeg");
|
let mut command2 = Command::new("ffmpeg");
|
||||||
command.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
|
command.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
|
||||||
command2.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
|
command2.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
|
||||||
|
let video_codec;
|
||||||
|
let audio_codec;
|
||||||
|
match codec {
|
||||||
|
VideoCodec::WEBM => {
|
||||||
|
video_codec = "libvpx-vp9";
|
||||||
|
audio_codec = "libopus";
|
||||||
|
new_path.set_extension("webm");
|
||||||
|
new_path.set_file_name(
|
||||||
|
"minified_".to_owned() + new_path.file_name().unwrap().to_str().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VideoCodec::HEVC => {
|
||||||
|
video_codec = "libx265";
|
||||||
|
audio_codec = "aac";
|
||||||
|
new_path.set_extension("mp4");
|
||||||
|
new_path.set_file_name(
|
||||||
|
"minified_".to_owned() + new_path.file_name().unwrap().to_str().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
println!(
|
||||||
|
"{} * ({}+{}) ~= {} (actually is {})",
|
||||||
|
duration,
|
||||||
|
video_bitrate,
|
||||||
|
audio_bitrate,
|
||||||
|
size,
|
||||||
|
(duration * ((video_bitrate + audio_bitrate) / 1000.)) as f32
|
||||||
|
);
|
||||||
|
*/
|
||||||
let pass = [
|
let pass = [
|
||||||
"-y",
|
"-y",
|
||||||
"-i",
|
"-i",
|
||||||
old_path_str,
|
old_path_str,
|
||||||
"-vcodec",
|
"-vcodec",
|
||||||
"libvpx-vp9",
|
video_codec,
|
||||||
"-acodec",
|
"-acodec",
|
||||||
"libopus",
|
audio_codec,
|
||||||
"-vf",
|
"-vf",
|
||||||
&scale_arg,
|
&scale_arg,
|
||||||
"-deadline",
|
"-deadline",
|
||||||
|
@ -163,12 +234,25 @@ impl FFMPEGCommand {
|
||||||
"6",
|
"6",
|
||||||
"-qmax",
|
"-qmax",
|
||||||
"60",
|
"60",
|
||||||
|
"-qmin",
|
||||||
|
"1",
|
||||||
"-g",
|
"-g",
|
||||||
"240",
|
"240",
|
||||||
|
"-passlogfile",
|
||||||
|
passlogfile
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.context("missing or bad path")?,
|
||||||
];
|
];
|
||||||
|
|
||||||
command.args(pass);
|
command.args(pass);
|
||||||
command.args(["-pass", "1", "-f", "webm"]);
|
|
||||||
|
command.args([
|
||||||
|
"-pass",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
new_path.extension().unwrap().to_str().unwrap(),
|
||||||
|
]);
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
command.arg("NUL");
|
command.arg("NUL");
|
||||||
} else {
|
} else {
|
||||||
|
@ -183,7 +267,10 @@ 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,
|
||||||
|
@ -235,7 +322,7 @@ impl FFMPEGCommand {
|
||||||
progress_bar: None,
|
progress_bar: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn create_animated_image(path: &PathBuf) -> anyhow::Result<Self> {
|
fn create_animated_image(_path: &PathBuf) -> anyhow::Result<Self> {
|
||||||
bail!("")
|
bail!("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,8 +342,20 @@ pub enum EncodingStatus {
|
||||||
NotStarted,
|
NotStarted,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<(Option<f32>, Option<(u16, u16)>)> {
|
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()
|
||||||
|
@ -265,72 +364,96 @@ async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<(Option<f32>, Option<(u
|
||||||
.status
|
.status
|
||||||
.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 ffprobe_output = std::str::from_utf8(&ffprobe.stderr)?;
|
|
||||||
let mut duration = None;
|
let text = std::str::from_utf8(&ffprobe.stdout)?;
|
||||||
let mut resolution = None;
|
|
||||||
let text = ffprobe_output;
|
let mem = text.split(',').collect::<Vec<_>>();
|
||||||
if text.contains("Duration") {
|
|
||||||
duration = Some(parse_duration(text)?);
|
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 = 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
|
||||||
}
|
}
|
||||||
if text.contains("Stream") {
|
|
||||||
resolution = Some(parse_resolution(text)?);
|
|
||||||
}
|
}
|
||||||
return Ok((duration, resolution));
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Ok(MediaData {
|
||||||
|
duration,
|
||||||
|
resolution,
|
||||||
|
old_kbit_rate,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_duration(text: &str) -> anyhow::Result<f32> {
|
async fn get_attribute_from_meta(attr: &str, path: &PathBuf) -> Option<String> {
|
||||||
let text = text[text.find("Duration").unwrap()..].to_owned();
|
let ffprobe = Command::new("ffprobe")
|
||||||
let dur_text = text[text
|
.args([
|
||||||
.find(":")
|
"-v",
|
||||||
.context("something wrong with the ffprobe output")?
|
"error",
|
||||||
+ 2
|
"-select_streams",
|
||||||
..text
|
"v:0",
|
||||||
.find(",")
|
"-show_entries",
|
||||||
.context("something wrong with the ffprobe output")?]
|
&format!("stream={attr}:stream_args={attr}"),
|
||||||
.to_owned();
|
"-of",
|
||||||
let durs_text: Vec<&str> = dur_text.split(":").collect();
|
"csv=s=,:p=0",
|
||||||
let mut durs_text_iter = durs_text.into_iter();
|
])
|
||||||
let h = durs_text_iter
|
.arg(path)
|
||||||
.next()
|
.stderr(Stdio::piped())
|
||||||
.context("something wrong with the ffprobe output")?
|
.output()
|
||||||
.parse::<f32>()?;
|
.await
|
||||||
let m = durs_text_iter
|
.ok()?;
|
||||||
.next()
|
ffprobe.status.exit_ok().ok()?;
|
||||||
.context("something wrong with the ffprobe output")?
|
|
||||||
.parse::<f32>()?;
|
std::str::from_utf8(&ffprobe.stdout)
|
||||||
let s = durs_text_iter
|
.ok()
|
||||||
.next()
|
.map(|v| v.to_string())
|
||||||
.context("something wrong with the ffprobe output")?
|
|
||||||
.parse::<f32>()?;
|
|
||||||
Ok(h * 60. * 60. + m * 60. + s)
|
|
||||||
}
|
|
||||||
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>()?;
|
|
||||||
|
|
||||||
return Ok((width, height));
|
|
||||||
}
|
}
|
||||||
|
|
74
src/main.rs
74
src/main.rs
|
@ -14,6 +14,31 @@ use crate::encoder::EncodingStatus;
|
||||||
mod encoder;
|
mod encoder;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum VideoCodec {
|
||||||
|
WEBM,
|
||||||
|
HEVC,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VideoCodec {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::WEBM => write!(f, "WEBM"),
|
||||||
|
Self::HEVC => write!(f, "HEVC"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoCodec {
|
||||||
|
pub fn from_string(string: &str) -> Option<Self> {
|
||||||
|
match string.to_lowercase().as_str() {
|
||||||
|
"webm" => Some(Self::WEBM),
|
||||||
|
"hevc" => Some(Self::HEVC),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let args = command!()
|
let args = command!()
|
||||||
|
@ -24,6 +49,11 @@ async fn main() -> anyhow::Result<()> {
|
||||||
.default_value("25")
|
.default_value("25")
|
||||||
.value_parser(value_parser!(u16))
|
.value_parser(value_parser!(u16))
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
arg!(-c --codec <CODEC> "Choose video codec between `HEVC` (H.265) and `WEBM` (vp9).")
|
||||||
|
.required(false)
|
||||||
|
.default_value("WEBM")
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
arg!(-f --files <FILES> "Comma separated files to convert. EG: -f=<FILE>,<FILE>")
|
arg!(-f --files <FILES> "Comma separated files to convert. EG: -f=<FILE>,<FILE>")
|
||||||
.required(true)
|
.required(true)
|
||||||
|
@ -33,12 +63,17 @@ async fn main() -> anyhow::Result<()> {
|
||||||
).get_matches();
|
).get_matches();
|
||||||
let size = args
|
let size = args
|
||||||
.get_one::<u16>("size")
|
.get_one::<u16>("size")
|
||||||
.expect("Default value dissapeared from rate");
|
.expect("Default value dissapeared from rate")
|
||||||
|
* 8;
|
||||||
let files = args
|
let files = args
|
||||||
.get_many::<PathBuf>("files")
|
.get_many::<PathBuf>("files")
|
||||||
.context("No files specified")?
|
.context("No files specified")?
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let binding = "webm".to_owned();
|
||||||
|
let codec = args.get_one::<String>("codec").unwrap_or(&binding);
|
||||||
|
let codec = VideoCodec::from_string(codec).unwrap_or(VideoCodec::WEBM);
|
||||||
|
|
||||||
let commands: Arc<Mutex<Vec<FFMPEGCommand>>> = Arc::new(Mutex::new(vec![]));
|
let commands: Arc<Mutex<Vec<FFMPEGCommand>>> = Arc::new(Mutex::new(vec![]));
|
||||||
{
|
{
|
||||||
let mut commands_mut = commands.try_lock().unwrap();
|
let mut commands_mut = commands.try_lock().unwrap();
|
||||||
|
@ -55,32 +90,33 @@ async fn main() -> anyhow::Result<()> {
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"webm" | "mp4" | "mov" | "avi" | "mpeg" | "mkv" => {
|
"webm" | "mp4" | "mov" | "avi" | "mpeg" | "mkv" => {
|
||||||
command = FFMPEGCommand::new(MediaType::Video, file, size.clone()).await?;
|
command =
|
||||||
|
FFMPEGCommand::new(MediaType::Video, file, size, codec.clone()).await?;
|
||||||
}
|
}
|
||||||
"mp3" | "wav" | "ogg" | "opus" | "flac" | "aiff" => {
|
"mp3" | "wav" | "ogg" | "opus" | "flac" | "aiff" => {
|
||||||
command = FFMPEGCommand::new(MediaType::Audio, file, size.clone()).await?;
|
command =
|
||||||
|
FFMPEGCommand::new(MediaType::Audio, file, size, codec.clone()).await?;
|
||||||
}
|
}
|
||||||
"jpg" | "png" | "webp" | "exr" | "jpeg" | "tiff" | "bpm" | "raw" | "tif" => {
|
"jpg" | "png" | "webp" | "exr" | "jpeg" | "tiff" | "bpm" | "raw" | "tif" => {
|
||||||
command = FFMPEGCommand::new(MediaType::Image, file, size.clone()).await?;
|
command =
|
||||||
|
FFMPEGCommand::new(MediaType::Image, file, size, codec.clone()).await?;
|
||||||
}
|
}
|
||||||
"gif" => {
|
"gif" => {
|
||||||
command =
|
command =
|
||||||
FFMPEGCommand::new(MediaType::AnimatedImage, file, size.clone()).await?;
|
FFMPEGCommand::new(MediaType::AnimatedImage, file, size, codec.clone())
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
//dbg!(&command.command.0);
|
|
||||||
|
|
||||||
command.command.0.stdout(Stdio::piped());
|
command.command.0.stdout(Stdio::piped());
|
||||||
command.command.0.stderr(Stdio::null());
|
command.command.0.stderr(Stdio::piped());
|
||||||
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::null());
|
pass2.stderr(Stdio::piped());
|
||||||
pass2.stdin(Stdio::null());
|
pass2.stdin(Stdio::null());
|
||||||
pass2.kill_on_drop(true);
|
|
||||||
command.command.1 = Some(pass2)
|
command.command.1 = Some(pass2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,15 +150,17 @@ 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(50));
|
let mut intv = interval(Duration::from_millis(10));
|
||||||
|
|
||||||
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(':')
|
||||||
.map(|s| s.to_owned())
|
.map(|s| s.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -132,9 +170,13 @@ 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 {
|
||||||
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 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;
|
||||||
|
@ -144,6 +186,8 @@ 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();
|
||||||
|
|
||||||
|
@ -167,7 +211,9 @@ 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,
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -8,7 +8,7 @@ use tokio::time::interval;
|
||||||
pub async fn display(commands: Arc<Mutex<Vec<FFMPEGCommand>>>) {
|
pub async fn display(commands: Arc<Mutex<Vec<FFMPEGCommand>>>) {
|
||||||
let mb = MultiProgress::new();
|
let mb = MultiProgress::new();
|
||||||
let sty = ProgressStyle::with_template(
|
let sty = ProgressStyle::with_template(
|
||||||
"{spinner:.blue} {msg} [{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} (ms)",
|
"{spinner:.blue} {msg} [{elapsed_precise}/{eta_precise}(eta)] {bar:40.cyan/blue} {pos:>7}/{len:7} (ms)",
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.tick_strings(&[
|
.tick_strings(&[
|
||||||
|
|
BIN
test.avi
Normal file
BIN
test.avi
Normal file
Binary file not shown.
BIN
test.mkv
Normal file
BIN
test.mkv
Normal file
Binary file not shown.
Loading…
Reference in a new issue