This commit is contained in:
Djkáťo 2023-09-24 19:40:00 +02:00
commit 767aabb129
10 changed files with 1297 additions and 0 deletions

4
.gitignore vendored Normal file
View file

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

644
Cargo.lock generated Normal file
View file

@ -0,0 +1,644 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anstream"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
[[package]]
name = "anstyle-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anstyle-wincon"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "console"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.45.0",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "gimli"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "indicatif"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-width",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "memchr"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "n-mb"
version = "1.0.0"
dependencies = [
"anyhow",
"clap",
"indicatif",
"pbr",
"tokio",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"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]]
name = "pbr"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514"
dependencies = [
"crossbeam-channel",
"libc",
"winapi",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "portable-atomic"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b"
[[package]]
name = "proc-macro2"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"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]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"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]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

19
Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "n-mb"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
version = "1.0.0"
edition = "2021"
description = "Simple ffmpeg wrapper to parse files to the most efficient formats within a set size"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/djkato/n-mb"
repository = "https://github.com/djkato/n-mb"
readme = "README.md"
keywords = ["media", "ffmpeg", "cli"]
categories = ["command-line-utilities"]
[dependencies]
anyhow = "1.0.75"
clap = { version = "4.4.4", features = ["cargo"] }
indicatif = "0.17.7"
pbr = "1.1.1"
tokio = { version = "1.32.0", features = ["full"] }

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# 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!
## This program outputs to following formats:
- audio codec: opus .ogg
- video codec: vp9 + opus .webm
- image codec: vp8 .webp (for gifs too)
## How to install(Windows, Linux, MacOS):
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
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!
<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>

336
src/encoder.rs Normal file
View file

@ -0,0 +1,336 @@
use anyhow::{bail, Context};
use pbr::{Pipe, ProgressBar};
use std::{path::PathBuf, process::Stdio};
use tokio::{
io::{AsyncBufReadExt, BufReader, Lines},
process::{Child, ChildStderr, ChildStdout, Command},
};
const MAX_OPUS_BITRATE: f32 = 256.; //kbits
const MIN_OPUS_BITRATE: f32 = 50.; //kbits
pub struct FFMPEGCommand {
pub file_name: String,
pub command: (Command, Option<Command>),
pub target_size: u16,
pub resolution: Option<(u32, u32)>,
pub duration: Option<f32>,
pub media_type: MediaType,
pub exec_handle: Option<Child>,
pub buff_reader: Option<Lines<BufReader<ChildStdout>>>,
pub status: EncodingStatus,
pub passed_pass_1: bool,
pub progressed_time: f32,
pub progress_bar: Option<ProgressBar<Pipe>>,
}
impl FFMPEGCommand {
pub async fn new(media_type: MediaType, path: &PathBuf, size: u16) -> anyhow::Result<Self> {
match media_type {
MediaType::Video => Self::create_video(path, size).await,
MediaType::Audio => Self::create_audio(path, size).await,
MediaType::Image => Self::create_image(path, size),
MediaType::AnimatedImage => Self::create_animated_image(path),
}
}
async fn create_audio(path: &PathBuf, size: u16) -> anyhow::Result<Self> {
let ffprobe_out = parse_ffprobe(path).await?;
let duration = ffprobe_out.0.context("Duration missing")?;
let bitrate = (size as f32 * 1000. / duration) * 0.95;
let bitrate = bitrate.clamp(MIN_OPUS_BITRATE, MAX_OPUS_BITRATE) as u16;
let mut new_path = path.clone();
new_path.set_extension("ogg");
let mut command = Command::new("ffmpeg");
command.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
command.args([
"-y",
"-i",
path.as_os_str()
.to_str()
.expect("Path dissapeared on unwrap"),
"-c:a",
"libopus",
"-b:a",
format!("{}k", bitrate).as_str(),
new_path
.as_os_str()
.to_str()
.expect("Path dissapeared on unwrap"),
]);
Ok(FFMPEGCommand {
file_name: path.file_name().unwrap().to_str().unwrap().to_owned(),
resolution: None,
duration: Some(duration),
command: (command, None),
media_type: MediaType::Audio,
target_size: size,
status: EncodingStatus::NotStarted,
exec_handle: None,
buff_reader: None,
progress_bar: None,
passed_pass_1: false,
progressed_time: 0.,
})
}
async fn create_video(path: &PathBuf, size: u16) -> anyhow::Result<Self> {
let ffprobe_out = parse_ffprobe(path).await?;
let duration = ffprobe_out.0.context("Duration missing")?;
let resolution = ffprobe_out.1.context("Missing resolution")?;
let mut overflown_audio_bitrate = None;
let mut audio_bitrate = (size as f32 * 1000. / duration) * 0.95 * 0.1;
let mut video_bitrate = (size as f32 * 1000. / duration) * 0.95 * 0.9;
if audio_bitrate < MIN_OPUS_BITRATE {
overflown_audio_bitrate = Some(MIN_OPUS_BITRATE - audio_bitrate);
audio_bitrate = MIN_OPUS_BITRATE;
}
if audio_bitrate > MAX_OPUS_BITRATE {
overflown_audio_bitrate = Some(audio_bitrate - MAX_OPUS_BITRATE);
audio_bitrate = MAX_OPUS_BITRATE;
}
if let Some(overflow) = overflown_audio_bitrate {
video_bitrate = video_bitrate + overflow;
}
let mut height = resolution.1;
if resolution.1 >= 1080 && duration > 150. {
height = 1080
}
if resolution.1 >= 720 && duration > 600. {
height = 720
}
if resolution.1 >= 480 && duration > 900. {
height = 480
}
let old_path_str = path.as_os_str().to_str().context("missing or bad path")?;
let mut new_path = path.clone();
new_path.set_extension("webm");
let scale_arg = format!("scale=-1:{height}");
let bitrate_arg = format!("{video_bitrate}k");
let minrate_arg = format!("{}k", (video_bitrate * 0.5) as u16);
let maxrate_arg = format!("{}k", (video_bitrate * 1.45) as u16);
let ba_arg = format!("{audio_bitrate}k");
let mut command = Command::new("ffmpeg");
let mut command2 = Command::new("ffmpeg");
command.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
command2.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
let pass = [
"-y",
"-i",
old_path_str,
"-vcodec",
"libvpx-vp9",
"-acodec",
"libopus",
"-vf",
&scale_arg,
"-deadline",
"good",
"-quality",
"good",
"-cpu-used",
"0",
"-undershoot-pct",
"0",
"-overshoot-pct",
"0",
"-b:v",
&bitrate_arg,
"-minrate",
&minrate_arg,
"-maxrate",
&maxrate_arg,
"-b:a",
&ba_arg,
"-row-mt",
"1",
"-tile-rows",
"2",
"-tile-columns",
"4",
"-threads",
"16",
"-auto-alt-ref",
"6",
"-qmax",
"60",
"-g",
"240",
];
command.args(pass);
command.args(["-pass", "1", "-f", "webm"]);
if cfg!(windows) {
command.arg("NUL");
} else {
command.arg("/dev/null");
}
command2.args(pass);
command2.args([
"-pass",
"2",
new_path
.as_os_str()
.to_str()
.context("missing or bad path")?,
]);
Ok(FFMPEGCommand {
file_name: path.file_name().unwrap().to_str().unwrap().to_owned(),
resolution: None,
duration: Some(duration),
command: (command, Some(command2)),
media_type: MediaType::Video,
target_size: size,
buff_reader: None,
exec_handle: None,
status: EncodingStatus::InProgress,
passed_pass_1: false,
progressed_time: 0.,
progress_bar: None,
})
}
fn create_image(path: &PathBuf, size: u16) -> anyhow::Result<Self> {
let mut new_path = path.clone();
new_path.set_extension("webp");
let mut command = Command::new("ffmpeg");
command.args(["-progress", "-", "-nostats", "-stats_period", "50ms"]);
command.args([
"-y",
"-i",
path.as_os_str()
.to_str()
.expect("Path dissapeared on unwrap"),
"-qscale",
"90",
"-compression_level",
"6",
new_path
.as_os_str()
.to_str()
.expect("Path dissapeared on unwrap"),
]);
Ok(FFMPEGCommand {
file_name: path.file_name().unwrap().to_str().unwrap().to_owned(),
resolution: None,
duration: None,
command: (command, None),
media_type: MediaType::Image,
target_size: size,
status: EncodingStatus::InProgress,
exec_handle: None,
buff_reader: None,
passed_pass_1: false,
progressed_time: 0.,
progress_bar: None,
})
}
fn create_animated_image(path: &PathBuf) -> anyhow::Result<Self> {
bail!("")
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum MediaType {
Video,
Audio,
Image,
AnimatedImage,
}
#[derive(PartialEq, Eq, Debug)]
pub enum EncodingStatus {
Finished,
Failed,
InProgress,
NotStarted,
}
async fn parse_ffprobe(path: &PathBuf) -> anyhow::Result<(Option<f32>, Option<(u16, u16)>)> {
let ffprobe = Command::new("ffprobe")
.arg(path)
.stderr(Stdio::piped())
.output()
.await?;
ffprobe
.status
.exit_ok()
.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 mut resolution = None;
let text = ffprobe_output;
if text.contains("Duration") {
duration = Some(parse_duration(text)?);
}
if text.contains("Stream") {
resolution = Some(parse_resolution(text)?);
}
return Ok((duration, resolution));
}
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)
}
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));
}

184
src/main.rs Normal file
View file

@ -0,0 +1,184 @@
#![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(())
}

95
src/ui.rs Normal file
View file

@ -0,0 +1,95 @@
use crate::encoder::{EncodingStatus, FFMPEGCommand, MediaType};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::time::interval;
pub async fn display(commands: Arc<Mutex<Vec<FFMPEGCommand>>>) {
let mb = MultiProgress::new();
let sty = ProgressStyle::with_template(
"{spinner:.blue} {msg} [{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} (ms)",
)
.unwrap()
.tick_strings(&[
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
]);
//.progress_chars("##-");
let mut pbs = vec![];
for (i, command) in commands.lock().await.iter_mut().enumerate() {
if let Some(dur) = command.duration {
let pb = mb.add(ProgressBar::new((dur * 100.) as u64));
pb.set_style(sty.clone());
pb.set_message("Starting : ");
pb.tick();
pbs.push((i, pb));
}
}
let mut spawns = vec![];
for pb in pbs.into_iter() {
let commands_mut = commands.clone();
spawns.push(tokio::spawn(async move {
let pr = pb.1;
let mut intv = interval(Duration::from_millis(50));
loop {
intv.tick().await;
let command = commands_mut.lock().await;
let command = command
.get(pb.0)
.expect("command for progressbar failed to index");
match command.status {
EncodingStatus::NotStarted => pr.set_message("Starting : "),
EncodingStatus::InProgress => match command.media_type {
MediaType::Video => match command.passed_pass_1 {
true => {
pr.set_message(command.file_name.clone() + ": Encoding (Pass 2/2)")
}
false => {
pr.set_message(command.file_name.clone() + ": Encoding (Pass 1/2)")
}
},
_ => pr.set_message(command.file_name.clone() + ": Encoding"),
},
EncodingStatus::Failed => {
pr.set_message(command.file_name.clone() + ": Failed!");
pr.set_position(command.duration.unwrap() as u64);
pr.finish();
break;
}
EncodingStatus::Finished => {
pr.set_message(command.file_name.clone() + ": Finished!");
pr.set_position(command.duration.unwrap() as u64);
pr.finish();
break;
}
};
pr.set_position((command.progressed_time * 100.) as u64);
}
}));
}
for spawn in spawns {
if let Ok(_) = spawn.await {};
}
/*
pb.tick_format("▏▎▍▌▋▊▉██▉▊▋▌▍▎▏");
pb.show_message = true;
pb.message("Waiting : ");
pb.tick();
pb.message("Connected: ");
pb.inc();
pb.message("Cleaning :");
pb.tick();
pb.finish_print(&format!(": Pull complete",));
mb.println("");
mb.println("Text lines separate between two sections: ");
mb.println("");
mb.listen();
println!("\nall bars done!\n");
*/
}

BIN
test.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
test.mp3 Normal file

Binary file not shown.

BIN
test.mp4 Normal file

Binary file not shown.