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
This commit is contained in:
Tal 2025-09-25 15:39:20 +02:00 committed by GitHub
parent 4d4f5e0920
commit 0162421db4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 288 additions and 210 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.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]]

View File

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

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.

View File

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

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

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,51 @@ 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?;
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<Self, BookmarkError> {
let text = fs::read_to_string(Self::path().await?).await?;
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 +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

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

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