beetbox / beets

music library manager and MusicBrainz tagger
http://beets.io/
MIT License
12.59k stars 1.8k forks source link

First release year of an album's track with Musicbrainz? #1498

Open m-urban opened 9 years ago

m-urban commented 9 years ago

Hi there,

Today I wanted to import a compilation by artist Al Stewart, "A Piece of Yesterday: The Anthology", which was released in 2006. Consequently, all the tracks on this album are also tagged with release year 2006 now. However, these songs have been released over the past 50 years, with the oldest songs from the '60s. Tagging a song from 1978 with 2006 just seems wrong to me here - is there a way, to get a track's very first release year into the library and the file's tags? The original_date:option doesn't appear to work here, because I want a particular track's original date, and not a release/release-group's original date…

Would I need to update the MB database in order to achieve this? If so, would it suffice to link the songs in question to an MB "work" and beets would fetch the work's original release date?

sampsyo commented 9 years ago

Beets currently fetches dates for releases (or, in the case of original_date, release groups), not tracks/works/recordings. In fact, in the database, the date fields are album-level, not track-level, to indicate this distinction.

You could imagine a different more, related to but different from original_date, that finds the first release the work appeared on. That might work in a plugin too.

m-urban commented 9 years ago

Sounds just like the right use-case for our new hooks :) Still, it also seems like a ton of work that's needed on the MB database's end… not all recordings are assigned to a work, let alone that works might not even exist. And then there might be cover versions, which are linked to the same work. Even for my relatively small collection of 5k songs, figuring all this out is a nightmare.

Right now, my collection is neatly tagged (manually, unfortunately…) with years of first appearance and I don't want to destroy all that information by importing it into beets. Is there a way I can tell beets to import a song but not modify the year tag? Like importing a song, but importing the year "as is"?

To be honest, I am a little surprised, this issue hasn't been raised yet. How do people create playlists like "Music from the '80s" for example, when the songs are linked to "Best of" compilations that have been released more recently?

sampsyo commented 9 years ago

Still, it also seems like a ton of work that's needed on the MB database's end… not all recordings are assigned to a work, let alone that works might not even exist. And then there might be cover versions, which are linked to the same work.

Indeed, getting to the first "intuitive" date that a song appeared does seem complex, even for a meticulously complete database like MusicBrainz.

Is there a way I can tell beets to import a song but not modify the year tag? Like importing a song, but importing the year "as is"?

Not currently; "as-is" is all-or-nothing.

m-urban commented 9 years ago

In case someone is similarly inclined: since this could have been a deal breaker for me, I developed a plugin that employs the following code. It's a bit of a hack, but it lets me manage a song's year manually now. The idea is to fetch a file's meta data tags from the hard disk, before the tags are rewritten. Next, the to-be-written tags are adjusted to reflect the file's original year in the new tags and the database. This is not well tested, so please be careful! My limited tests have shown that the year survives update and mbsync operations, though.

self.register_listener('write', self.write_item)

def write_item(self, item, path, tags):
    # Retain year from file, ignore updates from MusicBrainz et al
    if config["original_date"].get(bool):
        mf = MediaFile(path)
        file_year = mf.year
        item.original_year = item.year
        item.year = file_year
        item.try_sync()
        tags["original_year"] = tags["year"]
        tags["year"] = file_year

Edit: Forgot the try_sync() call in the listing above. Definitely needed to survive an mbsync operation.

sampsyo commented 9 years ago

I see! Interesting idea. Maybe this calls out for a new importer setting that just asks it to avoid touching certain fields.

m-urban commented 9 years ago

This seems related as well #1220

m-urban commented 9 years ago

Sorry to bother with this again… Now I am happy that I can at least preserve my file's year tags with the approach above. But how can I reflect this in the database? Since year tags are processed on album-level, an update operation, for example, will overwrite my years in the library again, because album-level fields are applied from the first track of an album to all other tracks (as described in the documentation).

How can I break this linkage of years being on album-level? I believe it might make sense to generally store years in the items table per track, especially since we already have the respective columns for as-track imported items anyways.

sampsyo commented 9 years ago

The album-level-ness of a field is not currently configurable.

I'm a little reticent to demote the year field to track-level (we have previously demoted tracktotal and perhaps other fields from album-level to track-level), because most users seem to want release years rather than individual track years. Maybe we can think of an elegant way to make this kind of thing configurable? Or perhaps just implementing #1220 would work for your use case?

m-urban commented 9 years ago

Thanks for your thoughts! Implementing #1220 would indeed be an option, however I am afraid that this note here is still problematic:

Resolve some strange behavior w.r.t. album-level fields and the update command. When there's an item with an inconsistent item-level field (i.e., two different items in the album have different values for the same album-level field) that is not written by the autotagger, it will get clobbered back to the uniform value even after beet update. The effect is that just importing and then immediately running update shows changes! That's confusing. The importer should probably enforce that all album-level fields are uniform.

If I am not mistaken, one option to circumvent this entirely might be a flexible attribute. Is it possible to add a new track-level flexattr in a plugin, e.g. track-year, which I could set on import? Would you mind pointing me to some example code, that shows how flexible attributes are handled?

I would still need #1220 to not flag years as changed on update, but that's a different story then. I guess I cannot map my new flexattr to the file's actual year tag, right?

sampsyo commented 9 years ago

Is it possible to add a new track-level flexattr in a plugin, e.g. track-year, which I could set on import? Would you mind pointing me to some example code, that shows how flexible attributes are handled?

A flexible attribute is a great idea! The example code couldn't be easier. You can just set a field on an Item object:

item = lib.get_item(22)
item.track_year = 1920
item.store()

Flexible attributes intentionally look exactly like fixed attributes when you use them.

m-urban commented 7 years ago

@nominum-systems Not sure what happened to your comment, but thanks for your offer to give this a shot!

If it was for me, the feature would be a (configurable) part of the core import functionality and not a plugin. Not sure, how @sampsyo would feel about this, though. I am not familiar with import tasks but it looks as though adding the original year in an extra stage to the import process could be a viable solution.

Ideally, the original_year would be stored in the beets database as a flexible attribute and then be copied over into the ID3 tag's year field. To my knowledge this poses some difficulty, since beets tries to keep the ID3 tag in sync with the database's album year information. beet update and beet mbsync are therefore prone to accidentally overwriting our new original_year information again.

I want to take a shot at this since I have the python goo that fetches the first appearance of a musicbrainz recording ID. What form should it take? I want it to run during import as well as be able to kick it off manually like fetchart/acousticbrainz/etc. Should it be an autotagger plugin? import task?

sampsyo commented 7 years ago

FWIW, I don't mind this being a core feature (it makes sense to be in the MusicBrainz data source).

A good way to start here would be to just keep the value in a flexible attribute—then we can cross the ID3 tag sync bridge when we get to it.

Thanks for the thoughts, @m-urban!

m-urban commented 7 years ago

Sounds great — divide et impera.

tweitzel commented 7 years ago

What happened above was I was logged into my work github account, not my personal one, so I had to nuke the earlier comment offering to take a look at it. Anyway, I have a proof of concept independent beet plugin at https://github.com/tweitzel/beets-recordingdate If anyone gets impatient with me cleaning it up (and measuring DB queries to make sure we don't overload MB with recursive lookups) for inclusion in beets core, I hereby give my blessing to fork and include. Otherwise I'll get to it someday once I take another vacation :)

sampsyo commented 7 years ago

Awesome! This looks good already.

akovia commented 7 years ago

This is exactly what I what I'm looking for. I installed Beets today just to use this. Would anyone be kind enough to comment on how to get this installed and working?

Are there any options to set in the config, etc..?

tweitzel commented 7 years ago

Install beets. Clone the repo somewhere should be able to do python setup.py install add 'recordingdate' to your plugins line in beets' config.yaml run beet rdate using beets' usual query formats in the docs. The dates will be added as fields alongside date and original_date. Eventually replacing the standard date fields is on the radar but I haven't considered it very important for now.

akovia commented 7 years ago

Thanks! Installed without a hitch.

The dates will be added as fields alongside date and original_date. Eventually replacing the standard date fields is on the radar but I haven't considered it very important for now.

Sorry if this is a stupid question, will this create custom date tags then? it was my intention to create some decades playlists that were actually accurate. I can't think of a good reason why I need the date or original_date of the actual greatest hits albums, so overwriting either to be able to create my playlists isn't a deal breaker for me.

I'm using Clementine as my player and the smart playlist creator allows for creating by original year which would be optimal, but I'm open to any suggestion to get the result I'm seeking.

tweitzel commented 7 years ago

It'll create new tags the same as original_date just called recording_date.

-- Sent from a device without a keyboard.

On Apr 23, 2017, at 19:35, akovia notifications@github.com wrote:

Thanks! Installed without a hitch.

The dates will be added as fields alongside date and original_date. Eventually replacing the standard date fields is on the radar but I haven't considered it very important for now.

Sorry if this is a stupid question, will this create custom date tags then? it was my intention to create some decades playlists that were actually accurate. I can't think of a good reason why I need the date or original_date of the actual greatest hits albums, so overwriting either to be able to create my playlists isn't a deal breaker for me.

I'm using Clementine as my player and the smart playlist creator allows for creating by original year which would be optimal, but I'm open to any suggestion to get the result I'm seeking.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

akovia commented 7 years ago

Thanks! I can always create Smart Playlists with beets using that tag.

Getting a unicode error though. Do I need to change an encoding option?

$ beet -v rdate /run/media/akovia/Elements/Music/Billy\ Joel/Greatest\ Hits\,\ Volume\ I\ \&\ Volume\ II/
user configuration: /home/akovia/.config/beets/config.yaml
data directory: /home/akovia/.config/beets
plugin paths: 
Sending event: pluginload
lyrics: Disabling google source: no API key configured.
library database: /home/akovia/.config/beets/library.db
library directory: /run/media/akovia/Elements/Music
Sending event: library_opened
Traceback (most recent call last):
  File "/usr/bin/beet", line 11, in <module>
    load_entry_point('beets==1.4.3', 'console_scripts', 'beet')()
  File "/usr/lib/python3.6/site-packages/beets/ui/__init__.py", line 1209, in main
    _raw_main(args)
  File "/usr/lib/python3.6/site-packages/beets/ui/__init__.py", line 1196, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/usr/lib/python3.6/site-packages/beetsplug/recordingdate.py", line 41, in func
    self.recording_date(lib, query)
  File "/usr/lib/python3.6/site-packages/beetsplug/recordingdate.py", line 67, in recording_date
    item[u'recording_disambiguation'] = unicode(disambig)
NameError: name 'unicode' is not defined
akovia commented 7 years ago

Found this and changed = unicode(disambig) to = str(disambig)

No errors anymore but it still isn't writing the tags.

Here is the relevant output now..
Sending event: library_opened
recordingdate: Applying changes to Billy Joel - Greatest Hits, Volume I & Volume II - Just the Way You Are
Sending event: write
Sending event: after_write
recordingdate: Applying changes to Billy Joel - Greatest Hits, Volume I & Volume II - It's Still Rock and Roll to Me
Sending event: write
Sending event: after_write
recordingdate: Applying changes to Billy Joel - Greatest Hits, Volume I & Volume II - The Longest Time
Sending event: write
Sending event: after_write
Sending event: cli_exit

It quit after 3 songs and I'm not seeing the tags in puddletag's Extended Tags in any of the songs. I tried with a single track that was mp3 instead of flac...

recordingdate: Applying changes to Billy Joel - An Innocent Man - An Innocent Man
Sending event: write
Sending event: after_write
Sending event: cli_exit

I understand from your other posts that you don't have much time for this, so I understand if you don't want to troubleshoot it. If I had the talent to further your work myself I would gladly.

Cheers either way!

akovia commented 7 years ago

Just a followup.

I took your quote literal and created a column in puddletag for recording_date. After looking at the code a little close I saw it's 3 separate tags. recording_year, recording_month, recording_day. (and recording_disambiguation)

So it is indeed writing a few tags correctly, but missing most of them. I am not getting any verbose or logging messages that I see in the code for either Skipping track with no mb_trackid or Recording ID not found. Tried both -vv and -l options to no avail. I manually checked that the mb_trackid exists on all the tracks it checked so I don't think that is the problem. I tried to find differences in tracks it is tagging and those it doesn't but I must be missing something.

I'll just go at it manually for everything it misses, I just feel it's a shame that a feature like this doesn't get more love from any software I know of. Most music services offer decades stations and lots of radio stations do playlists from a certain year. Not being able to harness a huge personal library in the same way is sad. I do understand wanting to have an accurate library with album release dates, but not at the expense of enjoying music.

I am so glad that somebody has taken the first steps at remedying this. Thanks!