From 319e90deadf2a5c5dd61d178af443bd669ad53ec Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Mon, 8 Jun 2026 23:15:31 -0700 Subject: [PATCH] Tag writer: stop downgrading full release dates to just the year (#824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- core/tag_writer.py | 27 ++++++++++++++-- tests/test_tag_writer_placeholder_guard.py | 37 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/core/tag_writer.py b/core/tag_writer.py index e0f0ea88..10993806 100644 --- a/core/tag_writer.py +++ b/core/tag_writer.py @@ -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] diff --git a/tests/test_tag_writer_placeholder_guard.py b/tests/test_tag_writer_placeholder_guard.py index d5b63ad6..482804aa 100644 --- a/tests/test_tag_writer_placeholder_guard.py +++ b/tests/test_tag_writer_placeholder_guard.py @@ -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