*добавлен API для аудиокниг (metadata/audiobook.v): TCOM, TIT1, COMM, TXXX, APIC, запись в файл без ID3v2, cp1251, потоковая перезапись
*публичные функции перехватывают панику (recover) и возвращают ошибку, проверка размера тэга *совместимость с V master: iconv больше не обрезает NUL, у Frame убран @[heap] (баг кодогенерации) *тесты test/audiobook_test.v Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
078e68efa8
commit
8a03ad3cd4
5 files changed
+791
-7
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import os
|
||||
import metadata
|
||||
|
||||
const data_dir = os.join_path(@VMODROOT, 'data')
|
||||
|
||||
fn tmp_copy(name string) string {
|
||||
dst := os.join_path(os.temp_dir(), 'id3_editor_${os.getpid()}_${name}')
|
||||
os.cp(os.join_path(data_dir, name), dst) or { panic(err) }
|
||||
return dst
|
||||
}
|
||||
|
||||
fn test_read_v23() {
|
||||
t := metadata.read_book_tags(os.join_path(data_dir, 'test_id3v23.mp3')) or { panic(err) }
|
||||
assert t.version == 3
|
||||
assert t.tags.title == 'Test Title'
|
||||
assert t.tags.author == 'Test Author'
|
||||
assert t.tags.album == 'Test Album'
|
||||
assert t.tags.year == '2026'
|
||||
assert t.tags.track == '6/7'
|
||||
}
|
||||
|
||||
fn test_read_v24() {
|
||||
t := metadata.read_book_tags(os.join_path(data_dir, 'test_id3v24.mp3')) or { panic(err) }
|
||||
assert t.version == 4
|
||||
assert t.tags.title == 'Test Title'
|
||||
assert t.tags.year == '2026'
|
||||
}
|
||||
|
||||
fn check_roundtrip(name string) {
|
||||
path := tmp_copy(name)
|
||||
defer {
|
||||
os.rm(path) or {}
|
||||
}
|
||||
before := metadata.read_book_tags(path) or { panic(err) }
|
||||
tags := metadata.BookTags{
|
||||
title: 'Глава третья'
|
||||
album: 'Тень ветра'
|
||||
author: 'Карлос Руис Сафон'
|
||||
narrator: 'Игорь Князев'
|
||||
series: 'Кладбище забытых книг'
|
||||
series_part: '1'
|
||||
year: '2004'
|
||||
track: '3/24'
|
||||
genre: 'Аудиокнига'
|
||||
comment: 'Даниель Семпере, сын букиниста…'
|
||||
}
|
||||
cover := metadata.Cover{
|
||||
mime: 'image/jpeg'
|
||||
data: [u8(0xFF), 0xD8, 0xFF, 0xE0, 1, 2, 3, 4]
|
||||
}
|
||||
metadata.write_book_tags(path, tags, cover: cover) or { panic(err) }
|
||||
after := metadata.read_book_tags(path) or { panic(err) }
|
||||
assert after.tags.title == tags.title
|
||||
assert after.tags.album == tags.album
|
||||
assert after.tags.author == tags.author
|
||||
assert after.tags.narrator == tags.narrator
|
||||
assert after.tags.series == tags.series
|
||||
assert after.tags.series_part == tags.series_part
|
||||
assert after.tags.year == tags.year
|
||||
assert after.tags.track == tags.track
|
||||
assert after.tags.genre == tags.genre
|
||||
assert after.tags.comment == tags.comment
|
||||
assert after.has_cover
|
||||
// Аудио после тэга не должно измениться
|
||||
assert after.audio_size == before.audio_size
|
||||
c := metadata.read_cover(path) or { panic(err) }
|
||||
assert c.data == cover.data
|
||||
|
||||
// Повторная запись без обложки сохраняет старую обложку
|
||||
metadata.write_book_tags(path, metadata.BookTags{ ...tags, title: 'Пролог' }) or { panic(err) }
|
||||
again := metadata.read_book_tags(path) or { panic(err) }
|
||||
assert again.tags.title == 'Пролог'
|
||||
assert again.has_cover
|
||||
assert again.audio_size == before.audio_size
|
||||
}
|
||||
|
||||
fn test_roundtrip_v23() {
|
||||
check_roundtrip('test_id3v23.mp3')
|
||||
}
|
||||
|
||||
fn test_roundtrip_v24() {
|
||||
check_roundtrip('test_id3v24.mp3')
|
||||
}
|
||||
|
||||
// В файл без ID3v2 тэг добавляется в начало
|
||||
fn test_write_without_id3v2() {
|
||||
path := tmp_copy('test_id3v1.mp3')
|
||||
defer {
|
||||
os.rm(path) or {}
|
||||
}
|
||||
size := os.file_size(path)
|
||||
metadata.write_book_tags(path, metadata.BookTags{ title: 'Глава 1', album: 'Книга' }) or {
|
||||
panic(err)
|
||||
}
|
||||
after := metadata.read_book_tags(path) or { panic(err) }
|
||||
assert after.version == 3
|
||||
assert after.tags.title == 'Глава 1'
|
||||
assert after.audio_size == size
|
||||
}
|
||||
|
||||
// Тэг, размер которого больше файла, — ошибка, а не паника
|
||||
fn test_broken_tag_is_error() {
|
||||
path := os.join_path(os.temp_dir(), 'id3_editor_${os.getpid()}_broken.mp3')
|
||||
os.write_file_array(path, [u8(`I`), `D`, `3`, 3, 0, 0, 0x7F, 0x7F, 0x7F, 0x7F, `T`, `I`]) or {
|
||||
panic(err)
|
||||
}
|
||||
defer {
|
||||
os.rm(path) or {}
|
||||
}
|
||||
metadata.read_book_tags(path) or {
|
||||
assert err.msg() != ''
|
||||
return
|
||||
}
|
||||
assert false, 'ошибка не возвращена'
|
||||
}
|
||||
Reference in new issue
Block a user