Repository: beetbox/beets

Issue:
albumtypes is converted from a string to a list of individual characters
### Problem

After importing new files I will run `beet update`. Some of the tracks will have the `albumtypes` 'transformed' from a string (`album`) to a list of character (`['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']`). The `albumtype` field will become a single character (`album` -> `a`)

I could not get the output with `-vv` because it doesn't seem to happen everytime. I deleted albums that previous triggered this problem and the problem didn't repeat on import and update again.

```sh
$ beet update
```

Led to this problem:

```
Ghost - Ceremony and Devotion - Square Hammer
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - From the Pinnacle to the Pit
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Con clavi con dio
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Per aspera ad inferi
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Elizabeth
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Body and Blood
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Devil Church
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Cirice
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Ghuleh / Zombie Queen
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Year Zero
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Spöksonat
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - He Is
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Mummy Dust
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Secular Haze
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Absolution
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Ghost - Ceremony and Devotion - Ritual
  albumtypes: album; live -> ['a', 'l', 'b', 'u', 'm', ';', ' ', 'l', 'i', 'v', 'e']
  albumtype: live -> a
Arch Enemy - Wages of Sin - Enemy Within
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Arch Enemy - Wages of Sin - Burning Angel
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Arch Enemy - Wages of Sin - Heart of Darkness
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Arch Enemy - Wages of Sin - Ravenous
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Arch Enemy - Wages of Sin - Savage Messiah
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Hard Core
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Paint It Black
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Enter the Storm
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Fuel Me Up
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Extinct
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Baptizm of Fire
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - The Healer
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
Glenn Tipton - Baptizm of Fire - Cruise Control
  albumtypes: album -> ['a', 'l', 'b', 'u', 'm']
  albumtype: album -> a
```

### Setup

* OS: Gentoo Linux (6.0.3-gentoo-x86_64)
* Python version: 3.10.8
* beets version: 1.6.0
* Turning off plugins made problem go away (yes/no): don't know: I don't have the `albumtypes` plugin in my config.

My configuration (output of `beet config`) is:

```yaml
lyrics:
    bing_lang_from: []
    auto: yes
    force: yes
    fallback: ''
    bing_client_secret: REDACTED
    bing_lang_to:
    google_API_key: REDACTED
    google_engine_ID: REDACTED
    genius_api_key: REDACTED
    local: no
    sources:
    - google
    - musixmatch
    - genius
    - tekstowo
library: /home/mcarpentier/Music/.beets/BeetMusicLibrary.blb
directory: /home/mcarpentier/Music

ui:
    color: yes
    colors:
        text_success: green
        text_warning: yellow
        text_error: red
        text_highlight: red
        text_highlight_minor: lightgray
        action_default: turquoise
        action: blue

import:
    copy: no
    write: yes
    move: yes

plugins: embedart fetchart info lyrics
embedart:
    auto: yes
    maxwidth: 0
    compare_threshold: 0
    ifempty: no
    remove_art_file: no
    quality: 0

paths:
    default: $albumartist/$album%aunique{}/$track $title
    singleton: Non-Album/$artist/$title
    comp: Compilations/$album%aunique{}/$track $title
fetchart:
    auto: yes
    minwidth: 0
    maxwidth: 0
    quality: 0
    max_filesize: 0
    enforce_ratio: no
    cautious: no
    cover_names:
    - cover
    - front
    - art
    - album
    - folder
    sources:
    - filesystem
    - coverart
    - itunes
    - amazon
    - albumart
    google_key: REDACTED
    google_engine: 001442825323518660753:hrh5ch1gjzm
    fanarttv_key: REDACTED
    lastfm_key: REDACTED
    store_source: no
    high_resolution: no
    deinterlace: no
    cover_format:
```


Discussion:
CharlesSchimmel: I thought I'd give this build a try after stumbling over #4528 and I'm getting the following error when running anything:

```
Traceback (most recent call last):
  File "/home/user/.local/bin/beet", line 5, in <module>
    from beets.ui import main
  File "/home/user/.local/lib/python3.10/site-packages/beets/ui/__init__.py", line 33, in <module>
    from beets import library
  File "/home/user/.local/lib/python3.10/site-packages/beets/library.py", line 33, in <module>
    from beets import dbcore
  File "/home/user/.local/lib/python3.10/site-packages/beets/dbcore/__init__.py", line 19, in <module>
    from .db import Model, Database
  File "/home/user/.local/lib/python3.10/site-packages/beets/dbcore/db.py", line 29, in <module>
    from beets.dbcore import types
  File "/home/user/.local/lib/python3.10/site-packages/beets/dbcore/types.py", line 255, in <module>
    SEMICOLON_SPACE_DSV = DelimitedString(delim='; ')
TypeError: DelimitedString.__init__() got an unexpected keyword argument 'delim'
```

Removing the `delim=` fixes it, I'm pretty rusty with python though so I couldn't say if that's appropriate or what the intent is.

Thanks for working on this issue by the way!
jpluscplusm: @CharlesSchimmel  You've caught a mistake I made when incorporating some changes from another user. I can't push the fix right now, but will do imminently. In the interim, change the "delim" abbreviation to the full word "delimiter", and it should work!
CharlesSchimmel: Thanks! I've built the change, and I'm still getting the original issue except with semicolons:

```
Milo - So The Flies Don't Come - song about a raygunn (an ode to Driver)
  albumtype: album -> [
  albumtypes: ['album'] -> [; '; a; l; b; u; m; '; ]
```
mkhl: @CharlesSchimmel that looks like your tags might have been degraded already; could you check the TXXX/MusicBrainz Album Type frame of one of those files?
`mid3v2 /path/to/the.mp3 | grep '^TXXX=MusicBrainz Album Type='`  should do that if you have mutagen installed

that file you mentioned should have the type `album` but i think it might be something like `[ / ' / a / l / b / u / m / ' / ]` instead
CharlesSchimmel: @mkhl Thanks, I think you're correct. Looks like a lot of my files have releasetype of `a;l;b;u;m` or `[;';a;l;b;u;m;';]`

I `beet update`d a couple files with correct releasetypes and this patch seems to have worked.
jpluscplusm: @CharlesSchimmel Thank you for checking further! Did you by any chance mean the albumtypes, not releasetypes, tag?

More generally, this suggests we need a Benevolent Dictator's view on the already-corrupt situation: do we need to recover if both the tags are now wrong, and (as I /suspect/ was the case with @CharlesSchimmel's library) the in-DB entries had been degraded as well?

I might very well be misunderstanding the scope/frequency of the underlying Issue's affected users, but is /automatically/ repairing the albumtypes/type data of folks who've upgraded to 1.6.0+ possible and/or desirable?
CharlesSchimmel: @jpluscplusm Correct, that's what I meant, the releasetype tag seems to be a mirror of the albumtype tag.

Personally, I'm re-importing the problem files with `beet import -L 'albumtype::^\['`, which only works for tracks in the MusicBrainz db.
mkhl: > I might very well be misunderstanding the scope/frequency of the underlying Issue's affected users, but is /automatically/ repairing the albumtypes/type data of folks who've upgraded to 1.6.0+ possible and/or desirable?

i think that should be out of scope. beets 1.6.0 writes problematic tags to the files but keeps the database intact, and this fix works fine if the database is intact.
to also "corrupt" the database one would have to update it from files with problematic tags, and for those cases reimporting the files will restore the correct data in the database anyway.
jpluscplusm: PR branch rebased against master, following the addition of the expected test failure in https://github.com/beetbox/beets/pull/4586.

Another commit added, changing the test to reflect that it shouldn't be failing after this PR is merged [to the best of my pythony- and beetsy- abilities!]
wisp3rwind: Some test seem to be failing due to not expecting a list-valued `albumtypes`, could you have a look into this? Probably, this only requires slight modification to those tests. Thanks!
JOJ0: @jpluscplusm would it help if someone would explain how those failing tests could be modified to pass? Is there anything else that would help to kickstart this PR? I think it's a nasty bug and it covers a lot of ground. Thanks for your heroic efforts so far! 
jpluscplusm: @JOJ0 Hello 👋 Yes, any assistance any at all would be very welcome. 

I feel very unsure in proposing changes beyond the current state because:

- I lack the project-knowledge to know how completely we test, and how to test appropriately 
- I'm very concerned about the disparate audiences facing this bug, across axes like how recently they upgraded to 1.6.0+, and if their tags have already/ever been "infected" by this bug.

I really really don't want to be responsible for proposing a "fix" that -- for instance -- only deals with my newcomer/fresh-library use case, whilst leaving long-time users in a potentially worse place. 

Any assistance or guidance is very very welcome!
JOJ0: Hi, first of all appologies for letting you hang there for another ten days...

Regarding your worries: At the end of the day, it's the beets project who is responsible because they merged something into the main code. If that breaks something, a contributor can't be made responsilbe for that. Certainly we will be happy if you would still support us and help moving forward with analysis and fixing. My personal opinion is: We are in this together and I only can be grateful that people like you are brave enough to submit a fix for such a tricky problem.

Speaking of, last weekend I fiddled around a little with the currently failing unittests wisp3rwind mentionend above. I was not successful so far but will continue working on it. Did you give it a try yet?


Also I'm still trying to wrap my head around "the problem" as a whole. What I understand so far is:

- "The problem" is only existing with the `albumtypes` field which has nothing to do with the `albumtype` field. They don't interact with each other.
- "The problem" is only existing when MusicBrainz is used as the metadata source. It's not present when the Discogs plugin or other metadata source plugins is in use. The don't touch the `albumtypes` field, they use the `albumtype` field only.
- This PR fixes "the problem" when data that is correctly existing in the database.
- The failing test wisp3wind provided in #4586 currently proves in master that "the problem" is existing and thanks to your latest commit will prove that "the problem" is fixed with this PR.
- _What will be fixed_ is the correct writing of the `albumtypes` field to the file's tag, as well as the correct reading from the tag and writing to the beets database.
- _What will not be fixed_ is the "magically" correcting of already messed up file tags. For example because a user issued `beet update` which reads from tag and writes to the database.



Please correct me if I'm technical wrong with any of this or give me your own opinion if you understood differently.



My conclusion now would be:

- This PR can be merged and it's unlikely that it's bringing more harm.
- The problem that is persisting is how to fix already messed up database entries (because of `beet update`).
- In my opinion a "magic" fix is almost impossible, especially in cases where a user for some reason had issued `beet write` and `beet update` several times! There is no way to clean up this mess, except by re-importing and overwriting the `albumtypes` field with fresh information. See my following post with an example of a quite messed up field that most probably can't be autofixed!


Thus, I have the following ideas of how this could be tackled further:
- Fix the existing tests to pass with the new code,
- include a detailed tutorial for users on:
  - how a possible "infection" can be detected,
  - how a reimport can be issued to fix the problem,
  - and how it can be verified that problem won't happen in the future.
- then merge this PR.

Fortunately proper hints around such a tutorial are provided here already: https://github.com/beetbox/beets/pull/4582#issuecomment-1352447275

Where would a tutorial like this be placed is the only question I'm unsure about: Release notes? Add as the final answer in the existing github issue to this PR? Somewhere in the documentation? All of this places, just to make sure ?!
  
Feel free to give your opinion on these thoughts.


JOJ0: Some database examples from my dev environment. We see that the releases on label "Music man records" are pretty messed up and I don't think that there is any possible auto-fix other then suggesting to reimport:

![image](https://user-images.githubusercontent.com/2733783/220386096-d0894d62-7511-4cb0-aaea-6a9db9b627f8.png)

JOJ0: To prove my assumption that Discogs tagged files don't touch the albumtypes field let's do the following experiment. Let's focus on the 3 Music Man Records releases. I just reimported the last one from MusicBrainz (catalognum: MMCD. We see that `albumtype` and `albumtypes` look correct - The messed up albumtypes field from my previous post is now fixed:

![image](https://user-images.githubusercontent.com/2733783/220398288-54bbb3da-23e0-4a7e-a93a-5a0c6f1eb691.png)

Now I reimport from Discogs and get:

![image](https://user-images.githubusercontent.com/2733783/220400243-ded877fa-de8f-48c7-b30a-16e2d8c1c7bd.png)

MM129A is the Catalog number we are taking about and we see that the Discogs metadata source plugin did not care about the `albumtypes` field, it left it as-is - it still shows what MusicBrainz had put in there before.

That proves my assumption that at least this source plugin, doesn't touch `albumtypes`

Final note: Certainly in such a situation a writing to file tags using `beet write` and then `beet update` *prior* to the fix in this PR would still lead to a messed up field as we have seen in my first "picture post"

Hope that clarifies and doesn't confuse further :-)


mkhl: First of all: Thank you!

> Also I'm still trying to wrap my head around "the problem" as a whole. What I understand so far is:

Let me preface with:
My understanding is that there are conflicting forces between
* the information beets stores about music files in its database
* the information beets reads from music files
* the information beets fetches from the metadata source

>   * "The problem" is only existing with the `albumtypes` field which has nothing to do with the `albumtype` field. They don't interact with each other.

I don't think that's technically correct.
Beets treats these fields as independent in the database, but in music files these are (currently) represented by *a single tag*, the "MusicBrainz Album Type" TXXX comment.
Example: `TXXX=MusicBrainz Album Type=album / live` (printed by mid3v2)

I also believe they are separate in the MusicBrainz API, but beets tries to derive the albumtype *when reading files* as being the first element of albumtypes. This can lead to the secondary problem (that this PR doesn't try to address) where `beet write` tries to modify the `albumtype` every time.

>   * "The problem" is only existing when MusicBrainz is used as the metadata source. It's not present when the Discogs plugin or other metadata source plugins is in use. The don't touch the `albumtypes` field, they use the `albumtype` field only.

I believe that's correct.

>   * This PR fixes "the problem" when data that is correctly existing in the database.

Yes it does.

>   * _What will be fixed_ is the correct writing of the `albumtypes` field to the file's tag, as well as the correct reading from the tag and writing to the beets database.

I believe that writing to the file's tag won't be modified, only reading it and matching it against the database get fixed.

>     * _What will not be fixed_ is the "magically" correcting of already messed up file tags. For example because a user issued `beet update` which reads from tag and writes to the database.

Yes, and we seem to agree that it doesn't need to.

> Where would a tutorial like this be placed is what I'm unsure about: Release notes? Add as the final answer in the existing github issue to this PR? Somewhere in the documentation? All of this places, just to make sure ?!

I'd recommend adding it as a page in either the wiki or the documentation, then linking to that page from this PR, from the issue it fixes, and *prominently* in the release notes.

Sciencentistguy: > In my opinion a "magic" fix is almost impossible, especially in cases where a user for some reason had issued beet write and beet update several times! There is no way to clean up this mess, except by re-importing and overwriting the albumtypes field with fresh information.

This can be done quite easily, if you don't mind effectively retagging your whole library in the process: `beet update`, `beet mbsync`, `beet write`
JOJ0: > Some test seem to be failing due to not expecting a list-valued `albumtypes`, could you have a look into this? Probably, this only requires slight modification to those tests. Thanks!

I finally found time to look into this and I think I have a solution. There is 2 things we need to include into this PR:

- The new database type for `albumtypes`  now returns a list. We need to make sure that the  albumtypes plugin uses it that way. Up to now it did the joining from `;`-separated string itself.
- All the failing unittests for the albumtypes plugin use the method `_create_album()` which also tries to do the do the translation of `;`-delimited string to list itself. We just need to make sure it uses the list we are passing it already.
JOJ0: Hi @mkhl, thanks so much for correcting and clarifying, that was tremendously helpful for moving on with this topic. Very much appreciated :-)

As for the location of the "manual fixing advice" I would prefer to add it to the docs instead of the wiki. The wiki is a place that I have to admit I personally haven't looked much into as a beets user. Now that I look at it, there is a lot of interesting and developer-centric things in there (Shame on me for not realising this earlier :-~) :-) )

So one place I would like to add it in the docs is the already mentionend [albumtypes plugin docs ](https://beets.readthedocs.io/en/stable/plugins/albumtypes.html). A link to the howto would suffice here.

So I'm still looking for a place in the docs where a tutorial containing of 3 to 4 steps should live......maybe the wiki _is_ a good place for that afterall. All the places we would like to mention it could then link to the wiki. Do you have an idea where you would see such a tutorial? Maybe create something like a "known issues" chapter and add it there?


JOJ0: My take on that "manual fixing advice". Very verbose I think, could be shorter, please help! :-)






## Longer description:

e.g lives in docs or wiki.

The resolution for a bug that was introduced in beets 1.6.0 requires manual intervention. The issue was tracked in several issues #FIXME and resolved in PR #4582. The bug usually shows itself by trying to repeatedly populate the `albumtype` field to files on disk when issuing `beet write`. Thios has been fixed in the mentionend PR. When `beet update` is used to read the `albumtype` tag back into the database, things get worse and broken data is saved into the database. This tutorial helps to spot and fix such a situation.

Have I been infected? If you've never used `beet update` you might as well get not results here and are good to go. If you get results, this means that these albums need to be fixed in the database:

```
beet ls -a 'albumtypes::^\[' data_source:musicbrainz
```

The quickest way to fix is by using the `mbsync` plugin, it will make sure the `albumtypes field is overwritten with data fetched from MusicBrainz:

```
beet mbsync 'albumtypes::^\[' data_source:musicbrainz
```

Alternatively, or if you don't use the `mbsync` plugin a full reimport of the albums in question would also fix the issue:


```
beet import -L 'albumtypes::^\[' data_source:musicbrainz
```



There might be albums in your library where initially MusicBrainz was used as the metadata source and later on a retag with another metadata source plugin (Discogs, Spotify, ...) was used. The existence of such broken albums can be verified with:


```
beet ls -a 'albumtypes::^\[' ^data_source:musicbrainz
```



## Short description

e.g for changelog?

A bug introduced in beets 1.6.0 requires your attention. Please follow [this link](FIXME) to read on.

JOJ0: > > In my opinion a "magic" fix is almost impossible, especially in cases where a user for some reason had issued beet write and beet update several times! There is no way to clean up this mess, except by re-importing and overwriting the albumtypes field with fresh information.
> 
> This can be done quite easily, if you don't mind effectively retagging your whole library in the process: `beet update`, `beet mbsync`, `beet write`

Hi thanks for the hint. I have to admit I didn't have the mbsync plugin on my radar since I don't use. But yes, essentially it does the same as essential part of reimporting: It overwrites the `albumtypes` database field with fresh data from MusicBrainz.
jpluscplusm: >. Sorry for hijacking, I hope that's ok! :-)

1000% ok! Please feel to take this PR anywhere the project needs ... including closing and starting again if that'd best serve the purpose of the fix! I'm just happy if my initial code or thoughts were at all helpful :-)

Patch:
diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py
index 7d7932faf..f380cd033 100644
--- a/beets/autotag/mb.py
+++ b/beets/autotag/mb.py
@@ -466,7 +466,7 @@ def album_info(release: Dict) -> beets.autotag.hooks.AlbumInfo:
         if release['release-group']['secondary-type-list']:
             for sec_type in release['release-group']['secondary-type-list']:
                 albumtypes.append(sec_type.lower())
-    info.albumtypes = '; '.join(albumtypes)
+    info.albumtypes = albumtypes
 
     # Release events.
     info.country, release_date = _preferred_release_event(release)
diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py
index 40f6a0805..8c8bfa3f6 100644
--- a/beets/dbcore/types.py
+++ b/beets/dbcore/types.py
@@ -208,6 +208,27 @@ class String(Type):
             return self.model_type(value)
 
 
+class DelimitedString(String):
+    """A list of Unicode strings, represented in-database by a single string
+    containing delimiter-separated values.
+    """
+    model_type = list
+
+    def __init__(self, delimiter):
+        self.delimiter = delimiter
+
+    def format(self, value):
+        return self.delimiter.join(value)
+
+    def parse(self, string):
+        if not string:
+            return []
+        return string.split(self.delimiter)
+
+    def to_sql(self, model_value):
+        return self.delimiter.join(model_value)
+
+
 class Boolean(Type):
     """A boolean type.
     """
@@ -231,3 +252,4 @@ FLOAT = Float()
 NULL_FLOAT = NullFloat()
 STRING = String()
 BOOLEAN = Boolean()
+SEMICOLON_SPACE_DSV = DelimitedString(delimiter='; ')
diff --git a/beets/library.py b/beets/library.py
index c05dcda18..9d5219b18 100644
--- a/beets/library.py
+++ b/beets/library.py
@@ -504,7 +504,7 @@ class Item(LibModel):
         'mb_releasetrackid': types.STRING,
         'trackdisambig': types.STRING,
         'albumtype': types.STRING,
-        'albumtypes': types.STRING,
+        'albumtypes': types.SEMICOLON_SPACE_DSV,
         'label': types.STRING,
         'acoustid_fingerprint': types.STRING,
         'acoustid_id': types.STRING,
@@ -1064,7 +1064,7 @@ class Album(LibModel):
         'mb_albumid': types.STRING,
         'mb_albumartistid': types.STRING,
         'albumtype': types.STRING,
-        'albumtypes': types.STRING,
+        'albumtypes': types.SEMICOLON_SPACE_DSV,
         'label': types.STRING,
         'mb_releasegroupid': types.STRING,
         'asin': types.STRING,
diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py
index 47f8dc64e..b54e802e6 100644
--- a/beetsplug/albumtypes.py
+++ b/beetsplug/albumtypes.py
@@ -55,7 +55,7 @@ class AlbumTypesPlugin(BeetsPlugin):
             bracket_r = ''
 
         res = ''
-        albumtypes = item.albumtypes.split('; ')
+        albumtypes = item.albumtypes
         is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID
         for type in types:
             if type[0] in albumtypes and type[1]:
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 921ae7b30..b71bc1104 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -145,6 +145,13 @@ Bug fixes:
   :bug:`4561` :bug:`4600`
 * Fix issue where deletion of flexible fields on an album doesn't cascade to items
   :bug:`4662`
+* Fix issue where ``beet write`` continuosly retags the ``albumtypes`` metadata
+  field in files. Additionally broken data could have been added to the library
+  when the tag was read from file back into the library using ``beet update``.
+  It is required for all users to **check if such broken data is present in the
+  library**. Following the instructions `described here
+  <https://github.com/beetbox/beets/pull/4582#issuecomment-1445023493>`_, a
+  sanity check and potential fix is easily possible. :bug:`4528`
 
 For packagers:
 
diff --git a/docs/plugins/albumtypes.rst b/docs/plugins/albumtypes.rst
index 7a1a08f95..bf736abca 100644
--- a/docs/plugins/albumtypes.rst
+++ b/docs/plugins/albumtypes.rst
@@ -11,6 +11,11 @@ you can use in your path formats or elsewhere.
 
 .. _MusicBrainz documentation: https://musicbrainz.org/doc/Release_Group/Type
 
+A bug introduced in beets 1.6.0 could have possibly imported broken data into
+the ``albumtypes`` library field. Please follow the instructions `described
+here <https://github.com/beetbox/beets/pull/4582#issuecomment-1445023493>`_ for
+a sanity check and potential fix. :bug:`4528`
+
 Configuration
 -------------
 


Test patch:
diff --git a/test/test_albumtypes.py b/test/test_albumtypes.py
index 91808553d..3d329dd7b 100644
--- a/test/test_albumtypes.py
+++ b/test/test_albumtypes.py
@@ -106,6 +106,6 @@ class AlbumTypesPluginTest(unittest.TestCase, TestHelper):
 
     def _create_album(self, album_types: [str], artist_id: str = 0):
         return self.add_album(
-            albumtypes='; '.join(album_types),
+            albumtypes=album_types,
             mb_albumartistid=artist_id
         )
diff --git a/test/test_ui.py b/test/test_ui.py
index 86c40d204..d3ce4a560 100644
--- a/test/test_ui.py
+++ b/test/test_ui.py
@@ -701,27 +701,30 @@ class UpdateTest(_common.TestCase):
         item = self.lib.items().get()
         self.assertEqual(item.title, 'full')
 
-    @unittest.expectedFailure
     def test_multivalued_albumtype_roundtrip(self):
         # https://github.com/beetbox/beets/issues/4528
 
         # albumtypes is empty for our test fixtures, so populate it first
         album = self.album
-        # setting albumtypes does not set albumtype currently...
-        # FIXME: When actually fixing the issue 4528, consider whether this
-        # should be set to "album" or ["album"]
-        album.albumtype = "album"
-        album.albumtypes = "album"
+        correct_albumtypes = ["album", "live"]
+
+        # Setting albumtypes does not set albumtype, currently.
+        # Using x[0] mirrors https://github.com/beetbox/mediafile/blob/057432ad53b3b84385e5582f69f44dc00d0a725d/mediafile.py#L1928  # noqa: E501
+        correct_albumtype = correct_albumtypes[0]
+
+        album.albumtype = correct_albumtype
+        album.albumtypes = correct_albumtypes
         album.try_sync(write=True, move=False)
 
         album.load()
-        albumtype_before = album.albumtype
-        self.assertEqual(albumtype_before, "album")
+        self.assertEqual(album.albumtype,  correct_albumtype)
+        self.assertEqual(album.albumtypes, correct_albumtypes)
 
         self._update()
 
         album.load()
-        self.assertEqual(albumtype_before, album.albumtype)
+        self.assertEqual(album.albumtype,  correct_albumtype)
+        self.assertEqual(album.albumtypes, correct_albumtypes)
 
 
 class PrintTest(_common.TestCase):