From aa04fd4df09739ea9674e572906f4189907865e4 Mon Sep 17 00:00:00 2001 From: K1ndWha1e Date: Tue, 29 Sep 2026 23:26:34 +0300 Subject: [PATCH] =?UTF-8?q?*=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D1=81=D1=87=D0=B8?= =?UTF-8?q?=D1=82=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D1=8E=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=B1=D0=B5=D0=B7=20BOM=20*=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D1=8E=D1=89=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.v | 52 +++++++++++++++++++------------- metadata/enums.v | 4 +-- metadata/frame.v | 15 ++++++++-- metadata/id3v1.v | 45 +++++++++++++++------------- metadata/id3v23.v | 7 +++-- metadata/id3v24.v | 5 +++- metadata/struct.v | 12 +++++--- metadata/utils.v | 2 -- test/id3v1_test.v | 29 ++++++++++++++++++ test/id3v2_test.v | 75 +++++++++++++++++++++++++++++++++++++++++++++++ test/test_frame.v | 0 11 files changed, 191 insertions(+), 55 deletions(-) create mode 100644 test/id3v1_test.v create mode 100644 test/id3v2_test.v delete mode 100644 test/test_frame.v diff --git a/main.v b/main.v index bd897ec..983d931 100644 --- a/main.v +++ b/main.v @@ -1,13 +1,14 @@ module main import os -import io +// import io import metadata fn id3(){ // 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("./out.mp3") or { panic(err) } + mut audio := os.open("./data/test_id3v23.mp3") or { panic(err) } // mut audio := os.open("./data/01_01.mp3") or { panic(err) } // mut audio := os.open("./test.mp3") or { panic(err) } @@ -26,9 +27,18 @@ fn id3(){ last_bytes := metadata.get_bytes(mut audio, -128, 128) wav.id3v1.get_metadata(last_bytes) + // wav.id3v1.genre = "Rock" + // wav.id3v1.title = "Test title" + // wav.id3v1.artist = "Test artist" + // wav.id3v1.album = "Test albulm" + // wav.id3v1.comment = "2/5" + // wav.id3v1.track = "2/5" + println(wav.id3v1) + } + if wav.id3v23.embedded { wav.id3v23.get_full_size(mut audio) wav.id3v23.fill_tags(mut audio) @@ -36,7 +46,7 @@ fn id3(){ wav.id3v23.tit2.encode_size(3) tit2 := wav.id3v23.tit2.get_encoded_frame_tag(3) println(tit2) - // println(wav.id3v23.tit2) + println(wav.id3v23) } if wav.id3v24.embedded { @@ -52,28 +62,28 @@ fn id3(){ // 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") - wav.id3v24.tpe2.init("tpe2") - wav.id3v24.tpe2.set_data("Hello2") - // metadata.encode_header(mut wav.id3v24) - // println(wav.id3v24.encoded_header) - // println(wav.id3v24.tpe1.name) + // // 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") + // wav.id3v24.tpe2.init("tpe2") + // wav.id3v24.tpe2.set_data("Hello2") + // // metadata.encode_header(mut wav.id3v24) + // // println(wav.id3v24.encoded_header) + // // println(wav.id3v24.tpe1.name) - wav.id3v24.encode_embed_tags() - println(wav.id3v24.encoded_header.len) + // wav.id3v24.encode_embed_tags() + // println(wav.id3v24.encoded_header.len) - wav.id3v24.insert_tags_into_audio(mut audio) + // wav.id3v24.insert_tags_into_audio(mut audio) - // println(wav.id3v24) + // // println(wav.id3v24) - mut f := os.create("./temp.mp3") or { panic(err) } + // mut f := os.create("./temp.mp3") or { panic(err) } - f.write(wav.id3v24.encoded_header) or { panic(err) } - f.close() + // f.write(wav.id3v24.encoded_header) or { panic(err) } + // f.close() // println(wav.id3v24) // mut audio_2 := os.open("./out.mp3") or { panic(err) } @@ -109,7 +119,7 @@ fn id3(){ // all_bytes := wav.id3v1.insert(mut audio, -128) - // os.write_bytes("./test.mp3", all_bytes) or { panic(err) } + // os.write_bytes("./test.mp3", new_bytes) or { panic(err) } } diff --git a/metadata/enums.v b/metadata/enums.v index 67b9a06..288fc97 100644 --- a/metadata/enums.v +++ b/metadata/enums.v @@ -10,7 +10,7 @@ pub enum EnumID3v24 as u8 { tcon tcop apic - comment + txxx unknown } @@ -24,6 +24,6 @@ pub enum EnumID3v23 as u8 { tcon tcop apic - comment + txxx unknown } \ No newline at end of file diff --git a/metadata/frame.v b/metadata/frame.v index 95f015a..e7263ef 100644 --- a/metadata/frame.v +++ b/metadata/frame.v @@ -54,10 +54,19 @@ fn (mut frame Frame) fill(bytes []u8, start_position u64, idv3_version u8) { // Фрейм целиком вместе с заголовком, чтобы записать его обратно без изменений frame.apic = bytes[frame.start..frame.end] } else { - // Выбор шага в записимости от версии ID3 - stride := if idv3_version == 3 { 13 } else { 11 } + // Данные начинаются после 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[frame.start+u64(stride)..frame.end], frame.encoding_name) or { "" }).trim_right('\x00') + frame.data = (iconv.encoding_to_vstring(bytes[data_start..frame.end], frame.encoding_name) or { "" }).trim_right('\x00') frame.addition = bytes[frame.start..frame.start + 10] } diff --git a/metadata/id3v1.v b/metadata/id3v1.v index dfac383..3d42c80 100644 --- a/metadata/id3v1.v +++ b/metadata/id3v1.v @@ -48,7 +48,7 @@ pub fn (id3v1 ID3v1) to_v23() ID3v23 { tyer: metadata.Frame{ data: id3v1.year } - comment: metadata.Frame{ + txxx: metadata.Frame{ data: id3v1.comment } } @@ -59,23 +59,24 @@ pub fn (id3v1 ID3v1) to_v23() ID3v23 { fn cut_or_trim(data string, capacity i8) []u8 { mut final_data := []u8{len: capacity, init: 0} - - bytes_data := iconv.vstring_to_encoding(data, "CP1251") or { []u8{len: capacity} } + if data.len > 0 { + bytes_data := iconv.vstring_to_encoding(data, "CP1251") or { []u8{len: capacity} } - diff := capacity - bytes_data.len - if diff > 0 { - for i, d in bytes_data { - final_data[i] = d + diff := capacity - bytes_data.len + if diff > 0 { + for i, d in bytes_data { + final_data[i] = d + } + } else { + final_data = bytes_data.clone()[..capacity] } - } else { - final_data = bytes_data.clone()[..capacity] } return final_data } // Перевести тэг в байты -pub fn (id3v1 ID3v1) to_bytes() []u8 { +pub fn (mut id3v1 ID3v1) to_bytes() []u8 { mut bytes := []u8{cap: 128} bytes << "TAG".bytes() @@ -88,23 +89,27 @@ pub fn (id3v1 ID3v1) to_bytes() []u8 { // bytes << cut_or_trim(id3v1.track, i8(2)) bytes << if id3v1.track.len != 0 { [u8(0), id3v1.track.u8()]} else { [u8(1), u8(0)] } - id_genre := Genre.from(id3v1.genre) or { Genre.unknown } + id_genre := Genre.from(id3v1.genre.to_lower()) or { Genre.unknown } bytes << u8(id_genre) // cut_or_trim([u8(id_genre)], i8(2)) + id3v1.embedded = true + return bytes } // Добавление изменённого тэга -pub fn (id3v1 ID3v1) insert(mut audio File, position i8) []u8 { - bytes_tag := id3v1.to_bytes() - // Уcтановка указателя на последний байт - audio.seek(0, os.SeekMode.end) or { panic(err) } - // Положение указателя - size := audio.tell() or { panic(err) } - // Чтение всех байта, кроме последних 128 - mut all_bytes := audio.read_bytes(int(size))#[..position] +pub fn (mut id3v1 ID3v1) insert(mut audio File, position i8) []u8 { + // if !id3v1.embedded { + bytes_tag := id3v1.to_bytes() + // Уcтановка указателя на последний байт + audio.seek(0, os.SeekMode.end) or { panic(err) } + // Положение указателя + size := audio.tell() or { panic(err) } + // Чтение всех байта, кроме последних 128 + mut all_bytes := audio.read_bytes(int(size))#[..position] - all_bytes << bytes_tag + all_bytes << bytes_tag + // } return all_bytes } diff --git a/metadata/id3v23.v b/metadata/id3v23.v index e33f12b..51c6268 100644 --- a/metadata/id3v23.v +++ b/metadata/id3v23.v @@ -58,7 +58,10 @@ pub fn (mut tag ID3v23) add_frame(frame Frame) { } .apic { tag.apic = frame - } + } + .txxx { + tag.txxx = frame + } else { tag.others << frame } @@ -96,7 +99,7 @@ pub fn (id3v23 ID3v23) to_v1() ID3v1 { album: id3v23.talb.data track: id3v23.trck.data.split("/")[0] year: id3v23.tyer.data - comment: id3v23.comment.data + comment: id3v23.txxx.data } } diff --git a/metadata/id3v24.v b/metadata/id3v24.v index 4e90747..d3a9c82 100644 --- a/metadata/id3v24.v +++ b/metadata/id3v24.v @@ -35,6 +35,9 @@ pub fn (mut tag ID3v24) add_frame(frame Frame) { .apic { tag.apic = frame } + .txxx { + tag.txxx = frame + } else { tag.others << frame } @@ -71,7 +74,7 @@ pub fn (id3v24 ID3v24) to_v1() ID3v1 { album: id3v24.talb.data track: id3v24.trck.data.split('/')[0] year: id3v24.tdrc.data - comment: id3v24.comment.data + comment: id3v24.txxx.data } } diff --git a/metadata/struct.v b/metadata/struct.v index b605c1c..c4f1256 100644 --- a/metadata/struct.v +++ b/metadata/struct.v @@ -17,8 +17,11 @@ pub struct ID3v1 { @[heap; minify] pub struct ID3v23 { pub mut: - size u64 // Размер тэга - tit2 Frame // Название аудиодорожки + // Размер тэга + size u64 + // Название аудиодорожки + tit2 Frame + tpe1 Frame // Автор tpe2 Frame // Исполнитель talb Frame // Название альбома @@ -27,9 +30,10 @@ pub struct ID3v23 { tcon Frame // Жанр tcop Frame // Композитор (автор) apic Frame // Обложка - comment Frame // Комментарий + txxx Frame // Комментарий unknown Frame // Затычка others []Frame // Остальные фреймы + encoded_header []u8 embedded bool // Присутвует ли тэг version u8 = 3 // Версия тэга title string = "ID3" @@ -59,7 +63,7 @@ pub struct ID3v24 { // Обложка apic Frame // Комментарий - comment Frame + txxx Frame // Затычка modified_frames []Frame // Остальные фреймы diff --git a/metadata/utils.v b/metadata/utils.v index 3027023..4435668 100644 --- a/metadata/utils.v +++ b/metadata/utils.v @@ -105,8 +105,6 @@ pub fn encode_header[T](mut id3v T) { // Количество нулей в конце тэга const tag_padding = 256 -type ID3vType = ID3v23 | ID3v24 - // Формируем полный полный тэг в байтах pub fn encode_embed_tags[T](mut id3v2 T) { // Проход по всем полям стуктуры diff --git a/test/id3v1_test.v b/test/id3v1_test.v new file mode 100644 index 0000000..ca57858 --- /dev/null +++ b/test/id3v1_test.v @@ -0,0 +1,29 @@ +import os +import metadata + +// Проверка чтения id3v1 +fn test_idv3v1() { + mut audio := os.open("./data/test_id3v1.mp3") or { panic(err) } + defer { + audio.close() + } + mut wav := metadata.Audio{} + wav.detect_tags(mut audio) + + assert wav.id3v1.embedded == true, 'Тег показан false, а должен быть true' + assert wav.id3v23.embedded == false, 'Тег показан true, а должен быть false' + assert wav.id3v24.embedded == false, 'Тег показан true, а должен быть false' + + last_bytes := metadata.get_bytes(mut audio, -128, 128) + wav.id3v1.get_metadata(last_bytes) + + assert wav.id3v1.genre == "Rock" + assert wav.id3v1.title == "Test Title" + assert wav.id3v1.artist == "Test Artist" + assert wav.id3v1.album == "Test Album" + assert wav.id3v1.track == "6" + assert wav.id3v1.year == "2026" + assert wav.id3v1.comment == "Test comment" + assert wav.id3v1.embedded == true +} + diff --git a/test/id3v2_test.v b/test/id3v2_test.v new file mode 100644 index 0000000..b757c06 --- /dev/null +++ b/test/id3v2_test.v @@ -0,0 +1,75 @@ +import os +import metadata + +// Проверка чтения id3v23 +fn test_idv3v23() { + mut audio := os.open("./data/test_id3v23.mp3") or { panic(err) } + defer { + audio.close() + } + mut wav := metadata.Audio{} + wav.detect_tags(mut audio) + + assert wav.id3v1.embedded == false, 'Тег показан false, а должен быть true' + assert wav.id3v23.embedded == true, 'Тег показан true, а должен быть false' + assert wav.id3v24.embedded == false, 'Тег показан true, а должен быть false' + wav.id3v23.get_full_size(mut audio) + wav.id3v23.fill_tags(mut audio) + + assert wav.id3v23.tit2.data == "Test Title" + assert wav.id3v23.tpe1.data == "Test Author" + assert wav.id3v23.tpe2.data == "Test Narator" + assert wav.id3v23.talb.data == "Test Album" + assert wav.id3v23.tyer.data == "2026" + assert wav.id3v23.trck.data == "6/7" + assert wav.id3v23.tcon.data == "Rock" + assert wav.id3v23.tcop.data == "Test" + assert wav.id3v23.txxx.data == "comment" + + id3v1 := wav.id3v23.to_v1() + + assert id3v1.genre == "Rock" + assert id3v1.title == "Test Title" + assert id3v1.artist == "Test Narator" + assert id3v1.album == "Test Album" + assert id3v1.track == "6" + assert id3v1.year == "2026" + assert id3v1.comment == "comment" +} + +// Проверка чтения id3v24 +fn test_idv3v24() { + mut audio := os.open("./data/test_id3v24.mp3") or { panic(err) } + defer { + audio.close() + } + mut wav := metadata.Audio{} + wav.detect_tags(mut audio) + + assert wav.id3v1.embedded == false, 'Тег показан false, а должен быть true' + assert wav.id3v23.embedded == false, 'Тег показан true, а должен быть false' + assert wav.id3v24.embedded == true, 'Тег показан true, а должен быть false' + wav.id3v24.get_full_size(mut audio) + wav.id3v24.fill_tags(mut audio) + + assert wav.id3v24.tit2.data == "Test Title" + assert wav.id3v24.tpe1.data == "Test Author" + assert wav.id3v24.tpe2.data == "Test Narator" + assert wav.id3v24.talb.data == "Test Album" + assert wav.id3v24.tdrc.data == "2026" + assert wav.id3v24.trck.data == "6/7" + assert wav.id3v24.tcon.data == "Rock" + assert wav.id3v24.tcop.data == "Test" + assert wav.id3v24.txxx.data == "comment" + + id3v1 := wav.id3v24.to_v1() + + assert id3v1.genre == "Rock" + assert id3v1.title == "Test Title" + assert id3v1.artist == "Test Narator" + assert id3v1.album == "Test Album" + assert id3v1.track == "6" + assert id3v1.year == "2026" + assert id3v1.comment == "comment" +} + diff --git a/test/test_frame.v b/test/test_frame.v deleted file mode 100644 index e69de29..0000000