Add covert art rendering
This commit is contained in:
parent
a5efd3db5e
commit
c995653c62
7 changed files with 109 additions and 17 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
target/
|
target/
|
||||||
subtails.toml
|
subtails.toml
|
||||||
|
src/cover_default.png
|
||||||
|
|
76
Cargo.lock
generated
76
Cargo.lock
generated
|
@ -127,6 +127,12 @@ dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
|
@ -958,6 +964,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icy_sixel"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86858ae800284d596cfdefcb0ad435c3493c12f35367431bbe9b2b3858c1155b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1669,6 +1681,22 @@ dependencies = [
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratatui-image"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a07161c498ddd5066a444a869f27fd97cfc164dbee8c0bdb5ff803b8bb54ce"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"icy_sixel",
|
||||||
|
"image",
|
||||||
|
"rand",
|
||||||
|
"ratatui",
|
||||||
|
"rustix",
|
||||||
|
"thiserror",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rav1e"
|
name = "rav1e"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1783,7 +1811,7 @@ version = "0.12.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
|
checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -2141,6 +2169,7 @@ dependencies = [
|
||||||
"pipewire",
|
"pipewire",
|
||||||
"rand",
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"ratatui-image",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -2599,6 +2628,51 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-registry"
|
name = "windows-registry"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
|
@ -10,6 +10,7 @@ md5 = "0.7.0"
|
||||||
pipewire = "0.8.0"
|
pipewire = "0.8.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
|
ratatui-image = "3.0.0"
|
||||||
reqwest = { version = "0.12.9", features = ["blocking", "json"] }
|
reqwest = { version = "0.12.9", features = ["blocking", "json"] }
|
||||||
serde = { version = "1.0.215", features = ["derive"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::ssonic::response::Song;
|
use image::DynamicImage;
|
||||||
|
|
||||||
|
use crate::{ssonic::response::Song, utils::default_cover};
|
||||||
|
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -6,6 +8,7 @@ pub struct Metadata {
|
||||||
pub playing: bool,
|
pub playing: bool,
|
||||||
pub duration: i32,
|
pub duration: i32,
|
||||||
pub current_time: i32,
|
pub current_time: i32,
|
||||||
|
pub cover: DynamicImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
|
@ -16,6 +19,7 @@ impl Metadata {
|
||||||
playing: false,
|
playing: false,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
current_time: 0,
|
current_time: 0,
|
||||||
|
cover: default_cover(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub fn init(
|
||||||
player_chan,
|
player_chan,
|
||||||
player_chan_in,
|
player_chan_in,
|
||||||
playlist: playlist::Playlist::new(),
|
playlist: playlist::Playlist::new(),
|
||||||
tui_root: tui::Root::new(),
|
tui_root: tui::Root::new().unwrap(),
|
||||||
};
|
};
|
||||||
match player.begin() {
|
match player.begin() {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
|
@ -4,16 +4,19 @@ use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{Paragraph, Widget, Wrap},
|
widgets::{Paragraph, Widget, Wrap},
|
||||||
};
|
};
|
||||||
|
use ratatui_image::{picker::Picker, protocol::StatefulProtocol, StatefulImage};
|
||||||
|
|
||||||
use crate::utils::Error;
|
use crate::utils::Error;
|
||||||
|
|
||||||
use super::{current::Metadata, Player};
|
use super::{current::Metadata, Player};
|
||||||
|
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
|
image_state: StatefulProtocol,
|
||||||
|
proto_picker: Picker,
|
||||||
pub metadata: Metadata,
|
pub metadata: Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &Root {
|
impl Widget for &mut Root {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer)
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
@ -31,12 +34,17 @@ impl Widget for &Root {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Root {
|
impl Root {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self, Error> {
|
||||||
Self {
|
let mut proto_picker = Picker::from_query_stdio()?;
|
||||||
metadata: Metadata::new(),
|
let metadata = Metadata::new();
|
||||||
|
let image_state = proto_picker.new_resize_protocol(metadata.cover.clone());
|
||||||
|
Ok(Self {
|
||||||
|
metadata,
|
||||||
|
image_state,
|
||||||
|
proto_picker,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
fn render_info(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
fn render_info(&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let layout_info = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
let layout_info = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
let [cover, track_info] = layout_info.areas(area);
|
let [cover, track_info] = layout_info.areas(area);
|
||||||
self.render_cover(cover, buf);
|
self.render_cover(cover, buf);
|
||||||
|
@ -44,14 +52,11 @@ impl Root {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_progress(&self, area: Rect, buf: &mut Buffer) {}
|
fn render_progress(&self, area: Rect, buf: &mut Buffer) {}
|
||||||
fn render_cover(&self, area: Rect, buf: &mut Buffer) {}
|
fn render_cover(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
|
StatefulImage::new(None).render(area, buf, &mut self.image_state);
|
||||||
|
}
|
||||||
fn render_track_info(&self, area: Rect, buf: &mut Buffer) {
|
fn render_track_info(&self, area: Rect, buf: &mut Buffer) {
|
||||||
let layout_track = Layout::vertical([
|
let layout_track = Layout::vertical([Constraint::Min(20), Constraint::Min(10)]);
|
||||||
Constraint::Min(20),
|
|
||||||
Constraint::Min(10),
|
|
||||||
Constraint::Fill(3),
|
|
||||||
])
|
|
||||||
.flex(Flex::Start);
|
|
||||||
let [title, artist, _] = layout_track.areas(area);
|
let [title, artist, _] = layout_track.areas(area);
|
||||||
self.render_title(title, buf);
|
self.render_title(title, buf);
|
||||||
self.render_artist(artist, buf);
|
self.render_artist(artist, buf);
|
||||||
|
@ -72,7 +77,7 @@ impl Root {
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn update(&mut self) -> Result<(), Error> {
|
pub fn update(&mut self) -> Result<(), Error> {
|
||||||
self.terminal
|
self.terminal
|
||||||
.draw(|frame| frame.render_widget(&self.tui_root, frame.area()))?;
|
.draw(|frame| frame.render_widget(&mut self.tui_root, frame.area()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use image::DynamicImage;
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
@ -13,3 +14,9 @@ pub fn generate_random_salt() -> String {
|
||||||
pub fn restore() {
|
pub fn restore() {
|
||||||
ratatui::restore()
|
ratatui::restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_COVER_RAW: &'static [u8] = include_bytes!("cover_default.png");
|
||||||
|
|
||||||
|
pub fn default_cover() -> DynamicImage {
|
||||||
|
image::load_from_memory(DEFAULT_COVER_RAW).unwrap()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue