Fix Invalid ID3TimeStamp error when writing date tags

Strip non-timestamp characters (BOM, invisible chars) from date/year values
both when reading existing tags in metadata_resolver and when writing in
executor. Also harden the EasyID3 except block to not wipe existing tags
when adding a missing ID3 header, and add per-field try/except in MP3 tag
writing so one bad field doesn't abort the entire track.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-04-28 22:06:50 +02:00
commit 460b92aab3
2 changed files with 23 additions and 5 deletions

View file

@ -73,7 +73,10 @@ def write_tags(path: Path, proposal: TrackProposal, album_proposal: AlbumProposa
if proposal.disc_number:
tags_to_write["discnumber"] = str(proposal.disc_number)
if album_proposal.date:
tags_to_write["date"] = album_proposal.date
# Strip everything except valid ID3 timestamp characters to prevent ID3TimeStamp errors
date_clean = re.sub(r"[^\d\-T:+Z]", "", str(album_proposal.date)).strip()
if date_clean:
tags_to_write["date"] = date_clean
if album_proposal.genre:
tags_to_write["genre"] = album_proposal.genre
if album_proposal.label:
@ -84,11 +87,22 @@ def write_tags(path: Path, proposal: TrackProposal, album_proposal: AlbumProposa
try:
audio = EasyID3(str(path))
except Exception:
audio = EasyID3()
audio.save(str(path))
# File has no ID3 header — add one without wiping audio data
from mutagen.id3 import ID3NoHeaderError
try:
from mutagen.mp3 import MP3
full = MP3(str(path))
full.tags = None
full.add_tags()
full.save(str(path), v2_version=4)
except Exception:
pass
audio = EasyID3(str(path))
for k, v in tags_to_write.items():
try:
audio[k] = [v]
except Exception as tag_err:
print(f" ⚠️ Tag-Feld '{k}' übersprungen ({path.name}): {tag_err}", file=sys.stderr)
audio.save(v2_version=4)
return True

View file

@ -337,8 +337,12 @@ def resolve(
album = album or Counter(tag_albums).most_common(1)[0][0]
# Tag year/genre/label
import re as _re
for t in hints.tracks:
year = year or t.existing_tags.get("date") or t.existing_tags.get("year")
raw_year = t.existing_tags.get("date") or t.existing_tags.get("year")
if raw_year and not year:
# Strip invisible chars so ID3TimeStamp validation doesn't fail later
year = _re.sub(r"[^\d\-T:+Z]", "", str(raw_year)).strip()[:10] or None
genre = genre or t.existing_tags.get("genre")
label = label or t.existing_tags.get("label") or t.existing_tags.get("organization")