commit 767aabb1297a97199131df06e9ee42a6bf53e989 Author: Djkáťo Date: Sun Sep 24 19:40:00 2023 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d19e35b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +*.webp +*.webm +*.ogg diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..be27aa1 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..87f3280 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "n-mb" +authors = ["Djkáťo "] +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"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..62a9413 --- /dev/null +++ b/README.md @@ -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 --files/-f=, . . .` command! + +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 diff --git a/src/encoder.rs b/src/encoder.rs new file mode 100644 index 0000000..d83b09d --- /dev/null +++ b/src/encoder.rs @@ -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), + pub target_size: u16, + pub resolution: Option<(u32, u32)>, + pub duration: Option, + pub media_type: MediaType, + pub exec_handle: Option, + pub buff_reader: Option>>, + pub status: EncodingStatus, + pub passed_pass_1: bool, + pub progressed_time: f32, + pub progress_bar: Option>, +} + +impl FFMPEGCommand { + pub async fn new(media_type: MediaType, path: &PathBuf, size: u16) -> anyhow::Result { + 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 { + 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 { + 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 { + 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 { + 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, 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 { + 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::()?; + let m = durs_text_iter + .next() + .context("something wrong with the ffprobe output")? + .parse::()?; + let s = durs_text_iter + .next() + .context("something wrong with the ffprobe output")? + .parse::()?; + 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::()?; + + let height = res_text[res_text + .find("x") + .context("something wrong with ffprobe output")? + + 1..] + .to_owned() + .parse::()?; + + return Ok((width, height)); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..703eb93 --- /dev/null +++ b/src/main.rs @@ -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 "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 "Comma separated files to convert. EG: -f=,") + .required(true) + .value_parser(value_parser!(PathBuf)) + .value_delimiter(',') + .num_args(1..=std::usize::MAX) + ).get_matches(); + let size = args + .get_one::("size") + .expect("Default value dissapeared from rate"); + let files = args + .get_many::("files") + .context("No files specified")? + .collect::>(); + + let commands: Arc>> = 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 = 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::() { + 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(()) +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..d2e7c39 --- /dev/null +++ b/src/ui.rs @@ -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>>) { + 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"); + */ +} diff --git a/test.jpg b/test.jpg new file mode 100755 index 0000000..f9fa539 Binary files /dev/null and b/test.jpg differ diff --git a/test.mp3 b/test.mp3 new file mode 100644 index 0000000..9b78585 Binary files /dev/null and b/test.mp3 differ diff --git a/test.mp4 b/test.mp4 new file mode 100644 index 0000000..6b8ca78 Binary files /dev/null and b/test.mp4 differ