vfmt: весь код в формате V master (без изменений логики)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
K1ndWha1eandClaude Opus 5.5 committed 2026-10-11 17:31:02 +03:00
1 parent 4c9184aeb2
commit 6161a7faaa
12 files changed
+405 -287

No files matched your search

+1 -1
View File
@@ -26,4 +26,4 @@ pub enum EnumID3v23 as u8 {
apic apic
txxx txxx
unknown unknown
} }
+43 -39
View File
@@ -4,23 +4,23 @@ import encoding.iconv
@[heap] @[heap]
pub struct Frame { pub struct Frame {
pub mut: pub mut:
data string data string
mut: mut:
prev_size u64 prev_size u64
cur_size u64 cur_size u64
start u64 start u64
end u64 end u64
encoding_name string encoding_name string
name string name string
encoding_name_idx u8 encoding_name_idx u8
addition []u8 addition []u8
encoded_data []u8 encoded_data []u8
apic ?[]u8 apic ?[]u8
encoded_size []u8 encoded_size []u8
modified bool modified bool
// Фрейм целиком в том виде, в каком он был прочитан из файла // Фрейм целиком в том виде, в каком он был прочитан из файла
raw []u8 raw []u8
} }
// Во фрейме первый байт указывает кодировку 0 - ISO-8859-1 (Latin-1), 1 - UTF-16LE // Во фрейме первый байт указывает кодировку 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.name = bytes[frame.start..frame.start + 4].bytestr()
// Размер фрейма // Размер фрейма
frame.cur_size = if idv3_version == 3 { frame.cur_size = if idv3_version == 3 {
get_size_v23(bytes[frame.start + 4..frame.start + 8]) get_size_v23(bytes[frame.start + 4..frame.start + 8])
} else { } 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.cur_size + 10 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.modified = false
// Содержимое фрейма // Содержимое фрейма
frame.encoding_name = match bytes[frame.start + 10] { frame.encoding_name = match bytes[frame.start + 10] {
0 { "ISO-8859-1" } 0 { 'ISO-8859-1' }
1 { "UTF-16LE" } 1 { 'UTF-16LE' }
2 { "UTF-16BE" } 2 { 'UTF-16BE' }
else { "UTF-8" } 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" { if frame.name.to_lower() == 'apic' {
// Дополнительные байты с информацией (MIME) // Дополнительные байты с информацией (MIME)
frame.addition = bytes[frame.start + 10..frame.start + 24] 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 байта маркера, по ним определяется порядок байт // UTF-16 с BOM (01): 2 байта маркера, по ним определяется порядок байт
if frame.encoding_name_idx == 1 && data_start + 2 <= frame.end { if frame.encoding_name_idx == 1 && data_start + 2 <= frame.end {
if bytes[data_start] == 0xFE && bytes[data_start + 1] == 0xFF { if bytes[data_start] == 0xFE && bytes[data_start + 1] == 0xFF {
frame.encoding_name = "UTF-16BE" frame.encoding_name = 'UTF-16BE'
} }
data_start += 2 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] 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) { fn (mut frame Frame) set_unicode_encoding(idv3_version u8) {
// В v2.3 нет UTF-8, там Юникод только UTF-16 с BOM // В v2.3 нет UTF-8, там Юникод только UTF-16 с BOM
if idv3_version == 3 { if idv3_version == 3 {
frame.encoding_name = "UTF-16LE" frame.encoding_name = 'UTF-16LE'
frame.encoding_name_idx = 1 frame.encoding_name_idx = 1
} else { } else {
frame.encoding_name = "UTF-8" frame.encoding_name = 'UTF-8'
frame.encoding_name_idx = 3 frame.encoding_name_idx = 3
} }
} }
@@ -97,10 +97,10 @@ fn (mut frame Frame) set_unicode_encoding(idv3_version u8) {
// Кодируем длину фрейма с нужным сдвигом // Кодируем длину фрейма с нужным сдвигом
@[inline] @[inline]
pub fn (mut frame Frame) encode_size(idv3_version u8) { pub fn (mut frame Frame) encode_size(idv3_version u8) {
stride := if idv3_version == 3 { stride := if idv3_version == 3 {
[u64(24), u64(16), u64(8), u64(0xFF)] [u64(24), u64(16), u64(8), u64(0xFF)]
} else { } else {
[u64(21), u64(14), u64(7), u64(0x7F)] [u64(21), u64(14), u64(7), u64(0x7F)]
} }
mut encoded_size := []u8{len: 4} 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) frame.set_unicode_encoding(idv3_version)
iconv.vstring_to_encoding(frame.data, frame.encoding_name) or { iconv.vstring_to_encoding(frame.data, frame.encoding_name) or {
panic("не удалось закодировать фрейм ${frame.name}: ${err}") panic('не удалось закодировать фрейм ${frame.name}: ${err}')
} }
} }
// Для UTF-16 с BOM (01) перед текстом нужен маркер порядка байт // Для UTF-16 с BOM (01) перед текстом нужен маркер порядка байт
frame.encoded_data = if frame.encoding_name_idx == 1 { 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 << encoded_data
bom bom
} else { } 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 { pub fn (mut frame Frame) to_bytes(idv3_version u8) []u8 {
// Картинка и нетронутые фреймы пишутся как были прочитаны, // Картинка и нетронутые фреймы пишутся как были прочитаны,
// чтобы не потерять то, что не разбирается (язык в COMM, значение в TXXX, бинарные фреймы) // чтобы не потерять то, что не разбирается (язык в 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 return frame.raw
} }
@@ -195,4 +199,4 @@ pub fn (mut frame Frame) set_data(data string) {
frame.data = data frame.data = data
frame.modified = true frame.modified = true
} }
+154 -32
View File
@@ -151,47 +151,169 @@ enum Genre as u8 {
anime = 145 anime = 145
jpop = 146 jpop = 146
synthpop = 147 synthpop = 147
unknown = 255 unknown = 255
} }
const genres_name = [ const genres_name = [
'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Blues',
'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', 'Classic Rock',
'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', 'Alternative', 'Ska', 'Country',
'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', 'Ambient', 'Dance',
'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Disco',
'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Funk',
'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Grunge',
'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave', 'Hip-Hop',
'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', 'Jazz',
'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 'Christian Rap', 'Metal',
'Pop/Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', 'New Age',
'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 'Oldies',
'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', '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', 'Hard Rock',
// Winamp // Winamp
'Folk', 'Folk-Rock', 'National Folk', 'Swing', 'Fast Fusion', 'Bebob', 'Folk',
'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', 'Folk-Rock',
'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', 'National Folk',
'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Swing',
'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Fast Fusion',
'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', 'Bebob',
'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet', 'Latin',
'Punk Ballad', 'Drum Solo', 'A Cappella', 'Euro-House', 'Dance Hall', 'Revival',
'Goa', 'Drum & Bass', 'Club-House', 'Hardcore', 'Terror', 'Indie', 'Celtic',
'BritPop', 'Negerpunk', 'Polsk Punk', 'Beat', 'Christian Gangsta', 'Bluegrass',
'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary Christian', 'Avantgarde',
'Christian Rock', 'Merengue', 'Salsa', 'Thrash Metal', 'Anime', 'JPop', 'Gothic Rock',
'Synthpop' '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 { match genre {
.unknown { .unknown {
return "Unknown" return 'Unknown'
} else { }
else {
return genres_name[u8(genre)] return genres_name[u8(genre)]
} }
} }
} }
+17 -17
View File
@@ -4,28 +4,28 @@ import os { File }
import encoding.iconv import encoding.iconv
import utils import utils
// Определить кодировку utf-8 или cp-1251 // Определить кодировку utf-8 или cp-1251
@[inline] @[inline]
fn decode_bytes(bytes []u8) string { 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') text := (iconv.encoding_to_vstring(bytes, format) or { "Can't decode" }).all_before('\x00')
return text return text
} }
// Извлечение метаданных из ID3 v1 // Извлечение метаданных из ID3 v1
pub fn (mut tag ID3v1) get_metadata(last_bytes []u8) { pub fn (mut tag ID3v1) get_metadata(last_bytes []u8) {
tag.title = decode_bytes(last_bytes[3..33]) tag.title = decode_bytes(last_bytes[3..33])
tag.artist = decode_bytes(last_bytes[33..63]) tag.artist = decode_bytes(last_bytes[33..63])
tag.album = decode_bytes(last_bytes[63..92]) tag.album = decode_bytes(last_bytes[63..92])
tag.year = decode_bytes(last_bytes[93..97]) tag.year = decode_bytes(last_bytes[93..97])
tag.comment = decode_bytes(last_bytes[97..125]) 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 } genre := Genre.from(last_bytes[127..128][0]) or { Genre.unknown }
tag.genre = genre.str()} tag.genre = genre.str()
}
// Конвертировать ID3 v2.3 в ID3 v1 // Конвертировать ID3 v2.3 в ID3 v1
pub fn (id3v1 ID3v1) to_v23() ID3v23 { 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} mut final_data := []u8{len: capacity, init: 0}
if data.len > 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 diff := capacity - bytes_data.len
if diff > 0 { if diff > 0 {
@@ -79,7 +79,7 @@ fn cut_or_trim(data string, capacity i8) []u8 {
pub fn (mut id3v1 ID3v1) to_bytes() []u8 { pub fn (mut id3v1 ID3v1) to_bytes() []u8 {
mut bytes := []u8{cap: 128} mut bytes := []u8{cap: 128}
bytes << "TAG".bytes() bytes << 'TAG'.bytes()
bytes << cut_or_trim(id3v1.title, i8(30)) bytes << cut_or_trim(id3v1.title, i8(30))
bytes << cut_or_trim(id3v1.artist, i8(30)) bytes << cut_or_trim(id3v1.artist, i8(30))
bytes << cut_or_trim(id3v1.album, 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.comment, i8(28))
// bytes << cut_or_trim(id3v1.track, i8(2)) // 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 } id_genre := Genre.from(id3v1.genre.to_lower()) or { Genre.unknown }
bytes << u8(id_genre) // cut_or_trim([u8(id_genre)], i8(2)) 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 { pub fn (mut id3v1 ID3v1) insert(mut audio File, position i8) []u8 {
// if !id3v1.embedded { // if !id3v1.embedded {
bytes_tag := id3v1.to_bytes() bytes_tag := id3v1.to_bytes()
// Уcтановка указателя на последний байт // Уcтановка указателя на последний байт
audio.seek(0, os.SeekMode.end) or { panic(err) } audio.seek(0, os.SeekMode.end) or { panic(err) }
// Положение указателя // Положение указателя
size := audio.tell() or { panic(err) } size := audio.tell() or { panic(err) }
// Чтение всех байта, кроме последних 128 // Чтение всех байта, кроме последних 128
mut all_bytes := audio.read_bytes(int(size))#[..position].clone() mut all_bytes := audio.read_bytes(int(size))#[..position].clone()
all_bytes << bytes_tag all_bytes << bytes_tag
// } // }
return all_bytes return all_bytes
+6 -8
View File
@@ -27,7 +27,6 @@ import os { File }
const start_idx = 10 const start_idx = 10
pub fn (mut tag ID3v23) add_frame(frame Frame) { pub fn (mut tag ID3v23) add_frame(frame Frame) {
name := EnumID3v23.from(frame.name.to_lower()) or { EnumID3v23.unknown } 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) encode_embed_tags[ID3v23](mut id3v23)
} }
// Заполнение тэга // Заполнение тэга
pub fn (mut id3v23 ID3v23) fill_tags(mut audio File) { pub fn (mut id3v23 ID3v23) fill_tags(mut audio File) {
dump_data[ID3v23](mut id3v23, mut audio) 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 // Конвертировать ID3 v2.3 в ID3 v1
pub fn (id3v23 ID3v23) to_v1() ID3v1 { pub fn (id3v23 ID3v23) to_v1() ID3v1 {
return ID3v1{ return ID3v1{
genre: id3v23.tcon.data genre: id3v23.tcon.data
title: id3v23.tit2.data title: id3v23.tit2.data
artist: id3v23.tpe2.data artist: id3v23.tpe2.data
album: id3v23.talb.data album: id3v23.talb.data
track: id3v23.trck.data.split("/")[0] track: id3v23.trck.data.split('/')[0]
year: id3v23.tyer.data year: id3v23.tyer.data
comment: id3v23.txxx.data comment: id3v23.txxx.data
} }
} }
-3
View File
@@ -2,7 +2,6 @@ module metadata
import os { File } import os { File }
pub fn (mut tag ID3v24) add_frame(frame Frame) { pub fn (mut tag ID3v24) add_frame(frame Frame) {
name := EnumID3v24.from(frame.name.to_lower()) or { EnumID3v24.unknown } name := EnumID3v24.from(frame.name.to_lower()) or { EnumID3v24.unknown }
@@ -43,7 +42,6 @@ pub fn (mut tag ID3v24) add_frame(frame Frame) {
} }
} }
@[inline] @[inline]
fn get_size_v24(size_bytes []u8) u64 { 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]) 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) ! { pub fn (mut id3v24 ID3v24) insert_tags_into_audio(mut audio File) ! {
insert_tags_into_audio[ID3v24](mut id3v24, mut audio)! insert_tags_into_audio[ID3v24](mut id3v24, mut audio)!
+90 -93
View File
@@ -1,109 +1,106 @@
module metadata module metadata
@[heap; minify] @[heap; minify]
pub struct ID3v1 { pub struct ID3v1 {
pub mut: pub mut:
// Жанр // Жанр
genre string genre string
// Название аудиодорожки // Название аудиодорожки
title string title string
// Актёр озучания // Актёр озучания
artist string artist string
// Название альбома // Название альбома
album string album string
// Записывается как 2/5, что значит 2 трэк из 5 // Записывается как 2/5, что значит 2 трэк из 5
track string track string
// Год записи, может int нужно // Год записи, может int нужно
year string year string
// Комментарий // Комментарий
comment string comment string
// Присутвует ли тэг // Присутвует ли тэг
embedded bool embedded bool
} }
@[heap; minify] @[heap; minify]
pub struct ID3v23 { pub struct ID3v23 {
pub mut: pub mut:
// Размер тэга // Размер тэга
size u64 size u64
// Название аудиодорожки // Название аудиодорожки
tit2 Frame tit2 Frame
// Автор // Автор
tpe1 Frame tpe1 Frame
// Исполнитель // Исполнитель
tpe2 Frame tpe2 Frame
// Название альбома // Название альбома
talb Frame talb Frame
// Год выпуска // Год выпуска
tyer Frame tyer Frame
// Номер трека // Номер трека
trck Frame trck Frame
// Жанр // Жанр
tcon Frame tcon Frame
// Композитор (автор) // Композитор (автор)
tcop Frame tcop Frame
// Обложка // Обложка
apic Frame apic Frame
// Комментарий // Комментарий
txxx Frame txxx Frame
// Затычка // Затычка
unknown Frame unknown Frame
// Остальные фреймы // Остальные фреймы
others []Frame others []Frame
encoded_header []u8 encoded_header []u8
// Присутвует ли тэг // Присутвует ли тэг
embedded bool embedded bool
// Версия тэга // Версия тэга
version u8 = 3 version u8 = 3
title string = "ID3" title string = 'ID3'
} }
@[heap; minify] @[heap; minify]
pub struct ID3v24 { pub struct ID3v24 {
pub mut: pub mut:
// Размер тэга // Размер тэга
size u64 size u64
// Название аудиодорожки // Название аудиодорожки
tit2 Frame tit2 Frame
// Автор // Автор
tpe1 Frame tpe1 Frame
// Исполнитель // Исполнитель
tpe2 Frame tpe2 Frame
// Название альбома // Название альбома
talb Frame talb Frame
// Год выпуска // Год выпуска
tdrc Frame tdrc Frame
// Номер трека // Номер трека
trck Frame trck Frame
// Жанр // Жанр
tcon Frame tcon Frame
// Композитор (автор) // Композитор (автор)
tcop Frame tcop Frame
// Обложка // Обложка
apic Frame apic Frame
// Комментарий // Комментарий
txxx Frame txxx Frame
// Затычка // Затычка
modified_frames []Frame modified_frames []Frame
// Остальные фреймы // Остальные фреймы
others []Frame others []Frame
// Версия тэга // Версия тэга
version u8 = 4 version u8 = 4
// Присутвует ли тэг // Присутвует ли тэг
embedded bool embedded bool
// Закодированный хэдер // Закодированный хэдер
encoded_header []u8 encoded_header []u8
title string = "ID3" title string = 'ID3'
} }
pub struct Audio { pub struct Audio {
pub mut: pub mut:
id3v1 ID3v1 id3v1 ID3v1
id3v23 ID3v23 id3v23 ID3v23
id3v24 ID3v24 id3v24 ID3v24
path string path string
extension string = ".mp3" extension string = '.mp3'
} }
+9 -10
View File
@@ -2,12 +2,11 @@ module metadata
import os import os
pub fn (mut audio Audio) detect_tags(mut data os.File) { pub fn (mut audio Audio) detect_tags(mut data os.File) {
// ? --- Проверка версии ID3v1 --- // ? --- Проверка версии ID3v1 ---
last_bytes := get_bytes(mut data, -128, 3) 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 audio.id3v1.embedded = true
} }
// =============================== // ===============================
@@ -15,7 +14,7 @@ pub fn (mut audio Audio) detect_tags(mut data os.File) {
// ? --- Проверка версии ID3v23.3 --- // ? --- Проверка версии ID3v23.3 ---
first_ten_bytes := get_bytes(mut data, 0, 4) 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 { if first_ten_bytes[3] == 3 {
audio.id3v23.embedded = true audio.id3v23.embedded = true
} else if first_ten_bytes[3] == 4 { } 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)) last_bytes := audio.read_bytes_at(size, u64(pos))
audio.seek(0, os.SeekMode.start) or { panic(err) } audio.seek(0, os.SeekMode.start) or { panic(err) }
// ========================================================= // =========================================================
return last_bytes return last_bytes
} }
@@ -92,7 +91,7 @@ pub fn encode_header[T](mut id3v T) {
id3v.encoded_header << [id3v.version] id3v.encoded_header << [id3v.version]
// Минорная версия и спец. флаги // Минорная версия и спец. флаги
id3v.encoded_header << []u8{len: 2} id3v.encoded_header << []u8{len: 2}
id3v.encoded_header << metadata.encode_header_size(id3v.size) 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 { if id3v2.$(field.name).cur_size > 0 {
// Фрейма не было в файле и init не вызывался: имя берётся из поля структуры // Фрейма не было в файле и 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) id3v2.$(field.name).init(field.name, id3v2.version)
} }
@@ -140,8 +139,8 @@ pub fn encode_embed_tags[T](mut id3v2 T) {
// Добавляет аудио из файла после нового тэга в encoded_header, старый тэг пропускается // Добавляет аудио из файла после нового тэга в encoded_header, старый тэг пропускается
pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! { pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! {
header := get_bytes(mut audio, 0, 10) header := get_bytes(mut audio, 0, 10)
if header.len < 10 || header[..3].bytestr() != "ID3" { if header.len < 10 || header[..3].bytestr() != 'ID3' {
return error("в файле нет тэга ID3v2") return error('в файле нет тэга ID3v2')
} }
// Размер старого тэга берётся из файла, id3v2.size уже хранит размер нового. // Размер старого тэга берётся из файла, id3v2.size уже хранит размер нового.
@@ -155,7 +154,7 @@ pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! {
audio.seek(0, .end)! audio.seek(0, .end)!
file_size := u64(audio.tell()!) file_size := u64(audio.tell()!)
if audio_start > file_size { if audio_start > file_size {
return error("размер тэга (${audio_start}) больше размера файла (${file_size})") return error('размер тэга (${audio_start}) больше размера файла (${file_size})')
} }
// Аудио читается сразу в конец encoded_header, без промежуточной копии всего файла // Аудио читается сразу в конец encoded_header, без промежуточной копии всего файла
@@ -168,7 +167,7 @@ pub fn insert_tags_into_audio[T](mut id3v2 T, mut audio os.File) ! {
for read < sound_len { for read < sound_len {
n := unsafe { audio.read_into_ptr(&u8(id3v2.encoded_header.data) + header_len + read, sound_len - read)! } n := unsafe { audio.read_into_ptr(&u8(id3v2.encoded_header.data) + header_len + read, sound_len - read)! }
if n == 0 { if n == 0 {
return error("файл закончился раньше ожидаемого") return error('файл закончился раньше ожидаемого')
} }
read += n read += n
} }
+6 -4
View File
@@ -98,10 +98,12 @@ fn test_read_skips_cover() {
author: 'Сафон' author: 'Сафон'
series: 'Кладбище забытых книг' series: 'Кладбище забытых книг'
} }
metadata.write_book_tags(path, tags, cover: metadata.Cover{ metadata.write_book_tags(path, tags,
mime: 'image/jpeg' cover: metadata.Cover{
data: data mime: 'image/jpeg'
}) or { panic(err) } data: data
}
) or { panic(err) }
before := u64(gc_heap_usage().total_bytes) before := u64(gc_heap_usage().total_bytes)
t := metadata.read_book_tags(path) or { panic(err) } t := metadata.read_book_tags(path) or { panic(err) }
allocated := u64(gc_heap_usage().total_bytes) - before allocated := u64(gc_heap_usage().total_bytes) - before
+8 -9
View File
@@ -3,7 +3,7 @@ import metadata
// Проверка чтения id3v1 // Проверка чтения id3v1
fn test_idv3v1() { 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 { defer {
audio.close() audio.close()
} }
@@ -17,13 +17,12 @@ fn test_idv3v1() {
last_bytes := metadata.get_bytes(mut audio, -128, 128) last_bytes := metadata.get_bytes(mut audio, -128, 128)
wav.id3v1.get_metadata(last_bytes) wav.id3v1.get_metadata(last_bytes)
assert wav.id3v1.genre == "Rock" assert wav.id3v1.genre == 'Rock'
assert wav.id3v1.title == "Test Title" assert wav.id3v1.title == 'Test Title'
assert wav.id3v1.artist == "Test Artist" assert wav.id3v1.artist == 'Test Artist'
assert wav.id3v1.album == "Test Album" assert wav.id3v1.album == 'Test Album'
assert wav.id3v1.track == "6" assert wav.id3v1.track == '6'
assert wav.id3v1.year == "2026" assert wav.id3v1.year == '2026'
assert wav.id3v1.comment == "Test comment" assert wav.id3v1.comment == 'Test comment'
assert wav.id3v1.embedded == true assert wav.id3v1.embedded == true
} }
+70 -70
View File
@@ -3,7 +3,7 @@ import metadata
// Проверка чтения id3v23 // Проверка чтения id3v23
fn test_idv3v23() { 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 { defer {
audio.close() audio.close()
} }
@@ -16,29 +16,29 @@ fn test_idv3v23() {
wav.id3v23.get_full_size(mut audio) wav.id3v23.get_full_size(mut audio)
wav.id3v23.fill_tags(mut audio) wav.id3v23.fill_tags(mut audio)
assert wav.id3v23.tit2.data == "Test Title" assert wav.id3v23.tit2.data == 'Test Title'
assert wav.id3v23.tpe1.data == "Test Author" assert wav.id3v23.tpe1.data == 'Test Author'
assert wav.id3v23.tpe2.data == "Test Narator" assert wav.id3v23.tpe2.data == 'Test Narator'
assert wav.id3v23.talb.data == "Test Album" assert wav.id3v23.talb.data == 'Test Album'
assert wav.id3v23.tyer.data == "2026" assert wav.id3v23.tyer.data == '2026'
assert wav.id3v23.trck.data == "6/7" assert wav.id3v23.trck.data == '6/7'
assert wav.id3v23.tcon.data == "Rock" assert wav.id3v23.tcon.data == 'Rock'
assert wav.id3v23.tcop.data == "Test" assert wav.id3v23.tcop.data == 'Test'
assert wav.id3v23.txxx.data == "comment" assert wav.id3v23.txxx.data == 'comment'
id3v1 := wav.id3v23.to_v1() id3v1 := wav.id3v23.to_v1()
assert id3v1.genre == "Rock" assert id3v1.genre == 'Rock'
assert id3v1.title == "Test Title" assert id3v1.title == 'Test Title'
assert id3v1.artist == "Test Narator" assert id3v1.artist == 'Test Narator'
assert id3v1.album == "Test Album" assert id3v1.album == 'Test Album'
assert id3v1.track == "6" assert id3v1.track == '6'
assert id3v1.year == "2026" assert id3v1.year == '2026'
assert id3v1.comment == "comment" assert id3v1.comment == 'comment'
} }
fn test_writeidv3v23() { 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 { defer {
audio.close() audio.close()
} }
@@ -52,23 +52,23 @@ fn test_writeidv3v23() {
wav.id3v23.fill_tags(mut audio) wav.id3v23.fill_tags(mut audio)
// Изменение значений тэга // Изменение значений тэга
wav.id3v23.tit2.set_data("Test Title2") wav.id3v23.tit2.set_data('Test Title2')
wav.id3v23.tpe1.set_data("Test Author2") wav.id3v23.tpe1.set_data('Test Author2')
wav.id3v23.tpe2.set_data("Test Narator2") wav.id3v23.tpe2.set_data('Test Narator2')
wav.id3v23.talb.set_data("Test Album2") wav.id3v23.talb.set_data('Test Album2')
wav.id3v23.tyer.set_data("2027") wav.id3v23.tyer.set_data('2027')
wav.id3v23.trck.set_data("7/7") wav.id3v23.trck.set_data('7/7')
wav.id3v23.tcon.set_data("Rock2") wav.id3v23.tcon.set_data('Rock2')
wav.id3v23.tcop.set_data("Test2") wav.id3v23.tcop.set_data('Test2')
wav.id3v23.txxx.set_data("comment2") wav.id3v23.txxx.set_data('comment2')
wav.id3v23.encode_embed_tags() wav.id3v23.encode_embed_tags()
wav.id3v23.insert_tags_into_audio(mut audio) or { panic(err) } 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 { defer {
temp_audio.close() temp_audio.close()
} }
@@ -82,31 +82,31 @@ fn test_writeidv3v23() {
temp_wav.id3v23.get_full_size(mut temp_audio) temp_wav.id3v23.get_full_size(mut temp_audio)
temp_wav.id3v23.fill_tags(mut temp_audio) temp_wav.id3v23.fill_tags(mut temp_audio)
assert temp_wav.id3v23.tit2.data == "Test Title2" assert temp_wav.id3v23.tit2.data == 'Test Title2'
assert temp_wav.id3v23.tpe1.data == "Test Author2" assert temp_wav.id3v23.tpe1.data == 'Test Author2'
assert temp_wav.id3v23.tpe2.data == "Test Narator2" assert temp_wav.id3v23.tpe2.data == 'Test Narator2'
assert temp_wav.id3v23.talb.data == "Test Album2" assert temp_wav.id3v23.talb.data == 'Test Album2'
assert temp_wav.id3v23.tyer.data == "2027" assert temp_wav.id3v23.tyer.data == '2027'
assert temp_wav.id3v23.trck.data == "7/7" assert temp_wav.id3v23.trck.data == '7/7'
assert temp_wav.id3v23.tcon.data == "Rock2" assert temp_wav.id3v23.tcon.data == 'Rock2'
assert temp_wav.id3v23.tcop.data == "Test2" assert temp_wav.id3v23.tcop.data == 'Test2'
assert temp_wav.id3v23.txxx.data == "comment2" assert temp_wav.id3v23.txxx.data == 'comment2'
// Проверяется прочитанный с диска тэг, а не тот, что в памяти // Проверяется прочитанный с диска тэг, а не тот, что в памяти
id3v1 := temp_wav.id3v23.to_v1() id3v1 := temp_wav.id3v23.to_v1()
assert id3v1.genre == "Rock2" assert id3v1.genre == 'Rock2'
assert id3v1.title == "Test Title2" assert id3v1.title == 'Test Title2'
assert id3v1.artist == "Test Narator2" assert id3v1.artist == 'Test Narator2'
assert id3v1.album == "Test Album2" assert id3v1.album == 'Test Album2'
assert id3v1.track == "7" assert id3v1.track == '7'
assert id3v1.year == "2027" assert id3v1.year == '2027'
assert id3v1.comment == "comment2" assert id3v1.comment == 'comment2'
} }
// Проверка чтения id3v24 // Проверка чтения id3v24
fn test_idv3v24() { 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 { defer {
audio.close() audio.close()
} }
@@ -119,30 +119,30 @@ fn test_idv3v24() {
wav.id3v24.get_full_size(mut audio) wav.id3v24.get_full_size(mut audio)
wav.id3v24.fill_tags(mut audio) wav.id3v24.fill_tags(mut audio)
assert wav.id3v24.tit2.data == "Test Title" assert wav.id3v24.tit2.data == 'Test Title'
assert wav.id3v24.tpe1.data == "Test Author" assert wav.id3v24.tpe1.data == 'Test Author'
assert wav.id3v24.tpe2.data == "Test Narator" assert wav.id3v24.tpe2.data == 'Test Narator'
assert wav.id3v24.talb.data == "Test Album" assert wav.id3v24.talb.data == 'Test Album'
assert wav.id3v24.tdrc.data == "2026" assert wav.id3v24.tdrc.data == '2026'
assert wav.id3v24.trck.data == "6/7" assert wav.id3v24.trck.data == '6/7'
assert wav.id3v24.tcon.data == "Rock" assert wav.id3v24.tcon.data == 'Rock'
assert wav.id3v24.tcop.data == "Test" assert wav.id3v24.tcop.data == 'Test'
assert wav.id3v24.txxx.data == "comment" assert wav.id3v24.txxx.data == 'comment'
id3v1 := wav.id3v24.to_v1() id3v1 := wav.id3v24.to_v1()
assert id3v1.genre == "Rock" assert id3v1.genre == 'Rock'
assert id3v1.title == "Test Title" assert id3v1.title == 'Test Title'
assert id3v1.artist == "Test Narator" assert id3v1.artist == 'Test Narator'
assert id3v1.album == "Test Album" assert id3v1.album == 'Test Album'
assert id3v1.track == "6" assert id3v1.track == '6'
assert id3v1.year == "2026" assert id3v1.year == '2026'
assert id3v1.comment == "comment" assert id3v1.comment == 'comment'
} }
// Кириллица во фрейме с кодировкой ISO-8859-1 не должна превращаться в '?' // Кириллица во фрейме с кодировкой ISO-8859-1 не должна превращаться в '?'
fn test_write_unicode_into_latin1_frame() { 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 { defer {
audio.close() audio.close()
} }
@@ -152,11 +152,11 @@ fn test_write_unicode_into_latin1_frame() {
wav.id3v23.fill_tags(mut audio) wav.id3v23.fill_tags(mut audio)
old_size := int(wav.id3v23.size) 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.encode_embed_tags()
wav.id3v23.insert_tags_into_audio(mut audio) or { panic(err) } 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) } os.write_bytes(temp_path, wav.id3v23.encoded_header) or { panic(err) }
defer { defer {
os.rm(temp_path) or {} 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.get_full_size(mut temp_audio)
temp_wav.id3v23.fill_tags(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 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) } written := os.read_bytes(temp_path) or { panic(err) }
assert written[int(temp_wav.id3v23.size) + 10..] == original[old_size + 10..] assert written[int(temp_wav.id3v23.size) + 10..] == original[old_size + 10..]
} }
// Вставка в файл без тэга ID3v2 возвращает ошибку, а не падает // Вставка в файл без тэга ID3v2 возвращает ошибку, а не падает
fn test_insert_without_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 { defer {
audio.close() audio.close()
} }
@@ -191,7 +191,7 @@ fn test_insert_without_id3v2() {
wav.id3v23.encode_embed_tags() wav.id3v23.encode_embed_tags()
wav.id3v23.insert_tags_into_audio(mut audio) or { wav.id3v23.insert_tags_into_audio(mut audio) or {
assert err.msg() == "в файле нет тэга ID3v2" assert err.msg() == 'в файле нет тэга ID3v2'
return return
} }
assert false, 'Ошибка не возвращена' assert false, 'Ошибка не возвращена'
+1 -1
View File
@@ -5,6 +5,6 @@ pub fn is_utf8(bytes []u8) bool {
percent := f32(first_byte_amount) / f32(bytes.len) percent := f32(first_byte_amount) / f32(bytes.len)
result := if percent >= 0.1 { true } else { false } result := if percent >= 0.1 { true } else { false }
return result return result
} }