fix: make id3 genre mapping robust and test the id3 plugin
This commit is contained in:
parent
3277e5b323
commit
675e069a51
5 changed files with 81 additions and 34 deletions
|
|
@ -26,7 +26,7 @@ So a as an example an audio-file could be assigned to project `+album` and have
|
|||
$ uv tool install 'orvanitag[all]'
|
||||
```
|
||||
|
||||
Requires Python 3.11 or newer. The core only needs rich. Reading and writing embedded metadata uses optional extras: `audio` (pytaglib, needs the TagLib C library), `pdf` (pypdf), `office` (olefile, only for legacy `.doc`/`.xls`/`.ppt`) and `markdown` (PyYAML). Plugins whose extra is not installed are skipped.
|
||||
Requires Python 3.11 or newer. The core only needs rich. Reading and writing embedded metadata uses optional extras: `audio` (pytaglib; its prebuilt wheels include TagLib, building from source needs the TagLib C library), `pdf` (pypdf), `office` (olefile, only for legacy `.doc`/`.xls`/`.ppt`) and `markdown` (PyYAML). Plugins whose extra is not installed are skipped.
|
||||
|
||||
## Command Line Tool
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +1,29 @@
|
|||
import re
|
||||
from pathlib import Path
|
||||
import taglib
|
||||
|
||||
NAME = 'id3'
|
||||
TYPES = ['.mp3', '.wav', '.flac', '.ogg']
|
||||
REGEX = r'\s*([@+]\w+|#\w+)'
|
||||
|
||||
def reader(o):
|
||||
universal = {i for i in o.schema if o.schema[i]==None}.pop()
|
||||
with taglib.File(o.file, save_on_exit=False) as song:
|
||||
metadata = song.tags
|
||||
id3map = {}
|
||||
if 'id3' in o.config:
|
||||
if 'map' in o.config['id3']:
|
||||
id3map = o.config['id3']['map'].copy()
|
||||
for context_key in id3map:
|
||||
if context_key not in o.schema.keys():
|
||||
del id3map[context_key]
|
||||
else:
|
||||
ctag = str(id3map[context_key]).upper()
|
||||
if ctag in metadata:
|
||||
id3map[context_key] = metadata[ctag]
|
||||
if 'COMMENT' in metadata:
|
||||
comment = metadata['COMMENT']
|
||||
argv = o.string(comment[0].strip())
|
||||
context = o.context(argv)
|
||||
if not universal in context:
|
||||
context[universal] = []
|
||||
else:
|
||||
context[universal] = [context[universal]]
|
||||
if id3map.keys():
|
||||
for key in id3map.keys():
|
||||
if key in context.keys():
|
||||
context[key] += id3map[key]
|
||||
context[universal] = '; '.join(context[universal])
|
||||
return context
|
||||
|
||||
|
||||
comment = metadata.get('COMMENT', [''])[0]
|
||||
context = o.context(o.string(comment))
|
||||
|
||||
# Audio tags mapped to categories by the folder config, e.g. `[id3.map] tags = "genre"`
|
||||
mapping = o.config.get('id3', {}).get('map', {})
|
||||
for context_key, tag in mapping.items():
|
||||
values = [value.strip() for value in metadata.get(str(tag).upper(), []) if value.strip()]
|
||||
if context_key not in o.schema or not values:
|
||||
continue
|
||||
if o.schema[context_key] is None: # the note
|
||||
context[context_key] = '; '.join(([context[context_key]] if context.get(context_key) else []) + values)
|
||||
else: # spaces would split the value into a tag and note words
|
||||
context[context_key] += ['-'.join(value.split()) for value in values]
|
||||
|
||||
if any(context.values()):
|
||||
return context
|
||||
|
||||
def writer(o):
|
||||
string = ' '.join(o.collect(o.context))
|
||||
with taglib.File(o.file, save_on_exit=True) as song:
|
||||
|
|
|
|||
|
|
@ -40,5 +40,5 @@ include = ["orvanitag*"]
|
|||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8",
|
||||
"orvanitag[pdf,office,markdown]",
|
||||
"orvanitag[audio,pdf,office,markdown]",
|
||||
]
|
||||
|
|
|
|||
59
tests/test_id3.py
Normal file
59
tests/test_id3.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import wave
|
||||
|
||||
import pytest
|
||||
|
||||
taglib = pytest.importorskip('taglib')
|
||||
|
||||
import orvanitag.orvani as orvani
|
||||
from orvanitag.orvani import OrvaniTag
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cwd(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
return tmp_path
|
||||
|
||||
|
||||
def make_wav(path, **tags):
|
||||
with wave.open(str(path), 'wb') as audio:
|
||||
audio.setnchannels(1)
|
||||
audio.setsampwidth(2)
|
||||
audio.setframerate(8000)
|
||||
audio.writeframes(b'\0\0' * 800)
|
||||
if tags:
|
||||
with taglib.File(path, save_on_exit=True) as song:
|
||||
for tag, value in tags.items():
|
||||
song.tags[tag.upper()] = [value]
|
||||
|
||||
|
||||
def scanned(cwd, config=None):
|
||||
if config:
|
||||
(cwd / orvani.CONFIG_FILE).write_text(config)
|
||||
OrvaniTag().scan('.', False)
|
||||
return (cwd / orvani.CONTEXT_FILE).read_text()
|
||||
|
||||
|
||||
def test_write_and_read_comment(cwd):
|
||||
make_wav(cwd / 'a.wav')
|
||||
o = OrvaniTag()
|
||||
o.add('a.wav', '+album', '@drums', '#lo-fi', 'first', 'take')
|
||||
assert o.write_metadata('a.wav', 'id3')[0] == 0
|
||||
with taglib.File(cwd / 'a.wav') as song:
|
||||
assert song.tags['COMMENT'] == ['+album @drums #lo-fi first take']
|
||||
(cwd / orvani.CONTEXT_FILE).unlink()
|
||||
assert scanned(cwd) == 'a.wav --- +album @drums #lo-fi first take\n'
|
||||
|
||||
|
||||
def test_map_genre_with_spaces_to_tags(cwd):
|
||||
make_wav(cwd / 'a.wav', comment='#lofi', genre='Lo Fi')
|
||||
assert scanned(cwd, '[id3.map]\ntags = "genre"\n') == 'a.wav --- #lofi #Lo-Fi\n'
|
||||
|
||||
|
||||
def test_map_works_without_comment(cwd):
|
||||
make_wav(cwd / 'a.wav', genre='Jazz', title='Night Walk')
|
||||
assert scanned(cwd, '[id3.map]\ntags = "genre"\nnote = "title"\n') == 'a.wav --- #Jazz Night Walk\n'
|
||||
|
||||
|
||||
def test_map_to_missing_tag_and_unknown_key_are_ignored(cwd):
|
||||
make_wav(cwd / 'a.wav', comment='#lofi')
|
||||
assert scanned(cwd, '[id3.map]\ncontexts = "albumartist"\nbogus = "genre"\n') == 'a.wav --- #lofi\n'
|
||||
4
uv.lock
generated
4
uv.lock
generated
|
|
@ -80,7 +80,7 @@ pdf = [
|
|||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "orvanitag", extra = ["markdown", "office", "pdf"] },
|
||||
{ name = "orvanitag", extra = ["audio", "markdown", "office", "pdf"] },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ provides-extras = ["audio", "pdf", "office", "markdown", "all"]
|
|||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "orvanitag", extras = ["pdf", "office", "markdown"] },
|
||||
{ name = "orvanitag", extras = ["audio", "pdf", "office", "markdown"] },
|
||||
{ name = "pytest", specifier = ">=8" },
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue