Compare commits

...

11 Commits

Author SHA1 Message Date
csd4ni3l
ff947b57a8 Fix std::os::unix included on Windows where it doesnt exist, and remove set permission in yt-dlp for anything other than unix altogether. 2026-03-05 22:36:47 +01:00
csd4ni3l
8d21c268c2 Fix yt-dlp binary not having the right permissions after download 2026-03-05 21:41:08 +01:00
csd4ni3l
dff8d5aeff Fix warnings, remove ffmpeg output from cmd 2026-03-05 21:29:12 +01:00
csd4ni3l
4128566048 Make bottom panel fixed size so content doesn't go up and down when playing new sound effects, add more apps to Linux excludes, add multiple virtual output support by using buttons(on/off) on Linux so you can select the virtual mic for multiple apps at the same time 2026-03-05 21:24:55 +01:00
csd4ni3l
8d036bca08 Fix Windows build crashing because virtual_mic is Option Device not just Device (not inside let Some) 2026-02-17 19:30:02 +01:00
csd4ni3l
9d3d027e45 Add exit after cable input error messagebox is closed. 2026-02-17 19:03:45 +01:00
csd4ni3l
4a21f60ee6 Fix Windows cable input error handling 2026-02-17 19:01:39 +01:00
csd4ni3l
b241117813 Add messagedialog when VB Cable is not installed. 2026-02-17 18:55:27 +01:00
csd4ni3l
dcf121c045 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 2026-02-17 18:01:33 +01:00
csd4ni3l
e7fbf8f4d8 Fix a bunch of issues with the windows standard to virtual mic routing, fix all warnings for youtube_downloader and others 2026-02-15 18:12:10 +01:00
csd4ni3l
815663b039 Fix windows not liking linux_lib import when creating dropdown/label for virtual mic 2026-02-15 17:36:14 +01:00
8 changed files with 1053 additions and 142 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target
data.json
/bin

681
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"] }

View File

@@ -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. |

View File

@@ -5,7 +5,8 @@ use rodio::{
use serde_json::Value;
use std::process::Command;
const APPS_TO_EXCLUDE: [&str; 1] = ["plasmashell"];
const APPS_TO_EXCLUDE: [&str; 7] = ["plasmashell", "pavucontrol", "pipewire", "wireplumber", "kwin_wayland", "kwin_x11", "obs"];
const NODE_NAMES_TO_EXCLUDE: [&str; 2] = ["VirtualMicSource", "SoundboardSink"];
fn pactl_list(sink_type: &str) -> Value {
let command_output = Command::new("pactl")
@@ -23,21 +24,35 @@ fn pactl_list(sink_type: &str) -> Value {
}
}
pub fn get_sink_by_index(sink_type: &str, index: String) -> Value {
let sinks = pactl_list(sink_type);
pub fn get_soundboard_sink_index() -> String {
let source_outputs = pactl_list("sinks");
source_outputs
.as_array()
.unwrap_or(&vec![])
.iter()
.find(|sink| sink["name"] == "SoundboardSink")
.and_then(|sink| {
Some(sink["index"].to_string())
})
.unwrap()
}
for sink in sinks.as_array().unwrap_or(&vec![]) {
if sink["index"]
.as_u64()
.expect("sink index is not a number")
.to_string()
== index
{
return sink.clone();
}
}
pub fn get_default_source() -> String {
let sources = pactl_list("sources");
return Value::Null {};
let command = Command::new("pactl")
.args(&["get-default-source"])
.output()
.unwrap();
let default_source_name = String::from_utf8_lossy(&command.stdout).trim().to_string();
sources.as_array()
.unwrap()
.iter()
.find(|sink|{ sink["name"].as_str() == Some(&default_source_name) })
.and_then(|s|{ Some(s["index"].to_string()) })
.unwrap()
}
fn find_soundboard_sinks() -> Vec<Value> {
@@ -73,10 +88,11 @@ pub fn list_outputs() -> Vec<(String, String)> {
.iter()
.filter_map(|sink| {
let app_name = sink["properties"]["application.name"].as_str()?;
let node_name = sink["properties"]["node.name"].as_str()?;
let binary = sink["properties"]["application.process.binary"]
.as_str()
.unwrap_or("Unknown");
if APPS_TO_EXCLUDE.contains(&binary) {
if APPS_TO_EXCLUDE.contains(&binary) || NODE_NAMES_TO_EXCLUDE.contains(&node_name) {
return None;
}
let index = sink["index"]
@@ -88,9 +104,9 @@ pub fn list_outputs() -> Vec<(String, String)> {
.collect();
}
pub fn move_index_to_virtualmic(index: String) {
Command::new("pactl")
.args(&["move-source-output", index.as_str(), "VirtualMicSource"]) // as_str is needed here as you cannot instantly dereference a growing String (Rust...)
pub fn move_output_to_sink(output_index: String, sink_index: String) {
let _ = Command::new("pactl")
.args(&["move-source-output", output_index.as_str(), sink_index.as_str()]) // as_str is needed here as you cannot instantly dereference a growing String (Rust...)
.output()
.expect("Failed to execute process");
}

View File

@@ -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}};
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<String>,
@@ -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<Mutex<String>>
}
#[derive(Resource)]
struct AppState {
loaded_files: HashMap<String, Vec<String>>,
@@ -48,10 +57,10 @@ struct AppState {
currently_playing: Vec<PlayingSound>,
sound_system: SoundSystem,
virt_outputs: Vec<(String, String)>,
virt_output_index_switch: String,
virt_output_index: String,
is_virt_output_used: HashMap<String, bool>,
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 +122,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(
@@ -139,10 +155,16 @@ fn main() {
currently_playing: Vec::new(),
sound_system: create_virtual_mic(),
virt_outputs: Vec::new(),
virt_output_index_switch: String::from("0"),
virt_output_index: String::from("999"),
is_virt_output_used: HashMap::new(),
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,
@@ -157,30 +179,29 @@ fn main() {
}
fn update(mut app_state: ResMut<AppState>) {
if app_state.last_virt_output_update.elapsed().as_secs_f32() >= 3.0 {
#[cfg(target_os = "linux")] {
if app_state.last_virt_output_update.elapsed().as_secs_f32() >= 1.5 {
app_state.last_virt_output_update = Instant::now();
app_state.virt_outputs = list_outputs();
let is_virt_output_used = app_state.is_virt_output_used.clone();
for virt_output in &app_state.virt_outputs.clone() {
if !is_virt_output_used.contains_key(&virt_output.1) {
app_state.is_virt_output_used.insert(virt_output.1.clone(), false);
}
if app_state.virt_outputs.is_empty() {
return;
if app_state.is_virt_output_used[&virt_output.1] {
linux_lib::move_output_to_sink(virt_output.1.clone(), linux_lib::get_soundboard_sink_index());
}
else {
linux_lib::move_output_to_sink(virt_output.1.clone(), linux_lib::get_default_source());
}
}
if !(app_state.virt_output_index == "999".to_string()) {
app_state.virt_output_index_switch = app_state.virt_outputs[0].1.clone();
}
if app_state.virt_output_index != app_state.virt_output_index_switch {
app_state.virt_output_index = app_state.virt_output_index_switch.clone();
#[cfg(target_os = "linux")]
linux_lib::move_index_to_virtualmic(app_state.virt_output_index_switch.clone());
}
}
fn load_system(mut app_state: ResMut<AppState>) {
if !app_state.virt_outputs.is_empty() {
app_state.virt_output_index_switch = app_state.virt_outputs[0].1.clone();
}
load_data(&mut app_state);
}
@@ -237,14 +258,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<R>(decoder: &mut rodio::Decoder<R>) -> 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 src = Decoder::new(BufReader::new(file)).unwrap();
let length = src
.total_duration()
.expect("Could not get source duration")
.as_secs_f32();
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 sink = Sink::connect_new(&app_state.sound_system.output_stream.mixer());
sink.append(src);
sink.play();
@@ -269,7 +306,36 @@ fn play_sound(file_path: String, app_state: &mut AppState) {
app_state.currently_playing.push(playing_sound);
}
fn main_ui(mut ctx: &Context, mut app_state: ResMut<AppState>) {
fn create_virtual_mic_ui(ui: &mut Ui, app_state: &mut ResMut<AppState>, available_width: f32, available_height: f32) {
#[cfg(target_os = "linux")] {
if app_state.is_virt_output_used.len() != 0 {
let outputs = app_state.virt_outputs.clone();
for output in &outputs {
let current_value = *app_state.is_virt_output_used.get(&output.1).unwrap_or(&false);
if ui
.add_sized(
[available_width, available_height / 30.0],
egui::Button::new(format!("{} - {}", output.0.clone(), current_value)),
)
.clicked()
{
*app_state.is_virt_output_used.entry(output.1.clone()).or_insert(false) = !current_value;
}
}
}
else {
ui.add(egui::Button::new("No apps found to use.".to_string()));
}
return;
}
#[allow(unreachable_code)]
{
ui.add(egui::Button::new("Unsupported. Select inside apps.".to_string()));
}
}
fn main_ui(ctx: &Context, mut app_state: ResMut<AppState>) {
egui::SidePanel::right("tools").show(ctx, |ui| {
ui.heading("Tools");
@@ -277,33 +343,8 @@ fn main_ui(mut ctx: &Context, mut app_state: ResMut<AppState>) {
let available_width = ui.available_width();
let available_height = ui.available_height();
let outputs = app_state.virt_outputs.clone();
ui.label("Virtual Mic Output");
if cfg!(target_os = "linux") {
let output_index = app_state.virt_output_index.clone();
let output_sink = linux_lib::get_sink_by_index("source-outputs", output_index);
if let Some(app_name) = output_sink["properties"]["application.name"].as_str() {
egui::ComboBox::from_id_salt("Virtual Mic Output")
.selected_text(app_name.to_string())
.width(available_width)
.height(available_height / 15.0)
.show_ui(ui, |ui| {
for output in &outputs {
ui.selectable_value(
&mut app_state.virt_output_index_switch,
output.1.clone(),
output.0.clone(),
);
}
});
}
else {
ui.add(egui::Button::new("No apps found to use.".to_string()));
}
}
else {
ui.add(egui::Button::new("Unsupported. Select inside apps.".to_string()));
}
create_virtual_mic_ui(ui, &mut app_state, available_width, available_height);
if ui
.add_sized(
@@ -314,7 +355,6 @@ fn main_ui(mut ctx: &Context, mut app_state: ResMut<AppState>) {
{
if let Some(folder) = rfd::FileDialog::new().pick_folder() {
if let Some(path_str) = folder.to_str() {
println!("Selected: {}", path_str);
app_state.json_data.tabs.push(path_str.to_string());
std::fs::write(
"data.json",
@@ -419,9 +459,91 @@ fn main_ui(mut ctx: &Context, mut app_state: ResMut<AppState>) {
});
}
fn youtube_downloader_ui(mut ctx: &Context, mut app_state: ResMut<AppState>) {
fn download_youtube_sound(app_state: &mut ResMut<AppState>) {
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<AppState>) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Coming Soon!");
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::<Vec<_>>() {
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);
};
});
}
@@ -429,10 +551,34 @@ fn draw(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Result {
let ctx = contexts.ctx_mut()?;
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
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| {
let window_height = ctx.content_rect().height();
egui::TopBottomPanel::bottom("currently_playing")
.exact_height(window_height * 0.1)
.show(ctx, |ui| {
ui.vertical(|ui| {
for playing_sound in &mut app_state.currently_playing {
ui.horizontal(|ui| {

View File

@@ -1,46 +1,47 @@
use rodio::{
OutputStream, OutputStreamBuilder,
cpal::{self, traits::DeviceTrait, traits::HostTrait},
cpal::{self, traits::{DeviceTrait, StreamTrait, HostTrait}, StreamConfig, SampleRate},
};
use rfd::{MessageButtons, MessageDialog, MessageDialogResult};
use ringbuf::{traits::*, HeapRb};
use std::process;
fn route_standard_to_virtual(virtual_mic: cpal::Device) {
let standard_mic = host.default_output_device();
fn route_standard_to_virtual(host: &cpal::Host, virtual_mic: &cpal::Device) {
let standard_mic = host.default_input_device().expect("Could not get default input device.");
let config = StreamConfig {
channels: 2,
sample_rate: SampleRate(48_000),
buffer_size: cpal::BufferSize::Default,
};
let rb = HeapRb::<i32>::new(48_000 * 2);
let rb = HeapRb::<f32>::new(48_000 * 2);
let (mut producer, mut consumer) = rb.split();
let input_stream = standard_mic.build_input_stream(
&config,
move |data: &[f32], _| {
for &sample in data {
let _ = producer.push(sample);
let _ = producer.push(sample);
let _ = producer.try_push(sample);
let _ = producer.try_push(sample);
}
},
move |err| eprintln!("Input stream error: {err}"),
None,
)?;
).expect("Could not build input stream for standard to virtual mic routing");
let output_stream = virtual_mic.build_output_stream(
&config,
move |data: &mut [f32], _| {
for sample in data {
*sample = consumer.pop().unwrap_or(0.0);
*sample = consumer.try_pop().unwrap_or(0.0);
}
},
move |err| eprintln!("Output stream error: {err}"),
None,
)?;
).expect("Could not build output stream for standard to virtual mic routing");
input_stream.play()?;
output_stream.play()?;
input_stream.play();
output_stream.play();
}
pub fn create_virtual_mic_windows() -> (OutputStream, OutputStream) {
@@ -56,10 +57,10 @@ pub fn create_virtual_mic_windows() -> (OutputStream, OutputStream) {
.ok()
.map(|name| name.contains("CABLE Input") || name.contains("VB-Audio"))
.unwrap_or(false)
})
.expect("Could not get VB Cable output device. Is VB Cable Driver installed?");
});
route_standard_to_virtual(virtual_mic);
if let Some(virtual_mic) = virtual_mic {
route_standard_to_virtual(&host, &virtual_mic);
let normal_output = host
.default_output_device()
@@ -75,4 +76,14 @@ pub fn create_virtual_mic_windows() -> (OutputStream, OutputStream) {
.open_stream()
.expect("Failed to open stream"),
);
}
else {
MessageDialog::new()
.set_title("VB Cable Driver not installed.")
.set_description("Could not access VB Cable output device. Is VB Cable Driver installed?")
.set_buttons(MessageButtons::Ok)
.show();
std::process::exit(1);
}
}

82
src/yt_dlp.rs Normal file
View File

@@ -0,0 +1,82 @@
use std::{env::current_dir, fs::{File, exists}, io, process::Command};
use reqwest;
use rfd::{MessageButtons, MessageDialog, MessageDialogResult};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
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");
#[cfg(unix)]
out.set_permissions(PermissionsExt::from_mode(0o755));
}
pub fn check_ffmpeg() -> bool{
return std::process::Command::new("ffmpeg").output().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();
}
}