From abab08f1335ce490ca4d321127b280745508d94f Mon Sep 17 00:00:00 2001 From: K1ndWha1e Date: Fri, 25 Sep 2026 21:57:30 +0300 Subject: [PATCH] =?UTF-8?q?*=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=D0=B5=20=D1=81?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE,=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BC=D0=BD=D1=8E=20=D1=87=D1=82=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.v | 46 +++++++++++++++++++++++++---- metadata/enums.v | 29 ++++++++++++++++++ metadata/frame.v | 75 ++++++++++++++++++++++++++++++++--------------- metadata/id3v23.v | 32 ++++++++++---------- metadata/id3v24.v | 55 +++++++++++++++++++++++----------- metadata/struct.v | 54 +++++++++++++++++++++++----------- metadata/utils.v | 43 +++++++++++++++++++++++---- test/test_frame.v | 0 8 files changed, 249 insertions(+), 85 deletions(-) create mode 100644 metadata/enums.v create mode 100644 test/test_frame.v diff --git a/main.v b/main.v index 05dea7d..5a77d29 100644 --- a/main.v +++ b/main.v @@ -1,11 +1,13 @@ module main import os +import io import metadata fn id3(){ - mut audio := os.open("./data/АЛИСА 11000.mp3") or { panic(err) } + // mut audio := os.open("./data/АЛИСА 11000.mp3") or { panic(err) } // mut audio := os.open("./data/Глава 2. 16 и 17 июля.mp3") or { panic(err) } + mut audio := os.open("./out.mp3") or { panic(err) } // mut audio := os.open("./data/01_01.mp3") or { panic(err) } // mut audio := os.open("./test.mp3") or { panic(err) } @@ -29,23 +31,55 @@ fn id3(){ if wav.id3v23.embedded { wav.id3v23.get_full_size(mut audio) - wav.id3v23.fill_tag(mut audio) + wav.id3v23.fill_tags(mut audio) println(wav.id3v23.to_v1()) wav.id3v23.tit2.encode_size(3) + tit2 := wav.id3v23.tit2.get_encoded_frame_tag(3) + println(tit2) // println(wav.id3v23.tit2) } if wav.id3v24.embedded { wav.id3v24.get_full_size(mut audio) - wav.id3v24.fill_tag(mut audio) + wav.id3v24.fill_tags(mut audio) println(wav.id3v24.to_v1()) // wav.id3v24.tit2.encode_size(4) - flex := wav.id3v24.tit2.get_encoded_frame_tag(4) + // flex := wav.id3v24.talb.get_encoded_frame_tag(4) - print(flex) + // println(flex) - // println(wav.id3v24.tit2) + // println(wav.id3v24.tdrc.data) + // wav.id3v24.tdrc.set_data("2017") + + // enc_size := metadata.encode_header_size(wav.id3v24.size, wav.id3v24.version) + // println(enc_size) + wav.id3v24.tit2.set_data("2017") + wav.id3v24.tpe1.init("tpe1") + wav.id3v24.tpe1.set_data("Hello") + // metadata.encode_header(mut wav.id3v24) + // println(wav.id3v24.encoded_header) + // println(wav.id3v24.tpe1.name) + + wav.id3v24.get_full_embed_tag() + println(wav.id3v24.encoded_header.len) + + wav.id3v24.insert_tags_into_audio(mut audio) + + // println(wav.id3v24) + + mut f := os.create("./temp.mp3") or { panic(err) } + + f.write(wav.id3v24.encoded_header) or { panic(err) } + f.close() + + // println(wav.id3v24) + // mut audio_2 := os.open("./out.mp3") or { panic(err) } + + // data := io.read_all(reader: audio_2) or { [u8(0), u8(0), u8(0), u8(0)] } + // println(data) + println(wav.id3v24.size) + println(wav.id3v24.encoded_header) } diff --git a/metadata/enums.v b/metadata/enums.v new file mode 100644 index 0000000..67b9a06 --- /dev/null +++ b/metadata/enums.v @@ -0,0 +1,29 @@ +module metadata + +pub enum EnumID3v24 as u8 { + tit2 + tpe1 + tpe2 + talb + tdrc + trck + tcon + tcop + apic + comment + unknown +} + +pub enum EnumID3v23 as u8 { + tit2 + tpe1 + tpe2 + talb + tyer + trck + tcon + tcop + apic + comment + unknown +} \ No newline at end of file diff --git a/metadata/frame.v b/metadata/frame.v index d195482..cf9d07e 100644 --- a/metadata/frame.v +++ b/metadata/frame.v @@ -4,12 +4,14 @@ import encoding.iconv @[heap] pub struct Frame { + pub mut: + data string mut: - size u64 + prev_size u64 + cur_size u64 start u64 end u64 encoding_name string - data string name string encoding_name_idx u8 addition []u8 @@ -19,37 +21,37 @@ pub struct Frame { modified bool } -// Во врейме первый байт указывает кодировку 0 - ISO-8859-1 (Latin-1), 1 - UTF-16LE +// Во фрейме первый байт указывает кодировку 0 - ISO-8859-1 (Latin-1), 1 - UTF-16LE @[inline] -fn (mut frame Frame) fill_frame(bytes []u8, start_position u64, idv3_version u8) { +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.size = if idv3_version == 3 { - get_size_v23(bytes[frame.start+4..frame.start+8]) + 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]) + get_size_v24(bytes[frame.start + 4..frame.start + 8]) } // Конец фрейма - frame.end = frame.start + frame.size + 10 + frame.end = frame.start + frame.cur_size + 10 // Содержимое фрейма - frame.encoding_name = match bytes[frame.start+10] { + 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]) + 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+24..frame.end] + frame.addition = bytes[frame.start + 10..frame.start + 24] + frame.apic = bytes[frame.start + 24..frame.end] } else { // Выбор шага в записимости от версии ID3 stride := if idv3_version == 3 { 13 } else { 11 } @@ -59,6 +61,13 @@ fn (mut frame Frame) fill_frame(bytes []u8, start_position u64, idv3_version u8) } } +pub fn (mut frame Frame) init(tag_name string) { + frame.name = tag_name.to_upper() + + frame.encoding_name = "UTF-8" + frame.encoding_name_idx = 3 +} + // Кодируем длину фрейма с нужным сдвигом @[inline] pub fn (mut frame Frame) encode_size(idv3_version u8) { @@ -69,10 +78,10 @@ pub fn (mut frame Frame) encode_size(idv3_version u8) { } mut encoded_size := []u8{len: 4} - encoded_size[0] = u8((frame.size >> stride[0]) & stride[3]) - encoded_size[1] = u8((frame.size >> stride[1]) & stride[3]) - encoded_size[2] = u8((frame.size >> stride[2]) & stride[3]) - encoded_size[3] = u8(frame.size & stride[3]) + 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 } @@ -80,12 +89,10 @@ pub fn (mut frame Frame) encode_size(idv3_version u8) { // Кодируем кадр для дальнейшей записи pub fn (mut frame Frame) encode_frame(idv3_version u8) { encoded_data := iconv.vstring_to_encoding(frame.data, frame.encoding_name) or { [u8(0), u8(0), u8(0), u8(0)] } - + frame.encoded_data = encoded_data - // - frame.size = u64(encoded_data.len + 10) - + // число -> [байт, байт, байт, байт] frame.encode_size(idv3_version) } @@ -95,16 +102,38 @@ pub fn (mut frame Frame) get_encoded_frame_tag(idv3_version u8) []u8 { // Название тэга 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_size // Уже сами данные - encoded_tag << frame.encoded_data + encoded_tag << frame.encoded_data // Запас нулей чтобы не пересчитывать весь фрейм при маленьком редактировании encoded_tag << []u8{len: 10} + // Замыкающий байт + encoded_tag << []u8{len: 1} + + frame.cur_size = u64(encoded_tag.len) return encoded_tag +} + +// Запись данных в тэг +@[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 } \ No newline at end of file diff --git a/metadata/id3v23.v b/metadata/id3v23.v index b4548dd..e11973c 100644 --- a/metadata/id3v23.v +++ b/metadata/id3v23.v @@ -11,19 +11,19 @@ import os { File } // 13 - 1 (байт кодировки) - 2 (BOM) = 10 байт. // В UTF-16 это будет всего 5 символов (так как 1 символ = 2 байта). -enum EnumID3v23 as u8 { - tit2 - tpe1 - tpe2 - talb - tyer - trck - tcon - tcop - apic - comment - unknown -} +// enum EnumID3v23 as u8 { +// tit2 +// tpe1 +// tpe2 +// talb +// tyer +// trck +// tcon +// tcop +// apic +// comment +// unknown +// } const start_idx = 10 @@ -73,12 +73,12 @@ fn get_size_v23(size_bytes []u8) u64 { // Получения размеров всех тэгов pub fn (mut id3v23 ID3v23) get_full_size(mut audio File) { - get_full_size(mut id3v23, mut audio) + get_current_tag_size(mut id3v23, mut audio) } // Заполнение тэга -pub fn (mut id3v23 ID3v23) fill_tag(mut audio File) { - fill_tag[ID3v23](mut id3v23, mut audio) +pub fn (mut id3v23 ID3v23) fill_tags(mut audio File) { + dump_data[ID3v23](mut id3v23, mut audio) } // Конвертировать ID3 v2.3 в ID3 v1 diff --git a/metadata/id3v24.v b/metadata/id3v24.v index 1d8f280..6363fd1 100644 --- a/metadata/id3v24.v +++ b/metadata/id3v24.v @@ -1,20 +1,8 @@ module metadata import os { File } +import io { read_all } -enum EnumID3v24 as u8 { - tit2 - tpe1 - tpe2 - talb - tdrc - trck - tcon - tcop - apic - comment - unknown -} pub fn (mut tag ID3v24) add_frame(frame Frame) { name := EnumID3v24.from(frame.name.to_lower()) or { EnumID3v24.unknown } @@ -53,6 +41,7 @@ pub fn (mut tag ID3v24) add_frame(frame Frame) { } } + @[inline] fn get_size_v24(size_bytes []u8) u64 { return (u64(size_bytes[0]) << 21) | (u64(size_bytes[1]) << 14) | (u64(size_bytes[2]) << 7) | u64(size_bytes[3]) @@ -60,12 +49,12 @@ fn get_size_v24(size_bytes []u8) u64 { // Получения размеров всех тэгов pub fn (mut id3v24 ID3v24) get_full_size(mut audio File) { - get_full_size(mut id3v24, mut audio) + get_current_tag_size(mut id3v24, mut audio) } // Заполнение тэга -pub fn (mut id3v24 ID3v24) fill_tag(mut audio File) { - fill_tag[ID3v24](mut id3v24, mut audio) +pub fn (mut id3v24 ID3v24) fill_tags(mut audio File) { + dump_data[ID3v24](mut id3v24, mut audio) } // Конвертировать ID3 v2.3 в ID3 v1 @@ -81,7 +70,39 @@ pub fn (id3v24 ID3v24) to_v1() ID3v1 { } } +// Формируем полный полный тэг в байтах +pub fn (mut id3v24 ID3v24) get_full_embed_tag() { + // Проход по всем полям стуктуры + mut tag_bytes := []u8{} + + id3v24.size = 0 -pub fn flexd() { + $for field in ID3v24.fields { + $if field.typ is Frame { + // Читаются только заполненные тэги + if id3v24.$(field.name).cur_size > 0 { + id3v24.size += id3v24.$(field.name).cur_size + id3v24.size += 10 + if id3v24.$(field.name).name.to_lower() != "apic" { + // Кодируем значения полей + tag_bytes << id3v24.$(field.name).get_encoded_frame_tag(id3v24.version) + } else { + // Картинка не трогается + tag_bytes << id3v24.$(field.name).apic or { []u8{len: 4}} + } + } + } + } + encode_header(mut id3v24) + id3v24.encoded_header << tag_bytes +} + + +pub fn (mut id3v24 ID3v24) insert_tags_into_audio(mut audio File) { + full_part := read_all(reader: audio) or { [u8(0), u8(0), u8(0), u8(0)] } + + sound_part := full_part[int(id3v24.size) + 10 .. ] + + id3v24.encoded_header << sound_part } diff --git a/metadata/struct.v b/metadata/struct.v index 0915897..b605c1c 100644 --- a/metadata/struct.v +++ b/metadata/struct.v @@ -1,7 +1,7 @@ module metadata -@[heap] +@[heap; minify] pub struct ID3v1 { pub mut: genre string// Жанр @@ -30,28 +30,47 @@ pub struct ID3v23 { comment Frame // Комментарий unknown Frame // Затычка others []Frame // Остальные фреймы - version u8 = 3 // Версия тэга embedded bool // Присутвует ли тэг + version u8 = 3 // Версия тэга + title string = "ID3" } @[heap; minify] pub struct ID3v24 { pub mut: - size u64 // Размер тэга - tit2 Frame // Название аудиодорожки - tpe1 Frame // Автор - tpe2 Frame // Исполнитель - talb Frame // Название альбома - tdrc Frame // Год выпуска - trck Frame // Номер трека - tcon Frame // Жанр - tcop Frame // Композитор (автор) - apic Frame // Обложка - comment Frame // Комментарий - modified_frames []Frame // Затычка - others []Frame // Остальные фреймы - version u8 = 4 // Версия тэга - embedded bool // Присутвует ли тэг + // Размер тэга + size u64 + // Название аудиодорожки + tit2 Frame + // Автор + tpe1 Frame + // Исполнитель + tpe2 Frame + // Название альбома + talb Frame + // Год выпуска + tdrc Frame + // Номер трека + trck Frame + // Жанр + tcon Frame + // Композитор (автор) + tcop Frame + // Обложка + apic Frame + // Комментарий + comment Frame + // Затычка + modified_frames []Frame + // Остальные фреймы + others []Frame + // Версия тэга + version u8 = 4 + // Присутвует ли тэг + embedded bool + // Закодированный хэдер + encoded_header []u8 + title string = "ID3" } @@ -63,3 +82,4 @@ pub struct Audio { path string extension string = ".mp3" } + diff --git a/metadata/utils.v b/metadata/utils.v index cd04ff4..42d7cab 100644 --- a/metadata/utils.v +++ b/metadata/utils.v @@ -27,7 +27,7 @@ pub fn (mut audio Audio) detect_tags(mut data os.File) { } 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) } @@ -42,7 +42,7 @@ pub fn get_bytes(mut audio os.File, position int, size int) []u8 { return last_bytes } -pub fn fill_tag[T](mut tag T, mut audio os.File) { +pub fn dump_data[T](mut tag T, mut audio os.File) { mut current_frame := &Frame{} // Самая первая позиция mut start_position := u64(10) @@ -50,17 +50,48 @@ pub fn fill_tag[T](mut tag T, mut audio os.File) { all_tag_bytes := get_bytes(mut audio, 10, int(tag.size + start_position)) for tag.size > start_position { - current_frame.fill_frame(all_tag_bytes, start_position, tag.version) + current_frame.fill(all_tag_bytes, start_position, tag.version) start_position = current_frame.end - tag.size -= current_frame.size + tag.size -= current_frame.cur_size tag.add_frame(current_frame) } } -pub fn get_full_size[T](mut tag T, mut audio os.File) { +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]) -} \ No newline at end of file +} + +// Кодирование размера заголовка со всеми тэгами +pub fn encode_header_size(size u64, idv3_version u8) []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((size >> stride[0]) & stride[3]) + encoded_size[1] = u8((size >> stride[1]) & stride[3]) + encoded_size[2] = u8((size >> stride[2]) & stride[3]) + encoded_size[3] = u8(size & stride[3]) + + 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, id3v.version) +} + diff --git a/test/test_frame.v b/test/test_frame.v new file mode 100644 index 0000000..e69de29