Files
id3_editor/metadata/utils.v
T

177 lines
6.3 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module metadata
import os
pub fn (mut audio Audio) detect_tags(mut data os.File) {
// ? --- Проверка версии ID3v1 ---
last_bytes := get_bytes(mut data, -128, 3)
if last_bytes[0..3].bytestr() == "TAG" {
audio.id3v1.embedded = true
}
// ===============================
// ? --- Проверка версии ID3v23.3 ---
first_ten_bytes := get_bytes(mut data, 0, 4)
if first_ten_bytes[..3].bytestr() == "ID3" {
if first_ten_bytes[3] == 3 {
audio.id3v23.embedded = true
} else if first_ten_bytes[3] == 4 {
// Проверка версии ID3v23.4
audio.id3v24.embedded = true
}
}
// ===============================
}
pub fn get_bytes(mut audio os.File, position int, size int) []u8 {
// > ---------Работа с расположением указателя--------------
// Ставим указатель на 128 байт с конца
if position < 0 {
audio.seek(position, os.SeekMode.end) or { panic(err) }
}
// Получаем номер 128 байта с конца
pos := audio.tell() or { panic(err) }
// Читаем байты с конца
last_bytes := audio.read_bytes_at(size, u64(pos))
audio.seek(0, os.SeekMode.start) or { panic(err) }
// =========================================================
return last_bytes
}
pub fn dump_data[T](mut tag T, mut audio os.File) {
mut current_frame := &Frame{}
// Самая первая позиция
mut start_position := u64(10)
// Получаем вне возможные байты
all_tag_bytes := get_bytes(mut audio, 10, int(tag.size + start_position))
// Конец тэга с учётом 10 байт заголовка
tag_end := tag.size + 10
for start_position + 10 <= tag_end {
// Нулевой байт вместо названия фрейма — начался запас нулей
if all_tag_bytes[start_position] == 0 {
break
}
current_frame.fill(all_tag_bytes, start_position, tag.version)
start_position = current_frame.end
tag.add_frame(current_frame)
}
}
pub fn get_current_tag_size[T](mut tag T, mut audio os.File) {
// Первые 10 байт тэга
first_ten_bytes := get_bytes(mut audio, 0, 10)
tag.size = (u64(first_ten_bytes[6]) << 21) | (u64(first_ten_bytes[7]) << 14) | (u64(first_ten_bytes[8]) << 7) | u64(first_ten_bytes[9])
}
// Кодирование размера заголовка со всеми тэгами.
// В заголовке размер всегда syncsafe (по 7 бит в байте), в том числе в v2.3
pub fn encode_header_size(size u64) []u8 {
mut encoded_size := []u8{len: 4}
encoded_size[0] = u8((size >> 21) & 0x7F)
encoded_size[1] = u8((size >> 14) & 0x7F)
encoded_size[2] = u8((size >> 7) & 0x7F)
encoded_size[3] = u8(size & 0x7F)
return encoded_size
}
// Кодирование первых 10 байт ID3
pub fn encode_header[T](mut id3v T) {
// Байты для символов "ID3"
id3v.encoded_header << [u8(73), u8(68), u8(51)]
// Версия IDv3
id3v.encoded_header << [id3v.version]
// Минорная версия и спец. флаги
id3v.encoded_header << []u8{len: 2}
id3v.encoded_header << metadata.encode_header_size(id3v.size)
}
// Количество нулей в конце тэга
const tag_padding = 256
// Формируем полный полный тэг в байтах
pub fn encode_embed_tags[T](mut id3v2 T) {
// Проход по всем полям стуктуры
mut tag_bytes := []u8{}
// Именно T.fields: с id3v2.fields V пропускает поля, которых нет в другом типе (tyer в ID3v24)
$for field in T.fields {
$if field.typ is Frame {
// Читаются только заполненные тэги
if id3v2.$(field.name).cur_size > 0 {
// Фрейма не было в файле и init не вызывался: имя берётся из поля структуры
if id3v2.$(field.name).name == "" && field.name.len == 4 {
id3v2.$(field.name).init(field.name, id3v2.version)
}
// Без 4-символьного ID фрейм испортит весь тэг
if id3v2.$(field.name).name.len == 4 {
tag_bytes << id3v2.$(field.name).to_bytes(id3v2.version)
}
}
}
}
// Фреймы, для которых нет отдельного поля в структуре
for mut frame in id3v2.others {
tag_bytes << frame.to_bytes(id3v2.version)
}
// Запас нулей чтобы не пересчитывать весь тэг при маленьком редактировании
tag_bytes << []u8{len: tag_padding}
// Размер считается по уже закодированным фреймам
id3v2.size = u64(tag_bytes.len)
id3v2.encoded_header = []u8{}
encode_header(mut id3v2)
id3v2.encoded_header << tag_bytes
}
// Добавляет аудио из файла после нового тэга в encoded_header, старый тэг пропускается
pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! {
header := get_bytes(mut audio, 0, 10)
if header.len < 10 || header[..3].bytestr() != "ID3" {
return error("в файле нет тэга ID3v2")
}
// Размер старого тэга берётся из файла, id3v2.size уже хранит размер нового.
// В заголовке он syncsafe в любой версии, плюс 10 байт самого заголовка
mut audio_start := get_size_v24(header[6..10]) + 10
// В v2.4 после тэга может идти футер ещё на 10 байт
if header[3] == 4 && header[5] & 0x10 != 0 {
audio_start += 10
}
audio.seek(0, .end)!
file_size := u64(audio.tell()!)
if audio_start > file_size {
return error("размер тэга (${audio_start}) больше размера файла (${file_size})")
}
// Аудио читается сразу в конец encoded_header, без промежуточной копии всего файла
sound_len := int(file_size - audio_start)
header_len := id3v2.encoded_header.len
unsafe { id3v2.encoded_header.grow_len(sound_len) }
audio.seek(i64(audio_start), .start)!
mut read := 0
for read < sound_len {
n := unsafe { audio.read_into_ptr(&u8(id3v2.encoded_header.data) + header_len + read, sound_len - read)! }
if n == 0 {
return error("файл закончился раньше ожидаемого")
}
read += n
}
audio.seek(0, .start)!
}