mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-13 22:22:31 +00:00
Add AIFF properties
This commit is contained in:
parent
1ead4f04f5
commit
0b79cd6bcf
3 changed files with 102 additions and 7 deletions
|
@ -1,17 +1,81 @@
|
|||
use crate::{LoftyError, Result};
|
||||
use crate::{FileProperties, LoftyError, Result};
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) fn read_from<T>(data: &mut T) -> Result<(Option<String>, Option<String>, Option<String>)>
|
||||
fn read_properties(comm: &mut &[u8], stream_len: u32) -> Result<FileProperties> {
|
||||
let channels = comm.read_u16::<BigEndian>()? as u8;
|
||||
let sample_frames = comm.read_u32::<BigEndian>()?;
|
||||
let _sample_size = comm.read_u16::<BigEndian>()?;
|
||||
|
||||
let mut sample_rate_bytes = [0; 10];
|
||||
comm.read_exact(&mut sample_rate_bytes)?;
|
||||
|
||||
let sign = u64::from(sample_rate_bytes[0] & 0x80);
|
||||
|
||||
sample_rate_bytes[0] &= 0x7f;
|
||||
|
||||
let mut exponent = u16::from(sample_rate_bytes[0]) << 8 | u16::from(sample_rate_bytes[1]);
|
||||
exponent = exponent - 16383 + 1023;
|
||||
|
||||
let fraction = &mut sample_rate_bytes[2..];
|
||||
fraction[0] &= 0x7f;
|
||||
|
||||
let fraction: Vec<u64> = fraction.iter_mut().map(|v| u64::from(*v)).collect();
|
||||
|
||||
let fraction = fraction[0] << 56
|
||||
| fraction[1] << 48
|
||||
| fraction[2] << 40
|
||||
| fraction[3] << 32
|
||||
| fraction[4] << 24
|
||||
| fraction[5] << 16
|
||||
| fraction[6] << 8
|
||||
| fraction[7];
|
||||
|
||||
let f64_bytes = sign << 56 | u64::from(exponent) << 52 | fraction >> 11;
|
||||
let float = f64::from_be_bytes(f64_bytes.to_be_bytes());
|
||||
|
||||
let sample_rate = float.round() as u32;
|
||||
|
||||
let (duration, bitrate) = if sample_rate > 0 && sample_frames > 0 {
|
||||
let length = (u64::from(sample_frames) * 1000) / u64::from(sample_rate);
|
||||
|
||||
(
|
||||
Duration::from_millis(length),
|
||||
(u64::from(stream_len * 8) / length) as u32,
|
||||
)
|
||||
} else {
|
||||
(Duration::ZERO, 0)
|
||||
};
|
||||
|
||||
Ok(FileProperties {
|
||||
duration,
|
||||
bitrate: Some(bitrate),
|
||||
sample_rate: Some(sample_rate),
|
||||
channels: Some(channels),
|
||||
})
|
||||
}
|
||||
|
||||
type AiffTags = (
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
FileProperties,
|
||||
);
|
||||
|
||||
pub(crate) fn read_from<T>(data: &mut T) -> Result<AiffTags>
|
||||
where
|
||||
T: Read + Seek,
|
||||
{
|
||||
verify_aiff(data)?;
|
||||
|
||||
let mut comm = None;
|
||||
let mut stream_len = 0;
|
||||
|
||||
let mut name_id: Option<String> = None;
|
||||
let mut author_id: Option<String> = None;
|
||||
let mut copyright_id: Option<String> = None;
|
||||
|
@ -23,6 +87,19 @@ where
|
|||
data.read_u32::<BigEndian>(),
|
||||
) {
|
||||
match &fourcc.to_le_bytes() {
|
||||
f if f == b"COMM" && comm.is_none() => {
|
||||
if size < 18 {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"AIFF file has an invalid COMM chunk size (< 18)",
|
||||
));
|
||||
}
|
||||
|
||||
let mut comm_data = vec![0; size as usize];
|
||||
data.read_exact(&mut comm_data)?;
|
||||
|
||||
comm = Some(comm_data);
|
||||
},
|
||||
f if f == b"SSND" => stream_len = size,
|
||||
f if f == b"NAME" && name_id.is_none() => {
|
||||
let mut name = vec![0; size as usize];
|
||||
data.read_exact(&mut name)?;
|
||||
|
@ -47,11 +124,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
if (&None, &None, &None) == (&name_id, &author_id, ©right_id) {
|
||||
return Err(LoftyError::InvalidData("AIFF file contains no text chunks"));
|
||||
if comm.is_none() {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"AIFF file does not contain a COMM chunk",
|
||||
));
|
||||
}
|
||||
|
||||
Ok((name_id, author_id, copyright_id))
|
||||
if stream_len == 0 {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"AIFF file does not contain a SSND chunk",
|
||||
));
|
||||
}
|
||||
|
||||
let properties = read_properties(&mut &*comm.unwrap(), stream_len)?;
|
||||
|
||||
Ok((name_id, author_id, copyright_id, properties))
|
||||
}
|
||||
|
||||
pub(crate) fn write_to(
|
||||
|
|
|
@ -32,7 +32,7 @@ impl AiffTag {
|
|||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let (name_id, author_id, copyright_id) = aiff::read_from(reader)?;
|
||||
let (name_id, author_id, copyright_id, properties) = aiff::read_from(reader)?;
|
||||
|
||||
Ok(Self {
|
||||
inner: AiffInnerTag {
|
||||
|
@ -40,7 +40,7 @@ impl AiffTag {
|
|||
author_id,
|
||||
copyright_id,
|
||||
},
|
||||
properties: FileProperties::default(), // TODO
|
||||
properties,
|
||||
_format: TagType::AiffText,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,6 +15,13 @@ const FLAC_PROPERTIES: FileProperties = FileProperties::new(
|
|||
Some(2),
|
||||
);
|
||||
|
||||
const AIFF_PROPERTIES: FileProperties = FileProperties::new(
|
||||
Duration::from_millis(1428),
|
||||
Some(1536),
|
||||
Some(48000),
|
||||
Some(2),
|
||||
);
|
||||
|
||||
macro_rules! properties_test {
|
||||
($function:ident, $path:expr, $expected:ident) => {
|
||||
#[test]
|
||||
|
@ -32,3 +39,4 @@ macro_rules! properties_test {
|
|||
properties_test!(test_opus, "tests/assets/a.opus", OPUS_PROPERTIES);
|
||||
properties_test!(test_vorbis, "tests/assets/a.ogg", VORBIS_PROPERTIES);
|
||||
properties_test!(test_flac, "tests/assets/a.flac", FLAC_PROPERTIES);
|
||||
properties_test!(test_aiff_text, "tests/assets/a_text.aiff", AIFF_PROPERTIES);
|
||||
|
|
Loading…
Reference in a new issue