Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,103 changes: 0 additions & 4,103 deletions Cargo.lock

This file was deleted.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,5 @@ futures-util = { version = "0.3.30", optional = true }
# shared
regex = { version = "1.10.3", default-features = false, features = [
"std",
], optional = true } # music, sys_info
], optional = true } # music, sys_info

4 changes: 4 additions & 0 deletions src/clients/music/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub enum PlayerUpdate {
/// Triggered at regular intervals while a track is playing.
/// Used to keep track of the progress through the current track.
ProgressTick(ProgressTick),
UpdateImage(Option<Vec<u8>>),
}

#[derive(Clone, Debug)]
Expand All @@ -32,6 +33,7 @@ pub struct Track {
pub genre: Option<String>,
pub track: Option<u64>,
pub cover_path: Option<String>,
pub uri: Option<String>,
}

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -65,6 +67,8 @@ pub trait MusicClient: Debug + Send + Sync {
fn seek(&self, duration: Duration) -> Result<()>;

fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate>;

fn send_album_art(&self, uri: String) -> Result<()>;
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
Expand Down
143 changes: 116 additions & 27 deletions src/clients/music/mpd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use mpd_client::client::{ConnectionEvent, Subsystem};
use mpd_client::commands::{self, SeekMode};
use mpd_client::responses::{PlayState, Song};
use mpd_client::tag::Tag;
use mpd_utils::mpd_client::commands::Command;
use mpd_utils::mpd_client::responses::TypedResponseError;
use mpd_utils::{mpd_client, PersistentClient};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
Expand Down Expand Up @@ -93,11 +96,13 @@ impl Client {
let status = client.command(commands::Status).await;

if let (Ok(current_song), Ok(status)) = (current_song, status) {
let track = current_song.map(|s| convert_song(&s.song, music_dir));
let track = current_song
.clone()
.map(|s| convert_song(&s.song, music_dir));
let status = Status::from(status);

let update = PlayerUpdate::Update(Box::new(track), status);
send!(tx, update);
let update_info = PlayerUpdate::Update(Box::new(track), status);
send!(tx, update_info);
}

Ok(())
Expand All @@ -119,6 +124,76 @@ impl Client {
}
}

fn convert_song(song: &Song, music_dir: &Path) -> Track {
let (track, disc) = song.number();

let cover_path = music_dir
.join(
song.file_path()
.parent()
.expect("Song Path should not be the root")
.join("cover.jpg"),
)
.into_os_string()
.into_string()
.ok();

Track {
title: song.title().map(ToString::to_string),
album: song.album().map(ToString::to_string),
artist: Some(song.artists().join(", ")),
date: try_get_first_tag(song, &Tag::Date).map(ToString::to_string),
genre: try_get_first_tag(song, &Tag::Genre).map(ToString::to_string),
disc: Some(disc),
track: Some(track),
uri: Some(song.file_path().display().to_string()),
cover_path,
}
}
pub async fn get_picture(
client: &PersistentClient,
uri: &str,
) -> Result<Vec<u8>, TypedResponseError> {
let mut offset = 0;

let mut slice = client
.command(ReadPicture {
uri: uri.to_string(),
offset,
})
.await
.map_err(Report::new)
.map_err(|e| {
tracing::error!("{e:#?}");
TypedResponseError::missing("cover art")
})?;
let total_length = slice.0;
let mut buffer = Vec::with_capacity(total_length as usize);
offset += slice.1.len();
buffer
.write_all(slice.1.as_slice())
.expect("Writing to in memory buffer");
while offset < total_length as usize {
slice = client
.command(ReadPicture {
uri: uri.to_string(),
offset,
})
.await
.map_err(Report::new)
.map_err(|e| {
tracing::error!("{e:#?}");
TypedResponseError::missing("cover art")
})?;
offset += slice.1.len();
buffer
.write_all(slice.1.as_slice())
.expect("Writing to an in memory buffer");
}
Write::flush(&mut buffer).unwrap();
Ok(buffer)
}

impl MusicClient for Client {
fn play(&self) -> Result<()> {
command!(self, commands::SetPause(false))
Expand Down Expand Up @@ -153,31 +228,12 @@ impl MusicClient for Client {
});
rx
}
}

fn convert_song(song: &Song, music_dir: &Path) -> Track {
let (track, disc) = song.number();

let cover_path = music_dir
.join(
song.file_path()
.parent()
.expect("Song path should not be root")
.join("cover.jpg"),
)
.into_os_string()
.into_string()
.ok();
Comment thread
eternalfrustation marked this conversation as resolved.

Track {
title: song.title().map(ToString::to_string),
album: song.album().map(ToString::to_string),
artist: Some(song.artists().join(", ")),
date: try_get_first_tag(song, &Tag::Date).map(ToString::to_string),
genre: try_get_first_tag(song, &Tag::Genre).map(ToString::to_string),
disc: Some(disc),
track: Some(track),
cover_path,
fn send_album_art(&self, uri: String) -> Result<()> {
let cover_image = await_sync(get_picture(self.client.as_ref(), uri.as_str())).ok();
let update_image = PlayerUpdate::UpdateImage(cover_image);
send!(self.tx, update_image);
Ok(())
}
}

Expand Down Expand Up @@ -209,3 +265,36 @@ impl From<PlayState> for PlayerState {
}
}
}

pub struct ReadPicture {
pub uri: String,
pub offset: usize,
}

impl Command for ReadPicture {
type Response = (u32, Vec<u8>);

fn command(&self) -> mpd_client::protocol::Command {
mpd_client::protocol::Command::new("readpicture")
.argument(self.uri.clone())
.argument(self.offset)
}

fn response(
self,
frame: mpd_client::protocol::response::Frame,
) -> Result<Self::Response, mpd_client::responses::TypedResponseError> {
if frame.is_empty() || !frame.has_binary() {
Err(TypedResponseError::missing("id3v2 thumbnail"))
} else {
Ok((
frame
.find("size")
.expect("Having a size field")
.parse()
.expect("Getting a unsigned int for the size field"),
frame.binary().map(|b| b.to_vec()).unwrap(),
))
}
}
}
9 changes: 9 additions & 0 deletions src/clients/music/mpris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,17 @@ impl MusicClient for Client {

rx
}

fn send_album_art(&self, _uri: String) -> Result<()> {
Ok(())
}
}

impl From<Metadata> for Track {
fn from(value: Metadata) -> Self {
const KEY_DATE: &str = "xesam:contentCreated";
const KEY_GENRE: &str = "xesam:genre";
const KEY_URL: &str = "xesam:url";

Comment thread
eternalfrustation marked this conversation as resolved.
Self {
title: value
Expand All @@ -314,6 +319,10 @@ impl From<Metadata> for Track {
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
track: value.track_number().map(|track| track as u64),
cover_path: value.art_url().map(ToString::to_string),
uri: value
.get(KEY_URL)
.and_then(mpris::MetadataValue::as_str_array)
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
}
}
}
Expand Down
45 changes: 25 additions & 20 deletions src/image/provider.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::desktop_file::get_desktop_icon_name;
#[cfg(feature = "http")]
use crate::{glib_recv_mpsc, send_async, spawn};
use cfg_if::cfg_if;
use color_eyre::{Help, Report, Result};
use gtk::cairo::Surface;
use gtk::gdk::ffi::gdk_cairo_surface_create_from_pixbuf;
Expand All @@ -13,12 +12,8 @@ use std::path::{Path, PathBuf};
use tokio::sync::mpsc;
use tracing::warn;

cfg_if!(
if #[cfg(feature = "http")] {
use gtk::gio::{Cancellable, MemoryInputStream};
use tracing::error;
}
);
use gtk::gio::{Cancellable, MemoryInputStream};
use tracing::error;

#[derive(Debug)]
enum ImageLocation<'a> {
Expand Down Expand Up @@ -157,21 +152,9 @@ impl<'a> ImageProvider<'a> {
{
let size = self.size;
glib_recv_mpsc!(rx, bytes => {
let stream = MemoryInputStream::from_bytes(&bytes);

let scale = image.scale_factor();
let scaled_size = size * scale;

let pixbuf = Pixbuf::from_stream_at_scale(
&stream,
scaled_size,
scaled_size,
true,
Some(&Cancellable::new()),
);

// Different error types makes this a bit awkward
match pixbuf.map(|pixbuf| Self::create_and_load_surface(&pixbuf, &image, scale))
match Self::load_into_image_from_encoded(size,&bytes, &image)
{
Ok(Err(err)) => error!("{err:?}"),
Err(err) => error!("{err:?}"),
Expand All @@ -189,6 +172,28 @@ impl<'a> ImageProvider<'a> {
Ok(())
}

pub fn load_into_image_from_encoded(
size: i32,
bytes: &glib::Bytes,
image: &gtk::Image,
) -> Result<Result<()>, glib::Error> {
let stream = MemoryInputStream::from_bytes(bytes);

let scale = image.scale_factor();
let scaled_size = size * scale;

let pixbuf = Pixbuf::from_stream_at_scale(
&stream,
scaled_size,
scaled_size,
true,
Some(&Cancellable::new()),
);

// Different error types makes this a bit awkward
pixbuf.map(|pixbuf| Self::create_and_load_surface(&pixbuf, image, scale))
}

/// Attempts to synchronously fetch an image from location
/// and load into into the image.
fn load_into_image_sync(&self, image: &gtk::Image) -> Result<()> {
Expand Down
1 change: 0 additions & 1 deletion src/modules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::rc::Rc;
use std::sync::Arc;

use color_eyre::Result;
use glib::IsA;
use gtk::gdk::{EventMask, Monitor};
use gtk::prelude::*;
use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widget};
Expand Down
6 changes: 4 additions & 2 deletions src/modules/music/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ pub struct MusicModule {
pub(crate) icon_size: i32,

#[serde(default = "default_cover_image_size")]
pub(crate) cover_image_size: i32,
pub(crate) cover_image_size: u32,

pub(crate) cover_image_path: Option<PathBuf>,

// -- Common --
pub(crate) truncate: Option<TruncateMode>,
Expand Down Expand Up @@ -152,6 +154,6 @@ const fn default_icon_size() -> i32 {
24
}

const fn default_cover_image_size() -> i32 {
const fn default_cover_image_size() -> u32 {
128
}
Loading