use super::{
beatgrid, color::Color, format::flac::FLACTag, format::id3::ID3Tag, format::mp4::MP4Tag,
format::ogg::OggTag, generic, markers, Autotags, Beatgrid, Markers, Markers2, Overview,
};
use crate::error::Error;
use std::io;
#[derive(Debug, Clone)]
pub struct TagContainer {
autotags: Option<Autotags>,
beatgrid: Option<Beatgrid>,
markers: Option<Markers>,
markers2: Option<Markers2>,
overview: Option<Overview>,
}
#[derive(Debug, Clone, Copy)]
pub enum TagFormat {
ID3,
FLAC,
MP4,
Ogg,
}
impl TagContainer {
#[must_use]
pub const fn new() -> Self {
Self {
autotags: None,
beatgrid: None,
markers: None,
markers2: None,
overview: None,
}
}
pub fn parse_autotags(&mut self, input: &[u8], tag_format: TagFormat) -> Result<(), Error> {
match tag_format {
TagFormat::ID3 => {
self.autotags = Some(Autotags::parse_id3(input)?);
}
TagFormat::FLAC => {
self.autotags = Some(Autotags::parse_flac(input)?);
}
TagFormat::MP4 => {
self.autotags = Some(Autotags::parse_mp4(input)?);
}
_ => return Err(Error::UnsupportedTagFormat),
}
Ok(())
}
pub fn write_autotags(
&self,
writer: &mut impl io::Write,
tag_format: TagFormat,
) -> Result<usize, Error> {
let tag = match &self.autotags {
Some(x) => x,
None => return Err(Error::NoTagDataAvailable),
};
match tag_format {
TagFormat::ID3 => tag.write_id3(writer),
TagFormat::FLAC => tag.write_flac(writer),
TagFormat::MP4 => tag.write_mp4(writer),
_ => Err(Error::UnsupportedTagFormat),
}
}
pub fn parse_beatgrid(&mut self, input: &[u8], tag_format: TagFormat) -> Result<(), Error> {
match tag_format {
TagFormat::ID3 => {
self.beatgrid = Some(Beatgrid::parse_id3(input)?);
}
TagFormat::FLAC => {
self.beatgrid = Some(Beatgrid::parse_flac(input)?);
}
TagFormat::MP4 => {
self.beatgrid = Some(Beatgrid::parse_mp4(input)?);
}
_ => return Err(Error::UnsupportedTagFormat),
}
Ok(())
}
pub fn write_beatgrid(
&self,
writer: &mut impl io::Write,
tag_format: TagFormat,
) -> Result<usize, Error> {
let tag = match &self.beatgrid {
Some(x) => x,
None => return Err(Error::NoTagDataAvailable),
};
match tag_format {
TagFormat::ID3 => tag.write_id3(writer),
TagFormat::FLAC => tag.write_flac(writer),
TagFormat::MP4 => tag.write_mp4(writer),
_ => Err(Error::UnsupportedTagFormat),
}
}
pub fn parse_markers(&mut self, input: &[u8], tag_format: TagFormat) -> Result<(), Error> {
match tag_format {
TagFormat::ID3 => {
self.markers = Some(Markers::parse_id3(input)?);
}
TagFormat::MP4 => {
self.markers = Some(Markers::parse_mp4(input)?);
}
_ => return Err(Error::UnsupportedTagFormat),
}
Ok(())
}
pub fn write_markers(
&self,
writer: &mut impl io::Write,
tag_format: TagFormat,
) -> Result<usize, Error> {
let tag = match &self.markers {
Some(x) => x,
None => return Err(Error::NoTagDataAvailable),
};
match tag_format {
TagFormat::ID3 => tag.write_id3(writer),
TagFormat::MP4 => tag.write_mp4(writer),
_ => Err(Error::UnsupportedTagFormat),
}
}
pub fn parse_markers2(&mut self, input: &[u8], tag_format: TagFormat) -> Result<(), Error> {
match tag_format {
TagFormat::ID3 => {
self.markers2 = Some(Markers2::parse_id3(input)?);
}
TagFormat::FLAC => {
self.markers2 = Some(Markers2::parse_flac(input)?);
}
TagFormat::MP4 => {
self.markers2 = Some(Markers2::parse_mp4(input)?);
}
TagFormat::Ogg => {
self.markers2 = Some(Markers2::parse_ogg(input)?);
}
}
Ok(())
}
pub fn write_markers2(
&self,
writer: &mut impl io::Write,
tag_format: TagFormat,
) -> Result<usize, Error> {
let tag = match &self.markers2 {
Some(x) => x,
None => return Err(Error::NoTagDataAvailable),
};
match tag_format {
TagFormat::ID3 => tag.write_id3(writer),
TagFormat::FLAC => tag.write_flac(writer),
TagFormat::MP4 => tag.write_mp4(writer),
_ => Err(Error::UnsupportedTagFormat),
}
}
pub fn parse_overview(&mut self, input: &[u8], tag_format: TagFormat) -> Result<(), Error> {
match tag_format {
TagFormat::ID3 => {
self.overview = Some(Overview::parse_id3(input)?);
}
TagFormat::FLAC => {
self.overview = Some(Overview::parse_flac(input)?);
}
TagFormat::MP4 => {
self.overview = Some(Overview::parse_mp4(input)?);
}
_ => return Err(Error::UnsupportedTagFormat),
}
Ok(())
}
pub fn write_overview(
&self,
writer: &mut impl io::Write,
tag_format: TagFormat,
) -> Result<usize, Error> {
let tag = match &self.overview {
Some(x) => x,
None => return Err(Error::NoTagDataAvailable),
};
match tag_format {
TagFormat::ID3 => tag.write_id3(writer),
TagFormat::FLAC => tag.write_flac(writer),
TagFormat::MP4 => tag.write_mp4(writer),
_ => Err(Error::UnsupportedTagFormat),
}
}
#[must_use]
pub fn auto_gain(&self) -> Option<f64> {
if let Some(tag) = &self.autotags {
return Some(tag.auto_gain);
}
None
}
#[must_use]
pub fn gain_db(&self) -> Option<f64> {
if let Some(tag) = &self.autotags {
return Some(tag.gain_db);
}
None
}
#[must_use]
pub fn beatgrid(
&self,
) -> Option<(&Vec<beatgrid::NonTerminalMarker>, &beatgrid::TerminalMarker)> {
if let Some(tag) = &self.beatgrid {
return Some((&tag.non_terminal_markers, &tag.terminal_marker));
}
None
}
#[must_use]
pub fn bpm_locked(&self) -> Option<bool> {
if let Some(m) = &self.markers2 {
return m.bpm_locked();
}
None
}
#[must_use]
pub fn cues(&self) -> Vec<generic::Cue> {
let mut map = std::collections::BTreeMap::new();
if let Some(m) = &self.markers2 {
for cue in m.cues() {
map.insert(cue.index, cue.to_owned());
}
}
if let Some(m) = &self.markers {
for (index, marker) in m.cues() {
match marker.marker_type {
markers::MarkerType::Invalid => {
map.remove(&index);
continue;
}
markers::MarkerType::Cue => {
if marker.start_position.is_none() {
map.remove(&index);
continue;
}
let position = marker.start_position.unwrap();
let markers2_cue = map.remove(&index);
let label = match markers2_cue {
Some(c) => c.label,
None => String::new(),
};
map.insert(
index,
generic::Cue {
index,
position,
color: marker.color,
label,
},
);
}
_ => {} }
}
}
map.into_values().collect()
}
#[must_use]
pub fn loops(&self) -> Vec<generic::Loop> {
let mut map = std::collections::BTreeMap::new();
if let Some(m) = &self.markers2 {
for saved_loop in m.loops() {
map.insert(saved_loop.index, saved_loop.to_owned());
}
}
if let Some(m) = &self.markers {
for (index, marker) in m.loops() {
if marker.marker_type != markers::MarkerType::Loop {
continue;
}
if marker.start_position.is_none() || marker.end_position.is_none() {
map.remove(&index);
continue;
}
let start_position = marker.start_position.unwrap();
let end_position = marker.end_position.unwrap();
let markers2_loop = map.remove(&index);
let label = match markers2_loop {
Some(c) => c.label,
None => String::new(),
};
map.insert(
index,
generic::Loop {
index,
start_position,
end_position,
color: marker.color,
label,
is_locked: marker.is_locked,
},
);
}
}
map.into_values().collect()
}
pub fn flips(&self) -> Option<impl Iterator<Item = &generic::Flip>> {
self.markers2.as_ref().map(Markers2::flips)
}
#[must_use]
pub fn track_color(&self) -> Option<Color> {
self.markers
.as_ref()
.map(Markers::track_color)
.or_else(|| self.markers2.as_ref().and_then(Markers2::track_color))
}
#[must_use]
pub fn overview_data(&self) -> Option<&[Vec<u8>]> {
self.overview.as_ref().map(|overview| &overview.data[..])
}
}
impl Default for TagContainer {
fn default() -> Self {
Self::new()
}
}