Création de la crate pmometadata
This commit is contained in:
80
Cargo.lock
generated
80
Cargo.lock
generated
@@ -24,15 +24,6 @@ dependencies = [
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@@ -338,21 +329,6 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
@@ -1388,12 +1364,6 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.30.8"
|
||||
@@ -1894,17 +1864,6 @@ dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
@@ -2455,15 +2414,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ogg"
|
||||
version = "0.8.0"
|
||||
@@ -2668,6 +2618,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"paste",
|
||||
"pmoflac",
|
||||
"pmometadata",
|
||||
"soxr",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@@ -2826,6 +2777,16 @@ dependencies = [
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pmometadata"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"paste",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pmoparadise"
|
||||
version = "0.1.0"
|
||||
@@ -3485,12 +3446,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -4294,30 +4249,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2 0.6.0",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -19,4 +19,5 @@ members = [
|
||||
"pmosource",
|
||||
"pmoplaylist",
|
||||
"pmoflac",
|
||||
"pmometadata",
|
||||
]
|
||||
|
||||
10
pmometadata/Cargo.toml
Normal file
10
pmometadata/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "pmometadata"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.89"
|
||||
paste = "1"
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
423
pmometadata/src/lib.rs
Normal file
423
pmometadata/src/lib.rs
Normal file
@@ -0,0 +1,423 @@
|
||||
//! Minimal metadata abstraction shared between PMO crates.
|
||||
#![allow(async_fn_in_trait)]
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Convenience alias for metadata operations that can fail or return no value.
|
||||
pub type MetadataResult<T> = Result<Option<T>, MetadataError>;
|
||||
|
||||
/// Errors that can occur when manipulating metadata.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MetadataError {
|
||||
#[error("metadata field is not implemented")]
|
||||
NotImplemented,
|
||||
#[error("metadata field is read-only")]
|
||||
ReadOnly,
|
||||
#[error("backend error: {0}")]
|
||||
Backend(String),
|
||||
}
|
||||
|
||||
impl MetadataError {
|
||||
fn is_transient(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
MetadataError::NotImplemented | MetadataError::ReadOnly
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented by metadata providers.
|
||||
///
|
||||
/// Only the fields supported by the implementation have to be overridden.
|
||||
#[async_trait]
|
||||
pub trait TrackMetadata: Send + Sync {
|
||||
async fn get_title(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_title(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_artist(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_artist(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_album(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_album(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_year(&self) -> MetadataResult<u32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_year(&mut self, _value: Option<u32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_duration(&self) -> MetadataResult<Duration> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_duration(&mut self, _value: Option<Duration>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_track_id(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_track_id(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_channel_id(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_channel_id(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_event(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_event(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_rating(&self) -> MetadataResult<f32> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_rating(&mut self, _value: Option<f32>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_cover_url(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_cover_url(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_cover_pk(&self) -> MetadataResult<String> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_cover_pk(&mut self, _value: Option<String>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_extra(&self) -> MetadataResult<HashMap<String, String>> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn set_extra(&mut self, _value: Option<HashMap<String, String>>) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn get_updated_at(&self) -> MetadataResult<SystemTime> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
|
||||
async fn touch(&mut self) -> MetadataResult<()> {
|
||||
Err(MetadataError::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn copy_metadata_into<S, D>(
|
||||
src: &Arc<RwLock<S>>,
|
||||
dest: &Arc<RwLock<D>>,
|
||||
) -> Result<(), MetadataError>
|
||||
where
|
||||
S: TrackMetadata + ?Sized,
|
||||
D: TrackMetadata + ?Sized,
|
||||
{
|
||||
let src_guard = src.read().await;
|
||||
|
||||
// Expansion de copy_metadata! pour toutes les propriétés
|
||||
match src_guard.get_title().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_title(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_title(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_artist().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_artist(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_artist(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_album().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_album(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_album(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_year().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_year(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_year(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_duration().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_duration(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_duration(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_track_id().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_track_id(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_track_id(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_channel_id().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_channel_id(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_channel_id(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_event().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_event(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_event(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_rating().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_rating(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_rating(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_cover_url().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_cover_url(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_cover_url(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_cover_pk().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_cover_pk(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_cover_pk(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
match src_guard.get_extra().await {
|
||||
Ok(Some(value)) => {
|
||||
dest.write().await.set_extra(Some(value)).await?;
|
||||
},
|
||||
Ok(None) | Err(_) => {
|
||||
dest.write().await.set_extra(None).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
dest.write().await.touch().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// In-memory metadata implementation with full read/write support.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct MemoryTrackMetadata {
|
||||
title: Option<String>,
|
||||
artist: Option<String>,
|
||||
album: Option<String>,
|
||||
year: Option<u32>,
|
||||
duration: Option<Duration>,
|
||||
elapsed: Option<Duration>,
|
||||
track_id: Option<String>,
|
||||
channel_id: Option<String>,
|
||||
event: Option<String>,
|
||||
rating: Option<f32>,
|
||||
cover_url: Option<String>,
|
||||
cover_pk: Option<String>,
|
||||
updated_at: Option<SystemTime>,
|
||||
extra: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl MemoryTrackMetadata {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TrackMetadata for MemoryTrackMetadata {
|
||||
async fn get_title(&self) -> MetadataResult<String> {
|
||||
Ok(self.title.clone())
|
||||
}
|
||||
|
||||
async fn set_title(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.title = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_artist(&self) -> MetadataResult<String> {
|
||||
Ok(self.artist.clone())
|
||||
}
|
||||
|
||||
async fn set_artist(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.artist = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_album(&self) -> MetadataResult<String> {
|
||||
Ok(self.album.clone())
|
||||
}
|
||||
|
||||
async fn set_album(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.album = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_year(&self) -> MetadataResult<u32> {
|
||||
Ok(self.year)
|
||||
}
|
||||
|
||||
async fn set_year(&mut self, value: Option<u32>) -> MetadataResult<()> {
|
||||
self.year = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_duration(&self) -> MetadataResult<Duration> {
|
||||
Ok(self.duration)
|
||||
}
|
||||
|
||||
async fn set_duration(&mut self, value: Option<Duration>) -> MetadataResult<()> {
|
||||
self.duration = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_track_id(&self) -> MetadataResult<String> {
|
||||
Ok(self.track_id.clone())
|
||||
}
|
||||
|
||||
async fn set_track_id(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.track_id = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_channel_id(&self) -> MetadataResult<String> {
|
||||
Ok(self.channel_id.clone())
|
||||
}
|
||||
|
||||
async fn set_channel_id(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.channel_id = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_event(&self) -> MetadataResult<String> {
|
||||
Ok(self.event.clone())
|
||||
}
|
||||
|
||||
async fn set_event(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.event = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_rating(&self) -> MetadataResult<f32> {
|
||||
Ok(self.rating)
|
||||
}
|
||||
|
||||
async fn set_rating(&mut self, value: Option<f32>) -> MetadataResult<()> {
|
||||
self.rating = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_cover_url(&self) -> MetadataResult<String> {
|
||||
Ok(self.cover_url.clone())
|
||||
}
|
||||
|
||||
async fn set_cover_url(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.cover_url = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_cover_pk(&self) -> MetadataResult<String> {
|
||||
Ok(self.cover_pk.clone())
|
||||
}
|
||||
|
||||
async fn set_cover_pk(&mut self, value: Option<String>) -> MetadataResult<()> {
|
||||
self.cover_pk = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_extra(&self) -> MetadataResult<HashMap<String, String>> {
|
||||
Ok(self.extra.clone())
|
||||
}
|
||||
|
||||
async fn set_extra(&mut self, value: Option<HashMap<String, String>>) -> MetadataResult<()> {
|
||||
self.extra = value;
|
||||
self.touch().await?;
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
async fn get_updated_at(&self) -> MetadataResult<SystemTime> {
|
||||
Ok(self.updated_at)
|
||||
}
|
||||
|
||||
async fn touch(&mut self) -> MetadataResult<()> {
|
||||
self.updated_at = Some(SystemTime::now());
|
||||
Ok(Some(()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user