mirror of
https://github.com/talwat/lowfi
synced 2025-03-12 16:12:22 +00:00
feat: add experimental local file loading in track lists
This commit is contained in:
parent
b68ce27d19
commit
84f386e0eb
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -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"
|
||||||
|
@ -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"
|
||||||
|
16
README.md
16
README.md
@ -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
2
data/file.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
file:///home/user/Music/
|
||||||
|
Anomaly.mp3
|
@ -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()?;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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].
|
||||||
|
Loading…
x
Reference in New Issue
Block a user