init
This commit is contained in:
commit
767aabb129
10 changed files with 1297 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/target
|
||||||
|
*.webp
|
||||||
|
*.webm
|
||||||
|
*.ogg
|
644
Cargo.lock
generated
Normal file
644
Cargo.lock
generated
Normal 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
19
Cargo.toml
Normal 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
15
README.md
Normal 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
336
src/encoder.rs
Normal 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
184
src/main.rs
Normal 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
95
src/ui.rs
Normal 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
BIN
test.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 207 KiB |
BIN
test.mp3
Normal file
BIN
test.mp3
Normal file
Binary file not shown.
BIN
test.mp4
Normal file
BIN
test.mp4
Normal file
Binary file not shown.
Loading…
Reference in a new issue