From 6161a7faaa99934678792448e4a5a89fadacaffa Mon Sep 17 00:00:00 2001 From: K1ndWha1e Date: Sun, 11 Oct 2026 17:31:02 +0300 Subject: [PATCH] =?UTF-8?q?vfmt:=20=D0=B2=D0=B5=D1=81=D1=8C=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=B2=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B5=20?= =?UTF-8?q?V=20master=20(=D0=B1=D0=B5=D0=B7=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- metadata/enums.v | 2 +- metadata/frame.v | 82 ++++++++++--------- metadata/genres.v | 186 ++++++++++++++++++++++++++++++++++-------- metadata/id3v1.v | 34 ++++---- metadata/id3v23.v | 14 ++-- metadata/id3v24.v | 3 - metadata/struct.v | 183 ++++++++++++++++++++--------------------- metadata/utils.v | 19 ++--- test/audiobook_test.v | 10 ++- test/id3v1_test.v | 17 ++-- test/id3v2_test.v | 140 +++++++++++++++---------------- utils/code_detector.v | 2 +- 12 files changed, 405 insertions(+), 287 deletions(-) diff --git a/metadata/enums.v b/metadata/enums.v index 288fc97..dc898a9 100644 --- a/metadata/enums.v +++ b/metadata/enums.v @@ -26,4 +26,4 @@ pub enum EnumID3v23 as u8 { apic txxx unknown -} \ No newline at end of file +} diff --git a/metadata/frame.v b/metadata/frame.v index 2bce404..9a9542d 100644 --- a/metadata/frame.v +++ b/metadata/frame.v @@ -4,23 +4,23 @@ 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 +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 @@ -32,10 +32,10 @@ fn (mut frame Frame) fill(bytes []u8, start_position u64, idv3_version u8) { 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.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 @@ -44,16 +44,16 @@ fn (mut frame Frame) fill(bytes []u8, start_position u64, idv3_version u8) { 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 = 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" { + if frame.name.to_lower() == 'apic' { // Дополнительные байты с информацией (MIME) frame.addition = bytes[frame.start + 10..frame.start + 24] // Фрейм целиком вместе с заголовком, чтобы записать его обратно без изменений @@ -65,13 +65,13 @@ fn (mut frame Frame) fill(bytes []u8, start_position u64, idv3_version u8) { // 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" + 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.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] } @@ -86,10 +86,10 @@ pub fn (mut frame Frame) init(tag_name string, idv3_version u8) { 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 = 'UTF-16LE' frame.encoding_name_idx = 1 } else { - frame.encoding_name = "UTF-8" + frame.encoding_name = 'UTF-8' frame.encoding_name_idx = 3 } } @@ -97,10 +97,10 @@ fn (mut frame Frame) set_unicode_encoding(idv3_version u8) { // Кодируем длину фрейма с нужным сдвигом @[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)] + 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} @@ -123,13 +123,17 @@ pub fn (mut frame Frame) encode_frame(idv3_version u8) { // Не удалось закодировать в исходной кодировке фрейма, переходим на Юникод frame.set_unicode_encoding(idv3_version) iconv.vstring_to_encoding(frame.data, frame.encoding_name) or { - panic("не удалось закодировать фрейм ${frame.name}: ${err}") + 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] } + mut bom := if frame.encoding_name == 'UTF-16BE' { + [u8(0xFE), 0xFF] + } else { + [u8(0xFF), 0xFE] + } bom << encoded_data bom } else { @@ -174,7 +178,7 @@ pub fn (mut frame Frame) get_encoded_frame_tag(idv3_version u8) []u8 { pub fn (mut frame Frame) to_bytes(idv3_version u8) []u8 { // Картинка и нетронутые фреймы пишутся как были прочитаны, // чтобы не потерять то, что не разбирается (язык в COMM, значение в TXXX, бинарные фреймы) - if frame.raw.len > 0 && (!frame.modified || frame.name == "APIC") { + if frame.raw.len > 0 && (!frame.modified || frame.name == 'APIC') { return frame.raw } @@ -195,4 +199,4 @@ pub fn (mut frame Frame) set_data(data string) { frame.data = data frame.modified = true -} \ No newline at end of file +} diff --git a/metadata/genres.v b/metadata/genres.v index 1768c72..47d6bd1 100644 --- a/metadata/genres.v +++ b/metadata/genres.v @@ -151,47 +151,169 @@ enum Genre as u8 { anime = 145 jpop = 146 synthpop = 147 - - unknown = 255 + + unknown = 255 } const genres_name = [ - 'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', - 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', - 'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', 'Alternative', 'Ska', - 'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', 'Ambient', - 'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', - 'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', - 'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', - 'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave', - 'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', - 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 'Christian Rap', - 'Pop/Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', - 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', - 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', + 'Blues', + 'Classic Rock', + 'Country', + 'Dance', + 'Disco', + 'Funk', + 'Grunge', + 'Hip-Hop', + 'Jazz', + 'Metal', + 'New Age', + 'Oldies', + 'Other', + 'Pop', + 'R&B', + 'Rap', + 'Reggae', + 'Rock', + 'Techno', + 'Industrial', + 'Alternative', + 'Ska', + 'Death Metal', + 'Pranks', + 'Soundtrack', + 'Euro-Techno', + 'Ambient', + 'Trip-Hop', + 'Vocal', + 'Jazz+Funk', + 'Fusion', + 'Trance', + 'Classical', + 'Instrumental', + 'Acid', + 'House', + 'Game', + 'Sound Clip', + 'Gospel', + 'Noise', + 'AlternRock', + 'Bass', + 'Soul', + 'Punk', + 'Space', + 'Meditative', + 'Instrumental Pop', + 'Instrumental Rock', + 'Ethnic', + 'Gothic', + 'Darkwave', + 'Techno-Industrial', + 'Electronic', + 'Pop-Folk', + 'Eurodance', + 'Dream', + 'Southern Rock', + 'Comedy', + 'Cult', + 'Gangsta', + 'Top 40', + 'Christian Rap', + 'Pop/Funk', + 'Jungle', + 'Native American', + 'Cabaret', + 'New Wave', + 'Psychadelic', + 'Rave', + 'Showtunes', + 'Trailer', + 'Lo-Fi', + 'Tribal', + 'Acid Punk', + 'Acid Jazz', + 'Polka', + 'Retro', + 'Musical', + 'Rock & Roll', 'Hard Rock', // Winamp - 'Folk', 'Folk-Rock', 'National Folk', 'Swing', 'Fast Fusion', 'Bebob', - 'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', - 'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', - 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', - 'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', - 'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', - 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet', - 'Punk Ballad', 'Drum Solo', 'A Cappella', 'Euro-House', 'Dance Hall', - 'Goa', 'Drum & Bass', 'Club-House', 'Hardcore', 'Terror', 'Indie', - 'BritPop', 'Negerpunk', 'Polsk Punk', 'Beat', 'Christian Gangsta', - 'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary Christian', - 'Christian Rock', 'Merengue', 'Salsa', 'Thrash Metal', 'Anime', 'JPop', - 'Synthpop' + 'Folk', + 'Folk-Rock', + 'National Folk', + 'Swing', + 'Fast Fusion', + 'Bebob', + 'Latin', + 'Revival', + 'Celtic', + 'Bluegrass', + 'Avantgarde', + 'Gothic Rock', + 'Progressive Rock', + 'Psychedelic Rock', + 'Symphonic Rock', + 'Slow Rock', + 'Big Band', + 'Chorus', + 'Easy Listening', + 'Acoustic', + 'Humour', + 'Speech', + 'Chanson', + 'Opera', + 'Chamber Music', + 'Sonata', + 'Symphony', + 'Booty Bass', + 'Primus', + 'Porn Groove', + 'Satire', + 'Slow Jam', + 'Club', + 'Tango', + 'Samba', + 'Folklore', + 'Ballad', + 'Power Ballad', + 'Rhythmic Soul', + 'Freestyle', + 'Duet', + 'Punk Ballad', + 'Drum Solo', + 'A Cappella', + 'Euro-House', + 'Dance Hall', + 'Goa', + 'Drum & Bass', + 'Club-House', + 'Hardcore', + 'Terror', + 'Indie', + 'BritPop', + 'Negerpunk', + 'Polsk Punk', + 'Beat', + 'Christian Gangsta', + 'Heavy Metal', + 'Black Metal', + 'Crossover', + 'Contemporary Christian', + 'Christian Rock', + 'Merengue', + 'Salsa', + 'Thrash Metal', + 'Anime', + 'JPop', + 'Synthpop', ] -pub fn (genre Genre) str() string{ +pub fn (genre Genre) str() string { match genre { .unknown { - return "Unknown" - } else { + return 'Unknown' + } + else { return genres_name[u8(genre)] } } -} \ No newline at end of file +} diff --git a/metadata/id3v1.v b/metadata/id3v1.v index 3c76b16..fbf267b 100644 --- a/metadata/id3v1.v +++ b/metadata/id3v1.v @@ -4,28 +4,28 @@ import os { File } import encoding.iconv import utils - // Определить кодировку utf-8 или cp-1251 @[inline] fn decode_bytes(bytes []u8) string { - format := if utils.is_utf8(bytes) { "UTF-8" } else { "CP1251" } + format := if utils.is_utf8(bytes) { 'UTF-8' } else { 'CP1251' } text := (iconv.encoding_to_vstring(bytes, format) or { "Can't decode" }).all_before('\x00') - return text + return text } // Извлечение метаданных из ID3 v1 pub fn (mut tag ID3v1) get_metadata(last_bytes []u8) { tag.title = decode_bytes(last_bytes[3..33]) - + tag.artist = decode_bytes(last_bytes[33..63]) tag.album = decode_bytes(last_bytes[63..92]) tag.year = decode_bytes(last_bytes[93..97]) tag.comment = decode_bytes(last_bytes[97..125]) - tag.track = if last_bytes[125] == u8(0) {last_bytes[126].str()} else { "" } + tag.track = if last_bytes[125] == u8(0) { last_bytes[126].str() } else { '' } genre := Genre.from(last_bytes[127..128][0]) or { Genre.unknown } - tag.genre = genre.str()} + tag.genre = genre.str() +} // Конвертировать ID3 v2.3 в ID3 v1 pub fn (id3v1 ID3v1) to_v23() ID3v23 { @@ -60,7 +60,7 @@ fn cut_or_trim(data string, capacity i8) []u8 { mut final_data := []u8{len: capacity, init: 0} if data.len > 0 { - bytes_data := iconv.vstring_to_encoding(data, "CP1251") or { []u8{len: capacity} } + bytes_data := iconv.vstring_to_encoding(data, 'CP1251') or { []u8{len: capacity} } diff := capacity - bytes_data.len if diff > 0 { @@ -79,7 +79,7 @@ fn cut_or_trim(data string, capacity i8) []u8 { pub fn (mut id3v1 ID3v1) to_bytes() []u8 { mut bytes := []u8{cap: 128} - bytes << "TAG".bytes() + bytes << 'TAG'.bytes() bytes << cut_or_trim(id3v1.title, i8(30)) bytes << cut_or_trim(id3v1.artist, i8(30)) bytes << cut_or_trim(id3v1.album, i8(30)) @@ -87,7 +87,7 @@ pub fn (mut id3v1 ID3v1) to_bytes() []u8 { bytes << cut_or_trim(id3v1.comment, i8(28)) // bytes << cut_or_trim(id3v1.track, i8(2)) - bytes << if id3v1.track.len != 0 { [u8(0), id3v1.track.u8()]} else { [u8(1), u8(0)] } + bytes << if id3v1.track.len != 0 { [u8(0), id3v1.track.u8()] } else { [u8(1), u8(0)] } id_genre := Genre.from(id3v1.genre.to_lower()) or { Genre.unknown } bytes << u8(id_genre) // cut_or_trim([u8(id_genre)], i8(2)) @@ -100,15 +100,15 @@ pub fn (mut id3v1 ID3v1) to_bytes() []u8 { // Добавление изменённого тэга 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].clone() + 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].clone() - all_bytes << bytes_tag + all_bytes << bytes_tag // } return all_bytes diff --git a/metadata/id3v23.v b/metadata/id3v23.v index 7122b3e..4428158 100644 --- a/metadata/id3v23.v +++ b/metadata/id3v23.v @@ -27,7 +27,6 @@ import os { File } const start_idx = 10 - pub fn (mut tag ID3v23) add_frame(frame Frame) { name := EnumID3v23.from(frame.name.to_lower()) or { EnumID3v23.unknown } @@ -84,7 +83,6 @@ pub fn (mut id3v23 ID3v23) encode_embed_tags() { encode_embed_tags[ID3v23](mut id3v23) } - // Заполнение тэга pub fn (mut id3v23 ID3v23) fill_tags(mut audio File) { dump_data[ID3v23](mut id3v23, mut audio) @@ -93,12 +91,12 @@ pub fn (mut id3v23 ID3v23) fill_tags(mut audio File) { // Конвертировать ID3 v2.3 в ID3 v1 pub fn (id3v23 ID3v23) to_v1() ID3v1 { return ID3v1{ - genre: id3v23.tcon.data - title: id3v23.tit2.data - artist: id3v23.tpe2.data - album: id3v23.talb.data - track: id3v23.trck.data.split("/")[0] - year: id3v23.tyer.data + genre: id3v23.tcon.data + title: id3v23.tit2.data + artist: id3v23.tpe2.data + album: id3v23.talb.data + track: id3v23.trck.data.split('/')[0] + year: id3v23.tyer.data comment: id3v23.txxx.data } } diff --git a/metadata/id3v24.v b/metadata/id3v24.v index f5099e0..af0283d 100644 --- a/metadata/id3v24.v +++ b/metadata/id3v24.v @@ -2,7 +2,6 @@ module metadata import os { File } - pub fn (mut tag ID3v24) add_frame(frame Frame) { name := EnumID3v24.from(frame.name.to_lower()) or { EnumID3v24.unknown } @@ -43,7 +42,6 @@ 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]) @@ -77,7 +75,6 @@ pub fn (id3v24 ID3v24) to_v1() ID3v1 { } } - // Добавление аудио после нового тэга pub fn (mut id3v24 ID3v24) insert_tags_into_audio(mut audio File) ! { insert_tags_into_audio[ID3v24](mut id3v24, mut audio)! diff --git a/metadata/struct.v b/metadata/struct.v index 61b58ba..9693828 100644 --- a/metadata/struct.v +++ b/metadata/struct.v @@ -1,109 +1,106 @@ module metadata - @[heap; minify] pub struct ID3v1 { - pub mut: - // Жанр - genre string - // Название аудиодорожки - title string - // Актёр озучания - artist string - // Название альбома - album string - // Записывается как 2/5, что значит 2 трэк из 5 - track string - // Год записи, может int нужно - year string - // Комментарий - comment string - // Присутвует ли тэг - embedded bool +pub mut: + // Жанр + genre string + // Название аудиодорожки + title string + // Актёр озучания + artist string + // Название альбома + album string + // Записывается как 2/5, что значит 2 трэк из 5 + track string + // Год записи, может int нужно + year string + // Комментарий + comment string + // Присутвует ли тэг + embedded bool } @[heap; minify] pub struct ID3v23 { - pub mut: - // Размер тэга - size u64 - // Название аудиодорожки - tit2 Frame - // Автор - tpe1 Frame - // Исполнитель - tpe2 Frame - // Название альбома - talb Frame - // Год выпуска - tyer Frame - // Номер трека - trck Frame - // Жанр - tcon Frame - // Композитор (автор) - tcop Frame - // Обложка - apic Frame - // Комментарий - txxx Frame - // Затычка - unknown Frame - // Остальные фреймы - others []Frame - encoded_header []u8 - // Присутвует ли тэг - embedded bool - // Версия тэга - version u8 = 3 - title string = "ID3" +pub mut: + // Размер тэга + size u64 + // Название аудиодорожки + tit2 Frame + // Автор + tpe1 Frame + // Исполнитель + tpe2 Frame + // Название альбома + talb Frame + // Год выпуска + tyer Frame + // Номер трека + trck Frame + // Жанр + tcon Frame + // Композитор (автор) + tcop Frame + // Обложка + apic Frame + // Комментарий + txxx Frame + // Затычка + unknown Frame + // Остальные фреймы + others []Frame + encoded_header []u8 + // Присутвует ли тэг + 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 - // Комментарий - txxx Frame - // Затычка - modified_frames []Frame - // Остальные фреймы - others []Frame - // Версия тэга - version u8 = 4 - // Присутвует ли тэг - embedded bool - // Закодированный хэдер - encoded_header []u8 - title string = "ID3" +pub mut: + // Размер тэга + size u64 + // Название аудиодорожки + tit2 Frame + // Автор + tpe1 Frame + // Исполнитель + tpe2 Frame + // Название альбома + talb Frame + // Год выпуска + tdrc Frame + // Номер трека + trck Frame + // Жанр + tcon Frame + // Композитор (автор) + tcop Frame + // Обложка + apic Frame + // Комментарий + txxx Frame + // Затычка + modified_frames []Frame + // Остальные фреймы + others []Frame + // Версия тэга + version u8 = 4 + // Присутвует ли тэг + embedded bool + // Закодированный хэдер + encoded_header []u8 + title string = 'ID3' } - pub struct Audio { - pub mut: - id3v1 ID3v1 - id3v23 ID3v23 - id3v24 ID3v24 - path string - extension string = ".mp3" +pub mut: + id3v1 ID3v1 + id3v23 ID3v23 + id3v24 ID3v24 + path string + extension string = '.mp3' } - diff --git a/metadata/utils.v b/metadata/utils.v index 6fd98ee..9544158 100644 --- a/metadata/utils.v +++ b/metadata/utils.v @@ -2,12 +2,11 @@ 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" { + if last_bytes[0..3].bytestr() == 'TAG' { audio.id3v1.embedded = true } // =============================== @@ -15,7 +14,7 @@ pub fn (mut audio Audio) detect_tags(mut data os.File) { // ? --- Проверка версии ID3v23.3 --- first_ten_bytes := get_bytes(mut data, 0, 4) - if first_ten_bytes[..3].bytestr() == "ID3" { + if first_ten_bytes[..3].bytestr() == 'ID3' { if first_ten_bytes[3] == 3 { audio.id3v23.embedded = true } else if first_ten_bytes[3] == 4 { @@ -38,7 +37,7 @@ pub fn get_bytes(mut audio os.File, position int, size int) []u8 { last_bytes := audio.read_bytes_at(size, u64(pos)) audio.seek(0, os.SeekMode.start) or { panic(err) } // ========================================================= - + return last_bytes } @@ -92,7 +91,7 @@ pub fn encode_header[T](mut id3v T) { id3v.encoded_header << [id3v.version] // Минорная версия и спец. флаги id3v.encoded_header << []u8{len: 2} - + id3v.encoded_header << metadata.encode_header_size(id3v.size) } @@ -110,7 +109,7 @@ pub fn encode_embed_tags[T](mut id3v2 T) { // Читаются только заполненные тэги if id3v2.$(field.name).cur_size > 0 { // Фрейма не было в файле и init не вызывался: имя берётся из поля структуры - if id3v2.$(field.name).name == "" && field.name.len == 4 { + if id3v2.$(field.name).name == '' && field.name.len == 4 { id3v2.$(field.name).init(field.name, id3v2.version) } @@ -140,8 +139,8 @@ pub fn encode_embed_tags[T](mut id3v2 T) { // Добавляет аудио из файла после нового тэга в 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") + if header.len < 10 || header[..3].bytestr() != 'ID3' { + return error('в файле нет тэга ID3v2') } // Размер старого тэга берётся из файла, id3v2.size уже хранит размер нового. @@ -155,7 +154,7 @@ pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! { audio.seek(0, .end)! file_size := u64(audio.tell()!) if audio_start > file_size { - return error("размер тэга (${audio_start}) больше размера файла (${file_size})") + return error('размер тэга (${audio_start}) больше размера файла (${file_size})') } // Аудио читается сразу в конец encoded_header, без промежуточной копии всего файла @@ -168,7 +167,7 @@ pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! { 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("файл закончился раньше ожидаемого") + return error('файл закончился раньше ожидаемого') } read += n } diff --git a/test/audiobook_test.v b/test/audiobook_test.v index 3c07a2f..adec371 100644 --- a/test/audiobook_test.v +++ b/test/audiobook_test.v @@ -98,10 +98,12 @@ fn test_read_skips_cover() { author: 'Сафон' series: 'Кладбище забытых книг' } - metadata.write_book_tags(path, tags, cover: metadata.Cover{ - mime: 'image/jpeg' - data: data - }) or { panic(err) } + metadata.write_book_tags(path, tags, + cover: metadata.Cover{ + mime: 'image/jpeg' + data: data + } + ) or { panic(err) } before := u64(gc_heap_usage().total_bytes) t := metadata.read_book_tags(path) or { panic(err) } allocated := u64(gc_heap_usage().total_bytes) - before diff --git a/test/id3v1_test.v b/test/id3v1_test.v index ca57858..37a90ca 100644 --- a/test/id3v1_test.v +++ b/test/id3v1_test.v @@ -3,7 +3,7 @@ import metadata // Проверка чтения id3v1 fn test_idv3v1() { - mut audio := os.open("./data/test_id3v1.mp3") or { panic(err) } + mut audio := os.open('./data/test_id3v1.mp3') or { panic(err) } defer { audio.close() } @@ -17,13 +17,12 @@ fn test_idv3v1() { 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.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 index d287dcd..90c955b 100644 --- a/test/id3v2_test.v +++ b/test/id3v2_test.v @@ -3,7 +3,7 @@ import metadata // Проверка чтения id3v23 fn test_idv3v23() { - mut audio := os.open("./data/test_id3v23.mp3") or { panic(err) } + mut audio := os.open('./data/test_id3v23.mp3') or { panic(err) } defer { audio.close() } @@ -16,29 +16,29 @@ fn test_idv3v23() { 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" + 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" + 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' } fn test_writeidv3v23() { - mut audio := os.open("./data/test_id3v23.mp3") or { panic(err) } + mut audio := os.open('./data/test_id3v23.mp3') or { panic(err) } defer { audio.close() } @@ -52,23 +52,23 @@ fn test_writeidv3v23() { wav.id3v23.fill_tags(mut audio) // Изменение значений тэга - wav.id3v23.tit2.set_data("Test Title2") - wav.id3v23.tpe1.set_data("Test Author2") - wav.id3v23.tpe2.set_data("Test Narator2") - wav.id3v23.talb.set_data("Test Album2") - wav.id3v23.tyer.set_data("2027") - wav.id3v23.trck.set_data("7/7") - wav.id3v23.tcon.set_data("Rock2") - wav.id3v23.tcop.set_data("Test2") - wav.id3v23.txxx.set_data("comment2") + wav.id3v23.tit2.set_data('Test Title2') + wav.id3v23.tpe1.set_data('Test Author2') + wav.id3v23.tpe2.set_data('Test Narator2') + wav.id3v23.talb.set_data('Test Album2') + wav.id3v23.tyer.set_data('2027') + wav.id3v23.trck.set_data('7/7') + wav.id3v23.tcon.set_data('Rock2') + wav.id3v23.tcop.set_data('Test2') + wav.id3v23.txxx.set_data('comment2') wav.id3v23.encode_embed_tags() wav.id3v23.insert_tags_into_audio(mut audio) or { panic(err) } - os.write_bytes("./data/temp_v23.mp3", wav.id3v23.encoded_header) or { panic(err) } + os.write_bytes('./data/temp_v23.mp3', wav.id3v23.encoded_header) or { panic(err) } // Чтение файла и проверка записи - mut temp_audio := os.open("./data/temp_v23.mp3") or { panic(err) } + mut temp_audio := os.open('./data/temp_v23.mp3') or { panic(err) } defer { temp_audio.close() } @@ -82,31 +82,31 @@ fn test_writeidv3v23() { temp_wav.id3v23.get_full_size(mut temp_audio) temp_wav.id3v23.fill_tags(mut temp_audio) - assert temp_wav.id3v23.tit2.data == "Test Title2" - assert temp_wav.id3v23.tpe1.data == "Test Author2" - assert temp_wav.id3v23.tpe2.data == "Test Narator2" - assert temp_wav.id3v23.talb.data == "Test Album2" - assert temp_wav.id3v23.tyer.data == "2027" - assert temp_wav.id3v23.trck.data == "7/7" - assert temp_wav.id3v23.tcon.data == "Rock2" - assert temp_wav.id3v23.tcop.data == "Test2" - assert temp_wav.id3v23.txxx.data == "comment2" + assert temp_wav.id3v23.tit2.data == 'Test Title2' + assert temp_wav.id3v23.tpe1.data == 'Test Author2' + assert temp_wav.id3v23.tpe2.data == 'Test Narator2' + assert temp_wav.id3v23.talb.data == 'Test Album2' + assert temp_wav.id3v23.tyer.data == '2027' + assert temp_wav.id3v23.trck.data == '7/7' + assert temp_wav.id3v23.tcon.data == 'Rock2' + assert temp_wav.id3v23.tcop.data == 'Test2' + assert temp_wav.id3v23.txxx.data == 'comment2' // Проверяется прочитанный с диска тэг, а не тот, что в памяти id3v1 := temp_wav.id3v23.to_v1() - assert id3v1.genre == "Rock2" - assert id3v1.title == "Test Title2" - assert id3v1.artist == "Test Narator2" - assert id3v1.album == "Test Album2" - assert id3v1.track == "7" - assert id3v1.year == "2027" - assert id3v1.comment == "comment2" + assert id3v1.genre == 'Rock2' + assert id3v1.title == 'Test Title2' + assert id3v1.artist == 'Test Narator2' + assert id3v1.album == 'Test Album2' + assert id3v1.track == '7' + assert id3v1.year == '2027' + assert id3v1.comment == 'comment2' } // Проверка чтения id3v24 fn test_idv3v24() { - mut audio := os.open("./data/test_id3v24.mp3") or { panic(err) } + mut audio := os.open('./data/test_id3v24.mp3') or { panic(err) } defer { audio.close() } @@ -119,30 +119,30 @@ fn test_idv3v24() { 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" + 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" + 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' } // Кириллица во фрейме с кодировкой ISO-8859-1 не должна превращаться в '?' fn test_write_unicode_into_latin1_frame() { - mut audio := os.open("./data/test_id3v23.mp3") or { panic(err) } + mut audio := os.open('./data/test_id3v23.mp3') or { panic(err) } defer { audio.close() } @@ -152,11 +152,11 @@ fn test_write_unicode_into_latin1_frame() { wav.id3v23.fill_tags(mut audio) old_size := int(wav.id3v23.size) - wav.id3v23.tit2.set_data("Глава 2") + wav.id3v23.tit2.set_data('Глава 2') wav.id3v23.encode_embed_tags() wav.id3v23.insert_tags_into_audio(mut audio) or { panic(err) } - temp_path := os.join_path(os.temp_dir(), "id3_editor_unicode_test.mp3") + temp_path := os.join_path(os.temp_dir(), 'id3_editor_unicode_test.mp3') os.write_bytes(temp_path, wav.id3v23.encoded_header) or { panic(err) } defer { os.rm(temp_path) or {} @@ -171,19 +171,19 @@ fn test_write_unicode_into_latin1_frame() { temp_wav.id3v23.get_full_size(mut temp_audio) temp_wav.id3v23.fill_tags(mut temp_audio) - assert temp_wav.id3v23.tit2.data == "Глава 2" + assert temp_wav.id3v23.tit2.data == 'Глава 2' // Нетронутые фреймы и аудио сохраняются без изменений - assert temp_wav.id3v23.tpe1.data == "Test Author" + assert temp_wav.id3v23.tpe1.data == 'Test Author' assert temp_wav.id3v23.others.len == 1 - original := os.read_bytes("./data/test_id3v23.mp3") or { panic(err) } + original := os.read_bytes('./data/test_id3v23.mp3') or { panic(err) } written := os.read_bytes(temp_path) or { panic(err) } assert written[int(temp_wav.id3v23.size) + 10..] == original[old_size + 10..] } // Вставка в файл без тэга ID3v2 возвращает ошибку, а не падает fn test_insert_without_id3v2() { - mut audio := os.open("./data/test_id3v1.mp3") or { panic(err) } + mut audio := os.open('./data/test_id3v1.mp3') or { panic(err) } defer { audio.close() } @@ -191,7 +191,7 @@ fn test_insert_without_id3v2() { wav.id3v23.encode_embed_tags() wav.id3v23.insert_tags_into_audio(mut audio) or { - assert err.msg() == "в файле нет тэга ID3v2" + assert err.msg() == 'в файле нет тэга ID3v2' return } assert false, 'Ошибка не возвращена' diff --git a/utils/code_detector.v b/utils/code_detector.v index d8e6ca2..ea31004 100644 --- a/utils/code_detector.v +++ b/utils/code_detector.v @@ -5,6 +5,6 @@ pub fn is_utf8(bytes []u8) bool { percent := f32(first_byte_amount) / f32(bytes.len) result := if percent >= 0.1 { true } else { false } - + return result }