use std::borrow::Cow;
use std::collections::VecDeque;
use std::convert::From;
use std::fmt;
use std::num::NonZeroU8;
use std::sync::Arc;
use crate::common::{FourCc, Limit};
use crate::errors::Result;
use crate::io::MediaSourceStream;
use crate::units::Time;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MetadataId(u32);
impl MetadataId {
pub const fn new(cc: FourCc) -> MetadataId {
Self(0x8000_0000 | u32::from_be_bytes(cc.get()))
}
}
impl From<FourCc> for MetadataId {
fn from(value: FourCc) -> Self {
MetadataId::new(value)
}
}
impl fmt::Display for MetadataId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#x}", self.0)
}
}
pub const METADATA_ID_NULL: MetadataId = MetadataId(0x0);
#[derive(Copy, Clone, Debug)]
pub struct MetadataInfo {
pub metadata: MetadataId,
pub short_name: &'static str,
pub long_name: &'static str,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Default)]
pub struct MetadataOptions {
pub limit_tag_bytes: Limit,
pub limit_visual_bytes: Limit,
}
impl MetadataOptions {
pub fn limit_tag_bytes(mut self, limit: Limit) -> Self {
self.limit_tag_bytes = limit;
self
}
pub fn limit_visual_bytes(mut self, limit: Limit) -> Self {
self.limit_visual_bytes = limit;
self
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum StandardVisualKey {
FileIcon,
OtherIcon,
FrontCover,
BackCover,
Leaflet,
Media,
LeadArtistPerformerSoloist,
ArtistPerformer,
Conductor,
BandOrchestra,
Composer,
Lyricist,
RecordingLocation,
RecordingSession,
Performance,
ScreenCapture,
Illustration,
BandArtistLogo,
PublisherStudioLogo,
Other,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ContentAdvisory {
None,
Explicit,
Censored,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum StandardTag {
AccurateRipCount(Arc<String>),
AccurateRipCountAllOffsets(Arc<String>),
AccurateRipCountWithOffset(Arc<String>),
AccurateRipCrc(Arc<String>),
AccurateRipDiscId(Arc<String>),
AccurateRipId(Arc<String>),
AccurateRipOffset(Arc<String>),
AccurateRipResult(Arc<String>),
AccurateRipTotal(Arc<String>),
AcoustIdFingerprint(Arc<String>),
AcoustIdId(Arc<String>),
Actor(Arc<String>),
Album(Arc<String>),
AlbumArtist(Arc<String>),
Arranger(Arc<String>),
ArtDirector(Arc<String>),
Artist(Arc<String>),
AssistantDirector(Arc<String>),
Author(Arc<String>),
Bpm(u64),
CdToc(Arc<String>),
CdTrackIndex(u8),
ChapterTitle(Arc<String>),
Choregrapher(Arc<String>),
Cinematographer(Arc<String>),
CollectionTitle(Arc<String>),
Comment(Arc<String>),
CompilationFlag(bool),
Composer(Arc<String>),
Conductor(Arc<String>),
ContentAdvisory(ContentAdvisory),
ContentRating(Arc<String>),
ContentType(Arc<String>),
Coproducer(Arc<String>),
Copyright(Arc<String>),
CostumeDesigner(Arc<String>),
CueToolsDbDiscConfidence(Arc<String>),
CueToolsDbTrackConfidence(Arc<String>),
Description(Arc<String>),
DigitizedDate(Arc<String>),
Director(Arc<String>),
DiscNumber(u64),
DiscSubtitle(Arc<String>),
DiscTotal(u64),
Distributor(Arc<String>),
EditedBy(Arc<String>),
EditionTitle(Arc<String>),
EncodedBy(Arc<String>),
Encoder(Arc<String>),
EncoderSettings(Arc<String>),
EncodingDate(Arc<String>),
Engineer(Arc<String>),
Ensemble(Arc<String>),
ExecutiveProducer(Arc<String>),
Genre(Arc<String>),
Grouping(Arc<String>),
IdentAsin(Arc<String>),
IdentBarcode(Arc<String>),
IdentCatalogNumber(Arc<String>),
IdentEanUpn(Arc<String>),
IdentIsbn(Arc<String>),
IdentIsrc(Arc<String>),
IdentLccn(Arc<String>),
IdentPn(Arc<String>),
IdentPodcast(Arc<String>),
IdentUpc(Arc<String>),
ImdbTitleId(Arc<String>),
InitialKey(Arc<String>),
InternetRadioName(Arc<String>),
InternetRadioOwner(Arc<String>),
Keywords(Arc<String>),
Label(Arc<String>),
LabelCode(Arc<String>),
Language(Arc<String>),
License(Arc<String>),
Lyricist(Arc<String>),
Lyrics(Arc<String>),
Measure(Arc<String>),
MediaFormat(Arc<String>),
MixDj(Arc<String>),
MixEngineer(Arc<String>),
Mood(Arc<String>),
MovementName(Arc<String>),
MovementNumber(u64),
MovementTotal(u64),
MovieTitle(Arc<String>),
Mp3GainAlbumMinMax(Arc<String>),
Mp3GainMinMax(Arc<String>),
Mp3GainUndo(Arc<String>),
MusicBrainzAlbumArtistId(Arc<String>),
MusicBrainzAlbumId(Arc<String>),
MusicBrainzArtistId(Arc<String>),
MusicBrainzDiscId(Arc<String>),
MusicBrainzGenreId(Arc<String>),
MusicBrainzLabelId(Arc<String>),
MusicBrainzOriginalAlbumId(Arc<String>),
MusicBrainzOriginalArtistId(Arc<String>),
MusicBrainzRecordingId(Arc<String>),
MusicBrainzReleaseGroupId(Arc<String>),
MusicBrainzReleaseStatus(Arc<String>),
MusicBrainzReleaseTrackId(Arc<String>),
MusicBrainzReleaseType(Arc<String>),
MusicBrainzTrackId(Arc<String>),
MusicBrainzTrmId(Arc<String>),
MusicBrainzWorkId(Arc<String>),
Narrator(Arc<String>),
Opus(Arc<String>),
OpusNumber(u64),
OriginalAlbum(Arc<String>),
OriginalArtist(Arc<String>),
OriginalFile(Arc<String>),
OriginalLyricist(Arc<String>),
OriginalRecordingDate(Arc<String>),
OriginalRecordingTime(Arc<String>),
OriginalRecordingYear(u16),
OriginalReleaseDate(Arc<String>),
OriginalReleaseTime(Arc<String>),
OriginalReleaseYear(u16),
OriginalWriter(Arc<String>),
Owner(Arc<String>),
Part(Arc<String>),
PartNumber(u64),
PartTitle(Arc<String>),
PartTotal(u64),
Performer(Arc<String>),
Period(Arc<String>),
PlayCounter(u64),
PodcastCategory(Arc<String>),
PodcastDescription(Arc<String>),
PodcastFlag(bool),
PodcastKeywords(Arc<String>),
Producer(Arc<String>),
ProductionCopyright(Arc<String>),
ProductionDesigner(Arc<String>),
ProductionStudio(Arc<String>),
PurchaseDate(Arc<String>),
Rating(u32), RecordingDate(Arc<String>),
RecordingLocation(Arc<String>),
RecordingTime(Arc<String>),
RecordingYear(u16),
ReleaseCountry(Arc<String>),
ReleaseDate(Arc<String>),
ReleaseTime(Arc<String>),
ReleaseYear(u16),
Remixer(Arc<String>),
ReplayGainAlbumGain(Arc<String>),
ReplayGainAlbumPeak(Arc<String>),
ReplayGainAlbumRange(Arc<String>),
ReplayGainReferenceLoudness(Arc<String>),
ReplayGainTrackGain(Arc<String>),
ReplayGainTrackPeak(Arc<String>),
ReplayGainTrackRange(Arc<String>),
ScreenplayAuthor(Arc<String>),
Script(Arc<String>),
Soloist(Arc<String>),
SortAlbum(Arc<String>),
SortAlbumArtist(Arc<String>),
SortArtist(Arc<String>),
SortCollectionTitle(Arc<String>),
SortComposer(Arc<String>),
SortEditionTitle(Arc<String>),
SortMovieTitle(Arc<String>),
SortOpusTitle(Arc<String>),
SortPartTitle(Arc<String>),
SortTrackTitle(Arc<String>),
SortTvEpisodeTitle(Arc<String>),
SortTvSeasonTitle(Arc<String>),
SortTvSeriesTitle(Arc<String>),
SortVolumeTitle(Arc<String>),
Subject(Arc<String>),
Summary(Arc<String>),
Synopsis(Arc<String>),
TaggingDate(Arc<String>),
TermsOfUse(Arc<String>),
Thanks(Arc<String>),
TmdbMovieId(Arc<String>),
TmdbSeriesId(Arc<String>),
TrackNumber(u64),
TrackSubtitle(Arc<String>),
TrackTitle(Arc<String>),
TrackTotal(u64),
Tuning(Arc<String>),
TvdbEpisodeId(Arc<String>),
TvdbMovieId(Arc<String>),
TvdbSeriesId(Arc<String>),
TvEpisodeNumber(u64),
TvEpisodeTitle(Arc<String>),
TvEpisodeTotal(u64),
TvNetwork(Arc<String>),
TvSeasonNumber(u64),
TvSeasonTitle(Arc<String>),
TvSeasonTotal(u64),
TvSeriesTitle(Arc<String>),
Url(Arc<String>),
UrlArtist(Arc<String>),
UrlCopyright(Arc<String>),
UrlInternetRadio(Arc<String>),
UrlLabel(Arc<String>),
UrlOfficial(Arc<String>),
UrlPayment(Arc<String>),
UrlPodcast(Arc<String>),
UrlPurchase(Arc<String>),
UrlSource(Arc<String>),
Version(Arc<String>),
VolumeNumber(u64),
VolumeTitle(Arc<String>),
VolumeTotal(u64),
Work(Arc<String>),
Writer(Arc<String>),
WrittenDate(Arc<String>),
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum RawValue {
Binary(Arc<Box<[u8]>>),
Boolean(bool),
Flag,
Float(f64),
SignedInt(i64),
String(Arc<String>),
StringList(Arc<Vec<String>>),
UnsignedInt(u64),
}
macro_rules! impl_from_for_value {
($value:ident, $from:ty, $conv:expr) => {
impl From<$from> for RawValue {
fn from($value: $from) -> Self {
$conv
}
}
};
}
impl_from_for_value!(v, &[u8], RawValue::Binary(Arc::new(Box::from(v))));
impl_from_for_value!(v, Box<[u8]>, RawValue::Binary(Arc::new(v)));
impl_from_for_value!(v, Arc<Box<[u8]>>, RawValue::Binary(v));
impl_from_for_value!(v, bool, RawValue::Boolean(v));
impl_from_for_value!(v, f32, RawValue::Float(f64::from(v)));
impl_from_for_value!(v, f64, RawValue::Float(v));
impl_from_for_value!(v, i8, RawValue::SignedInt(i64::from(v)));
impl_from_for_value!(v, i16, RawValue::SignedInt(i64::from(v)));
impl_from_for_value!(v, i32, RawValue::SignedInt(i64::from(v)));
impl_from_for_value!(v, i64, RawValue::SignedInt(v));
impl_from_for_value!(v, u8, RawValue::UnsignedInt(u64::from(v)));
impl_from_for_value!(v, u16, RawValue::UnsignedInt(u64::from(v)));
impl_from_for_value!(v, u32, RawValue::UnsignedInt(u64::from(v)));
impl_from_for_value!(v, u64, RawValue::UnsignedInt(v));
impl_from_for_value!(v, &str, RawValue::String(Arc::new(v.to_string())));
impl_from_for_value!(v, String, RawValue::String(Arc::new(v)));
impl_from_for_value!(v, Arc<String>, RawValue::String(v));
impl_from_for_value!(v, Cow<'_, str>, RawValue::String(Arc::new(v.into_owned())));
impl_from_for_value!(v, Vec<String>, RawValue::StringList(Arc::new(v)));
fn buffer_to_hex_string(buf: &[u8]) -> String {
let mut output = String::with_capacity(5 * buf.len());
for ch in buf {
let u = (ch & 0xf0) >> 4;
let l = ch & 0x0f;
output.push_str("\\0x");
output.push(if u < 10 { (b'0' + u) as char } else { (b'a' + u - 10) as char });
output.push(if l < 10 { (b'0' + l) as char } else { (b'a' + l - 10) as char });
}
output
}
impl fmt::Display for RawValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RawValue::Binary(buf) => f.write_str(&buffer_to_hex_string(buf)),
RawValue::Boolean(boolean) => fmt::Display::fmt(boolean, f),
RawValue::Flag => write!(f, "<flag>"),
RawValue::Float(float) => fmt::Display::fmt(float, f),
RawValue::SignedInt(int) => fmt::Display::fmt(int, f),
RawValue::String(string) => fmt::Display::fmt(string, f),
RawValue::StringList(list) => fmt::Display::fmt(&list.join("\n"), f),
RawValue::UnsignedInt(uint) => fmt::Display::fmt(uint, f),
}
}
}
#[derive(Clone, Debug)]
pub struct RawTagSubField {
pub field: String,
pub value: RawValue,
}
impl RawTagSubField {
pub fn new<F, V>(field: F, value: V) -> Self
where
F: Into<String>,
V: Into<RawValue>,
{
RawTagSubField { field: field.into(), value: value.into() }
}
}
#[derive(Clone, Debug)]
pub struct RawTag {
pub key: String,
pub value: RawValue,
pub sub_fields: Option<Box<[RawTagSubField]>>,
}
impl RawTag {
pub fn new<K, V>(key: K, value: V) -> Self
where
K: Into<String>,
V: Into<RawValue>,
{
RawTag { key: key.into(), value: value.into(), sub_fields: None }
}
pub fn new_with_sub_fields<K, V>(key: K, value: V, sub_fields: Box<[RawTagSubField]>) -> Self
where
K: Into<String>,
V: Into<RawValue>,
{
RawTag { key: key.into(), value: value.into(), sub_fields: Some(sub_fields) }
}
}
#[derive(Clone, Debug)]
pub struct Tag {
pub raw: RawTag,
pub std: Option<StandardTag>,
}
impl Tag {
pub fn new(raw: RawTag) -> Self {
Tag { raw, std: None }
}
pub fn new_std(raw: RawTag, std: StandardTag) -> Self {
Tag { raw, std: Some(std) }
}
pub fn new_from_parts<K, V>(key: K, value: V, std: Option<StandardTag>) -> Self
where
K: Into<String>,
V: Into<RawValue>,
{
Tag { raw: RawTag { key: key.into(), value: value.into(), sub_fields: None }, std }
}
pub fn has_std_tag(&self) -> bool {
self.std.is_some()
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct Size {
pub width: u32,
pub height: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ColorModel {
Y(NonZeroU8),
YA(NonZeroU8),
RGB(NonZeroU8),
RGBA(NonZeroU8),
CMYK(NonZeroU8),
}
impl ColorModel {
pub fn bits_per_pixel(&self) -> u32 {
match self {
ColorModel::Y(bits) => u32::from(bits.get()),
ColorModel::YA(bits) => 2 * u32::from(bits.get()),
ColorModel::RGB(bits) => 3 * u32::from(bits.get()),
ColorModel::RGBA(bits) => 4 * u32::from(bits.get()),
ColorModel::CMYK(bits) => 4 * u32::from(bits.get()),
}
}
pub fn has_alpha_channel(&self) -> bool {
matches!(self, ColorModel::YA(_) | ColorModel::RGBA(_))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ColorPaletteInfo {
pub bits_per_pixel: NonZeroU8,
pub color_model: ColorModel,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ColorMode {
Direct(ColorModel),
Indexed(ColorPaletteInfo),
}
#[derive(Clone, Debug)]
pub struct Visual {
pub media_type: Option<String>,
pub dimensions: Option<Size>,
pub color_mode: Option<ColorMode>,
pub usage: Option<StandardVisualKey>,
pub tags: Vec<Tag>,
pub data: Box<[u8]>,
}
#[derive(Clone, Debug)]
pub struct ChapterGroup {
pub items: Vec<ChapterGroupItem>,
pub tags: Vec<Tag>,
pub visuals: Vec<Visual>,
}
#[derive(Clone, Debug)]
pub struct Chapter {
pub start_time: Time,
pub end_time: Option<Time>,
pub start_byte: Option<u64>,
pub end_byte: Option<u64>,
pub tags: Vec<Tag>,
pub visuals: Vec<Visual>,
}
#[derive(Clone, Debug)]
pub enum ChapterGroupItem {
Group(ChapterGroup),
Chapter(Chapter),
}
#[derive(Clone, Debug, Default)]
pub struct MetadataContainer {
pub tags: Vec<Tag>,
pub visuals: Vec<Visual>,
}
#[derive(Clone, Debug, Default)]
pub struct PerTrackMetadata {
pub track_id: u64,
pub metadata: MetadataContainer,
}
#[derive(Clone, Debug)]
pub struct MetadataRevision {
pub info: MetadataInfo,
pub media: MetadataContainer,
pub per_track: Vec<PerTrackMetadata>,
}
#[derive(Clone, Debug)]
pub struct MetadataBuilder {
revision: MetadataRevision,
}
impl MetadataBuilder {
pub fn new(info: MetadataInfo) -> Self {
let revision =
MetadataRevision { info, media: Default::default(), per_track: Default::default() };
MetadataBuilder { revision }
}
pub fn add_tag(&mut self, tag: Tag) -> &mut Self {
self.revision.media.tags.push(tag);
self
}
pub fn add_visual(&mut self, visual: Visual) -> &mut Self {
self.revision.media.visuals.push(visual);
self
}
pub fn add_track(&mut self, per_track: PerTrackMetadata) -> &mut Self {
self.revision.per_track.push(per_track);
self
}
pub fn build(self) -> MetadataRevision {
self.revision
}
}
#[derive(Clone, Debug)]
pub struct PerTrackMetadataBuilder {
per_track: PerTrackMetadata,
}
impl PerTrackMetadataBuilder {
pub fn new(track_id: u64) -> Self {
PerTrackMetadataBuilder {
per_track: PerTrackMetadata { track_id, metadata: Default::default() },
}
}
pub fn add_tag(&mut self, tag: Tag) -> &mut Self {
self.per_track.metadata.tags.push(tag);
self
}
pub fn add_visual(&mut self, visual: Visual) -> &mut Self {
self.per_track.metadata.visuals.push(visual);
self
}
pub fn build(self) -> PerTrackMetadata {
self.per_track
}
}
#[derive(Debug)]
pub struct Metadata<'a> {
revisions: &'a mut VecDeque<MetadataRevision>,
}
impl Metadata<'_> {
pub fn is_latest(&self) -> bool {
self.revisions.len() <= 1
}
pub fn current(&self) -> Option<&MetadataRevision> {
self.revisions.front()
}
pub fn skip_to_latest(&mut self) -> Option<&MetadataRevision> {
loop {
if self.pop().is_none() {
break;
}
}
self.current()
}
pub fn pop(&mut self) -> Option<MetadataRevision> {
if self.revisions.len() > 1 { self.revisions.pop_front() } else { None }
}
}
#[derive(Clone, Debug, Default)]
pub struct MetadataLog {
revisions: VecDeque<MetadataRevision>,
}
impl MetadataLog {
pub fn metadata(&mut self) -> Metadata<'_> {
Metadata { revisions: &mut self.revisions }
}
pub fn push(&mut self, rev: MetadataRevision) {
self.revisions.push_back(rev);
}
pub fn append(&mut self, other: &mut MetadataLog) {
self.revisions.append(&mut other.revisions);
}
pub fn push_front(&mut self, rev: MetadataRevision) {
self.revisions.push_front(rev);
}
pub fn append_front(&mut self, other: &mut MetadataLog) {
while let Some(revision) = other.revisions.pop_back() {
self.revisions.push_front(revision)
}
}
}
#[non_exhaustive]
pub enum MetadataSideData {
Chapters(ChapterGroup),
}
pub struct MetadataBuffer {
pub revision: MetadataRevision,
pub side_data: Vec<MetadataSideData>,
}
pub trait MetadataReader: Send + Sync {
fn metadata_info(&self) -> &MetadataInfo;
fn read_all(&mut self) -> Result<MetadataBuffer>;
fn into_inner<'s>(self: Box<Self>) -> MediaSourceStream<'s>
where
Self: 's;
}
pub mod well_known {
use super::MetadataId;
pub const METADATA_ID_VORBIS_COMMENT: MetadataId = MetadataId(0x100);
pub const METADATA_ID_FLAC: MetadataId = MetadataId(0x101);
pub const METADATA_ID_ID3V1: MetadataId = MetadataId(0x200);
pub const METADATA_ID_ID3V2: MetadataId = MetadataId(0x201);
pub const METADATA_ID_APEV1: MetadataId = MetadataId(0x300);
pub const METADATA_ID_APEV2: MetadataId = MetadataId(0x301);
pub const METADATA_ID_WAVE: MetadataId = MetadataId(0x400);
pub const METADATA_ID_AIFF: MetadataId = MetadataId(0x401);
pub const METADATA_ID_EXIF: MetadataId = MetadataId(0x402);
pub const METADATA_ID_MATROSKA: MetadataId = MetadataId(0x403);
pub const METADATA_ID_ISOMP4: MetadataId = MetadataId(0x404);
}