Repository: beetbox/beets

Issue:
Always set year, month, and day tags on MediaFiles in that order
1.3.10

_Immediately_ after 'write'

> $ beet write -p album:Mutations
> Beck - Mutations - Cold Brains
>   original_day: 00 -> 02
>   bitrate: 931kbps -> 931kbps
> Beck - Mutations - Nobody’s Fault but My Own
>   original_day: 00 -> 02
>   bitrate: 902kbps -> 902kbps
> ...

I check the FLAC files and see

>    comment[44]: ORIGINALDATE=1998-11

Issue 1)  original_day wasn't written.
IMO, 'write' should think the db and files are in sync immediately after a 'write'.

Issue 2)  write thinks the bitrate doesn't match.
I'm not sure what FLAC data to look at in the file...

'update' apparently thinks things are in sync.

> $ beet update -p album:Mutations
> $

So 'update' doesn't care about original_day ??



Discussion:
That's odd! Can you please provide a sample file for testing?

The bit about bitrate was recently fixed in 790c41a73d0aacff94a73266600582fe03b0daba.

If it makes any difference, my files already had musicbrainz ids and were imported as-is

> beet import -WCAiq

then synced

> beet mbsync -WM

then written

> beet write

FLAC file here for a limited time:
https://www.dropbox.com/s/begfpl6k983vzjs/01.%20Beck%20-%20Cold%20Brains.flac?dl=0

Hmm. Oddly, this works fine here:

```
$ beet imp ~/Downloads/t
[autotagging output clipped; I get "Mutations" with all but one track missing]
$ beet mbsync -WM
$ beet write
Beck - Mutations - Cold Brains
  original_day: 00 -> 02
$ beet write
$
```

As you can see, the second `beet write` does nothing, as expected. I'm not sure how to get into your buggy state.

OK.  I did a second 'write' instead of 'write -p' and then a subsequent '-p' only
reports the bitrate and metaflac confirms

> comment[44]: ORIGINALDATE=1998-11-02

So that's consistent with what you saw, but the file I gave you before was after my import/mbsync/write.
Here's one before that sequence:
https://www.dropbox.com/s/fevs6z0ebqppdll/02.%20Beck%20-%20Nobody%27s%20Fault%20but%20My%20Own.flac?dl=0

OK, here's what I get with that:

```
$ ls ~/Downloads/t
02. Beck - Nobody's Fault but My Own.flac
$ beet imp ~/Downloads/t
[clip]
$ beet mbsync -WM
$ beet write
Beck - Mutations - Nobody's Fault but My Own
  disc: 00 -> 01
  albumartist_sort:  -> Beck
  title: Nobody's Fault but My Own -> Nobody’s Fault but My Own
  mb_releasegroupid:  -> 00a9ccbf-341b-3b58-8865-7fc7fc9a64d9
  albumartist_credit:  -> Beck
  original_year: 0000 -> 1998
  artist_credit:  -> Beck
  disctotal: 00 -> 01
  albumstatus:  -> Official
  original_day: 00 -> 02
  albumartist:  -> Beck
  media:  -> HDCD
  artist_sort:  -> Beck
  tracktotal: 00 -> 11
  catalognum:  -> DGCD-25309
  original_month: 00 -> 11
  asin:  -> B00000DHYK
  language:  -> eng
  country:  -> US
  script:  -> Latn
  label:  -> Bong Load Custom Records
  albumtype:  -> album
$ beet write
Beck - Mutations - Nobody’s Fault but My Own
  original_day: 00 -> 02
$ beet write
$
```

So for some reason, all fields take effect on the first `write` except for `original_day`.

Can you please do a little more investigation along these lines? For example, what does the metaflac metadata look like in the first state vs. the intermediate state vs. the final state? Can you recreate the problem with a sequence of `beet modify` commands to erase the data and then write it again? If we can narrow in on what updates are causing the issue, the problem may become obvious.

Initial state:

> ```
> comment[0]: ARTIST=Beck
> comment[1]: TITLE=Cold Brains
> comment[2]: ALBUM=Mutations
> comment[3]: TRACKNUMBER=1
> comment[4]: DATE=1998-11-03
> comment[5]: MUSICBRAINZ_TRACKID=9155d32e-ea14-45ab-b418-0475d10be664
> comment[6]: MUSICBRAINZ_ARTISTID=309c62ba-7a22-4277-9f67-4a162526d18a
> comment[7]: MUSICBRAINZ_ALBUMID=295d2001-f034-447d-9545-56c8c63f6455
> comment[8]: MUSICBRAINZ_ALBUMARTISTID=309c62ba-7a22-4277-9f67-4a162526d18a
> comment[9]: REPLAYGAIN_REFERENCE_LOUDNESS=89.0 dB
> comment[10]: REPLAYGAIN_TRACK_GAIN=-7.17 dB
> comment[11]: REPLAYGAIN_TRACK_PEAK=0.99981689
> comment[12]: REPLAYGAIN_ALBUM_GAIN=-7.85 dB
> comment[13]: REPLAYGAIN_ALBUM_PEAK=0.99987793
> ```

After import/mbsync/write

> ```
> comment[0]: REPLAYGAIN_REFERENCE_LOUDNESS=89.0 dB
> comment[1]: LYRICS= [ removed ]
> comment[2]: DISCSUBTITLE=
> comment[3]: GENRE=Rock
> comment[4]: DISC=1
> comment[5]: DISCNUMBER=1
> comment[6]: MUSICBRAINZ_TRACKID=9155d32e-ea14-45ab-b418-0475d10be664
> comment[7]: COMPOSER=
> comment[8]: ALBUMARTISTSORT=Beck
> comment[9]: TITLE=Cold Brains
> comment[10]: MUSICBRAINZ_ALBUMID=295d2001-f034-447d-9545-56c8c63f6455
> comment[11]: ACOUSTID_FINGERPRINT=
> comment[12]: REPLAYGAIN_ALBUM_GAIN=-7.85 dB
> comment[13]: MUSICBRAINZ_RELEASEGROUPID=00a9ccbf-341b-3b58-8865-7fc7fc9a64d9
> comment[14]: REPLAYGAIN_ALBUM_PEAK=0.999878
> comment[15]: ALBUMARTIST_CREDIT=Beck
> comment[16]: ACOUSTID_ID=
> comment[17]: REPLAYGAIN_TRACK_GAIN=-7.17 dB
> comment[18]: TRACKTOTAL=11
> comment[19]: TRACKC=11
> comment[20]: TOTALTRACKS=11
> comment[21]: MUSICBRAINZ_ALBUMARTISTID=309c62ba-7a22-4277-9f67-4a162526d18a
> comment[22]: ARTIST=Beck
> comment[23]: BPM=0
> comment[24]: ARTIST_CREDIT=Beck
> comment[25]: DISCTOTAL=1
> comment[26]: DISCC=1
> comment[27]: TOTALDISCS=1
> comment[28]: MUSICBRAINZ_ALBUMSTATUS=Official
> comment[29]: ALBUM ARTIST=Beck
> comment[30]: ALBUMARTIST=Beck
> comment[31]: DATE=1998-11-03
> comment[32]: YEAR=1998
> comment[33]: MUSICBRAINZ_ALBUMCOMMENT=
> comment[34]: ALBUM=Mutations
> comment[35]: ASIN=B00000DHYK
> comment[36]: MEDIA=HDCD
> comment[37]: ARTISTSORT=Beck
> comment[38]: DESCRIPTION=
> comment[39]: COMMENT=
> comment[40]: LABEL=Bong Load Custom Records
> comment[41]: PUBLISHER=Bong Load Custom Records
> comment[42]: REPLAYGAIN_TRACK_PEAK=0.999817
> comment[43]: CATALOGNUMBER=DGCD-25309
> comment[44]: ORIGINALDATE=1998-11
> comment[45]: MUSICBRAINZ_ARTISTID=309c62ba-7a22-4277-9f67-4a162526d18a
> comment[46]: TRACK=1
> comment[47]: TRACKNUMBER=1
> comment[48]: COMPILATION=0
> comment[49]: ENCODEDBY=
> comment[50]: ENCODER=
> comment[51]: LANGUAGE=eng
> comment[52]: RELEASECOUNTRY=US
> comment[53]: SCRIPT=Latn
> comment[54]: MUSICBRAINZ_ALBUMTYPE=album
> comment[55]: GROUPING=
> ```

After second write:

> ```
> comment[0]: REPLAYGAIN_REFERENCE_LOUDNESS=89.0 dB
> comment[1]: LYRICS= [ removed ]
> comment[2]: DISCSUBTITLE=
> comment[3]: GENRE=Rock
> comment[4]: DISC=1
> comment[5]: DISCNUMBER=1
> comment[6]: MUSICBRAINZ_TRACKID=9155d32e-ea14-45ab-b418-0475d10be664
> comment[7]: COMPOSER=
> comment[8]: ALBUMARTISTSORT=Beck
> comment[9]: TITLE=Cold Brains
> comment[10]: MUSICBRAINZ_ALBUMID=295d2001-f034-447d-9545-56c8c63f6455
> comment[11]: ACOUSTID_FINGERPRINT=
> comment[12]: REPLAYGAIN_ALBUM_GAIN=-7.85 dB
> comment[13]: MUSICBRAINZ_RELEASEGROUPID=00a9ccbf-341b-3b58-8865-7fc7fc9a64d9
> comment[14]: REPLAYGAIN_ALBUM_PEAK=0.999878
> comment[15]: ALBUMARTIST_CREDIT=Beck
> comment[16]: ACOUSTID_ID=
> comment[17]: REPLAYGAIN_TRACK_GAIN=-7.17 dB
> comment[18]: TRACKTOTAL=11
> comment[19]: TRACKC=11
> comment[20]: TOTALTRACKS=11
> comment[21]: MUSICBRAINZ_ALBUMARTISTID=309c62ba-7a22-4277-9f67-4a162526d18a
> comment[22]: ARTIST=Beck
> comment[23]: BPM=0
> comment[24]: ARTIST_CREDIT=Beck
> comment[25]: DISCTOTAL=1
> comment[26]: DISCC=1
> comment[27]: TOTALDISCS=1
> comment[28]: MUSICBRAINZ_ALBUMSTATUS=Official
> comment[29]: ALBUM ARTIST=Beck
> comment[30]: ALBUMARTIST=Beck
> comment[31]: DATE=1998-11-03
> comment[32]: YEAR=1998
> comment[33]: MUSICBRAINZ_ALBUMCOMMENT=
> comment[34]: ALBUM=Mutations
> comment[35]: ASIN=B00000DHYK
> comment[36]: MEDIA=HDCD
> comment[37]: ARTISTSORT=Beck
> comment[38]: DESCRIPTION=
> comment[39]: COMMENT=
> comment[40]: LABEL=Bong Load Custom Records
> comment[41]: PUBLISHER=Bong Load Custom Records
> comment[42]: REPLAYGAIN_TRACK_PEAK=0.999817
> comment[43]: CATALOGNUMBER=DGCD-25309
> comment[44]: ORIGINALDATE=1998-11-02
> comment[45]: MUSICBRAINZ_ARTISTID=309c62ba-7a22-4277-9f67-4a162526d18a
> comment[46]: TRACK=1
> comment[47]: TRACKNUMBER=1
> comment[48]: COMPILATION=0
> comment[49]: ENCODEDBY=
> comment[50]: ENCODER=
> comment[51]: LANGUAGE=eng
> comment[52]: RELEASECOUNTRY=US
> comment[53]: SCRIPT=Latn
> comment[54]: MUSICBRAINZ_ALBUMTYPE=album
> comment[55]: GROUPING=
> ```

One clue may be that it shows up in the standard output of the **first** write:

>   original_day: 00 -> 02

So the code that creates that output knows it should be written.

Thanks for following up.

I was able to track down the problem. The issue is that tags can't represent days without months in this format, and sometimes beets tries to apply the fields in the wrong order. That is, if a file has the year 1998 but no month, then the field contains the string `1998`. It is impossible to add a day without adding a month, so if beets says `mf.original_day = 2`, for example, MediaFile just drops the update. There is no prescribed order to the updates, to `mbsync` sometimes does this out of order.

We should probably enforce the order somehow, but I'm not entirely clear how.


Resolving patch:
diff --git a/beets/mediafile.py b/beets/mediafile.py
--- a/beets/mediafile.py
+++ b/beets/mediafile.py
@@ -1460,6 +1460,28 @@ def fields(cls):
             if isinstance(descriptor, MediaField):
                 yield property.decode('utf8')
 
+    @classmethod
+    def field_sort_name(cls, name):
+        """Get field name for sorting purposes. Fields names are kept
+        unchanged, unless they are instances of :class:`DateItemField`,
+        in which case `year`, `month`, and `day` are replaced by `date0`,
+        `date1`, and `date2`, respectively.
+        """
+        if isinstance(cls.__dict__[name], DateItemField):
+            name = re.sub('year',  'date0', name)
+            name = re.sub('month', 'date1', name)
+            name = re.sub('day',   'date2', name)
+        return name
+
+    @classmethod
+    def sorted_fields(cls):
+        """Get the names of all writable metadata fields sorted by
+        lexicographic order (except for instances of :class:`DateItemField`,
+        which are sorted in year-month-day order).
+        """
+        for property in sorted(cls.fields(), key=cls.field_sort_name):
+            yield property
+
     @classmethod
     def readable_fields(cls):
         """Get all metadata fields: the writable ones from
@@ -1496,7 +1518,7 @@ def update(self, dict):
         the `MediaFile`. If a key has the value `None`, the
         corresponding property is deleted from the `MediaFile`.
         """
-        for field in self.fields():
+        for field in self.sorted_fields():
             if field in dict:
                 if dict[field] is None:
                     delattr(self, field)


Test patch: