Add covert art rendering

This commit is contained in:
Muaz Ahmad 2024-11-28 16:28:10 +05:00
parent a5efd3db5e
commit c995653c62
7 changed files with 109 additions and 17 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
target/ target/
subtails.toml subtails.toml
src/cover_default.png

76
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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) => {

View file

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

View file

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