feat: add experimental local file loading in track lists

This commit is contained in:
talwat 2025-02-28 19:07:18 +01:00
parent b68ce27d19
commit 84f386e0eb
8 changed files with 46 additions and 27 deletions

11
Cargo.lock generated
View File

@ -973,7 +973,7 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [ dependencies = [
"unicode-width 0.1.14", "unicode-width",
] ]
[[package]] [[package]]
@ -1453,7 +1453,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]] [[package]]
name = "lowfi" name = "lowfi"
version = "1.6.2-dev" version = "1.6.3-dev"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"arc-swap", "arc-swap",
@ -1472,7 +1472,6 @@ dependencies = [
"scraper", "scraper",
"tokio", "tokio",
"unicode-segmentation", "unicode-segmentation",
"unicode-width 0.2.0",
"url", "url",
] ]
@ -2819,12 +2818,6 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lowfi" name = "lowfi"
version = "1.6.2-dev" version = "1.6.3-dev"
edition = "2021" edition = "2021"
description = "An extremely simple lofi player." description = "An extremely simple lofi player."
license = "MIT" license = "MIT"
@ -50,5 +50,4 @@ Inflector = "0.11.4"
lazy_static = "1.5.0" lazy_static = "1.5.0"
libc = "0.2.167" libc = "0.2.167"
url = "2.5.4" url = "2.5.4"
unicode-width = "0.2.0"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"

View File

@ -121,8 +121,18 @@ Yeah, that's it.
### Extra Flags ### Extra Flags
If you have something you'd like to tweak about lowfi, you can run `lowfi help` If you have something you'd like to tweak about lowfi, you use additional flags which
to view the available options. 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`, `--tracklist <TRACKLIST>` | Use a [custom track list](#custom-track-lists) |
### Scraping ### Scraping
@ -166,7 +176,7 @@ This is also known as the "header", because it comes first.
Each track will be first appended to the base URL, and then the result use to download 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. the track. All tracks must be in the MP3 format, as lowfi doesn't support any others currently.
Additionally, lowfi *won't* put a `/` between the base & track for added flexibility, 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. 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://`, 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. where in that case the base will not be prepended to it.

2
data/file.txt Normal file
View File

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

View File

@ -236,7 +236,7 @@ impl Player {
// for only a frame in the other case that the buffer is not empty. // for only a frame in the other case that the buffer is not empty.
self.current.store(None); self.current.store(None);
self.list.random(&self.client).await? self.list.random(&self.client).await.0?
}; };
let decoded = track.decode()?; let decoded = track.decode()?;

View File

@ -51,10 +51,11 @@ impl Downloader {
while self.rx.recv().await == Some(()) { while self.rx.recv().await == Some(()) {
// For each update notification, we'll push tracks until the buffer is completely full. // For each update notification, we'll push tracks until the buffer is completely full.
while self.player.tracks.read().await.len() < BUFFER_SIZE { while self.player.tracks.read().await.len() < BUFFER_SIZE {
match self.player.list.random(&self.player.client).await { let (data, timeout) = self.player.list.random(&self.player.client).await;
match data {
Ok(track) => self.player.tracks.write().await.push_back(track), Ok(track) => self.player.tracks.write().await.push_back(track),
Err(error) => { Err(_) => {
if !error.is_timeout() { if !timeout {
sleep(TIMEOUT).await; sleep(TIMEOUT).await;
} }
} }

View File

@ -8,7 +8,7 @@ use bytes::Bytes;
use eyre::OptionExt as _; use eyre::OptionExt as _;
use inflector::Inflector as _; use inflector::Inflector as _;
use rodio::{Decoder, Source as _}; use rodio::{Decoder, Source as _};
use unicode_width::UnicodeWidthStr as _; use unicode_segmentation::UnicodeSegmentation;
use url::form_urlencoded; use url::form_urlencoded;
pub mod list; pub mod list;
@ -102,7 +102,7 @@ impl Info {
Ok(Self { Ok(Self {
duration: decoded.total_duration(), duration: decoded.total_duration(),
width: name.width(), width: name.graphemes(true).count(),
name, name,
}) })
} }

View File

@ -50,7 +50,7 @@ impl List {
} }
/// Downloads a raw track, but doesn't decode it. /// Downloads a raw track, but doesn't decode it.
async fn download(&self, track: &str, client: &Client) -> reqwest::Result<Bytes> { async fn download(&self, track: &str, client: &Client) -> (eyre::Result<Bytes>, bool) {
// If the track has a protocol, then we should ignore the base for it. // If the track has a protocol, then we should ignore the base for it.
let url = if track.contains("://") { let url = if track.contains("://") {
track.to_owned() track.to_owned()
@ -58,22 +58,36 @@ impl List {
format!("{}{}", self.base(), track) format!("{}{}", self.base(), track)
}; };
let response = client.get(url).send().await?; let (timeout, data) = if let Some(x) = url.strip_prefix("file://") {
let data = response.bytes().await?; let result = tokio::fs::read(x).await.unwrap();
(false, Ok(result.into()))
} else {
let response = client.get(url).send().await;
Ok(data) match response {
Ok(x) => (false, x.bytes().await),
Err(x) => (x.is_timeout(), Err(x)),
}
};
(data.map_err(|x| eyre::eyre!(x)), timeout)
} }
/// Fetches and downloads a random track from the [List]. /// Fetches and downloads a random track from the [List].
pub async fn random(&self, client: &Client) -> reqwest::Result<Track> { pub async fn random(&self, client: &Client) -> (eyre::Result<Track>, bool) {
let (path, custom_name) = self.random_path(); let (path, custom_name) = self.random_path();
let data = self.download(&path, client).await?; let (data, timeout) = self.download(&path, client).await;
let name = custom_name.map_or(super::TrackName::Raw(path), |formatted| { let name = custom_name.map_or(super::TrackName::Raw(path), |formatted| {
super::TrackName::Formatted(formatted) super::TrackName::Formatted(formatted)
}); });
Ok(Track { name, data }) let track = match data {
Ok(x) => Ok(Track { name, data: x }),
Err(x) => Err(x),
};
(track, timeout)
} }
/// Parses text into a [List]. /// Parses text into a [List].