-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[Don't merge / Obsolete] Custom Tags #2656
Conversation
The Custom Tags right click menu closes when a tag is clicked. This is inconvenient. Maybe this happens because you are popping up a modal dialog? I don't understand the need for this modal. It is shown so briefly that it just looks like a graphical glitch. |
I noticed this, too. Don't know if QWidgetAction is used correctly here and how to batch changes before they are applied. The modal dialog is necessary to monitor progress when updating multiple selected tracks. This takes some time. We need some kind of feedback that Mixxx is currently missing for all selected track actions. This was a first try to achieve this. |
Select >= 20-30 tracks at once and you will see the progress bar. |
I don't understand why we need to give feedback to the user on the progress of this even if it takes more than a second. What does it matter to the user? Can't they just keep going using Mixxx? The task will be done soon enough in the background, right? |
I don't think a modal dialog is appropriate unless the task in progress should prevent doing anything else in Mixxx. We sure could use a progress bar somewhere in the library GUI which we could also use for batch analysis, but that's a separate topic from this PR. |
Freezing the UI is inconvenient. Remember, you could easily select all/thousands of tracks and accidentally trigger this action. |
Why would the GUI freeze? Is that because of the current database architecture with DB queries in the GUI thread? If I understand correctly, that problem will be obsolete with Aoide? |
Not really. Sure, you could accidentally right click. But accidentally right click then navigate multiple levels through a nested menu? Unlikely. |
I used the menu to add a custom tag. I did not see a way to add a faceted tag through this menu as I wanted. I expected to see my new custom tag appear the next time I opened the menu, but it did not. |
The UI stalls will not disappear magically. We still need to decouple UI interaction from executed tasks. The menu is currently read-only and populated solely from the JSON file. Another option would be to populate it from the database from all existing tracks, but the number of tags could be come huge. I think about adding tags entered manually with "Custom..." permanently to the menu by saving them into the JSON file. This is a simple and effective extension that does not involve any database interaction. If you want to delete them from the menu, you still need to edit the JSON file manually. |
Still build errors. I had to include one commit from @Holzhaus Serato PR to avoid merge conflicts. Not your fault, Jan ;) I messed around. |
👍 |
@Be-ing Done. Manually entered tag labels ("Custom...") are now permanently saved into your configuration file. Faceted tags without a score like "energy" or "valence" need to be handled differently. Probably by configuring which facets only have a score without a label. How do we add/remove those tags and select/assign a score to them? |
I'm unclear what the long term plan is for this JSON file. Is this JSON file the same format that Aoide will provide when asked for the list of all tags? |
Can you hack a QSlider into a QMenu item? |
No. This is just the Mixxx tagging format that is also written into file tags. A JSON representation of the |
The menu is only a quick proof-of-concept to access and modify the underlying data. It is supposed to be adapted and extended in the future. Maybe we exchange it with a more suitable UI paradigm when available. But at least we have some tools to start experimenting now. |
Why are faceted versus plain tags explicitly separated in the data format? Couldn't you implicitly figure out which tags are "plain" by them not having a label? I'm also unclear if the data model supports assigning a faceted tag without picking a label for it. This is something I would want. For example, I may want to apply a "house" tag, but not know what subgenres to assign it. |
Well, the proof-of-concept seems to work. If you don't want to improve the proof-of-concept GUI to fully implement all features, I think it would make sense to start working on the actual GUI. As discussed on Zulip, I think imitating Rekordbox's example would be good here. We can have a QTreeView popout panel activated by a button to the right of the library that would be independent of both WTrackTable and WLibrarySidebar. Cramming this into the already conceptually confused WLibrarySidebar would exacerbate the flaws of the current design. We don't need to overhaul the library GUI design all at once if we add new widgets separate from the current ones. To make it functional we just need to get WTrackTable to communicate the currently selected tracks to the new widget. |
I guess an empty key "" in a JSON object map is illegal and it would be awkward and error prone. That's why plain tags (without a faceted) need to be stored separately. Internally we store everything in a single map, where the entry with the empty facet contains the plain tags. Plain tag
Faceted tag
Each combination of the tuple (facet, label) may appear only once in You have 3 options for a faceted tag: No label, explicit score (mandatory if no label is specified): "facets": {
"house": <score>
} Very special case: With label, implicit score (= 1.0): "facets": {
"house": "My label"
} With label and explicit score: "facets": {
"house": ["My label", <score>]
} You could use 1-element arrays even for the first two variants. But the parser is tolerant and allows to omit the array structure in this case. The general structure for the plain tag part is |
Perhaps one way to handle this would be to treat an empty string as a special label. |
Valid JSON: {
"": [
"Top40",
["A plain tag label with a score", 0.45]
],
"genre": [
["House", 0.5],
["Deep House", 0.75]
],
"house": [
0.5,
["Deep", 0.75]
]
} In this example I have included two different options how to organize genre tags. I would prefer the first variant, but you are free to use whatever works for you. Ok, let's think about removing the "plain"/"facets" distinction if this is more intuitive. Thanks for the suggestions, @Be-ing 👍 |
If it loads it is a valid set of tags that could also be assigned to a single track. |
Remark: aoide uses "genre" facets and doesn't have a dedicated genre field. In Mixxx these will be mapped (= concatenated) into a single string. This is a special case we might need to think about. Since there is a genre field in file tags we need to provide a bridge anyway. This is what the |
Empty strings should already be parsed as an empty facet or label. I will add a test case. |
I have modified the JSON structure as suggested and transformed it into a single object without a syntactical distinction between plain and faceted tags. Plain tags are stored in the array behind the empty string key. This also helped to remove some special case handling in the code. See the example file in res/preferences how it looks like. |
I found a clever solution for suppressing the progress dialog during short processing times! You can now pass a grace period (default = 1000ms) to the track processor. The modal progress dialog is only shown if processing time exceeds this period. Works great. |
This feature won't be ready for 2.4.0 without a decent UI. |
I think it would be fine for its initial release to have a clunky proof-of-concept GUI that doesn't utilize its full capabilities. |
I consider both the database backend and file tag import/export as finished and stable while the stored configuration data depends on the yet unfinished UI. Let's aim for more frequent releases and avoid the mistakes from the past. We should probably focus on other, more important features to enable a quick 2.4 release with the new QJSEngine. Releasing a half-baked feature that then requires to migrate configuration data would cause more work in the future. |
Also, do you need the UI for testing? If not we could merge it without one and add the UI later. That way you won't run into merge conflicts and we make sure the code builds on all OSes. |
Ah OK, commenting race-condition. |
If you split off the GUI changes, we can merge the application logic code and potentially experiment with QML GUIs instead. |
I expect changes on how to store the user settings that are used for populating the track context menu. Currently this is just a reinterpretation of the JSON serialization instead of a dedicated format. It is sufficient for the simple use cases, but doesn't cover tags with an adjustable score or a discrete number of predefined score values. |
I am not sure if it is a good idea to merge unused code before we finish the actual feature. If we then find out that we need substantial changes we have to do a full migration of what has already been released. Not worth it. |
If you are willing to continue rebasing unmerged code for some indefinite time, sure, that's up to you. I would not do so myself. |
# Conflicts: # src/track/trackrecord.cpp # src/track/trackrecord.h
facetsWithMandatoryTagMappingConfig[] = { | ||
{CustomTags::kFacetGenre, kDefaultGenreTagMappingConfig}, | ||
{CustomTags::kFacetMood, kDefaultMoodTagMappingConfig}}; | ||
for (const auto [facet, defaultConfig] : facetsWithMandatoryTagMappingConfig) { |
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.
loop variable '[facet, defaultConfig]' of type 'const std::__1::pair<const mixxx::TagFacet &, const mixxx::TagMappingConfig &>' creates a copy from type 'const std::__1::pair<const mixxx::TagFacet &, const mixxx::TagMappingConfig &>' [-Wrange-loop-analysis]
@@ -454,7 +458,7 @@ void Track::emitChangedSignalsForAllMetadata() { | |||
emit titleChanged(getTitle()); | |||
emit albumChanged(getAlbum()); | |||
emit albumArtistChanged(getAlbumArtist()); | |||
emit genreChanged(getGenre()); | |||
emit genreTextChanged(getGenreText()); |
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.
Could we split off the genre text renamings and already merge them or is this too much 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.
This could probably reduce the change set. I will check that.
After #4101 has been merged I will split this PR into smaller PRs. The main data structure (including serialization) and the mapping to file tags can safely be merged at any time. Database and UI changes will be deferred. |
2021-05-13 I have included another preliminary database update that adds a time stamp for metadata synchronization (import/export). This requires to reapply schema update 37 and 38, either by resetting the version number manually or by starting Mixxx 2.3 at least once. The next restart will then reapply all preliminary schema updates and add the new column.
This PR includes a new approach on how to organize the code for individual features. It isolates (as far as possible) all the required pieces including database access and UI components/utilities in a separate folder instead of scattering them over the whole code base.
TODO
last_played_at column
to database (Library: Add last_played_at column #3140, database schema v34)mood
but not yet displayed or used)rating
and the labelmixxx.org
as a score valueAdd UI support for unlabeled, faceted tags that only have a score (Valence, Energy, ...)Not in the first step, maybe laterBUG: Modifications inDlgTrackInfo
are not saved. Probably caused by a previous merge conflict resolution, no time to investigate yet.Related issues
https://bugs.launchpad.net/mixxx/+bug/1831227
Custom Tags
Support for aoide-like (faceted) tags in Mixxx:
Disclaimer: This PR is intended to provide
No fancy UI and no query support or further integration into Mixxx. Just the foundation to manage and synchronize custom tags, compatible with the aoide data model.
Build
Requires CMake, SCons is no longer supported.
Configuration
The assignment of tags to tracks is currently handled by an additional context menu, adopted from the crates selection menu. This menu is populated with the contents of the file
custom_tags.json
, loaded from the Mixxx settings folder. An example set of predefined tags is provided inpreferences/custom_tags_example.json
. Use this as a starting point for your own tagging system.Update: No need to edit the JSON file manually. It will be created and both facets and labels can be added from the context menu.
Tagging
A custom tag is a triple with 3 elements:
QRegularExpression("[\\x{0021}-\\x{0040}\\x{005B}-\\x{007E}])"
Tags are composed of a facet identifier (optional) and a label text. Tags without a facet are called plain tags in contrast to faceted tags. The facet associates a tag with a predefined category, while the label assigns the actual content.
Facet identifiers are restricted to lowercase strings without whitespaces. These identifiers could be translated into human-readable strings for display purposes. How and where this is done needs to be defined. These display strings belong to the presentation layer and will not become part of the data model.
The optional score value in the range [0.0, 1.0] defines a weight or strength of the relationship. It defaults to 1.0 when unspecified.
Faceted tags may only have a score and no label. This is useful for encoding anonymous analysis features like energy = 0.75 that are represented by a facet and a score.
Searching
It works, sort of. It is just a temporary hack.
File Tags
Custom tags are permanently saved (import/export) as UTF-8 JSON and encoded into custom string fields in ID3v2, MP4, and Xiph/VorbisComment (FLAG, OGG, Opus) file tags. This allows to restore the newly added
track_custom_tags
table from file tags whenever needed.ID3v2: GEOB frame
Description:
"Mixxx CustomTags"
MIME type:
"application/json"
Filename: empty
Data: UTF-8
$ eye3D <filename.mp3>
MP4: Atom
Key "----:<mean>:<name>":
"----:org.mixxx.dj:CustomTags"
$ exiftool -l <filename.m4a>
$ AtomicParsley <filename.m4a> -t
Xiph/VorbisComment
Field name:
"MIXXX_CUSTOM_TAGS"
$ exiftool -l <filename.flac/.ogg/.opus>
$ metaflac --list <filename.flac>