///|
fn encmeta_integer(
out : Array[Byte],
value : UInt64,
width : Int32,
little~ : Bool = false,
) -> Unit {
for i = 0; i < width; i = i + 1 {
let shift = if little { i * 8 } else { (width - i - 1) * 8 }
out.push((value >> shift).to_byte())
}
}
///|
fn encmeta_comment(
comment : VorbisComment,
limit : Int32,
) -> Bytes raise FlacError {
let vendor = @utf8.encode(comment.vendor)
let mut size = 8L + vendor.length().to_int64()
for entry in comment.entries {
size += 4L + @utf8.encode(entry).length().to_int64()
if size > 16777215L || size > limit.to_int64() {
raise Limit("encoded Vorbis comment exceeds metadata byte limit")
}
}
if size > 16777215L || size > limit.to_int64() {
raise Limit("encoded Vorbis comment exceeds metadata byte limit")
}
let out : Array[Byte] = []
encmeta_integer(out, vendor.length().to_uint64(), 4, little=true)
for byte in vendor {
out.push(byte)
}
encmeta_integer(out, comment.entries.length().to_uint64(), 4, little=true)
for entry in comment.entries {
let bytes = @utf8.encode(entry)
encmeta_integer(out, bytes.length().to_uint64(), 4, little=true)
for byte in bytes {
out.push(byte)
}
}
Bytes::from_array(out)
}
///|
fn encmeta_serialize(
streaminfo : Bytes,
blocks : Array[MetadataBlock],
limit : Int32,
) -> Bytes raise FlacError {
let mut size = 42L
for block in blocks {
if block.kind < 1 || block.kind > 126 {
raise Invalid(
size, "encoder metadata excludes STREAMINFO and forbidden block types",
)
}
if block.data.length() > 16777215 {
raise Limit("metadata block exceeds 24-bit length")
}
size += 4L + block.data.length().to_int64()
if size > limit.to_int64() {
raise Limit("encoder metadata byte limit")
}
}
if size > limit.to_int64() {
raise Limit("encoder metadata byte limit")
}
let out : Array[Byte] = [
102,
76,
97,
67,
if blocks.is_empty() {
128
} else {
0
},
0,
0,
34,
]
for byte in streaminfo {
out.push(byte)
}
for i = 0; i < blocks.length(); i = i + 1 {
let block = blocks[i]
out.push(
(block.kind | (if i == blocks.length() - 1 { 128 } else { 0 })).to_byte(),
)
encmeta_integer(out, block.data.length().to_uint64(), 3)
for byte in block.data {
out.push(byte)
}
}
Bytes::from_array(out)
}
///|
/// Build a native header with encoder-owned STREAMINFO and caller metadata.
/// Zero statistics represent an unfinished stream. Finalization can replace
/// the first 42 bytes while retaining metadata and the first-frame offset.
fn encoder_header(
config : EncoderConfig,
total_samples : Int64,
md5 : Bytes,
min_frame_size : Int32,
max_frame_size : Int32,
) -> Bytes raise FlacError {
let format = config.format
if format.sample_rate < 0 ||
format.sample_rate > 1048575 ||
(format.sample_rate == 0 && !config.non_audio) ||
format.channels < 1 ||
format.channels > 8 ||
format.bits_per_sample < 4 ||
format.bits_per_sample > 32 ||
config.block_size < 16 ||
config.block_size > 65535 {
raise Invalid(0L, "invalid encoder stream format")
}
if total_samples < 0L || total_samples > 0xfffffffffL {
raise Invalid(0L, "encoder sample count exceeds 36-bit range")
}
if md5.length() != 16 {
raise Invalid(0L, "STREAMINFO MD5 must contain 16 bytes")
}
if min_frame_size < 0 ||
max_frame_size < 0 ||
min_frame_size > 16777215 ||
max_frame_size > 16777215 ||
(max_frame_size != 0 && min_frame_size > max_frame_size) {
raise Invalid(0L, "invalid encoder frame size statistics")
}
let streaminfo : Array[Byte] = []
encmeta_integer(
streaminfo,
(if config.variable_block { 16 } else { config.block_size }).to_uint64(),
2,
)
encmeta_integer(streaminfo, config.block_size.to_uint64(), 2)
encmeta_integer(streaminfo, min_frame_size.to_uint64(), 3)
encmeta_integer(streaminfo, max_frame_size.to_uint64(), 3)
let packed = (format.sample_rate.to_uint64() << 44) |
((format.channels - 1).to_uint64() << 41) |
((format.bits_per_sample - 1).to_uint64() << 36) |
total_samples.reinterpret_as_uint64()
encmeta_integer(streaminfo, packed, 8)
for byte in md5 {
streaminfo.push(byte)
}
let info_bytes = Bytes::from_array(streaminfo)
let header = encmeta_serialize(
info_bytes,
config.metadata,
config.max_metadata_bytes,
)
let (parsed, _) = parse_metadata(
header,
max_metadata_bytes=config.max_metadata_bytes,
)
let existing_mask = parsed.channel_mask()
match config.channel_mask {
None => header
Some(mask) =>
match existing_mask {
Some(existing) => {
if existing != mask {
raise Invalid(
0L, "encoder channel mask conflicts with supplied Vorbis comment",
)
}
header
}
None => {
let entries = match parsed.comments {
Some(comment) => comment.entries.map(entry => entry)
None => []
}
entries.push(
"WAVEFORMATEXTENSIBLE_CHANNEL_MASK=0x" + mask.to_string(radix=16),
)
let vendor = match parsed.comments {
Some(comment) => comment.vendor
None => "MoonFLAC"
}
let comment = encmeta_comment(
{ vendor, entries, },
config.max_metadata_bytes,
)
let blocks = config.metadata.map(block => {
if block.kind == 4 {
MetadataBlock::{ kind: 4, data: comment, }
} else {
block
}
})
if parsed.comments is None {
blocks.push({ kind: 4, data: comment, })
}
let result = encmeta_serialize(
info_bytes,
blocks,
config.max_metadata_bytes,
)
let _ = parse_metadata(
result,
max_metadata_bytes=config.max_metadata_bytes,
)
result
}
}
}
}