// flac_meta.mbt
//
// FLAC 元数据块链的解析与重写(F4 的基础)。
//
// 结构:`fLaC` 魔数 + 一个或多个元数据块 + 音频帧。
// 每个块头 4 字节:1 位"是否最后一个块" + 7 位块类型 + 24 位大端长度。
//
// 本文件只负责"块链"这一层;VORBIS_COMMENT 的载荷语义在 vorbis_comment.mbt。
///|
/// FLAC 元数据块类型码。
pub enum FlacBlockType {
StreamInfo
Padding
Application
SeekTable
VorbisComment
CueSheet
Picture
/// 4..126 为保留区,原样保留。
Reserved
/// 127 为非法值。
Invalid
}
///|
/// 元数据块类型码 → 枚举。
pub fn FlacBlockType::from_code(code : Int32) -> FlacBlockType {
match code {
0 => StreamInfo
1 => Padding
2 => Application
3 => SeekTable
4 => VorbisComment
5 => CueSheet
6 => Picture
127 => Invalid
_ => Reserved
}
}
///|
/// 一个元数据块(位置 + 类型,载荷按需切片)。
pub struct FlacBlock {
/// 块类型码(原始值)。
code : Int32
/// 相对文件起始的载荷偏移。
offset : Int32
/// 载荷长度。
length : Int32
/// 是否为最后一个元数据块。
is_last : Bool
}
///|
/// 元数据块链解析结果。
pub struct FlacMeta {
/// 全部元数据块(含 STREAMINFO,按文件顺序)。
blocks : Array[FlacBlock]
/// 第一个音频帧的偏移。
audio_offset : Int32
}
///|
/// 解析 FLAC 元数据块链。
///
/// 注意 7..126 是**保留**类型而非非法类型:RFC 9639 只把 127 定为 forbidden,
/// 保留类型应当跳过而不是报错(xiph 官方测试集里确有此类文件)。
pub fn parse_meta(data : BytesView) -> FlacMeta raise TagError {
if data.length() < 4 {
raise Truncated(offset=0, need=4, actual=data.length())
}
if data.at(0) != b'f' ||
data.at(1) != b'L' ||
data.at(2) != b'a' ||
data.at(3) != b'C' {
raise NotTagged(format="flac")
}
let blocks : Array[FlacBlock] = []
let mut pos = 4
let mut last = false
while !last {
require_range(data, pos, 4)
let first = data.at(pos).to_int()
last = (first & 0x80) != 0
let code = first & 0x7F
if code == 127 {
raise BadHeader(reason="FLAC 元数据块类型 127 为禁用值")
}
let len = read_be(data, pos + 1, 3)
let payload_off = pos + 4
require_range(data, payload_off, len)
blocks.push({ code, offset: payload_off, length: len, is_last: last, })
pos = payload_off + len
}
{ blocks, audio_offset: pos, }
}
///|
/// 取第一个指定类型的块。
pub fn FlacMeta::block(self : FlacMeta, type_ : FlacBlockType) -> FlacBlock? {
let code = type_.code()
for b in self.blocks {
if b.code == code {
return Some(b)
}
}
None
}
///|
/// 块类型枚举 → 类型码。
pub fn FlacBlockType::code(self : FlacBlockType) -> Int32 {
match self {
StreamInfo => 0
Padding => 1
Application => 2
SeekTable => 3
VorbisComment => 4
CueSheet => 5
Picture => 6
Reserved => 7
Invalid => 127
}
}