///|
fn stream_info(data : Bytes) -> StreamInfo raise FlacError {
guard data.length() == 34 else {
raise Invalid(0, "STREAMINFO must be 34 bytes")
}
let min_block = (data[0].to_int() << 8) | data[1].to_int()
let max_block = (data[2].to_int() << 8) | data[3].to_int()
guard min_block >= 16 && max_block >= min_block else {
raise Invalid(0, "invalid STREAMINFO block sizes")
}
let sample_rate = (data[10].to_int() << 12) |
(data[11].to_int() << 4) |
(data[12].to_int() >> 4)
let channels = ((data[12].to_int() >> 1) & 7) + 1
let bits_per_sample = (
((data[12].to_int() & 1) << 4) | (data[13].to_int() >> 4)
) +
1
guard bits_per_sample >= 4 else { raise Invalid(13, "invalid bit depth") }
let mut total = (data[13].to_int() & 15).to_int64()
for i in 14..<18 {
total = (total << 8) | data[i].to_int().to_int64()
}
{
sample_rate,
channels,
bits_per_sample,
total_samples: total,
md5: data[18:34].to_owned(),
}
}
///|
/// Parse native FLAC metadata. Audio frames are opaque and not validated.
pub fn parse(
data : Bytes,
max_metadata? : Int = MaxMetadata,
) -> Document raise FlacError {
guard max_metadata >= 42 && max_metadata <= MaxMetadata else {
raise Limit("max_metadata must be 42..16777216")
}
guard data.has_prefix(b"fLaC") else {
raise Invalid(0, "missing native FLAC signature")
}
let r = { data, pos: 4, }
let blocks : Array[Block] = []
let mut comments_seen = false
let mut seektable_seen = false
let mut cuesheet_seen = false
let mut icon1 = false
let mut icon2 = false
for ;; {
guard blocks.length() < MaxBlocks else {
raise Limit("too many metadata blocks")
}
let header = r.take(4)
let kind = header[0].to_int() & 127
let last = (header[0].to_int() & 128) != 0
let size = (header[1].to_int() << 16) |
(header[2].to_int() << 8) |
header[3].to_int()
guard r.pos <= max_metadata && size <= max_metadata - r.pos else {
raise Limit("metadata budget exceeded")
}
guard kind != 127 else {
raise Invalid(r.pos - 4, "forbidden block type 127")
}
guard !blocks.is_empty() || kind == 0 else {
raise Invalid(4, "first block must be STREAMINFO")
}
let payload = r.take(size)
match kind {
0 => {
guard blocks.is_empty() else {
raise Invalid(r.pos, "duplicate STREAMINFO")
}
ignore(stream_info(payload))
}
1 =>
for byte in payload {
guard byte == b'\x00' else { raise Invalid(r.pos, "nonzero padding") }
}
2 => {
guard size >= 4 else {
raise Invalid(r.pos, "APPLICATION requires identifier")
}
}
3 => {
guard !seektable_seen else {
raise Invalid(r.pos, "duplicate SEEKTABLE")
}
seektable_seen = true
guard size % 18 == 0 else {
raise Invalid(r.pos, "SEEKTABLE size must be multiple of 18")
}
validate_seektable(payload)
}
4 => {
guard !comments_seen else {
raise Invalid(r.pos, "duplicate VORBIS_COMMENT")
}
comments_seen = true
ignore(decode_comments(payload))
}
6 => {
let picture = decode_picture(payload)
if picture.kind == 1 {
guard !icon1 else { raise Invalid(r.pos, "duplicate type 1 icon") }
icon1 = true
}
if picture.kind == 2 {
guard !icon2 else { raise Invalid(r.pos, "duplicate type 2 icon") }
icon2 = true
}
}
5 => {
guard !cuesheet_seen else { raise Invalid(r.pos, "duplicate CUESHEET") }
cuesheet_seen = true
}
_ => ()
}
blocks.push({ kind, data: payload, })
if last {
break
}
}
let offset = r.pos
let audio = r.take(data.length() - r.pos)
{ blocks, audio, offset, }
}
///|
pub fn Document::stream_info(self : Document) -> StreamInfo raise FlacError {
stream_info(self.blocks[0].data)
}
///|
pub fn Document::blocks(self : Document) -> Array[Block] {
self.blocks.copy()
}
///|
pub fn Document::audio_bytes(self : Document) -> Bytes {
self.audio
}
///|
pub fn Document::audio_offset(self : Document) -> Int {
self.offset
}
///|
pub fn Document::comments(self : Document) -> Comments raise FlacError {
for block in self.blocks {
if block.kind == 4 {
return decode_comments(block.data)
}
}
{ vendor: "MoonFlacTag/0.4.0", entries: [], }
}
///|
pub fn Document::pictures(self : Document) -> Array[Picture] raise FlacError {
let result = []
for block in self.blocks {
if block.kind == 6 {
result.push(decode_picture(block.data))
}
}
result
}