diff --git a/.gitignore b/.gitignore index 0b7da79..e6dbc00 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ tmp.flf # ignore tmp files created from unit tests ffmpeg/*log* + +# ignore Visual Studio Code configuration files +.vscode/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 08dd87f..7a7f90e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,16 +1,87 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "benchmark" version = "0.6.1" @@ -33,41 +104,82 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "cc" -version = "1.0.79" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] [[package]] name = "clap" -version = "4.1.6" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "is-terminal", - "once_cell", - "strsim 0.10.0", - "termcolor", + "strsim", ] [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", "syn", @@ -75,12 +187,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" -dependencies = [ - "os_str_bytes", -] +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cli" @@ -99,43 +208,51 @@ dependencies = [ "itertools", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "compound_duration" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c803d816c4ed6d0dadd5b54f7ef4f3761418fe802106b161d77476cc3c664c" +checksum = "391da30e746a798f948778dff8d2e58a47b33bc6e2f37156ef8480d3b49a6565" [[package]] name = "console" -version = "0.15.5" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", - "lazy_static", "libc", + "once_cell", "unicode-width", - "windows-sys 0.42.0", + "windows-sys", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -232,19 +349,19 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.5" +version = "3.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ "nix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "darling" -version = "0.10.2" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -252,23 +369,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.10.2" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.9.3", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -286,20 +403,21 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "engine" version = "0.6.1" dependencies = [ + "chrono", "cli", "compound_duration", "crossbeam-channel", @@ -314,27 +432,6 @@ dependencies = [ name = "environment" version = "0.6.1" -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "ffmpeg" version = "0.6.1" @@ -354,14 +451,14 @@ checksum = "4742a071cd9694fc86f9fa1a08fa3e53d40cc899d7ee532295da2d085639fbc5" [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.45.0", + "libredox", + "windows-sys", ] [[package]] @@ -379,24 +476,39 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ - "libc", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "hermit-abi" -version = "0.3.1" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] [[package]] name = "ident_case" @@ -406,37 +518,22 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ "console", "number_prefix", "portable-atomic", "unicode-width", + "web-time", ] [[package]] -name = "io-lifetimes" -version = "1.0.5" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -448,16 +545,20 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "js-sys" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] [[package]] name = "libc" -version = "0.2.139" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -470,10 +571,21 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" +name = "libredox" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "markdown-rs" @@ -483,29 +595,38 @@ checksum = "e3ea041fc3a6fb9235217bde03eb9d505eba5d79a8dbee5f08cb4c2e59c4ed9c" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "nix" -version = "0.26.2" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.9.1", "cfg-if", + "cfg_aliases", "libc", - "static_assertions", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -521,7 +642,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd21b9f5a1cce3c3515c9ffa85f5c7443e07162dae0ccf4339bb7ca38ad3454" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libloading", "nvml-wrapper-sys", "static_assertions", @@ -540,15 +661,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "os_str_bytes" -version = "6.4.1" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "permutation" @@ -572,66 +693,54 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -640,9 +749,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rev_buf_reader" @@ -654,18 +763,16 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.36.8" +name = "rustversion" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.45.0", -] +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "static_assertions" @@ -681,36 +788,21 @@ checksum = "e6700c1ed393aa9c1fff950032a64a4856436dadee820641ce1b914cab65019f" [[package]] name = "strsim" -version = "0.9.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "1.0.108" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "termios" version = "0.3.3" @@ -722,24 +814,24 @@ dependencies = [ [[package]] name = "text_io" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f0c8eb2ad70c12a6a69508f499b3051c924f4b1cfeae85bfad96e6bc5bba46" +checksum = "4d8d3ca3b06292094e03841d8995e910712d2a10b5869c8f9725385b29761115" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -748,21 +840,89 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "winapi" @@ -781,53 +941,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "winapi-util" -version = "0.1.5" +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-core" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "winapi", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-implement" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-interface" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -836,51 +1032,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wrapcenum-derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bcc065c85ad2c3bd12aa4118bf164835712e25080c392557801a13292c60aec" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" dependencies = [ "darling", "proc-macro2", diff --git a/benchmark/src/main.rs b/benchmark/src/main.rs index b1d4d5a..4ae03cf 100644 --- a/benchmark/src/main.rs +++ b/benchmark/src/main.rs @@ -2,6 +2,7 @@ use std::path::Path; use std::{env, panic}; use clap::Parser; +use ffmpeg::args::FfmpegQuality; use text_io::read; use cli::cli_util::{is_dev, log_cli_header, pause}; @@ -64,7 +65,7 @@ fn benchmark() { let settings = get_benchmark_settings_for(&cli); let bitrate = get_bitrate_for(&permutation.get_metadata(), cli.encoder.clone()); - permutation.bitrate = bitrate; + permutation.ffmpeg_quality = FfmpegQuality::Bitrate(bitrate); permutation.encoder_settings = settings; permutation.verbose = cli.verbose; @@ -252,7 +253,7 @@ fn get_benchmark_settings_for(cli: &BenchmarkCli) -> String { return match vendor { Vendor::Nvidia => { - let nvenc = Nvenc::new(cli.encoder == "hevc_nvenc", cli.gpu, cli.no_b_frame); + let nvenc = Nvenc::new(cli.encoder == "hevc_nvenc", cli.gpu, cli.no_b_frame, false); nvenc.get_benchmark_settings() } diff --git a/codecs/src/codecs.rs b/codecs/src/codecs.rs new file mode 100644 index 0000000..3041897 --- /dev/null +++ b/codecs/src/codecs.rs @@ -0,0 +1,55 @@ + +use crate::amf::Amf; +use crate::apple_silicon::Apple; +use crate::av1_qsv::AV1QSV; +use crate::nvenc::Nvenc; +use crate::qsv::QSV; +use crate::permute::Permute; + +pub enum Codecs { + AMF(Amf), + APPLE(Apple), + AV1QSV(AV1QSV), + NVENC(Nvenc), + QSV(QSV) +} + +impl Iterator for Codecs { + type Item = (usize, String); + + fn next(&mut self) -> Option { + match self { + Codecs::AMF(amf) => amf.next(), + Codecs::APPLE(apple) => apple.next(), + Codecs::AV1QSV(av1_qsv) => av1_qsv.next(), + Codecs::NVENC(nvenc) => nvenc.next(), + Codecs::QSV(qsv) => qsv.next() + } + } +} + +impl Permute for Codecs { + fn init(&mut self) -> &Vec { + match self { + Codecs::AMF(amf) => amf.init(), + Codecs::APPLE(apple) => apple.init(), + Codecs::AV1QSV(av1_qsv) => av1_qsv.init(), + Codecs::NVENC(nvenc) => nvenc.init(), + Codecs::QSV(qsv) => qsv.init() + } + } + + fn run_standard_only(&mut self) -> &Vec { + match self { + Codecs::AMF(amf) => amf.run_standard_only(), + Codecs::APPLE(apple) => apple.run_standard_only(), + Codecs::AV1QSV(av1_qsv) => av1_qsv.run_standard_only(), + Codecs::NVENC(nvenc) => nvenc.run_standard_only(), + Codecs::QSV(qsv) => qsv.run_standard_only() + } + } + + fn get_resolution_to_bitrate_map(_fps: u32) -> std::collections::HashMap { + todo!() + } +} \ No newline at end of file diff --git a/codecs/src/lib.rs b/codecs/src/lib.rs index 99a4833..37e3334 100644 --- a/codecs/src/lib.rs +++ b/codecs/src/lib.rs @@ -8,6 +8,7 @@ pub mod permute; pub mod qsv; mod resolutions; pub mod vendor; +pub mod codecs; pub fn get_vendor_for_codec(codec: &String) -> Vendor { if codec.contains("nvenc") { diff --git a/codecs/src/nvenc.rs b/codecs/src/nvenc.rs index b6572ed..69ed099 100644 --- a/codecs/src/nvenc.rs +++ b/codecs/src/nvenc.rs @@ -16,30 +16,33 @@ pub struct Nvenc { permutations: Vec, index: i32, gpu: u8, + using_bitrate: bool, } impl Nvenc { - pub fn new(is_hevc: bool, gpu: u8, no_b_frames: bool) -> Self { + pub fn new(is_hevc: bool, gpu: u8, no_b_frames: bool, using_bitrate: bool) -> Self { Self { presets: get_nvenc_presets(), tunes: get_nvenc_tunes(), // this is the only difference between hevc & h264 profiles: if is_hevc { vec!["main"] } else { vec!["high"] }, // leaving out vbr rate controls as these are not ideal for game streaming - rate_controls: vec!["cbr"], + rate_controls: if using_bitrate { vec!["cbr"] } else { vec!["vbr"] }, no_b_frames, permutations: Vec::new(), // starts at -1, so that first next() will return the first element index: -1, gpu, + using_bitrate, } } pub fn get_benchmark_settings(&self) -> String { return format!( - "-preset p1 -tune ll -profile:v {} -rc cbr -cbr true -gpu {}", + "-preset p1 -tune ll -profile:v {} {} -gpu {}", self.profiles.get(0).unwrap(), - self.gpu + if self.using_bitrate { "-rc cbr -cbr true" } else { "-rc vbr -cbr false" }, + self.gpu, ); } @@ -64,6 +67,7 @@ struct NvencSettings { rate_control: &'static str, no_b_frame: bool, gpu: u8, + using_bitrate: bool, } impl NvencSettings { @@ -82,8 +86,8 @@ impl NvencSettings { args.push_str(" -b_ref_mode 0"); } - // always set this to constant bit rate to ensure reliable stream - args.push_str(" -cbr true"); + args.push_str(if self.using_bitrate {" -cbr true" } else { " -cbr false" } ); + args.push_str(" -gpu "); args.push_str(self.gpu.to_string().as_str()); @@ -140,6 +144,7 @@ impl Permute for Nvenc { rate_control: unwrapped_perm.get(3).unwrap(), no_b_frame: self.no_b_frames, gpu: self.gpu, + using_bitrate: self.using_bitrate, }; self.permutations.push(settings.to_string()); @@ -186,19 +191,19 @@ mod tests { #[test] fn create_h264_test() { - let nvenc = Nvenc::new(false, 0, false); + let nvenc = Nvenc::new(false, 0, false, false); assert!(nvenc.profiles.contains(&"high")); } #[test] fn create_hevc_test() { - let nvenc = Nvenc::new(true, 0, false); + let nvenc = Nvenc::new(true, 0, false, false); assert!(nvenc.profiles.contains(&"main")); } #[test] fn iterate_to_end_test() { - let mut nvenc = Nvenc::new(false, 0, false); + let mut nvenc = Nvenc::new(false, 0, false, false); let perm_count = nvenc.init().len(); let mut total = 0; @@ -212,20 +217,20 @@ mod tests { #[test] fn total_permutations_test() { - let mut nvenc = Nvenc::new(false, 0, false); + let mut nvenc = Nvenc::new(false, 0, false, false); assert_eq!(nvenc.init().len(), get_expected_len(&nvenc)); } #[test] fn init_twice_not_double_test() { - let mut nvenc = Nvenc::new(false, 0, false); + let mut nvenc = Nvenc::new(false, 0, false, false); nvenc.init(); assert_eq!(nvenc.init().len(), get_expected_len(&nvenc)); } #[test] fn no_b_frame_test() { - let mut nvenc = Nvenc::new(false, 0, true); + let mut nvenc = Nvenc::new(false, 0, true, false); nvenc.init(); assert_eq!(nvenc.no_b_frames, true); } diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 44e7121..f5ee1bd 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -14,3 +14,4 @@ stoppable_thread = "0.2.1" permutation = { path = "../permutation" } ffmpeg = { path = "../ffmpeg" } cli = { path = "../cli" } +chrono = "0.4.41" diff --git a/engine/src/benchmark_engine.rs b/engine/src/benchmark_engine.rs index fcd0111..e9d9c72 100644 --- a/engine/src/benchmark_engine.rs +++ b/engine/src/benchmark_engine.rs @@ -44,7 +44,7 @@ impl BenchmarkEngine { self.results.clone(), &runtime_str, Vec::new(), - self.permutations[0].bitrate, + self.permutations[0].ffmpeg_quality, true, &self.log_files_directory, ); diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 78d4459..cb7e5ca 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -9,8 +9,9 @@ use crossbeam_channel::Receiver; use ctrlc::Error; use cli::cli_util::error_with_ack; -use ffmpeg::args::FfmpegArgs; +use ffmpeg::args::{FfmpegArgs, FfmpegQuality}; use ffmpeg::metadata::MetaData; +use ffmpeg::report_files::{get_latest_ffmpeg_report_file, find_and_extract_frames_and_bytes}; use permutation::permutation::Permutation; use crate::progressbar; @@ -24,7 +25,7 @@ pub fn run_encode( ) -> PermutationResult { let mut result = PermutationResult::new( &p.get_metadata(), - p.bitrate, + p.ffmpeg_quality, &p.encoder_settings, &p.encoder, p.decode_run, @@ -36,10 +37,11 @@ pub fn run_encode( p.video_file, p.encoder, &p.encoder_settings, - p.bitrate, + p.ffmpeg_quality.clone(), p.decode_run, p.ten_bit, ); + ffmpeg_args.report = !p.is_decoding; let encode_start_time = SystemTime::now(); @@ -67,8 +69,35 @@ pub fn run_encode( error_with_ack(true); } - result.was_overloaded = trial_result.was_overloaded; - result.encode_time = encode_start_time.elapsed().unwrap().as_secs(); + + if !p.is_decoding { + result.was_overloaded = trial_result.was_overloaded; + result.encode_time = encode_start_time.elapsed().unwrap().as_secs(); + + + let encode_log_file = get_latest_ffmpeg_report_file(); + let (_, bytes) = find_and_extract_frames_and_bytes().expect("Could not parse frames and bytes from ffmpeg encode log"); + let src_metadata = fs::metadata(ffmpeg_args.first_input.clone()).expect("Could not read metadata from source file"); + + let seconds = 30f32; + let bytes_to_megabytes = 1e6f32; + let bytes_to_bits = 8f32; + + result.average_bitrate = (bytes as f32) / seconds / bytes_to_megabytes * bytes_to_bits; + result.compression_ratio = (src_metadata.len() as f32) / (bytes as f32); + + // Cleanup encode log file, might need to wait for it to be released + for _ in 0..3 { + match fs::remove_file(encode_log_file.as_path()) { + Ok(_) => break, + Err(_) => { + //println!("Waiting for ffmpeg to release encode log file"); + sleep(Duration::from_millis(300)); + } + } + } + } + // calculate the fps statistics and store this in the result calculate_fps_statistics(&mut result, &mut trial_result); @@ -76,7 +105,10 @@ pub fn run_encode( // log the calculated fps statistics; two spaces match the progress bar println!(" Average FPS:\t{:.0}", result.fps_stats.avg); println!(" 1%'ile:\t{}", result.fps_stats.one_perc_low); - println!(" 90%'ile:\t{}\n", result.fps_stats.ninety_perc); + println!(" 90%'ile:\t{}", result.fps_stats.ninety_perc); + if !p.is_decoding { + println!(" Avg. Bitrate:\t{:.2}Mb/s\n", result.average_bitrate); + } // delete the file we created to save on storage space if p.decode_run { @@ -143,7 +175,10 @@ fn log_header( println!("[Resolution:\t{}x{}]", metadata.width, metadata.height); println!("[Encoder:\t{}]", permutation.encoder); println!("[FPS:\t\t{}]", metadata.fps); - println!("[Bitrate:\t{}Mb/s]", permutation.bitrate); + match permutation.ffmpeg_quality { + FfmpegQuality::Bitrate(b) => println!("[Bitrate:\t{}Mb/s]", b), + FfmpegQuality::Quality(q) => println!("[Quality:\t{} CQ]", q), + } println!("[{}]", permutation.encoder_settings); } diff --git a/engine/src/permutation_engine.rs b/engine/src/permutation_engine.rs index 865e57a..c96aeba 100644 --- a/engine/src/permutation_engine.rs +++ b/engine/src/permutation_engine.rs @@ -8,7 +8,7 @@ use crossbeam_channel::Receiver; use ctrlc::Error; use ffmpeg::args::FfmpegArgs; -use ffmpeg::report_files::{extract_vmaf_score, get_latest_ffmpeg_report_file, read_last_line_at}; +use ffmpeg::report_files::{get_latest_ffmpeg_report_file, read_last_line_at, find_and_extract_vmaf_score}; use permutation::permutation::Permutation; use crate::engine::{log_permutation_header, run_encode, spawn_ffmpeg_child}; @@ -93,7 +93,7 @@ impl PermutationEngine { } let is_initial_bitrate_permutation_over = i == self.permutations.len() - 1 - || self.permutations[i + 1].clone().bitrate != permutation.bitrate; + || self.permutations[i + 1].clone().ffmpeg_quality != permutation.ffmpeg_quality; self.add_result( result, is_initial_bitrate_permutation_over, @@ -125,7 +125,7 @@ impl PermutationEngine { self.results.clone(), &runtime_str, self.dup_results.clone(), - self.permutations[0].bitrate, + self.permutations[0].ffmpeg_quality.clone(), false, &self.log_files_directory, ); @@ -175,7 +175,7 @@ fn calc_vmaf_score( p.video_file.clone(), p.encoder.clone(), &p.encoder_settings, - p.bitrate.clone(), + p.ffmpeg_quality.clone(), p.decode_run, p.ten_bit, ); @@ -224,26 +224,16 @@ fn calc_vmaf_score( println!("VMAF calculation finishing up..."); let vmaf_child_status = vmaf_child.wait().expect("Vmaf child could not wait"); let vmaf_log_file = get_latest_ffmpeg_report_file(); - // TODO: this does fix the issue for apple however, this may not scale very well across other vendors - // or the output line number may have changed recently where we'll need to make this not dependent on line numbers at all - let line_number = if encoder_args.encoder.contains("videotoolbox") { - 15 - } else { - 3 - }; - let vmaf_score_line = read_last_line_at(line_number); + //Cleanup process encoder_child .kill() .expect("Could not kill encoder process"); - if vmaf_child_status.success() { - let vmaf_score_extract = extract_vmaf_score(vmaf_score_line.as_str()); - let vmaf_score = vmaf_score_extract.expect(&format!( - "Could not parse score from line: {}", - vmaf_score_line - )); + if vmaf_child_status.success() { + let vmaf_score = find_and_extract_vmaf_score().expect("Could not parse score from ffmpeg vmaf log"); println!("VMAF score: {}\n", vmaf_score); + // Cleanup log file remove_file(vmaf_log_file.as_path()).unwrap(); return Some(vmaf_score); diff --git a/engine/src/result.rs b/engine/src/result.rs index 72e31a0..fb84554 100644 --- a/engine/src/result.rs +++ b/engine/src/result.rs @@ -4,15 +4,18 @@ use std::io::Write; use compound_duration::format_dhms; +use ffmpeg::args::FfmpegQuality; use ffmpeg::metadata::MetaData; use crate::fps_stats::FpsStats; +use chrono::prelude::Utc; + #[derive(Clone)] pub struct PermutationResult { pub encoder: String, pub was_overloaded: bool, - bitrate: u32, + ffmpeg_quality: FfmpegQuality, metadata: MetaData, pub encoder_settings: String, // only if the encodes were successful @@ -21,12 +24,14 @@ pub struct PermutationResult { pub vmaf_score: c_float, pub fps_stats: FpsStats, pub decode_run: bool, + pub compression_ratio: f32, + pub average_bitrate: f32, } impl PermutationResult { pub fn new( metadata: &MetaData, - bitrate: u32, + ffmpeg_quality: FfmpegQuality, encoder_settings: &String, encoder: &str, decode: bool, @@ -34,7 +39,7 @@ impl PermutationResult { Self { encoder: String::from(encoder), was_overloaded: false, - bitrate, + ffmpeg_quality, metadata: metadata.clone(), encoder_settings: encoder_settings.to_string(), encode_time: 0, @@ -42,6 +47,8 @@ impl PermutationResult { vmaf_score: 0.0, fps_stats: FpsStats::default(), decode_run: decode, + compression_ratio: 0.0f32, + average_bitrate: 0.0f32, } } @@ -49,16 +56,28 @@ impl PermutationResult { let mut default = String::new(); let overloaded_indicator = if self.was_overloaded { "[O]" } else { " " }; - default.push_str( - format!( - "{}{}x{}\t{}\t{}Mb/s", - overloaded_indicator, - self.metadata.width, - self.metadata.height, - self.metadata.fps, - self.bitrate - ) - .as_str(), + default.push_str(match self.ffmpeg_quality { + FfmpegQuality::Bitrate(b) => format!( + "{}{}x{}\t{}\t{}Mb/s\t\t{:.2}x\t\t{:.2}Mb/s", + overloaded_indicator, + self.metadata.width, + self.metadata.height, + self.metadata.fps, + b.to_string(), + self.compression_ratio, + self.average_bitrate + ), + FfmpegQuality::Quality(q) => format!( + "{}{}x{}\t{}\t{} CQ\t\t{:.2}x\t\t{:.2}Mb/s", + overloaded_indicator, + self.metadata.width, + self.metadata.height, + self.metadata.fps, + q.to_string(), + self.compression_ratio, + self.average_bitrate + ), + }.as_str() ); // adjust tabs based on expected vmaf score, or lack of one @@ -79,7 +98,7 @@ impl PermutationResult { default.push_str( format!( - "\t\t{}\t\t{}\t\t{}{:.0}\t\t{}\t\t{}\t\t{}", + "\t{}\t\t{}\t\t{}{:.0}\t\t{}\t\t{}\t\t{}", format_dhms(self.encode_time), format_dhms(self.vmaf_calculation_time), vmaf_score_str, @@ -99,7 +118,7 @@ pub fn log_results_to_file( results: Vec, runtime_str: &String, dup_results: Vec, - bitrate: u32, + ffmpeg_quality: FfmpegQuality, is_benchmark: bool, log_directory: &String, ) { @@ -107,10 +126,11 @@ pub fn log_results_to_file( let first_metadata = results.get(0).unwrap().metadata; let encoder = results.get(0).unwrap().encoder.as_str(); let permute_file_name = format!( - "{}-{}-{}.log", + "{}-{}-{} {}.log", encoder, first_metadata.get_res(), - first_metadata.fps + first_metadata.fps, + Utc::now().to_string().chars().take_while(|&ch| ch != '.').collect::().replace(":", "-").replace(" ", "_") // datetime formatting ) .to_string(); let benchmark_file_name = format!("{}-benchmark.log", encoder).to_string(); @@ -128,25 +148,28 @@ pub fn log_results_to_file( let mut w = File::create(file_name).unwrap(); writeln!(&mut w, "Results from entire permutation:").unwrap(); - writeln!(&mut w, "==================================================================================================================================================================").unwrap(); + writeln!(&mut w, "========================================================================================================================================================================================================================================").unwrap(); let mut time = "[Encode Time]"; if results.len() > 1 && results[1].decode_run { time = "[Encode/Decode Time]" } - writeln!(&mut w, " [Resolution]\t[FPS]\t[Bitrate]\t{}\t[VMAF Time]\t[VMAF Score]\t[Average FPS]\t[1%'ile]\t[90%'ile]\t[Encoder Settings]", time).unwrap(); - let mut current_bitrate = 0; + match ffmpeg_quality { + FfmpegQuality::Bitrate(_) => writeln!(&mut w, " [Resolution]\t[FPS]\t[Bitrate]\t[Comp. Ratio]\t[Avg. Bitrate]\t{}\t[VMAF Time]\t[VMAF Score]\t[Average FPS]\t[1%'ile]\t[90%'ile]\t[Encoder Settings]", time).unwrap(), + FfmpegQuality::Quality(_) => writeln!(&mut w, " [Resolution]\t[FPS]\t[Quality]\t[Comp. Ratio]\t[Avg. Bitrate]\t{}\t[VMAF Time]\t[VMAF Score]\t[Average FPS]\t[1%'ile]\t[90%'ile]\t[Encoder Settings]", time).unwrap(), + } + let mut current_quality = FfmpegQuality::Bitrate(0); for result in &results { // print a line split between bitrate permutations for improved readability - if !is_benchmark && current_bitrate != result.bitrate { - writeln!(&mut w, "##################################################################################################################################################################").unwrap(); - current_bitrate = result.bitrate; + if !is_benchmark && current_quality != result.ffmpeg_quality { + writeln!(&mut w, "########################################################################################################################################################################################################################################").unwrap(); + current_quality = result.ffmpeg_quality.clone(); } writeln!(&mut w, "{}", result.to_string()).unwrap(); } - writeln!(&mut w, "==================================================================================================================================================================").unwrap(); + writeln!(&mut w, "========================================================================================================================================================================================================================================").unwrap(); writeln!(&mut w, "Benchmark runtime: {}\n", runtime_str).unwrap(); let mut has_logged_dup_header = false; @@ -154,7 +177,7 @@ pub fn log_results_to_file( // log out the duplicated results so we can keep track of them let initial_perms: Vec = results .into_iter() - .filter(|res| res.bitrate == bitrate) + .filter(|res| res.ffmpeg_quality == ffmpeg_quality) .collect(); // for each of these, collect the duplicates with the same score @@ -172,7 +195,7 @@ pub fn log_results_to_file( if !has_logged_dup_header { writeln!(&mut w, "Encoder settings that produced identical scores:").unwrap(); - writeln!(&mut w, "==================================================================================================================================================================").unwrap(); + writeln!(&mut w, "========================================================================================================================================================================================================================================").unwrap(); has_logged_dup_header = true; } @@ -186,5 +209,5 @@ pub fn log_results_to_file( writeln!(&mut w, "\n").unwrap(); } - writeln!(&mut w, "==================================================================================================================================================================").unwrap(); + writeln!(&mut w, "========================================================================================================================================================================================================================================").unwrap(); } diff --git a/ffmpeg/src/args.rs b/ffmpeg/src/args.rs index 47f42db..324df59 100644 --- a/ffmpeg/src/args.rs +++ b/ffmpeg/src/args.rs @@ -3,17 +3,34 @@ use std::ffi::c_float; use codecs::get_vendor_for_codec; use codecs::vendor::Vendor; +use std::string::ToString; + pub static TCP_LISTEN: &str = "tcp://localhost:2000?listen&listen_timeout=3000&timeout=1000000"; pub static NO_OUTPUT: &str = "-f null -"; +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FfmpegQuality { + Bitrate(u32), + Quality(u32) +} + +impl ToString for FfmpegQuality { + fn to_string(&self) -> String { + match self { + FfmpegQuality::Bitrate(b) => b.to_string(), + FfmpegQuality::Quality(q) => q.to_string() + } + } +} + #[derive(Clone)] pub struct FfmpegArgs { fps_limit: u32, - report: bool, + pub report: bool, send_progress: bool, pub first_input: String, second_input: String, - pub bitrate: u32, + pub ffmpeg_quality: FfmpegQuality, pub encoder: String, pub encoder_args: String, pub output_args: String, @@ -31,7 +48,7 @@ impl Default for FfmpegArgs { send_progress: true, first_input: String::new(), second_input: String::new(), - bitrate: u32::default(), + ffmpeg_quality: FfmpegQuality::Bitrate(0), encoder: String::new(), encoder_args: String::new(), output_args: NO_OUTPUT.to_string(), @@ -50,13 +67,13 @@ impl FfmpegArgs { first_input: String, encoder: String, encoder_args: &String, - current_bitrate: u32, + current_quality: FfmpegQuality, decode: bool, ten_bit: bool, ) -> FfmpegArgs { let ffmpeg_args = FfmpegArgs { first_input, - bitrate: current_bitrate, + ffmpeg_quality: current_quality, encoder, encoder_args: encoder_args.to_string(), decode, @@ -141,7 +158,7 @@ impl FfmpegArgs { } else { append_encode_only_args( &mut output, - self.bitrate, + self.ffmpeg_quality.clone(), &self.encoder, &self.encoder_args, ); @@ -192,13 +209,41 @@ impl FfmpegArgs { fn append_encode_only_args( arg_str: &mut String, - bitrate: u32, + ffmpeg_quality: FfmpegQuality, encoder: &String, encoder_args: &String, ) { - arg_str.push_str([" -b:v", bitrate.to_string().as_str()].join(" ").as_str()); - // adding the rate amount to the end of the bitrate - arg_str.push('M'); + match ffmpeg_quality { + FfmpegQuality::Bitrate(b) => { + arg_str.push_str([" -b:v", b.to_string().as_str()].join(" ").as_str()); + // adding the rate amount to the end of the bitrate + arg_str.push('M'); + } + FfmpegQuality::Quality(q) => { + + // ugly matching encoder but it should work + match encoder.as_str() { + "h264_nvenc" => arg_str.push_str([" -cq", q.to_string().as_str()].join(" ").as_str()), + "hevc_nvenc" => arg_str.push_str([" -cq", q.to_string().as_str()].join(" ").as_str()), + "h264_amf" => { + let q_str = q.to_string(); + arg_str.push_str([" -qp_i", q_str.as_str(), "-qp_p", q_str.as_str()].join(" ").as_str()); + }, + "hevc_amf" => { + let q_str = q.to_string(); + arg_str.push_str([" -qp_i", q_str.as_str(), "-qp_p", q_str.as_str(), "-qp_b", q_str.as_str()].join(" ").as_str()); + }, + "h264_qsv" | "hevc_qsv" => { + let q_str = q.to_string(); + arg_str.push_str([" -min_qp_i", q_str.as_str(), "-min_qp_p", q_str.as_str(), "-min_qp_b", q_str.as_str()].join(" ").as_str()); + arg_str.push_str([" -max_qp_i", q_str.as_str(), "-max_qp_p", q_str.as_str(), "-max_qp_b", q_str.as_str()].join(" ").as_str()); + }, + "av1_qsv" => panic!("av1_qsv does not support quality parameters"), + err => panic!("{} unknown codec", err), + } + + } + } arg_str.push_str([" -c:v", encoder.as_str()].join(" ").as_str()); arg_str.push(' '); arg_str.push_str(encoder_args.as_str()); @@ -217,7 +262,7 @@ fn append_vmaf_only_args(arg_str: &mut String) { // TODO: get rid of this later pub struct Cli { pub encoder: String, - pub bitrate: u32, + pub quality: FfmpegQuality, pub check_quality: bool, pub detect_overload: bool, pub source_file: String, @@ -230,11 +275,11 @@ pub struct Cli { #[cfg(test)] mod tests { - use crate::args::{Cli, FfmpegArgs, NO_OUTPUT, TCP_LISTEN}; + use crate::args::{Cli, FfmpegArgs, FfmpegQuality, NO_OUTPUT, TCP_LISTEN}; static INPUT_ONE: &str = "1080-60.y4m"; static INPUT_TWO: &str = "1080-60-2.y4m"; - static BITRATE: u32 = 6; + static QUALITY: FfmpegQuality = FfmpegQuality::Bitrate(6); static FPS_LIMIT: u32 = 60; static ENCODER: &str = "h264_nvenc"; static ENCODER_ARGS: &str = @@ -248,7 +293,7 @@ mod tests { assert_eq!(args.fps_limit, 0); assert_eq!(args.send_progress, true); assert_eq!(args.report, false); - assert_eq!(args.bitrate, u32::default()); + assert_eq!(args.ffmpeg_quality, FfmpegQuality::Bitrate(0)); assert_eq!(args.output_args, "-f null -"); assert_eq!(args.is_vmaf, false); assert_eq!(args.stats_period, 0.5); @@ -266,7 +311,7 @@ mod tests { assert_eq!(ffmpeg_args.first_input, INPUT_ONE); assert_eq!(ffmpeg_args.second_input, INPUT_TWO); - assert_eq!(ffmpeg_args.bitrate, BITRATE); + assert_eq!(ffmpeg_args.ffmpeg_quality, QUALITY); assert_eq!(ffmpeg_args.encoder, ENCODER); assert_eq!(ffmpeg_args.encoder_args, ENCODER_ARGS); } @@ -277,7 +322,7 @@ mod tests { assert_eq!(ffmpeg_args.first_input, INPUT_ONE); assert_eq!(ffmpeg_args.second_input, ""); - assert_eq!(ffmpeg_args.bitrate, BITRATE); + assert_eq!(ffmpeg_args.ffmpeg_quality, QUALITY); assert_eq!(ffmpeg_args.encoder, ENCODER); assert_eq!(ffmpeg_args.encoder_args, ENCODER_ARGS); } @@ -321,7 +366,7 @@ mod tests { fn get_one_input_args() -> FfmpegArgs { let args = Cli { encoder: ENCODER.to_string(), - bitrate: BITRATE, + quality: QUALITY.clone(), check_quality: false, detect_overload: false, source_file: INPUT_ONE.to_string(), @@ -336,7 +381,7 @@ mod tests { args.source_file, args.encoder, &ENCODER_ARGS.to_string(), - args.bitrate, + args.quality, false, false, ); diff --git a/ffmpeg/src/report_files.rs b/ffmpeg/src/report_files.rs index 7084e05..9d569aa 100644 --- a/ffmpeg/src/report_files.rs +++ b/ffmpeg/src/report_files.rs @@ -19,6 +19,23 @@ pub fn extract_vmaf_score(line: &str) -> Result { return capture_group(line, r"VMAF score: (\d+\.\d+)").parse::(); } +pub fn extract_frames_and_bytes(line: &str) -> Option<(u32, u32)> { + // frames and bytes will be on the same line + // if frames are found, find bytes, otherwise return error + + let frames = capture_group(line, r"(\d+) frames encoded").parse::(); + if frames.is_err() { + return Option::None; + } + + let bytes = capture_group(line, r"(\d+) bytes").parse::(); + if bytes.is_err() { + return Option::None; + } + + return Option::Some((frames.unwrap(), bytes.unwrap())); +} + pub fn read_last_line_at(line_number: i32) -> String { let log_file = File::open(get_latest_ffmpeg_report_file()).unwrap(); let reader = RevBufReader::new(log_file); @@ -32,6 +49,45 @@ pub fn read_last_line_at(line_number: i32) -> String { return lines.next().unwrap().unwrap(); } +pub fn find_and_extract_vmaf_score() -> Result { + let log_file = File::open(get_latest_ffmpeg_report_file()).unwrap(); + let reader = RevBufReader::new(log_file); + let lines = reader.lines(); + + // read from bottom until VMAF score is found + for candidate_line in lines { + let unwrapped_line = candidate_line.unwrap(); + let vmaf_candidate = extract_vmaf_score(&unwrapped_line.as_str()); + match vmaf_candidate { + Ok(vmaf_score) => return Ok(vmaf_score), + Err(_) => continue, + } + } + + // if VMAF score could not be found, return error + Err("Could not find VMAF score") +} + +pub fn find_and_extract_frames_and_bytes() -> Result<(u32, u32), &'static str> { + let log_file = File::open(get_latest_ffmpeg_report_file()).unwrap(); + let reader = RevBufReader::new(log_file); + let lines = reader.lines(); + + // read from bottom until VMAF score is found + for candidate_line in lines { + let unwrapped_line = candidate_line.unwrap(); + let candidate_data = extract_frames_and_bytes(&unwrapped_line.as_str()); + + match candidate_data { + Some(data) => return Ok(data), + None => continue, + } + } + + // if VMAF score could not be found, return error + Err("Could not frames and bytes from report file") +} + pub fn capture_group(str: &str, regex: &str) -> String { let re = Regex::new(regex).unwrap(); let caps = re.captures(str); diff --git a/permutation/src/permutation.rs b/permutation/src/permutation.rs index a07ce3c..cbbffb9 100644 --- a/permutation/src/permutation.rs +++ b/permutation/src/permutation.rs @@ -1,3 +1,4 @@ +use ffmpeg::args::FfmpegQuality; use ffmpeg::ffprobe::probe_for_video_metadata; use ffmpeg::metadata::MetaData; @@ -6,7 +7,7 @@ pub struct Permutation { pub video_file: String, pub encoder: String, pub encoder_settings: String, - pub bitrate: u32, + pub ffmpeg_quality: FfmpegQuality, pub metadata: MetaData, pub check_quality: bool, pub allow_duplicates: bool, @@ -25,7 +26,7 @@ impl Permutation { video_file, encoder, encoder_settings: String::from(""), - bitrate: 0, + ffmpeg_quality: FfmpegQuality::Bitrate(0), metadata: MetaData::new(), check_quality: false, allow_duplicates: false, diff --git a/permutor-cli/src/main.rs b/permutor-cli/src/main.rs index fde74ee..5d29ba9 100644 --- a/permutor-cli/src/main.rs +++ b/permutor-cli/src/main.rs @@ -1,3 +1,5 @@ +use core::panic; + use clap::Parser; use cli::cli_util::log_cli_header; @@ -9,7 +11,9 @@ use codecs::nvenc::Nvenc; use codecs::permute::Permute; use codecs::qsv::QSV; use codecs::vendor::Vendor; +use codecs::codecs::Codecs; use engine::permutation_engine::PermutationEngine; +use ffmpeg::args::FfmpegQuality; use permutation::permutation::Permutation; use crate::permutor_cli::PermutorCli; @@ -25,25 +29,71 @@ fn main() { let mut engine = PermutationEngine::new(cli.log_output_directory.clone()); let vendor = get_vendor_for_codec(&cli.encoder.clone()); - for bitrate in get_bitrate_permutations(cli.bitrate, cli.max_bitrate_permutation.unwrap()) { - match vendor { - Vendor::Nvidia => { - build_nvenc_setting_permutations(&mut engine, &cli, bitrate); - } - Vendor::AMD => { - build_amf_setting_permutations(&mut engine, &cli, bitrate); - } - Vendor::IntelQSV => { - if cli.encoder.contains("av1") { - build_intel_av1_permutations(&mut engine, &cli, bitrate); - } else { - build_intel_igpu_permutations(&mut engine, &cli, bitrate); - } + + let min_quality = cli.get_quality(); + let max_quality = cli.get_max_quality(); + let using_bitrate = match min_quality { FfmpegQuality::Bitrate(_) => true, _ => false, }; + + let mut codec: Codecs; + match vendor { + Vendor::Nvidia => { + let nvenc = Nvenc::new(cli.encoder == "hevc_nvenc", cli.gpu, cli.no_b_frame, using_bitrate); + codec = Codecs::NVENC(nvenc); + //build_nvenc_setting_permutations(&mut engine, &cli, bitrate); + } + Vendor::AMD => { + let amf = Amf::new(cli.encoder == "hevc_amf", cli.gpu); + codec = Codecs::AMF(amf); + //build_amf_setting_permutations(&mut engine, &cli, bitrate); + } + Vendor::IntelQSV => { + if cli.encoder.contains("av1") { + let intel_av1 = AV1QSV::new(); + codec = Codecs::AV1QSV(intel_av1); + //build_intel_av1_permutations(&mut engine, &cli, bitrate); + } else { + let intel_i_gpu = QSV::new(cli.encoder == "hevc_qsv"); + codec = Codecs::QSV(intel_i_gpu); + //build_intel_igpu_permutations(&mut engine, &cli, bitrate); } - Vendor::Apple => { - build_apple_silicon_h264_permutations(&mut engine, &cli, bitrate); + } + Vendor::Apple => { + // this can probably be simplified with a type + let h264 = cli.encoder.contains("h264"); + let prores = cli.encoder.contains("prores"); + let apple_silicon = Apple::new(h264, prores); + codec = Codecs::APPLE(apple_silicon); + //build_apple_silicon_h264_permutations(&mut engine, &cli, bitrate); + } + Vendor::Unknown => { panic!("Unknown"); } + } + + for quality in get_quality_permutations(&min_quality, &max_quality) { + + // initialize the permutations each time + codec.init(); + + while let Some((_encoder_index, settings)) = codec.next() { + let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); + permutation.video_file = cli.source_file.clone(); + permutation.encoder_settings = settings; + permutation.ffmpeg_quality = quality; + permutation.check_quality = cli.check_quality; + permutation.verbose = cli.verbose; + permutation.detect_overload = cli.detect_overload; + permutation.allow_duplicates = cli.allow_duplicate_scores; + permutation.verbose = cli.verbose; + permutation.ten_bit = cli.ten_bit; + engine.add(permutation); + + // break out early here to just make 1 permutation + if cli.test_run { + break; } - Vendor::Unknown => {} + } + + if cli.test_run { + break; } } @@ -75,150 +125,30 @@ fn log_special_arguments(cli: &PermutorCli) { } } -fn build_nvenc_setting_permutations( - engine: &mut PermutationEngine, - cli: &PermutorCli, - bitrate: u32, -) { - let mut nvenc = Nvenc::new(cli.encoder == "hevc_nvenc", cli.gpu, cli.no_b_frame); - - // initialize the permutations each time - nvenc.init(); - - while let Some((_encoder_index, settings)) = nvenc.next() { - let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); - permutation.video_file = cli.source_file.clone(); - permutation.encoder_settings = settings; - permutation.bitrate = bitrate; - permutation.check_quality = cli.check_quality; - permutation.verbose = cli.verbose; - permutation.detect_overload = cli.detect_overload; - permutation.allow_duplicates = cli.allow_duplicate_scores; - permutation.verbose = cli.verbose; - permutation.ten_bit = cli.ten_bit; - engine.add(permutation); - - // break out early here to just make 1 permutation - if cli.test_run { - break; - } - } -} - -fn build_amf_setting_permutations(engine: &mut PermutationEngine, cli: &PermutorCli, bitrate: u32) { - let mut amf = Amf::new(cli.encoder == "hevc_amf", cli.gpu); - - // initialize the permutations each time - amf.init(); - - while let Some((_encoder_index, settings)) = amf.next() { - let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); - permutation.video_file = cli.source_file.clone(); - permutation.encoder_settings = settings; - permutation.bitrate = bitrate; - permutation.check_quality = cli.check_quality; - permutation.verbose = cli.verbose; - permutation.detect_overload = cli.detect_overload; - permutation.allow_duplicates = cli.allow_duplicate_scores; - permutation.ten_bit = cli.ten_bit; - engine.add(permutation); - - // break out early here to just make 1 permutation - if cli.test_run { - break; - } - } -} - -fn build_intel_av1_permutations(engine: &mut PermutationEngine, cli: &PermutorCli, bitrate: u32) { - let mut intel_av1 = AV1QSV::new(); - - // initialize the permutations each time - intel_av1.init(); - - while let Some((_encoder_index, settings)) = intel_av1.next() { - let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); - permutation.video_file = cli.source_file.clone(); - permutation.encoder_settings = settings; - permutation.bitrate = bitrate; - permutation.check_quality = cli.check_quality; - permutation.verbose = cli.verbose; - permutation.detect_overload = cli.detect_overload; - permutation.allow_duplicates = cli.allow_duplicate_scores; - permutation.ten_bit = cli.ten_bit; - engine.add(permutation); - - // break out early here to just make 1 permutation - if cli.test_run { - break; - } - } -} - -fn build_intel_igpu_permutations(engine: &mut PermutationEngine, cli: &PermutorCli, bitrate: u32) { - let mut intel_i_gpu = QSV::new(cli.encoder == "hevc_qsv"); - - // initialize the permutations each time - intel_i_gpu.init(); - - while let Some((_encoder_index, settings)) = intel_i_gpu.next() { - let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); - permutation.video_file = cli.source_file.clone(); - permutation.encoder_settings = settings; - permutation.bitrate = bitrate; - permutation.check_quality = cli.check_quality; - permutation.verbose = cli.verbose; - permutation.detect_overload = cli.detect_overload; - permutation.allow_duplicates = cli.allow_duplicate_scores; - permutation.ten_bit = cli.ten_bit; - engine.add(permutation); - - // break out early here to just make 1 permutation - if cli.test_run { - break; - } - } -} - -// TODO: we'll probably need to do more of these per apple silicon one -fn build_apple_silicon_h264_permutations( - engine: &mut PermutationEngine, - cli: &PermutorCli, - bitrate: u32, -) { - // this can probably be simplified with a type - let h264 = cli.encoder.contains("h264"); - let prores = cli.encoder.contains("prores"); - let mut apple_silicon = Apple::new(h264, prores); - - // initialize the permutations each time - apple_silicon.init(); - - while let Some((_encoder_index, settings)) = apple_silicon.next() { - let mut permutation = Permutation::new(cli.source_file.clone(), cli.encoder.clone()); - permutation.video_file = cli.source_file.clone(); - permutation.encoder_settings = settings; - permutation.bitrate = bitrate; - permutation.check_quality = cli.check_quality; - permutation.verbose = cli.verbose; - permutation.detect_overload = cli.detect_overload; - permutation.allow_duplicates = cli.allow_duplicate_scores; - permutation.ten_bit = cli.ten_bit; - engine.add(permutation); - - // break out early here to just make 1 permutation - if cli.test_run { - break; +fn get_quality_permutations(min_quality: &FfmpegQuality, max_quality: &FfmpegQuality) -> Vec { + let mut qualities = Vec::new(); + + match min_quality { + FfmpegQuality::Bitrate(b_min) => { + let interval = 5; + let b_max = match max_quality { FfmpegQuality::Bitrate(b) => b, _ => panic!("max_bitrate doesn't match FfmpegQuality enum of starting_bitrate"), }; + + for i in 0..(((b_max - b_min) / interval) + 1) { + let bitrate = b_min + (interval * i); + qualities.push(FfmpegQuality::Bitrate(bitrate)); + } + }, + FfmpegQuality::Quality(q_min) => { + let interval = 2; + let q_max = match max_quality { FfmpegQuality::Quality(q) => q, _ => panic!("max_bitrate doesn't match FfmpegQuality enum of starting_bitrate"), }; + + // iterates from maximum quality value (lowest quality) to minimum quality value, i.e. 22 -> 18 + for i in 0..(((q_min - q_max) / interval) + 1) { + let quality = q_min - (interval * i); + qualities.push(FfmpegQuality::Quality(quality)); + } } } -} - -fn get_bitrate_permutations(starting_bitrate: u32, max_bitrate: u32) -> Vec { - let interval = 5; - let mut bitrates = Vec::new(); - for i in 0..(((max_bitrate - starting_bitrate) / interval) + 1) { - bitrates.push(starting_bitrate + (interval * i)); - } - return bitrates; + return qualities; } diff --git a/permutor-cli/src/permutor_cli.rs b/permutor-cli/src/permutor_cli.rs index 6185171..b249236 100644 --- a/permutor-cli/src/permutor_cli.rs +++ b/permutor-cli/src/permutor_cli.rs @@ -1,6 +1,7 @@ use clap::Parser; use cli::cli_util::{error_with_ack, standard_cli_check}; +use ffmpeg::args::FfmpegQuality; #[derive(Parser)] pub struct PermutorCli { @@ -10,6 +11,9 @@ pub struct PermutorCli { /// target bitrate (in Mb/s) to output; in combination with --bitrate-max-permutation, this is the starting permutation #[arg(short, long, value_name = "bitrate", default_value = "10")] pub bitrate: u32, + /// target quality to output; in combination with --max-quality-permutation, this is the starting permutation + #[arg(short, long, value_name = "quality")] + pub quality: Option, /// whether to run vmaf score on each permutation or not #[arg(short, long)] pub check_quality: bool, @@ -34,9 +38,9 @@ pub struct PermutorCli { /// adds in '-pix_fmt yuv420p10le' to force 10-bit encoding #[arg(long)] pub ten_bit: bool, - /// maximum value to increase the bitrate to (in 5Mb/s intervals); if not specified, tool will not permute over bitrate values - #[arg(short, long, value_name = "bitrate")] - pub max_bitrate_permutation: Option, + /// maximum value to increase the bitrate or quality to (in 5Mb/s or 2 CQ intervals); if not specified, tool will not permute over bitrate values + #[arg(short, long)] + pub max_quality_permutation: Option, /// logs useful information to help troubleshooting #[arg(short, long)] pub verbose: bool, @@ -66,8 +70,8 @@ impl PermutorCli { error_with_ack(false); } - if self.max_bitrate_permutation.is_none() { - self.max_bitrate_permutation = Option::from(self.bitrate); + if self.max_quality_permutation.is_none() { + self.max_quality_permutation = Option::from(self.bitrate); } if self.source_file.is_empty() && !self.files_directory.is_empty() { @@ -76,6 +80,24 @@ impl PermutorCli { } } + pub fn get_quality(&self) -> FfmpegQuality { + if self.quality.is_none() { + FfmpegQuality::Bitrate(self.bitrate) + } + else { + FfmpegQuality::Quality(self.quality.unwrap()) + } + } + + pub fn get_max_quality(&self) -> FfmpegQuality { + if self.quality.is_none() { + FfmpegQuality::Bitrate(self.max_quality_permutation.unwrap()) + } + else { + FfmpegQuality::Quality(self.max_quality_permutation.unwrap()) + } + } + pub fn has_special_options(&self) -> bool { return self.check_quality || self.detect_overload