203 lines
6.7 KiB
V
203 lines
6.7 KiB
V
module metadata
|
||
|
||
import encoding.iconv
|
||
|
||
@[heap]
|
||
pub struct Frame {
|
||
pub mut:
|
||
data string
|
||
mut:
|
||
prev_size u64
|
||
cur_size u64
|
||
start u64
|
||
end u64
|
||
encoding_name string
|
||
name string
|
||
encoding_name_idx u8
|
||
addition []u8
|
||
encoded_data []u8
|
||
apic ?[]u8
|
||
encoded_size []u8
|
||
modified bool
|
||
// Фрейм целиком в том виде, в каком он был прочитан из файла
|
||
raw []u8
|
||
}
|
||
|
||
// Во фрейме первый байт указывает кодировку 0 - ISO-8859-1 (Latin-1), 1 - UTF-16LE
|
||
@[inline]
|
||
fn (mut frame Frame) fill(bytes []u8, start_position u64, idv3_version u8) {
|
||
// Начало фрейма
|
||
frame.start = start_position
|
||
// Название фрейма
|
||
frame.name = bytes[frame.start..frame.start + 4].bytestr()
|
||
|
||
// Размер фрейма
|
||
frame.cur_size = if idv3_version == 3 {
|
||
get_size_v23(bytes[frame.start + 4..frame.start + 8])
|
||
} else {
|
||
get_size_v24(bytes[frame.start + 4..frame.start + 8])
|
||
}
|
||
// Конец фрейма
|
||
frame.end = frame.start + frame.cur_size + 10
|
||
|
||
frame.raw = bytes[frame.start..frame.end]
|
||
frame.modified = false
|
||
|
||
// Содержимое фрейма
|
||
frame.encoding_name = match bytes[frame.start + 10] {
|
||
0 { 'ISO-8859-1' }
|
||
1 { 'UTF-16LE' }
|
||
2 { 'UTF-16BE' }
|
||
else { 'UTF-8' }
|
||
}
|
||
|
||
frame.encoding_name_idx = u8(bytes[frame.start + 10])
|
||
|
||
if frame.name.to_lower() == 'apic' {
|
||
// Дополнительные байты с информацией (MIME)
|
||
frame.addition = bytes[frame.start + 10..frame.start + 24]
|
||
// Фрейм целиком вместе с заголовком, чтобы записать его обратно без изменений
|
||
frame.apic = bytes[frame.start..frame.end]
|
||
} else {
|
||
// Данные начинаются после 10 байт заголовка и байта кодировки
|
||
mut data_start := frame.start + 11
|
||
|
||
// UTF-16 с BOM (01): 2 байта маркера, по ним определяется порядок байт
|
||
if frame.encoding_name_idx == 1 && data_start + 2 <= frame.end {
|
||
if bytes[data_start] == 0xFE && bytes[data_start + 1] == 0xFF {
|
||
frame.encoding_name = 'UTF-16BE'
|
||
}
|
||
data_start += 2
|
||
}
|
||
|
||
// Замыкающие нули не относятся к данным
|
||
frame.data = (iconv.encoding_to_vstring(bytes[data_start..frame.end], frame.encoding_name) or { '' }).all_before('\x00')
|
||
|
||
frame.addition = bytes[frame.start..frame.start + 10]
|
||
}
|
||
}
|
||
|
||
pub fn (mut frame Frame) init(tag_name string, idv3_version u8) {
|
||
frame.name = tag_name.to_upper()
|
||
frame.set_unicode_encoding(idv3_version)
|
||
}
|
||
|
||
// Кодировка, в которой представим любой текст
|
||
fn (mut frame Frame) set_unicode_encoding(idv3_version u8) {
|
||
// В v2.3 нет UTF-8, там Юникод только UTF-16 с BOM
|
||
if idv3_version == 3 {
|
||
frame.encoding_name = 'UTF-16LE'
|
||
frame.encoding_name_idx = 1
|
||
} else {
|
||
frame.encoding_name = 'UTF-8'
|
||
frame.encoding_name_idx = 3
|
||
}
|
||
}
|
||
|
||
// Кодируем длину фрейма с нужным сдвигом
|
||
@[inline]
|
||
pub fn (mut frame Frame) encode_size(idv3_version u8) {
|
||
stride := if idv3_version == 3 {
|
||
[u64(24), u64(16), u64(8), u64(0xFF)]
|
||
} else {
|
||
[u64(21), u64(14), u64(7), u64(0x7F)]
|
||
}
|
||
|
||
mut encoded_size := []u8{len: 4}
|
||
encoded_size[0] = u8((frame.cur_size >> stride[0]) & stride[3])
|
||
encoded_size[1] = u8((frame.cur_size >> stride[1]) & stride[3])
|
||
encoded_size[2] = u8((frame.cur_size >> stride[2]) & stride[3])
|
||
encoded_size[3] = u8(frame.cur_size & stride[3])
|
||
|
||
frame.encoded_size = encoded_size
|
||
}
|
||
|
||
// Кодируем кадр для дальнейшей записи
|
||
pub fn (mut frame Frame) encode_frame(idv3_version u8) {
|
||
// В ISO-8859-1 есть только символы до U+00FF, остальные iconv молча заменяет на '?'
|
||
if frame.encoding_name_idx == 0 && frame.data.runes().any(it > 0xFF) {
|
||
frame.set_unicode_encoding(idv3_version)
|
||
}
|
||
|
||
encoded_data := iconv.vstring_to_encoding(frame.data, frame.encoding_name) or {
|
||
// Не удалось закодировать в исходной кодировке фрейма, переходим на Юникод
|
||
frame.set_unicode_encoding(idv3_version)
|
||
iconv.vstring_to_encoding(frame.data, frame.encoding_name) or {
|
||
panic('не удалось закодировать фрейм ${frame.name}: ${err}')
|
||
}
|
||
}
|
||
|
||
// Для UTF-16 с BOM (01) перед текстом нужен маркер порядка байт
|
||
frame.encoded_data = if frame.encoding_name_idx == 1 {
|
||
mut bom := if frame.encoding_name == 'UTF-16BE' {
|
||
[u8(0xFE), 0xFF]
|
||
} else {
|
||
[u8(0xFF), 0xFE]
|
||
}
|
||
bom << encoded_data
|
||
bom
|
||
} else {
|
||
encoded_data
|
||
}
|
||
|
||
// Размер тела фрейма: байт кодировки + данные + замыкающие нули
|
||
frame.cur_size = u64(1 + frame.encoded_data.len + frame.terminator_len())
|
||
|
||
// число -> [байт, байт, байт, байт]
|
||
frame.encode_size(idv3_version)
|
||
}
|
||
|
||
// Для UTF-16 строка завершается двумя нулями, для остальных кодировок одним
|
||
@[inline]
|
||
fn (frame Frame) terminator_len() int {
|
||
return if frame.encoding_name_idx == 1 || frame.encoding_name_idx == 2 { 2 } else { 1 }
|
||
}
|
||
|
||
// Получить закодированый тэг
|
||
pub fn (mut frame Frame) get_encoded_frame_tag(idv3_version u8) []u8 {
|
||
frame.encode_frame(idv3_version)
|
||
|
||
// Название тэга
|
||
mut encoded_tag := frame.name.bytes()
|
||
|
||
// Четыре числа на закодированный размер
|
||
encoded_tag << frame.encoded_size
|
||
// Флаги, оставляем пустыми
|
||
encoded_tag << []u8{len: 2}
|
||
// Идекс кодировщика текста
|
||
encoded_tag << frame.encoding_name_idx
|
||
// Уже сами данные
|
||
encoded_tag << frame.encoded_data
|
||
// Замыкающие нули
|
||
encoded_tag << []u8{len: frame.terminator_len()}
|
||
|
||
return encoded_tag
|
||
}
|
||
|
||
// Байты фрейма для записи в файл
|
||
pub fn (mut frame Frame) to_bytes(idv3_version u8) []u8 {
|
||
// Картинка и нетронутые фреймы пишутся как были прочитаны,
|
||
// чтобы не потерять то, что не разбирается (язык в COMM, значение в TXXX, бинарные фреймы)
|
||
if frame.raw.len > 0 && (!frame.modified || frame.name == 'APIC') {
|
||
return frame.raw
|
||
}
|
||
|
||
return frame.get_encoded_frame_tag(idv3_version)
|
||
}
|
||
|
||
// Запись данных в тэг
|
||
@[inline]
|
||
pub fn (mut frame Frame) set_data(data string) {
|
||
if frame.data.len != 0 {
|
||
frame.prev_size = u64(frame.data.len)
|
||
} else {
|
||
mut b_data := data.bytes()
|
||
b_data << []u8{len: 1}
|
||
frame.prev_size = u64(b_data.len)
|
||
frame.cur_size = u64(b_data.len)
|
||
}
|
||
|
||
frame.data = data
|
||
frame.modified = true
|
||
}
|