Stop using metaflac for reading, add AudioTagEdit::properties

This commit is contained in:
Serial 2021-07-24 11:12:19 -04:00
parent d519fa5ea1
commit dd0eb41549
11 changed files with 228 additions and 50 deletions

View file

@ -0,0 +1,133 @@
use super::read::{read_comments, OGGTags};
use crate::{FileProperties, LoftyError, OggFormat, Picture, Result};
use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{BigEndian, ReadBytesExt};
use unicase::UniCase;
fn read_properties<R>(stream_info: &mut R, stream_length: u64) -> Result<FileProperties>
where
R: Read,
{
// Skip 4 bytes
// Minimum block size (2)
// Maximum block size (2)
stream_info.read_u16::<BigEndian>()?;
// Skip 6 bytes
// Minimum frame size (3)
// Maximum frame size (3)
stream_info.read_uint::<BigEndian>(6)?;
// Read 24 bits
// Sample rate (20)
// Number of channels (3)
// First bit of bits per sample (1)
let info = stream_info.read_uint::<BigEndian>(3)?;
let sample_rate = info >> 4;
let channels = (info & 0x15) + 1;
// There are still 4 bits remaining of the bits per sample
// This number isn't used, so just discard it
let total_samples_first = stream_info.read_u8()? << 4;
// Read the remaining 32 bits of the total samples
let total_samples = stream_info.read_u32::<BigEndian>()? | u32::from(total_samples_first);
let (duration, bitrate) = if sample_rate > 0 && total_samples > 0 {
let length = (u64::from(total_samples) * 1000) / sample_rate;
(
Duration::from_millis(length),
((stream_length * 8) / length) as u32,
)
} else {
(Duration::ZERO, 0)
};
Ok(FileProperties {
duration,
bitrate: Some(bitrate),
sample_rate: Some(sample_rate as u32),
channels: Some(channels as u8),
})
}
pub(crate) fn read_from<R>(data: &mut R) -> Result<OGGTags>
where
R: Read + Seek,
{
let mut marker = [0; 4];
data.read_exact(&mut marker)?;
if &marker != b"fLaC" {
return Err(LoftyError::InvalidData(
"FLAC file missing \"fLaC\" stream marker",
));
}
let mut byte = data.read_u8()?;
if (byte & 0x7f) != 0 {
return Err(LoftyError::InvalidData(
"FLAC file missing mandatory STREAMINFO block",
));
}
let mut last_block = (byte & 0x80) != 0;
let stream_info_len = data.read_uint::<BigEndian>(3)? as u32;
if stream_info_len < 18 {
return Err(LoftyError::InvalidData("FLAC file has an invalid STREAMINFO block size (< 18)"))
}
let mut stream_info_data = vec![0; stream_info_len as usize];
data.read_exact(&mut stream_info_data)?;
let mut vendor = String::new();
let mut comments = HashMap::<UniCase<String>, String>::new();
let mut pictures = Vec::<Picture>::new();
while !last_block {
byte = data.read_u8()?;
last_block = (byte & 0x80) != 0;
let block_type = byte & 0x7f;
let block_len = data.read_uint::<BigEndian>(3)? as u32;
match block_type {
4 => {
let mut comment_data = vec![0; block_len as usize];
data.read_exact(&mut comment_data)?;
vendor = read_comments(&mut &*comment_data, &mut comments, &mut pictures)?
},
6 => {
let mut picture_data = vec![0; block_len as usize];
data.read_exact(&mut picture_data)?;
pictures.push(Picture::from_apic_bytes(&*picture_data)?)
},
_ => {
data.seek(SeekFrom::Current(i64::from(block_len)))?;
continue;
},
}
}
let stream_length = {
let current = data.seek(SeekFrom::Current(0))?;
let end = data.seek(SeekFrom::End(0))?;
end - current
};
let properties = read_properties(&mut &*stream_info_data, stream_length)?;
Ok((vendor, pictures, comments, properties, OggFormat::Flac))
}

View file

@ -8,6 +8,8 @@ pub(crate) mod constants;
pub(crate) mod read; pub(crate) mod read;
pub(crate) mod write; pub(crate) mod write;
#[cfg(feature = "format-flac")]
pub(crate) mod flac;
#[cfg(feature = "format-opus")] #[cfg(feature = "format-opus")]
mod opus; mod opus;
#[cfg(feature = "format-vorbis")] #[cfg(feature = "format-vorbis")]

View file

@ -39,6 +39,50 @@ where
Ok(properties) Ok(properties)
} }
pub(crate) fn read_comments<R>(
data: &mut R,
storage: &mut HashMap<UniCase<String>, String>,
pictures: &mut Vec<Picture>,
) -> Result<String>
where
R: Read,
{
let vendor_len = data.read_u32::<LittleEndian>()?;
let mut vendor = vec![0; vendor_len as usize];
data.read_exact(&mut vendor)?;
let vendor = match String::from_utf8(vendor) {
Ok(v) => v,
Err(_) => {
return Err(LoftyError::InvalidData(
"OGG file has an invalid vendor string",
))
},
};
let comments_total_len = data.read_u32::<LittleEndian>()?;
for _ in 0..comments_total_len {
let comment_len = data.read_u32::<LittleEndian>()?;
let mut comment_bytes = vec![0; comment_len as usize];
data.read_exact(&mut comment_bytes)?;
let comment = String::from_utf8(comment_bytes)?;
let split: Vec<&str> = comment.splitn(2, '=').collect();
if split[0] == "METADATA_BLOCK_PICTURE" {
pictures.push(Picture::from_apic_bytes(split[1].as_bytes())?)
} else {
storage.insert(UniCase::from(split[0].to_string()), split[1].to_string());
}
}
Ok(vendor)
}
pub(crate) fn read_from<T>( pub(crate) fn read_from<T>(
data: &mut T, data: &mut T,
header_sig: &[u8], header_sig: &[u8],
@ -75,40 +119,9 @@ where
let mut pictures = Vec::new(); let mut pictures = Vec::new();
let reader = &mut &md_pages[..]; let reader = &mut &md_pages[..];
let vendor = read_comments(reader, &mut md, &mut pictures)?;
let vendor_len = reader.read_u32::<LittleEndian>()?;
let mut vendor = vec![0; vendor_len as usize];
reader.read_exact(&mut vendor)?;
let vendor_str = match String::from_utf8(vendor) {
Ok(v) => v,
Err(_) => {
return Err(LoftyError::InvalidData(
"OGG file has an invalid vendor string",
))
},
};
let comments_total_len = reader.read_u32::<LittleEndian>()?;
for _ in 0..comments_total_len {
let comment_len = reader.read_u32::<LittleEndian>()?;
let mut comment_bytes = vec![0; comment_len as usize];
reader.read_exact(&mut comment_bytes)?;
let comment = String::from_utf8(comment_bytes)?;
let split: Vec<&str> = comment.splitn(2, '=').collect();
if split[0] == "METADATA_BLOCK_PICTURE" {
pictures.push(Picture::from_apic_bytes(split[1].as_bytes())?)
} else {
md.insert(UniCase::from(split[0].to_string()), split[1].to_string());
}
}
let properties = read_properties(data, header_sig, &first_page)?; let properties = read_properties(data, header_sig, &first_page)?;
Ok((vendor_str, pictures, md, properties, format)) Ok((vendor, pictures, md, properties, format))
} }

View file

@ -46,20 +46,25 @@ where
let last_page = find_last_page(data)?; let last_page = find_last_page(data)?;
let last_page_abgp = last_page.abgp; let last_page_abgp = last_page.abgp;
last_page_abgp.checked_sub(first_page_abgp).map_or_else(|| Err(LoftyError::InvalidData( last_page_abgp.checked_sub(first_page_abgp).map_or_else(
"OGG file contains incorrect PCM values", || {
)), |frame_count| { Err(LoftyError::InvalidData(
let length = frame_count * 1000 / u64::from(sample_rate); "OGG file contains incorrect PCM values",
let duration = Duration::from_millis(length as u64); ))
let bitrate = ((audio_size * 8) / length) as u32; },
|frame_count| {
let length = frame_count * 1000 / u64::from(sample_rate);
let duration = Duration::from_millis(length as u64);
let bitrate = ((audio_size * 8) / length) as u32;
Ok(FileProperties { Ok(FileProperties {
duration, duration,
bitrate: Some(bitrate), bitrate: Some(bitrate),
sample_rate: Some(sample_rate), sample_rate: Some(sample_rate),
channels: Some(channels), channels: Some(channels),
}) })
}) },
)
} }
pub fn write_to( pub fn write_to(

View file

@ -80,6 +80,10 @@ impl AudioTagEdit for AiffTag {
fn tag_type(&self) -> TagType { fn tag_type(&self) -> TagType {
TagType::AiffText TagType::AiffText
} }
fn properties(&self) -> &FileProperties {
&self.properties
}
} }
impl AudioTagWrite for AiffTag { impl AudioTagWrite for AiffTag {

View file

@ -294,6 +294,10 @@ impl AudioTagEdit for ApeTag {
fn remove_key(&mut self, key: &str) { fn remove_key(&mut self, key: &str) {
self.remove_key(key) self.remove_key(key)
} }
fn properties(&self) -> &FileProperties {
&self.properties
}
} }
impl AudioTagWrite for ApeTag { impl AudioTagWrite for ApeTag {

View file

@ -389,6 +389,10 @@ impl AudioTagEdit for Id3v2Tag {
fn remove_key(&mut self, key: &str) { fn remove_key(&mut self, key: &str) {
self.inner.remove(key) self.inner.remove(key)
} }
fn properties(&self) -> &FileProperties {
&self.properties
}
} }
impl AudioTagWrite for Id3v2Tag { impl AudioTagWrite for Id3v2Tag {

View file

@ -314,6 +314,10 @@ impl AudioTagEdit for Mp4Tag {
fn tag_type(&self) -> TagType { fn tag_type(&self) -> TagType {
TagType::Mp4 TagType::Mp4
} }
fn properties(&self) -> &FileProperties {
&self.properties
}
} }
impl AudioTagWrite for Mp4Tag { impl AudioTagWrite for Mp4Tag {

View file

@ -10,8 +10,6 @@ use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, FileProperties, LoftyError, OggFormat, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, FileProperties, LoftyError, OggFormat,
Picture, PictureType, Result, TagType, ToAny, ToAnyTag, Picture, PictureType, Result, TagType, ToAny, ToAnyTag,
}; };
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
use crate::components::logic::ogg::read::OGGTags; use crate::components::logic::ogg::read::OGGTags;
use std::borrow::Cow; use std::borrow::Cow;
@ -161,7 +159,7 @@ impl OggTag {
}, },
#[cfg(feature = "format-flac")] #[cfg(feature = "format-flac")]
OggFormat::Flac => { OggFormat::Flac => {
let tag = metaflac::Tag::read_from(reader)?; let tag = ogg::flac::read_from(reader)?;
tag.try_into()? tag.try_into()?
}, },
@ -380,6 +378,10 @@ impl AudioTagEdit for OggTag {
fn remove_key(&mut self, key: &str) { fn remove_key(&mut self, key: &str) {
self.remove_key(key) self.remove_key(key)
} }
fn properties(&self) -> &FileProperties {
&self.properties
}
} }
impl AudioTagWrite for OggTag { impl AudioTagWrite for OggTag {

View file

@ -150,6 +150,10 @@ impl AudioTagEdit for RiffTag {
fn remove_key(&mut self, key: &str) { fn remove_key(&mut self, key: &str) {
self.remove_key(key) self.remove_key(key)
} }
fn properties(&self) -> &FileProperties {
&self.properties
}
} }
impl AudioTagWrite for RiffTag { impl AudioTagWrite for RiffTag {

View file

@ -1,6 +1,6 @@
#[allow(clippy::wildcard_imports)] #[allow(clippy::wildcard_imports)]
use crate::components::tags::*; use crate::components::tags::*;
use crate::{Album, AnyTag, Picture, Result, TagType}; use crate::{Album, AnyTag, Picture, Result, TagType, FileProperties};
use std::borrow::Cow; use std::borrow::Cow;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
@ -128,6 +128,9 @@ pub trait AudioTagEdit {
/// ///
/// See [`get_key`][AudioTagEdit::get_key]'s note /// See [`get_key`][AudioTagEdit::get_key]'s note
fn remove_key(&mut self, _key: &str) {} fn remove_key(&mut self, _key: &str) {}
/// Returns the [`FileProperties`][crate::FileProperties]
fn properties(&self) -> &FileProperties;
} }
/// Functions for writing to a file /// Functions for writing to a file