mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
Properties: Breakup module
`FileProperties` and `ChannelMask` are no longer in the root. They are moved to `lofty::properties`.
This commit is contained in:
parent
b407fcf3fb
commit
5c69024b91
11 changed files with 649 additions and 636 deletions
|
@ -12,10 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
Lofty writes tags. These are best used as global user-configurable options, as most options will
|
||||
not apply to all files. The defaults are set to be as safe as possible,
|
||||
see [here](https://docs.rs/lofty/latest/lofty/struct.WriteOptions.html#impl-Default-for-WriteOptions).
|
||||
- **ChannelMask** ([PR](https://github.com/Serial-ATA/lofty-rs/pull/370))
|
||||
- **ChannelMask** ([PR](https://github.com/Serial-ATA/lofty-rs/pull/371))
|
||||
- `BitAnd` and `BitOr` implementations
|
||||
- Associated constants for common channels, ex. `ChannelMask::FRONT_LEFT`
|
||||
|
||||
### Changed
|
||||
- **Properties**: `FileProperties` and `ChannelMask` have been moved from the root to the new `lofty::properties`
|
||||
module ([PR](https://github.com/Serial-ATA/lofty-rs/pull/372))
|
||||
|
||||
### Fixed
|
||||
- **Vorbis**: Fix panic when reading properties of zero-length files ([issue](https://github.com/Serial-ATA/lofty-rs/issues/342)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/365))
|
||||
- **ID3v2**: Fix panic when reading an RVA2 frame with a peak larger than 248 bits ([issue](https://github.com/Serial-ATA/lofty-rs/issues/295)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/364))
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use lofty::ape::ApeTag;
|
||||
use lofty::error::Result as LoftyResult;
|
||||
use lofty::id3::v2::Id3v2Tag;
|
||||
use lofty::properties::FileProperties;
|
||||
use lofty::resolve::FileResolver;
|
||||
use lofty::{FileProperties, FileType, GlobalOptions, ParseOptions, TagType};
|
||||
use lofty::{FileType, GlobalOptions, ParseOptions, TagType};
|
||||
use lofty_attr::LoftyFile;
|
||||
|
||||
use std::fs::File;
|
||||
|
|
|
@ -429,7 +429,7 @@ fn generate_audiofile_impl(file: &LoftyFile) -> syn::Result<proc_macro2::TokenSt
|
|||
|
||||
let properties_field_ty = &properties_field.ty;
|
||||
let assert_properties_impl = quote_spanned! {properties_field_ty.span()=>
|
||||
struct _AssertIntoFileProperties where #properties_field_ty: ::std::convert::Into<::lofty::FileProperties>;
|
||||
struct _AssertIntoFileProperties where #properties_field_ty: ::std::convert::Into<::lofty::properties::FileProperties>;
|
||||
};
|
||||
|
||||
let struct_name = &file.struct_info.name;
|
||||
|
@ -537,7 +537,7 @@ fn generate_from_taggedfile_impl(file: &LoftyFile) -> proc_macro2::TokenStream {
|
|||
|
||||
::lofty::TaggedFile::new(
|
||||
#file_type_variant,
|
||||
::lofty::FileProperties::from(input.properties),
|
||||
::lofty::properties::FileProperties::from(input.properties),
|
||||
{
|
||||
let mut tags: Vec<::lofty::Tag> = Vec::new();
|
||||
#( #conditions )*
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::error::Result;
|
||||
use crate::macros::decode_err;
|
||||
use crate::math::RoundedDivision;
|
||||
use crate::properties::FileProperties;
|
||||
use crate::ChannelMask;
|
||||
use crate::properties::{ChannelMask, FileProperties};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ pub mod musepack;
|
|||
pub mod ogg;
|
||||
pub(crate) mod picture;
|
||||
mod probe;
|
||||
pub(crate) mod properties;
|
||||
pub mod properties;
|
||||
pub mod resolve;
|
||||
pub(crate) mod tag;
|
||||
mod traits;
|
||||
|
@ -181,7 +181,6 @@ pub use crate::write_options::WriteOptions;
|
|||
|
||||
pub use crate::file::{AudioFile, BoundTaggedFile, FileType, TaggedFile, TaggedFileExt};
|
||||
pub use crate::picture::{MimeType, Picture, PictureType};
|
||||
pub use crate::properties::{ChannelMask, FileProperties};
|
||||
pub use crate::tag::{Tag, TagType};
|
||||
pub use tag::item::{ItemKey, ItemValue, TagItem};
|
||||
pub use util::text::TextEncoding;
|
||||
|
|
|
@ -2,8 +2,7 @@ use super::header::{ChannelMode, Emphasis, Header, Layer, MpegVersion, XingHeade
|
|||
use crate::error::Result;
|
||||
use crate::math::RoundedDivision;
|
||||
use crate::mpeg::header::{cmp_header, rev_search_for_frame_sync, HeaderCmpResult, HEADER_MASK};
|
||||
use crate::properties::FileProperties;
|
||||
use crate::ChannelMask;
|
||||
use crate::properties::{ChannelMask, FileProperties};
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::time::Duration;
|
||||
|
|
|
@ -1,626 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
/// Various *immutable* audio properties
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct FileProperties {
|
||||
pub(crate) duration: Duration,
|
||||
pub(crate) overall_bitrate: Option<u32>,
|
||||
pub(crate) audio_bitrate: Option<u32>,
|
||||
pub(crate) sample_rate: Option<u32>,
|
||||
pub(crate) bit_depth: Option<u8>,
|
||||
pub(crate) channels: Option<u8>,
|
||||
pub(crate) channel_mask: Option<ChannelMask>,
|
||||
}
|
||||
|
||||
impl Default for FileProperties {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
duration: Duration::ZERO,
|
||||
overall_bitrate: None,
|
||||
audio_bitrate: None,
|
||||
sample_rate: None,
|
||||
bit_depth: None,
|
||||
channels: None,
|
||||
channel_mask: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileProperties {
|
||||
/// Create a new `FileProperties`
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
duration: Duration,
|
||||
overall_bitrate: Option<u32>,
|
||||
audio_bitrate: Option<u32>,
|
||||
sample_rate: Option<u32>,
|
||||
bit_depth: Option<u8>,
|
||||
channels: Option<u8>,
|
||||
channel_mask: Option<ChannelMask>,
|
||||
) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
overall_bitrate,
|
||||
audio_bitrate,
|
||||
sample_rate,
|
||||
bit_depth,
|
||||
channels,
|
||||
channel_mask,
|
||||
}
|
||||
}
|
||||
|
||||
/// Duration of the audio
|
||||
pub fn duration(&self) -> Duration {
|
||||
self.duration
|
||||
}
|
||||
|
||||
/// Overall bitrate (kbps)
|
||||
pub fn overall_bitrate(&self) -> Option<u32> {
|
||||
self.overall_bitrate
|
||||
}
|
||||
|
||||
/// Audio bitrate (kbps)
|
||||
pub fn audio_bitrate(&self) -> Option<u32> {
|
||||
self.audio_bitrate
|
||||
}
|
||||
|
||||
/// Sample rate (Hz)
|
||||
pub fn sample_rate(&self) -> Option<u32> {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
/// Bits per sample (usually 16 or 24 bit)
|
||||
pub fn bit_depth(&self) -> Option<u8> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
/// Channel count
|
||||
pub fn channels(&self) -> Option<u8> {
|
||||
self.channels
|
||||
}
|
||||
|
||||
/// Channel mask
|
||||
pub fn channel_mask(&self) -> Option<ChannelMask> {
|
||||
self.channel_mask
|
||||
}
|
||||
}
|
||||
|
||||
use std::ops::{BitAnd, BitOr};
|
||||
|
||||
macro_rules! define_channels {
|
||||
([
|
||||
$(
|
||||
$(#[$meta:meta])?
|
||||
$name:ident => $shift:literal
|
||||
),+
|
||||
]) => {
|
||||
impl ChannelMask {
|
||||
$(
|
||||
$(#[$meta])?
|
||||
#[allow(missing_docs)]
|
||||
pub const $name: Self = Self(1 << $shift);
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Channel mask
|
||||
///
|
||||
/// A mask of (at least) 18 bits, one for each channel.
|
||||
///
|
||||
/// * Standard speaker channels: <https://en.wikipedia.org/wiki/Surround_sound#Channel_notation>
|
||||
/// * CAF channel bitmap: <https://developer.apple.com/library/archive/documentation/MusicAudio/Reference/CAFSpec/CAF_spec/CAF_spec.html#//apple_ref/doc/uid/TP40001862-CH210-BCGBHHHI>
|
||||
/// * WAV default channel ordering: <https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)>
|
||||
/// * FFmpeg: <https://ffmpeg.org/doxygen/trunk/group__channel__masks.html>
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct ChannelMask(pub(crate) u32);
|
||||
|
||||
define_channels! {
|
||||
[
|
||||
FRONT_LEFT => 0,
|
||||
FRONT_RIGHT => 1,
|
||||
FRONT_CENTER => 2,
|
||||
LOW_FREQUENCY => 3,
|
||||
BACK_LEFT => 4,
|
||||
BACK_RIGHT => 5,
|
||||
FRONT_LEFT_OF_CENTER => 6,
|
||||
FRONT_RIGHT_OF_CENTER => 7,
|
||||
BACK_CENTER => 8,
|
||||
SIDE_LEFT => 9,
|
||||
SIDE_RIGHT => 10,
|
||||
TOP_CENTER => 11,
|
||||
TOP_FRONT_LEFT => 12,
|
||||
TOP_FRONT_CENTER => 13,
|
||||
TOP_FRONT_RIGHT => 14,
|
||||
TOP_BACK_LEFT => 15,
|
||||
TOP_BACK_CENTER => 16,
|
||||
TOP_BACK_RIGHT => 17
|
||||
]
|
||||
}
|
||||
|
||||
impl ChannelMask {
|
||||
/// A single front center channel
|
||||
#[must_use]
|
||||
pub const fn mono() -> Self {
|
||||
Self::FRONT_CENTER
|
||||
}
|
||||
|
||||
/// Front left+right channels
|
||||
#[must_use]
|
||||
pub const fn stereo() -> Self {
|
||||
// TODO: #![feature(const_trait_impl)]
|
||||
Self(Self::FRONT_LEFT.0 | Self::FRONT_RIGHT.0)
|
||||
}
|
||||
|
||||
/// The bit mask
|
||||
#[must_use]
|
||||
pub const fn bits(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for ChannelMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for ChannelMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self {
|
||||
Self(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::aac::{AACProperties, AacFile};
|
||||
use crate::ape::{ApeFile, ApeProperties};
|
||||
use crate::flac::{FlacFile, FlacProperties};
|
||||
use crate::iff::aiff::{AiffFile, AiffProperties};
|
||||
use crate::iff::wav::{WavFile, WavFormat, WavProperties};
|
||||
use crate::mp4::{AudioObjectType, Mp4Codec, Mp4File, Mp4Properties};
|
||||
use crate::mpeg::{ChannelMode, Layer, MpegFile, MpegProperties, MpegVersion};
|
||||
use crate::musepack::sv4to6::MpcSv4to6Properties;
|
||||
use crate::musepack::sv7::{Link, MpcSv7Properties, Profile};
|
||||
use crate::musepack::sv8::{EncoderInfo, MpcSv8Properties, ReplayGain, StreamHeader};
|
||||
use crate::musepack::{MpcFile, MpcProperties};
|
||||
use crate::ogg::{
|
||||
OpusFile, OpusProperties, SpeexFile, SpeexProperties, VorbisFile, VorbisProperties,
|
||||
};
|
||||
use crate::probe::ParseOptions;
|
||||
use crate::wavpack::{WavPackFile, WavPackProperties};
|
||||
use crate::{AudioFile, ChannelMask};
|
||||
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
|
||||
// These values are taken from FFmpeg's ffprobe
|
||||
// There is a chance they will be +/- 1, anything greater (for real world files)
|
||||
// is an issue.
|
||||
|
||||
const AAC_PROPERTIES: AACProperties = AACProperties {
|
||||
version: MpegVersion::V4,
|
||||
audio_object_type: AudioObjectType::AacLowComplexity,
|
||||
duration: Duration::from_millis(1474), /* TODO: This is ~100ms greater than FFmpeg's report, can we do better? */
|
||||
overall_bitrate: 117, // 9 less than FFmpeg reports
|
||||
audio_bitrate: 117, // 9 less than FFmpeg reports
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
copyright: false,
|
||||
original: false,
|
||||
};
|
||||
|
||||
const AIFF_PROPERTIES: AiffProperties = AiffProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 1542,
|
||||
audio_bitrate: 1536,
|
||||
sample_rate: 48000,
|
||||
sample_size: 16,
|
||||
channels: 2,
|
||||
compression_type: None,
|
||||
};
|
||||
|
||||
const APE_PROPERTIES: ApeProperties = ApeProperties {
|
||||
version: 3990,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 361,
|
||||
audio_bitrate: 360,
|
||||
sample_rate: 48000,
|
||||
bit_depth: 16,
|
||||
channels: 2,
|
||||
};
|
||||
|
||||
const FLAC_PROPERTIES: FlacProperties = FlacProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 321,
|
||||
audio_bitrate: 275,
|
||||
sample_rate: 48000,
|
||||
bit_depth: 16,
|
||||
channels: 2,
|
||||
signature: 164_506_065_180_489_231_127_156_351_872_182_799_315,
|
||||
};
|
||||
|
||||
const MP1_PROPERTIES: MpegProperties = MpegProperties {
|
||||
version: MpegVersion::V1,
|
||||
layer: Layer::Layer1,
|
||||
channel_mode: ChannelMode::Stereo,
|
||||
mode_extension: None,
|
||||
copyright: false,
|
||||
original: true,
|
||||
duration: Duration::from_millis(588), // FFmpeg reports 576, possibly an issue
|
||||
overall_bitrate: 383, // TODO: FFmpeg reports 392
|
||||
audio_bitrate: 384,
|
||||
sample_rate: 32000,
|
||||
channels: 2,
|
||||
emphasis: None,
|
||||
};
|
||||
|
||||
const MP2_PROPERTIES: MpegProperties = MpegProperties {
|
||||
version: MpegVersion::V1,
|
||||
layer: Layer::Layer2,
|
||||
channel_mode: ChannelMode::Stereo,
|
||||
mode_extension: None,
|
||||
copyright: false,
|
||||
original: true,
|
||||
duration: Duration::from_millis(1344), // TODO: FFmpeg reports 1440 here
|
||||
overall_bitrate: 411, // FFmpeg reports 384, related to above issue
|
||||
audio_bitrate: 384,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
emphasis: None,
|
||||
};
|
||||
|
||||
const MP3_PROPERTIES: MpegProperties = MpegProperties {
|
||||
version: MpegVersion::V1,
|
||||
layer: Layer::Layer3,
|
||||
channel_mode: ChannelMode::Stereo,
|
||||
mode_extension: None,
|
||||
copyright: false,
|
||||
original: false,
|
||||
duration: Duration::from_millis(1464),
|
||||
overall_bitrate: 64,
|
||||
audio_bitrate: 62,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
emphasis: None,
|
||||
};
|
||||
|
||||
const MP4_AAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::AAC,
|
||||
extended_audio_object_type: Some(AudioObjectType::AacLowComplexity),
|
||||
duration: Duration::from_millis(1449),
|
||||
overall_bitrate: 135,
|
||||
audio_bitrate: 124,
|
||||
sample_rate: 48000,
|
||||
bit_depth: None,
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MP4_ALAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::ALAC,
|
||||
extended_audio_object_type: None,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 331,
|
||||
audio_bitrate: 1536,
|
||||
sample_rate: 48000,
|
||||
bit_depth: Some(16),
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MP4_ALS_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::AAC,
|
||||
extended_audio_object_type: Some(AudioObjectType::AudioLosslessCoding),
|
||||
duration: Duration::from_millis(1429),
|
||||
overall_bitrate: 1083,
|
||||
audio_bitrate: 1078,
|
||||
sample_rate: 48000,
|
||||
bit_depth: None,
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MP4_FLAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::FLAC,
|
||||
extended_audio_object_type: None,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 280,
|
||||
audio_bitrate: 275,
|
||||
sample_rate: 48000,
|
||||
bit_depth: Some(16),
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MPC_SV5_PROPERTIES: MpcSv4to6Properties = MpcSv4to6Properties {
|
||||
duration: Duration::from_millis(27),
|
||||
audio_bitrate: 41,
|
||||
channels: 2,
|
||||
frame_count: 1009,
|
||||
mid_side_stereo: true,
|
||||
stream_version: 5,
|
||||
max_band: 31,
|
||||
sample_rate: 44100,
|
||||
};
|
||||
|
||||
const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 86,
|
||||
audio_bitrate: 86,
|
||||
channels: 2,
|
||||
frame_count: 60,
|
||||
intensity_stereo: false,
|
||||
mid_side_stereo: true,
|
||||
max_band: 26,
|
||||
profile: Profile::Standard,
|
||||
link: Link::VeryLowStartOrEnd,
|
||||
sample_freq: 48000,
|
||||
max_level: 0,
|
||||
title_gain: 16594,
|
||||
title_peak: 0,
|
||||
album_gain: 16594,
|
||||
album_peak: 0,
|
||||
true_gapless: true,
|
||||
last_frame_length: 578,
|
||||
fast_seeking_safe: false,
|
||||
encoder_version: 192,
|
||||
};
|
||||
|
||||
const MPC_SV8_PROPERTIES: MpcSv8Properties = MpcSv8Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 82,
|
||||
audio_bitrate: 82,
|
||||
stream_header: StreamHeader {
|
||||
crc: 4_252_559_415,
|
||||
stream_version: 8,
|
||||
sample_count: 68546,
|
||||
beginning_silence: 0,
|
||||
sample_rate: 48000,
|
||||
max_used_bands: 26,
|
||||
channels: 2,
|
||||
ms_used: true,
|
||||
audio_block_frames: 64,
|
||||
},
|
||||
replay_gain: ReplayGain {
|
||||
version: 1,
|
||||
title_gain: 16655,
|
||||
title_peak: 21475,
|
||||
album_gain: 16655,
|
||||
album_peak: 21475,
|
||||
},
|
||||
encoder_info: Some(EncoderInfo {
|
||||
profile: 10.0,
|
||||
pns_tool: false,
|
||||
major: 1,
|
||||
minor: 30,
|
||||
build: 1,
|
||||
}),
|
||||
};
|
||||
|
||||
const OPUS_PROPERTIES: OpusProperties = OpusProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 120,
|
||||
audio_bitrate: 120,
|
||||
channels: 2,
|
||||
version: 1,
|
||||
input_sample_rate: 48000,
|
||||
};
|
||||
|
||||
const SPEEX_PROPERTIES: SpeexProperties = SpeexProperties {
|
||||
duration: Duration::from_millis(1469),
|
||||
version: 1,
|
||||
sample_rate: 32000,
|
||||
mode: 2,
|
||||
channels: 2,
|
||||
vbr: false,
|
||||
overall_bitrate: 32,
|
||||
audio_bitrate: 29,
|
||||
nominal_bitrate: 29600,
|
||||
};
|
||||
|
||||
const VORBIS_PROPERTIES: VorbisProperties = VorbisProperties {
|
||||
duration: Duration::from_millis(1451),
|
||||
overall_bitrate: 96,
|
||||
audio_bitrate: 112,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
version: 0,
|
||||
bitrate_maximum: 0,
|
||||
bitrate_nominal: 112_000,
|
||||
bitrate_minimum: 0,
|
||||
};
|
||||
|
||||
const WAV_PROPERTIES: WavProperties = WavProperties {
|
||||
format: WavFormat::PCM,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 1542,
|
||||
audio_bitrate: 1536,
|
||||
sample_rate: 48000,
|
||||
bit_depth: 16,
|
||||
channels: 2,
|
||||
channel_mask: None,
|
||||
};
|
||||
|
||||
const WAVPACK_PROPERTIES: WavPackProperties = WavPackProperties {
|
||||
version: 1040,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 598,
|
||||
audio_bitrate: 597,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
channel_mask: ChannelMask::stereo(),
|
||||
bit_depth: 16,
|
||||
lossless: true,
|
||||
};
|
||||
|
||||
fn get_properties<T>(path: &str) -> T::Properties
|
||||
where
|
||||
T: AudioFile,
|
||||
<T as AudioFile>::Properties: Clone,
|
||||
{
|
||||
let mut f = File::open(path).unwrap();
|
||||
|
||||
let audio_file = T::read_from(&mut f, ParseOptions::default()).unwrap();
|
||||
|
||||
audio_file.properties().clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<AacFile>("tests/files/assets/minimal/full_test.aac"),
|
||||
AAC_PROPERTIES
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aiff_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<AiffFile>("tests/files/assets/minimal/full_test.aiff"),
|
||||
AIFF_PROPERTIES
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ape_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<ApeFile>("tests/files/assets/minimal/full_test.ape"),
|
||||
APE_PROPERTIES
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<FlacFile>("tests/files/assets/minimal/full_test.flac"),
|
||||
FLAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp1_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpegFile>("tests/files/assets/minimal/full_test.mp1"),
|
||||
MP1_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp2_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpegFile>("tests/files/assets/minimal/full_test.mp2"),
|
||||
MP2_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp3_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpegFile>("tests/files/assets/minimal/full_test.mp3"),
|
||||
MP3_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_aac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/m4a_codec_aac.m4a"),
|
||||
MP4_AAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_alac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/m4a_codec_alac.m4a"),
|
||||
MP4_ALAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_als_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/mp4_codec_als.mp4"),
|
||||
MP4_ALS_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_flac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/mp4_codec_flac.mp4"),
|
||||
MP4_FLAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv5_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv5.mpc"),
|
||||
MpcProperties::Sv4to6(MPC_SV5_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv7_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv7.mpc"),
|
||||
MpcProperties::Sv7(MPC_SV7_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv8_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv8.mpc"),
|
||||
MpcProperties::Sv8(MPC_SV8_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opus_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<OpusFile>("tests/files/assets/minimal/full_test.opus"),
|
||||
OPUS_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn speex_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<SpeexFile>("tests/files/assets/minimal/full_test.spx"),
|
||||
SPEEX_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vorbis_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<VorbisFile>("tests/files/assets/minimal/full_test.ogg"),
|
||||
VORBIS_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wav_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<WavFile>("tests/files/assets/minimal/wav_format_pcm.wav"),
|
||||
WAV_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wavpack_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<WavPackFile>("tests/files/assets/minimal/full_test.wv"),
|
||||
WAVPACK_PROPERTIES
|
||||
)
|
||||
}
|
||||
}
|
90
src/properties/channel_mask.rs
Normal file
90
src/properties/channel_mask.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use std::ops::{BitAnd, BitOr};
|
||||
|
||||
macro_rules! define_channels {
|
||||
([
|
||||
$(
|
||||
$(#[$meta:meta])?
|
||||
$name:ident => $shift:literal
|
||||
),+
|
||||
]) => {
|
||||
impl ChannelMask {
|
||||
$(
|
||||
$(#[$meta])?
|
||||
#[allow(missing_docs)]
|
||||
pub const $name: Self = Self(1 << $shift);
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Channel mask
|
||||
///
|
||||
/// A mask of (at least) 18 bits, one for each channel.
|
||||
///
|
||||
/// * Standard speaker channels: <https://en.wikipedia.org/wiki/Surround_sound#Channel_notation>
|
||||
/// * CAF channel bitmap: <https://developer.apple.com/library/archive/documentation/MusicAudio/Reference/CAFSpec/CAF_spec/CAF_spec.html#//apple_ref/doc/uid/TP40001862-CH210-BCGBHHHI>
|
||||
/// * WAV default channel ordering: <https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)>
|
||||
/// * FFmpeg: <https://ffmpeg.org/doxygen/trunk/group__channel__masks.html>
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct ChannelMask(pub(crate) u32);
|
||||
|
||||
define_channels! {
|
||||
[
|
||||
FRONT_LEFT => 0,
|
||||
FRONT_RIGHT => 1,
|
||||
FRONT_CENTER => 2,
|
||||
LOW_FREQUENCY => 3,
|
||||
BACK_LEFT => 4,
|
||||
BACK_RIGHT => 5,
|
||||
FRONT_LEFT_OF_CENTER => 6,
|
||||
FRONT_RIGHT_OF_CENTER => 7,
|
||||
BACK_CENTER => 8,
|
||||
SIDE_LEFT => 9,
|
||||
SIDE_RIGHT => 10,
|
||||
TOP_CENTER => 11,
|
||||
TOP_FRONT_LEFT => 12,
|
||||
TOP_FRONT_CENTER => 13,
|
||||
TOP_FRONT_RIGHT => 14,
|
||||
TOP_BACK_LEFT => 15,
|
||||
TOP_BACK_CENTER => 16,
|
||||
TOP_BACK_RIGHT => 17
|
||||
]
|
||||
}
|
||||
|
||||
impl ChannelMask {
|
||||
/// A single front center channel
|
||||
#[must_use]
|
||||
pub const fn mono() -> Self {
|
||||
Self::FRONT_CENTER
|
||||
}
|
||||
|
||||
/// Front left+right channels
|
||||
#[must_use]
|
||||
pub const fn stereo() -> Self {
|
||||
// TODO: #![feature(const_trait_impl)]
|
||||
Self(Self::FRONT_LEFT.0 | Self::FRONT_RIGHT.0)
|
||||
}
|
||||
|
||||
/// The bit mask
|
||||
#[must_use]
|
||||
pub const fn bits(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for ChannelMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for ChannelMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self {
|
||||
Self(self.0 & rhs.0)
|
||||
}
|
||||
}
|
88
src/properties/file_properties.rs
Normal file
88
src/properties/file_properties.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use super::channel_mask::ChannelMask;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Various *immutable* audio properties
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct FileProperties {
|
||||
pub(crate) duration: Duration,
|
||||
pub(crate) overall_bitrate: Option<u32>,
|
||||
pub(crate) audio_bitrate: Option<u32>,
|
||||
pub(crate) sample_rate: Option<u32>,
|
||||
pub(crate) bit_depth: Option<u8>,
|
||||
pub(crate) channels: Option<u8>,
|
||||
pub(crate) channel_mask: Option<ChannelMask>,
|
||||
}
|
||||
|
||||
impl Default for FileProperties {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
duration: Duration::ZERO,
|
||||
overall_bitrate: None,
|
||||
audio_bitrate: None,
|
||||
sample_rate: None,
|
||||
bit_depth: None,
|
||||
channels: None,
|
||||
channel_mask: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileProperties {
|
||||
/// Create a new `FileProperties`
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
duration: Duration,
|
||||
overall_bitrate: Option<u32>,
|
||||
audio_bitrate: Option<u32>,
|
||||
sample_rate: Option<u32>,
|
||||
bit_depth: Option<u8>,
|
||||
channels: Option<u8>,
|
||||
channel_mask: Option<ChannelMask>,
|
||||
) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
overall_bitrate,
|
||||
audio_bitrate,
|
||||
sample_rate,
|
||||
bit_depth,
|
||||
channels,
|
||||
channel_mask,
|
||||
}
|
||||
}
|
||||
|
||||
/// Duration of the audio
|
||||
pub fn duration(&self) -> Duration {
|
||||
self.duration
|
||||
}
|
||||
|
||||
/// Overall bitrate (kbps)
|
||||
pub fn overall_bitrate(&self) -> Option<u32> {
|
||||
self.overall_bitrate
|
||||
}
|
||||
|
||||
/// Audio bitrate (kbps)
|
||||
pub fn audio_bitrate(&self) -> Option<u32> {
|
||||
self.audio_bitrate
|
||||
}
|
||||
|
||||
/// Sample rate (Hz)
|
||||
pub fn sample_rate(&self) -> Option<u32> {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
/// Bits per sample (usually 16 or 24 bit)
|
||||
pub fn bit_depth(&self) -> Option<u8> {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
/// Channel count
|
||||
pub fn channels(&self) -> Option<u8> {
|
||||
self.channels
|
||||
}
|
||||
|
||||
/// Channel mask
|
||||
pub fn channel_mask(&self) -> Option<ChannelMask> {
|
||||
self.channel_mask
|
||||
}
|
||||
}
|
14
src/properties/mod.rs
Normal file
14
src/properties/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
//! Generic audio properties
|
||||
//!
|
||||
//! Many audio formats have their own custom properties, but there are some properties that are
|
||||
//! common to all audio formats. When using [`TaggedFile`](crate::TaggedFile), any custom properties
|
||||
//! will simply be converted to [`FileProperties`].
|
||||
|
||||
mod channel_mask;
|
||||
mod file_properties;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use channel_mask::ChannelMask;
|
||||
pub use file_properties::FileProperties;
|
445
src/properties/tests.rs
Normal file
445
src/properties/tests.rs
Normal file
|
@ -0,0 +1,445 @@
|
|||
use crate::aac::{AACProperties, AacFile};
|
||||
use crate::ape::{ApeFile, ApeProperties};
|
||||
use crate::flac::{FlacFile, FlacProperties};
|
||||
use crate::iff::aiff::{AiffFile, AiffProperties};
|
||||
use crate::iff::wav::{WavFile, WavFormat, WavProperties};
|
||||
use crate::mp4::{AudioObjectType, Mp4Codec, Mp4File, Mp4Properties};
|
||||
use crate::mpeg::{ChannelMode, Layer, MpegFile, MpegProperties, MpegVersion};
|
||||
use crate::musepack::sv4to6::MpcSv4to6Properties;
|
||||
use crate::musepack::sv7::{Link, MpcSv7Properties, Profile};
|
||||
use crate::musepack::sv8::{EncoderInfo, MpcSv8Properties, ReplayGain, StreamHeader};
|
||||
use crate::musepack::{MpcFile, MpcProperties};
|
||||
use crate::ogg::{
|
||||
OpusFile, OpusProperties, SpeexFile, SpeexProperties, VorbisFile, VorbisProperties,
|
||||
};
|
||||
use crate::probe::ParseOptions;
|
||||
use crate::properties::ChannelMask;
|
||||
use crate::wavpack::{WavPackFile, WavPackProperties};
|
||||
use crate::AudioFile;
|
||||
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
|
||||
// These values are taken from FFmpeg's ffprobe
|
||||
// There is a chance they will be +/- 1, anything greater (for real world files)
|
||||
// is an issue.
|
||||
|
||||
const AAC_PROPERTIES: AACProperties = AACProperties {
|
||||
version: MpegVersion::V4,
|
||||
audio_object_type: AudioObjectType::AacLowComplexity,
|
||||
duration: Duration::from_millis(1474), /* TODO: This is ~100ms greater than FFmpeg's report, can we do better? */
|
||||
overall_bitrate: 117, // 9 less than FFmpeg reports
|
||||
audio_bitrate: 117, // 9 less than FFmpeg reports
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
copyright: false,
|
||||
original: false,
|
||||
};
|
||||
|
||||
const AIFF_PROPERTIES: AiffProperties = AiffProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 1542,
|
||||
audio_bitrate: 1536,
|
||||
sample_rate: 48000,
|
||||
sample_size: 16,
|
||||
channels: 2,
|
||||
compression_type: None,
|
||||
};
|
||||
|
||||
const APE_PROPERTIES: ApeProperties = ApeProperties {
|
||||
version: 3990,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 361,
|
||||
audio_bitrate: 360,
|
||||
sample_rate: 48000,
|
||||
bit_depth: 16,
|
||||
channels: 2,
|
||||
};
|
||||
|
||||
const FLAC_PROPERTIES: FlacProperties = FlacProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 321,
|
||||
audio_bitrate: 275,
|
||||
sample_rate: 48000,
|
||||
bit_depth: 16,
|
||||
channels: 2,
|
||||
signature: 164_506_065_180_489_231_127_156_351_872_182_799_315,
|
||||
};
|
||||
|
||||
const MP1_PROPERTIES: MpegProperties = MpegProperties {
|
||||
version: MpegVersion::V1,
|
||||
layer: Layer::Layer1,
|
||||
channel_mode: ChannelMode::Stereo,
|
||||
mode_extension: None,
|
||||
copyright: false,
|
||||
original: true,
|
||||
duration: Duration::from_millis(588), // FFmpeg reports 576, possibly an issue
|
||||
overall_bitrate: 383, // TODO: FFmpeg reports 392
|
||||
audio_bitrate: 384,
|
||||
sample_rate: 32000,
|
||||
channels: 2,
|
||||
emphasis: None,
|
||||
};
|
||||
|
||||
const MP2_PROPERTIES: MpegProperties = MpegProperties {
|
||||
version: MpegVersion::V1,
|
||||
layer: Layer::Layer2,
|
||||
channel_mode: ChannelMode::Stereo,
|
||||
mode_extension: None,
|
||||
copyright: false,
|
||||
original: true,
|
||||
duration: Duration::from_millis(1344), // TODO: FFmpeg reports 1440 here
|
||||
overall_bitrate: 411, // FFmpeg reports 384, related to above issue
|
||||
audio_bitrate: 384,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
emphasis: None,
|
||||
};
|
||||
|
||||
const MP3_PROPERTIES: MpegProperties = MpegProperties {
|
||||
version: MpegVersion::V1,
|
||||
layer: Layer::Layer3,
|
||||
channel_mode: ChannelMode::Stereo,
|
||||
mode_extension: None,
|
||||
copyright: false,
|
||||
original: false,
|
||||
duration: Duration::from_millis(1464),
|
||||
overall_bitrate: 64,
|
||||
audio_bitrate: 62,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
emphasis: None,
|
||||
};
|
||||
|
||||
const MP4_AAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::AAC,
|
||||
extended_audio_object_type: Some(AudioObjectType::AacLowComplexity),
|
||||
duration: Duration::from_millis(1449),
|
||||
overall_bitrate: 135,
|
||||
audio_bitrate: 124,
|
||||
sample_rate: 48000,
|
||||
bit_depth: None,
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MP4_ALAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::ALAC,
|
||||
extended_audio_object_type: None,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 331,
|
||||
audio_bitrate: 1536,
|
||||
sample_rate: 48000,
|
||||
bit_depth: Some(16),
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MP4_ALS_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::AAC,
|
||||
extended_audio_object_type: Some(AudioObjectType::AudioLosslessCoding),
|
||||
duration: Duration::from_millis(1429),
|
||||
overall_bitrate: 1083,
|
||||
audio_bitrate: 1078,
|
||||
sample_rate: 48000,
|
||||
bit_depth: None,
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MP4_FLAC_PROPERTIES: Mp4Properties = Mp4Properties {
|
||||
codec: Mp4Codec::FLAC,
|
||||
extended_audio_object_type: None,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 280,
|
||||
audio_bitrate: 275,
|
||||
sample_rate: 48000,
|
||||
bit_depth: Some(16),
|
||||
channels: 2,
|
||||
drm_protected: false,
|
||||
};
|
||||
|
||||
const MPC_SV5_PROPERTIES: MpcSv4to6Properties = MpcSv4to6Properties {
|
||||
duration: Duration::from_millis(27),
|
||||
audio_bitrate: 41,
|
||||
channels: 2,
|
||||
frame_count: 1009,
|
||||
mid_side_stereo: true,
|
||||
stream_version: 5,
|
||||
max_band: 31,
|
||||
sample_rate: 44100,
|
||||
};
|
||||
|
||||
const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 86,
|
||||
audio_bitrate: 86,
|
||||
channels: 2,
|
||||
frame_count: 60,
|
||||
intensity_stereo: false,
|
||||
mid_side_stereo: true,
|
||||
max_band: 26,
|
||||
profile: Profile::Standard,
|
||||
link: Link::VeryLowStartOrEnd,
|
||||
sample_freq: 48000,
|
||||
max_level: 0,
|
||||
title_gain: 16594,
|
||||
title_peak: 0,
|
||||
album_gain: 16594,
|
||||
album_peak: 0,
|
||||
true_gapless: true,
|
||||
last_frame_length: 578,
|
||||
fast_seeking_safe: false,
|
||||
encoder_version: 192,
|
||||
};
|
||||
|
||||
const MPC_SV8_PROPERTIES: MpcSv8Properties = MpcSv8Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 82,
|
||||
audio_bitrate: 82,
|
||||
stream_header: StreamHeader {
|
||||
crc: 4_252_559_415,
|
||||
stream_version: 8,
|
||||
sample_count: 68546,
|
||||
beginning_silence: 0,
|
||||
sample_rate: 48000,
|
||||
max_used_bands: 26,
|
||||
channels: 2,
|
||||
ms_used: true,
|
||||
audio_block_frames: 64,
|
||||
},
|
||||
replay_gain: ReplayGain {
|
||||
version: 1,
|
||||
title_gain: 16655,
|
||||
title_peak: 21475,
|
||||
album_gain: 16655,
|
||||
album_peak: 21475,
|
||||
},
|
||||
encoder_info: Some(EncoderInfo {
|
||||
profile: 10.0,
|
||||
pns_tool: false,
|
||||
major: 1,
|
||||
minor: 30,
|
||||
build: 1,
|
||||
}),
|
||||
};
|
||||
|
||||
const OPUS_PROPERTIES: OpusProperties = OpusProperties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 120,
|
||||
audio_bitrate: 120,
|
||||
channels: 2,
|
||||
version: 1,
|
||||
input_sample_rate: 48000,
|
||||
};
|
||||
|
||||
const SPEEX_PROPERTIES: SpeexProperties = SpeexProperties {
|
||||
duration: Duration::from_millis(1469),
|
||||
version: 1,
|
||||
sample_rate: 32000,
|
||||
mode: 2,
|
||||
channels: 2,
|
||||
vbr: false,
|
||||
overall_bitrate: 32,
|
||||
audio_bitrate: 29,
|
||||
nominal_bitrate: 29600,
|
||||
};
|
||||
|
||||
const VORBIS_PROPERTIES: VorbisProperties = VorbisProperties {
|
||||
duration: Duration::from_millis(1451),
|
||||
overall_bitrate: 96,
|
||||
audio_bitrate: 112,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
version: 0,
|
||||
bitrate_maximum: 0,
|
||||
bitrate_nominal: 112_000,
|
||||
bitrate_minimum: 0,
|
||||
};
|
||||
|
||||
const WAV_PROPERTIES: WavProperties = WavProperties {
|
||||
format: WavFormat::PCM,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 1542,
|
||||
audio_bitrate: 1536,
|
||||
sample_rate: 48000,
|
||||
bit_depth: 16,
|
||||
channels: 2,
|
||||
channel_mask: None,
|
||||
};
|
||||
|
||||
const WAVPACK_PROPERTIES: WavPackProperties = WavPackProperties {
|
||||
version: 1040,
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 598,
|
||||
audio_bitrate: 597,
|
||||
sample_rate: 48000,
|
||||
channels: 2,
|
||||
channel_mask: ChannelMask::stereo(),
|
||||
bit_depth: 16,
|
||||
lossless: true,
|
||||
};
|
||||
|
||||
fn get_properties<T>(path: &str) -> T::Properties
|
||||
where
|
||||
T: AudioFile,
|
||||
<T as AudioFile>::Properties: Clone,
|
||||
{
|
||||
let mut f = File::open(path).unwrap();
|
||||
|
||||
let audio_file = T::read_from(&mut f, ParseOptions::default()).unwrap();
|
||||
|
||||
audio_file.properties().clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<AacFile>("tests/files/assets/minimal/full_test.aac"),
|
||||
AAC_PROPERTIES
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aiff_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<AiffFile>("tests/files/assets/minimal/full_test.aiff"),
|
||||
AIFF_PROPERTIES
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ape_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<ApeFile>("tests/files/assets/minimal/full_test.ape"),
|
||||
APE_PROPERTIES
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<FlacFile>("tests/files/assets/minimal/full_test.flac"),
|
||||
FLAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp1_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpegFile>("tests/files/assets/minimal/full_test.mp1"),
|
||||
MP1_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp2_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpegFile>("tests/files/assets/minimal/full_test.mp2"),
|
||||
MP2_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp3_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpegFile>("tests/files/assets/minimal/full_test.mp3"),
|
||||
MP3_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_aac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/m4a_codec_aac.m4a"),
|
||||
MP4_AAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_alac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/m4a_codec_alac.m4a"),
|
||||
MP4_ALAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_als_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/mp4_codec_als.mp4"),
|
||||
MP4_ALS_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mp4_flac_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<Mp4File>("tests/files/assets/minimal/mp4_codec_flac.mp4"),
|
||||
MP4_FLAC_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv5_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv5.mpc"),
|
||||
MpcProperties::Sv4to6(MPC_SV5_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv7_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv7.mpc"),
|
||||
MpcProperties::Sv7(MPC_SV7_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv8_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv8.mpc"),
|
||||
MpcProperties::Sv8(MPC_SV8_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opus_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<OpusFile>("tests/files/assets/minimal/full_test.opus"),
|
||||
OPUS_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn speex_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<SpeexFile>("tests/files/assets/minimal/full_test.spx"),
|
||||
SPEEX_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vorbis_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<VorbisFile>("tests/files/assets/minimal/full_test.ogg"),
|
||||
VORBIS_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wav_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<WavFile>("tests/files/assets/minimal/wav_format_pcm.wav"),
|
||||
WAV_PROPERTIES
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wavpack_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<WavPackFile>("tests/files/assets/minimal/full_test.wv"),
|
||||
WAVPACK_PROPERTIES
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue