Compare commits

...

14 Commits

Author SHA1 Message Date
talwat
e702c1de00 docs: update feature list and fix some wording 2025-09-25 19:40:19 +02:00
talwat
8f837302c3 docs: update list of flags 2025-09-25 19:25:03 +02:00
talwat
226c674295 style: format code 2025-09-25 19:19:43 +02:00
talwat
66f2243b2c fix: don't crash if bookmarks.txt is missing
this was a one line fix that i just completely forgot about whilst improving the bookmarking system.
i feel very, very stupid.
2025-09-25 19:16:22 +02:00
Dario Griffo
05fe8069ea
docs: fix debian mirror url
Fix mirror url
2025-09-25 18:42:34 +02:00
talwat
9b61dffb12 chore: bump version due to mistake with scraper
yes, i messed up... it's fixed now.
2025-09-25 16:07:32 +02:00
talwat
c2530453fb chore: rescrape and update chillhop list 2025-09-25 16:07:07 +02:00
talwat
e4fd542edf chore: purge a few more songs from scraper 2025-09-25 16:03:32 +02:00
talwat
632b298de2 docs: properly link music.md 2025-09-25 15:50:26 +02:00
talwat
41ba98b9cf docs: add leading newline to MUSIC.md 2025-09-25 15:40:47 +02:00
Tal
0162421db4
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
2025-09-25 15:39:20 +02:00
Lim Ding Wen
4d4f5e0920
docs: add Fedora installation dependencies (#98) 2025-09-12 16:16:28 +02:00
danielwerg
d41bd16069
chore(chillhop): remove tracks with lyrics (#99) 2025-09-12 11:01:20 +02:00
Dan
d2c8bdb8aa
chore: update README.md (#96)
* chore: Update README.md

updated the custom tracklists section with the proper command

* Update README.md
2025-09-06 20:57:02 +02:00
25 changed files with 344 additions and 262 deletions

View File

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

210
Cargo.lock generated
View File

@ -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.2"
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]]

View File

@ -1,6 +1,6 @@
[package]
name = "lowfi"
version = "1.7.0-dev"
version = "1.7.2"
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"

75
MUSIC.md Normal file
View File

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

143
README.md
View File

@ -7,30 +7,18 @@ 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](MUSIC.md) for more information.
## Why?
I really hate modern music platforms, and I wanted a small, "suckless"
app that would just play random lofi without video.
I really hate modern music platforms, and I wanted a small, simple
app that would just play random ambient music without video and other fluff.
It was also designed to be fairly resilient to inconsistent networks,
Beyond that, it was also designed to be fairly resilient to inconsistent networks,
and as such it buffers 5 whole songs at a time instead of parts of the same song.
See [Scraping](#scraping) if you're interested in downloading the tracks.
Beware, there's a lot of them.
## Installing
> [!NOTE]
@ -46,8 +34,8 @@ On MacOS & Windows, no extra dependencies are needed.
On Linux, you'll also need openssl & alsa, as well as their headers.
- `alsa-lib` on Arch, `libasound2-dev` on Ubuntu.
- `openssl` on Arch, `libssl-dev` on Ubuntu.
- `alsa-lib` on Arch, `libasound2-dev` on Ubuntu, `alsa-lib-devel` on Fedora.
- `openssl` on Arch, `libssl-dev` on Ubuntu, `openssl-devel` on Fedora.
Make sure to also install `pulseaudio-alsa` if you're using PulseAudio.
@ -88,7 +76,7 @@ zypper install lowfi
```sh
curl -sS https://debian.griffo.io/3B9335DF576D3D58059C6AA50B56A1A69762E9FF.asc | gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/debian.griffo.io.gpg
echo "deb https://debian.griffo.io//apt $(lsb_release -sc 2>/dev/null) main" | sudo tee /etc/apt/sources.list.d/debian.griffo.io.list
echo "deb https://debian.griffo.io/apt $(lsb_release -sc 2>/dev/null) main" | sudo tee /etc/apt/sources.list.d/debian.griffo.io.list
sudo apt install -y lowfi
```
@ -135,12 +123,13 @@ 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
> and [MPRIS](https://wiki.archlinux.org/title/MPRIS) (with tools like `playerctl`).
>
> MPRIS is currently optional feature in cargo (enabled with `--features mpris`)
> MPRIS is currently an optional feature in cargo (enabled with `--features mpris`)
> due to it being only for Linux, as well as the fact that the main point of
> lowfi is it's unique & minimal interface.
@ -149,69 +138,87 @@ Yeah, that's it.
If you have something you'd like to tweak about lowfi, you use additional flags which
slightly tweak the UI or behaviour of the menu. The flags can be viewed with `lowfi help`.
| Flag | Function |
| ----------------------------------- | ---------------------------------------------- |
| `-a`, `--alternate` | Use an alternate terminal screen |
| `-m`, `--minimalist` | Hide the bottom control bar |
| `-b`, `--borderless` | Exclude borders in UI |
| `-p`, `--paused` | Start lowfi paused |
| `-d`, `--debug` | Include ALSA & other logs |
| `-w`, `--width <WIDTH>` | Width of the player, from 0 to 32 [default: 3] |
| `-t`, `--track-list <TRACK_LIST>` | Use a [custom track list](#custom-track-lists) |
| `-s`, `--buffer-size <BUFFER_SIZE>` | Internal song buffer size [default: 5] |
| Flag | Function |
| ----------------------------------- | --------------------------------------------------- |
| `-a`, `--alternate` | Use an alternate terminal screen |
| `-m`, `--minimalist` | Hide the bottom control bar |
| `-b`, `--borderless` | Exclude borders in UI |
| `-p`, `--paused` | Start lowfi paused |
| `-f`, `--fps` | FPS of the UI [default: 12] |
| `--timeout` | Timeout in seconds for music downloads [default: 3] |
| `-d`, `--debug` | Include ALSA & other logs |
| `-w`, `--width <WIDTH>` | Width of the player, from 0 to 32 [default: 3] |
| `-t`, `--track-list <TRACK_LIST>` | Use a [custom track list](#custom-track-lists) |
| `-s`, `--buffer-size <BUFFER_SIZE>` | Internal song buffer size [default: 5] |
### Scraping
### Extra Features
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 uses cargo/rust's "feature" system to make certain parts of the program optional,
like those which are only expected to be used by a handful of users.
An example of scrape is as follows,
#### `scrape` - Scraping
`lowfi scrape --extension zip --include-full`
This feature provides the `scrape` command.
It's usually not very useful, but is included for transparency's sake.
where more information can be found by running `lowfi help scrape`.
More information can be found by running `lowfi help scrape`.
#### `mpris` - MPRIS
Enables MPRIS. It's not rocket science.
#### `extra-audio-formats` - Extra Audio Formats
This is only relevant to those using a custom track list, in which case
it allows for more formats than just MP3. Those are FLAC, Vorbis, and WAV.
These should be sufficient for some 99% of music files people might want to play.
If you dealing with the 1% using another audio format which is in
[this list](https://github.com/pdeljanov/Symphonia?tab=readme-ov-file#codecs-decoders), open an issue.
### 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 `--tracks` 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
lowfi also supports custom track lists, although the default one from chillhop
is embedded into the binary.
To use a custom list, use the `--tracks` flag. This can either be a path to some file,
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.
For example, `lowfi --tracks minipop` would load `~/.local/share/lowfi/minipop.txt`.
Whereas if you did `lowfi --tracks ~/Music/minipop.txt` it would load from that
> [!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 the combination 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:
@ -237,13 +244,13 @@ For example, if you had an entry like this:
Then lowfi would download from the first section, and display the second as the track name.
You can also prepend `file://` to the header track name, which will make lowfi treat it as a local file.
This is useful if you want to use a local file as the base URL, such as:
`file://` can be used in front a track/header to make lowfi treat it as a local file.
This is useful if you want to use a local file as the base URL, for example:
```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.

View File

@ -30,7 +30,6 @@ https://stream.chillhop.com/mp3/
8943!Wondering
8906!Soundscapes
8421!Wine & Roses
8469!Interlude
8464!Underwater
8221!The Creator
8467!Cloud Steps
@ -151,7 +150,6 @@ https://stream.chillhop.com/mp3/
8118!Too Young For Secrets
8162!Noodlez
9269!Le Voyage Du Coeur
8157!As Long As I Got You
8540!3UO
8956!Tiger
9315!Sticks & Stones
@ -200,7 +198,6 @@ https://stream.chillhop.com/mp3/
8274!Lazy Pacific
8144!Digital Sex
8768!White Cadillac
8141!All Your Love
8140!San Junipero
8760!Body N Soul
8481!Hideout
@ -225,7 +222,6 @@ https://stream.chillhop.com/mp3/
9372!Love from NGC 7318
8554!Faces
8005!Interstellar
8001!The Dream
7998!Stay.
7994!Don't know why
7991!Chinatown
@ -313,7 +309,6 @@ https://stream.chillhop.com/mp3/
7839!New Light
7837!Lost Love
7834!Foggy Road
7832!Isolated
7830!Wistful
7842!Nimbus
7841!Chiaroscuro
@ -403,7 +398,6 @@ https://stream.chillhop.com/mp3/
8604!Sleepwell
9485!Dusk
9484!Slowdrive
9446!Quietly, Now
8321!Loved
7880!Get Up and Go
9488!Goodmorning
@ -601,14 +595,12 @@ https://stream.chillhop.com/mp3/
8536!Novel (Calm)
9255!Manner (Forms)
9447!Vinho Verde
9396!Upstream Color
8083!You're Asleep So I Gotta Keep It Down Shh
8688!Home Video
8514!Old Friends
9219!Growing Apart
7925!Upset (hold it in)
9222!Keep Going
9217!Feeling Lost
9228!Reflection
9225!Tumbling
9224!Fox
@ -735,7 +727,6 @@ https://stream.chillhop.com/mp3/
10450!Not A Cloud In Sight
10460!Deeper
10454!Creswick
10448!Slim Bobby
9771!Her Eyes
9775!Perspectives
8599!Otherside
@ -1003,7 +994,6 @@ https://stream.chillhop.com/mp3/
24830!Learning How to Skateboard
24829!Fireworks Festival
24828!Man I Miss My Cats
24827!I Thought Graduating Would Feel Weirder
24826!Setting Up Our Beds in Minecraft
24825!Library Card
24824!Tournament Arc
@ -1064,7 +1054,6 @@ https://stream.chillhop.com/mp3/
31610!Chill'n
31608!Vision Nocturne
31516!Tropical Midnight
31612!When I'm With U
31591!Night Fishing
31589!Lose Her Way
32888!Glow
@ -1120,7 +1109,6 @@ https://stream.chillhop.com/mp3/
36922!Treecko is a Cool Starter
37133!Penpals Perhaps
37127!Apple Turnover
41956!Making a Way
43908!Memory
43907!Soft Spot
43906!Envy You
@ -1177,7 +1165,6 @@ https://stream.chillhop.com/mp3/
55317!Fly High Newborn
55313!Coffeebreak
55310!Peaceful Dissociation
55372!Once
53592!Let Go
58841!Still Looking
58840!Longest Wait
@ -1319,15 +1306,8 @@ https://stream.chillhop.com/mp3/
64054!I Don't Want Love
64040!One for Florian
75541!Seu Trio
64045!Cut Free
75544!You Bring Me Life
64036!Curtain Call
64038!High Hope
64043!In the Sun
64047!Sleeping Norboo
64050!Autumn Turned Winter
64052!Light of World
64056!Harp Trees
64045!Cut Free
75547!Last One
75546!That Summer
75545!Hold it Down
@ -1335,6 +1315,12 @@ https://stream.chillhop.com/mp3/
75542!Hope
75540!When All I Heard Was Artifacts
75539!Cantar
64036!Curtain Call
64038!High Hope
64043!In the Sun
64047!Sleeping Norboo
64050!Autumn Turned Winter
64056!Harp Trees
79272!Early June
74856!Light of World
77527!Guitar Shop

View File

@ -1,2 +1,2 @@
file:///home/user/Music/
Anomaly.mp3
Test.mp3

2
data/noheader.txt Normal file
View File

@ -0,0 +1,2 @@
noheader
https://stream.chillhop.com/mp3/9476

View File

@ -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
https://stream.chillhop.com/mp3/9476

View File

@ -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<String>,
/// Internal song buffer size.
@ -91,7 +96,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?,

View File

@ -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(())
}

View File

@ -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<Player>` 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(&&current).await?;
player.bookmarks.bookmark(current).await?;
}
Message::Quit => break,
}

View File

@ -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,54 @@ pub enum BookmarkError {
/// Manages the bookmarks in the current player.
pub struct Bookmarks {
/// The different entries in the bookmarks file.
entries: RwLock<Vec<String>>,
file: RwLock<File>,
/// 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<Self, BookmarkError> {
/// Gets the path of the bookmarks file.
pub async fn path() -> eyre::Result<PathBuf, BookmarkError> {
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?;
Ok(data_dir.join("bookmarks.txt"))
}
let mut text = String::new();
file.read_to_string(&mut text).await?;
/// Loads bookmarks from the `bookmarks.txt` file.
pub async fn load() -> eyre::Result<Self, BookmarkError> {
let text = fs::read_to_string(Self::path().await?)
.await
.unwrap_or_default();
let lines: Vec<String> = 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 +91,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

View File

@ -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;
}
}
}

View File

@ -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?;

View File

@ -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<Player>,
minimalist: bool,

View File

@ -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())))
}
};

View File

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

View File

@ -118,13 +118,12 @@ pub async fn scrape() -> eyre::Result<()> {
const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36";
const TRACK_COUNT: u64 = 1625;
const IGNORED_TRACKS: [u32; 6] = [
74707, // 404
21655, // Lyrics
21773, // Lyrics
8172, // Lyrics
55397, // Lyrics
75135, // Lyrics
const IGNORED_TRACKS: [u32; 20] = [
// 404
74707, // Lyrics
21655, 21773, 8172, 55397, 75135, 24827, 8141, 8157, 64052, 31612, 41956, 8001, 9217,
55372, // Abnormal
8469, 7832, 10448, 9446, 9396,
];
const IGNORED_ARTISTS: [&str; 1] = [

View File

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

View File

@ -44,7 +44,7 @@ where
Kind: From<E>,
{
fn from((track, err): (T, E)) -> Self {
Error {
Self {
track: track.into(),
kind: Kind::from(err),
}

View File

@ -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()