use crate::mp3::{ChannelMode, Emphasis, MPEGLayer, MPEGVersion};
use crate::{AudexError, Result};
use byteorder::{BigEndian, ReadBytesExt};
use std::fmt;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::time::Duration;
type MPEGHeaderInfo = (
MPEGVersion,
MPEGLayer,
u32,
u32,
ChannelMode,
Emphasis,
bool,
bool,
bool,
bool,
bool,
u8,
);
#[derive(Debug, Clone)]
pub struct LAMEError {
pub message: String,
}
impl fmt::Display for LAMEError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LAME Error: {}", self.message)
}
}
impl std::error::Error for LAMEError {}
impl LAMEError {
pub fn new(message: &str) -> Self {
Self {
message: message.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct XingHeaderError {
pub message: String,
}
impl fmt::Display for XingHeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Xing Header Error: {}", self.message)
}
}
impl std::error::Error for XingHeaderError {}
impl XingHeaderError {
pub fn new(message: &str) -> Self {
Self {
message: message.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct VBRIHeaderError {
pub message: String,
}
impl fmt::Display for VBRIHeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "VBRI Header Error: {}", self.message)
}
}
impl std::error::Error for VBRIHeaderError {}
impl VBRIHeaderError {
pub fn new(message: &str) -> Self {
Self {
message: message.to_string(),
}
}
}
#[derive(Debug)]
pub struct BitReader {
data: Vec<u8>,
byte_pos: usize,
bit_pos: u8, }
impl BitReader {
pub fn new(data: &[u8]) -> Self {
Self {
data: data.to_vec(),
byte_pos: 0,
bit_pos: 0,
}
}
pub fn bits(&mut self, count: usize) -> std::result::Result<u32, LAMEError> {
if count == 0 {
return Ok(0);
}
if count > 32 {
return Err(LAMEError::new("Cannot read more than 32 bits at once"));
}
let mut result = 0u32;
let mut bits_read = 0;
while bits_read < count {
if self.byte_pos >= self.data.len() {
return Err(LAMEError::new("Not enough data"));
}
let current_byte = self.data[self.byte_pos];
let bits_in_current_byte = 8 - self.bit_pos as usize;
let bits_needed = count - bits_read;
let bits_to_read = std::cmp::min(bits_in_current_byte, bits_needed);
let shift = bits_in_current_byte - bits_to_read;
let mask = if bits_to_read >= 8 {
0xFF
} else {
(1u8 << bits_to_read) - 1
};
let bits = (current_byte >> shift) & mask;
result = (result << bits_to_read) | (bits as u32);
bits_read += bits_to_read;
self.bit_pos += bits_to_read as u8;
if self.bit_pos >= 8 {
self.bit_pos = 0;
self.byte_pos += 1;
}
}
Ok(result)
}
pub fn bytes(&mut self, count: usize) -> std::result::Result<Vec<u8>, LAMEError> {
if self.bit_pos != 0 {
return Err(LAMEError::new(
"Cannot read bytes from non-byte-aligned position",
));
}
if self.byte_pos + count > self.data.len() {
return Err(LAMEError::new("Not enough data"));
}
let result = self.data[self.byte_pos..self.byte_pos + count].to_vec();
self.byte_pos += count;
Ok(result)
}
pub fn skip(&mut self, count: usize) -> std::result::Result<(), LAMEError> {
self.bits(count)?;
Ok(())
}
pub fn is_aligned(&self) -> bool {
self.bit_pos == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BitrateMode {
#[default]
Unknown = 0,
CBR = 1,
VBR = 2,
ABR = 3,
}
#[derive(Debug, Clone)]
pub struct XingHeaderFlags;
impl XingHeaderFlags {
pub const FRAMES: u32 = 0x1;
pub const BYTES: u32 = 0x2;
pub const TOC: u32 = 0x4;
pub const VBR_SCALE: u32 = 0x8;
}
const XING_FRAMES_FLAG: u32 = XingHeaderFlags::FRAMES;
const XING_BYTES_FLAG: u32 = XingHeaderFlags::BYTES;
const XING_TOC_FLAG: u32 = XingHeaderFlags::TOC;
const XING_VBR_SCALE_FLAG: u32 = XingHeaderFlags::VBR_SCALE;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XingHeader {
pub frames: Option<u32>,
pub bytes: Option<u32>,
pub toc: Vec<i32>,
pub vbr_scale: i32,
pub lame_header: Option<LAMEHeader>,
pub lame_version: (i32, i32),
pub lame_version_desc: String,
pub is_info: bool,
}
impl Default for XingHeader {
fn default() -> Self {
Self {
frames: None,
bytes: None,
toc: Vec::new(),
vbr_scale: -1,
lame_header: None,
lame_version: (0, 0),
lame_version_desc: String::new(),
is_info: false,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LAMEHeader {
pub vbr_method: i32,
pub lowpass_filter: i32,
pub quality: i32,
pub vbr_quality: i32,
pub track_peak: Option<f32>,
pub track_gain_origin: i32,
pub track_gain_adjustment: Option<f32>,
pub album_gain_origin: i32,
pub album_gain_adjustment: Option<f32>,
pub encoding_flags: i32,
pub ath_type: i32,
pub bitrate: i32,
pub encoder_delay_start: i32,
pub encoder_padding_end: i32,
pub source_sample_frequency_enum: i32,
pub unwise_setting_used: bool,
pub stereo_mode: i32,
pub noise_shaping: i32,
pub mp3_gain: i32,
pub surround_info: i32,
pub preset_used: i32,
pub music_length: u32,
pub music_crc: i32,
pub header_crc: i32,
}
impl Default for LAMEHeader {
fn default() -> Self {
Self {
vbr_method: 0,
lowpass_filter: 0,
quality: -1,
vbr_quality: -1,
track_peak: None,
track_gain_origin: 0,
track_gain_adjustment: None,
album_gain_origin: 0,
album_gain_adjustment: None,
encoding_flags: 0,
ath_type: -1,
bitrate: -1,
encoder_delay_start: 0,
encoder_padding_end: 0,
source_sample_frequency_enum: -1,
unwise_setting_used: false,
stereo_mode: 0,
noise_shaping: 0,
mp3_gain: 0,
surround_info: 0,
preset_used: 0,
music_length: 0,
music_crc: -1,
header_crc: -1,
}
}
}
impl LAMEHeader {
pub fn new(xing: &XingHeader, data: &[u8]) -> std::result::Result<Self, LAMEError> {
if data.len() < 27 {
return Err(LAMEError::new("Not enough data"));
}
let mut result = LAMEHeader::default();
let mut reader = BitReader::new(data);
let revision = reader.bits(4)?;
if revision != 0 {
return Err(LAMEError::new(&format!(
"unsupported header revision {}",
revision
)));
}
result.vbr_method = reader.bits(4)? as i32;
result.lowpass_filter = (reader.bits(8)? * 100) as i32;
if xing.vbr_scale >= 0 {
let vbr_scale = xing.vbr_scale.clamp(0, 100);
result.quality = (100 - vbr_scale) % 10;
result.vbr_quality = (100 - vbr_scale) / 10;
}
let track_peak_data = reader.bytes(4)?;
if track_peak_data == vec![0, 0, 0, 0] {
result.track_peak = None;
} else {
let peak_value = u32::from_be_bytes([
track_peak_data[0],
track_peak_data[1],
track_peak_data[2],
track_peak_data[3],
]);
result.track_peak = Some(peak_value as f32 / (2.0_f32).powi(23));
}
let track_gain_type = reader.bits(3)? as i32;
result.track_gain_origin = reader.bits(3)? as i32;
let sign = reader.bits(1)? != 0;
let mut gain_adj = reader.bits(9)? as f32 / 10.0;
if sign {
gain_adj *= -1.0;
}
if track_gain_type == 1 {
result.track_gain_adjustment = Some(gain_adj);
} else {
result.track_gain_adjustment = None;
}
if !reader.is_aligned() {
return Err(LAMEError::new("Reader not aligned after track gain"));
}
let album_gain_type = reader.bits(3)? as i32;
result.album_gain_origin = reader.bits(3)? as i32;
let sign = reader.bits(1)? != 0;
let mut album_gain_adj = reader.bits(9)? as f32 / 10.0;
if sign {
album_gain_adj *= -1.0;
}
if album_gain_type == 2 {
result.album_gain_adjustment = Some(album_gain_adj);
} else {
result.album_gain_adjustment = None;
}
result.encoding_flags = reader.bits(4)? as i32;
result.ath_type = reader.bits(4)? as i32;
result.bitrate = reader.bits(8)? as i32;
result.encoder_delay_start = reader.bits(12)? as i32;
result.encoder_padding_end = reader.bits(12)? as i32;
result.source_sample_frequency_enum = reader.bits(2)? as i32;
result.unwise_setting_used = reader.bits(1)? != 0;
result.stereo_mode = reader.bits(3)? as i32;
result.noise_shaping = reader.bits(2)? as i32;
let mp3_gain_raw = reader.bits(8)? as u8;
result.mp3_gain = (mp3_gain_raw as i8) as i32;
reader.skip(2)?; result.surround_info = reader.bits(3)? as i32;
result.preset_used = reader.bits(11)? as i32;
result.music_length = reader.bits(32)?;
result.music_crc = reader.bits(16)? as i32;
result.header_crc = reader.bits(16)? as i32;
if !reader.is_aligned() {
return Err(LAMEError::new("Reader not aligned at end"));
}
Ok(result)
}
pub fn parse_version(input_data: &[u8]) -> Result<((u8, u8), String, bool)> {
if input_data.len() < 20 {
return Err(AudexError::InvalidData("Not a lame header".to_string()));
}
let data = &input_data[..20];
if !data.starts_with(b"LAME") && !data.starts_with(b"L3.99") {
return Err(AudexError::InvalidData("Not a lame header".to_string()));
}
let mut data_stripped = data;
while !data_stripped.is_empty()
&& (data_stripped[0] == b'E'
|| data_stripped[0] == b'M'
|| data_stripped[0] == b'A'
|| data_stripped[0] == b'L')
{
data_stripped = &data_stripped[1..];
}
if data_stripped.is_empty() {
return Err(AudexError::InvalidData("Invalid version".to_string()));
}
let major = data_stripped[0];
data_stripped = &data_stripped[1..];
while !data_stripped.is_empty() && data_stripped[0] == b'.' {
data_stripped = &data_stripped[1..];
}
let mut minor_bytes = Vec::new();
while !data_stripped.is_empty() && data_stripped[0].is_ascii_digit() {
minor_bytes.push(data_stripped[0]);
data_stripped = &data_stripped[1..];
}
let major_int = (major as char)
.to_digit(10)
.ok_or_else(|| AudexError::InvalidData("Invalid major version".to_string()))?
as u8;
let minor_str = std::str::from_utf8(&minor_bytes)
.map_err(|_| AudexError::InvalidData("Invalid minor version".to_string()))?;
let minor_int = minor_str
.parse::<u8>()
.map_err(|_| AudexError::InvalidData("Invalid minor version".to_string()))?;
if (major_int, minor_int) < (3, 90)
|| ((major_int, minor_int) == (3, 90)
&& data_stripped.len() >= 11
&& data_stripped[data_stripped.len() - 11..data_stripped.len() - 10] == [b'('])
{
let flag: Vec<u8> = data_stripped.to_vec();
let flag_trimmed: Vec<u8> = flag
.iter()
.rev()
.skip_while(|&&b| b == 0)
.copied()
.collect::<Vec<u8>>()
.into_iter()
.rev()
.collect();
let flag_final: Vec<u8> = flag_trimmed
.iter()
.rev()
.skip_while(|&&b| b == b' ')
.copied()
.collect::<Vec<u8>>()
.into_iter()
.rev()
.collect();
let flag_string = std::str::from_utf8(&flag_final)
.map(|s| s.to_string())
.unwrap_or_else(|_| " (?)".to_string());
let version_desc = format!("{}.{}{}", major_int, minor_int, flag_string);
return Ok(((major_int, minor_int), version_desc, false));
}
if data_stripped.len() < 11 {
return Err(AudexError::InvalidData(
"Invalid version: too long".to_string(),
));
}
let flag_end = data_stripped.len() - 11;
let flag: Vec<u8> = data_stripped[..flag_end]
.iter()
.rev()
.skip_while(|&&b| b == 0)
.copied()
.collect::<Vec<u8>>()
.into_iter()
.rev()
.collect();
let mut patch = String::new();
let mut flag_string = String::new();
if flag == b"a" {
flag_string = " (alpha)".to_string();
} else if flag == b"b" {
flag_string = " (beta)".to_string();
} else if flag == b"r" {
patch = ".1+".to_string();
} else if flag == b" " {
if (major_int, minor_int) > (3, 96) {
patch = ".0".to_string();
} else {
patch = ".0+".to_string();
}
} else if flag.is_empty() || flag == b"." {
patch = ".0+".to_string();
} else {
flag_string = " (?)".to_string();
}
let version_desc = format!("{}.{}{}{}", major_int, minor_int, patch, flag_string);
Ok(((major_int, minor_int), version_desc, true))
}
pub fn from_bytes(data: &[u8], xing: &XingHeader) -> Result<Self> {
if data.len() < 20 + 27 {
return Err(AudexError::InvalidData("LAME header too short".to_string()));
}
let mut cursor = Cursor::new(&data[20..]);
let revision = cursor.read_u8()? >> 4; if revision != 0 {
return Err(AudexError::InvalidData(format!(
"Unsupported LAME revision {}",
revision
)));
}
cursor.set_position(cursor.position() - 1); let vbr_method = cursor.read_u8()? & 0x0F;
let lowpass_filter = cursor.read_u8()? as u16 * 100;
let (quality, vbr_quality) = if xing.vbr_scale >= 0 {
let vbr_scale = xing.vbr_scale.clamp(0, 100) as u32;
(
((100 - vbr_scale) % 10) as u8,
((100 - vbr_scale) / 10) as u8,
)
} else {
(0, 0)
};
let peak_bytes = [
cursor.read_u8()?,
cursor.read_u8()?,
cursor.read_u8()?,
cursor.read_u8()?,
];
let track_peak = if peak_bytes == [0, 0, 0, 0] {
None
} else {
Some(u32::from_be_bytes(peak_bytes) as f32 / (2_u32.pow(23) as f32))
};
let gain_byte1 = cursor.read_u8()?;
let gain_byte2 = cursor.read_u8()?;
let track_gain_type = (gain_byte1 >> 5) & 0x07;
let track_gain_origin = (gain_byte1 >> 2) & 0x07;
let track_gain_sign = (gain_byte1 >> 1) & 0x01;
let track_gain_value = (((gain_byte1 & 0x01) as u16) << 8) | (gain_byte2 as u16);
let track_gain_adjustment = if track_gain_type == 1 {
let mut gain = track_gain_value as f32 / 10.0;
if track_gain_sign == 1 {
gain = -gain;
}
Some(gain)
} else {
None
};
let gain_byte1 = cursor.read_u8()?;
let gain_byte2 = cursor.read_u8()?;
let album_gain_type = (gain_byte1 >> 5) & 0x07;
let album_gain_origin = (gain_byte1 >> 2) & 0x07;
let album_gain_sign = (gain_byte1 >> 1) & 0x01;
let album_gain_value = (((gain_byte1 & 0x01) as u16) << 8) | (gain_byte2 as u16);
let album_gain_adjustment = if album_gain_type == 2 {
let mut gain = album_gain_value as f32 / 10.0;
if album_gain_sign == 1 {
gain = -gain;
}
Some(gain)
} else {
None
};
let encoding_flags = cursor.read_u8()?;
let ath_type = encoding_flags & 0x0F;
let encoding_flags = encoding_flags >> 4;
let bitrate = cursor.read_u8()?;
let delay_pad_bytes = [cursor.read_u8()?, cursor.read_u8()?, cursor.read_u8()?];
let encoder_delay_start =
((delay_pad_bytes[0] as u16) << 4) | (((delay_pad_bytes[1] >> 4) & 0x0F) as u16);
let encoder_padding_end =
(((delay_pad_bytes[1] & 0x0F) as u16) << 8) | (delay_pad_bytes[2] as u16);
let misc = cursor.read_u8()?;
let source_sample_frequency_enum = (misc >> 6) & 0x03;
let unwise_setting_used = (misc >> 5) & 0x01 != 0;
let stereo_mode = (misc >> 2) & 0x07;
let noise_shaping = misc & 0x03;
let gain_byte = cursor.read_u8()?;
let mp3_gain = gain_byte as i8;
let preset_bytes = [cursor.read_u8()?, cursor.read_u8()?];
let surround_info = (preset_bytes[0] >> 3) & 0x07;
let preset_used = (((preset_bytes[0] & 0x07) as u16) << 8) | (preset_bytes[1] as u16);
let music_length = cursor.read_u32::<BigEndian>()?;
let music_crc = cursor.read_u16::<BigEndian>()?;
let header_crc = cursor.read_u16::<BigEndian>()?;
Ok(LAMEHeader {
vbr_method: vbr_method as i32,
lowpass_filter: lowpass_filter as i32,
track_peak,
track_gain_origin: track_gain_origin as i32,
track_gain_adjustment,
album_gain_origin: album_gain_origin as i32,
album_gain_adjustment,
encoding_flags: encoding_flags as i32,
ath_type: ath_type as i32,
bitrate: bitrate as i32,
encoder_delay_start: encoder_delay_start as i32,
encoder_padding_end: encoder_padding_end as i32,
quality: quality as i32,
vbr_quality: vbr_quality as i32,
source_sample_frequency_enum: source_sample_frequency_enum as i32,
unwise_setting_used,
stereo_mode: stereo_mode as i32,
noise_shaping: noise_shaping as i32,
mp3_gain: mp3_gain as i32,
surround_info: surround_info as i32,
preset_used: preset_used as i32,
music_length,
music_crc: music_crc as i32,
header_crc: header_crc as i32,
})
}
pub fn guess_settings(&self, major: u8, minor: u8) -> String {
let version = (major, minor);
if self.vbr_method == 2 {
if matches!(version, (3, 90) | (3, 91) | (3, 92)) && self.encoding_flags != 0 {
if self.bitrate < 255 {
return format!("--alt-preset {}", self.bitrate);
} else {
return format!("--alt-preset {}+", self.bitrate);
}
}
if self.preset_used != 0 {
return format!("--preset {}", self.preset_used);
} else if self.bitrate < 255 {
return format!("--abr {}", self.bitrate);
} else {
return format!("--abr {}+", self.bitrate);
}
}
if self.vbr_method == 1 {
if self.preset_used == 0 {
if self.bitrate < 255 {
return format!("-b {}", self.bitrate);
} else {
return "-b 255+".to_string();
}
} else if self.preset_used == 1003 {
return "--preset insane".to_string();
}
return format!("-b {}", self.preset_used);
}
if matches!(version, (3, 90) | (3, 91) | (3, 92)) {
let preset_key = (
self.vbr_quality,
self.quality,
self.vbr_method,
self.lowpass_filter,
self.ath_type,
);
match preset_key {
(1, 2, 4, 19500, 3) => return "--preset r3mix".to_string(),
(2, 2, 3, 19000, 4) => return "--alt-preset standard".to_string(),
(2, 2, 3, 19500, 2) => return "--alt-preset extreme".to_string(),
_ => {}
}
match self.vbr_method {
3 => return format!("-V {}", self.vbr_quality),
4 | 5 => return format!("-V {} --vbr-new", self.vbr_quality),
_ => {}
}
} else if matches!(version, (3, 93) | (3, 94) | (3, 95) | (3, 96) | (3, 97)) {
match self.preset_used {
1001 => return "--preset standard".to_string(),
1002 => return "--preset extreme".to_string(),
1004 => return "--preset fast standard".to_string(),
1005 => return "--preset fast extreme".to_string(),
1006 => return "--preset medium".to_string(),
1007 => return "--preset fast medium".to_string(),
_ => {}
}
match self.vbr_method {
3 => return format!("-V {}", self.vbr_quality),
4 | 5 => return format!("-V {} --vbr-new", self.vbr_quality),
8.. if self.vbr_quality >= 0 => return format!("-V {}", self.vbr_quality),
_ => {}
}
} else if version == (3, 98) {
match self.vbr_method {
3 => return format!("-V {} --vbr-old", self.vbr_quality),
4 | 5 => return format!("-V {}", self.vbr_quality),
_ => {}
}
} else if major > 3 || (major == 3 && minor >= 99) {
match self.vbr_method {
3 => return format!("-V {} --vbr-old", self.vbr_quality),
4 | 5 => {
let mut quality = self.vbr_quality;
let adjust_key = (quality, self.bitrate, self.lowpass_filter);
quality = match adjust_key {
(5, 32, 0) => 7,
(5, 8, 0) => 8,
(6, 8, 0) => 9,
_ => quality,
};
return format!("-V {}", quality);
}
_ => {}
}
}
String::new()
}
}
impl XingHeader {
pub fn new(data: &[u8]) -> std::result::Result<Self, XingHeaderError> {
if data.len() < 8 || (!data.starts_with(b"Xing") && !data.starts_with(b"Info")) {
return Err(XingHeaderError::new("Not a Xing header"));
}
let mut result = XingHeader {
is_info: data.starts_with(b"Info"),
..Default::default()
};
let flags = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let mut pos = 8;
if flags & XingHeaderFlags::FRAMES != 0 {
if data.len() < pos + 4 {
return Err(XingHeaderError::new("Xing header truncated"));
}
result.frames = Some(u32::from_be_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
]));
pos += 4;
}
if flags & XingHeaderFlags::BYTES != 0 {
if data.len() < pos + 4 {
return Err(XingHeaderError::new("Xing header truncated"));
}
result.bytes = Some(u32::from_be_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
]));
pos += 4;
}
if flags & XingHeaderFlags::TOC != 0 {
if data.len() < pos + 100 {
return Err(XingHeaderError::new("Xing header truncated"));
}
result.toc = data[pos..pos + 100].iter().map(|&b| b as i32).collect();
pos += 100;
}
if flags & XingHeaderFlags::VBR_SCALE != 0 {
if data.len() < pos + 4 {
return Err(XingHeaderError::new("Xing header truncated"));
}
let raw_scale =
u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
result.vbr_scale = i32::try_from(raw_scale).unwrap_or(i32::MAX);
pos += 4;
}
if data.len() >= pos + 20 {
if let Ok((version, desc, has_extended)) = LAMEHeader::parse_version(&data[pos..]) {
result.lame_version = (version.0 as i32, version.1 as i32);
result.lame_version_desc = desc;
if has_extended {
if let Ok(lame) = LAMEHeader::new(&result, &data[pos + 9..]) {
result.lame_header = Some(lame);
}
}
}
}
Ok(result)
}
pub fn get_encoder_settings(&self) -> String {
if let Some(ref lame_header) = self.lame_header {
let major = self.lame_version.0.clamp(0, 255) as u8;
let minor = self.lame_version.1.clamp(0, 255) as u8;
return lame_header.guess_settings(major, minor);
}
String::new()
}
pub fn get_offset(frame: &MPEGFrame) -> u32 {
debug_assert_eq!(frame.layer, MPEGLayer::Layer3);
match (frame.version, frame.channel_mode) {
(MPEGVersion::MPEG1, ChannelMode::Mono) => 21,
(MPEGVersion::MPEG1, _) => 36,
(MPEGVersion::MPEG2, ChannelMode::Mono) | (MPEGVersion::MPEG25, ChannelMode::Mono) => {
13
}
(MPEGVersion::MPEG2, _) | (MPEGVersion::MPEG25, _) => 21,
}
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 4 {
return Err(AudexError::InvalidData("Xing header too short".to_string()));
}
let header_type = &data[0..4];
let is_info = match header_type {
b"Xing" => false,
b"Info" => true,
_ => {
return Err(AudexError::InvalidData(
"Not a Xing/Info header".to_string(),
));
}
};
if data.len() < 8 {
return Err(AudexError::InvalidData("Xing header truncated".to_string()));
}
let flags = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let mut cursor = Cursor::new(&data[8..]);
let mut xing = Self {
frames: None,
bytes: None,
toc: Vec::new(),
vbr_scale: -1,
is_info,
lame_header: None,
lame_version: (0, 0),
lame_version_desc: String::new(),
};
if flags & XING_FRAMES_FLAG != 0 {
xing.frames = Some(cursor.read_u32::<BigEndian>()?);
}
if flags & XING_BYTES_FLAG != 0 {
xing.bytes = Some(cursor.read_u32::<BigEndian>()?);
}
if flags & XING_TOC_FLAG != 0 {
let mut toc = Vec::new();
for _ in 0..100 {
toc.push(cursor.read_u8()? as i32);
}
xing.toc = toc;
}
if flags & XING_VBR_SCALE_FLAG != 0 {
let raw_scale = cursor.read_u32::<BigEndian>()?;
xing.vbr_scale = i32::try_from(raw_scale).unwrap_or(i32::MAX);
}
let position = 8 + cursor.position() as usize;
if position + 20 <= data.len() {
if let Ok((version, desc, has_extended)) = LAMEHeader::parse_version(&data[position..])
{
xing.lame_version = (version.0 as i32, version.1 as i32);
xing.lame_version_desc = desc;
if has_extended {
if let Ok(lame) = LAMEHeader::from_bytes(&data[position..], &xing) {
xing.lame_header = Some(lame);
}
}
}
}
Ok(xing)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut data = Vec::new();
if self.is_info {
data.extend_from_slice(b"Info");
} else {
data.extend_from_slice(b"Xing");
}
let mut flags = 0u32;
if self.frames.is_some() {
flags |= XING_FRAMES_FLAG;
}
if self.bytes.is_some() {
flags |= XING_BYTES_FLAG;
}
if !self.toc.is_empty() {
flags |= XING_TOC_FLAG;
}
if self.vbr_scale != -1 {
flags |= XING_VBR_SCALE_FLAG;
}
data.extend_from_slice(&flags.to_be_bytes());
if let Some(frames) = self.frames {
data.extend_from_slice(&frames.to_be_bytes());
}
if let Some(bytes) = self.bytes {
data.extend_from_slice(&bytes.to_be_bytes());
}
if !self.toc.is_empty() {
for &entry in &self.toc {
data.push(entry.clamp(0, 255) as u8);
}
}
if self.vbr_scale != -1 {
data.extend_from_slice(&(self.vbr_scale.max(0) as u32).to_be_bytes());
}
data
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VBRIHeader {
pub version: i32,
pub quality: i32,
pub bytes: u32,
pub frames: u32,
pub toc_scale_factor: i32,
pub toc_frames: i32,
pub toc: Vec<i32>,
}
impl VBRIHeader {
pub fn new(data: &[u8]) -> std::result::Result<Self, VBRIHeaderError> {
if data.len() < 26 || !data.starts_with(b"VBRI") {
return Err(VBRIHeaderError::new("Not a VBRI header"));
}
let mut result = VBRIHeader {
version: 0,
quality: 0,
bytes: 0,
frames: 0,
toc_scale_factor: 0,
toc_frames: 0,
toc: Vec::new(),
};
let mut pos = 4;
result.version = u16::from_be_bytes([data[pos], data[pos + 1]]) as i32;
pos += 2;
if result.version != 1 {
return Err(VBRIHeaderError::new(&format!(
"Unsupported header version: {}",
result.version
)));
}
pos += 2; result.quality = u16::from_be_bytes([data[pos], data[pos + 1]]) as i32;
pos += 2;
result.bytes = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
pos += 4;
result.frames =
u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
pos += 4;
let toc_num_entries = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
pos += 2;
result.toc_scale_factor = u16::from_be_bytes([data[pos], data[pos + 1]]) as i32;
pos += 2;
let toc_entry_size = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
pos += 2;
result.toc_frames = u16::from_be_bytes([data[pos], data[pos + 1]]) as i32;
pos += 2;
let toc_size = toc_entry_size
.checked_mul(toc_num_entries)
.ok_or_else(|| VBRIHeaderError::new("VBRI TOC size overflow"))?;
let required = pos
.checked_add(toc_size)
.ok_or_else(|| VBRIHeaderError::new("VBRI TOC size overflow"))?;
if data.len() < required {
return Err(VBRIHeaderError::new("VBRI header truncated"));
}
result.toc = Vec::new();
if toc_entry_size == 1 {
for i in 0..toc_size {
let entry = data[pos + i] as i32;
result.toc.push(entry);
}
} else if toc_entry_size == 2 {
for i in (0..toc_size).step_by(2) {
let entry = u16::from_be_bytes([data[pos + i], data[pos + i + 1]]) as i32;
result.toc.push(entry);
}
} else if toc_entry_size == 4 {
for i in (0..toc_size).step_by(4) {
let raw = u32::from_be_bytes([
data[pos + i],
data[pos + i + 1],
data[pos + i + 2],
data[pos + i + 3],
]);
let entry = i32::try_from(raw).unwrap_or(i32::MAX);
result.toc.push(entry);
}
} else {
return Err(VBRIHeaderError::new("Invalid TOC entry size"));
}
Ok(result)
}
pub fn get_offset(_frame: &MPEGFrame) -> u32 {
debug_assert_eq!(_frame.layer, MPEGLayer::Layer3);
36
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 4 || &data[0..4] != b"VBRI" {
return Err(AudexError::InvalidData("Not a VBRI header".to_string()));
}
if data.len() < 26 {
return Err(AudexError::InvalidData("VBRI header too short".to_string()));
}
let mut cursor = Cursor::new(&data[4..]);
let version = cursor.read_u16::<BigEndian>()?;
let _delay = cursor.read_u16::<BigEndian>()?;
let quality = cursor.read_u16::<BigEndian>()?;
let bytes = cursor.read_u32::<BigEndian>()?;
let frames = cursor.read_u32::<BigEndian>()?;
let toc_entries = cursor.read_u16::<BigEndian>()?;
let toc_scale = cursor.read_u16::<BigEndian>()?;
let toc_entry_size = cursor.read_u16::<BigEndian>()?;
let toc_frames_per_entry = cursor.read_u16::<BigEndian>()?;
let toc_data_needed = (toc_entries as usize).saturating_mul(toc_entry_size as usize);
let remaining = data.len().saturating_sub(26); if toc_data_needed > remaining {
return Err(AudexError::InvalidData(format!(
"VBRI TOC claims {} entries of {} bytes each ({} bytes) \
but only {} bytes remain",
toc_entries, toc_entry_size, toc_data_needed, remaining
)));
}
let mut toc = Vec::with_capacity(toc_entries as usize);
for _ in 0..toc_entries {
let entry = match toc_entry_size {
1 => cursor.read_u8()? as u32,
2 => cursor.read_u16::<BigEndian>()? as u32,
4 => cursor.read_u32::<BigEndian>()?,
_ => {
return Err(AudexError::InvalidData(
"Invalid VBRI TOC entry size".to_string(),
));
}
};
toc.push(entry);
}
Ok(Self {
version: version as i32,
quality: quality as i32,
bytes,
frames,
toc_scale_factor: toc_scale as i32,
toc_frames: toc_frames_per_entry as i32,
toc: toc
.into_iter()
.map(|t| i32::try_from(t).unwrap_or(i32::MAX))
.collect(),
})
}
}
#[derive(Debug, Clone)]
pub struct MPEGFrame {
pub version: MPEGVersion,
pub layer: MPEGLayer,
pub bitrate: u32,
pub sample_rate: u32,
pub channel_mode: ChannelMode,
pub emphasis: Emphasis,
pub protected: bool,
pub padding: bool,
pub private: bool,
pub copyright: bool,
pub original: bool,
pub mode_extension: u8,
pub frame_size: u32,
pub frame_offset: u64,
pub sketchy: bool,
pub bitrate_mode: BitrateMode,
pub encoder_info: Option<String>,
pub encoder_settings: Option<String>,
pub track_gain: Option<f32>,
pub track_peak: Option<f32>,
pub album_gain: Option<f32>,
pub length: Option<Duration>,
}
impl MPEGFrame {
pub fn from_header_at_offset(header: &[u8; 4], offset: u64) -> Result<Self> {
let (
version,
layer,
bitrate,
sample_rate,
channel_mode,
emphasis,
protected,
padding,
private,
copyright,
original,
mode_extension,
) = parse_mpeg_header(header)?;
let frame_size = calculate_frame_size(version, layer, bitrate, sample_rate, padding);
Ok(MPEGFrame {
version,
layer,
bitrate,
sample_rate,
channel_mode,
emphasis,
protected,
padding,
private,
copyright,
original,
mode_extension,
frame_size,
frame_offset: offset,
sketchy: true, bitrate_mode: BitrateMode::Unknown,
encoder_info: None,
encoder_settings: None,
track_gain: None,
track_peak: None,
album_gain: None,
length: None,
})
}
pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let frame_offset = reader.stream_position()?;
let mut header = [0u8; 4];
reader.read_exact(&mut header)?;
let (
version,
layer,
bitrate,
sample_rate,
channel_mode,
emphasis,
protected,
padding,
private,
copyright,
original,
mode_extension,
) = parse_mpeg_header(&header)?;
let frame_size = calculate_frame_size(version, layer, bitrate, sample_rate, padding);
let mut frame = MPEGFrame {
version,
layer,
bitrate,
sample_rate,
channel_mode,
emphasis,
protected,
padding,
private,
copyright,
original,
mode_extension,
frame_size,
frame_offset,
sketchy: true, bitrate_mode: BitrateMode::Unknown,
encoder_info: None,
encoder_settings: None,
track_gain: None,
track_peak: None,
album_gain: None,
length: None,
};
if layer == MPEGLayer::Layer3 {
frame.parse_vbr_header(reader)?;
}
reader.seek(SeekFrom::Start(frame_offset + frame_size as u64))?;
Ok(frame)
}
pub fn samples_per_frame(&self) -> u32 {
match (self.version, self.layer) {
(MPEGVersion::MPEG1, MPEGLayer::Layer1) => 384,
(MPEGVersion::MPEG1, _) => 1152,
(_, MPEGLayer::Layer1) => 384,
(_, MPEGLayer::Layer3) => 576,
(_, _) => 1152, }
}
pub fn guess_bitrate_mode(&self, xing: &XingHeader) -> BitrateMode {
if let Some(lame) = &xing.lame_header {
match lame.vbr_method {
1 | 8 => {
return BitrateMode::CBR;
}
2 | 9 => return BitrateMode::ABR,
3..=6 => return BitrateMode::VBR,
_ => {} }
}
if xing.is_info {
return BitrateMode::CBR;
}
if xing.vbr_scale != -1 || !xing.lame_version_desc.is_empty() {
return BitrateMode::VBR;
}
BitrateMode::Unknown
}
pub fn channels(&self) -> u16 {
match self.channel_mode {
ChannelMode::Mono => 1,
_ => 2,
}
}
pub(crate) fn parse_vbr_header<R: Read + Seek>(&mut self, reader: &mut R) -> Result<()> {
let xing_offset = XingHeader::get_offset(self);
reader.seek(SeekFrom::Start(self.frame_offset + xing_offset as u64))?;
let mut vbr_data = vec![0u8; 512];
if reader.read_exact(&mut vbr_data).is_ok() {
if let Ok(xing) = XingHeader::new(&vbr_data) {
self.sketchy = false;
self.bitrate_mode = if xing.is_info {
BitrateMode::CBR
} else {
BitrateMode::VBR };
if let Some(frames) = xing.frames {
let samples_per_frame = self.samples_per_frame() as i64;
let mut total_samples = frames as i64 * samples_per_frame;
if let Some(bytes) = xing.bytes {
if total_samples > 0 {
let audio_bytes =
(bytes as i64).saturating_sub(self.frame_size as i64).max(0);
let bitrate = (audio_bytes * 8 * self.sample_rate as i64) as f64
/ total_samples as f64;
if bitrate.is_finite() {
self.bitrate = bitrate.round().clamp(0.0, u32::MAX as f64) as u32;
}
}
}
if let Some(lame) = &xing.lame_header {
let delay = lame.encoder_delay_start as i64;
let padding = lame.encoder_padding_end as i64;
total_samples -= delay;
total_samples -= padding;
if total_samples < 0 {
total_samples = 0;
}
}
if total_samples > 0 && self.sample_rate > 0 {
self.length = Some(Duration::from_secs_f64(
total_samples as f64 / self.sample_rate as f64,
));
} else {
self.length = Some(Duration::ZERO);
}
}
if !xing.lame_version_desc.is_empty() {
self.encoder_info = Some(format!("LAME {}", xing.lame_version_desc));
}
if let Some(lame) = &xing.lame_header {
let settings = lame.guess_settings(
xing.lame_version.0.clamp(0, 255) as u8,
xing.lame_version.1.clamp(0, 255) as u8,
);
if !settings.is_empty() {
self.encoder_settings = Some(settings);
}
self.track_gain = lame.track_gain_adjustment;
self.track_peak = lame.track_peak;
self.album_gain = lame.album_gain_adjustment;
}
self.bitrate_mode = self.guess_bitrate_mode(&xing);
return Ok(());
}
}
let vbri_offset = VBRIHeader::get_offset(self);
reader.seek(SeekFrom::Start(self.frame_offset + vbri_offset as u64))?;
if reader.read_exact(&mut vbr_data).is_ok() {
if let Ok(vbri) = VBRIHeader::from_bytes(&vbr_data) {
self.sketchy = false;
self.bitrate_mode = BitrateMode::VBR;
self.encoder_info = Some("FhG".to_string());
if vbri.frames > 0 && self.sample_rate > 0 {
let total_samples = self.samples_per_frame() as f64 * vbri.frames as f64;
let length_secs = total_samples / self.sample_rate as f64;
self.length = Some(Duration::from_secs_f64(length_secs));
if vbri.bytes > 0 && length_secs > 0.0 {
self.bitrate = ((vbri.bytes as f64 * 8.0) / length_secs)
.round()
.clamp(0.0, u32::MAX as f64)
as u32;
}
}
}
}
Ok(())
}
}
pub fn parse_mpeg_header(header: &[u8]) -> Result<MPEGHeaderInfo> {
if header.len() < 4 {
return Err(AudexError::InvalidData("MPEG header too short".to_string()));
}
let sync = ((header[0] as u16) << 3) | ((header[1] as u16) >> 5);
if sync != 0x7FF {
return Err(AudexError::InvalidData("Invalid MPEG sync".to_string()));
}
let version_bits = (header[1] >> 3) & 0x03;
let version = match version_bits {
0b00 => MPEGVersion::MPEG25,
0b01 => return Err(AudexError::InvalidData("Reserved MPEG version".to_string())),
0b10 => MPEGVersion::MPEG2,
0b11 => MPEGVersion::MPEG1,
_ => {
return Err(AudexError::InternalError(
"2-bit mask produced value > 3".to_string(),
));
}
};
let layer_bits = (header[1] >> 1) & 0x03;
let layer = match layer_bits {
0b00 => return Err(AudexError::InvalidData("Reserved layer".to_string())),
0b01 => MPEGLayer::Layer3,
0b10 => MPEGLayer::Layer2,
0b11 => MPEGLayer::Layer1,
_ => {
return Err(AudexError::InternalError(
"2-bit mask produced value > 3".to_string(),
));
}
};
let protected = (header[1] & 0x01) == 0;
let bitrate_index = (header[2] >> 4) & 0x0F;
if bitrate_index == 0x0F || bitrate_index == 0x00 {
return Err(AudexError::InvalidData("Invalid bitrate index".to_string()));
}
let bitrate = get_bitrate(version, layer, bitrate_index)?;
let samplerate_index = (header[2] >> 2) & 0x03;
if samplerate_index == 0x03 {
return Err(AudexError::InvalidData("Reserved sample rate".to_string()));
}
let sample_rate = get_sample_rate(version, samplerate_index)?;
let padding = (header[2] & 0x02) != 0;
let private = (header[2] & 0x01) != 0;
let channel_mode = match (header[3] >> 6) & 0x03 {
0b00 => ChannelMode::Stereo,
0b01 => ChannelMode::JointStereo,
0b10 => ChannelMode::DualChannel,
0b11 => ChannelMode::Mono,
_ => {
return Err(AudexError::InternalError(
"2-bit mask produced value > 3".to_string(),
));
}
};
let mode_extension = (header[3] >> 4) & 0x03;
let copyright = (header[3] & 0x08) != 0;
let original = (header[3] & 0x04) != 0;
let emphasis = match header[3] & 0x03 {
0b00 => Emphasis::None,
0b01 => Emphasis::MS50_15,
0b10 => Emphasis::Reserved,
0b11 => Emphasis::CCITT,
_ => {
return Err(AudexError::InternalError(
"2-bit mask produced value > 3".to_string(),
));
}
};
Ok((
version,
layer,
bitrate,
sample_rate,
channel_mode,
emphasis,
protected,
padding,
private,
copyright,
original,
mode_extension,
))
}
fn get_bitrate(version: MPEGVersion, layer: MPEGLayer, index: u8) -> Result<u32> {
if index == 0 || index == 15 {
return Err(AudexError::InvalidData("Invalid bitrate index".to_string()));
}
let bitrates_kbps: &[u32] = match (version, layer) {
(MPEGVersion::MPEG1, MPEGLayer::Layer1) => &[
0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,
],
(MPEGVersion::MPEG1, MPEGLayer::Layer2) => &[
0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,
],
(MPEGVersion::MPEG1, MPEGLayer::Layer3) => &[
0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
],
(MPEGVersion::MPEG2, MPEGLayer::Layer1) | (MPEGVersion::MPEG25, MPEGLayer::Layer1) => &[
0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,
],
(MPEGVersion::MPEG2, MPEGLayer::Layer2)
| (MPEGVersion::MPEG2, MPEGLayer::Layer3)
| (MPEGVersion::MPEG25, MPEGLayer::Layer2)
| (MPEGVersion::MPEG25, MPEGLayer::Layer3) => {
&[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
}
};
if index as usize >= bitrates_kbps.len() {
return Err(AudexError::InvalidData(
"Bitrate index out of range".to_string(),
));
}
let bitrate_kbps = bitrates_kbps[index as usize];
if bitrate_kbps == 0 {
return Err(AudexError::InvalidData(
"Free format not supported".to_string(),
));
}
Ok(bitrate_kbps * 1000)
}
fn get_sample_rate(version: MPEGVersion, index: u8) -> Result<u32> {
if index == 3 {
return Err(AudexError::InvalidData("Reserved sample rate".to_string()));
}
let rates = match version {
MPEGVersion::MPEG1 => [44100, 48000, 32000],
MPEGVersion::MPEG2 => [22050, 24000, 16000],
MPEGVersion::MPEG25 => [11025, 12000, 8000],
};
rates
.get(index as usize)
.copied()
.ok_or_else(|| AudexError::InvalidData("Sample rate index out of range".to_string()))
}
pub fn iter_sync<R: Read + Seek>(reader: &mut R, max_read: u64) -> Result<Vec<u64>> {
const MAX_SYNC_POSITIONS: usize = 100_000;
let mut syncs = Vec::new();
let mut read = 0u64;
let mut size = 2usize;
let mut last_byte = 0u8;
while read < max_read {
let data_offset = reader.stream_position()?;
let bytes_to_read = std::cmp::min(max_read - read, size as u64) as usize;
let mut new_data = vec![0u8; bytes_to_read];
let bytes_read = reader.read(&mut new_data)?;
if bytes_read == 0 {
break;
}
new_data.truncate(bytes_read);
read += bytes_read as u64;
let is_second = |b: u8| (b & 0xE0) == 0xE0;
if last_byte == 0xFF && !new_data.is_empty() && is_second(new_data[0]) {
syncs.push(data_offset - 1);
if syncs.len() >= MAX_SYNC_POSITIONS {
return Ok(syncs);
}
}
let mut find_offset = 0;
while let Some(index) = new_data[find_offset..].iter().position(|&b| b == 0xFF) {
let abs_index = find_offset + index;
if abs_index < new_data.len() - 1 && is_second(new_data[abs_index + 1]) {
syncs.push(data_offset + abs_index as u64);
if syncs.len() >= MAX_SYNC_POSITIONS {
return Ok(syncs);
}
}
find_offset = abs_index + 1;
}
if !new_data.is_empty() {
last_byte = *new_data.last().expect("checked non-empty above");
}
const MAX_BUF_SIZE: usize = 64 * 1024;
if size < MAX_BUF_SIZE {
size = (size * 2).min(MAX_BUF_SIZE);
}
reader.seek(SeekFrom::Start(data_offset + bytes_read as u64))?;
}
Ok(syncs)
}
const MIN_FRAME_SIZE: u32 = 24;
pub fn calculate_frame_size(
version: MPEGVersion,
layer: MPEGLayer,
bitrate: u32, sample_rate: u32, padding: bool,
) -> u32 {
if sample_rate == 0 {
return MIN_FRAME_SIZE;
}
let samples_per_frame = match (version, layer) {
(MPEGVersion::MPEG1, MPEGLayer::Layer1) => 384,
(MPEGVersion::MPEG1, _) => 1152,
(_, MPEGLayer::Layer1) => 384,
(_, MPEGLayer::Layer3) => 576,
(_, _) => 1152, };
let mut frame_size = if layer == MPEGLayer::Layer1 {
(((samples_per_frame as u64 * bitrate as u64) / 8) / sample_rate as u64) as u32 * 4
} else {
(((samples_per_frame as u64 * bitrate as u64) / 8) / sample_rate as u64) as u32
};
if padding {
frame_size += match layer {
MPEGLayer::Layer1 => 4, _ => 1, };
}
if frame_size < MIN_FRAME_SIZE {
frame_size = MIN_FRAME_SIZE;
}
frame_size
}
pub fn skip_id3<R: Read + Seek>(fileobj: &mut R) -> Result<u64> {
fileobj.seek(SeekFrom::Start(0))?;
let file_size = fileobj.seek(SeekFrom::End(0))?;
fileobj.seek(SeekFrom::Start(0))?;
const MAX_ID3_SKIP_ITERATIONS: usize = 1000;
let mut id3_iterations = 0usize;
loop {
id3_iterations += 1;
if id3_iterations > MAX_ID3_SKIP_ITERATIONS {
break;
}
let mut id3_header = [0u8; 10];
let bytes_read = fileobj.read(&mut id3_header)?;
if bytes_read < 10 {
fileobj.seek(SeekFrom::Current(-(bytes_read as i64)))?;
break;
}
if &id3_header[0..3] == b"ID3" {
let tag_size =
crate::id3::util::decode_synchsafe_int_checked(&id3_header[6..10])? as u64;
let current_pos = fileobj.stream_position()?;
if tag_size > 0 && current_pos + tag_size <= file_size {
let seek_offset = i64::try_from(tag_size).map_err(|_| {
AudexError::ParseError(format!(
"ID3 tag size {} exceeds maximum seekable offset",
tag_size
))
})?;
fileobj.seek(SeekFrom::Current(seek_offset))?;
continue;
}
break;
}
fileobj.seek(SeekFrom::Current(-(bytes_read as i64)))?;
break;
}
Ok(fileobj.stream_position()?)
}