vfmt: весь код в формате V master (без изменений логики)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c9184aeb2
commit
6161a7faaa
10 files changed
+387
-269
No files matched your search
+33
-29
@@ -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
|
||||
@@ -45,15 +45,15 @@ fn (mut frame Frame) fill(bytes []u8, start_position u64, idv3_version u8) {
|
||||
|
||||
// Содержимое фрейма
|
||||
frame.encoding_name = match bytes[frame.start + 10] {
|
||||
0 { "ISO-8859-1" }
|
||||
1 { "UTF-16LE" }
|
||||
2 { "UTF-16BE" }
|
||||
else { "UTF-8" }
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+152
-30
@@ -152,45 +152,167 @@ enum Genre as u8 {
|
||||
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)]
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -4,11 +4,10 @@ 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
|
||||
@@ -22,10 +21,11 @@ pub fn (mut tag ID3v1) get_metadata(last_bytes []u8) {
|
||||
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
|
||||
|
||||
+6
-8
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)!
|
||||
|
||||
+90
-93
@@ -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'
|
||||
}
|
||||
|
||||
+7
-8
@@ -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 {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-9
@@ -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
|
||||
}
|
||||
|
||||
+70
-70
@@ -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, 'Ошибка не возвращена'
|
||||
|
||||
Reference in new issue
Block a user