Elabore une crate pmosource
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -2355,6 +2355,7 @@ dependencies = [
|
|||||||
"hound",
|
"hound",
|
||||||
"pmodidl",
|
"pmodidl",
|
||||||
"pmoserver",
|
"pmoserver",
|
||||||
|
"pmosource",
|
||||||
"pmoupnp",
|
"pmoupnp",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2384,6 +2385,7 @@ dependencies = [
|
|||||||
"pmocovers",
|
"pmocovers",
|
||||||
"pmodidl",
|
"pmodidl",
|
||||||
"pmoserver",
|
"pmoserver",
|
||||||
|
"pmosource",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -2418,6 +2420,14 @@ dependencies = [
|
|||||||
"utoipa-swagger-ui",
|
"utoipa-swagger-ui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pmosource"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"image",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pmoupnp"
|
name = "pmoupnp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["PMOMusic", "pmoupnp", "pmomediarenderer", "pmoconfig", "pmoutils", "pmodidl", "pmoserver", "pmoapp", "pmocache", "pmocovers", "pmoaudiocache", "pmoaudio", "pmoqobuz", "pmoparadise"]
|
members = ["PMOMusic", "pmoupnp", "pmomediarenderer", "pmoconfig", "pmoutils", "pmodidl", "pmoserver", "pmoapp", "pmocache", "pmocovers", "pmoaudiocache", "pmoaudio", "pmoqobuz", "pmoparadise", "pmosource"]
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ pmoserver = { path = "../pmoserver", optional = true }
|
|||||||
pmodidl = { path = "../pmodidl", optional = true }
|
pmodidl = { path = "../pmodidl", optional = true }
|
||||||
uuid = { version = "1.18", optional = true }
|
uuid = { version = "1.18", optional = true }
|
||||||
|
|
||||||
|
# Common music source traits
|
||||||
|
pmosource = { path = "../pmosource" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["metadata-only"]
|
default = ["metadata-only"]
|
||||||
# Mode métadonnées seules (pas de décodage FLAC)
|
# Mode métadonnées seules (pas de décodage FLAC)
|
||||||
|
|||||||
BIN
pmoparadise/assets/default.webp
Normal file
BIN
pmoparadise/assets/default.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
46
pmoparadise/examples/show_source_image.rs
Normal file
46
pmoparadise/examples/show_source_image.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//! Example showing how to access and save the Radio Paradise source image
|
||||||
|
//!
|
||||||
|
//! This example demonstrates:
|
||||||
|
//! - Getting source information via the MusicSource trait
|
||||||
|
//! - Accessing the embedded WebP image
|
||||||
|
//! - Optionally saving it to a file
|
||||||
|
|
||||||
|
use pmoparadise::RadioParadiseSource;
|
||||||
|
use pmosource::MusicSource;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Create the source
|
||||||
|
let source = RadioParadiseSource;
|
||||||
|
|
||||||
|
// Display source information
|
||||||
|
println!("Music Source Information");
|
||||||
|
println!("========================");
|
||||||
|
println!("Name: {}", source.name());
|
||||||
|
println!("ID: {}", source.id());
|
||||||
|
println!("Image MIME type: {}", source.default_image_mime_type());
|
||||||
|
|
||||||
|
// Get the embedded image
|
||||||
|
let image_data = source.default_image();
|
||||||
|
println!("Embedded image size: {} bytes", image_data.len());
|
||||||
|
|
||||||
|
// Verify WebP format
|
||||||
|
if image_data.len() >= 12 {
|
||||||
|
let is_webp = &image_data[0..4] == b"RIFF" && &image_data[8..12] == b"WEBP";
|
||||||
|
println!("Valid WebP format: {}", is_webp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: save to file
|
||||||
|
if std::env::args().any(|arg| arg == "--save") {
|
||||||
|
let filename = format!("{}_default.webp", source.id());
|
||||||
|
let mut file = fs::File::create(&filename)?;
|
||||||
|
file.write_all(image_data)?;
|
||||||
|
println!("\nImage saved to: {}", filename);
|
||||||
|
println!("You can view it with: open {}", filename);
|
||||||
|
} else {
|
||||||
|
println!("\nTo save the image to disk, run with: --save");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -195,6 +195,7 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod source;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
|
||||||
#[cfg(feature = "per-track")]
|
#[cfg(feature = "per-track")]
|
||||||
@@ -207,6 +208,7 @@ pub mod mediaserver;
|
|||||||
pub use client::{ClientBuilder, RadioParadiseClient};
|
pub use client::{ClientBuilder, RadioParadiseClient};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use models::{Bitrate, Block, DurationMs, EventId, NowPlaying, Song};
|
pub use models::{Bitrate, Block, DurationMs, EventId, NowPlaying, Song};
|
||||||
|
pub use source::RadioParadiseSource;
|
||||||
pub use stream::BlockStream;
|
pub use stream::BlockStream;
|
||||||
|
|
||||||
#[cfg(feature = "per-track")]
|
#[cfg(feature = "per-track")]
|
||||||
|
|||||||
70
pmoparadise/src/source.rs
Normal file
70
pmoparadise/src/source.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//! Music source implementation for Radio Paradise
|
||||||
|
//!
|
||||||
|
//! This module implements the [`pmosource::MusicSource`] trait for Radio Paradise,
|
||||||
|
//! providing access to the service's default image and identification information.
|
||||||
|
|
||||||
|
use pmosource::MusicSource;
|
||||||
|
|
||||||
|
/// Default image for Radio Paradise (300x300 WebP, embedded in binary)
|
||||||
|
const DEFAULT_IMAGE: &[u8] = include_bytes!("../assets/default.webp");
|
||||||
|
|
||||||
|
/// Radio Paradise music source
|
||||||
|
///
|
||||||
|
/// This struct implements the [`MusicSource`] trait to provide
|
||||||
|
/// standardized access to Radio Paradise's identification and branding.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use pmoparadise::RadioParadiseSource;
|
||||||
|
/// use pmosource::MusicSource;
|
||||||
|
///
|
||||||
|
/// let source = RadioParadiseSource;
|
||||||
|
/// assert_eq!(source.name(), "Radio Paradise");
|
||||||
|
/// assert_eq!(source.id(), "radio-paradise");
|
||||||
|
///
|
||||||
|
/// // Get default image as WebP bytes
|
||||||
|
/// let image_data = source.default_image();
|
||||||
|
/// assert!(image_data.len() > 0);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct RadioParadiseSource;
|
||||||
|
|
||||||
|
impl MusicSource for RadioParadiseSource {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Radio Paradise"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"radio-paradise"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_image(&self) -> &[u8] {
|
||||||
|
DEFAULT_IMAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_source_info() {
|
||||||
|
let source = RadioParadiseSource;
|
||||||
|
assert_eq!(source.name(), "Radio Paradise");
|
||||||
|
assert_eq!(source.id(), "radio-paradise");
|
||||||
|
assert_eq!(source.default_image_mime_type(), "image/webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_image_present() {
|
||||||
|
let source = RadioParadiseSource;
|
||||||
|
let image = source.default_image();
|
||||||
|
assert!(image.len() > 0, "Default image should not be empty");
|
||||||
|
|
||||||
|
// Check WebP magic bytes (RIFF...WEBP)
|
||||||
|
assert!(image.len() >= 12, "Image too small to be valid WebP");
|
||||||
|
assert_eq!(&image[0..4], b"RIFF", "Missing RIFF header");
|
||||||
|
assert_eq!(&image[8..12], b"WEBP", "Missing WEBP signature");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,9 @@ axum = { version = "0.8", optional = true }
|
|||||||
# Documentation OpenAPI
|
# Documentation OpenAPI
|
||||||
utoipa = { version = "5.3", optional = true }
|
utoipa = { version = "5.3", optional = true }
|
||||||
|
|
||||||
|
# Common music source traits
|
||||||
|
pmosource = { path = "../pmosource" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
# Feature pour activer les extensions pmoserver
|
# Feature pour activer les extensions pmoserver
|
||||||
|
|||||||
BIN
pmoqobuz/assets/default.webp
Normal file
BIN
pmoqobuz/assets/default.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
46
pmoqobuz/examples/show_source_image.rs
Normal file
46
pmoqobuz/examples/show_source_image.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//! Example showing how to access and save the Qobuz source image
|
||||||
|
//!
|
||||||
|
//! This example demonstrates:
|
||||||
|
//! - Getting source information via the MusicSource trait
|
||||||
|
//! - Accessing the embedded WebP image
|
||||||
|
//! - Optionally saving it to a file
|
||||||
|
|
||||||
|
use pmoqobuz::QobuzSource;
|
||||||
|
use pmosource::MusicSource;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Create the source
|
||||||
|
let source = QobuzSource;
|
||||||
|
|
||||||
|
// Display source information
|
||||||
|
println!("Music Source Information");
|
||||||
|
println!("========================");
|
||||||
|
println!("Name: {}", source.name());
|
||||||
|
println!("ID: {}", source.id());
|
||||||
|
println!("Image MIME type: {}", source.default_image_mime_type());
|
||||||
|
|
||||||
|
// Get the embedded image
|
||||||
|
let image_data = source.default_image();
|
||||||
|
println!("Embedded image size: {} bytes", image_data.len());
|
||||||
|
|
||||||
|
// Verify WebP format
|
||||||
|
if image_data.len() >= 12 {
|
||||||
|
let is_webp = &image_data[0..4] == b"RIFF" && &image_data[8..12] == b"WEBP";
|
||||||
|
println!("Valid WebP format: {}", is_webp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: save to file
|
||||||
|
if std::env::args().any(|arg| arg == "--save") {
|
||||||
|
let filename = format!("{}_default.webp", source.id());
|
||||||
|
let mut file = fs::File::create(&filename)?;
|
||||||
|
file.write_all(image_data)?;
|
||||||
|
println!("\nImage saved to: {}", filename);
|
||||||
|
println!("You can view it with: open {}", filename);
|
||||||
|
} else {
|
||||||
|
println!("\nTo save the image to disk, run with: --save");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -151,6 +151,7 @@ pub mod client;
|
|||||||
pub mod didl;
|
pub mod didl;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod source;
|
||||||
|
|
||||||
// Extension pmoserver (feature-gated)
|
// Extension pmoserver (feature-gated)
|
||||||
#[cfg(feature = "pmoserver")]
|
#[cfg(feature = "pmoserver")]
|
||||||
@@ -165,6 +166,7 @@ mod pmoserver_impl;
|
|||||||
pub use client::QobuzClient;
|
pub use client::QobuzClient;
|
||||||
pub use error::{QobuzError, Result};
|
pub use error::{QobuzError, Result};
|
||||||
pub use models::{Album, Artist, AudioFormat, Genre, Playlist, SearchResult, Track};
|
pub use models::{Album, Artist, AudioFormat, Genre, Playlist, SearchResult, Track};
|
||||||
|
pub use source::QobuzSource;
|
||||||
|
|
||||||
/// Ré-exporte les types DIDL pour faciliter l'utilisation
|
/// Ré-exporte les types DIDL pour faciliter l'utilisation
|
||||||
pub use didl::ToDIDL;
|
pub use didl::ToDIDL;
|
||||||
|
|||||||
70
pmoqobuz/src/source.rs
Normal file
70
pmoqobuz/src/source.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//! Music source implementation for Qobuz
|
||||||
|
//!
|
||||||
|
//! This module implements the [`pmosource::MusicSource`] trait for Qobuz,
|
||||||
|
//! providing access to the service's default image and identification information.
|
||||||
|
|
||||||
|
use pmosource::MusicSource;
|
||||||
|
|
||||||
|
/// Default image for Qobuz (300x300 WebP, embedded in binary)
|
||||||
|
const DEFAULT_IMAGE: &[u8] = include_bytes!("../assets/default.webp");
|
||||||
|
|
||||||
|
/// Qobuz music source
|
||||||
|
///
|
||||||
|
/// This struct implements the [`MusicSource`] trait to provide
|
||||||
|
/// standardized access to Qobuz's identification and branding.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use pmoqobuz::QobuzSource;
|
||||||
|
/// use pmosource::MusicSource;
|
||||||
|
///
|
||||||
|
/// let source = QobuzSource;
|
||||||
|
/// assert_eq!(source.name(), "Qobuz");
|
||||||
|
/// assert_eq!(source.id(), "qobuz");
|
||||||
|
///
|
||||||
|
/// // Get default image as WebP bytes
|
||||||
|
/// let image_data = source.default_image();
|
||||||
|
/// assert!(image_data.len() > 0);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct QobuzSource;
|
||||||
|
|
||||||
|
impl MusicSource for QobuzSource {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Qobuz"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"qobuz"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_image(&self) -> &[u8] {
|
||||||
|
DEFAULT_IMAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_source_info() {
|
||||||
|
let source = QobuzSource;
|
||||||
|
assert_eq!(source.name(), "Qobuz");
|
||||||
|
assert_eq!(source.id(), "qobuz");
|
||||||
|
assert_eq!(source.default_image_mime_type(), "image/webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_image_present() {
|
||||||
|
let source = QobuzSource;
|
||||||
|
let image = source.default_image();
|
||||||
|
assert!(image.len() > 0, "Default image should not be empty");
|
||||||
|
|
||||||
|
// Check WebP magic bytes (RIFF...WEBP)
|
||||||
|
assert!(image.len() >= 12, "Image too small to be valid WebP");
|
||||||
|
assert_eq!(&image[0..4], b"RIFF", "Missing RIFF header");
|
||||||
|
assert_eq!(&image[8..12], b"WEBP", "Missing WEBP signature");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
pmosource/Cargo.toml
Normal file
17
pmosource/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "pmosource"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["PMOMusic Contributors"]
|
||||||
|
description = "Common traits and types for PMOMusic sources"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://github.com/yourusername/pmomusic"
|
||||||
|
keywords = ["music", "source", "streaming"]
|
||||||
|
categories = ["multimedia"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Gestion des erreurs
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
# Image format support
|
||||||
|
image = { version = "0.25", default-features = false, features = ["webp"] }
|
||||||
113
pmosource/README.md
Normal file
113
pmosource/README.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# pmosource
|
||||||
|
|
||||||
|
Common traits and types for PMOMusic sources.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`pmosource` provides the foundational abstractions for different music sources in the PMOMusic ecosystem, such as Radio Paradise, Qobuz, and potentially others in the future.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **`MusicSource` trait**: Common interface for all music sources
|
||||||
|
- **Default images**: Standardized 300x300px WebP images embedded in binaries
|
||||||
|
- **Source identification**: Consistent naming and ID scheme
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Implementing the trait
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use pmosource::MusicSource;
|
||||||
|
|
||||||
|
const DEFAULT_IMAGE: &[u8] = include_bytes!("../assets/default.webp");
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MyMusicSource;
|
||||||
|
|
||||||
|
impl MusicSource for MyMusicSource {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"My Music Service"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"my-music-service"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_image(&self) -> &[u8] {
|
||||||
|
DEFAULT_IMAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a music source
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use pmosource::MusicSource;
|
||||||
|
use pmoparadise::RadioParadiseSource;
|
||||||
|
use pmoqobuz::QobuzSource;
|
||||||
|
|
||||||
|
let rp = RadioParadiseSource;
|
||||||
|
let qobuz = QobuzSource;
|
||||||
|
|
||||||
|
println!("Source: {} ({})", rp.name(), rp.id());
|
||||||
|
println!("Image size: {} bytes", rp.default_image().len());
|
||||||
|
```
|
||||||
|
|
||||||
|
## Image Format
|
||||||
|
|
||||||
|
All default images should be:
|
||||||
|
- **Format**: WebP
|
||||||
|
- **Dimensions**: 300x300 pixels (square)
|
||||||
|
- **Quality**: 85 (good balance between size and quality)
|
||||||
|
- **Location**: `<crate>/assets/default.webp`
|
||||||
|
|
||||||
|
### Converting images
|
||||||
|
|
||||||
|
Use the provided Python script or similar tool:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def convert_to_webp(input_path, output_path, size=300):
|
||||||
|
img = Image.open(input_path)
|
||||||
|
|
||||||
|
# Convert to RGB if necessary
|
||||||
|
if img.mode not in ('RGB', 'RGBA'):
|
||||||
|
img = img.convert('RGB')
|
||||||
|
|
||||||
|
# Make it square (center crop)
|
||||||
|
width, height = img.size
|
||||||
|
if width != height:
|
||||||
|
min_dim = min(width, height)
|
||||||
|
left = (width - min_dim) // 2
|
||||||
|
top = (height - min_dim) // 2
|
||||||
|
right = left + min_dim
|
||||||
|
bottom = top + min_dim
|
||||||
|
img = img.crop((left, top, right, bottom))
|
||||||
|
|
||||||
|
# Resize to target size
|
||||||
|
img = img.resize((size, size), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
# Save as WebP
|
||||||
|
img.save(output_path, 'WEBP', quality=85, method=6)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Implementations
|
||||||
|
|
||||||
|
- **pmoparadise**: Radio Paradise
|
||||||
|
- **pmoqobuz**: Qobuz
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
The `MusicSource` trait can be extended with additional methods such as:
|
||||||
|
|
||||||
|
- Authentication status
|
||||||
|
- Available quality levels
|
||||||
|
- Streaming capabilities
|
||||||
|
- Search functionality
|
||||||
|
- Playlist management
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT OR Apache-2.0
|
||||||
66
pmosource/examples/show_sources.rs
Normal file
66
pmosource/examples/show_sources.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
//! Example showing how to use the MusicSource trait
|
||||||
|
//!
|
||||||
|
//! This example demonstrates accessing source information and images
|
||||||
|
//! from different music sources (requires pmoparadise and pmoqobuz to be compiled).
|
||||||
|
|
||||||
|
use pmosource::{MusicSource, DEFAULT_IMAGE_SIZE};
|
||||||
|
|
||||||
|
// Mock implementations for demonstration
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RadioParadiseSource;
|
||||||
|
|
||||||
|
impl MusicSource for RadioParadiseSource {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Radio Paradise"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"radio-paradise"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_image(&self) -> &[u8] {
|
||||||
|
// This would normally be: include_bytes!("../../pmoparadise/assets/default.webp")
|
||||||
|
// For this example, we return an empty slice
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct QobuzSource;
|
||||||
|
|
||||||
|
impl MusicSource for QobuzSource {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Qobuz"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"qobuz"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_image(&self) -> &[u8] {
|
||||||
|
// This would normally be: include_bytes!("../../pmoqobuz/assets/default.webp")
|
||||||
|
// For this example, we return an empty slice
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("PMOMusic Sources\n");
|
||||||
|
println!("Standard image size: {}x{} pixels\n", DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE);
|
||||||
|
|
||||||
|
let sources: Vec<Box<dyn MusicSource>> = vec![
|
||||||
|
Box::new(RadioParadiseSource),
|
||||||
|
Box::new(QobuzSource),
|
||||||
|
];
|
||||||
|
|
||||||
|
for source in sources {
|
||||||
|
println!("Source: {}", source.name());
|
||||||
|
println!(" ID: {}", source.id());
|
||||||
|
println!(" Image MIME: {}", source.default_image_mime_type());
|
||||||
|
println!(" Image size: {} bytes", source.default_image().len());
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Note: In a real implementation, the images would be embedded in the binary");
|
||||||
|
println!(" and would be approximately 3-10 KB each in WebP format.");
|
||||||
|
}
|
||||||
112
pmosource/src/lib.rs
Normal file
112
pmosource/src/lib.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
//! # PMOSource
|
||||||
|
//!
|
||||||
|
//! Common traits and types for PMOMusic sources.
|
||||||
|
//!
|
||||||
|
//! This crate provides the foundational abstractions for different music sources
|
||||||
|
//! in the PMOMusic ecosystem, such as Radio Paradise, Qobuz, etc.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Standard size for default images (300x300 pixels)
|
||||||
|
pub const DEFAULT_IMAGE_SIZE: u32 = 300;
|
||||||
|
|
||||||
|
/// Error types for music source operations
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum MusicSourceError {
|
||||||
|
#[error("Failed to load default image: {0}")]
|
||||||
|
ImageLoadError(String),
|
||||||
|
|
||||||
|
#[error("Invalid image format: {0}")]
|
||||||
|
InvalidImageFormat(String),
|
||||||
|
|
||||||
|
#[error("Source not available: {0}")]
|
||||||
|
SourceUnavailable(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result type for music source operations
|
||||||
|
pub type Result<T> = std::result::Result<T, MusicSourceError>;
|
||||||
|
|
||||||
|
/// Main trait for music sources
|
||||||
|
///
|
||||||
|
/// This trait defines the common interface that all music sources must implement.
|
||||||
|
/// It provides methods for:
|
||||||
|
/// - Getting the source name and identification
|
||||||
|
/// - Retrieving default images/logos
|
||||||
|
/// - Other common operations (to be extended)
|
||||||
|
pub trait MusicSource: Debug + Send + Sync {
|
||||||
|
/// Returns the human-readable name of the music source
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// assert_eq!(source.name(), "Radio Paradise");
|
||||||
|
/// ```
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
|
/// Returns a unique identifier for the music source
|
||||||
|
///
|
||||||
|
/// This is typically a lowercase, hyphenated version of the name
|
||||||
|
/// suitable for use in URLs, file names, etc.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// assert_eq!(source.id(), "radio-paradise");
|
||||||
|
/// ```
|
||||||
|
fn id(&self) -> &str;
|
||||||
|
|
||||||
|
/// Returns the default image/logo for this source as WebP bytes
|
||||||
|
///
|
||||||
|
/// The image should be square (300x300 pixels) and in WebP format.
|
||||||
|
/// This is embedded in the binary for offline availability.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A byte slice containing the WebP-encoded image data
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// let image_data = source.default_image();
|
||||||
|
/// assert!(image_data.len() > 0);
|
||||||
|
/// ```
|
||||||
|
fn default_image(&self) -> &[u8];
|
||||||
|
|
||||||
|
/// Returns the MIME type of the default image
|
||||||
|
///
|
||||||
|
/// By default, this returns "image/webp" since all default images
|
||||||
|
/// should be in WebP format.
|
||||||
|
fn default_image_mime_type(&self) -> &str {
|
||||||
|
"image/webp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TestSource;
|
||||||
|
|
||||||
|
impl MusicSource for TestSource {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Test Source"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"test-source"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_image(&self) -> &[u8] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_music_source_trait() {
|
||||||
|
let source = TestSource;
|
||||||
|
assert_eq!(source.name(), "Test Source");
|
||||||
|
assert_eq!(source.id(), "test-source");
|
||||||
|
assert_eq!(source.default_image_mime_type(), "image/webp");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user