diff --git a/Cargo.lock b/Cargo.lock index 27b6878..6155b93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" dependencies = [ "ab_glyph_rasterizer", - "owned_ttf_parser", + "owned_ttf_parser 0.25.0", ] [[package]] @@ -117,6 +117,37 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "android-activity" version = "0.5.2" @@ -131,9 +162,9 @@ dependencies = [ "jni-sys", "libc", "log", - "ndk", + "ndk 0.8.0", "ndk-context", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "num_enum", "thiserror", ] @@ -153,6 +184,27 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "arboard" version = "3.6.0" @@ -169,6 +221,17 @@ dependencies = [ "x11rb", ] +[[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 2.0.104", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -503,6 +566,29 @@ 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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42" +dependencies = [ + "arrayvec", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -536,6 +622,12 @@ 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 = "block" version = "0.1.6" @@ -611,6 +703,12 @@ dependencies = [ "piper", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + [[package]] name = "bumpalo" version = "3.19.0" @@ -724,6 +822,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[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.1" @@ -876,6 +984,46 @@ dependencies = [ "libc", ] +[[package]] +name = "coreaudio-rs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17" +dependencies = [ + "bitflags 1.3.2", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "cpal" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd307f43cc2a697e2d1f8bc7a1d824b5269e052209e28883e5bc04d095aaa3f" +dependencies = [ + "alsa", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk 0.9.0", + "ndk-context", + "num-derive", + "num-traits", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -941,6 +1089,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "derivative" version = "2.2.0" @@ -1141,6 +1295,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -1197,6 +1360,26 @@ dependencies = [ "parking_lot", ] +[[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 2.0.104", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1272,6 +1455,12 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "fastrand" version = "1.9.0" @@ -1464,8 +1653,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1664,6 +1855,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1844,10 +2041,55 @@ 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 = "imageproc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" +dependencies = [ + "ab_glyph", + "approx", + "getrandom 0.2.16", + "image 0.25.6", + "itertools", + "nalgebra", + "num", + "rand", + "rand_distr", + "rayon", +] + +[[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" @@ -1867,6 +2109,17 @@ dependencies = [ "cfg-if", ] +[[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 2.0.104", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1889,6 +2142,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.21.1" @@ -1957,6 +2219,12 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lebe" version = "0.5.2" @@ -1969,6 +2237,16 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.7.4" @@ -1989,6 +2267,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.4" @@ -2046,6 +2330,24 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" 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 = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2055,6 +2357,26 @@ dependencies = [ "libc", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[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.5" @@ -2103,6 +2425,12 @@ dependencies = [ "paste", ] +[[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" @@ -2134,6 +2462,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + [[package]] name = "ndk" version = "0.8.0" @@ -2143,13 +2486,27 @@ dependencies = [ "bitflags 2.9.1", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "num_enum", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", "thiserror", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror", +] + [[package]] name = "ndk-context" version = "0.1.1" @@ -2165,6 +2522,21 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[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.26.4" @@ -2196,6 +2568,46 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[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 = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[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-complex" version = "0.4.6" @@ -2205,6 +2617,48 @@ dependencies = [ "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 2.0.104", +] + +[[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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "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" @@ -2212,6 +2666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2336,6 +2791,43 @@ dependencies = [ "objc2-foundation 0.3.1", ] +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cbe18d879e20a4aea544f8befe38bcf52255eb63d3f23eca2842f3319e4c07" +dependencies = [ + "bitflags 2.9.1", + "libc", + "objc2 0.6.1", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca44961e888e19313b808f23497073e3f6b3c22bb485056674c8b49f3b025c82" +dependencies = [ + "dispatch2", + "objc2 0.6.1", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -2498,13 +2990,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "owned_ttf_parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +dependencies = [ + "ttf-parser 0.15.2", +] + [[package]] name = "owned_ttf_parser" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ - "ttf-parser", + "ttf-parser 0.25.1", ] [[package]] @@ -2684,6 +3185,19 @@ 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 2.0.104", +] [[package]] name = "qoi" @@ -2694,6 +3208,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -2748,6 +3268,66 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[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", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "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 = "raw-window-handle" version = "0.5.2" @@ -2760,6 +3340,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -2856,6 +3442,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + [[package]] name = "robot36-encoder" version = "0.2.1" @@ -2867,6 +3459,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rodio" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40ecf59e742e03336be6a3d53755e789fd05a059fa22dfa0ed624722319e183" +dependencies = [ + "cpal", + "dasp_sample", + "num-rational", + "symphonia", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2919,12 +3523,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rusttype" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser 0.15.2", +] + [[package]] name = "rustversion" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2990,6 +3613,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3016,12 +3648,34 @@ dependencies = [ "libc", ] +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[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" @@ -3141,8 +3795,11 @@ dependencies = [ "env_logger", "hound", "image 0.24.9", + "imageproc", "rfd", "robot36-encoder", + "rodio", + "rusttype", ] [[package]] @@ -3163,6 +3820,153 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" @@ -3196,6 +4000,25 @@ dependencies = [ "syn 2.0.104", ] +[[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 = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.20.0" @@ -3284,11 +4107,26 @@ dependencies = [ "zerovec", ] +[[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 0.22.27", +] + [[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" @@ -3308,6 +4146,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow 0.7.12", ] @@ -3343,6 +4183,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -3423,6 +4269,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[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 = "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" @@ -3784,7 +4647,7 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot", @@ -3811,6 +4674,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.0" @@ -3865,7 +4738,17 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", "windows-targets 0.52.6", ] @@ -3878,6 +4761,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-implement" version = "0.48.0" @@ -3900,6 +4793,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -4135,8 +5037,8 @@ dependencies = [ "libc", "log", "memmap2", - "ndk", - "ndk-sys", + "ndk 0.8.0", + "ndk-sys 0.5.0+25.2.9519653", "objc2 0.4.1", "once_cell", "orbclient", @@ -4495,6 +5397,12 @@ dependencies = [ "syn 2.0.104", ] +[[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" @@ -4504,6 +5412,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index 52e59ec..fae355e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ robot36-encoder = { version = "0.2", features = ["image"] } image = "0.24" hound = "3.5" env_logger = "0.10" +rodio = "0.21.1" +imageproc = "0.25.0" +rusttype = "0.9.3" diff --git a/font8x8_basic.bin b/font8x8_basic.bin new file mode 100644 index 0000000..2116412 --- /dev/null +++ b/font8x8_basic.bin @@ -0,0 +1,16 @@ +00000000 00 00 00 00 00 00 00 00 18 18 18 18 18 00 18 00 +00000010 6c 6c 6c 00 00 00 00 00 6c 6c fe 6c fe 6c 6c 00 +00000020 18 3e 60 3c 06 7c 18 00 00 c6 cc 18 30 66 c6 00 +00000030 38 6c 38 76 dc cc 76 00 18 18 30 00 00 00 00 00 +00000040 0c 18 30 30 30 18 0c 00 30 18 0c 0c 0c 18 30 00 +00000050 00 66 3c ff 3c 66 00 00 00 18 18 7e 18 18 00 00 +00000060 00 00 00 00 18 18 30 00 00 00 00 7e 00 00 00 00 +00000070 00 00 00 00 00 18 18 00 06 0c 18 30 60 c0 80 00 +00000080 38 6c c6 c6 c6 6c 38 00 18 38 18 18 18 18 7e 00 +00000090 7c c6 06 1c 30 66 fe 00 7c c6 06 3c 06 c6 7c 00 +000000a0 1c 3c 6c cc fe 0c 1e 00 fe c0 fc 06 06 c6 7c 00 +000000b0 38 60 c0 fc c6 c6 7c 00 fe c6 0c 18 30 30 30 00 +000000c0 7c c6 c6 7c c6 c6 7c 00 7c c6 c6 7e 06 0c 78 00 +000000d0 00 18 18 00 00 18 18 00 00 18 18 00 00 18 18 30 +000000e0 0c 18 30 60 30 18 0c 00 00 00 7e 00 7e 00 00 00 +000000f0 30 18 0c 06 0c 18 30 00 7c c6 0c 18 18 00 18 00 diff --git a/src/main.rs b/src/main.rs index a979cb9..bab23b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,49 @@ use eframe::egui; use robot36_encoder::{Encoder, Robot36Image}; use std::path::PathBuf; -use std::time::{Duration, Instant}; +use std::time::Instant; use std::thread; use std::sync::mpsc::{self, Receiver, Sender}; use hound::{WavSpec, WavWriter, SampleFormat}; +use image::{Rgba, RgbaImage}; + +// Simple 8x8 bitmap font implementation +mod builtin_font { + // Each character is 8 bytes (8x8 pixels) + const FONT_DATA: &[u8] = include_bytes!("../font8x8_basic.bin"); + + pub fn draw_text( + image: &mut RgbaImage, + x: u32, + y: u32, + text: &str, + color: Rgba, + ) { + let char_width = 8; + let char_height = 8; + + for (i, c) in text.chars().enumerate() { + let char_index = c as usize; + if char_index >= 128 { + continue; + } + + let font_data = &FONT_DATA[char_index * char_width..(char_index + 1) * char_width]; + + for row in 0..char_height { + for col in 0..char_width { + if font_data[row as usize] & (1 << (7 - col)) != 0 { + let px = x + (i as u32 * char_width) + col; + let py = y + row; + if px < image.width() && py < image.height() { + image.put_pixel(px, py, color); + } + } + } + } + } + } +} #[derive(Debug, Clone)] enum EncodingState { @@ -23,24 +62,17 @@ enum ProgressMessage { } struct Robot36EncoderApp { - // File paths input_file: Option, output_file: Option, - - // Encoding state encoding_state: EncodingState, progress: f32, - - // Audio settings sample_rate: u32, - - // Threading + callsign: String, encoding_thread: Option>, progress_receiver: Option>, - - // UI state show_success_dialog: bool, error_message: Option, + settings_open: bool, } impl Default for Robot36EncoderApp { @@ -51,10 +83,12 @@ impl Default for Robot36EncoderApp { encoding_state: EncodingState::Idle, progress: 0.0, sample_rate: 48000, + callsign: String::new(), encoding_thread: None, progress_receiver: None, show_success_dialog: false, error_message: None, + settings_open: false, } } } @@ -71,13 +105,9 @@ impl Robot36EncoderApp { .pick_file() { self.input_file = Some(path.clone()); - - // Auto-generate output filename let mut output_path = path.clone(); output_path.set_extension("wav"); self.output_file = Some(output_path); - - // Reset state self.encoding_state = EncodingState::Idle; self.progress = 0.0; self.error_message = None; @@ -88,7 +118,12 @@ impl Robot36EncoderApp { if let Some(path) = rfd::FileDialog::new() .add_filter("Audio Files", &["wav"]) .set_title("Save Audio Output") - .set_file_name("output.wav") + .set_file_name( + self.input_file.as_ref() + .and_then(|p| p.file_stem()) + .map(|s| format!("{}.wav", s.to_string_lossy())) + .unwrap_or_else(|| "output.wav".to_string()) + ) .save_file() { self.output_file = Some(path); @@ -107,9 +142,10 @@ impl Robot36EncoderApp { let input_path = input_path.clone(); let output_path = output_path.clone(); let sample_rate = self.sample_rate; + let callsign = self.callsign.clone(); self.encoding_thread = Some(thread::spawn(move || { - Self::encode_image_thread(input_path, output_path, sample_rate, tx); + Self::encode_image_thread(input_path, output_path, sample_rate, callsign, tx); })); } } @@ -118,9 +154,10 @@ impl Robot36EncoderApp { input_path: PathBuf, output_path: PathBuf, sample_rate: u32, + callsign: String, tx: Sender, ) { - let result = Self::encode_image_impl(input_path, output_path.clone(), sample_rate, &tx); + let result = Self::encode_image_impl(input_path, output_path.clone(), sample_rate, callsign, &tx); match result { Ok(()) => { @@ -136,31 +173,70 @@ impl Robot36EncoderApp { input_path: PathBuf, output_path: PathBuf, sample_rate: u32, + callsign: String, tx: &Sender, ) -> Result<(), Box> { - // Load and process image let _ = tx.send(ProgressMessage::Progress(0.1)); - let image = image::open(&input_path)?; + // Load image and convert to RGBA + let mut image = image::open(&input_path)?.to_rgba8(); let _ = tx.send(ProgressMessage::Progress(0.2)); - // Resize image to Robot36 specifications (320x240) - let resized_image = image.resize(320, 240, image::imageops::FilterType::Lanczos3); + // Add callsign overlay if provided + if !callsign.is_empty() { + // Draw black background for text + for y in 0..16 { + for x in 0..(callsign.len() as u32 * 8).min(image.width()) { + if x < image.width() && y < image.height() { + image.put_pixel(x, y, Rgba([0u8, 0u8, 0u8, 200u8])); + } + } + } + + // Draw text + builtin_font::draw_text( + &mut image, + 5, 5, // x, y position + &callsign, + Rgba([255u8, 255u8, 255u8, 255u8]), // white text + ); + } + + // Calculate target size while maintaining aspect ratio + let target_width = 320; + let target_height = 240; + + let (width, height) = image.dimensions(); + let ratio = width as f32 / height as f32; + let (new_width, new_height) = if ratio > (target_width as f32 / target_height as f32) { + (target_width, (target_width as f32 / ratio) as u32) + } else { + ((target_height as f32 * ratio) as u32, target_height) + }; + + // Resize with padding to maintain 320x240 + let mut resized_image = image::DynamicImage::new_rgb8(target_width, target_height); + let temp_image = image::DynamicImage::ImageRgba8(image) + .resize_exact(new_width, new_height, image::imageops::FilterType::Lanczos3); + + // Center the image + let x_offset = (target_width - new_width) / 2; + let y_offset = (target_height - new_height) / 2; + + image::imageops::overlay(&mut resized_image, &temp_image, x_offset as i64, y_offset as i64); + let _ = tx.send(ProgressMessage::Progress(0.3)); - // Convert to Robot36Image + // Convert to Robot36Image (original encoding logic untouched) let robot36_image = Robot36Image::from_image(resized_image)?; let _ = tx.send(ProgressMessage::Progress(0.4)); - // Create encoder let encoder = Encoder::new(robot36_image, sample_rate as u64); let _ = tx.send(ProgressMessage::Progress(0.5)); - // Encode to audio samples let samples: Vec = encoder.encode().collect(); let _ = tx.send(ProgressMessage::Progress(0.8)); - // Write to WAV file let spec = WavSpec { channels: 1, sample_rate, @@ -174,7 +250,6 @@ impl Robot36EncoderApp { for (i, sample) in samples.into_iter().enumerate() { writer.write_sample(sample)?; - // Update progress periodically if i % 10000 == 0 { let progress = 0.8 + (i as f32 / total_samples as f32) * 0.2; let _ = tx.send(ProgressMessage::Progress(progress)); @@ -192,8 +267,6 @@ impl Robot36EncoderApp { self.progress = 0.0; if let Some(handle) = self.encoding_thread.take() { - // Note: In a production app, you'd want to implement proper cancellation - // For now, we'll just detach the thread handle.join().ok(); } @@ -202,6 +275,7 @@ impl Robot36EncoderApp { fn update_progress(&mut self) { let mut should_clear_receiver = false; + let mut completed_path = None; if let Some(receiver) = &self.progress_receiver { while let Ok(message) = receiver.try_recv() { @@ -215,22 +289,17 @@ impl Robot36EncoderApp { } } ProgressMessage::Completed(output_path) => { - self.encoding_state = EncodingState::Completed { output_path }; + completed_path = Some(output_path); + self.encoding_state = EncodingState::Completed { output_path: completed_path.clone().unwrap() }; self.progress = 1.0; self.show_success_dialog = true; should_clear_receiver = true; - if let Some(handle) = self.encoding_thread.take() { - handle.join().ok(); - } } ProgressMessage::Error(error) => { self.encoding_state = EncodingState::Error(error.clone()); self.error_message = Some(error); self.progress = 0.0; should_clear_receiver = true; - if let Some(handle) = self.encoding_thread.take() { - handle.join().ok(); - } } } } @@ -238,13 +307,15 @@ impl Robot36EncoderApp { if should_clear_receiver { self.progress_receiver = None; + if let Some(handle) = self.encoding_thread.take() { + handle.join().ok(); + } } } fn get_remaining_time(&self) -> f32 { match &self.encoding_state { EncodingState::Encoding { start_time } => { - // Robot36 encoding typically takes about 45 seconds let _elapsed = start_time.elapsed().as_secs_f32(); let estimated_total = 45.0; let progress_based_remaining = (1.0 - self.progress) * estimated_total; @@ -263,231 +334,311 @@ impl Robot36EncoderApp { self.output_file.is_some() && !self.is_encoding() } + + fn open_with_vlc(&self, path: &PathBuf) { + let path_str = path.to_string_lossy().to_string(); + + #[cfg(target_os = "windows")] + { + let _ = std::process::Command::new("cmd") + .args(["/C", "start", "vlc", &path_str]) + .spawn(); + } + + #[cfg(target_os = "macos")] + { + let _ = std::process::Command::new("open") + .args(["-a", "VLC", &path_str]) + .spawn(); + } + + #[cfg(target_os = "linux")] + { + let _ = std::process::Command::new("vlc") + .arg(&path_str) + .spawn(); + } + } } impl eframe::App for Robot36EncoderApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.update_progress(); - // Success dialog + ctx.set_visuals(egui::Visuals { + dark_mode: true, + override_text_color: Some(egui::Color32::WHITE), + window_fill: egui::Color32::from_rgb(32, 32, 32), + panel_fill: egui::Color32::from_rgb(40, 40, 40), + ..egui::Visuals::dark() + }); + if self.show_success_dialog { - egui::Window::new("Encoding Complete") + let output_path = if let EncodingState::Completed { output_path } = &self.encoding_state { + output_path.clone() + } else { + PathBuf::new() + }; + + egui::Window::new("🎉 Encoding Complete!") .collapsible(false) .resizable(false) .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) .show(ctx, |ui| { - ui.label("Image successfully encoded to Robot36 audio!"); + ui.add_space(10.0); - if let EncodingState::Completed { output_path } = &self.encoding_state { - ui.label(format!("Output saved to: {}", output_path.display())); - } + ui.vertical_centered(|ui| { + ui.heading("✅ Success!"); + ui.add_space(5.0); + ui.label("Your image has been encoded to Robot36 audio format"); + }); + + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + + ui.label(format!("📁 Saved to: {}", output_path.display())); + + ui.add_space(15.0); ui.horizontal(|ui| { - if ui.button("OK").clicked() { - self.show_success_dialog = false; - } - - if let EncodingState::Completed { output_path } = &self.encoding_state { - if ui.button("Open Folder").clicked() { - if let Some(parent) = output_path.parent() { - let _ = std::process::Command::new("xdg-open") - .arg(parent) - .spawn(); - } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("Close").clicked() { + self.show_success_dialog = false; } - } + + if ui.button("đŸŽĩ Open with VLC").clicked() { + self.open_with_vlc(&output_path); + self.show_success_dialog = false; + } + + if ui.button("📂 Open Folder").clicked() { + if let Some(parent) = output_path.parent() { + #[cfg(target_os = "windows")] + { + let _ = std::process::Command::new("explorer") + .arg(parent) + .spawn(); + } + + #[cfg(target_os = "macos")] + { + let _ = std::process::Command::new("open") + .arg(parent) + .spawn(); + } + + #[cfg(target_os = "linux")] + { + let _ = std::process::Command::new("xdg-open") + .arg(parent) + .spawn(); + } + } + self.show_success_dialog = false; + } + }); }); }); } egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Robot36 Image Encoder"); - ui.label("Convert images to SSTV Robot36 audio format"); - ui.separator(); - - // File selection section - ui.group(|ui| { - ui.label("File Selection"); - ui.separator(); - - // Input file - ui.horizontal(|ui| { - if ui.button("Select Input Image").clicked() { - self.select_input_file(); - } - - if let Some(file_path) = &self.input_file { - ui.label(format!("Input: {}", - file_path.file_name() - .unwrap_or_default() - .to_string_lossy())); - } else { - ui.colored_label(egui::Color32::GRAY, "No input file selected"); - } - }); - - // Output file - ui.horizontal(|ui| { - if ui.button("Select Output Audio").clicked() { - self.select_output_file(); - } - - if let Some(file_path) = &self.output_file { - ui.label(format!("Output: {}", - file_path.file_name() - .unwrap_or_default() - .to_string_lossy())); - } else { - ui.colored_label(egui::Color32::GRAY, "No output file selected"); - } - }); - }); - - ui.add_space(10.0); - - // Audio settings - ui.group(|ui| { - ui.label("Audio Settings"); - ui.separator(); - - ui.horizontal(|ui| { - ui.label("Sample Rate:"); - ui.add(egui::Slider::new(&mut self.sample_rate, 8000..=96000) - .suffix(" Hz") - .logarithmic(false)); - }); - - ui.label("Robot36 will encode to 320x240 resolution"); - }); - - ui.add_space(10.0); - - // Progress section - ui.group(|ui| { - ui.label("Encoding Progress"); - ui.separator(); - - // Progress bar - let progress_text = match &self.encoding_state { - EncodingState::Idle => "Ready".to_string(), - EncodingState::Loading => "Loading image...".to_string(), - EncodingState::Encoding { .. } => format!("Encoding... {:.1}%", self.progress * 100.0), - EncodingState::Completed { .. } => "Completed".to_string(), - EncodingState::Error(_) => "Error".to_string(), - }; - - let progress_bar = egui::ProgressBar::new(self.progress) - .text(progress_text) - .show_percentage(); - ui.add(progress_bar); - - ui.add_space(10.0); - - // Time remaining display - if matches!(self.encoding_state, EncodingState::Encoding { .. }) { - let remaining_time = self.get_remaining_time(); - ui.horizontal(|ui| { - ui.label("Estimated time remaining:"); - ui.monospace(format!("{:.2} seconds", remaining_time)); - }); - } - - // Large LCD display - ui.separator(); - ui.vertical_centered(|ui| { - let remaining_time = self.get_remaining_time(); - let lcd_text = if self.is_encoding() { - format!("{:05.2}", remaining_time) - } else { - "45.00".to_string() - }; - - let lcd_display = egui::Label::new( - egui::RichText::new(lcd_text) - .size(48.0) - .monospace() - .color(if self.is_encoding() { - egui::Color32::from_rgb(0, 255, 0) - } else { - egui::Color32::from_rgb(100, 100, 100) - }) - ); - - // Add background - let response = ui.add(lcd_display); - let rect = response.rect; - ui.painter().rect_filled( - rect.expand(10.0), - egui::Rounding::same(5.0), - egui::Color32::from_rgb(20, 20, 20), - ); - }); - }); - - ui.add_space(10.0); - - // Control buttons ui.horizontal(|ui| { + ui.label("📡 Callsign:"); + ui.add( + egui::TextEdit::singleline(&mut self.callsign) + .hint_text("Enter your callsign (optional)") + .desired_width(150.0) + ); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let stop_button = ui.add_enabled( - self.is_encoding(), - egui::Button::new("Stop") - ); - - if stop_button.clicked() { - self.stop_encoding(); - } - - let start_button = ui.add_enabled( - self.can_start_encoding(), - egui::Button::new("Start Encoding") - ); - - if start_button.clicked() { - self.start_encoding(); + if ui.button("âš™ī¸ Settings").clicked() { + self.settings_open = !self.settings_open; } }); }); ui.add_space(10.0); + ui.separator(); + ui.add_space(20.0); - // Status messages - match &self.encoding_state { - EncodingState::Idle => { - if self.can_start_encoding() { - ui.colored_label(egui::Color32::BLUE, "Ready to encode"); + ui.vertical_centered(|ui| { + ui.heading("🤖 Robot36 SSTV Encoder"); + ui.label("Convert images to SSTV audio format"); + }); + + ui.add_space(30.0); + + ui.vertical_centered(|ui| { + egui::Frame::none() + .fill(egui::Color32::from_rgb(48, 48, 48)) + .rounding(egui::Rounding::same(12.0)) + .inner_margin(egui::Margin::same(20.0)) + .show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("📁 Select Files"); + ui.add_space(10.0); + + if ui.add_sized([300.0, 40.0], egui::Button::new("📷 Choose Image")).clicked() { + self.select_input_file(); + } + + if let Some(file_path) = &self.input_file { + ui.add_space(5.0); + ui.label(format!("📄 {}", file_path.file_name().unwrap_or_default().to_string_lossy())); + } + + ui.add_space(10.0); + + if ui.add_sized([300.0, 40.0], egui::Button::new("💾 Save Audio As")).clicked() { + self.select_output_file(); + } + + if let Some(file_path) = &self.output_file { + ui.add_space(5.0); + ui.label(format!("🔊 {}", file_path.file_name().unwrap_or_default().to_string_lossy())); + } + }); + }); + + ui.add_space(30.0); + + if self.is_encoding() || matches!(self.encoding_state, EncodingState::Completed { .. }) { + egui::Frame::none() + .fill(egui::Color32::from_rgb(48, 48, 48)) + .rounding(egui::Rounding::same(12.0)) + .inner_margin(egui::Margin::same(20.0)) + .show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("⚡ Encoding Progress"); + ui.add_space(15.0); + + let progress_bar = egui::ProgressBar::new(self.progress) + .text(format!("{:.1}%", self.progress * 100.0)) + .desired_width(300.0) + .desired_height(20.0); + ui.add(progress_bar); + + ui.add_space(10.0); + + let status_text = match &self.encoding_state { + EncodingState::Loading => "🔄 Loading image...", + EncodingState::Encoding { .. } => "đŸŽĩ Encoding to audio...", + EncodingState::Completed { .. } => "✅ Encoding complete!", + _ => "", + }; + ui.label(status_text); + + if self.is_encoding() { + let remaining = self.get_remaining_time(); + ui.label(format!("âąī¸ {:.1}s remaining", remaining)); + } + }); + }); + + ui.add_space(20.0); + } + + ui.horizontal(|ui| { + if self.is_encoding() { + if ui.add_sized([150.0, 50.0], egui::Button::new("âšī¸ Stop")).clicked() { + self.stop_encoding(); + } } else { - ui.colored_label(egui::Color32::GRAY, "Select input and output files to begin"); + let start_button = egui::Button::new("🚀 Start Encoding") + .fill(egui::Color32::from_rgb(0, 120, 215)); + + if ui.add_sized([200.0, 50.0], start_button).clicked() && self.can_start_encoding() { + self.start_encoding(); + } } - } - EncodingState::Loading => { - ui.colored_label(egui::Color32::YELLOW, "Loading and processing image..."); - } - EncodingState::Encoding { .. } => { - ui.colored_label(egui::Color32::YELLOW, "Encoding to Robot36 audio format..."); - } - EncodingState::Completed { output_path } => { - ui.colored_label(egui::Color32::GREEN, - format!("Successfully encoded to {}", output_path.display())); - } - EncodingState::Error(msg) => { - ui.colored_label(egui::Color32::RED, format!("Error: {}", msg)); - } - } - - // Error details - if let Some(error) = &self.error_message { - ui.add_space(5.0); - ui.group(|ui| { - ui.colored_label(egui::Color32::RED, "Error Details:"); - ui.monospace(error); }); - } + + ui.add_space(20.0); + + match &self.encoding_state { + EncodingState::Idle => { + if !self.can_start_encoding() { + ui.colored_label(egui::Color32::GRAY, "â„šī¸ Please select input and output files"); + } + } + EncodingState::Completed { output_path } => { + ui.colored_label(egui::Color32::GREEN, + format!("✅ Audio saved to: {}", output_path.file_name().unwrap_or_default().to_string_lossy())); + + ui.add_space(10.0); + ui.horizontal(|ui| { + if ui.button("đŸŽĩ Open with VLC").clicked() { + self.open_with_vlc(output_path); + } + + if ui.button("📂 Open Folder").clicked() { + if let Some(parent) = output_path.parent() { + #[cfg(target_os = "windows")] + { + let _ = std::process::Command::new("explorer") + .arg(parent) + .spawn(); + } + + #[cfg(target_os = "macos")] + { + let _ = std::process::Command::new("open") + .arg(parent) + .spawn(); + } + + #[cfg(target_os = "linux")] + { + let _ = std::process::Command::new("xdg-open") + .arg(parent) + .spawn(); + } + } + } + }); + } + EncodingState::Error(msg) => { + ui.colored_label(egui::Color32::RED, format!("❌ Error: {}", msg)); + } + _ => {} + } + }); }); - // Request repaint for smooth updates + if self.settings_open { + egui::Window::new("âš™ī¸ Settings") + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::RIGHT_TOP, [-10.0, 60.0]) + .show(ctx, |ui| { + ui.label("đŸŽĩ Audio Settings"); + ui.separator(); + + ui.horizontal(|ui| { + ui.label("Sample Rate:"); + ui.add( + egui::Slider::new(&mut self.sample_rate, 8000..=96000) + .suffix(" Hz") + .logarithmic(true) + .clamp_to_range(true) + ); + }); + + ui.add_space(10.0); + + ui.label("â„šī¸ Higher sample rates produce better quality audio but larger files."); + + ui.add_space(10.0); + + if ui.button("Close").clicked() { + self.settings_open = false; + } + }); + } + if self.is_encoding() { - ctx.request_repaint_after(Duration::from_millis(50)); + ctx.request_repaint(); } } } @@ -497,13 +648,14 @@ fn main() -> eframe::Result<()> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_inner_size([600.0, 500.0]) - .with_min_inner_size([500.0, 400.0]), + .with_inner_size([700.0, 600.0]) + .with_min_inner_size([600.0, 500.0]) + .with_title("Robot36 SSTV Encoder"), ..Default::default() }; eframe::run_native( - "Robot36 Image Encoder", + "Robot36 SSTV Encoder", options, Box::new(|cc| Ok(Box::new(Robot36EncoderApp::new(cc)))), )