use std::ascii;
use symphonia_core::audio::Channels;
use symphonia_core::errors::{decode_error, Result};
use symphonia_core::formats::{util::SeekIndex, Cue, CuePoint};
use symphonia_core::io::*;
use symphonia_core::meta::{StandardTagKey, Tag, Value, VendorData};
#[derive(PartialEq, Eq)]
pub enum MetadataBlockType {
StreamInfo,
Padding,
Application,
SeekTable,
VorbisComment,
Cuesheet,
Picture,
Unknown(u8),
}
fn flac_channels_to_channels(channels: u32) -> Channels {
debug_assert!(channels > 0 && channels < 9);
match channels {
1 => Channels::FRONT_LEFT,
2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT,
3 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT | Channels::FRONT_CENTRE,
4 => {
Channels::FRONT_LEFT
| Channels::FRONT_RIGHT
| Channels::REAR_LEFT
| Channels::REAR_RIGHT
}
5 => {
Channels::FRONT_LEFT
| Channels::FRONT_RIGHT
| Channels::FRONT_CENTRE
| Channels::REAR_LEFT
| Channels::REAR_RIGHT
}
6 => {
Channels::FRONT_LEFT
| Channels::FRONT_RIGHT
| Channels::FRONT_CENTRE
| Channels::LFE1
| Channels::REAR_LEFT
| Channels::REAR_RIGHT
}
7 => {
Channels::FRONT_LEFT
| Channels::FRONT_RIGHT
| Channels::FRONT_CENTRE
| Channels::LFE1
| Channels::REAR_CENTRE
| Channels::SIDE_LEFT
| Channels::SIDE_RIGHT
}
8 => {
Channels::FRONT_LEFT
| Channels::FRONT_RIGHT
| Channels::FRONT_CENTRE
| Channels::LFE1
| Channels::REAR_LEFT
| Channels::REAR_RIGHT
| Channels::SIDE_LEFT
| Channels::SIDE_RIGHT
}
_ => unreachable!(),
}
}
#[derive(Debug, Default)]
pub struct StreamInfo {
pub block_len_min: u16,
pub block_len_max: u16,
pub frame_byte_len_min: u32,
pub frame_byte_len_max: u32,
pub sample_rate: u32,
pub channels: Channels,
pub bits_per_sample: u32,
pub n_samples: Option<u64>,
pub md5: Option<[u8; 16]>,
}
impl StreamInfo {
pub fn read<B: ReadBytes>(reader: &mut B) -> Result<StreamInfo> {
let mut info = StreamInfo {
block_len_min: 0,
block_len_max: 0,
frame_byte_len_min: 0,
frame_byte_len_max: 0,
sample_rate: 0,
channels: Channels::empty(),
bits_per_sample: 0,
n_samples: None,
md5: None,
};
info.block_len_min = reader.read_be_u16()?;
info.block_len_max = reader.read_be_u16()?;
if info.block_len_min < 16 || info.block_len_max < 16 {
return decode_error("flac: minimum block length is 16 samples");
}
if info.block_len_max < info.block_len_min {
return decode_error(
"flac: maximum block length is less than the minimum block length",
);
}
info.frame_byte_len_min = reader.read_be_u24()?;
info.frame_byte_len_max = reader.read_be_u24()?;
if info.frame_byte_len_min > 0
&& info.frame_byte_len_max > 0
&& info.frame_byte_len_max < info.frame_byte_len_min
{
return decode_error(
"flac: maximum frame length is less than the minimum frame length",
);
}
let mut br = BitStreamLtr::new(reader);
info.sample_rate = br.read_bits_leq32(20)?;
if info.sample_rate < 1 || info.sample_rate > 655_350 {
return decode_error("flac: stream sample rate out of bounds");
}
let channels_enc = br.read_bits_leq32(3)? + 1;
if channels_enc < 1 || channels_enc > 8 {
return decode_error("flac: stream channels are out of bounds");
}
info.channels = flac_channels_to_channels(channels_enc);
info.bits_per_sample = br.read_bits_leq32(5)? + 1;
if info.bits_per_sample < 4 || info.bits_per_sample > 32 {
return decode_error("flac: stream bits per sample are out of bounds");
}
info.n_samples = match br.read_bits_leq64(36)? {
0 => None,
samples => Some(samples),
};
let mut md5 = [0; 16];
reader.read_buf_exact(&mut md5)?;
if md5 != [0; 16] {
info.md5 = Some(md5);
}
Ok(info)
}
pub fn is_valid_size(size: u64) -> bool {
const STREAM_INFO_BLOCK_SIZE: u64 = 34;
size == STREAM_INFO_BLOCK_SIZE
}
}
pub fn read_seek_table_block<B: ReadBytes>(
reader: &mut B,
block_length: u32,
table: &mut SeekIndex,
) -> Result<()> {
let count = block_length / 18;
for _ in 0..count {
let sample = reader.read_be_u64()?;
if sample != 0xffff_ffff_ffff_ffff {
table.insert(sample, reader.read_be_u64()?, u32::from(reader.read_be_u16()?));
}
else {
reader.ignore_bytes(10)?;
}
}
Ok(())
}
fn printable_ascii_to_string(bytes: &[u8]) -> Option<String> {
let mut result = String::with_capacity(bytes.len());
for c in bytes {
match c {
0x00 => break,
0x20..=0x7e => result.push(char::from(*c)),
_ => return None,
}
}
Some(result)
}
pub fn read_cuesheet_block<B: ReadBytes>(reader: &mut B, cues: &mut Vec<Cue>) -> Result<()> {
let mut catalog_number_buf = vec![0u8; 128];
reader.read_buf_exact(&mut catalog_number_buf)?;
let _catalog_number = match printable_ascii_to_string(&catalog_number_buf) {
Some(s) => s,
None => return decode_error("flac: cuesheet catalog number contains invalid characters"),
};
let n_lead_in_samples = reader.read_be_u64()?;
let is_cdda = (reader.read_u8()? & 0x80) == 0x80;
if !is_cdda && n_lead_in_samples > 0 {
return decode_error("flac: cuesheet lead-in samples should be zero if not CD-DA");
}
for _ in 0..129 {
if reader.read_be_u16()? != 0 {
return decode_error("flac: cuesheet reserved bits should be zero");
}
}
let n_tracks = reader.read_u8()?;
if n_tracks == 0 {
return decode_error("flac: cuesheet must have at-least one track");
}
if is_cdda && n_tracks > 100 {
return decode_error("flac: cuesheets for CD-DA must not have more than 100 tracks");
}
for _ in 0..n_tracks {
read_cuesheet_track(reader, is_cdda, cues)?;
}
Ok(())
}
fn read_cuesheet_track<B: ReadBytes>(
reader: &mut B,
is_cdda: bool,
cues: &mut Vec<Cue>,
) -> Result<()> {
let n_offset_samples = reader.read_be_u64()?;
if is_cdda && n_offset_samples % 588 != 0 {
return decode_error(
"flac: cuesheet track sample offset is not a multiple of 588 for CD-DA",
);
}
let number = u32::from(reader.read_u8()?);
if number == 0 {
return decode_error("flac: cuesheet track number of 0 not allowed");
}
if is_cdda && number > 99 && number != 170 {
return decode_error(
"flac: cuesheet track numbers greater than 99 are not allowed for CD-DA",
);
}
let mut isrc_buf = vec![0u8; 12];
reader.read_buf_exact(&mut isrc_buf)?;
let isrc = match printable_ascii_to_string(&isrc_buf) {
Some(s) => s,
None => return decode_error("flac: cuesheet track ISRC contains invalid characters"),
};
let flags = reader.read_be_u16()?;
let _is_audio = (flags & 0x8000) == 0x0000;
let _use_pre_emphasis = (flags & 0x4000) == 0x4000;
if flags & 0x3fff != 0 {
return decode_error("flac: cuesheet track reserved bits should be zero");
}
for _ in 0..3 {
if reader.read_be_u32()? != 0 {
return decode_error("flac: cuesheet track reserved bits should be zero");
}
}
let n_indicies = reader.read_u8()? as usize;
if is_cdda && n_indicies > 100 {
return decode_error("flac: cuesheet track indicies cannot exceed 100 for CD-DA");
}
let mut cue =
Cue { index: number, start_ts: n_offset_samples, tags: Vec::new(), points: Vec::new() };
cue.tags.push(Tag::new(Some(StandardTagKey::IdentIsrc), "ISRC", Value::from(isrc)));
for _ in 0..n_indicies {
cue.points.push(read_cuesheet_track_index(reader, is_cdda)?);
}
cues.push(cue);
Ok(())
}
fn read_cuesheet_track_index<B: ReadBytes>(reader: &mut B, is_cdda: bool) -> Result<CuePoint> {
let n_offset_samples = reader.read_be_u64()?;
let idx_point_enc = reader.read_be_u32()?;
if is_cdda && n_offset_samples % 588 != 0 {
return decode_error(
"flac: cuesheet track index point sample offset is not a multiple of 588 for CD-DA",
);
}
if idx_point_enc & 0x00ff_ffff != 0 {
return decode_error("flac: cuesheet track index reserved bits should be 0");
}
let _idx_point = ((idx_point_enc & 0xff00_0000) >> 24) as u8;
Ok(CuePoint { start_offset_ts: n_offset_samples, tags: Vec::new() })
}
pub fn read_application_block<B: ReadBytes>(
reader: &mut B,
block_length: u32,
) -> Result<VendorData> {
let ident_buf = reader.read_quad_bytes()?;
let ident = String::from_utf8(
ident_buf.as_ref().iter().copied().flat_map(ascii::escape_default).collect(),
)
.unwrap();
let data = reader.read_boxed_slice_exact(block_length as usize - 4)?;
Ok(VendorData { ident, data })
}
pub use symphonia_metadata::flac::read_comment_block;
pub use symphonia_metadata::flac::read_picture_block;
pub struct MetadataBlockHeader {
pub is_last: bool,
pub block_type: MetadataBlockType,
pub block_len: u32,
}
impl MetadataBlockHeader {
pub fn read<B: ReadBytes>(reader: &mut B) -> Result<MetadataBlockHeader> {
let header_enc = reader.read_u8()?;
let is_last = (header_enc & 0x80) == 0x80;
let block_type_id = header_enc & 0x7f;
let block_type = match block_type_id {
0 => MetadataBlockType::StreamInfo,
1 => MetadataBlockType::Padding,
2 => MetadataBlockType::Application,
3 => MetadataBlockType::SeekTable,
4 => MetadataBlockType::VorbisComment,
5 => MetadataBlockType::Cuesheet,
6 => MetadataBlockType::Picture,
_ => MetadataBlockType::Unknown(block_type_id),
};
let block_len = reader.read_be_u24()?;
Ok(MetadataBlockHeader { is_last, block_type, block_len })
}
}