From 0162421db4e7f3678474a7905e8527af28e26d83 Mon Sep 17 00:00:00 2001 From: Tal <83217276+talwat@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:39:20 +0200 Subject: [PATCH] feat: the 1.7.0 release (#97) * docs: update to be relevant to the current version * chore: bump version * fix: change default progress to 0 why it was ever -1.0 is a mystery to me, it doesn't make any logical sense... * fix: switch from rand to fastrand * feat: prepare for 1.7.0 release docs: explain music situation docs: more internal documentation feat: make timeout configurable chore: clean up some sections of code * fix: use boring fs functions for bookmark loading and writing * chore: remove useless internal doc * chore: bump version --- CHILLHOP.md | 5 + Cargo.lock | 210 ++++++++++++------------- Cargo.toml | 8 +- MUSIC.md | 75 +++++++++ README.md | 76 ++++----- data/{lofigirl-new.txt => archive.txt} | 0 data/file.txt | 2 +- data/noheader.txt | 2 + data/{ => old}/lofigirl.txt | 0 data/{ => old}/synthboy.txt | 0 data/sample.txt | 2 +- src/main.rs | 9 +- src/play.rs | 4 +- src/player.rs | 14 +- src/player/bookmark.rs | 57 +++---- src/player/downloader.rs | 4 +- src/player/queue.rs | 4 +- src/player/ui.rs | 2 - src/player/ui/components.rs | 2 +- src/scrapers.rs | 3 + src/tracks.rs | 2 +- src/tracks/error.rs | 2 +- src/tracks/list.rs | 15 +- 23 files changed, 288 insertions(+), 210 deletions(-) create mode 100644 MUSIC.md rename data/{lofigirl-new.txt => archive.txt} (100%) create mode 100644 data/noheader.txt rename data/{ => old}/lofigirl.txt (100%) rename data/{ => old}/synthboy.txt (100%) diff --git a/CHILLHOP.md b/CHILLHOP.md index 6aebf22..ddbd687 100644 --- a/CHILLHOP.md +++ b/CHILLHOP.md @@ -1,5 +1,10 @@ # Using the chillhop list +> [!WARNING] +> As of lowfi 1.7.0, the chillhop list is included by default. For a more +> detailed explanation, see [MUSIC.md](MUSIC.md). This document is included +> to preserve any old links or references. The instructions are still valid. + ## Linux ```sh diff --git a/Cargo.lock b/Cargo.lock index c4e4638..9fdaeb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,7 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.6.0", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -183,7 +183,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.59.0", @@ -215,7 +215,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.42", "tracing", ] @@ -242,7 +242,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -312,9 +312,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "block-buffer" @@ -480,6 +480,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "convert_case" version = "0.8.0" @@ -562,16 +571,18 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "crossterm_winapi", + "derive_more 2.0.1", + "document-features", "futures-core", "mio", "parking_lot", - "rustix", + "rustix 1.0.8", "signal-hook", "signal-hook-mio", "winapi", @@ -636,6 +647,27 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case 0.7.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -648,23 +680,23 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -673,7 +705,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "objc2", ] @@ -688,6 +720,15 @@ dependencies = [ "syn", ] +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "dtoa" version = "1.0.10" @@ -1421,7 +1462,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "libc", ] @@ -1431,12 +1472,24 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + [[package]] name = "lock_api" version = "0.4.12" @@ -1455,24 +1508,24 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lowfi" -version = "1.7.0-dev" +version = "1.7.0" dependencies = [ "arc-swap", "atomic_float", "bytes", "clap", "color-eyre", - "convert_case", + "convert_case 0.8.0", "crossterm", "dirs", "eyre", + "fastrand", "futures", "html-escape", "indicatif", "lazy_static", "libc", "mpris-server", - "rand", "regex", "reqwest", "rodio", @@ -1603,7 +1656,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "jni-sys", "log", "ndk-sys", @@ -1638,7 +1691,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "cfg-if", "cfg_aliases", "libc", @@ -1732,7 +1785,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cbe18d879e20a4aea544f8befe38bcf52255eb63d3f23eca2842f3319e4c07" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "libc", "objc2", "objc2-core-audio", @@ -1759,7 +1812,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "objc2", ] @@ -1769,7 +1822,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "dispatch2", "objc2", ] @@ -1810,7 +1863,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "cfg-if", "foreign-types", "libc", @@ -1996,7 +2049,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.42", "tracing", "windows-sys 0.59.0", ] @@ -2091,18 +2144,18 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", ] [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] @@ -2219,13 +2272,26 @@ version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.3", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + [[package]] name = "rustls" version = "0.23.20" @@ -2323,7 +2389,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "core-foundation", "core-foundation-sys", "libc", @@ -2346,9 +2412,9 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "cssparser", - "derive_more", + "derive_more 0.99.20", "fxhash", "log", "new_debug_unreachable", @@ -2740,7 +2806,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", "core-foundation", "system-configuration-sys", ] @@ -2765,7 +2831,7 @@ dependencies = [ "fastrand", "getrandom 0.2.15", "once_cell", - "rustix", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -3321,15 +3387,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3372,21 +3429,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3426,12 +3468,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3450,12 +3486,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3474,12 +3504,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3510,12 +3534,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3534,12 +3552,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3558,12 +3570,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3582,12 +3588,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3615,7 +3615,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9e84a3a..419052e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lowfi" -version = "1.7.0-dev" +version = "1.7.0" edition = "2021" description = "An extremely simple lofi player." license = "MIT" @@ -25,7 +25,7 @@ scrape = ["dep:serde", "dep:serde_json", "dep:html-escape", "dep:scraper", "dep: # Basics clap = { version = "4.5.21", features = ["derive", "cargo"] } eyre = "0.6.12" -rand = "0.8.5" +fastrand = "2.3.0" thiserror = "2.0.12" color-eyre = { version = "0.6.5", default-features = false } @@ -39,10 +39,10 @@ reqwest = { version = "0.12.9", features = ["stream"] } bytes = "1.9.0" # I/O -crossterm = { version = "0.28.1", features = ["event-stream"] } +crossterm = { version = "0.29.0", features = ["event-stream"] } rodio = { version = "0.21.1", features = ["symphonia-mp3", "playback"], default-features = false } mpris-server = { version = "0.8.1", optional = true } -dirs = "5.0.1" +dirs = "6.0.0" # Misc convert_case = "0.8.0" diff --git a/MUSIC.md b/MUSIC.md new file mode 100644 index 0000000..e09d492 --- /dev/null +++ b/MUSIC.md @@ -0,0 +1,75 @@ +# The State of Lowfi's Music + +> [!WARNING] +> This document will be a bit long and has almost nothing to do with the actual +> usage of lowfi, just the music embedded by default. + +Before that though, some context. lowfi includes an extensive track list +embedded into the software, so you can download it and have it "just work" +out of the box. + +I always hated apps that required extensive configuration just to be usable. +Sometimes it's justified, but often, it's just pointless when most will end up +with the same set of "defaults" that aren't really defaults. + +Lowfi is so nice and simple because of the "plug and play" aspect, +but it's become a lot harder to continue it as of late. + +## The Lofi Girl List + +Originally, it was planned that lowfi would use music scraped from Lofi Girl's own +website. The scraper actually came before the rest of the program, believe it or not. + +However, after a long period of downtime, the Lofi Girl website was redone without the +mp3 track files. Those are now pretty much inaccessible aside from paying for individual +albums on bandcamp which gets very expensive very quickly. + +Doing this was never actually disallowed, but it is now simply impossible. So, the question was, +what to do next after losing lowfi's primary source of music? + +## Tracklists + +I was originally against the idea of custom tracklists, because of my almost purist +ideals of a 100% no config at all vision for lowfi. But eventually, I gave in, which proved +to be a very good decision in hindsight. Now, regardless of what choices I make on the music +which is embedded, all may opt out of that and choose whatever they like. + +This culminated in a few templates located in the `data` directory of this repository +which included a handful of tracklists, and in particular, the chillhop list by user +[danielwerg](https://github.com/danielwerg). + +## The Switch + +After `lofigirl.com` went down, I thought a bit and eventually decided +to just bite the bullet and switch to the chillhop list. This was despite the fact +that chillhop entirely bans third party players in their TOS. They also ban +scrapers, which I only learned after writing one. + +So, is lowfi really going to have to violate the TOS of it's own music provider? +Well, yes. I thought about it, and came to the conclusion that lowfi is probably +not much of a threat for a few reasons. + +Firstly, it emulates exactly the behavior of chillhop's own radio player. +The only difference is that one shoves you into a web browser, and the other, +into a nice terminal window. + +Then, I also realize that lowfi is just a small program used by few. +I'm not making money on any of this, and I think degrading the experience for my +fellow nerds who just want to listen to some lowfi without all the crap is not worth it. + +At the end of the day, lowfi has a distinct UserAgent. Should chillhop ever take issue with +it's behaviour, banning it is extremely simple. I don't want that to happen, but I +understand if it does. + +## Well, *I* Hate the Chillhop Music + +It's not as "lofi". It is almost certainly a compromise, that much I cannot even pretend to +deny. I find myself hitting the skip button almost three times as often with chillhop. + +If you are undeterred enough by TOS's to read this far, then you can use the `archive.txt` +list in the `data` folder. The list is a product of me worrying that the tracks on `lofigirl.com` +could've possibly been lost somehow, relating to the website going down. + +It's hosted on `archive.org`, and could be taken down at any point for any reason. +Being derived from my own local archive, it retains ~2700 out of the ~3700 tracks. +That's not perfect, the organization is also *bad*, but it exists. \ No newline at end of file diff --git a/README.md b/README.md index ec3597d..b2fe52d 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,9 @@ It'll do this as simply as it can: no albums, no ads, just lofi. ## Disclaimer -> [!NOTE] -> -> As of the 19th of July 2025, lofigirl.com is temporarily down. If your lowfi is up to date, -> you can follow the [quick instructions](CHILLHOP.md) for using the [chillhop](https://chillhop.com/) alternative track list. -> -> Apologies for the inconvenience, it's out of lowfi's control. - -**All** of the audio files embedded into in lowfi by default are from [Lofi Girl's](https://lofigirl.com/) website, -under their [licensing guidelines](https://form.lofigirl.com/CommercialLicense). - -If, god forbid, you're planning to use lowfi in a commercial setting, please -follow their rules. +As of the 1.7.0 version of lowfi, **all** of the audio files embedded +by default are from [chillhop](https://chillhop.com/). Read +[MUSIC.md] for more information. ## Why? @@ -135,6 +126,7 @@ Yeah, that's it. | `-`, `_`, `j`, `↓` | Volume Down 10% | | `←` | Volume Down 1% | | `q`, CTRL+C | Quit | +| `b` | Bookmark | > [!NOTE] > Besides its regular controls, lowfi offers compatibility with Media Keys @@ -162,56 +154,54 @@ slightly tweak the UI or behaviour of the menu. The flags can be viewed with `lo ### Scraping -lowfi also has a `scrape` command which is usually not relevant, but -if you're trying to download some files from Lofi Girls' website, -it can be useful. +lowfi also has an optional `scrape` command enabled by the `scrape` feature. +It's usually not very useful, but is included for transparency's sake. -An example of scrape is as follows, - -`lowfi scrape --extension zip --include-full` - -where more information can be found by running `lowfi help scrape`. +More information can be found by running `lowfi help scrape`. ### Custom Track Lists -Some nice users, especially [danielwerg](https://github.com/danielwerg), -have aleady made alternative track lists located in the [data](https://github.com/talwat/lowfi/blob/main/data/) -directory of this repo. You can use them with lowfi by using the `--track-list` flag. - -Feel free to contribute your own list with a PR. - -> [!WARNING] +> [!NOTE] +> Some nice users, especially [danielwerg](https://github.com/danielwerg), +> have aleady made alternative track lists located in the [data](https://github.com/talwat/lowfi/blob/main/data/) +> directory of this repo. You can use them with lowfi by using the `--track-list` flag. > -> Custom track lists are going to be pretty particular. -> This is because I still want to keep `lowfi` as simple as possible, -> so custom lists will be very similar to how the built in list functions. -> -> This also means that there will be no added flexibility to these lists, -> so you'll have to work that out on your own. +> Feel free to contribute your own list with a PR. lowfi also supports custom track lists, although the default one from Lofi Girl is embedded into the binary. To use a custom list, use the `--track-list` flag. This can either be a path to some file, or it could also be the name of a file (without the `.txt` extension) in the data -directory, so on Linux it's `~/.local/share/lowfi`. +directory. + +> [!NOTE] +> Data directories: +> +> - Linux - `~/.local/share/lowfi` +> - macOS - `~/Library/Application Support/lowfi` +> - Windows - `%appdata%\Roaming\lowfi` For example, `lowfi --track-list minipop` would load `~/.local/share/lowfi/minipop.txt`. Whereas if you did `lowfi --track-list ~/Music/minipop.txt` it would load from that specified directory. +All tracks must be in the MP3 format, unless lowfi has been compiled with the +`extra-audio-formats` feature which includes support for some others. + #### The Format -In lists, the first line should be the base URL, followed by the rest of the tracks. -This is also known as the "header", because it comes first. +In lists, the first line is what's known as the header, followed by the rest of the tracks. +Each track will be first appended to the header, and then use that to download +the track. -Each track will be first appended to the base URL, and then the result use to download -the track. All tracks must be in the MP3 format, as lowfi doesn't support any others currently. +> [!NOTE] +> lowfi _will not_ put a `/` between the base & track for added flexibility, +> so for most cases you should have a trailing `/` in your header. -Additionally, lowfi _won't_ put a `/` between the base & track for added flexibility, -so for most cases you should have a trailing `/` in your base url. -The exception to this is if the track name begins with something like `https://`, -where in that case the base will not be prepended to it. +The exception to this is if the track name begins with a protocol like `https://`, +in which case the base will not be prepended to it. If all of your tracks are like this, +then you can put `noheader` as the first line and not have a header at all. For example, in this list: @@ -243,7 +233,7 @@ This is useful if you want to use a local file as the base URL, such as: ```txt file:///home/user/Music/ file.mp3 -file:///home/user/Music/second-file.mp3 +file:///home/user/Other Music/second-file.mp3 ``` Further examples can be found in the [data](https://github.com/talwat/lowfi/tree/main/data) folder. diff --git a/data/lofigirl-new.txt b/data/archive.txt similarity index 100% rename from data/lofigirl-new.txt rename to data/archive.txt diff --git a/data/file.txt b/data/file.txt index 4042c0b..6d63944 100644 --- a/data/file.txt +++ b/data/file.txt @@ -1,2 +1,2 @@ file:///home/user/Music/ -Anomaly.mp3 \ No newline at end of file +Test.mp3 \ No newline at end of file diff --git a/data/noheader.txt b/data/noheader.txt new file mode 100644 index 0000000..285bacd --- /dev/null +++ b/data/noheader.txt @@ -0,0 +1,2 @@ +noheader +https://stream.chillhop.com/mp3/9476 \ No newline at end of file diff --git a/data/lofigirl.txt b/data/old/lofigirl.txt similarity index 100% rename from data/lofigirl.txt rename to data/old/lofigirl.txt diff --git a/data/synthboy.txt b/data/old/synthboy.txt similarity index 100% rename from data/synthboy.txt rename to data/old/synthboy.txt diff --git a/data/sample.txt b/data/sample.txt index affaf3d..64ca88d 100644 --- a/data/sample.txt +++ b/data/sample.txt @@ -1,4 +1,4 @@ https://lofigirl.com/wp-content/uploads/ 2023/06/Foudroie-Finding-The-Edge-V2.mp3 2023/04/2-In-Front-Of-Me.mp3 -https://file-examples.com/storage/fe85f7a43b689349d9c8f18/2017/11/file_example_MP3_1MG.mp3 \ No newline at end of file +https://stream.chillhop.com/mp3/9476 \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d731ebe..9a732d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ mod scrapers; #[cfg(feature = "scrape")] use crate::scrapers::Source; + /// An extremely simple lofi player. #[derive(Parser, Clone)] #[command(about, version)] @@ -41,6 +42,10 @@ struct Args { #[clap(long, short, default_value_t = 12)] fps: u8, + /// Timeout in seconds for music downloads. + #[clap(long, default_value_t = 3)] + timeout: u64, + /// Include ALSA & other logs. #[clap(long, short)] debug: bool, @@ -50,7 +55,7 @@ struct Args { width: usize, /// Use a custom track list - #[clap(long, short, alias = "list", short_alias = 'l')] + #[clap(long, short, alias = "list", alias = "tracks", short_alias = 'l')] track_list: Option, /// Internal song buffer size. @@ -70,6 +75,7 @@ enum Commands { #[cfg(feature = "scrape")] Scrape { // The source to scrape from. + #[clap(long, short)] source: scrapers::Source, }, } @@ -91,7 +97,6 @@ async fn main() -> eyre::Result<()> { if let Some(command) = cli.command { match command { - // TODO: Actually distinguish between sources. #[cfg(feature = "scrape")] Commands::Scrape { source } => match source { Source::Archive => scrapers::archive::scrape().await?, diff --git a/src/play.rs b/src/play.rs index 41306eb..0c70229 100644 --- a/src/play.rs +++ b/src/play.rs @@ -70,7 +70,9 @@ pub async fn play(args: Args) -> eyre::Result<(), player::Error> { drop(stream); player.sink.stop(); - ui.map(|x| x.abort()); + if let Some(x) = ui { + x.abort(); + } Ok(()) } diff --git a/src/player.rs b/src/player.rs index c6ce22f..118a159 100644 --- a/src/player.rs +++ b/src/player.rs @@ -41,10 +41,6 @@ pub use error::Error; #[cfg(feature = "mpris")] pub mod mpris; -/// The time to wait in between errors. -/// TODO: Make this configurable. -const TIMEOUT: Duration = Duration::from_secs(3); - /// Main struct responsible for queuing up & playing tracks. // TODO: Consider refactoring [Player] from being stored in an [Arc], into containing many smaller [Arc]s. // TODO: In other words, this would change the type from `Arc` to just `Player`. @@ -76,6 +72,9 @@ pub struct Player { /// The bookmarks, which are saved on quit. pub bookmarks: Bookmarks, + /// The timeout for track downloads, as a [Duration]. + timeout: Duration, + /// The actual list of tracks to be played. list: List, @@ -144,14 +143,15 @@ impl Player { "/", env!("CARGO_PKG_VERSION") )) - .timeout(TIMEOUT * 5) + .timeout(Duration::from_secs(args.timeout * 5)) .build()?; let player = Self { tracks: RwLock::new(VecDeque::with_capacity(args.buffer_size)), buffer_size: args.buffer_size, current: ArcSwapOption::new(None), - progress: AtomicF32::new(-1.0), + progress: AtomicF32::new(0.0), + timeout: Duration::from_secs(args.timeout), bookmarks, client, sink, @@ -301,7 +301,7 @@ impl Player { let current = player.current.load(); let current = current.as_ref().unwrap(); - player.bookmarks.bookmark(&¤t).await?; + player.bookmarks.bookmark(current).await?; } Message::Quit => break, } diff --git a/src/player/bookmark.rs b/src/player/bookmark.rs index c71b7a4..e57594b 100644 --- a/src/player/bookmark.rs +++ b/src/player/bookmark.rs @@ -1,12 +1,15 @@ -use std::io::SeekFrom; +//! Module for handling saving, loading, and adding +//! bookmarks. + +use std::path::PathBuf; use std::sync::atomic::AtomicBool; -use tokio::fs::{create_dir_all, File, OpenOptions}; -use tokio::io::{self, AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use tokio::sync::RwLock; +use tokio::{fs, io}; use crate::{data_dir, tracks}; +/// Errors that might occur while managing bookmarks. #[derive(Debug, thiserror::Error)] pub enum BookmarkError { #[error("data directory not found")] @@ -18,57 +21,51 @@ pub enum BookmarkError { /// Manages the bookmarks in the current player. pub struct Bookmarks { + /// The different entries in the bookmarks file. entries: RwLock>, - file: RwLock, + + /// The internal bookmarked register, which keeps track + /// of whether a track is bookmarked or not. + /// + /// This is much more efficient than checking every single frame. bookmarked: AtomicBool, } impl Bookmarks { - pub async fn load() -> eyre::Result { + /// Gets the path of the bookmarks file. + pub async fn path() -> eyre::Result { let data_dir = data_dir().map_err(|_| BookmarkError::DataDir)?; - create_dir_all(data_dir.clone()).await?; + fs::create_dir_all(data_dir.clone()).await?; - let mut file = OpenOptions::new() - .create(true) - .write(true) - .read(true) - .append(false) - .truncate(false) - .open(data_dir.join("bookmarks.txt")) - .await?; - - let mut text = String::new(); - file.read_to_string(&mut text).await?; + Ok(data_dir.join("bookmarks.txt")) + } + /// Loads bookmarks from the `bookmarks.txt` file. + pub async fn load() -> eyre::Result { + let text = fs::read_to_string(Self::path().await?).await?; let lines: Vec = text .trim_start_matches("noheader") .trim() .lines() .filter_map(|x| { - if !x.is_empty() { - Some(x.to_string()) - } else { + if x.is_empty() { None + } else { + Some(x.to_string()) } }) .collect(); Ok(Self { entries: RwLock::new(lines), - file: RwLock::new(file), bookmarked: AtomicBool::new(false), }) } + // Saves the bookmarks to the `bookmarks.txt` file. pub async fn save(&self) -> eyre::Result<(), BookmarkError> { let text = format!("noheader\n{}", self.entries.read().await.join("\n")); - - let mut lock = self.file.write().await; - lock.seek(SeekFrom::Start(0)).await?; - lock.set_len(0).await?; - lock.write_all(text.as_bytes()).await?; - lock.flush().await?; - + fs::write(Self::path().await?, text).await?; Ok(()) } @@ -91,10 +88,14 @@ impl Bookmarks { Ok(()) } + /// Returns whether a track is bookmarked or not by using the internal + /// bookmarked register. pub fn bookmarked(&self) -> bool { self.bookmarked.load(std::sync::atomic::Ordering::Relaxed) } + /// Sets the internal bookmarked register by checking against + /// the current track's info. pub async fn set_bookmarked(&self, track: &tracks::Info) { let val = self.entries.read().await.contains(&track.to_entry()); self.bookmarked diff --git a/src/player/downloader.rs b/src/player/downloader.rs index 1c71e6b..4963139 100644 --- a/src/player/downloader.rs +++ b/src/player/downloader.rs @@ -8,7 +8,7 @@ use tokio::{ time::sleep, }; -use super::{Player, TIMEOUT}; +use super::Player; /// This struct is responsible for downloading tracks in the background. /// @@ -53,7 +53,7 @@ impl Downloader { } if !error.is_timeout() { - sleep(TIMEOUT).await; + sleep(self.player.timeout).await; } } } diff --git a/src/player/queue.rs b/src/player/queue.rs index 6997b0b..1fd665f 100644 --- a/src/player/queue.rs +++ b/src/player/queue.rs @@ -6,7 +6,7 @@ use tokio::{sync::mpsc::Sender, time::sleep}; use crate::{ messages::Message, - player::{downloader::Downloader, Player, TIMEOUT}, + player::{downloader::Downloader, Player}, tracks, }; @@ -76,7 +76,7 @@ impl Player { } if !error.is_timeout() { - sleep(TIMEOUT).await; + sleep(player.timeout).await; } tx.send(Message::TryAgain).await?; diff --git a/src/player/ui.rs b/src/player/ui.rs index 68b8623..259365c 100644 --- a/src/player/ui.rs +++ b/src/player/ui.rs @@ -159,8 +159,6 @@ impl Window { /// The code for the terminal interface itself. /// /// * `minimalist` - All this does is hide the bottom control bar. -/// * `borderless` - Whether to include borders or not. -/// * `width` - The width of player async fn interface( player: Arc, minimalist: bool, diff --git a/src/player/ui/components.rs b/src/player/ui/components.rs index 41cc730..6873850 100644 --- a/src/player/ui/components.rs +++ b/src/player/ui/components.rs @@ -87,7 +87,7 @@ impl ActionBar { Self::Muted => { let msg = "+ to increase volume"; - ("muted", Some((String::from(msg), msg.len()))) + ("muted,", Some((String::from(msg), msg.len()))) } }; diff --git a/src/scrapers.rs b/src/scrapers.rs index 522726d..041a696 100644 --- a/src/scrapers.rs +++ b/src/scrapers.rs @@ -12,6 +12,7 @@ pub mod archive; pub mod chillhop; pub mod lofigirl; +/// Represents the different sources which can be scraped. #[derive(Clone, Copy, PartialEq, Eq, Debug, ValueEnum)] pub enum Source { Lofigirl, @@ -20,6 +21,7 @@ pub enum Source { } impl Source { + /// Gets the cache directory name, for example, `chillhop`. pub fn cache_dir(&self) -> &'static str { match self { Source::Lofigirl => "lofigirl", @@ -28,6 +30,7 @@ impl Source { } } + /// Gets the full root URL of the source. pub fn url(&self) -> &'static str { match self { Source::Chillhop => "https://chillhop.com", diff --git a/src/tracks.rs b/src/tracks.rs index 78aad75..7ecf6f8 100644 --- a/src/tracks.rs +++ b/src/tracks.rs @@ -135,7 +135,7 @@ impl Info { .collect() } - /// Formats a name with [convert_case]. + /// Formats a name with [`convert_case`]. /// /// This will also strip the first few numbers that are /// usually present on most lofi tracks and do some other diff --git a/src/tracks/error.rs b/src/tracks/error.rs index 1f763d1..49a1e3c 100644 --- a/src/tracks/error.rs +++ b/src/tracks/error.rs @@ -44,7 +44,7 @@ where Kind: From, { fn from((track, err): (T, E)) -> Self { - Error { + Self { track: track.into(), kind: Kind::from(err), } diff --git a/src/tracks/list.rs b/src/tracks/list.rs index 2913438..e62b7c7 100644 --- a/src/tracks/list.rs +++ b/src/tracks/list.rs @@ -7,7 +7,6 @@ use atomic_float::AtomicF32; use bytes::{BufMut, Bytes, BytesMut}; use eyre::OptionExt as _; use futures::StreamExt; -use rand::Rng as _; use reqwest::Client; use tokio::fs; @@ -52,7 +51,7 @@ impl List { // We're also not pre-trimming `self.lines` into `base` & `tracks` due to // how rust vectors work, since it is slower to drain only a single element from // the start, so it's faster to just keep it in & work around it. - let random = rand::thread_rng().gen_range(1..self.lines.len()); + let random = fastrand::usize(1..self.lines.len()); let line = self.lines[random].clone(); if let Some((first, second)) = line.split_once('!') { @@ -134,7 +133,7 @@ impl List { let name = custom_name.map_or_else( || super::TrackName::Raw(path.clone()), - |formatted| super::TrackName::Formatted(formatted), + super::TrackName::Formatted, ); Ok(QueuedTrack { @@ -154,7 +153,7 @@ impl List { Self { lines, - path: path.map(|s| s.to_owned()), + path: path.map(ToOwned::to_owned), name: name.to_owned(), } } @@ -169,11 +168,9 @@ impl List { let raw = fs::read_to_string(path.clone()).await?; // Get rid of special noheader case for tracklists without a header. - let raw = if let Some(stripped) = raw.strip_prefix("noheader") { - stripped - } else { - &raw - }; + let raw = raw + .strip_prefix("noheader") + .map_or(raw.as_ref(), |stripped| stripped); let name = path .file_stem()