-
-
Notifications
You must be signed in to change notification settings - Fork 388
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PICARD-2607: Add a “_genre” variable #2545
base: master
Are you sure you want to change the base?
Conversation
The check has been moved here from picard/mbjson.py as we are loading _genres and _folksonomy_tags regardless of user setting. Thus for filling in the actual genre tag, we are checking the user preference.
I tried to follow the Contribution Guidelines as much as possible but was a bit unsure about having multiple commits but couldn't figure a good commit message so broke the commit into 4 parts. |
I am really unclear whether this is actually a bug fix. And if it is an enhancement instead, then if it is not going to do something much more generalised, then it least needs to make sure it will be compatible with a future generalised function. The issue with Genres is that there are so many competing sources of variable quality - many are free-form rather than a fixed list, those that are fixed lists have different fixed lists, and even when there are identical names, the definitions may be different or the allocation subjective. So if you are trying to fill a genre tag from multiple sources, you need to make sense of the mess you may often be given. In addition, there really needs to be needs to be a hierarchy i.e. there are many sub-forms of "Rock", and indeed these need to be many-to-many because there are also fusion genres which are sub-categories of more than one parent category. So Picard IMO really needs to provide several generalised functions...
I therefore encourage the authors and reviewers of this PR to think about the above and do their best to at least try to guide this PR into a form that is in the same general direction and which will at a minimum not make delivery of comprehensive genre functionality more difficult and ideally at least takes us a step in that direction. (See also: Lyric handling which has similar (but simpler) plugin-based requirements.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for this PR. I initially thought we can handle it more simple inside mbjson
and directly write the values for _genres
and _folksonomy_tags
in the metadadata object there. But your implementation of storing the list with counts is more complex but needed to run the proper filtering later on.
Also tests should be fixed.
picard/track.py
Outdated
config = get_config() | ||
tags = Counter(self._folksonomy_tags) | ||
tags += self.album._folksonomy_tags | ||
self.metadata['_folksonomy_tags'] = self._genres_to_metadata(tags, join_with=config.setting['join_genres']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To my understanding we can do this simpler and don't need to filter the values for _folksonomy_tags
and _genres
. We just include all the loaded values in either variable. Only the final genre
variable gets filtered.
But I asked UltimateRiff on PICARD-2607 for clarification also.
What I'm also unsure about if _folksonomy_tags
should strictly only contain the non-genre tags. In that case we would filter out everything that is in _genres
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I think _folksonomy_tags should only have the non-genre tags. But well I'll just wait to see what UltimateRiff expects before I implement it.
For the implementation though, is iterating through _genres.keys() and deleting them from _folksonomy_tags going to be good enough or is there a more efficient method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Would you expect the _genres and _folksonomy_tags variables to hold all genres / folksonomy tags, or them to be filtered according to the genre filter rules? I assumed the first.
- Would you expect the _folksonomy_tags variable to hold all folksoonomy tags (of which the genres are just a part of) or strictly only the non-genre tags?
on the first expectation, I'm honestly not sure what my preference would be
on the second tho, I think I'd expect _folksonomy_tags to be only non-genre tags
I think for first, including everything is the right way to go for now.
For the deleting genres from folksonomy tags, is my earlier approach fine?
Is iterating through _genres.keys() and deleting them from _folksonomy_tags going to be good enough or is there a more efficient method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rakim-0 @Sophist-UK We have a separate ticket PICARD-2054 for this, but this needs additional considerations. But it has no effect on this story, which is only about adding the Genres for separate sources would be handled by the genre handling functions in It's not unlikely that if we implement this the code added here will need some refactoring as well, but that needs to be done when PICARD-2054 actually gets implemented. |
I'll squash the smaller commits after I fix the tests. |
Fixed tests and resolved most comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the updates. This looks good to me and should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for your contribution.
If you don't mind, can you please enter a ticket to update the Picard documentation to include this new variable? I suspect that the page that needs to be updated is https://github.com/metabrainz/picard-docs/blob/master/variables/variables_basic.rst Thanks. |
This function would remove the tags that are present in genre.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you again. For me this looks great. Waiting for last review from @zas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declaring attributes as "protected" (prefixing them with underscore) is good, but then they aren't meant to be used outside the class, apart its subclasses.
For _genres
and _folksonomy_tags
, please check carefully, and use the getter method if not in the class itself or a subclass.
genres += self.album.genres | ||
use_folksonomy = config.setting['folksonomy_tags'] | ||
if use_folksonomy: | ||
genres += self.album._folksonomy_tags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
genres += self.album._folksonomy_tags | |
genres += self.album.folksonomy_tags |
if use_folksonomy: | ||
genres += self.album._folksonomy_tags | ||
else: | ||
genres += self.album._genres |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
genres += self.album._genres | |
genres += self.album.genres |
self.assertEqual(a.genres, { | ||
'genre1': 6, 'genre2': 3, | ||
'tag1': 6, 'tag2': 3}) | ||
self.assertEqual(a._genres, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(a._genres, { | |
self.assertEqual(a.genres, { |
self.assertEqual(a._genres, { | ||
'genre1': 6, 'genre2': 3 | ||
}) | ||
self.assertEqual(a._folksonomy_tags, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(a._folksonomy_tags, { | |
self.assertEqual(a.folksonomy_tags, { |
for artist in a._album_artists: | ||
self.assertEqual(artist.genres, { | ||
self.assertEqual(artist._folksonomy_tags, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(artist._folksonomy_tags, { | |
self.assertEqual(artist.folksonomy_tags, { |
'blue-eyed soul': 1, | ||
'pop': 3}) | ||
for artist in t._track_artists: | ||
self.assertEqual(artist.genres, { | ||
self.assertEqual(artist._folksonomy_tags, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(artist._folksonomy_tags, { | |
self.assertEqual(artist.folksonomy_tags, { |
@@ -770,7 +776,7 @@ def test_release_group(self): | |||
self.assertEqual(m['releasetype'], 'album') | |||
self.assertEqual(m['~primaryreleasetype'], 'album') | |||
self.assertEqual(m['~releasegroup'], 'The Dark Side of the Moon') | |||
self.assertEqual(r.genres, {'test2': 3, 'test': 6}) | |||
self.assertEqual(r._folksonomy_tags, {'test2': 3, 'test': 6}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(r._folksonomy_tags, {'test2': 3, 'test': 6}) | |
self.assertEqual(r.folksonomy_tags, {'test2': 3, 'test': 6}) |
|
||
def _add_folksonomy_tags(self): | ||
tags = Counter(self._folksonomy_tags) | ||
tags += self.album._folksonomy_tags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tags += self.album._folksonomy_tags | |
tags += self.album.folksonomy_tags |
|
||
def _add_genres(self): | ||
genre = Counter(self._genres) | ||
genre += self.album._genres |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
genre += self.album._genres | |
genre += self.album.genres |
genre += self.album._genres | ||
self._add_tags(genre, '_genres') | ||
|
||
def _delete_genres_from_tags(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we're in Track
and delete stuff in Album
, what about moving this method to Album
instead and calling it from here?
Aside from in tests, are these used outside the class? Because I wonder whether using protected attributes in tests is actually a good thing or not. (And funny enough about 20mins before reading this I was considering exactly this question but for PHP - except in PHP protected attributes are not actually accessible outside a class except through reflection manipulation). The reason I wonder is because the idea behind protecting them is to prevent their use in genuine application code that shouldn't have access - and tests are IMO a special case. So on the one hand, making them non-protected fits the language intent and avoids e.g. codacy flags, but then makes them available to other genuine code when they probably should be hidden - and on the other hand using protected attributes outside the class in tests may lead to issues if Python ever decides that protected really means protected. On the whole, my personal opinion leans towards continuing to have them as protected attributes and using them outside the class only in tests - and I am wondering how to achieve this in php tests using reflection. |
Summary
Adds a custom field for folksonomy tags and genre.
Problem
There was no custom field for folksonomy tags. User was forced to either set all genre as genre tags or all folksonomy tags as Genre.
This PR aims to resolve it by introducing two new tags _folksonomy_tag and _genre.
Solution
To get both _genres and _folksonomy tags from the MusicBrainz server, I removed the 'use_folksonomy` check in picard/items.py and stored the tags in respective variables. Then for writing the metadata, I added two new functions in picard/track.py that would update the metadata with _genre and _folksonomy_tag. And before writing genre into the file, check whether genre should be set to _folksonomy_tag or _genre itself.
Check for 'Use only my genre' option is still done in picard/items.py
Action
Additional actions required: