diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 62304ee..b84ada5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,10 +26,10 @@ jobs: - name: Install rust toolchain run: sudo apt-get install -qy cargo-1.80 rust-1.80-clippy - name: Install system dependencies - run: sudo apt-get install -qy liblzma-dev + run: sudo apt-get install -qy liblzma-dev libpipewire-0.3-dev libclang-dev libdbus-1-dev ffmpeg libavutil-dev libavformat-dev libavfilter-dev libavdevice-dev - name: Run Clippy run: cargo clippy --all-targets --all-features - name: Build - run: cargo build --release --verbose + run: cargo build --release diff --git a/Cargo.lock b/Cargo.lock index b05ca8f..ec4150a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -12,10 +18,29 @@ dependencies = [ ] [[package]] -name = "anstream" -version = "0.6.18" +name = "aligned-vec" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "annotate-snippets" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +dependencies = [ + "unicode-width", + "yansi-term", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -28,39 +53,160 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19135c0c7a60bfee564dbe44ab5ce0557c6bf3884e5291a50be76a15640c4fbd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "annotate-snippets", + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +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 = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "bitvec" version = "1.0.1" @@ -73,6 +219,12 @@ dependencies = [ "wyz", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -83,28 +235,87 @@ dependencies = [ ] [[package]] -name = "byteorder" -version = "1.5.0" +name = "built" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] [[package]] name = "clap" -version = "4.5.28" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -112,9 +323,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.27" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -124,9 +335,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", @@ -136,25 +347,172 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -167,9 +525,20 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] [[package]] name = "digest" @@ -181,6 +550,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "env_filter" version = "0.1.3" @@ -193,29 +583,244 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fast_image_resize" +version = "5.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d372ab3252d8f162d858d675a3d88a8c33ba24a6238837c50c8851911c7e89cd" +dependencies = [ + "bytemuck", + "cfg-if", + "document-features", + "image", + "num-traits", + "thiserror 1.0.69", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ffmpeg-next" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da02698288e0275e442a47fc12ca26d50daf0d48b15398ba5906f20ac2e2a9f9" +dependencies = [ + "bitflags 2.9.1", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e9c75ebd4463de9d8998fb134ba26347fe5faee62fabf0a4b4d41bd500b4ad" +dependencies = [ + "bindgen 0.70.1", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -228,15 +833,59 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + [[package]] name = "heck" version = "0.5.0" @@ -244,10 +893,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "http" -version = "1.2.0" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -256,15 +911,80 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "humantime" -version = "2.1.0" +name = "image" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inherent" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "is_terminal_polyfill" @@ -273,68 +993,545 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "itoa" -version = "1.0.14" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libfuzzer-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "libspa" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" +dependencies = [ + "bitflags 2.9.1", + "cc", + "convert_case", + "cookie-factory", + "libc", + "libspa-sys", + "nix", + "nom", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" +dependencies = [ + "bindgen 0.69.5", + "cc", + "system-deps", +] + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[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.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pipewire" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda" +dependencies = [ + "anyhow", + "bitflags 2.9.1", + "libc", + "libspa", + "libspa-sys", + "nix", + "once_cell", + "pipewire-sys", + "thiserror 1.0.69", +] + +[[package]] +name = "pipewire-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" +dependencies = [ + "bindgen 0.69.5", + "libspa-sys", + "system-deps", +] [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.38" +name = "profiling" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -348,8 +1545,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -359,7 +1566,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -368,7 +1585,95 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.5", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", ] [[package]] @@ -400,6 +1705,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + [[package]] name = "rust-lzma" version = "0.6.0" @@ -411,27 +1722,122 @@ dependencies = [ ] [[package]] -name = "servicepoint" -version = "0.13.0" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8bc9e40503ece07e3f12232f648484191323b8126e74abce3d1644ba04dbd0" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "scap" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55817d631bc81ba5f347ff5e112c87b8cc0dd99fac0e8e363bf451c3edfdb763" +dependencies = [ + "cocoa", + "core-graphics-helmer-fork", + "dbus", + "objc", + "pipewire", + "rand 0.8.5", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.58.0", + "windows-capture", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "servicepoint" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2800caad491cb44f67e5dd5b8c61ece368eecfe588155d03c7d9864acbad6919" dependencies = [ "bitvec", + "inherent", "log", "once_cell", "rust-lzma", - "thiserror", - "tungstenite", + "thiserror 2.0.12", ] [[package]] name = "servicepoint-cli" -version = "0.1.0" +version = "0.4.1" dependencies = [ "clap", "env_logger", + "fast_image_resize", + "ffmpeg-next", + "image", "log", + "scap", "servicepoint", + "tungstenite", ] [[package]] @@ -445,6 +1851,39 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "strsim" version = "0.11.1" @@ -453,15 +1892,55 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + [[package]] name = "tap" version = "1.0.1" @@ -469,19 +1948,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "thiserror" -version = "2.0.11" +name = "target-lexicon" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -489,34 +1983,101 @@ dependencies = [ ] [[package]] -name = "tungstenite" -version = "0.26.1" +name = "thiserror-impl" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", "httparse", "log", - "rand", + "rand 0.9.1", "sha1", - "thiserror", + "thiserror 2.0.12", "utf-8", ] [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "utf-8" @@ -530,12 +2091,29 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -544,9 +2122,302 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[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 = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[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" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-capture" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757c9e7b920233fec91cb314ad92b96853ba2907e3482a193d290d33838e3fc5" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.12", + "windows 0.61.3", + "windows-future", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "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-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[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" @@ -554,7 +2425,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -563,14 +2434,39 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" 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", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", ] [[package]] @@ -579,48 +2475,114 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "wyz" version = "0.5.1" @@ -631,22 +2593,54 @@ dependencies = [ ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "yansi-term" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index d9bd0fb..5b118a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "servicepoint-cli" description = "A command line interface for the ServicePoint display." -version = "0.1.0" +version = "0.4.1" edition = "2021" -rust-version = "1.78.0" +rust-version = "1.80.0" publish = true resolver = "2" readme = "README.md" @@ -13,7 +13,17 @@ homepage = "https://crates.io/crates/servicepoint-cli" keywords = ["cccb", "cccb-servicepoint", "cli"] [dependencies] -servicepoint = { version = "0.13.0", features = ["protocol_websocket"] } -clap = { version = "4.5", features = ["derive"]} +clap = { version = "4.5", features = ["derive"] } env_logger = "0.11" log = "0.4" +scap = "0.0.8" +image = "0.25" +fast_image_resize = { version = "5.1", features = ["image"] } +tungstenite = "0.26" +ffmpeg-next = "7.1" +servicepoint = "0.15.1" + +[profile.release] +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations +strip = true # Strip symbols from binary diff --git a/README.md b/README.md index f16d4ab..80b84d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # servicepoint-cli +[![Release](https://git.berlin.ccc.de/servicepoint/servicepoint-cli/badges/release.svg)](https://git.berlin.ccc.de/servicepoint/servicepoint-cli/releases) +[![crates.io](https://img.shields.io/crates/v/servicepoint-cli.svg)](https://crates.io/crates/servicepoint-cli) +[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint-cli)](https://crates.io/crates/servicepoint-cli) +![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint-cli) +[![CI](https://git.berlin.ccc.de/servicepoint/servicepoint-cli/badges/workflows/rust.yml/badge.svg)](https://git.berlin.ccc.de/servicepoint/servicepoint-cli) + This repository contains a command line interface for the ServicePoint display. To send commands, this uses the [servicepoint crate](https://crates.io/crates/servicepoint). @@ -15,14 +21,9 @@ If you have set your PATH to include the ~/.cargo/bin, you can now run `servicep ## Running with nix ```shell -# from CCCB Forgejo nix run git+https://git.berlin.ccc.de/servicepoint/servicepoint-cli.git -- - -# from GitHub mirror -nix run github:kaesaecracker/servicepoint-cli -- ``` - ## Running a debug build ```shell @@ -31,13 +32,150 @@ cd servicepoint-cli cargo run -- ``` +## Usage + +```text +Usage: servicepoint-cli [OPTIONS] + +Commands: + reset Reset both pixels and brightness [aliases: r] + pixels Commands for manipulating pixels [aliases: p] + brightness Commands for manipulating the brightness [aliases: b] + text Commands for sending text to the screen [aliases: t] + help Print this message or the help of the given subcommand(s) + +Options: + -d, --destination ip:port of the servicepoint display [default: 127.0.0.1:2342] + -t, --transport protocol to use for communication with display [default: udp] [possible values: udp, web-socket, fake] + -v, --verbose verbose logging + -h, --help Print help + -V, --version Print version +``` + +### Pixels + +```text +Commands for manipulating pixels + +Usage: servicepoint-cli pixels + +Commands: + off Reset all pixels to the default (off) state [aliases: r, reset, clear] + flip Invert the state of all pixels [aliases: f] + on Set all pixels to the on state + image Send an image file (e.g. jpeg or png) to the display. [aliases: i] + video Stream a video file (e.g. mp4) to the display. [aliases: v] + screen Stream the default screen capture source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. [aliases: s] +``` + +#### Image + +```text +Send an image file (e.g. jpeg or png) to the display. + +Usage: servicepoint-cli pixels image [OPTIONS] + +Arguments: + + +Options: + --no-hist Disable histogram correction + --no-blur Disable blur + --no-sharp Disable sharpening + --no-dither Disable dithering. Brightness will be adjusted so that around half of the pixels are on. + --no-spacers Do not remove the spacers from the image. + --no-aspect Do not keep aspect ratio when resizing. +``` + +#### Video file + +```text +Stream a video file (e.g. mp4) to the display. + +Usage: servicepoint-cli pixels video [OPTIONS] + +Arguments: + + +Options: + --no-hist Disable histogram correction + --no-blur Disable blur + --no-sharp Disable sharpening + --no-dither Disable dithering. Brightness will be adjusted so that around half of the pixels are on. + --no-spacers Do not remove the spacers from the image. + --no-aspect Do not keep aspect ratio when resizing. +``` + +#### Screen + +```text +Stream the default screen capture source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. + +Usage: servicepoint-cli pixels screen [OPTIONS] + +Options: + -p, --pointer Show mouse pointer in video feed + --no-hist Disable histogram correction + --no-blur Disable blur + --no-sharp Disable sharpening + --no-dither Disable dithering. Brightness will be adjusted so that around half of the pixels are on. + --no-spacers Do not remove the spacers from the image. + --no-aspect Do not keep aspect ratio when resizing. +``` + +### Brightness + +```text +Commands for manipulating the brightness + +Usage: servicepoint-cli brightness + +Commands: + max Reset brightness to the default (max) level [aliases: r, reset] + set Set one brightness for the whole screen [aliases: s] + min Set brightness to lowest possible level. +``` + +### Text + +```text +Commands for sending text to the screen + +Usage: servicepoint-cli text + +Commands: + stdin Pipe text to the display, example: `journalctl | servicepoint-cli text stdin` +``` + +#### Stdin + +```text +Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin` + +Usage: servicepoint-cli stream stdin [OPTIONS] + +Options: + -s, --slow Wait for a short amount of time before sending the next line +``` + +### Reset + +```text +Reset both pixels and brightness + +Usage: servicepoint-cli reset [OPTIONS] + +Options: +-f, --force hard reset screen +``` + ## Contributing If you have ideas on how to improve the code, add features or improve documentation feel free to open a pull request. You think you found a bug? Please open an issue. -Submissions on Forgejo are preferred, but you can also use GitHub. +Submissions on [Forgejo](https://git.berlin.ccc.de/servicepoint/servicepoint-cli) are preferred, but you can also use [GitHub](https://github.com/kaesaecracker/servicepoint-cli). All creatures welcome. diff --git a/flake.lock b/flake.lock index abacd0c..4999493 100644 --- a/flake.lock +++ b/flake.lock @@ -1,17 +1,40 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "naersk", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1752475459, + "narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=", + "owner": "nix-community", + "repo": "fenix", + "rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "naersk": { "inputs": { + "fenix": "fenix", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1736429655, - "narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=", + "lastModified": 1752689277, + "narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=", "owner": "nix-community", "repo": "naersk", - "rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce", + "rev": "0e72363d0938b0208d6c646d10649164c43f4d64", "type": "github" }, "original": { @@ -37,16 +60,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1736549401, - "narHash": "sha256-ibkQrMHxF/7TqAYcQE+tOnIsSEzXmMegzyBWza6uHKM=", + "lastModified": 1757545623, + "narHash": "sha256-mCxPABZ6jRjUQx3bPP4vjA68ETbPLNz9V2pk9tO7pRQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "1dab772dd4a68a7bba5d9460685547ff8e17d899", + "rev": "8cd5ce828d5d1d16feff37340171a98fc3bf6526", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.11", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } @@ -57,6 +80,23 @@ "nix-filter": "nix-filter", "nixpkgs": "nixpkgs" } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1752428706, + "narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "591e3b7624be97e4443ea7b5542c191311aa141d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index b5ad692..0586565 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,8 @@ { - description = "Flake for servicepoint-cli"; + description = "Flake for command line interface of the ServicePoint display."; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; nix-filter.url = "github:numtide/nix-filter"; naersk = { url = "github:nix-community/naersk"; @@ -11,7 +11,7 @@ }; outputs = - inputs@{ + { self, nixpkgs, naersk, @@ -42,7 +42,7 @@ naersk' = pkgs.callPackage naersk { }; in rec { - servicepoint-cli = naersk'.buildPackage rec { + servicepoint-cli = naersk'.buildPackage { src = nix-filter.lib.filter { root = ./.; include = [ @@ -55,12 +55,23 @@ }; nativeBuildInputs = with pkgs; [ pkg-config + rustPlatform.bindgenHook ]; strictDeps = true; - buildInputs = with pkgs; [ - xe - xz - ]; + buildInputs = + with pkgs; + [ + xe + xz + ffmpeg-headless + ] + ++ lib.optionals pkgs.stdenv.isLinux ( + with pkgs; + [ + dbus + pipewire + ] + ); }; default = servicepoint-cli; @@ -69,6 +80,14 @@ legacyPackages = packages; + nixosModules.default = { + nixpkgs.overlays = [ self.overlays.default ]; + }; + + overlays.default = final: prev: { + servicepoint-cli = self.legacyPackages."${prev.system}".servicepoint-cli; + }; + devShells = forAllSystems ( { pkgs, @@ -77,8 +96,7 @@ { default = pkgs.mkShell rec { inputsFrom = [ self.packages.${system}.default ]; - packages = [ - pkgs.gdb + packages = with pkgs; [ (pkgs.symlinkJoin { name = "rust-toolchain"; paths = with pkgs; [ @@ -90,13 +108,18 @@ cargo-expand ]; }) + + cargo-flamegraph + gdb ]; LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}"; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + RUST_LOG = "all"; + RUST_BACKTRACE = "1"; }; } ); - formatter = forAllSystems ({ pkgs, ... }: pkgs.nixfmt-rfc-style); + formatter = forAllSystems ({ pkgs, ... }: pkgs.nixfmt-tree); }; } diff --git a/src/brightness.rs b/src/brightness.rs new file mode 100644 index 0000000..49ad898 --- /dev/null +++ b/src/brightness.rs @@ -0,0 +1,20 @@ +use crate::{cli::BrightnessCommand, transport::Transport}; +use log::info; +use servicepoint::{Brightness, GlobalBrightnessCommand}; + +pub(crate) fn brightness(connection: &Transport, brightness_command: BrightnessCommand) { + match brightness_command { + BrightnessCommand::Max => brightness_set(connection, Brightness::MAX), + BrightnessCommand::Min => brightness_set(connection, Brightness::MIN), + BrightnessCommand::Set { brightness } => { + brightness_set(connection, Brightness::saturating_from(brightness)) + } + } +} + +pub(crate) fn brightness_set(connection: &Transport, brightness: Brightness) { + connection + .send_command(GlobalBrightnessCommand::from(brightness)) + .expect("Failed to set brightness"); + info!("set brightness to {brightness:?}"); +} diff --git a/src/cli.rs b/src/cli.rs index 8826a7d..c7a4a45 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,9 @@ #[derive(clap::Parser, std::fmt::Debug)] -#[clap(version, arg_required_else_help = true)] +#[clap( + version, + arg_required_else_help = true, + about = "A command line interface for the ServicePoint display." +)] pub struct Cli { #[arg( short, @@ -15,7 +19,7 @@ pub struct Cli { value_enum, default_value = "udp" )] - pub transport: Protocol, + pub transport: TransportType, #[clap(subcommand)] pub command: Mode, #[clap(short, long, help = "verbose logging")] @@ -24,8 +28,11 @@ pub struct Cli { #[derive(clap::Parser, std::fmt::Debug)] pub enum Mode { - #[command(visible_alias = "r")] - ResetEverything, + #[command(visible_alias = "r", about = "Reset both pixels and brightness")] + Reset { + #[arg(short, long, help = "hard reset screen")] + force: bool, + }, #[command(visible_alias = "p")] Pixels { #[clap(subcommand)] @@ -36,29 +43,140 @@ pub enum Mode { #[clap(subcommand)] brightness_command: BrightnessCommand, }, + #[command(visible_alias = "t")] + Text { + #[clap(subcommand)] + text_command: TextCommand, + }, } #[derive(clap::Parser, std::fmt::Debug)] +#[clap(about = "Commands for manipulating pixels")] pub enum PixelCommand { - #[command(visible_alias = "r")] - Reset, + #[command( + visible_alias = "r", + visible_alias = "reset", + visible_alias = "clear", + about = "Reset all pixels to the default (off) state" + )] + Off, + #[command(visible_alias = "f", about = "Invert the state of all pixels")] + Flip, + #[command(about = "Set all pixels to the on state")] + On, + #[command( + visible_alias = "i", + about = "Send an image file (e.g. jpeg or png) to the display." + )] + Image { + #[command(flatten)] + send_image_options: SendImageOptions, + #[command(flatten)] + image_processing_options: ImageProcessingOptions, + }, + #[command( + visible_alias = "v", + about = "Stream a video file (e.g. mp4) to the display." + )] + Video { + #[command(flatten)] + send_image_options: SendImageOptions, + #[command(flatten)] + image_processing_options: ImageProcessingOptions, + }, + #[command( + visible_alias = "s", + about = "Stream the default screen capture source to the display. \ + On Linux Wayland, this pops up a screen or window chooser, \ + but it also may directly start streaming your main screen." + )] + Screen { + #[command(flatten)] + stream_options: StreamScreenOptions, + #[command(flatten)] + image_processing: ImageProcessingOptions, + }, } #[derive(clap::Parser, std::fmt::Debug)] +#[clap(about = "Commands for manipulating the brightness")] pub enum BrightnessCommand { - #[command(visible_alias = "r")] - Reset, - #[command(visible_alias = "s")] + #[command( + visible_alias = "r", + visible_alias = "reset", + about = "Reset brightness to the default (max) level" + )] + Max, + #[command(visible_alias = "s", about = "Set one brightness for the whole screen")] Set { + #[arg()] brightness: u8, }, + #[command(about = "Set brightness to lowest possible level.")] Min, - Max, } #[derive(clap::ValueEnum, Clone, Debug)] -pub enum Protocol { +pub enum TransportType { Udp, WebSocket, Fake, } + +#[derive(clap::Parser, std::fmt::Debug)] +#[clap(about = "Commands for sending text to the screen")] +pub enum TextCommand { + #[command( + about = "Pipe text to the display, example: `journalctl | servicepoint-cli text stdin`" + )] + Stdin { + #[arg( + long, + short, + default_value_t = false, + help = "Wait for a short amount of time before sending the next line" + )] + slow: bool, + }, +} + +#[derive(clap::Parser, std::fmt::Debug, Clone)] +pub struct StreamScreenOptions { + #[arg( + long, + short, + default_value_t = false, + help = "Show mouse pointer in video feed" + )] + pub pointer: bool, +} + +#[derive(clap::Parser, std::fmt::Debug, Clone)] +pub struct ImageProcessingOptions { + #[arg(long, help = "Disable histogram correction")] + pub no_hist: bool, + + #[arg(long, help = "Disable blur")] + pub no_blur: bool, + + #[arg(long, help = "Disable sharpening")] + pub no_sharp: bool, + + #[arg( + long, + help = "Disable dithering. Brightness will be adjusted so that around half of the pixels are on." + )] + pub no_dither: bool, + + #[arg(long, help = "Do not remove the spacers from the image.")] + pub no_spacers: bool, + + #[arg(long, help = "Do not keep aspect ratio when resizing.")] + pub no_aspect: bool, +} + +#[derive(clap::Parser, std::fmt::Debug, Clone)] +pub struct SendImageOptions { + #[arg()] + pub file_name: String, +} diff --git a/src/execute.rs b/src/execute.rs deleted file mode 100644 index bbad4ed..0000000 --- a/src/execute.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::cli::{BrightnessCommand, Mode, PixelCommand}; -use log::info; -use servicepoint::{Brightness, Command, Connection}; - -pub fn execute_mode(mode: Mode, connection: Connection) { - match mode { - Mode::ResetEverything => { - brightness_reset(&connection); - pixels_reset(&connection); - } - Mode::Pixels { pixel_command } => pixels(&connection, pixel_command), - Mode::Brightness { brightness_command } => brightness(&connection, brightness_command), - } -} - -fn pixels(connection: &Connection, pixel_command: PixelCommand) { - match pixel_command { - PixelCommand::Reset => pixels_reset(connection), - } -} - -fn brightness(connection: &Connection, brightness_command: BrightnessCommand) { - match brightness_command { - BrightnessCommand::Reset => brightness_reset(connection), - BrightnessCommand::Min => brightness_set(connection, Brightness::MIN), - BrightnessCommand::Max => brightness_set(connection, Brightness::MAX), - BrightnessCommand::Set { brightness } => { - brightness_set(connection, Brightness::saturating_from(brightness)) - } - } -} - -fn pixels_reset(connection: &Connection) { - connection - .send(Command::Clear) - .expect("failed to clear pixels"); - info!("Reset pixels"); -} - -fn brightness_reset(connection: &Connection) { - connection - .send(Command::Brightness(Brightness::MAX)) - .expect("Failed to reset brightness to maximum"); - info!("Reset brightness"); -} - -fn brightness_set(connection: &Connection, brightness: Brightness) { - connection - .send(Command::Brightness(brightness)) - .expect("Failed to set brightness"); - info!("set brightness to {brightness:?}"); -} diff --git a/src/image_processing.rs b/src/image_processing.rs new file mode 100644 index 0000000..6e27cde --- /dev/null +++ b/src/image_processing.rs @@ -0,0 +1,173 @@ +use crate::{ + cli::ImageProcessingOptions, + ledwand_dither::{blur, histogram_correction, median_brightness, ostromoukhov_dither, sharpen}, +}; +use fast_image_resize::{ResizeOptions, Resizer}; +use image::{DynamicImage, GrayImage}; +use log::{debug, trace}; +use servicepoint::{Bitmap, Grid, PIXEL_HEIGHT, PIXEL_WIDTH, TILE_HEIGHT, TILE_SIZE}; +use std::{default::Default, time::Instant}; + +#[derive(Debug)] +pub struct ImageProcessingPipeline { + options: ImageProcessingOptions, + resizer: Resizer, + render_size: (u32, u32), +} + +const SPACER_HEIGHT: usize = TILE_SIZE / 2; + +impl ImageProcessingPipeline { + pub fn new(options: ImageProcessingOptions) -> Self { + debug!("Creating image pipeline: {:?}", options); + + let height = PIXEL_HEIGHT + + if options.no_spacers { + 0 + } else { + SPACER_HEIGHT * (TILE_HEIGHT - 1) + }; + + Self { + options, + resizer: Resizer::new(), + render_size: (PIXEL_WIDTH as u32, height as u32), + } + } + + #[must_use] + pub fn process(&mut self, frame: DynamicImage) -> Bitmap { + let start_time = Instant::now(); + + let frame = self.resize_grayscale(frame); + let frame = self.grayscale_processing(frame); + let mut result = self.grayscale_to_bitmap(frame); + + if !self.options.no_spacers { + result = Self::remove_spacers(result); + } + + trace!("pipeline took {:?}", start_time.elapsed()); + result + } + + fn resize_grayscale(&mut self, frame: DynamicImage) -> GrayImage { + let start_time = Instant::now(); + + let (scaled_width, scaled_height) = if self.options.no_aspect { + self.render_size + } else { + self.calc_scaled_size_keep_aspect((frame.width(), frame.height())) + }; + let mut dst_image = DynamicImage::new(scaled_width, scaled_height, frame.color()); + + self.resizer + .resize(&frame, &mut dst_image, &ResizeOptions::default()) + .expect("image resize failed"); + + trace!("resizing took {:?}", start_time.elapsed()); + + let start_time = Instant::now(); + let result = dst_image.into_luma8(); + trace!("grayscale took {:?}", start_time.elapsed()); + + result + } + + fn grayscale_processing(&self, mut frame: GrayImage) -> GrayImage { + let start_time = Instant::now(); + if !self.options.no_hist { + histogram_correction(&mut frame); + } + + let mut orig = frame.clone(); + + if !self.options.no_blur { + blur(&orig, &mut frame); + std::mem::swap(&mut frame, &mut orig); + } + + if !self.options.no_sharp { + sharpen(&orig, &mut frame); + std::mem::swap(&mut frame, &mut orig); + } + + trace!("image processing took {:?}", start_time.elapsed()); + orig + } + + fn grayscale_to_bitmap(&self, orig: GrayImage) -> Bitmap { + let start_time = Instant::now(); + let result = if self.options.no_dither { + let cutoff = median_brightness(&orig); + let bits = orig.iter().map(move |x| x > &cutoff).collect(); + Bitmap::from_bitvec(orig.width() as usize, bits).unwrap() + } else { + ostromoukhov_dither(orig, u8::MAX / 2) + }; + trace!("bitmap conversion took {:?}", start_time.elapsed()); + result + } + + fn remove_spacers(source: Bitmap) -> Bitmap { + let start_time = Instant::now(); + + let width = source.width(); + let result_height = Self::calc_height_without_spacers(source.height()); + let mut result = Bitmap::new(width, result_height).unwrap(); + + let mut source_y = 0; + for result_y in 0..result_height { + for x in 0..width { + result.set(x, result_y, source.get(x, source_y)); + } + + if result_y != 0 && result_y % TILE_SIZE == 0 { + source_y += SPACER_HEIGHT; + } + source_y += 1; + } + + trace!("removing spacers took {:?}", start_time.elapsed()); + result + } + + fn calc_height_without_spacers(height: usize) -> usize { + let full_tile_rows_with_spacers = height / (TILE_SIZE + SPACER_HEIGHT); + let remaining_pixel_rows = height % (TILE_SIZE + SPACER_HEIGHT); + let total_spacer_height = full_tile_rows_with_spacers * SPACER_HEIGHT + + remaining_pixel_rows.saturating_sub(TILE_SIZE); + let height_without_spacers = height - total_spacer_height; + trace!( + "spacers take up {total_spacer_height}, resulting in final height {height_without_spacers}" + ); + height_without_spacers + } + + fn calc_scaled_size_keep_aspect(&self, source: (u32, u32)) -> (u32, u32) { + let (source_width, source_height) = source; + let (target_width, target_height) = self.render_size; + debug_assert_eq!(target_width % TILE_SIZE as u32, 0); + + let width_scale = target_width as f32 / source_width as f32; + let height_scale = target_height as f32 / source_height as f32; + let scale = f32::min(width_scale, height_scale); + + let height = (source_height as f32 * scale) as u32; + let mut width = (source_width as f32 * scale) as u32; + + if width % TILE_SIZE as u32 != 0 { + // because we do not have many pixels, round up even if it is a worse fit + width += 8 - width % 8; + } + + let result = (width, height); + trace!( + "scaling {:?} to {:?} to fit {:?}", + source, + result, + self.render_size + ); + result + } +} diff --git a/src/ledwand_dither.rs b/src/ledwand_dither.rs new file mode 100644 index 0000000..c1caed4 --- /dev/null +++ b/src/ledwand_dither.rs @@ -0,0 +1,520 @@ +//! Based on https://github.com/WarkerAnhaltRanger/CCCB_Ledwand + +use image::GrayImage; +use log::debug; +use servicepoint::{Bitmap, DisplayBitVec, PIXEL_HEIGHT}; + +type GrayHistogram = [usize; 256]; + +struct HistogramCorrection { + pre_offset: f32, + post_offset: f32, + factor: f32, +} + +pub fn histogram_correction(image: &mut GrayImage) { + let histogram = make_histogram(image); + let correction = determine_histogram_correction(image, histogram); + apply_histogram_correction(image, correction) +} + +fn make_histogram(image: &GrayImage) -> GrayHistogram { + let mut histogram = [0; 256]; + for pixel in image.pixels() { + histogram[pixel.0[0] as usize] += 1; + } + histogram +} + +fn determine_histogram_correction( + image: &GrayImage, + histogram: GrayHistogram, +) -> HistogramCorrection { + let adjustment_pixels = image.len() / PIXEL_HEIGHT; + + let mut num_pixels = 0; + let mut brightness = 0; + + let mincut = loop { + num_pixels += histogram[brightness as usize]; + brightness += 1; + if num_pixels >= adjustment_pixels { + break u8::min(brightness, 20); + } + }; + + let minshift = loop { + num_pixels += histogram[brightness as usize]; + brightness += 1; + if num_pixels >= 2 * adjustment_pixels { + break u8::min(brightness, 64); + } + }; + + brightness = u8::MAX; + num_pixels = 0; + let maxshift = loop { + num_pixels += histogram[brightness as usize]; + brightness -= 1; + if num_pixels >= 2 * adjustment_pixels { + break u8::max(brightness, 192); + } + }; + + let pre_offset = -(mincut as f32 / 2.); + let post_offset = -(minshift as f32); + let factor = (255.0 - post_offset) / maxshift as f32; + HistogramCorrection { + pre_offset, + post_offset, + factor, + } +} + +fn apply_histogram_correction(image: &mut GrayImage, correction: HistogramCorrection) { + for pixel in image.pixels_mut() { + let pixel = &mut pixel.0[0]; + let value = + (*pixel as f32 + correction.pre_offset) * correction.factor + correction.post_offset; + *pixel = value.clamp(0f32, u8::MAX as f32) as u8; + } +} + +pub fn median_brightness(image: &GrayImage) -> u8 { + let histogram = make_histogram(image); + let midpoint = image.len() / 2; + + debug_assert_eq!( + image.len(), + histogram.iter().copied().map(usize::from).sum() + ); + + let mut num_pixels = 0; + for brightness in u8::MIN..=u8::MAX { + num_pixels += histogram[brightness as usize]; + if num_pixels >= midpoint { + return brightness; + } + } + + unreachable!("Somehow less pixels where counted in the histogram than exist in the image") +} + +pub fn blur(source: &GrayImage, destination: &mut GrayImage) { + assert_eq!(source.len(), destination.len()); + + copy_border(source, destination); + blur_inner_pixels(source, destination); +} + +pub fn sharpen(source: &GrayImage, destination: &mut GrayImage) { + assert_eq!(source.len(), destination.len()); + + copy_border(source, destination); + sharpen_inner_pixels(source, destination); +} + +fn copy_border(source: &GrayImage, destination: &mut GrayImage) { + let last_row = source.height() - 1; + for x in 0..source.width() { + destination[(x, 0)] = source[(x, 0)]; + destination[(x, last_row)] = source[(x, last_row)]; + } + let last_col = source.width() - 1; + for y in 0..source.height() { + destination[(0, y)] = source[(0, y)]; + destination[(last_col, y)] = source[(last_col, y)]; + } +} + +fn blur_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { + for y in 1..source.height() - 2 { + for x in 1..source.width() - 2 { + let weighted_sum = source.get_pixel(x - 1, y - 1).0[0] as u32 + + source.get_pixel(x, y - 1).0[0] as u32 + + source.get_pixel(x + 1, y - 1).0[0] as u32 + + source.get_pixel(x - 1, y).0[0] as u32 + + 8 * source.get_pixel(x, y).0[0] as u32 + + source.get_pixel(x + 1, y).0[0] as u32 + + source.get_pixel(x - 1, y + 1).0[0] as u32 + + source.get_pixel(x, y + 1).0[0] as u32 + + source.get_pixel(x + 1, y + 1).0[0] as u32; + let blurred = weighted_sum / 16; + destination.get_pixel_mut(x, y).0[0] = + blurred.clamp(u8::MIN as u32, u8::MAX as u32) as u8; + } + } +} + +fn sharpen_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { + for y in 1..source.height() - 2 { + for x in 1..source.width() - 2 { + let weighted_sum = -(source.get_pixel(x - 1, y - 1).0[0] as i32) + - source.get_pixel(x, y - 1).0[0] as i32 + - source.get_pixel(x + 1, y - 1).0[0] as i32 + - source.get_pixel(x - 1, y).0[0] as i32 + + 9 * source.get_pixel(x, y).0[0] as i32 + - source.get_pixel(x + 1, y).0[0] as i32 + - source.get_pixel(x - 1, y + 1).0[0] as i32 + - source.get_pixel(x, y + 1).0[0] as i32 + - source.get_pixel(x + 1, y + 1).0[0] as i32; + destination.get_pixel_mut(x, y).0[0] = + weighted_sum.clamp(u8::MIN as i32, u8::MAX as i32) as u8; + } + } +} + +pub(crate) fn ostromoukhov_dither(source: GrayImage, bias: u8) -> Bitmap { + let width = source.width(); + let height = source.height(); + assert_eq!(width % 8, 0); + + let mut source = source.into_raw(); + let mut destination = DisplayBitVec::repeat(false, source.len()); + + for y in 0..height as usize { + let start = y * width as usize; + let last_row = y == (height - 1) as usize; + if y % 2 == 0 { + for x in start..start + width as usize { + ostromoukhov_dither_pixel( + &mut source, + &mut destination, + x, + width as usize, + last_row, + 1, + bias, + ); + } + } else { + for x in (start..start + width as usize).rev() { + ostromoukhov_dither_pixel( + &mut source, + &mut destination, + x, + width as usize, + last_row, + -1, + bias, + ); + } + } + } + + Bitmap::from_bitvec(width as usize, destination).unwrap() +} + +#[inline] +fn ostromoukhov_dither_pixel( + source: &mut [u8], + destination: &mut DisplayBitVec, + position: usize, + width: usize, + last_row: bool, + direction: isize, + bias: u8, +) { + let (destination_value, error) = gray_to_bit(source[position], bias); + destination.set(position, destination_value); + + let mut diffuse = |to: usize, mat: i16| { + match source.get(to) { + None => { + // last row has a out of bounds error on the last pixel + // TODO fix the iter bounds instead of ignoring here + } + Some(val) => { + let diffuse_value = *val as i16 + mat; + source[to] = diffuse_value.clamp(u8::MIN.into(), u8::MAX.into()) as u8; + } + }; + }; + + let lookup = if destination_value { + ERROR_DIFFUSION_MATRIX[error as usize].map(move |i| -i) + } else { + ERROR_DIFFUSION_MATRIX[error as usize] + }; + diffuse((position as isize + direction) as usize, lookup[0]); + + if !last_row { + debug!("begin"); + diffuse( + ((position + width) as isize - direction) as usize, + lookup[1], + ); + debug!("mit"); + diffuse(((position + width) as isize) as usize, lookup[2]); + debug!("end"); + } +} + +fn gray_to_bit(old_pixel: u8, bias: u8) -> (bool, u8) { + let destination_value = old_pixel > bias; + let error = if destination_value { + 255 - old_pixel + } else { + old_pixel + }; + (destination_value, error) +} + +const ERROR_DIFFUSION_MATRIX: [[i16; 3]; 256] = [ + [0, 1, 0], + [1, 0, 0], + [1, 0, 1], + [2, 0, 1], + [2, 0, 2], + [3, 0, 2], + [4, 0, 2], + [4, 1, 2], + [5, 1, 2], + [5, 2, 2], + [5, 3, 2], + [6, 3, 2], + [6, 3, 3], + [7, 3, 3], + [7, 4, 3], + [8, 4, 3], + [8, 5, 3], + [9, 5, 3], + [9, 5, 4], + [10, 6, 3], + [10, 6, 4], + [11, 7, 3], + [11, 7, 4], + [11, 8, 4], + [12, 7, 5], + [12, 7, 6], + [12, 7, 7], + [12, 7, 8], + [12, 7, 9], + [13, 7, 9], + [13, 7, 10], + [13, 7, 11], + [13, 7, 12], + [14, 7, 12], + [14, 8, 12], + [15, 8, 12], + [15, 9, 12], + [16, 9, 12], + [16, 10, 12], + [17, 10, 12], + [17, 11, 12], + [18, 12, 11], + [19, 12, 11], + [19, 13, 11], + [20, 13, 11], + [20, 14, 11], + [21, 15, 10], + [22, 15, 10], + [22, 17, 9], + [23, 17, 9], + [24, 18, 8], + [24, 19, 8], + [25, 19, 8], + [26, 20, 7], + [26, 21, 7], + [27, 22, 6], + [28, 23, 5], + [28, 24, 5], + [29, 25, 4], + [30, 26, 3], + [31, 26, 3], + [31, 28, 2], + [32, 28, 2], + [33, 29, 1], + [34, 30, 0], + [33, 31, 1], + [32, 33, 1], + [32, 33, 2], + [31, 34, 3], + [30, 36, 3], + [29, 37, 4], + [29, 37, 5], + [28, 39, 5], + [32, 34, 7], + [37, 29, 8], + [42, 23, 10], + [46, 19, 11], + [51, 13, 12], + [52, 14, 13], + [53, 13, 12], + [53, 14, 13], + [54, 14, 13], + [55, 14, 13], + [55, 14, 13], + [56, 15, 14], + [57, 14, 13], + [56, 15, 15], + [55, 17, 15], + [54, 18, 16], + [53, 20, 16], + [52, 21, 17], + [52, 22, 17], + [51, 24, 17], + [50, 25, 18], + [49, 27, 18], + [47, 29, 19], + [48, 29, 19], + [48, 29, 20], + [49, 29, 20], + [49, 30, 20], + [50, 31, 20], + [50, 31, 20], + [51, 31, 20], + [51, 31, 21], + [52, 31, 21], + [52, 32, 21], + [53, 32, 21], + [53, 32, 22], + [55, 32, 21], + [56, 31, 22], + [58, 31, 21], + [59, 30, 22], + [61, 30, 21], + [62, 29, 22], + [64, 29, 21], + [65, 28, 22], + [67, 28, 21], + [68, 27, 22], + [70, 27, 21], + [71, 26, 22], + [73, 26, 21], + [75, 25, 21], + [76, 25, 21], + [78, 24, 21], + [80, 23, 21], + [81, 23, 21], + [83, 22, 21], + [85, 21, 20], + [85, 22, 21], + [85, 22, 22], + [84, 24, 22], + [84, 24, 23], + [84, 25, 23], + [83, 27, 23], + [83, 28, 23], + [82, 29, 24], + [82, 30, 24], + [81, 31, 25], + [80, 32, 26], + [80, 33, 26], + [79, 35, 26], + [79, 36, 26], + [78, 37, 27], + [77, 38, 28], + [77, 39, 28], + [76, 41, 28], + [75, 42, 29], + [75, 43, 29], + [74, 44, 30], + [74, 45, 30], + [75, 46, 30], + [75, 46, 30], + [76, 46, 30], + [76, 46, 31], + [77, 46, 31], + [77, 47, 31], + [78, 47, 31], + [78, 47, 32], + [79, 47, 32], + [79, 48, 32], + [80, 49, 32], + [83, 46, 32], + [86, 44, 32], + [90, 42, 31], + [93, 40, 31], + [96, 39, 30], + [100, 36, 30], + [103, 35, 29], + [106, 33, 29], + [110, 30, 29], + [113, 29, 28], + [114, 29, 28], + [115, 29, 28], + [115, 29, 28], + [116, 30, 29], + [117, 29, 28], + [117, 30, 29], + [118, 30, 29], + [119, 30, 29], + [109, 43, 27], + [100, 57, 23], + [90, 71, 20], + [80, 85, 17], + [70, 99, 14], + [74, 98, 12], + [78, 97, 10], + [81, 96, 9], + [85, 95, 7], + [89, 94, 5], + [92, 93, 4], + [96, 92, 2], + [100, 91, 0], + [100, 90, 2], + [100, 88, 5], + [100, 87, 7], + [99, 86, 10], + [99, 85, 12], + [99, 84, 14], + [99, 82, 17], + [98, 81, 20], + [98, 80, 22], + [98, 79, 24], + [98, 77, 27], + [98, 76, 29], + [97, 75, 32], + [97, 73, 35], + [97, 72, 37], + [96, 71, 40], + [96, 69, 43], + [96, 67, 46], + [96, 66, 48], + [95, 65, 51], + [95, 63, 54], + [95, 61, 57], + [94, 60, 60], + [94, 58, 63], + [94, 57, 65], + [93, 55, 69], + [93, 54, 71], + [93, 52, 74], + [92, 51, 77], + [92, 49, 80], + [91, 47, 84], + [91, 46, 86], + [93, 49, 82], + [96, 52, 77], + [98, 55, 73], + [101, 58, 68], + [104, 61, 63], + [106, 65, 58], + [109, 68, 53], + [111, 71, 49], + [114, 74, 44], + [116, 78, 39], + [118, 76, 40], + [119, 74, 42], + [120, 73, 43], + [122, 71, 44], + [123, 69, 46], + [124, 67, 48], + [125, 66, 49], + [127, 64, 50], + [128, 62, 52], + [129, 60, 54], + [131, 58, 55], + [132, 57, 56], + [136, 47, 63], + [139, 38, 70], + [143, 29, 76], + [147, 19, 83], + [151, 9, 90], + [154, 0, 97], + [160, 0, 92], + [171, 0, 82], + [183, 0, 71], + [184, 0, 71], +]; diff --git a/src/main.rs b/src/main.rs index cb6515a..cdae13c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,48 @@ -use crate::cli::{Cli, Protocol}; +use crate::{ + brightness::{brightness, brightness_set}, + cli::{Cli, Mode}, + pixels::{pixels, pixels_off}, + text::text, + transport::Transport, +}; use clap::Parser; use log::debug; -use servicepoint::Connection; +use servicepoint::{Brightness, HardResetCommand}; +mod brightness; mod cli; -mod execute; +mod image_processing; +mod ledwand_dither; +mod pixels; +mod stream_stdin; +mod stream_window; +mod text; +mod transport; fn main() { let cli = Cli::parse(); init_logging(cli.verbose); debug!("running with arguments: {:?}", cli); - let connection = make_connection(cli.destination, cli.transport); - debug!("connection established: {:#?}", connection); + let transport = Transport::connect(cli.transport, &cli.destination); + debug!("connection established: {:#?}", transport); - execute::execute_mode(cli.command, connection); + execute_mode(cli.command, transport); } -fn make_connection(destination: String, transport: Protocol) -> Connection { - match transport { - Protocol::Udp => Connection::open(destination).expect("Failed to open UDP connection"), - Protocol::WebSocket => { - let url = destination.parse().expect( - "provided destination is not a valid url - make sure it starts with 'ws://'", - ); - Connection::open_websocket(url).expect("Failed to open WebSocket connection") +pub fn execute_mode(mode: Mode, connection: Transport) { + match mode { + Mode::Reset { force } => { + if force { + connection.send_command(HardResetCommand).unwrap() + } else { + brightness_set(&connection, Brightness::MAX); + pixels_off(&connection); + } } - Protocol::Fake => Connection::Fake, + Mode::Pixels { pixel_command } => pixels(&connection, pixel_command), + Mode::Brightness { brightness_command } => brightness(&connection, brightness_command), + Mode::Text { text_command } => text(&connection, text_command), } } diff --git a/src/pixels.rs b/src/pixels.rs new file mode 100644 index 0000000..3f9665d --- /dev/null +++ b/src/pixels.rs @@ -0,0 +1,157 @@ +use crate::{ + cli::{ImageProcessingOptions, PixelCommand, SendImageOptions}, + image_processing::ImageProcessingPipeline, + stream_window::stream_window, + transport::Transport, +}; +use ffmpeg_next as ffmpeg; +use image::{DynamicImage, RgbImage}; +use log::info; +use servicepoint::{ + BinaryOperation, BitVecCommand, BitmapCommand, ClearCommand, CompressionCode, DisplayBitVec, + Origin, PIXEL_COUNT, +}; + +pub(crate) fn pixels(connection: &Transport, pixel_command: PixelCommand) { + match pixel_command { + PixelCommand::Off => pixels_off(connection), + PixelCommand::Flip => pixels_invert(connection), + PixelCommand::On => pixels_on(connection), + PixelCommand::Image { + image_processing_options: processing_options, + send_image_options: image_options, + } => pixels_image(connection, image_options, processing_options), + PixelCommand::Screen { + stream_options, + image_processing, + } => stream_window(connection, stream_options, image_processing), + PixelCommand::Video { + image_processing_options: processing_options, + send_image_options: image_options, + } => pixels_video(connection, image_options, processing_options), + } +} + +fn pixels_on(connection: &Transport) { + let mask = DisplayBitVec::repeat(true, PIXEL_COUNT); + let command = BitVecCommand { + offset: 0, + bitvec: mask, + compression: CompressionCode::Lzma, + operation: BinaryOperation::Overwrite, + }; + connection + .send_command(command) + .expect("could not send command"); + info!("turned on all pixels") +} + +fn pixels_invert(connection: &Transport) { + let mask = DisplayBitVec::repeat(true, PIXEL_COUNT); + let command = BitVecCommand { + offset: 0, + bitvec: mask, + compression: CompressionCode::Lzma, + operation: BinaryOperation::Xor, + }; + connection + .send_command(command) + .expect("could not send command"); + info!("inverted all pixels"); +} + +pub(crate) fn pixels_off(connection: &Transport) { + connection + .send_command(ClearCommand) + .expect("failed to clear pixels"); + info!("reset pixels"); +} + +fn pixels_image( + connection: &Transport, + options: SendImageOptions, + processing_options: ImageProcessingOptions, +) { + let image = image::open(&options.file_name).expect("failed to open image file"); + let mut pipeline = ImageProcessingPipeline::new(processing_options); + let bitmap = pipeline.process(image); + connection + .send_command(BitmapCommand { + origin: Origin::ZERO, + bitmap, + compression: CompressionCode::default(), + }) + .expect("failed to send image command"); + info!("sent image to display"); +} + +fn pixels_video( + connection: &Transport, + options: SendImageOptions, + processing_options: ImageProcessingOptions, +) { + ffmpeg::init().unwrap(); + + let mut ictx = ffmpeg::format::input(&options.file_name).expect("failed to open video input file"); + + let input = ictx + .streams() + .best(ffmpeg::media::Type::Video) + .ok_or(ffmpeg::Error::StreamNotFound) + .expect("could not get video stream from input file"); + let video_stream_index = input.index(); + + let context_decoder = ffmpeg::codec::context::Context::from_parameters(input.parameters()) + .expect("could not extract video context from parameters"); + let mut decoder = context_decoder.decoder().video() + .expect("failed to create decoder for video stream"); + + let src_width = decoder.width(); + let src_height = decoder.height(); + + let mut scaler = ffmpeg::software::scaling::Context::get( + decoder.format(), + src_width, + src_height, + ffmpeg::format::Pixel::RGB24, + src_width, + src_height, + ffmpeg::software::scaling::Flags::BILINEAR, + ).expect("failed to create scaling context"); + + let mut frame_index = 0; + + let mut processing_pipeline = ImageProcessingPipeline::new(processing_options); + + let mut receive_and_process_decoded_frames = + |decoder: &mut ffmpeg::decoder::Video| -> Result<(), ffmpeg::Error> { + let mut decoded = ffmpeg::util::frame::video::Video::empty(); + let mut rgb_frame = ffmpeg::util::frame::video::Video::empty(); + while decoder.receive_frame(&mut decoded).is_ok() { + scaler.run(&decoded, &mut rgb_frame) + .expect("failed to scale frame"); + + let image = RgbImage::from_raw(src_width, src_height, rgb_frame.data(0).to_owned()) + .expect("could not read rgb data to image"); + let image = DynamicImage::from(image); + let bitmap= processing_pipeline.process(image); + connection.send_command(BitmapCommand::from(bitmap)) + .expect("failed to send image command"); + + frame_index += 1; + } + Ok(()) + }; + + for (stream, packet) in ictx.packets() { + if stream.index() == video_stream_index { + decoder.send_packet(&packet) + .expect("failed to send video packet"); + receive_and_process_decoded_frames(&mut decoder) + .expect("failed to process video packet"); + } + } + decoder.send_eof().expect("failed to send eof"); + receive_and_process_decoded_frames(&mut decoder) + .expect("failed to eof packet"); +} diff --git a/src/stream_stdin.rs b/src/stream_stdin.rs new file mode 100644 index 0000000..872461c --- /dev/null +++ b/src/stream_stdin.rs @@ -0,0 +1,86 @@ +use crate::transport::Transport; +use log::warn; +use servicepoint::*; +use std::thread::sleep; + +pub(crate) fn stream_stdin(connection: &Transport, slow: bool) { + warn!("This mode will break when using multi-byte characters and does not support ANSI escape sequences yet."); + let mut app = App { + connection, + mirror: CharGrid::new(TILE_WIDTH, TILE_HEIGHT), + y: 0, + slow, + }; + app.run() +} + +struct App<'t> { + connection: &'t Transport, + mirror: CharGrid, + y: usize, + slow: bool, +} + +impl App<'_> { + fn run(&mut self) { + self.connection + .send_command(ClearCommand) + .expect("couldn't clear screen"); + let last_y = self.mirror.height() - 1; + for line in std::io::stdin().lines() { + let line = line.expect("could not read from stdin"); + + if self.y <= last_y { + self.single_line(&line); + self.y += 1; + } else { + self.shift_rows(); + Self::line_onto_grid(&mut self.mirror, last_y, &line); + self.send_mirror() + // we stay on last y + } + + if self.slow { + sleep(FRAME_PACING); + } + } + } + + fn shift_rows(&mut self) { + let data = self.mirror.data_ref_mut(); + data.rotate_left(TILE_WIDTH); + if let Some(row) = data.last_chunk_mut::() { + row.fill(' ') + } + } + + fn line_onto_grid(grid: &mut CharGrid, y: usize, line: &str) { + for (x, char) in line.chars().enumerate() { + if x < grid.width() { + grid.set(x, y, char); + } + } + } + + fn send_mirror(&self) { + self.connection + .send_command(CharGridCommand { + origin: Origin::ZERO, + grid: self.mirror.clone(), + }) + .expect("couldn't send screen to display"); + } + + fn single_line(&mut self, line: &str) { + let mut line_grid = CharGrid::new(TILE_WIDTH, 1); + line_grid.fill(' '); + Self::line_onto_grid(&mut line_grid, 0, line); + Self::line_onto_grid(&mut self.mirror, self.y, line); + self.connection + .send_command(CharGridCommand { + origin: Origin::new(0, self.y), + grid: line_grid, + }) + .expect("couldn't send single line to screen"); + } +} diff --git a/src/stream_window.rs b/src/stream_window.rs new file mode 100644 index 0000000..ffbc717 --- /dev/null +++ b/src/stream_window.rs @@ -0,0 +1,113 @@ +use crate::{ + cli::{ImageProcessingOptions, StreamScreenOptions}, + image_processing::ImageProcessingPipeline, + transport::Transport, +}; +use image::{DynamicImage, ImageBuffer, Rgb, Rgba}; +use log::{debug, error, info, trace, warn}; +use scap::{ + capturer::{Capturer, Options}, + frame::convert_bgra_to_rgb, + frame::Frame, +}; +use servicepoint::{BitmapCommand, CompressionCode, Origin, FRAME_PACING}; +use std::time::{Duration, Instant}; + +pub fn stream_window( + connection: &Transport, + options: StreamScreenOptions, + processing_options: ImageProcessingOptions, +) { + info!("Starting capture with options: {:?}", options); + let capturer = match start_capture(&options) { + Some(value) => value, + None => return, + }; + + let mut pipeline = ImageProcessingPipeline::new(processing_options); + + info!("now starting to stream images"); + loop { + let start = Instant::now(); + + let frame = capture_frame(&capturer); + let frame = frame_to_image(frame); + let bitmap = pipeline.process(frame); + + trace!("bitmap ready to send in: {:?}", start.elapsed()); + + connection + .send_command(BitmapCommand { + origin: Origin::ZERO, + bitmap: bitmap.clone(), + compression: CompressionCode::default(), + }) + .expect("failed to send frame to display"); + + debug!("frame time: {:?}", start.elapsed()); + } +} + +fn start_capture(options: &StreamScreenOptions) -> Option { + if !scap::is_supported() { + error!("platform not supported by scap"); + return None; + } + + if !scap::has_permission() { + warn!("requesting screen recording permission"); + if !scap::request_permission() { + error!("screen recording ermission denied"); + return None; + } + } + + // all options are more like a suggestion + let mut capturer = Capturer::build(Options { + fps: FRAME_PACING.div_duration_f32(Duration::from_secs(1)) as u32, + show_cursor: options.pointer, + output_type: scap::frame::FrameType::BGR0, + ..Default::default() + }) + .expect("failed to create screen capture"); + capturer.start_capture(); + Some(capturer) +} + +fn capture_frame(capturer: &Capturer) -> Frame { + let start_time = Instant::now(); + let result = capturer.get_next_frame().expect("failed to capture frame"); + trace!("capture took: {:?}", start_time.elapsed()); + result +} + +fn frame_to_image(frame: Frame) -> DynamicImage { + let start_time = Instant::now(); + let result = match frame { + Frame::BGRx(frame) => bgrx_to_rgb(frame.width, frame.height, frame.data), + Frame::RGBx(frame) => DynamicImage::from( + ImageBuffer::, _>::from_raw( + frame.width as u32, + frame.height as u32, + frame.data, + ) + .unwrap(), + ), + Frame::BGR0(frame) => bgrx_to_rgb(frame.width, frame.height, frame.data), + Frame::RGB(frame) => DynamicImage::from( + ImageBuffer::, _>::from_raw(frame.width as u32, frame.height as u32, frame.data) + .unwrap(), + ), + Frame::BGRA(frame) => bgrx_to_rgb(frame.width, frame.height, frame.data), + Frame::YUVFrame(_) | Frame::XBGR(_) => panic!("unsupported frame format"), + }; + trace!("conversion to image took: {:?}", start_time.elapsed()); + result +} + +fn bgrx_to_rgb(width: i32, height: i32, data: Vec) -> DynamicImage { + DynamicImage::from( + ImageBuffer::, _>::from_raw(width as u32, height as u32, convert_bgra_to_rgb(data)) + .unwrap(), + ) +} diff --git a/src/text.rs b/src/text.rs new file mode 100644 index 0000000..b5fbd4c --- /dev/null +++ b/src/text.rs @@ -0,0 +1,7 @@ +use crate::{cli::TextCommand, stream_stdin::stream_stdin, transport::Transport}; + +pub fn text(connection: &Transport, command: TextCommand) { + match command { + TextCommand::Stdin { slow } => stream_stdin(connection, slow), + } +} diff --git a/src/transport.rs b/src/transport.rs new file mode 100644 index 0000000..03c651a --- /dev/null +++ b/src/transport.rs @@ -0,0 +1,51 @@ +use crate::cli::TransportType; +use servicepoint::{FakeConnection, Packet, UdpSocketExt}; +use std::fmt::Debug; +use std::net::{TcpStream, UdpSocket}; +use std::sync::Mutex; +use tungstenite::client::IntoClientRequest; +use tungstenite::stream::MaybeTlsStream; +use tungstenite::{ClientRequestBuilder, WebSocket}; + +#[derive(Debug)] +pub enum Transport { + Fake, + Udp(UdpSocket), + WebSocket(Mutex>>), +} + +impl Transport { + pub fn connect(kind: TransportType, destination: &str) -> Transport { + match kind { + TransportType::Udp => { + Self::Udp(UdpSocket::bind_connect(destination).expect("failed to bind socket")) + } + TransportType::WebSocket => { + let request = ClientRequestBuilder::new( + destination.parse().expect("Invalid destination url"), + ) + .into_client_request() + .unwrap(); + let (sock, _) = + tungstenite::connect(request).expect("failed to connect to websocket"); + Self::WebSocket(Mutex::new(sock)) + } + TransportType::Fake => Self::Fake, + } + } + + pub(crate) fn send_command>(&self, command: T) -> Option<()> + where + >::Error: Debug, + { + match self { + Self::Udp(socket) => socket.send_command(command), + Self::WebSocket(socket) => { + let bytes: Vec = command.try_into().unwrap().into(); + let mut socket = socket.lock().unwrap(); + socket.send(tungstenite::Message::Binary(bytes.into())).ok() + } + Self::Fake => FakeConnection.send_command(command), + } + } +}