mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-03-04 14:57:17 +00:00
MP4: Support property reading for files with FLAC audio
This commit is contained in:
parent
22bd6a7513
commit
e1c10bee66
7 changed files with 88 additions and 8 deletions
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
- **Vorbis Comments**: `VorbisComments::{pictures, set_picture, remove_picture}`
|
||||
- **Tag**: `Tag::{set_picture, remove_picture}`
|
||||
- **MP4**: Support property reading for files with FLAC audio
|
||||
|
||||
### Changed
|
||||
- **ID3v2**: `ID3v2Tag` now derives `Eq`
|
||||
|
|
|
@ -5,11 +5,11 @@ use std::io::{Read, Seek};
|
|||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
pub(super) struct Block {
|
||||
pub(crate) struct Block {
|
||||
pub(super) byte: u8,
|
||||
pub(super) ty: u8,
|
||||
pub(super) last: bool,
|
||||
pub(super) content: Vec<u8>,
|
||||
pub(crate) content: Vec<u8>,
|
||||
pub(super) start: u64,
|
||||
pub(super) end: u64,
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
//!
|
||||
//! * See [`FlacFile`]
|
||||
|
||||
mod block;
|
||||
mod properties;
|
||||
pub(crate) mod block;
|
||||
pub(crate) mod properties;
|
||||
mod read;
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
pub(crate) mod write;
|
||||
|
|
|
@ -47,8 +47,9 @@ where
|
|||
|
||||
if sample_rate > 0 && total_samples > 0 {
|
||||
let length = (u64::from(total_samples) * 1000) / u64::from(sample_rate);
|
||||
if length > 0 {
|
||||
properties.duration = Duration::from_millis(length);
|
||||
properties.duration = Duration::from_millis(length);
|
||||
|
||||
if length > 0 && file_length > 0 && stream_length > 0 {
|
||||
properties.overall_bitrate = Some(((file_length * 8) / length) as u32);
|
||||
properties.audio_bitrate = Some(((stream_length * 8) / length) as u32);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ pub enum Mp4Codec {
|
|||
AAC,
|
||||
ALAC,
|
||||
MP3,
|
||||
FLAC,
|
||||
}
|
||||
|
||||
impl Default for Mp4Codec {
|
||||
|
@ -341,9 +342,9 @@ where
|
|||
match fourcc {
|
||||
b"mp4a" => mp4a_properties(&mut stsd_reader, &mut properties)?,
|
||||
b"alac" => alac_properties(&mut stsd_reader, &mut properties)?,
|
||||
b"fLaC" => flac_properties(&mut stsd_reader, &mut properties)?,
|
||||
// Maybe do these?
|
||||
// TODO: dfla (https://github.com/xiph/flac/blob/master/doc/isoflac.txt)
|
||||
// TODO: dops
|
||||
// TODO: dops (opus)
|
||||
// TODO: wave (https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-134202)
|
||||
_ => {},
|
||||
}
|
||||
|
@ -585,6 +586,64 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn flac_properties<R>(stsd: &mut R, properties: &mut Mp4Properties) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
properties.codec = Mp4Codec::FLAC;
|
||||
|
||||
// Skipping 16 bytes
|
||||
//
|
||||
// Reserved (6)
|
||||
// Data reference index (2)
|
||||
// Version (2)
|
||||
// Revision level (2)
|
||||
// Vendor (4)
|
||||
stsd.seek(SeekFrom::Current(16))?;
|
||||
|
||||
properties.channels = stsd.read_u16::<BigEndian>()? as u8;
|
||||
properties.bit_depth = Some(stsd.read_u16::<BigEndian>()? as u8);
|
||||
|
||||
// Skipping 4 bytes
|
||||
//
|
||||
// Compression ID (2)
|
||||
// Packet size (2)
|
||||
stsd.seek(SeekFrom::Current(4))?;
|
||||
|
||||
properties.sample_rate = u32::from(stsd.read_u16::<BigEndian>()?);
|
||||
|
||||
let _reserved = stsd.read_u16::<BigEndian>()?;
|
||||
|
||||
let dfla_atom = AtomInfo::read(stsd)?;
|
||||
match dfla_atom.ident {
|
||||
// There should be a dfla atom, but it's not worth erroring if absent.
|
||||
AtomIdent::Fourcc(ref fourcc) if fourcc == b"dfla" => {},
|
||||
_ => return Ok(()),
|
||||
}
|
||||
|
||||
// Skipping 4 bytes
|
||||
//
|
||||
// Version (1)
|
||||
// Flags (3)
|
||||
stsd.seek(SeekFrom::Current(4))?;
|
||||
|
||||
if dfla_atom.len - 12 < 18 {
|
||||
// The atom isn't long enough to hold a STREAMINFO block, also not worth an error.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let stream_info_block = crate::flac::block::Block::read(stsd)?;
|
||||
let flac_properties =
|
||||
crate::flac::properties::read_properties(&mut &stream_info_block.content[..], 0, 0)?;
|
||||
|
||||
// Safe to unwrap, since these fields are guaranteed to be present
|
||||
properties.sample_rate = flac_properties.sample_rate.unwrap();
|
||||
properties.bit_depth = flac_properties.bit_depth;
|
||||
properties.channels = flac_properties.channels.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Used to calculate the bitrate, when it isn't readily available to us
|
||||
fn mdat_length<R>(data: &mut R) -> Result<u64>
|
||||
where
|
||||
|
|
|
@ -153,6 +153,17 @@ mod tests {
|
|||
channels: 2,
|
||||
};
|
||||
|
||||
const MP4_FLAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::FLAC,
|
||||
extended_audio_object_type: None,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 280, // TODO: FFmpeg reports 279
|
||||
audio_bitrate: 275,
|
||||
sample_rate: 48000,
|
||||
bit_depth: Some(16),
|
||||
channels: 2,
|
||||
};
|
||||
|
||||
const OPUS_PROPERTIES: OpusProperties = OpusProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 120,
|
||||
|
@ -275,6 +286,14 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_flac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/mp4_codec_flac.mp4"),
|
||||
MP4_FLAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opus_properties() {
|
||||
assert_eq!(
|
||||
|
|
BIN
tests/files/assets/minimal/mp4_codec_flac.mp4
Normal file
BIN
tests/files/assets/minimal/mp4_codec_flac.mp4
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue