From dcf121c04526af6dad5ff5f686a726db031c00da Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Tue, 17 Feb 2026 18:01:33 +0100 Subject: [PATCH] Improve REAMDE with feature table, add youtube downloader support with yt-dlp running in a thread, add yt-dlp auto-downnload and ffmpeg auto-download for windows/warn for linux, add back button for youtube downloader --- .gitignore | 1 + Cargo.lock | 681 +++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + README.md | 33 ++- src/main.rs | 170 +++++++++++-- src/yt_dlp.rs | 75 ++++++ 6 files changed, 917 insertions(+), 44 deletions(-) create mode 100644 src/yt_dlp.rs diff --git a/.gitignore b/.gitignore index 002497b..dd55b00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target data.json +/bin \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 604f47a..f54defc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -437,6 +437,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.22.1" @@ -722,7 +744,7 @@ dependencies = [ "crossbeam-channel", "egui", "encase", - "getrandom", + "getrandom 0.3.4", "image", "itertools", "js-sys", @@ -1023,7 +1045,7 @@ dependencies = [ "critical-section", "foldhash 0.2.0", "futures-channel", - "getrandom", + "getrandom 0.3.4", "hashbrown 0.16.1", "js-sys", "portable-atomic", @@ -1630,6 +1652,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "codespan-reporting" version = "0.12.0" @@ -1801,7 +1832,7 @@ dependencies = [ "fontdb", "log", "rangemap", - "rustc-hash", + "rustc-hash 1.1.0", "rustybuzz", "self_cell", "smol_str", @@ -2008,6 +2039,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecolor" version = "0.33.3" @@ -2268,6 +2305,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -2348,6 +2391,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -2355,6 +2404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -2393,6 +2443,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.31" @@ -2408,6 +2464,7 @@ dependencies = [ "futures-core", "futures-io", "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -2425,6 +2482,19 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -2518,6 +2588,25 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -2599,6 +2688,108 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -2734,6 +2925,22 @@ dependencies = [ "rustversion", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2777,7 +2984,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom", + "getrandom 0.3.4", "libc", ] @@ -2869,6 +3076,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mach2" version = "0.4.3" @@ -2935,6 +3148,12 @@ dependencies = [ "paste", ] +[[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" @@ -2945,6 +3164,17 @@ dependencies = [ "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" @@ -2976,7 +3206,7 @@ dependencies = [ "num-traits", "once_cell", "pp-rs", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "thiserror 2.0.17", "unicode-ident", @@ -2993,7 +3223,7 @@ dependencies = [ "indexmap", "naga", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "thiserror 2.0.17", "tracing", "unicode-ident", @@ -3469,6 +3699,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "orbclient" version = "0.3.49" @@ -3725,6 +3961,62 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.42" @@ -3772,7 +4064,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] [[package]] @@ -3881,6 +4173,46 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rfd" version = "0.16.0" @@ -3905,6 +4237,20 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "ringbuf" version = "0.4.8" @@ -3953,6 +4299,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -3988,6 +4340,81 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -4035,6 +4462,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4060,6 +4496,29 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.1" @@ -4227,6 +4686,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "soundboard" version = "0.1.0" @@ -4234,6 +4703,7 @@ dependencies = [ "bevy", "bevy_egui", "rand", + "reqwest", "rfd", "ringbuf", "rodio", @@ -4286,6 +4756,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.5" @@ -4461,6 +4937,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -4481,6 +4966,27 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "taffy" version = "0.7.7" @@ -4500,7 +5006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.3.4", "once_cell", "rustix 1.1.2", "windows-sys 0.61.2", @@ -4628,6 +5134,43 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -4658,6 +5201,51 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -4742,6 +5330,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.20.0" @@ -4849,6 +5443,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -4879,7 +5479,7 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom", + "getrandom 0.3.4", "js-sys", "serde_core", "wasm-bindgen", @@ -4918,6 +5518,21 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -5129,6 +5744,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -5180,7 +5804,7 @@ dependencies = [ "portable-atomic", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.17", "wgpu-core-deps-apple", @@ -5449,6 +6073,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -5476,6 +6111,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -5495,6 +6139,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6002,6 +6655,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index b6767af..d485ad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] bevy_egui = "0.38.1" rand = "0.9.2" +reqwest = { version = "0.13.2", features = ["blocking"] } rfd = "0.16.0" ringbuf = "0.4.8" rodio = { version = "0.21.1", features = ["mp3", "wav", "flac", "vorbis"] } diff --git a/README.md b/README.md index 50faa77..70cabc0 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,18 @@ # csd4ni3l Soundboard -Soundboard made in Rust & Bevy. My first Rust project. +Cross-platform soundboard made in Rust & Bevy. My first Rust project. +You might ask, why? And my answer is why not? Also because i wanted to learn Rust and this was a good way. -## Support & Requirements +## Features & Requirements -## Linux - -- Needs the `mold` linker and `clang` to compile fast -- ALSA & PulseAudio/Pipewire-pulse is a requirement -- Can use auto-selection of app to use the virtual mic in. -- Auto-routes mic to virtual mic by default, so others can also hear you. - -## Windows - -- Needs the [VB-Cable driver](https://vb-audio.com/Cable/) -- You need to still select the device inside the app you want to use it in. -- They only hear the soundboard as of right now, not your actual mic. - -## MacOS & Other - -- Might work as a music player with the default output device. -- Not supported and not planned. +| Topic | Linux | Windows | MacOS & Other +| -------- | ------- | ------- | ------- | +| Requirements | ALSA & PulseAudio/Pipewire-pulse, optionally FFmpeg for youtube downloader | Needs the [VB-Cable driver](https://vb-audio.com/Cable), optionally FFmpeg for youtube downloader | Unknown (optionally FFmpeg for youtube downloader)| +| Build Requirements | Rust, the `mold` linker and `clang` to compile fast | Rust, any C compiler | Unknown | +| FFmpeg | Optionally for youtube downloader | Optional, Automatic install on Windows 11 (winget) | Optionally for youtube downloader | +| Virtual Mic | Pulseaudio/Pipewire | VB-Cable | No | +| App Selection | Yes | No | No | +| Youtube Downloader support | Yes (ffmpeg required) | Yes (ffmpeg required) | Unknown (ffmpeg required) | +| Can others hear you? | Yes | Experimental | Unknown | +| Support | Best | Medium | None/Unknown | +| Download | [Download for Linux](https://github.com/csd4ni3l/soundboard/releases/download/latest/soundboard) | [Download for Windows](https://github.com/csd4ni3l/soundboard/releases/download/latest/soundboard.exe) | Build it yourself. | \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 99ef0bb..5b99092 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ use bevy::{log::Level, prelude::*}; +use bevy_egui::{EguiContextSettings, EguiContexts, EguiPrimaryContextPass, EguiStartupSet, egui::{self, Context, TextBuffer, Ui, ecolor::Color32}}; -use std::{collections::HashMap, fs::File, io::BufReader, path::Path, time::Instant}; +use std::{collections::HashMap, fs::{File, create_dir, exists, rename}, io::{BufReader, Read, Seek}, path::Path, process::{Command, Stdio}, sync::{Arc, Mutex}, thread, time::Instant}; use serde::{Deserialize, Serialize}; -use bevy_egui::{EguiContextSettings, EguiContexts, EguiPrimaryContextPass, EguiStartupSet, egui::{self, Context, Ui}}; - -use egui::ecolor::Color32; +mod yt_dlp; #[cfg(target_os = "linux")] mod linux_lib; @@ -19,6 +18,8 @@ use rodio::{ cpal::{self, traits::HostTrait}, }; +use crate::yt_dlp::*; + #[derive(Serialize, Deserialize)] struct JSONData { tabs: Vec, @@ -40,6 +41,14 @@ struct SoundSystem { output_stream: OutputStream, } +struct YoutubeDownloaderState { + current_url: String, + current_filename: String, + download_directory: String, + yt_dlp_running: bool, + yt_dlp_stdout_text: Arc> +} + #[derive(Resource)] struct AppState { loaded_files: HashMap>, @@ -51,7 +60,8 @@ struct AppState { virt_output_index_switch: String, virt_output_index: String, last_virt_output_update: Instant, - current_view: String + current_view: String, + youtube_downloader_state: YoutubeDownloaderState } const ALLOWED_FILE_EXTENSIONS: [&str; 4] = ["mp3", "wav", "flac", "ogg"]; @@ -113,6 +123,13 @@ fn list_outputs() -> Vec<(String, String)> { } fn main() { + if !exists("bin").expect("Could not check existence of bin folder") { + let _ = create_dir("bin"); + } + + check_and_download_ffmpeg(); + check_and_download_yt_dlp(); + App::new() .insert_resource(ClearColor(Color::BLACK)) .add_plugins( @@ -142,7 +159,14 @@ fn main() { virt_output_index_switch: String::from("0"), virt_output_index: String::from("999"), current_view: "main".to_string(), - last_virt_output_update: Instant::now() + last_virt_output_update: Instant::now(), + youtube_downloader_state: YoutubeDownloaderState { + current_url: String::new(), + current_filename: String::new(), + download_directory: String::new(), + yt_dlp_running: false, + yt_dlp_stdout_text: Arc::new(Mutex::new(String::new())) + } }) .add_systems( PreStartup, @@ -237,14 +261,30 @@ fn update_ui_scale_factor_system(egui_context: Single<(&mut EguiContextSettings, egui_settings.scale_factor = 1.5 / camera.target_scaling_factor().unwrap_or(1.5); } +fn get_duration(decoder: &mut rodio::Decoder) -> f32 // get_duration is needed cause some MP3 files dont provide duration metadata so we need to count +where + R: Read + Seek, +{ + let mut total_samples: u32 = 0; + + for _ in decoder.by_ref() { + total_samples += 1; + } + + let sample_rate = decoder.sample_rate() as u32; + let channels = decoder.channels() as u32; + + total_samples as f32 / (sample_rate * channels) as f32 +} + fn play_sound(file_path: String, app_state: &mut AppState) { + let file = File::open(&file_path).unwrap(); + let mut src = Decoder::new(BufReader::new(file)).unwrap(); + let length = get_duration(&mut src); + + // need to recreate since get_duration seeks to the end and nothing is left let file = File::open(&file_path).unwrap(); let src = Decoder::new(BufReader::new(file)).unwrap(); - let length = src - .total_duration() - .expect("Could not get source duration") - .as_secs_f32(); - let sink = Sink::connect_new(&app_state.sound_system.output_stream.mixer()); sink.append(src); sink.play(); @@ -426,9 +466,91 @@ fn main_ui(ctx: &Context, mut app_state: ResMut) { }); } -fn youtube_downloader_ui(ctx: &Context, app_state: ResMut) { +fn download_youtube_sound(app_state: &mut ResMut) { + let filename = app_state.youtube_downloader_state.current_filename.clone(); + let download_directory = app_state.youtube_downloader_state.download_directory.clone(); + let current_url = app_state.youtube_downloader_state.current_url.clone(); + let stdout_text = Arc::clone(&app_state.youtube_downloader_state.yt_dlp_stdout_text); + + app_state.youtube_downloader_state.yt_dlp_running = true; + + thread::spawn(move || { + let mut command = Command::new(get_yt_dlp_path()) + .args(&["-x", "--audio-format", "mp3", "-o", "sound.mp3", current_url.as_str()]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to execute process"); + + if let Some(mut stdout) = command.stdout.take() { + let mut buffer = String::new(); + loop { + let mut chunk = vec![0u8; 1024]; + match stdout.read(&mut chunk) { + Ok(0) => break, + Ok(n) => { + if let Ok(text) = String::from_utf8(chunk[..n].to_vec()) { + buffer.push_str(&text); + if let Ok(mut locked) = stdout_text.lock() { + *locked = buffer.clone(); + } + } + } + Err(_) => break, + } + } + } + let _ = command.wait(); + + let path = Path::new(&download_directory).join(filename); + let _ = rename("sound.mp3", path.to_string_lossy().as_str()); + }); +} + +fn youtube_downloader_ui(ctx: &Context, mut app_state: ResMut) { egui::CentralPanel::default().show(ctx, |ui| { - ui.heading(format!("Coming Soon! Currently on {} view.", app_state.current_view)); // view is only included here so there is no warning about app_state not being used. + let available_width = ui.available_width(); + let available_height = ui.available_height(); + + ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { + ui.heading("Directory"); + egui::ComboBox::from_id_salt("Download Directory Selector") + .selected_text(app_state.youtube_downloader_state.download_directory.clone()) + .width(available_width) + .height(available_height / 15.0) + .show_ui(ui, |ui| { + for directory in &app_state.loaded_files.keys().cloned().collect::>() { + ui.selectable_value( + &mut app_state.youtube_downloader_state.download_directory, + directory.clone(), + directory, + ); + } + }); + + ui.heading("Filename"); + ui.add_sized([available_width, available_height / 20.0], egui::TextEdit::singleline(&mut app_state.youtube_downloader_state.current_filename)); + + ui.heading("Youtube URL"); + ui.add_sized([available_width, available_height / 20.0], egui::TextEdit::singleline(&mut app_state.youtube_downloader_state.current_url)); + }); + + if let Ok(text) = app_state.youtube_downloader_state.yt_dlp_stdout_text.lock() { + ui.colored_label(Color32::GREEN, text.clone()); + }; + + if ui + .add_sized( + [ + available_width as f32, + available_height / 15.0, + ], + egui::Button::new("Download Sound"), + ) + .clicked() + { + download_youtube_sound(&mut app_state); + }; }); } @@ -436,7 +558,27 @@ fn draw(mut contexts: EguiContexts, mut app_state: ResMut) -> Result { let ctx = contexts.ctx_mut()?; egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { - ui.heading("csd4ni3l Soundboard"); + if app_state.current_view != "main" { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + let available_width = ui.available_width(); + let available_height = ui.available_height(); + + if ui + .add_sized( + [available_width / 25.0, available_height], + egui::Button::new("<--"), + ) + .clicked() + { + app_state.current_view = "main".to_string(); + } + + ui.heading("csd4ni3l Soundboard"); + }); + } + else { + ui.heading("csd4ni3l Soundboard"); + } }); egui::TopBottomPanel::bottom("currently_playing").show(ctx, |ui| { diff --git a/src/yt_dlp.rs b/src/yt_dlp.rs new file mode 100644 index 0000000..53680d5 --- /dev/null +++ b/src/yt_dlp.rs @@ -0,0 +1,75 @@ +use std::{env::current_dir, fs::{File, exists}, io, process::Command}; +use reqwest; +use rfd::{MessageButtons, MessageDialog, MessageDialogResult}; + +pub fn get_yt_dlp_path() -> String { + if cfg!(target_os = "windows"){ + current_dir().expect("Failed to get current working directory").join("bin").join("yt-dlp.exe").to_string_lossy().to_string() + } + else if cfg!(target_os = "macos"){ + current_dir().expect("Failed to get current working directory").join("bin").join("yt-dlp_macos").to_string_lossy().to_string() + } + else if cfg!(target_os = "linux"){ + current_dir().expect("Failed to get current working directory").join("bin").join("yt-dlp_linux").to_string_lossy().to_string() + } + else { + "".to_string() + } +} + +pub fn check_and_download_yt_dlp() { + let url: &str; + + if cfg!(target_os = "windows"){ + url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"; + } + else if cfg!(target_os = "macos"){ + url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos"; + } + else if cfg!(target_os = "linux"){ + url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux"; + } + else { + return; + } + + if exists(get_yt_dlp_path()).expect("Could not check existence of yt dlp executable.") { + return; + } + + let mut body = reqwest::blocking::get(url).expect("Could not download yt-dlp"); + let mut out = File::create(get_yt_dlp_path()).expect("failed to create file"); + io::copy(&mut body, &mut out).expect("failed to copy content"); +} + +pub fn check_ffmpeg() -> bool{ + return std::process::Command::new("ffmpeg").spawn().is_ok(); +} + +pub fn check_and_download_ffmpeg() { + if check_ffmpeg() { + return; + } + + if cfg!(target_os = "windows"){ + let confirmed = MessageDialog::new() + .set_title("FFmpeg Download Optional.") + .set_description("The youtube downloader depends on FFmpeg for mp3 conversion. This app can auto-install FFmpeg with winget. Do you want to install FFmpeg?") + .set_buttons(MessageButtons::YesNo) + .show(); + + if confirmed == MessageDialogResult::Ok { + Command::new("winget") + .args(&["install", "BtbN.FFmpeg.GPL.Shared.8.0", "--source winget", "--accept-source-agreements", "--accept-package-agreements"]) // as_str is needed here as you cannot instantly dereference a growing String (Rust...) + .output() + .expect("Failed to execute process"); + } + } + else { + MessageDialog::new() + .set_title("FFmpeg Download Optional.") + .set_description("The youtube downloader depends on FFmpeg for mp3 conversion. You are on a Linux or Darwin based OS. If you want to use the Youtube Downloader, you need to install FFmpeg and libavcodec shared libraries from your package manager to make sure it is in PATH.") + .set_buttons(MessageButtons::Ok) + .show(); + } +} \ No newline at end of file