extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum PictureType {
Other,
FileIcon, OtherFileIcon,
#[default]
CoverFront,
CoverBack,
LeafletPage,
Media, LeadArtist,
Artist,
Conductor,
Band,
Composer,
Lyricist,
RecordingLocation,
DuringRecording,
DuringPerformance,
VideoScreenCapture,
BrightColouredFish, Illustration,
BandLogo,
PublisherLogo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Picture {
pub mime_type: String,
pub picture_type: PictureType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(with = "serde_bytes")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lyrics {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub text: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SyncedLyricsContentType {
Other,
#[default]
Lyrics,
TextTranscription,
PartName, Events, Chord, Trivia, WebpageUrl,
ImageUrl,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncedLyricsLine {
pub timestamp_ms: u64,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncedLyrics {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default)]
pub content_type: SyncedLyricsContentType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub lines: Vec<SyncedLyricsLine>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserText {
pub description: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserUrl {
pub description: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Popularimeter {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
pub rating: u8,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub play_count: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaveformData {
pub peaks_per_second: u32,
pub peaks: Vec<f32>,
#[serde(default = "default_one")]
pub channels: u8,
}
fn default_one() -> u8 {
1
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SectionType {
Intro,
Verse,
PreChorus,
Chorus,
PostChorus,
Bridge,
Breakdown,
Drop,
Buildup,
Solo,
Instrumental,
Outro,
Silence,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionMarker {
pub timestamp_ms: u64,
pub section_type: SectionType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BpmChange {
pub timestamp_ms: u64,
pub bpm: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyChange {
pub timestamp_ms: u64,
pub key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoudnessPoint {
pub timestamp_ms: u64,
pub lufs: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatorNote {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timestamp_ms: Option<u64>,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollaborationCredit {
pub role: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timestamp_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemixChainEntry {
pub title: String,
pub artist: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub year: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub isrc: Option<String>,
pub relationship: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimatedCover {
pub mime_type: String,
#[serde(with = "serde_bytes")]
pub data: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub loop_count: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CoverVariantType {
Standard,
Explicit,
Clean,
Remix,
Deluxe,
Limited,
Vinyl,
Cassette,
Digital,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoverVariant {
pub variant_type: CoverVariantType,
pub mime_type: String,
#[serde(with = "serde_bytes")]
pub data: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FloMetadata {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtitle: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_group: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub album: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_album: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub set_subtitle: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub track_number: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub track_total: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disc_number: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disc_total: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub isrc: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artist: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub album_artist: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub conductor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub remixer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_artist: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub composer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lyricist: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_lyricist: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encoded_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub involved_people: Option<Vec<(String, String)>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub musician_credits: Option<Vec<(String, String)>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub genre: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mood: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bpm: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub length_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub year: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recording_time: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub release_time: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_release_time: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encoding_time: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tagging_time: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub copyright: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub produced_notice: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub publisher: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_owner: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub radio_station: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub radio_station_owner: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub album_sort: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artist_sort: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title_sort: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_filename: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub playlist_delay: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encoder_settings: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_commercial: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_copyright: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_audio_file: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_artist: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_audio_source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_radio_station: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_payment: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_publisher: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub user_urls: Vec<UserUrl>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub comments: Vec<Comment>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub lyrics: Vec<Lyrics>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub synced_lyrics: Vec<SyncedLyrics>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub pictures: Vec<Picture>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub user_text: Vec<UserText>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub play_count: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub popularimeter: Option<Popularimeter>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub waveform_data: Option<WaveformData>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(with = "serde_bytes_option")]
pub spectrum_fingerprint: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bpm_map: Vec<BpmChange>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub key_changes: Vec<KeyChange>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub loudness_profile: Vec<LoudnessPoint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub integrated_loudness_lufs: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub loudness_range_lu: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub true_peak_dbtp: Option<f32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub section_markers: Vec<SectionMarker>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub creator_notes: Vec<CreatorNote>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub collaboration_credits: Vec<CollaborationCredit>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remix_chain: Vec<RemixChainEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub animated_cover: Option<AnimatedCover>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cover_variants: Vec<CoverVariant>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artist_signature: Option<Picture>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flo_encoder_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_format: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub custom: BTreeMap<String, String>,
}
mod serde_bytes_option {
use alloc::vec::Vec;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(data: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match data {
Some(bytes) => serde_bytes::serialize(bytes, serializer),
None => Option::<&[u8]>::None.serialize(serializer),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<serde_bytes::ByteBuf> = Option::deserialize(deserializer)?;
Ok(opt.map(|b| b.into_vec()))
}
}
impl FloMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn with_basic(
title: Option<String>,
artist: Option<String>,
album: Option<String>,
) -> Self {
Self {
title,
artist,
album,
..Default::default()
}
}
pub fn to_msgpack(
&self,
) -> Result<Vec<u8>, messagepack_serde::ser::Error<core::convert::Infallible>> {
messagepack_serde::to_vec(self)
}
pub fn from_msgpack(
data: &[u8],
) -> Result<Self, messagepack_serde::de::Error<messagepack_serde::messagepack_core::io::RError>>
{
messagepack_serde::from_slice(data)
}
pub fn is_empty(&self) -> bool {
self.title.is_none()
&& self.artist.is_none()
&& self.album.is_none()
&& self.pictures.is_empty()
&& self.comments.is_empty()
&& self.lyrics.is_empty()
&& self.synced_lyrics.is_empty()
}
pub fn add_picture(&mut self, mime_type: &str, picture_type: PictureType, data: Vec<u8>) {
self.pictures.push(Picture {
mime_type: mime_type.to_string(),
picture_type,
description: None,
data,
});
}
pub fn front_cover(&self) -> Option<&Picture> {
self.pictures
.iter()
.find(|p| p.picture_type == PictureType::CoverFront)
}
pub fn any_picture(&self) -> Option<&Picture> {
self.pictures.first()
}
pub fn add_comment(&mut self, text: &str, language: Option<&str>) {
self.comments.push(Comment {
language: language.map(|s| s.to_string()),
description: None,
text: text.to_string(),
});
}
pub fn add_lyrics(&mut self, text: &str, language: Option<&str>) {
self.lyrics.push(Lyrics {
language: language.map(|s| s.to_string()),
description: None,
text: text.to_string(),
});
}
pub fn add_synced_lyrics_line(
&mut self,
timestamp_ms: u64,
text: &str,
language: Option<&str>,
) {
let lang = language.map(|s| s.to_string());
if let Some(synced) = self.synced_lyrics.iter_mut().find(|s| s.language == lang) {
synced.lines.push(SyncedLyricsLine {
timestamp_ms,
text: text.to_string(),
});
} else {
self.synced_lyrics.push(SyncedLyrics {
language: lang,
content_type: SyncedLyricsContentType::Lyrics,
description: None,
lines: vec![SyncedLyricsLine {
timestamp_ms,
text: text.to_string(),
}],
});
}
}
pub fn set_custom(&mut self, key: &str, value: &str) {
self.custom.insert(key.to_string(), value.to_string());
}
pub fn get_custom(&self, key: &str) -> Option<&str> {
self.custom.get(key).map(|s| s.as_str())
}
pub fn add_section(
&mut self,
timestamp_ms: u64,
section_type: SectionType,
label: Option<&str>,
) {
self.section_markers.push(SectionMarker {
timestamp_ms,
section_type,
label: label.map(|s| s.to_string()),
});
}
pub fn add_bpm_change(&mut self, timestamp_ms: u64, bpm: f32) {
self.bpm_map.push(BpmChange { timestamp_ms, bpm });
}
pub fn add_key_change(&mut self, timestamp_ms: u64, key: &str) {
self.key_changes.push(KeyChange {
timestamp_ms,
key: key.to_string(),
});
}
pub fn add_creator_note(&mut self, text: &str, timestamp_ms: Option<u64>) {
self.creator_notes.push(CreatorNote {
timestamp_ms,
text: text.to_string(),
});
}
pub fn add_collaboration(&mut self, role: &str, name: &str, timestamp_ms: Option<u64>) {
self.collaboration_credits.push(CollaborationCredit {
role: role.to_string(),
name: name.to_string(),
timestamp_ms,
});
}
}