Tag writer: stop downgrading full release dates to just the year (#824)

Files tagged with a full date (e.g. 2023-11-03) were overwritten with only the
year on every enrichment/retag: the tag reader pulls the full TDRC/date string,
but the library DB stores `year` as an INTEGER, and the writer wrote str(year)
back unconditionally — truncating 2023-11-03 → 2023 (Tacobell444 #824).

Fix: new _date_to_write(existing, year) keeps the file's existing, more-specific
date when its year already matches what we'd write (the full date is correct and
consistent); it only writes the bare year when the file has no date or the year
genuinely differs (a real correction). Wired into the ID3 (TDRC), Vorbis (date)
and MP4 (©day) writers. build_tag_diff no longer flags the year as "changed"
when the years match, so the preview stops showing a phantom 2023-11-03 → 2023.

Tests: diff doesn't flag same-year full dates (but does flag a different year);
end-to-end write preserves 2023-11-03 against DB year 2023, and still corrects a
wrong year. 339 tag/retag tests pass.

NOTE (not fixed here): manually entering a NEW full date still can't persist —
the library albums/tracks tables store `year` as INT, so a yyyy-mm-dd entry is
truncated at the DB layer. Supporting manual full dates + enrichment fetching
full dates needs a release_date column (follow-up).
This commit is contained in:
BoulderBadgeDad 2026-06-08 23:15:31 -07:00
parent e6bf7c26de
commit 319e90dead
2 changed files with 61 additions and 3 deletions

View file

@ -214,6 +214,12 @@ def build_tag_diff(file_tags: Dict[str, Any], db_data: Dict[str, Any]) -> List[D
if db_key == 'year' and db_val is not None:
db_str = str(db_val)
db_val = str(db_val)
# Don't flag a full file date (2023-11-03) → year (2023) as a change
# when the years already match: the writer preserves the full date,
# so it isn't actually changing (#824). Only a different year is a
# real change.
if file_str and file_str[:4] == db_str:
file_str = db_str
# Only mark as changed if DB has a value AND it differs from file
# (writer skips fields where DB value is empty, so don't show them as diffs)
@ -442,6 +448,20 @@ def _multi_artist_write_enabled() -> bool:
return False
def _date_to_write(existing: Optional[str], year) -> str:
"""Value to write for the date/year tag. Writes the DB year, BUT keeps an
existing, MORE-specific file date (e.g. ``2023-11-03``) when its year already
matches so enrichment/retag never downgrades a real full release date to
just the year (#824). When the years differ (a genuine correction) or the
file has no date, the year is written as before."""
year_str = str(year)
if existing:
existing = str(existing).strip()
if len(existing) > 4 and existing[:4] == year_str:
return existing
return year_str
def _write_id3(audio, title, artist, album_artist, album, year, genre,
track_num, total_tracks, disc_num, bpm,
artists_list: Optional[List[str]] = None) -> List[str]:
@ -473,8 +493,9 @@ def _write_id3(audio, title, artist, album_artist, album, year, genre,
audio.tags.add(TALB(encoding=3, text=[album]))
written.append('album')
if year is not None:
existing_date = _id3_text(audio.tags, 'TDRC')
audio.tags.delall('TDRC')
audio.tags.add(TDRC(encoding=3, text=[str(year)]))
audio.tags.add(TDRC(encoding=3, text=[_date_to_write(existing_date, year)]))
written.append('year')
if genre:
audio.tags.delall('TCON')
@ -520,7 +541,7 @@ def _write_vorbis(audio, title, artist, album_artist, album, year, genre,
audio['album'] = [album]
written.append('album')
if year is not None:
audio['date'] = [str(year)]
audio['date'] = [_date_to_write(_vorbis_first(audio, 'date'), year)]
written.append('year')
if genre:
audio['genre'] = [genre]
@ -564,7 +585,7 @@ def _write_mp4(audio, title, artist, album_artist, album, year, genre,
audio['\xa9alb'] = [album]
written.append('album')
if year is not None:
audio['\xa9day'] = [str(year)]
audio['\xa9day'] = [_date_to_write(_mp4_first(audio, '\xa9day'), year)]
written.append('year')
if genre:
audio['\xa9gen'] = [genre]

View file

@ -141,3 +141,40 @@ def test_write_real_value_still_overwrites(flac_path):
result = write_tags_to_file(flac_path, {'artist_name': 'Coldplay'}, embed_cover=False)
assert result['success'] is True
assert FLAC(flac_path).get('artist') == ['Coldplay']
# ---------------------------------------------------------------------------
# #824 — full release dates must not be downgraded to just the year
# ---------------------------------------------------------------------------
def test_diff_full_date_same_year_not_flagged():
# File has the full date 2023-11-03; DB has year 2023. Same year → the writer
# keeps the full date, so it must NOT show as a change.
diff = {d['field']: d for d in build_tag_diff({'year': '2023-11-03'}, {'year': 2023})}
assert diff['Year']['changed'] is False
def test_diff_different_year_still_flagged():
# A genuinely different year is still a real change.
diff = {d['field']: d for d in build_tag_diff({'year': '2022-11-03'}, {'year': 2023})}
assert diff['Year']['changed'] is True
def test_write_preserves_full_date_when_year_matches(flac_path):
audio = FLAC(flac_path)
audio['date'] = ['2023-11-03'] # file already has the full release date
audio.save()
write_tags_to_file(flac_path, {'year': 2023}, embed_cover=False) # DB knows only the year
assert FLAC(flac_path).get('date') == ['2023-11-03'] # full date preserved, NOT downgraded
def test_write_corrects_year_when_it_actually_differs(flac_path):
audio = FLAC(flac_path)
audio['date'] = ['2022']
audio.save()
write_tags_to_file(flac_path, {'year': 2023}, embed_cover=False)
assert FLAC(flac_path).get('date') == ['2023'] # wrong year still corrected