diff --git a/Cargo.lock b/Cargo.lock index c1e7fb8..e1aaadd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "anstream" version = "0.6.21" @@ -58,30 +82,176 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[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 = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +dependencies = [ + "arrayvec", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.11.1" @@ -95,6 +265,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -144,6 +316,12 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[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.4" @@ -189,6 +367,55 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +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 = "dirs" version = "6.0.0" @@ -221,6 +448,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -236,6 +469,26 @@ dependencies = [ "cfg-if", ] +[[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" @@ -252,18 +505,78 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -294,6 +607,12 @@ dependencies = [ "percent-encoding", ] +[[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" @@ -406,6 +725,16 @@ dependencies = [ "wasip2", ] +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "h2" version = "0.4.13" @@ -425,6 +754,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -636,6 +976,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icy_sixel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85518b9086bf01117761b90e7691c0ef3236fa8adfb1fb44dd248fe5f87215d5" +dependencies = [ + "quantette", + "thiserror", +] + [[package]] name = "idna" version = "1.1.0" @@ -657,6 +1007,46 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.12", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + [[package]] name = "indexmap" version = "2.13.0" @@ -680,6 +1070,17 @@ dependencies = [ "web-time", ] +[[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 = "ipnet" version = "2.11.0" @@ -702,12 +1103,31 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -724,11 +1144,27 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] [[package]] name = "libloading" @@ -740,6 +1176,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.12" @@ -778,43 +1220,154 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "memchr" -version = "2.7.6" +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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 = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[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 = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "mime" -version = "0.3.17" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] [[package]] -name = "mio" -version = "1.1.1" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "autocfg", + "libm", ] [[package]] @@ -885,6 +1438,39 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "bytemuck", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -914,6 +1500,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -938,6 +1530,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -953,6 +1558,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -962,6 +1576,69 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quantette" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c98fecda8b16396ff9adac67644a523dd1778c42b58606a29df5c31ca925d174" +dependencies = [ + "bitvec", + "bytemuck", + "image", + "libm", + "num-traits", + "ordered-float", + "palette", + "rand", + "rand_xoshiro", + "rayon", + "ref-cast", + "wide", +] + +[[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.44" @@ -977,6 +1654,120 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -997,6 +1788,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "reqwest" version = "0.12.28" @@ -1037,6 +1848,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + [[package]] name = "ring" version = "0.17.14" @@ -1109,6 +1926,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "safe_arch" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629516c85c29fe757770fa03f2074cf1eac43d44c02a3de9fc2ef7b0e207dfdd" +dependencies = [ + "bytemuck", +] + [[package]] name = "schannel" version = "0.1.28" @@ -1227,6 +2053,21 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[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.12" @@ -1260,11 +2101,15 @@ name = "steamfetch" version = "0.4.0" dependencies = [ "anyhow", + "base64", "clap", "colored", "dirs", "futures", + "icy_sixel", + "image", "indicatif", + "libc", "libloading", "reqwest", "serde", @@ -1357,6 +2202,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.24.0" @@ -1390,6 +2241,20 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -1614,6 +2479,17 @@ 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" @@ -1723,6 +2599,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ca908d26e4786149c48efcf6c0ea09ab0e06d1fe3c17dc1b4b0f1ca4a7e788" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -1944,6 +2836,21 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yoke" version = "0.8.1" @@ -1967,6 +2874,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -2032,3 +2959,42 @@ name = "zmij" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" +dependencies = [ + "zune-core 0.5.1", +] diff --git a/Cargo.toml b/Cargo.toml index fd13dbc..11b7b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,7 @@ anyhow = "1" steamworks = { version = "0.12", features = ["raw-bindings"] } libloading = "0.8" indicatif = "0.17" +image = "0.25" +icy_sixel = "0.5" +base64 = "0.22.1" +libc = "0.2.182" diff --git a/README.md b/README.md index 26a370f..c89e3a6 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,18 @@ export STEAM_ID="your_steam_id_here" # Display your Steam stats steamfetch +# Show profile avatar image instead of ASCII logo +steamfetch --image + +# Specify image protocol (auto, kitty, iterm, sixel) +steamfetch --image --image-protocol sixel + # Demo mode (no API key required) steamfetch --demo +# Demo mode with image +steamfetch --demo --image + # Show config file path steamfetch --config-path @@ -110,8 +119,23 @@ steamfetch --help - Recently played games (last 2 weeks) - Rarest achievement display - Beautiful SteamOS ASCII art with gradient colors +- **Image display**: Show your Steam avatar with `--image` flag - Demo mode for testing without API setup +### Image Display + +Use `--image` to show your Steam profile avatar instead of the ASCII logo. + +Supported protocols: +- **Sixel** - Windows Terminal, WezTerm, foot, mlterm, xterm +- **Kitty** - Kitty terminal +- **iTerm2** - iTerm2 +- **Block characters** - Fallback for unsupported terminals + +Protocol is auto-detected by default. Use `--image-protocol` to override. + +Images are cached locally at `~/.cache/steamfetch/images/`. + ## How It Works ### With Steam Client Running diff --git a/src/display.rs b/src/display.rs index 79a3c3e..b1d4a13 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,14 +1,76 @@ use colored::Colorize; +use std::io::{self, Write}; +use crate::image_display; use crate::steam::SteamStats; +use crate::ImageProtocol; -pub fn render(stats: &SteamStats) { +const IMAGE_COLS: u32 = 34; +const IMAGE_ROWS: u32 = 18; + +pub struct ImageConfig { + pub enabled: bool, + pub protocol: ImageProtocol, +} + +pub async fn render(stats: &SteamStats, image_config: &ImageConfig) { let info_lines = build_info_lines(stats); + + if image_config.enabled { + render_with_image(stats, &info_lines, image_config).await; + } else { + render_with_ascii(&info_lines); + } +} + +async fn render_with_image(stats: &SteamStats, info_lines: &[String], config: &ImageConfig) { + let avatar = match &stats.avatar_url { + Some(url) => { + let cache_key = format!("avatar_{}.png", stats.username); + image_display::load_cached_or_download(url, &cache_key).await + } + None => None, + }; + + let Some(img) = avatar else { + return render_with_ascii(info_lines); + }; + + println!(); + + // Print image and rewind cursor to top-left of image area + let image_rows = + image_display::print_image_and_rewind(&img, &config.protocol, IMAGE_COLS, IMAGE_ROWS); + + let Some(image_rows) = image_rows else { + return render_with_ascii(info_lines); + }; + + let col_offset = IMAGE_COLS + 3; // image width + gap + let mut stdout = io::stdout().lock(); + + // Print info lines to the right of the image + for (i, line) in info_lines.iter().enumerate() { + if i > 0 { + writeln!(stdout).unwrap(); + } + image_display::cursor_right(col_offset); + write!(stdout, "{}", line).unwrap(); + } + + // Ensure we end up below the image area + let extra = (image_rows as usize).saturating_sub(info_lines.len()); + for _ in 0..=extra { + writeln!(stdout).unwrap(); + } + stdout.flush().unwrap(); +} + +fn render_with_ascii(info_lines: &[String]) { let logo_lines = build_logo(); println!(); for (i, logo_line) in logo_lines.iter().enumerate() { - // Offset info by 1 line to align vertically let info = if i == 0 { "" } else { diff --git a/src/image_display.rs b/src/image_display.rs new file mode 100644 index 0000000..02bcf45 --- /dev/null +++ b/src/image_display.rs @@ -0,0 +1,367 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use image::DynamicImage; +use std::fs; +use std::io::{self, Cursor, Write}; +use std::path::PathBuf; + +use crate::ImageProtocol; + +const KITTY_CHUNK_SIZE: usize = 4096; + +pub async fn load_cached_or_download(url: &str, cache_key: &str) -> Option { + let safe_key = sanitize_cache_key(cache_key); + if let Some(img) = load_from_cache(&safe_key) { + return Some(img); + } + let img = download_image(url).await?; + save_to_cache(&safe_key, &img); + Some(img) +} + +fn sanitize_cache_key(key: &str) -> String { + key.chars() + .map(|c| { + if c.is_alphanumeric() || c == '_' || c == '-' || c == '.' { + c + } else { + '_' + } + }) + .collect() +} + +/// Print image and rewind cursor to the top of the image (like fastfetch). +/// Returns the number of terminal rows the image occupies. +pub fn print_image_and_rewind( + img: &DynamicImage, + protocol: &ImageProtocol, + cols: u32, + rows: u32, +) -> Option { + let resolved = resolve_protocol(protocol); + + if matches!(resolved, ResolvedProtocol::Block) { + let term_rows = print_block(img, cols).ok()?; + print!("\x1b[1G\x1b[{}A", term_rows); + return Some(term_rows); + } + + // For graphic protocols, we need accurate cell pixel size + let (cell_w, cell_h) = query_cell_size(); + let pixel_w = cols * cell_w; + let pixel_h = rows * cell_h; + + let resized = img.resize_exact(pixel_w, pixel_h, image::imageops::FilterType::Lanczos3); + let rgba = resized.to_rgba8(); + let (w, h) = (rgba.width(), rgba.height()); + let raw = rgba.into_raw(); + + let result = match resolved { + ResolvedProtocol::Kitty => print_kitty(&raw, w, h), + ResolvedProtocol::Iterm => print_iterm(&resized, w, h), + ResolvedProtocol::Sixel => print_sixel(&raw, w as usize, h as usize), + ResolvedProtocol::Block => unreachable!(), + }; + result.ok()?; + + // Rewind cursor to top-left of image area (same as fastfetch) + let image_rows = rows; + print!("\x1b[1G\x1b[{}A", image_rows); + io::stdout().flush().ok(); + Some(image_rows) +} + +/// Move cursor right by `cols` columns. +pub fn cursor_right(cols: u32) { + if cols > 0 { + print!("\x1b[{}C", cols); + } +} + +// --- Terminal size detection --- + +/// Returns (cell_width, cell_height) in pixels. +/// Tries ioctl first, then ESC[14t query, then defaults. +fn query_cell_size() -> (u32, u32) { + #[cfg(unix)] + { + if let Some(size) = query_cell_size_ioctl() { + return size; + } + if let Some(size) = query_cell_size_escape() { + return size; + } + } + (10, 20) // conservative default +} + +#[cfg(unix)] +fn query_cell_size_ioctl() -> Option<(u32, u32)> { + unsafe { + let mut ws = std::mem::MaybeUninit::::zeroed().assume_init(); + if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut ws) == 0 + && ws.ws_xpixel > 0 + && ws.ws_ypixel > 0 + && ws.ws_col > 0 + && ws.ws_row > 0 + { + return Some(( + ws.ws_xpixel as u32 / ws.ws_col as u32, + ws.ws_ypixel as u32 / ws.ws_row as u32, + )); + } + } + None +} + +/// Query terminal pixel size via ESC[14t → ESC[4;{height};{width}t +/// Then get rows/cols via ioctl to compute cell size. +#[cfg(unix)] +fn query_cell_size_escape() -> Option<(u32, u32)> { + use std::os::unix::io::AsRawFd; + + let tty = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty") + .ok()?; + let fd = tty.as_raw_fd(); + + // Get rows/cols from ioctl (these usually work even when pixel size doesn't) + let (rows, cols) = unsafe { + let mut ws = std::mem::MaybeUninit::::zeroed().assume_init(); + if libc::ioctl(fd, libc::TIOCGWINSZ, &mut ws) != 0 || ws.ws_row == 0 || ws.ws_col == 0 { + return None; + } + (ws.ws_row as u32, ws.ws_col as u32) + }; + + // Save terminal state and switch to raw mode for reading response + let mut orig_termios = std::mem::MaybeUninit::::uninit(); + unsafe { + if libc::tcgetattr(fd, orig_termios.as_mut_ptr()) != 0 { + return None; + } + } + let orig_termios = unsafe { orig_termios.assume_init() }; + let mut raw = orig_termios; + raw.c_lflag &= !(libc::ICANON | libc::ECHO); + raw.c_cc[libc::VMIN] = 0; + raw.c_cc[libc::VTIME] = 1; // 100ms timeout + unsafe { + libc::tcsetattr(fd, libc::TCSANOW, &raw); + } + + // Send ESC[14t to query pixel size + let query = b"\x1b[14t"; + let written = unsafe { libc::write(fd, query.as_ptr() as *const _, query.len()) }; + if written < 0 { + unsafe { libc::tcsetattr(fd, libc::TCSANOW, &orig_termios) }; + return None; + } + + // Read response: ESC[4;{height};{width}t + let mut buf = [0u8; 64]; + let mut pos = 0; + loop { + let mut byte = [0u8; 1]; + let n = unsafe { libc::read(fd, byte.as_mut_ptr() as *mut _, 1) }; + if n <= 0 || pos >= buf.len() { + break; + } + buf[pos] = byte[0]; + pos += 1; + if byte[0] == b't' { + break; + } + } + + // Restore terminal state + unsafe { + libc::tcsetattr(fd, libc::TCSANOW, &orig_termios); + } + + // Parse ESC[4;{height};{width}t + let response = std::str::from_utf8(&buf[..pos]).ok()?; + let inner = response.strip_prefix("\x1b[4;")?; + let inner = inner.strip_suffix('t')?; + let mut parts = inner.split(';'); + let ypixel: u32 = parts.next()?.parse().ok()?; + let xpixel: u32 = parts.next()?.parse().ok()?; + + if xpixel > 0 && ypixel > 0 { + Some((xpixel / cols, ypixel / rows)) + } else { + None + } +} + +// --- Protocol detection --- + +#[derive(Clone, Copy)] +enum ResolvedProtocol { + Kitty, + Iterm, + Sixel, + Block, +} + +fn resolve_protocol(protocol: &ImageProtocol) -> ResolvedProtocol { + match protocol { + ImageProtocol::Kitty => ResolvedProtocol::Kitty, + ImageProtocol::Iterm => ResolvedProtocol::Iterm, + ImageProtocol::Sixel => ResolvedProtocol::Sixel, + ImageProtocol::Auto => detect_protocol(), + } +} + +fn detect_protocol() -> ResolvedProtocol { + if std::env::var("KITTY_WINDOW_ID").is_ok() { + return ResolvedProtocol::Kitty; + } + if std::env::var("ITERM_SESSION_ID").is_ok() { + return ResolvedProtocol::Iterm; + } + if is_sixel_capable_terminal() { + return ResolvedProtocol::Sixel; + } + ResolvedProtocol::Block +} + +fn is_sixel_capable_terminal() -> bool { + if std::env::var("WT_SESSION").is_ok() { + return true; + } + let sixel_programs = ["WezTerm", "foot", "mlterm", "contour", "Black Box"]; + if let Ok(prog) = std::env::var("TERM_PROGRAM") { + if sixel_programs.iter().any(|&s| prog.contains(s)) { + return true; + } + } + if let Ok(term) = std::env::var("LC_TERMINAL") { + if term.contains("iTerm2") { + return true; + } + } + if std::env::var("XTERM_VERSION").is_ok() { + return true; + } + false +} + +// --- Protocol output --- + +fn print_kitty(rgba: &[u8], w: u32, h: u32) -> io::Result<()> { + let encoded = STANDARD.encode(rgba); + let chunks: Vec<&str> = encoded + .as_bytes() + .chunks(KITTY_CHUNK_SIZE) + .map(|c| std::str::from_utf8(c).unwrap_or("")) + .collect(); + + let mut stdout = io::stdout().lock(); + for (i, chunk) in chunks.iter().enumerate() { + let more = if i < chunks.len() - 1 { 1 } else { 0 }; + if i == 0 { + write!( + stdout, + "\x1b_Ga=T,f=32,s={},v={},m={};{}\x1b\\", + w, h, more, chunk + )?; + } else { + write!(stdout, "\x1b_Gm={};{}\x1b\\", more, chunk)?; + } + } + stdout.flush() +} + +fn print_iterm(img: &DynamicImage, _w: u32, _h: u32) -> io::Result<()> { + let mut png_buf = Cursor::new(Vec::new()); + img.write_to(&mut png_buf, image::ImageFormat::Png) + .map_err(io::Error::other)?; + let encoded = STANDARD.encode(png_buf.into_inner()); + + let mut stdout = io::stdout().lock(); + write!( + stdout, + "\x1b]1337;File=inline=1;preserveAspectRatio=1:{}\x07", + encoded + )?; + stdout.flush() +} + +fn print_sixel(rgba: &[u8], w: usize, h: usize) -> io::Result<()> { + let sixel_img = icy_sixel::SixelImage::from_rgba(rgba.to_vec(), w, h); + let encoded = sixel_img.encode().map_err(io::Error::other)?; + + let mut stdout = io::stdout().lock(); + write!(stdout, "{}", encoded)?; + stdout.flush() +} + +fn print_block(img: &DynamicImage, max_cols: u32) -> io::Result { + let rgba = img.to_rgba8(); + let (w, h) = (rgba.width(), rgba.height()); + + let scale = (max_cols as f64) / (w as f64); + let scaled_w = max_cols; + let scaled_h = ((h as f64) * scale) as u32; + + let resized = img.resize_exact(scaled_w, scaled_h, image::imageops::FilterType::Lanczos3); + let rgba = resized.to_rgba8(); + let (rw, rh) = (rgba.width(), rgba.height()); + + let mut stdout = io::stdout().lock(); + let term_rows = rh.div_ceil(2); + + for row in 0..term_rows { + let top_y = row * 2; + let bot_y = row * 2 + 1; + for x in 0..rw { + let top = rgba.get_pixel(x, top_y); + let bot = if bot_y < rh { + rgba.get_pixel(x, bot_y) + } else { + &image::Rgba([0, 0, 0, 0]) + }; + write!( + stdout, + "\x1b[38;2;{};{};{}m\x1b[48;2;{};{};{}m▄", + bot[0], bot[1], bot[2], top[0], top[1], top[2] + )?; + } + writeln!(stdout, "\x1b[0m")?; + } + stdout.flush()?; + Ok(term_rows) +} + +// --- Cache helpers --- + +async fn download_image(url: &str) -> Option { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(10)) + .build() + .ok()?; + let bytes = client.get(url).send().await.ok()?.bytes().await.ok()?; + image::load_from_memory(&bytes).ok() +} + +fn cache_dir() -> Option { + dirs::cache_dir().map(|p| p.join("steamfetch").join("images")) +} + +fn load_from_cache(key: &str) -> Option { + let path = cache_dir()?.join(key); + let bytes = fs::read(path).ok()?; + image::load_from_memory(&bytes).ok() +} + +fn save_to_cache(key: &str, img: &DynamicImage) { + let Some(dir) = cache_dir() else { return }; + let _ = fs::create_dir_all(&dir); + let path = dir.join(key); + let mut buf = Cursor::new(Vec::new()); + let _ = img.write_to(&mut buf, image::ImageFormat::Png); + let _ = fs::write(path, buf.into_inner()); +} diff --git a/src/main.rs b/src/main.rs index 90464bf..75c6988 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,24 @@ mod cache; mod config; mod display; +mod image_display; mod steam; use anyhow::Result; -use clap::Parser; +use clap::{Parser, ValueEnum}; use std::path::PathBuf; use config::Config; use steam::{NativeSteamClient, SteamClient}; +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum ImageProtocol { + Auto, + Kitty, + Iterm, + Sixel, +} + #[derive(Parser)] #[command(name = "steamfetch")] #[command(about = "neofetch for Steam - Display your Steam stats in terminal")] @@ -34,6 +43,14 @@ struct Cli { /// Request timeout in seconds (default: 30) #[arg(long, value_name = "SECONDS", default_value = "30", value_parser = clap::value_parser!(u64).range(1..))] timeout: u64, + + /// Show profile avatar as image instead of ASCII logo + #[arg(long)] + image: bool, + + /// Image display protocol (auto, kitty, iterm, sixel) + #[arg(long, value_enum, default_value = "auto")] + image_protocol: ImageProtocol, } #[tokio::main] @@ -48,13 +65,18 @@ async fn main() -> Result<()> { return Ok(()); } + let image_config = display::ImageConfig { + enabled: cli.image, + protocol: cli.image_protocol, + }; + let stats = if cli.demo { demo_stats() } else { fetch_stats(&cli).await? }; - display::render(&stats); + display::render(&stats, &image_config).await; Ok(()) } @@ -145,5 +167,6 @@ fn demo_stats() -> steam::SteamStats { playtime_minutes: 480, }, ], + avatar_url: None, } } diff --git a/src/steam/client.rs b/src/steam/client.rs index f81acd1..f032035 100644 --- a/src/steam/client.rs +++ b/src/steam/client.rs @@ -88,6 +88,7 @@ impl SteamClient { account_created: player.timecreated, steam_level, recently_played, + avatar_url: player.avatarfull, }) } @@ -147,6 +148,7 @@ impl SteamClient { account_created: player.timecreated, steam_level, recently_played, + avatar_url: player.avatarfull, }) } diff --git a/src/steam/models.rs b/src/steam/models.rs index 1511eff..525f3fc 100644 --- a/src/steam/models.rs +++ b/src/steam/models.rs @@ -61,6 +61,7 @@ pub struct PlayerSummaryData { pub struct Player { pub personaname: String, pub timecreated: Option, + pub avatarfull: Option, } // Achievements API @@ -140,6 +141,7 @@ pub struct SteamStats { pub account_created: Option, pub steam_level: Option, pub recently_played: Vec, + pub avatar_url: Option, } #[derive(Debug)]