From 553d8c075d3c325eb3c120d2a0996578dbad5b42 Mon Sep 17 00:00:00 2001 From: Mark Evens Date: Mon, 29 Oct 2018 23:41:23 +0000 Subject: [PATCH] Version 2.0.2 See readme for update details --- plugins/classical_extras/Readme.md | 187 +- plugins/classical_extras/__init__.py | 5146 +++-- .../options_classical_extras.ui | 18401 +++++++++------- plugins/classical_extras/suffixtree.py | 3 +- .../ui_options_classical_extras.py | 5164 +++-- 5 files changed, 16516 insertions(+), 12385 deletions(-) diff --git a/plugins/classical_extras/Readme.md b/plugins/classical_extras/Readme.md index 57ec1662..e107fa2d 100644 --- a/plugins/classical_extras/Readme.md +++ b/plugins/classical_extras/Readme.md @@ -1,5 +1,5 @@ # General Information -This is the documentation for version 2.0 of "classical\_extras". There may be beta versions later than this - check [my github site](https://github.com/MetaTunes/picard-plugins/tree/metabrainz/2.0/plugins/classical_extras) for newer releases. For further help, please review [the forum thread](https://community.metabrainz.org/t/classical-extras-2-0/394627) or post any new questions there. It only works with Picard version 2.0, **NOT** earlier versions. If you are using Picard 1.4.x, please choose the ["1.0" branch on github](https://github.com/MetaTunes/picard-plugins/tree/1.0/plugins/classical_extras) and use the latest release there - also use the [earlier forum thread](https://community.metabrainz.org/t/classical-extras-plugin/300217). +This is the documentation for version 2.0.2 of "classical\_extras". There may be beta versions later than this - check [my github site](https://github.com/MetaTunes/picard-plugins/tree/metabrainz/2.0/plugins/classical_extras) for newer releases. For further help, please review [the forum thread](https://community.metabrainz.org/t/classical-extras-2-0/394627) or post any new questions there. It only works with Picard version 2.0, **NOT** earlier versions. If you are using Picard 1.4.x, please choose the ["1.0" branch on github](https://github.com/MetaTunes/picard-plugins/tree/1.0/plugins/classical_extras) and use the latest release there - also use the [earlier forum thread](https://community.metabrainz.org/t/classical-extras-plugin/300217). This version has only been tested with FLAC and mp3 files. It does work with m4a files, but Picard does not write all m4a tags (see further notes for iTunes users at the end of the "works and parts tab" section). "Classical Extras" populates tags and hidden variables in Picard with information from the MusicBrainz database about the recording, artists and work(s), and of any containing works, passing up through multiple work-part levels until the top is reached. The "Options" page (Options->Options->Plugins->Classical Extras) allows the user to determine how hidden variables are written to file tags, as well as a variety of other options. @@ -11,6 +11,11 @@ Tags are output depending on the choices specified by the user in the Options Pa If the Options Page does not provide sufficient flexibility, users familiar with scripting can write Tagger Scripts to access the hidden variables directly. ## Updates +Version 2.0.2: Changed layout of tabs - the order is now Artists, Works, Genres and Tag-mapping. The help tab has been much reduced as it was of limited assistance and difficult to maintain. There is a lot of owing to the use of context-sensitive help and the readme file contains the latest full documentation. +Added a check-box on the genres tab to enable/disable genre filtering (previously the genre names would have to be blank to eliminate filtering and in any case this was buggy). +A general code tidy-up has led to significant performance enhancements. The only significant slowing factor is now the unavoidable MusicBrainz 1 look-up per second constraint (for looking up works). +A number of changes have been made to the way in which "extended" metadata is supplied - i.e. where title metatdata is combined with the "canonical" MusicBrainz work names. Hopefully the result is a more consistent and helpful presentation. Also some minor changes to the way in which text is eliminated to arrive at part names, including a new option on the "advanced" tab to "Allow blank part names for arrangements and part recordings, if an arrangement/partial label is provided" (see documentation under the advanced tab section for more details). Plus bug fixes. + Version 2.0.1: Minor update to add _composer_lastnames variable. Version 2.0: Major overhaul of version 0.9.4 to achieve Picard 2.0 and Python 3.7 compatibility. All webservice calls re-written for JSON rather than XmlNode responses. Periods are written to tag in date order. Addition of sub-options for inclusion of key signature information in work names. If the MB database has circular work references (i.e a parent is a descendant of itself) then these will be trapped, ignored and reported. Numerous small refinements, especially of text comparison algorithms (e.g. option to control removal of prepositions - see advanced tab). Bug fixes. @@ -35,7 +40,7 @@ After installation, go to the Options Page and modify choices as required. There ## Artists tab There are five coloured sections as shown in the screen image below: -![Artist options](http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/artists/) +![Artist options](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/artists/) 1. "Create extra artist metadata" should be selected otherwise this section (and the tag mapping section) will not run. This is the default. @@ -66,7 +71,7 @@ There are five coloured sections as shown in the screen image below: The bottom box then (a) allows a choice as to whether aliases will over-ride "credited as" names or vice versa and (b) whether, if there are still some names in non-Latin script, these should be replaced (this will always remove middle [patronymic] names from Cyrillic-script names [but does not deal fully with other non-Latin scripts]; it is based on the sort names wherever possible). - Note that **none of this processing affects the contents of the "artist or "album\_artist" tags**. These tags may be either work-artists or performing artists. Their contents are determined by the standard Picard options "translate artist names" and "use standardized artist names" in Options-->Metadata. If "translate name" is selected, the name will be the alias or (if no alias) the 'unsorted' sort-name; otherwise the name will be the MusicBrainz name if "use standardized artist names" is selected or the "credited as" name (if available) if it is not selected. + Note that **none of this processing affects the contents of the "artist" or "album\_artist" tags, unless they are replaced by a "tag mapping" action**. These tags may be either work-artists or performing artists. Their contents are determined by the standard Picard options "translate artist names" and "use standardized artist names" in Options-->Metadata. If "translate name" is selected, the name will be the alias or (if no alias) the 'unsorted' sort-name; otherwise the name will be the MusicBrainz name if "use standardized artist names" is selected or the "credited as" name (if available) if it is not selected. Using the saved options to over-ride the displayed options has no effect on this as the processing takes place in Picard itself, not the plugin (but **over-write** will over-write all Picard and plugin options with the saved ones). 3. "Recording artist options". In MusicBrainz, the recording artist may be different from the track artist. For classical music, the MusicBrainz guidelines state that the track artist should be the composer; however the recording artist(s) is/are usually the principal performer(s). @@ -126,74 +131,11 @@ There are five coloured sections as shown in the screen image below: Note that if the 'output' tags are not specified, then internal 'hidden' variables will still be available for use in the tag-mapping section (called album\_notes and track\_notes). -## Tag mapping tab -There are two coloured sections as shown in the screen image below: - -![Tag mapping options](http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/tag-mapping/) - -Note that the "Create extra artist metadata" option needs to be selected on the Artist tab for these sections to run. - -1. "Initial tag processing": This takes place before any of the detailed tag mapping in the second section. - - "Remove Picard-generated tags before applying subsequent actions?". Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options->Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive. - - "List existing file tags which will be appended ...": This refers to the tags which already exist on files which have been matched to MusicBrainz, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if 'Clear existing tags' is specified in the main Options->Tags screen) or keep them (if 'Preserve these tags...' is specified after the 'Clear existing tags' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below) or otherwise used. List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if "Clear existing tags" is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular "is\_classical" (which is set by SongKong to '1' if the track is deemed to be classical, based on an extensive database) is used to add 'classical' to the variable "\_cea\_worktype", if "Infer work types" is selected in the first section of the Artists tab. If you include "is\_classical" in this list then any files which have "is\_classical" = 1 will be treated as being classical, regardless of the genre tag. - - Note that if "Split lyrics tag" is specified (see the Artists tab), then the tag named there will be included in the "...existing file tags..." list and does not need to be added in this section. - - "Clear any previous file tags...": This operates in an almost similar way to the main Picard option (Options->Tags->"Clear existing tags"). All existing file tags will be cleared **unless** they are in the main Picard "Preserve tags..." option or the "...existing file tags..." list. The main differences from the basic Picard option are that (a) artwork is always preseved - i.e. this largely addresses [PICARD-257](https://tickets.metabrainz.org/browse/PICARD-257) and (b) the tags that are not kept are **not shown as deleted** in the bottom pane of Picard; however, a warning tag is written. - -2. "Tag map details". This section permits the contents of any hidden variable or tag to be written to one or more tags. - - * **Sources**: - Some of the most useful sources are available from the drop-down list. Otherwise they can simply be typed in the box. Click on the "source from" button to enable entry (otherwise the text box / drop-down for the source is locked). Some useful names are: - - - soloists : List of performers (with instruments in brackets), who are NOT ensembles or conductors, separated by semi-colons. Note they may not strictly be "soloists" in that they may be part of an ensemble. - - soloist\_names : Names of the above (i.e. no instruments). - - vocalists / instrumentalists / other\_soloists : Soloists who are vocalists, instrumentalists or not specified, respectively. - - vocalist\_names / instrumentalist\_names : Names of vocalists / instrumentalists (i.e. no instrument / voice). - - ensembles : List of performers who are ensembles (with type / instruments - e.g. "orchestra" - in brackets), separated by semi-colons. - - ensemble\_names : Names of the above (i.e. no instruments). - - album\_soloists : Sub-list of soloist\_names who are also album artists. - - album\_conductors : List of conductors who are also album artists. - - album\_ensembles: Sub-list of ensemble\_names who are also album artists. - - album\_composers : List of composers who are also album artists. - - album\_composer\_lastnames : Last names of composers, of ANY track on the album, who are also album artists. This is the source used to prefix the album name (when that option is selected). - - support\_performers : Sub-list of soloist\_names who are NOT album artists. - - composers : List of composers, after applying the naming options in the artists tab. - - conductors : List of conductors, after applying the naming options in the artists tab. - - arrangers : Includes all arrangers and instrument arrangers (except more specific roles such as orchestrators) - the standard Picard tag omits some. - - orchestrators : Arrangers who are orchestrators. - - leaders : AKA concertmasters. - - chorusmasters : as distinct from conductors (chorus masters may rehearse the choir but not conduct the performance). - - Note that the Classical Extras sources for all artist types are spelled in the plural (to differentiate from the native Picard tags). Most of the names are for artist data and are sourced from hidden variables (prefixed with "\_cea\_" or "\_cwp\_"). In specifying the source, the prefix is not necessary - e.g. "arrangers" will pick up all data in \_cea\_arrangers and \_cwp\_arrangers (covering those with recording and work relationships respectively). Using the prefix will get just the specific variable. - - In addition, the drop-down contains some typical combinations of multiple sources (see note on multiple sources below). - - Any Picard tag names can also be typed in as sources. Any hidden variables may also be used. Any source names which are prefixed by a backslash will be treated as string constants; blanks may also be used. - - It is possible to specify multiple sources. If these are separated by a comma, then each will be appended to the mapped tag(s) (if not already filled or if not "conditional"). So, for example, a source of "album\_soloists, album\_conductors, album\_ensembles" mapped to a tag of "artist" with "conditional" ticked will fill artist (if blanked) by album\_soloists, if any, otherwise album\_conductors etc. Sources separated by a + will be concatenated before being used to fill the mapped tags. The concatenated result **will only be applied if the contents of each of the sources to be concatenated is non-blank** (note that this constraint only applies to **concatenation** of multiple sources). No spaces will be added on concatenation, so these have to be added explicitly by concatenating "\ ". So, for example "ensemble\_names + \ (conducted by + conductors +\\), ensemble\_names", with "Conditional" selected, will yield something like "BBC Symphony Orchestra (conducted by Walter Weller)" or just "BBC Symphony Orchestra" if there is no conductor. **Do not use any commas in text strings**. - - Another example: to add the leader's name in brackets to the tag with the performing orchestra, put "\\ (leader +leaders+\\)" in the source box and the tag containing the orchestra in the tag box. If there is no leader, the text will not be appended. - - The tag mapping section is not restricted to artist metadata - any metadata or hidden variablescan be used. - - * **Tags**: - Enter the (comma-separated) "destination" tag names into which the sources should be written (case sensitive). Note that this will result in the source data being APPENDED in the tag - it will not overwrite the existing contents. Check "Conditional?" if the tag is only to be updated if it is previously blank (all non-empty sources in the current line will be applied in sequence). The lines will be applied in the order shown. Users should be able to achieve most requirements via a combination of blanking tags, using the right source order and "conditional" flags. For example, to overwrite a tag sourced from "composer" with "conductor", specify "conductor" first, then "composer" as conditional. Note that, for example, to demote the MB-supplied artist to only appear if no other listed choices are present, blank the artist tag and then add it as a conditional source at the end of the list. - - * **"Also populate sort tags"**: - If a sort tag is associated with the source tag then the sort names will be placed in a sort tag corresponding to the destination tag. Note that the only explicit sort tags written by Picard are for artist, albumartist and composer. Picard also writes hidden variables '\_artists\_sort' and 'albumartists\_sort' (note the plurals - these are the sort tags for multi-valued alternatives 'artists' and '\_albumartists'). To be consistent with this approach, the plugin writes hidden variables for other tags - e.g. '\_arranger\_sort'. The plugin also writes hidden sort variables for the various hidden artist variables - e.g. '\_cwp\_librettists' has a matching sort variable '\_cwp\_librettists\_sort'. Therefore most artist-type sources **will** have a sort tag/variable associated with them and these will be placed in a destination sort tag if this option is selected - **in other words, selecting this option will cause most destination tags to have associated sort tags. Furthermore, any hidden sort variables associated with tags which are not listed explicitly in the tag mapping section will also be written out as tags** (i.e. even if the related tags are not included as destination tags). Note, however, that composite sources (e.g. " ensemble\_names + \; + conductors") do not have sort tags associated with them. - - If this option is not selected, no additional sort tags will be written, but the hidden variables will still be available, so if a sort tag is required explicitly, just map the sort tag directly - e.g. map 'conductors\_sort' to 'conductor\_sort'. - - More complex operations can be built using tagger scripts. If required, these can be set to run conditionally by setting a tag or hidden variable in this section and then testing it in the script. - ## Work and parts tab There six coloured sections as shown in the screen print below: -![Works and parts options](http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/work-parts/) +![Works and parts options](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/work-parts/) 1. "Include all work levels" should be selected otherwise this section will not run. This is the default. @@ -208,7 +150,7 @@ There six coloured sections as shown in the screen print below: - "Use only metadata from canonical works". The names from the hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below). - "Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles **where it is significantly different**. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager). - ![Respighi](http://music.highmossergate.co.uk/images/Respighi.jpg) + ![Respighi](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/respighi/) * **Source of canonical work text**. Where either of the second two options above are chosen, there is a further choice to be made: - "Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. E.g. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose. @@ -255,9 +197,11 @@ There six coloured sections as shown in the screen print below: 6. "SongKong-compatible tag usage". - "Use work tags on file (no look up on MB) if Use Cache selected": This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, "Use cache" also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. **In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.** + "Use work tags on file (no look up on MB) if Use Cache selected": This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, "Use cache" also needs to be selected. Although faster, many of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. Other information, such as composed-dates will also be missing. **In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless you have already tagged your files with SongKong and speed is more important than quality.** - "Write SongKong-compatible work tags" does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data). + "Write SongKong-compatible work tags" does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data). The same caveats as those above apply. + + **Note that, as from version 2.0.2, there is no significant speed difference between (basic) SongKong and Picard with Classical Extras, so this feature is only ever useful if the album has already been tagged with SongKong. The default for both these options is unchecked. @@ -269,7 +213,7 @@ There six coloured sections as shown in the screen print below: This section is dependent on both the artists and workparts sections. If either of those sections are not run then this section will not operate correctly. At the very top of the tab is a checkbox "Use Muso reference database...". For [Muso](http://klarita.net/muso.html) users, selecting this enables you to use reference data for genres, composers and periods which have been entered in Muso's "Options->Classical Music" section. Regardless as to whether this is selected, there are then three main coloured sections, each with a number of subsections. The details in each section differ depending on whether the "Muso" option is selected. The screen print below shows the options assuming it is not selected (differences occurring when "Muso" is selected are discussed later): -![Genres etc.](http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/genres-plain/) +![Genres etc.](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/genres-plain/) 1. "Genres". Two separate tags may be used to store genre information, a main genre tage (usually just "genre") and a sub-genre tag. These need to be specified at the top of the section. If either is left blank then the related processing will not run. @@ -286,13 +230,8 @@ This section is dependent on both the artists and workparts sections. If either Orchestral, Concerto, Choral, Opera, Duet, ,Trio, Quartet, Chamber music, Aria ('classical values') and Vocal, Song, Instrumental ('generic values'). If the track is a recorded work and the track artist is the composer (i.e. MusicBrainz 'classical style'), the candidate genre values will also include "Classical". The 'classical values' will only be included as candidate genres if the track is deemed to be 'classical' by some part of the genre processing section. * **Allowed genres** - Four boxes are provided for lists of genres which are "allowed" to appear in the specified tags. Each list should be comma-separated (and no commas in any genre name). Candidate genres matching those in a "main genre" box will be added to the specified main genre tag. Similarly for sub-genres. If a candidate genre matches a 'classical genre' (in one of the top two boxes), then the track will be deemed to be "Classical" (see next part for more details). - - If none of the boxes are filled, then all genres found will be included. - - If only one main genre box is filled (e.g. "classical main genres") and there is a matching genre found, then remaining genres will only be included if they match genres in that box or the related sub-genre box. So in this case if "classical sub-genres" is filled and "general sub-genres" is not, then only matching genres will be included. - - If both "classical" and "general" main genre boxes are filled, then only genres matching those boxes or "classical sub-genres" will be included. + A check-box (ticked by default) enables this section. If it is unchecked, then no genre filtering is applied - all 'candidate genres' will be written to the genre tab. + Four boxes are provided for lists of genres which are "allowed" to appear in the specified tags. Each list should be comma-separated (and no commas in any genre name). Candidate genres matching those in a "main genre" box will be added to the specified main genre tag. Similarly for sub-genres. If a candidate genre matches a 'classical genre' (in one of the top two boxes), then the track will be deemed to be "Classical" (see next part for more details). You may also enter a genre name to be used if no matching main genre is found (otherwise the tag will be blank). @@ -326,7 +265,7 @@ This section is dependent on both the artists and workparts sections. If either Users of [Muso](http://klarita.net/muso.html) have additional capabilities, illustrated in the following screen, which appear when the option "Use Muso reference database ..." is selected at the top of the tab. -![Genres etc. - Muso](http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/genres-muso/) +![Genres etc. - Muso](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/genres-muso/) For these options to work, the path/name of the Muso reference database needs to be specified on the advanced tab. The default path is "C:\\Users\\Public\\Music\\muso\\database" and the default filename is "Reference.xml". The additional options are as follows. @@ -355,12 +294,79 @@ Note that non-Muso users may also use this functionality, if they wish, by manua 1800 1850 + +## Tag mapping tab +There are two coloured sections as shown in the screen image below: + +![Tag mapping options](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/tag-mapping/) + +Note that the "Create extra artist metadata" option needs to be selected on the Artist tab for these sections to run. + +1. "Initial tag processing": This takes place before any of the detailed tag mapping in the second section. + + "Remove Picard-generated tags before applying subsequent actions?". Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options->Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive. + + "List existing file tags which will be appended ...": This refers to the tags which already exist on files which have been matched to MusicBrainz, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if 'Clear existing tags' is specified in the main Options->Tags screen) or keep them (if 'Preserve these tags...' is specified after the 'Clear existing tags' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below) or otherwise used. List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if "Clear existing tags" is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular "is\_classical" (which is set by SongKong to '1' if the track is deemed to be classical, based on an extensive database) is used to add 'classical' to the variable "\_cea\_worktype", if "Infer work types" is selected in the first section of the Artists tab. If you include "is\_classical" in this list then any files which have "is\_classical" = 1 will be treated as being classical, regardless of the genre tag. + + **Make sure that any tags listed here are not also included in Picard's "Preserve .. tags.." option, otherwise Picard will prevent the tag from being amended** + + Note that if "Split lyrics tag" is specified (see the Artists tab), then the tag named there will be included in the "...existing file tags..." list and does not need to be added in this section. + + "Clear any previous file tags...": This operates in an almost similar way to the main Picard option (Options->Tags->"Clear existing tags"). All existing file tags will be cleared **unless** they are in the main Picard "Preserve tags..." option or the "...existing file tags..." list. The main differences from the basic Picard option are that (a) artwork is always preseved - i.e. this largely addresses [PICARD-257](https://tickets.metabrainz.org/browse/PICARD-257) and (b) the tags that are not kept are **not shown as deleted** in the bottom pane of Picard; however, a warning tag is written. + +2. "Tag map details". This section permits the contents of any hidden variable or tag to be written to one or more tags. + + * **Sources**: + Some of the most useful sources are available from the drop-down list. Otherwise they can simply be typed in the box. Click on the "source from" button to enable entry (otherwise the text box / drop-down for the source is locked). Some useful names are: + + - soloists : List of performers (with instruments in brackets), who are NOT ensembles or conductors, separated by semi-colons. Note they may not strictly be "soloists" in that they may be part of an ensemble. + - soloist\_names : Names of the above (i.e. no instruments). + - vocalists / instrumentalists / other\_soloists : Soloists who are vocalists, instrumentalists or not specified, respectively. + - vocalist\_names / instrumentalist\_names : Names of vocalists / instrumentalists (i.e. no instrument / voice). + - ensembles : List of performers who are ensembles (with type / instruments - e.g. "orchestra" - in brackets), separated by semi-colons. + - ensemble\_names : Names of the above (i.e. no instruments). + - album\_soloists : Sub-list of soloist\_names who are also album artists. + - album\_conductors : List of conductors who are also album artists. + - album\_ensembles: Sub-list of ensemble\_names who are also album artists. + - album\_composers : List of composers who are also album artists. + - album\_composer\_lastnames : Last names of composers, of ANY track on the album, who are also album artists. This is the source used to prefix the album name (when that option is selected). + - support\_performers : Sub-list of soloist\_names who are NOT album artists. + - composers : List of composers, after applying the naming options in the artists tab. + - conductors : List of conductors, after applying the naming options in the artists tab. + - arrangers : Includes all arrangers and instrument arrangers (except more specific roles such as orchestrators) - the standard Picard tag omits some. + - orchestrators : Arrangers who are orchestrators. + - leaders : AKA concertmasters. + - chorusmasters : as distinct from conductors (chorus masters may rehearse the choir but not conduct the performance). + + Note that the Classical Extras sources for all artist types are spelled in the plural (to differentiate from the native Picard tags). Most of the names are for artist data and are sourced from hidden variables (prefixed with "\_cea\_" or "\_cwp\_"). In specifying the source, the prefix is not necessary - e.g. "arrangers" will pick up all data in \_cea\_arrangers and \_cwp\_arrangers (covering those with recording and work relationships respectively). Using the prefix will get just the specific variable. + + In addition, the drop-down contains some typical combinations of multiple sources (see note on multiple sources below). + + Any Picard tag names can also be typed in as sources. Any hidden variables may also be used. Any source names which are prefixed by a backslash will be treated as string constants; blanks may also be used. + + It is possible to specify multiple sources. If these are separated by a comma, then each will be appended to the mapped tag(s) (if not already filled or if not "conditional"). So, for example, a source of "album\_soloists, album\_conductors, album\_ensembles" mapped to a tag of "artist" with "conditional" ticked will fill artist (if blanked) by album\_soloists, if any, otherwise album\_conductors etc. Sources separated by a + will be concatenated before being used to fill the mapped tags. The concatenated result **will only be applied if the contents of each of the sources to be concatenated is non-blank** (note that this constraint only applies to **concatenation** of multiple sources). No spaces will be added on concatenation, so these have to be added explicitly by concatenating "\ ". So, for example "ensemble\_names + \ (conducted by + conductors +\\), ensemble\_names", with "Conditional" selected, will yield something like "BBC Symphony Orchestra (conducted by Walter Weller)" or just "BBC Symphony Orchestra" if there is no conductor. **Do not use any commas in text strings**. + + Another example: to add the leader's name in brackets to the tag with the performing orchestra, put "\\ (leader +leaders+\\)" in the source box and the tag containing the orchestra in the tag box. If there is no leader, the text will not be appended. + + The tag mapping section is not restricted to artist metadata - any metadata or hidden variablescan be used. + + * **Tags**: + Enter the (comma-separated) "destination" tag names into which the sources should be written (case sensitive). Note that this will result in the source data being APPENDED in the tag - it will not overwrite the existing contents. Check "Conditional?" if the tag is only to be updated if it is previously blank (all non-empty sources in the current line will be applied in sequence). The lines will be applied in the order shown. Users should be able to achieve most requirements via a combination of blanking tags, using the right source order and "conditional" flags. For example, to overwrite a tag sourced from "composer" with "conductor", specify "conductor" first, then "composer" as conditional. Note that, for example, to demote the MB-supplied artist to only appear if no other listed choices are present, blank the artist tag and then add it as a conditional source at the end of the list. + + * **"Also populate sort tags"**: + If a sort tag is associated with the source tag then the sort names will be placed in a sort tag corresponding to the destination tag. Note that the only explicit sort tags written by Picard are for artist, albumartist and composer. Picard also writes hidden variables '\_artists\_sort' and 'albumartists\_sort' (note the plurals - these are the sort tags for multi-valued alternatives 'artists' and '\_albumartists'). To be consistent with this approach, the plugin writes hidden variables for other tags - e.g. '\_arranger\_sort'. The plugin also writes hidden sort variables for the various hidden artist variables - e.g. '\_cwp\_librettists' has a matching sort variable '\_cwp\_librettists\_sort'. Therefore most artist-type sources **will** have a sort tag/variable associated with them and these will be placed in a destination sort tag if this option is selected - **in other words, selecting this option will cause most destination tags to have associated sort tags. Furthermore, any hidden sort variables associated with tags which are not listed explicitly in the tag mapping section will also be written out as tags** (i.e. even if the related tags are not included as destination tags). Note, however, that composite sources (e.g. " ensemble\_names + \; + conductors") do not have sort tags associated with them. + + If this option is not selected, no additional sort tags will be written, but the hidden variables will still be available, so if a sort tag is required explicitly, just map the sort tag directly - e.g. map 'conductors\_sort' to 'conductor\_sort'. + + More complex operations can be built using tagger scripts. If required, these can be set to run conditionally by setting a tag or hidden variable in this section and then testing it in the script. + + ## Advanced tab Hopefully, this tab should not be much used. In any case, it should not need to be changed frequently. There are seven sections as shown in the sceeen print below: -![Advanced options](http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/advanced/) +![Advanced options](https://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/advanced/) 1. "General". There is only one checkbox - "Do not run Classical Extras for tracks where no pre-existing file is detected (warning tag will be written)". This option will disable Classical Extras processing if no file is present; this means (for example) that single discs from box sets can be loaded without incurring the additional processing overhead (work look-ups etc.) for all the other discs. Also if a compilation album is loaded, where the tracks are on multiple releases, the plugin will only process the release tracks which match. If a file is present but it does not yet have a MusicBrainz trackid tag, then it will initially be treated in the same way as a non-existent file; however, after the initial loading it will (if matched by Picard) be given a MB trackid and "refreshing" the release will result in any such tracks being processed by Classical Extras, while the unmatched tracks are left untouched. @@ -370,12 +376,14 @@ If it is important that only whole words are to be matched, be sure to include a 3. "Work levels". This section has parameters applicable to the "works and parts" functions. * **Max number of re-tries to access works (in case of server errors)**. Sometimes MB lookups fail. Unfortunately Picard (currently) has no automatic "retry" function. The plugin will attempt to retry for the specified number of attempts. If it still fails, the hidden variable \_cwp\_error will be set with a message; if error logging is checked in section 5, an error message will be written to the log and the contents of \_cwp\_error will be written out to a special tag called "001\_errors" which should appear prominently in the bottom pane of Picard. The problem may be resolved by refreshing, otherwise there may be a problem with the MB database availability. It is unlikely to be a software problem with the plugin. + + * **Allow blank part names for arrangements and part recordings, if an arrangement/partial label is provided**. The default is checked (true) - in which case where an arrangement has the same name as the original work, the part will just show the arrangement/partial label (e.g. "Arrangement:"). If the option is blank (false), there will be text for the part name (which may be the same name as the parent work). Note that if "extended" metadata is being used, then blank part names will always have the title metadata added {in curly brackets} regardless of whether it is similar to the canonical part name. * **Removal of common text between parent and child works**. This section controls the naming of parts, by stripping parent text from the work name. If the work begins with the parent name followed by punctuation then the common text will always be stripped to give the part name (even if there are punctuation or some other minor differences). - However, common text which is not followed by punctuation or which is not at the start may also be stripped: to prevent this, set "Minimum number of similar words required before eliminating (other than at start)" to zero. Otherwise common text longer than the specified number of words (default = 2) will be stripped. + However, common text which is not followed by punctuation or which is not at the start may also be stripped: to prevent this, set "Minimum number of similar words required before eliminating (other than at start)" to zero. Otherwise common text longer than the specified number of words (default = 2) will be stripped. (Note that this minimum is over-ridden by the previous option - i.e. if a smaller number of words than the minimum could be eliminated and would result in a blank part name, and that is allowed by the previous option, then the words will be removed). - * **How title metadata should be included in extended metadata**. This subsection contains various parameters affecting the processing of strings in titles. (Some of it also affects the elimination of common text between parent and child works referred to above). Because titles are free-form, not all circumstances can be anticipated. If pure canonical works are used ("Use only metadata from canonical works" and, if necessary, "Full MusicBrainz work hierarchy" on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below: + * **How title metadata should be included in extended metadata**. This subsection contains various parameters affecting the processing of strings in titles, where these are used to "extend" the text in work names. This is generally only relevant if "Use canonical work metadata enhanced with title text" is selected on the "Works and parts" tab, although the "prefixes" section is also relevant if "Use only metadata from title text" is selected. Because titles are free-form, not all circumstances can be anticipated. If pure canonical works are used ("Use only metadata from canonical works" and, if necessary, "Full MusicBrainz work hierarchy" on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below: * "Proximity of new words". When using extended metadata - i.e. "metadata enhanced with title text", the plugin will attempt to remove similar words in the canonical work name (in MusicBrainz) and the title before extending the canonical name. After removing such words, a rather "bitty" result may occur. To avoid this, any new words with the specified proximity will have the words between them (or up to the start/end) included even if they repeat words in the work name. @@ -383,13 +391,14 @@ If it is important that only whole words are to be matched, be sure to include a * "Proportion of a string to be matched ... for it to be considered essentially similar..." (default = 66%). If the title and work descriptions are largely the same then the title text will not be used to extend the work text, even if there are some new words. - * "Prepositions". Words listed here will not generally be treated as "new" (i.e. if they are in the title text but not in the work text, they will not be included in the "extended" text) unless they precede a new word which is not itself a preposition. Note that, although the term "preposition" is used here, because that is the obvious usage, any word can be listed. A group of more than one such words will be treated as new if they are not in the work text and they precede a new word which is not listed as a preposition. + * "Prepositions and conjunctions". Words listed here will not generally be treated as "new" (i.e. if they are in the title text but not in the work text, they will not be included in the "extended" text) unless they precede a new word which is not itself a preposition. Note that, although the term "preposition" is used here, because that is the obvious usage, any word can be listed. A group of more than one such words will be treated as new if they are not in the work text and they precede a new word which is not listed as a preposition. * "Prefixes". When using "metadata from titles" or extended metadata, the structure of the works in MusicBrainz is used to infer the structure in the title text, so strings that are repeated between tracks which are part of the same MusicBrainz work will be treated as "higher level". This can lead to anomalies if, for instance, the titles are "Work name: Part 1", "Work name: Part 2", "Part" is repeated and so will be treated as part of the parent work name. Specifying such words in "Prefixes" will prevent this. - * "Synonyms". These words will be considered equivalent when comparing work name and title text. Thus if one word appears in the work name, that and its synonym will be removed from the title in extending the metadata (subject to the proximity setting above). Note that only single word synonyms are allowed, with no duplicates or punctuation. Each entry should be a pair (2-tuple) in the form *(key word, equivalent word)* - no quote marks are necessary. Each pair should be separated by a forward slash - /. + * "Synonyms". These words/phrases will be considered equivalent when comparing work name and title text. Thus if one word/phrase appears in the work name, it and its synonyms will be removed from the title in extending the metadata (subject to the proximity setting above). Each entry should be a tuple in the form *(key word/phrase, ..., equivalent word/phrase)* - no quote marks are necessary. Each tuple should be separated by a forward slash - /. Tuples may contain multiple synonyms - each item in a tuple will be treated as similar when comparing works/parts and titles. The text in tags will be unaltered. Spaces and punctuation are permitted, as are regular expressions, but unless entering regex, use backslash \ to escape any regex metacharacters \ ^ $ . | ? * + ( ) [ ] { + Also escape commas , and forward slashes /. Do not enclose strings in quote marks. The last member of the tuple should be the canonical form (i.e. the one to which others are converted for comparison) and must be a normal string (not a regex). The sequence of synonyms within a tuple may be important, particularly if one synonym is a subset of another - always list the longer one first, as the matching will proceed in the listed order. Thus if "nro" and "nr" are synonyms of "no", then list them thus: (nro, nr, no). This will ensure that "nro" gets matched to "nro" and not to "nr". - * "Replacements". These words/phrases will be replaced in the title text in extended metadata, regardless of the text in the work name. Each entry should be a pair (2-tuple) in the form *(original text, replacement text)* - no quote marks are necessary. Each pair should be separated by a forward slash - /. If required, the original text (to be replaced) can be a regular expression, in which case it must be surrounded by double exclamation marks, thus: *(!!regex here!!, replacement text here)* + * "Replacements". These are entered in a similar fashion to synonyms. The difference is that the last item in a tuple will be used to replace any text in earlier items in the tuple. This last element may also be left blank (after the preceding comma) in order to remove any text matching any of the earlier items. 4. "Genres etc. ...". This is only required if Muso-specific options are used for genres/periods. Specify the path and file name for the reference database (the default is the Muso default for a shared database). Note that the database is only loaded when Picard starts so you will need to restart Picard is these options are changed. @@ -411,7 +420,7 @@ If it is important that only whole words are to be matched, be sure to include a ***Note that* *very occasionally (if the tag containing the options has been corrupted) use of this option may cause an error. In such a case you will need to deselect the "over-ride" option and set the required options manually; then save the resulting tags and the corrupted tag should be over-written*** - The last checkbox, "Overwrite options in Options Pages", is for **VERY CAREFUL USE ONLY**. It will cause any options read from the saved tags (if the relevant box has been ticked) to over-write the options on the plugin Options Page UI. The intended use of this is if for some reason the user's preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, and will always be turned off when starting Picard, to prevent inadvertant use. Far better is to make a **backup copy** of the picard.ini file. + The last checkbox, "Overwrite options in Options Pages", is for **VERY CAREFUL USE ONLY**. It will cause any options read from the saved tags to over-write the options on the plugin Options Page UI. (Note that it will only operate if all the "Over-ride plugin options..." boxes are checked as well.) The intended use of this is if for some reason the user's preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, and will always be turned off when starting Picard, to prevent inadvertant use. Far better is to make a **backup copy** of the picard.ini file. # Information on hidden variables @@ -562,16 +571,18 @@ Leave the "title" tag unchanged or make it a combination of work and movement. # Possible Enhancements All planned functionality has been included in version 2.0. Please post any suggestions for improvements to the forum. -# Technical Matters +# Technical Matters (of possible interest to developers) Issues were encountered with the Picard API in that there is not a documented way to let Picard know that it is still doing asynchronous tasks in the background and has not finished processing metadata. Many thanks to @dns\_server for assistance in dealing with this and to @sophist for the albumartist\_website code which I have borrowed from. I have tried to add some more comments to help any others trying the same techniques. Also, the documentation of the XML lookup is virtually non-existent. The response is an XmlNode object (not a dict, although it is represented as one). Each node has a name with {attribs, text, children} values. The structure is more clearly understood if the web-based lookup is used (which is well documented at https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2 ) as this gives an XML response. I wrote a function (parse\_data) to parse XmlNode objects, or lists thereof, for a (parameterised) hierarchy of nodes (and optional attribs value tests) in order to extract required data from the response. This may be of use to other plugin authors. The situation has been made slightly better in Picard 2.0 as the objects returned from register_track_metadata_processor are standard JSON. See https://musicbrainz.org/doc/Development/JSON_Web_Service. My parse_data function works with JSON as well as XmlNode formats, but the arguments you need to provide to it are different as the formats are structured differently. Picard 2.0 allows tagger.webservice calls to specify XML or default to JSON. This plugin has now migrated over to JSON-only. To get the whole picture, in XML, for a release, use (for example) https://musicbrainz.org/ws/2/release/f3bb4fdd-5db0-43a8-be73-7a1747f6c2ef?inc=release-groups+media+recordings+artist-credits+artists+aliases+labels+isrcs+collections+artist-rels+release-rels+url-rels+recording-rels+work-rels+recording-level-rels+work-level-rels. (Add &fmt=JSON at the end to get the JSON response). This simulates the response given by Picard with the "Use release relationships" and "Use track relationships" options selected. Note that the Picard album\_metadata\_processor returns releaseXmlNode (as JSON from Picard 2.0 on) which is everything from the Release node of the XML downwards, whereas track\_metadata\_processor returns trackXmlNode which is everything from a Track node downwards (release -> medium-list -> medium -> track-list is above track). Replace all hyphens in XML with underscores when parsing the Python object (JSON uses hyphens). +Care needs to be taken when using the metadata property in Picard (e.g. as in track.metadata). This returns an object of class Metadata, which looks just like a dictionary but isn't. All values are in fact lists, which works as follows: when a value is set, it is always set as a list - i.e. a list value is entered as it is, but a string is set as a list : viz. ['string']. Then, when getting an item from the object, multiple values are joined to create a string. Thus track.metadata['item'] and track.metadata.get('item') will return list.join('; ') - i.e. a string with the list values separated by semi-colons - not the list. To get the list, you need to use track.metadata.getall('item'). The plugin stores work ids, internally and in "hidden variables" (such as _cwp_workid_0), as tuples. The Metadata class puts these into a string before setting the value in the object as a list, so the resulting value is ["(wid1, wid2, ...)"]. In order to extract the original tuple, the eval function is required. + A large variety of releases were used to test this plugin, but there may still be bugs, so further testing is welcome. The following release was particularly complex and useful for testing: https://musicbrainz.org/release/ec519fde-94ee-4812-9717-659d91be11d4. Also this release was a bit tricky - a large box set with some works appearing as originals and in arrangements: https://musicbrainz.org/release/5288f266-bab8-45bd-83e4-555730f02fa0. -# Very technical matters +# Very technical matters (probably not of interest to anyone) I've done a bit of research and observed the following behaviour in Picard when using the `register_track_metadata_processor()` API: 1. If a new album (i.e. not yet tagged by Picard and with no MBIDs) is loaded, clustered and looked-up/scanned, resulting in the matched files being shown in the right-hand pane, then: * `track.metadata` gives the lookup result from MB - i.e. no file information, just the track info. diff --git a/plugins/classical_extras/__init__.py b/plugins/classical_extras/__init__.py index 9a0820df..9a20a4e6 100644 --- a/plugins/classical_extras/__init__.py +++ b/plugins/classical_extras/__init__.py @@ -19,9 +19,11 @@ PLUGIN_NAME = u'Classical Extras' PLUGIN_AUTHOR = u'Mark Evens' -PLUGIN_DESCRIPTION = u"""Classical Extras provides tagging enhancements for artists/performers and, -in particular, utilises MB’s hierarchy of works to provide work/movement tags. -All options are set through a user interface in Picard options->plugins. +PLUGIN_DESCRIPTION = u"""Classical Extras provides tagging enhancements for Picard and, in particular, +utilises MusicBrainz’s hierarchy of works to provide work/movement tags. All options are set through a +user interface in Picard options->plugins. This interface provides separate sections +to enhance artist/performer tags, works and parts, genres and also allows for a generalised +"tag mapping" (simple scripting). While it is designed to cater for the complexities of classical music tagging, it may also be useful for other music which has more than just basic song/artist/album data.

@@ -32,20 +34,20 @@ Ability to read lyrics tags on the file which has been loaded and assign them to track and album levels if required. (Note: Picard will not normally process incoming file tags).

-2. Tag mapping: in some ways, this is a simple substitute for some of Picard's scripting capability. The main advantage - is that the plugin will remember what tag mapping you use for each release (or even track). -

-3. Works and parts: The plugin will build a hierarchy of works and parts (e.g. Work -> Part -> Movement or +2. Works and parts: The plugin will build a hierarchy of works and parts (e.g. Work -> Part -> Movement or Opera -> Act -> Number) based on the works in MusicBrainz's database. These can then be displayed in tags in a variety of ways according to user preferences. Furthermore partial recordings, medleys, arrangements and collections of works are all handled according to user choices. There is a processing overhead for this at present because MusicBrainz limits look-ups to one per second.

-4. Genres etc.: Options are available to customise the source and display of information relating to genres, -instruments, keys, work dates and periods. Additional capabilities are provided for users of Muso (or others who -provide the relevant XML files) to use pre-existing databases of classical genres, classical composers and classical +3. Genres etc.: Options are available to customise the source and display of information relating to genres, +instruments, keys, work dates and periods. Additional capabilities are provided for users of Muso (or others who +provide the relevant XML files) to use pre-existing databases of classical genres, classical composers and classical periods.

+4. Tag mapping: in some ways, this is a simple substitute for some of Picard's scripting capability. The main advantage + is that the plugin will remember what tag mapping you use for each release (or even track). +

5. Advanced: Various options to control the detailed processing of the above.

All user options can be saved on a per-album (or even per-track) basis so that tweaks can be used to deal with @@ -53,7 +55,7 @@ are in the composer's language and/or script). Also existing file tags can be processed (not possible in native Picard).

-See the readme file +See the readme file on GitHub here for full details. """ @@ -79,13 +81,14 @@ # # The main control routine is at the end of the module -PLUGIN_VERSION = '2.0.1' +PLUGIN_VERSION = '2.0.2' PLUGIN_API_VERSIONS = ["2.0"] PLUGIN_LICENSE = "GPL-2.0" PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html" from picard.ui.options import register_options_page, OptionsPage from picard.plugins.classical_extras.ui_options_classical_extras import Ui_ClassicalExtrasOptionsPage +import picard.plugins.classical_extras.suffixtree from picard import config, log from picard.config import ConfigSection, BoolOption, IntOption, TextOption from picard.util import LockableObject, uniqify @@ -98,30 +101,78 @@ import collections import re import unicodedata -import time import json import copy import os -import itertools -import codecs # needed for Python 2.7 from PyQt5.QtCore import QXmlStreamReader -from picard.file import File -from picard.track import Track -from picard.tagger import Tagger from picard.const import USER_DIR -import picard.plugins.classical_extras.suffixtree import operator from builtins import str -import string ########################## # MODULE-WIDE COMPONENTS # ########################## +# CONSTANTS + +PRESERVE = [x.strip() for x in config.setting["preserved_tags"].split(',')] +DATE_SEP = '-' + +RELATION_TYPES = { + 'work': [ + 'arranger', + 'instrument arranger', + 'orchestrator', + 'composer', + 'writer', + 'lyricist', + 'librettist', + 'revised by', + 'translator', + 'reconstructed by', + 'vocal arranger'], + 'release': [ + 'instrument', + 'performer', + 'vocal', + 'performing orchestra', + 'conductor', + 'chorus master', + 'concertmaster', + 'arranger', + 'instrument arranger', + 'orchestrator', + 'vocal arranger'], + 'recording': [ + 'instrument', + 'performer', + 'vocal', + 'performing orchestra', + 'conductor', + 'chorus master', + 'concertmaster', + 'arranger', + 'instrument arranger', + 'orchestrator', + 'vocal arranger']} + +# COMMONLY USED REGEX +ROMAN_NUMERALS = r'\b((?=[MDCLXVI])(M{0,4}(CM|CD|D?)?C{0,3}(XC|XL|L?)?X{0,3}(IX|IV|V?)?I{0,3}))(?:\.|\-|:|;|,|\s|$)' +ROMAN_NUMERALS_AT_START = r'^\W*' + ROMAN_NUMERALS +RE_ROMANS = re.compile(ROMAN_NUMERALS, re.IGNORECASE) +RE_ROMANS_AT_START = re.compile(ROMAN_NUMERALS_AT_START, re.IGNORECASE) +# KEYS +RE_NOTES = r'(\b[ABCDEFG])' +RE_ACCENTS = r'(\-sharp(?:\s+|\b)|\-flat(?:\s+|\b)|\ssharp(?:\s+|\b)|\sflat(?:\s+|\b)|\u266F(?:\s+|\b)|\u266D(?:\s+|\b)|(?:[:,.]?\s+|$|\-))' +RE_SCALES = r'(major|minor)?(?:\b|$)' +RE_KEYS = re.compile( + RE_NOTES + RE_ACCENTS + RE_SCALES, + re.UNICODE | re.IGNORECASE) # LOGGING -# If logging occurs before any album is loaded, the startup log file will be written +# If logging occurs before any album is loaded, the startup log file will +# be written log_files = collections.defaultdict(dict) # entries are release-ids: to keep track of which log files are open release_status = collections.defaultdict(dict) @@ -131,14 +182,17 @@ # release_status[release_id]['name'] holds the album name # release_status[release_id]['lookups'] holds number of lookups for this release # release_status[release_id]['file_objects'] holds a cumulative list of file objects (tagger seems a bit unreliable) -# release_status[release_id]['file_found'] = False indicates that "No file with matching trackid" has (yet) been found +# release_status[release_id]['file_found'] = False indicates that "No file +# with matching trackid" has (yet) been found + def write_log(release_id, log_type, message, *args): """ Custom logging function - if log_info is set, all messages will be written to a custom file in a 'Classical_Extras' subdirectory in the same directory as the main Picard log. A different file is used for each album, to aid in debugging - the log file is release_id.log. Any startup messages (i.e. before a release has been loaded) - are written to startup.log + are written to session.log. Summary information for each release is also written to session.log even if log_info + is not set. :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor :param log_type: 'error', 'warning', 'debug' or 'info' @@ -156,7 +210,8 @@ def write_log(release_id, log_type, message, *args): if options["log_info"] or log_type == "basic": # if log_info is True, all log messages will be written to the custom log, regardless of other log_... settings - # basic session log will always be written (summary of releases and processing times) + # basic session log will always be written (summary of releases and + # processing times) filename = release_id + ".log" log_dir = os.path.join(USER_DIR, "Classical_Extras") if not os.path.exists(log_dir): @@ -164,19 +219,36 @@ def write_log(release_id, log_type, message, *args): if release_id not in log_files: try: if release_id == 'session': - log_file = open(os.path.join(log_dir, filename), 'w', encoding='utf8', buffering=1) - # buffering=1 so that session log (low volume) is up to date even if not closed + log_file = open( + os.path.join( + log_dir, + filename), + 'w', + encoding='utf8', + buffering=1) + # buffering=1 so that session log (low volume) is up to + # date even if not closed else: - log_file = open(os.path.join(log_dir, filename), 'w', encoding='utf8') #, buffering=1) + log_file = open( + os.path.join( + log_dir, + filename), + 'w', + encoding='utf8') # , buffering=1) # default buffering for speed, buffering = 1 for currency log_files[release_id] = log_file - log_file.write(PLUGIN_NAME + ' Version:' + PLUGIN_VERSION + '\n') + log_file.write( + PLUGIN_NAME + + ' Version:' + + PLUGIN_VERSION + + '\n') if release_id == 'session': log_file.write('session' + '\n') else: log_file.write('Release id: ' + release_id + '\n') if release_id in release_status and 'name' in release_status[release_id]: - log_file.write('Album name: ' + release_status[release_id]['name'] + '\n') + log_file.write( + 'Album name: ' + release_status[release_id]['name'] + '\n') except IOError: log.error('Unable to open file %s for writing log', filename) return @@ -190,7 +262,8 @@ def write_log(release_id, log_type, message, *args): except IOError: log.error('Unable to write to log file %s', filename) return - # Only debug, warning and error messages will be written to the main Picard log, if those options have been set + # Only debug, warning and error messages will be written to the main + # Picard log, if those options have been set if log_type != 'info' and log_type != 'basic': # i.e. non-custom log items message2 = PLUGIN_NAME + ': ' + message else: @@ -236,56 +309,92 @@ def close_log(release_id, caller): return duration = 'N/A' lookups = 'N/A' + artists_time = 0 + works_time = 0 + lookup_time = 0 + album_process_time = 0 if release_id in release_status: duration = datetime.now() - release_status[release_id]['start'] lookups = release_status[release_id]['lookups'] done_lookups = release_status[release_id]['done-lookups'] lookup_time = done_lookups - release_status[release_id]['start'] album_process_time = duration - lookup_time - artists_time = release_status[release_id]['artists-done'] - release_status[release_id]['start'] - works_time = release_status[release_id]['works-done'] - release_status[release_id]['start'] + artists_time = release_status[release_id]['artists-done'] - \ + release_status[release_id]['start'] + works_time = release_status[release_id]['works-done'] - \ + release_status[release_id]['start'] del release_status[release_id]['start'] del release_status[release_id]['lookups'] del release_status[release_id]['done-lookups'] del release_status[release_id]['artists-done'] del release_status[release_id]['works-done'] if release_id in log_files: - write_log(release_id, 'info', 'Duration = %s. Number of lookups = %s.', duration, lookups) + write_log( + release_id, + 'info', + 'Duration = %s. Number of lookups = %s.', + duration, + lookups) write_log(release_id, 'info', 'Closing log file for %s', release_id) log_files[release_id].close() del log_files[release_id] if 'session' in log_files and release_id in release_status: - write_log('session', 'basic', '\n Completed processing release id %s. Details below:-', release_id) + write_log( + 'session', + 'basic', + '\n Completed processing release id %s. Details below:-', + release_id) if 'name' in release_status[release_id]: - write_log('session', 'basic', 'Album name %s', release_status[release_id]['name']) + write_log('session', 'basic', 'Album name %s', + release_status[release_id]['name']) if 'errors' in release_status[release_id]: - write_log('session', 'basic', '-------------------- Errors --------------------') + write_log( + 'session', + 'basic', + '-------------------- Errors --------------------') for error in release_status[release_id]['errors']: write_log('session', 'basic', error) del release_status[release_id]['errors'] if 'warnings' in release_status[release_id]: - write_log('session', 'basic', '-------------------- Warnings --------------------') + write_log( + 'session', + 'basic', + '-------------------- Warnings --------------------') for warning in release_status[release_id]['warnings']: write_log('session', 'basic', warning) del release_status[release_id]['warnings'] if 'debug' in release_status[release_id]: - write_log('session', 'basic', '-------------------- Debug log --------------------') + write_log( + 'session', + 'basic', + '-------------------- Debug log --------------------') for debug in release_status[release_id]['debug']: write_log('session', 'basic', debug) del release_status[release_id]['debug'] - write_log('session', 'basic', 'Duration = %s. Artists time = %s. Works time = %s. Of which: Lookup time = %s. ' - 'Album-process time = %s. Number of lookups = %s.', - duration, artists_time, works_time, lookup_time, album_process_time, lookups) + write_log( + 'session', + 'basic', + 'Duration = %s. Artists time = %s. Works time = %s. Of which: Lookup time = %s. ' + 'Album-process time = %s. Number of lookups = %s.', + duration, + artists_time, + works_time, + lookup_time, + album_process_time, + lookups) if release_id in release_status: del release_status[release_id] -# CONSTANTS +# FILE READING AND OBJECT PARSING + _node_name_re = re.compile('[^a-zA-Z0-9]') + def _node_name(n): return _node_name_re.sub('_', str(n)) + def _read_xml(stream): document = XmlNode() current_node = document @@ -310,22 +419,23 @@ def _read_xml(stream): def parse_data(release_id, obj, response_list, *match): """ + This function takes any XmlNode object, or list thereof, or a JSON object + and extracts a list of all objects exactly matching the hierarchy listed in match. + match should contain list of each node in hierarchical sequence, with no gaps in the sequence + of nodes, to lowest level required. :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor - :param obj: an XmlNode object, list or dictionary containing nodes + :param obj: an XmlNode or JSON object, list or dictionary containing nodes :param response_list: working memory for recursive calls - :param match: list of items to search for in node (see detailed notes below + :param match: list of items to search for in node (see detailed notes below) :return: a list of matching items (always a list, even if only one item) - This function takes any XmlNode object, or list thereof, - and extracts a list of all objects exactly matching the hierarchy listed in match - match should contain list of each node in hierarchical sequence, with no gaps in the sequence - of nodes, to lowest level required. + Insert attribs.attribname:attribvalue in the list to select only branches where attribname is attribvalue. (Omit the attribs prefix if the obj is JSON) Insert childname.text:childtext in the list to select only branches where a sibling with childname has text childtext. - (Note: childname can be a dot-list if the text is more than one level down - e.g. child1.child2) - # TODO - Check this works fully + (Note: childname can be a dot-list if the text is more than one level down - e.g. child1.child2 + # TODO - Check this works fully ) """ if '!log' in response_list: DEBUG = True @@ -333,7 +443,8 @@ def parse_data(release_id, obj, response_list, *match): else: DEBUG = False INFO = False - # Over-ridden options as these can be VERY wordy + # Normally logging options are off as these can be VERY wordy + # They can be turned on by using !log in the call # XmlNode instances are not iterable, so need to convert to dict if isinstance(obj, XmlNode): @@ -348,10 +459,22 @@ def parse_data(release_id, obj, response_list, *match): if isinstance(item, XmlNode): item = item.__dict__ if INFO: - write_log(release_id, 'info', 'Getting response for list item no.%s of %s - object is: %s', i+1, objlen, item) + write_log( + release_id, + 'info', + 'Getting response for list item no.%s of %s - object is: %s', + i + 1, + objlen, + item) parse_data(release_id, item, response_list, *match) if INFO: - write_log(release_id, 'info', 'response_list for list item no.%s of %s is %s', i+1, objlen, response_list) + write_log( + release_id, + 'info', + 'response_list for list item no.%s of %s is %s', + i + 1, + objlen, + response_list) return response_list elif isinstance(obj, dict): if match[0] in obj: @@ -360,22 +483,39 @@ def parse_data(release_id, obj, response_list, *match): if response is not None: # To prevent adding NoneTypes to list response_list.append(response) if INFO: - write_log(release_id, 'info', 'response_list (last match item): %s', response_list) + write_log( + release_id, + 'info', + 'response_list (last match item): %s', + response_list) else: match_list = list(match) match_list.pop(0) - parse_data(release_id, obj[match[0]], response_list, *match_list) + parse_data(release_id, obj[match[0]], + response_list, *match_list) if INFO: - write_log(release_id, 'info', 'response_list (passing up): %s', response_list) + write_log( + release_id, + 'info', + 'response_list (passing up): %s', + response_list) return response_list elif ':' in match[0]: test = match[0].split(':') match2 = test[0].split('.') test_data = parse_data(release_id, obj, [], *match2) if INFO: - write_log(release_id, 'info', 'Value comparison - looking in %s for value %s', test_data, test[1]) + write_log( + release_id, + 'info', + 'Value comparison - looking in %s for value %s', + test_data, + test[1]) if len(test) > 1: - if (test[1] in test_data) or ((test[1]=='True') in test_data): # latter is because Booleans are stored as such, not as strings, in JSON + # latter is because Booleans are stored as such, not as + # strings, in JSON + if (test[1] in test_data) or ( + (test[1] == 'True') in test_data): if len(match) == 1: response = obj if response is not None: @@ -387,24 +527,43 @@ def parse_data(release_id, obj, response_list, *match): else: parse_data(release_id, obj, response_list, *match2) if INFO: - write_log(release_id, 'info', 'response_list (from value look-up): %s', response_list) + write_log( + release_id, + 'info', + 'response_list (from value look-up): %s', + response_list) return response_list else: if 'children' in obj: parse_data(release_id, obj['children'], response_list, *match) if INFO: - write_log(release_id, 'info', 'response_list (from children): %s', response_list) + write_log( + release_id, + 'info', + 'response_list (from children): %s', + response_list) return response_list else: if INFO: - write_log(release_id, 'info', 'response_list (obj is not a list or dict): %s', response_list) + write_log( + release_id, + 'info', + 'response_list (obj is not a list or dict): %s', + response_list) return response_list + def create_dict_from_ref_list(options, release_id, ref_list, keys, tags): ref_dict_list = [] for refs in ref_list: for ref in refs: - parsed_refs = [parse_data(release_id, ref, [], t, 'text') for t in tags] + parsed_refs = [ + parse_data( + release_id, + ref, + [], + t, + 'text') for t in tags] ref_dict_list.append(dict(zip(keys, parsed_refs))) return ref_dict_list @@ -422,79 +581,61 @@ def get_references_from_file(release_id, path, filename): period_dict_list = [] genre_dict_list = [] try: - xml_file = open(os.path.join(path, filename), encoding="utf8") + xml_file = open(os.path.join(path, filename), encoding="utf8") reply = xml_file.read() xml_file.close() document = _read_xml(QXmlStreamReader(reply)) # Composers - composer_list = parse_data(release_id, document, [], 'ReferenceDB', 'Composer') + composer_list = parse_data( + release_id, document, [], 'ReferenceDB', 'Composer') keys = ['name', 'sort', 'birth', 'death', 'country', 'core'] tags = ['Name', 'Sort', 'Birth', 'Death', 'CountryCode', 'Core'] - composer_dict_list = create_dict_from_ref_list(options, release_id, composer_list, keys, tags) + composer_dict_list = create_dict_from_ref_list( + options, release_id, composer_list, keys, tags) # Periods - period_list = parse_data(release_id, document, [], 'ReferenceDB', 'ClassicalPeriod') + period_list = parse_data( + release_id, + document, + [], + 'ReferenceDB', + 'ClassicalPeriod') keys = ['name', 'start', 'end'] tags = ['Name', 'Start_x0020_Date', 'End_x0020_Date'] - period_dict_list = create_dict_from_ref_list(options, release_id, period_list, keys, tags) + period_dict_list = create_dict_from_ref_list( + options, release_id, period_list, keys, tags) # Genres - genre_list = parse_data(release_id, document, [], 'ReferenceDB', 'ClassicalGenre') + genre_list = parse_data( + release_id, + document, + [], + 'ReferenceDB', + 'ClassicalGenre') keys = ['name'] tags = ['Name'] - genre_dict_list = create_dict_from_ref_list(options, release_id, genre_list, keys, tags) + genre_dict_list = create_dict_from_ref_list( + options, release_id, genre_list, keys, tags) except IOError or FileNotFoundError or UnicodeDecodeError: if options['cwp_muso_genres'] or options['cwp_muso_classical'] or options['cwp_muso_dates'] or options['cwp_muso_periods']: - write_log(release_id, 'error', 'File %s does not exist or is corrupted', os.path.join(path, filename)) + write_log( + release_id, + 'error', + 'File %s does not exist or is corrupted', + os.path.join( + path, + filename)) finally: - return {'composers': composer_dict_list, 'periods': period_dict_list, 'genres': genre_dict_list} - -prefixes = ['the', 'a', 'an', 'le', 'la', 'les', 'los', 'il'] - -PRESERVE = [x.strip() for x in config.setting["preserved_tags"].split(',')] -DATE_SEP = '-' - -RELATION_TYPES = { - 'work': [ - 'arranger', - 'instrument arranger', - 'orchestrator', - 'composer', - 'writer', - 'lyricist', - 'librettist', - 'revised by', - 'translator', - 'reconstructed by', - 'vocal arranger'], - 'release': [ - 'instrument', - 'performer', - 'vocal', - 'performing orchestra', - 'conductor', - 'chorus master', - 'concertmaster', - 'arranger', - 'instrument arranger', - 'orchestrator', - 'vocal arranger'], - 'recording': [ - 'instrument', - 'performer', - 'vocal', - 'performing orchestra', - 'conductor', - 'chorus master', - 'concertmaster', - 'arranger', - 'instrument arranger', - 'orchestrator', - 'vocal arranger']} + return { + 'composers': composer_dict_list, + 'periods': period_dict_list, + 'genres': genre_dict_list} # OPTIONS + def get_options(release_id, album, track): """ + Get the saved options from a release and use them according to flags set on the "advanced" tab :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor :param album: current release @@ -505,26 +646,42 @@ def get_options(release_id, album, track): """ release_status[release_id]['done'] = False set_options = collections.defaultdict(dict) - sections = ['artists', 'workparts'] + main_sections = ['artists', 'workparts'] + all_sections = ['artists', 'tag', 'workparts', 'genres'] + parent_sections = { + 'artists': 'artists', + 'tag': 'artists', + 'workparts': 'workparts', + 'genres': 'workparts'} + # The above needs to be done for legacy reasons - there are only two tags which store options - artists and workparts + # This dates from when there were only two sections + # To split these now will create compatibility issues override = { 'artists': 'cea_override', - 'tagmap': 'ce_tagmap_override', + 'tag': 'ce_tagmap_override', 'workparts': 'cwp_override', 'genres': 'ce_genres_override'} sect_text = {'artists': 'Artists', 'workparts': 'Works'} prefix = {'artists': 'cea', 'workparts': 'cwp'} if album.tagger.config.setting['ce_options_overwrite'] and all( - album.tagger.config.setting[override[sect]] for sect in sections): + album.tagger.config.setting[override[sect]] for sect in main_sections): set_options[track] = album.tagger.config.setting # mutable else: set_options[track] = option_settings( album.tagger.config.setting) # make a copy - write_log(release_id, 'info', 'Default (i.e. per UI) options for track %s are %r', track, set_options[track]) + if set_options[track]["log_info"]: + write_log( + release_id, + 'info', + 'Default (i.e. per UI) options for track %s are %r', + track, + set_options[track]) # As we use some of the main Picard options and may over-write them, save them here # set_options[track]['translate_artist_names'] = config.setting['translate_artist_names'] # set_options[track]['standardize_artists'] = config.setting['standardize_artists'] + # (not sure this is needed - TODO reconsider) options = set_options[track] tm = track.metadata @@ -536,34 +693,63 @@ def get_options(release_id, album, track): music_file_found = None release_status[release_id]['file_found'] = False start = datetime.now() - write_log(release_id, 'info', 'Clock start at %s', start) + if options["log_info"]: + write_log(release_id, 'info', 'Clock start at %s', start) trackno = tm['tracknumber'] discno = tm['discnumber'] album_filenames = album.tagger.get_files_from_objects([album]) - write_log(release_id, 'info', 'No. of album files found = %s', len(album_filenames)) + if options["log_info"]: + write_log( + release_id, + 'info', + 'No. of album files found = %s', + len(album_filenames)) # Note that sometimes Picard fails to get all the file objects, even if they are there (network issues) # so we will cache whatever we can get! if release_id in release_status and 'file_objects' in release_status[release_id]: - add_list_uniquely(release_status[release_id]['file_objects'], album_filenames) + add_list_uniquely( + release_status[release_id]['file_objects'], + album_filenames) else: release_status[release_id]['file_objects'] = album_filenames - write_log(release_id, 'info', 'No. of album files cached = %s', len(release_status[release_id]['file_objects'])) + if options["log_info"]: + write_log(release_id, 'info', 'No. of album files cached = %s', + len(release_status[release_id]['file_objects'])) track_file = None for album_file in release_status[release_id]['file_objects']: - write_log(release_id, 'info', 'Track file = %s, tracknumber = %s, discnumber = %s. Metadata trackno = %s, discno = %s', - album_file.filename, str(album_file.tracknumber), str(album_file.discnumber), trackno, discno) - if str(album_file.tracknumber) == trackno and str(album_file.discnumber) == discno: - write_log(release_id, 'info', 'Track file found = %r', album_file.filename) + if options["log_info"]: + write_log(release_id, + 'info', + 'Track file = %s, tracknumber = %s, discnumber = %s. Metadata trackno = %s, discno = %s', + album_file.filename, + str(album_file.tracknumber), + str(album_file.discnumber), + trackno, + discno) + if str( + album_file.tracknumber) == trackno and str( + album_file.discnumber) == discno: + if options["log_info"]: + write_log( + release_id, + 'info', + 'Track file found = %r', + album_file.filename) track_file = album_file.filename break - # Note: It would have been nice to do a rough check beforehand of total tracks, # but ~totalalbumtracks is not yet populated if not track_file: - album_fullnames = [x.filename for x in release_status[release_id]['file_objects']] - write_log(release_id, 'info', 'Album files found = %r', album_fullnames) + album_fullnames = [ + x.filename for x in release_status[release_id]['file_objects']] + if options["log_info"]: + write_log( + release_id, + 'info', + 'Album files found = %r', + album_fullnames) for music_file in album_fullnames: new_metadata = album.tagger.files[music_file].metadata @@ -574,14 +760,22 @@ def get_options(release_id, album, track): # Nothing found... if new_metadata and 'musicbrainz_trackid' not in new_metadata: if options['log_warning']: - write_log(release_id, 'warning', 'No trackid in file %s', music_file) + write_log( + release_id, + 'warning', + 'No trackid in file %s', + music_file) if 'musicbrainz_trackid' not in tm: if options['log_warning']: - write_log(release_id, 'warning', 'No trackid in track %s', track) - """ - Note that, on initial load, new_metadata == orig_metadata; but, after refresh, new_metadata will have + write_log( + release_id, + 'warning', + 'No trackid in track %s', + track) + """ + Note that, on initial load, new_metadata == orig_metadata; but, after refresh, new_metadata will have the same track metadata as tm (plus the file metadata as per orig_metadata), so a trackid match - is then possible for files that do not have musicbrainz_trackid in orig_metadata. That is why + is then possible for files that do not have musicbrainz_trackid in orig_metadata. That is why new_metadata is used in the above test, rather than orig_metadata, but orig_metadata is then used below to get the saved options. """ @@ -589,14 +783,20 @@ def get_options(release_id, album, track): # Find the tag with the options:- if track_file: orig_metadata = album.tagger.files[track_file].orig_metadata - write_log(release_id, 'info', 'METADATA == %s', tm) - write_log(release_id, 'info', 'COMPARING V1 ORIG METADATA WITH linked_files. V1 = %s, linked_files = %s', orig_metadata, track.linked_files) + # if options["log_info"]: + # write_log(release_id, 'info', 'METADATA == %s', tm) + # write_log(release_id, 'info', 'COMPARING V1 ORIG METADATA WITH linked_files. V1 = %s, linked_files = %s', orig_metadata, track.linked_files) music_file_found = track_file if options['log_info']: - write_log(release_id, 'info', 'orig_metadata for file %s is', music_file) + write_log( + release_id, + 'info', + 'orig_metadata for file %s is', + music_file) write_log(release_id, 'info', orig_metadata) - for section in sections: - if options[override[section]]: + for child_section in all_sections: + section = parent_sections[child_section] + if options[override[child_section]]: if options[prefix[section] + '_options_tag'] + ':' + \ section + '_options' in orig_metadata: file_options[section] = interpret( @@ -623,13 +823,22 @@ def get_options(release_id, album, track): orig_metadata[om]) if section not in file_options or not file_options[section]: if options['log_error']: - write_log(release_id, 'error', 'Saved ' + - section + - ' options cannot be read for file %s. Using current settings', - music_file) - append_tag(release_id, tm, '~' + prefix[section] + '_error', '1. Saved ' + - section + - ' options cannot be read. Using current settings') + write_log( + release_id, + 'error', + 'Saved ' + + section + + ' options cannot be read for file %s. Using current settings', + music_file) + append_tag( + release_id, + tm, + '~' + + prefix[section] + + '_error', + '1. Saved ' + + section + + ' options cannot be read. Using current settings') release_status[release_id]['file_found'] = True @@ -640,36 +849,57 @@ def get_options(release_id, album, track): if not release_status[release_id]['file_found']: if options['log_warning']: - write_log(release_id, 'warning', - "No file with matching trackid for track %s. IF THERE SHOULD BE ONE, TRY 'REFRESH'", track) - append_tag(release_id, tm, "002_important_warning", - "No file with matching trackid - IF THERE SHOULD BE ONE, TRY 'REFRESH' - " - "(unable to process any saved options, lyrics or 'keep' tags)") + write_log( + release_id, + 'warning', + "No file with matching trackid for track %s. IF THERE SHOULD BE ONE, TRY 'REFRESH'", + track) + append_tag( + release_id, + tm, + "002_important_warning", + "No file with matching trackid - IF THERE SHOULD BE ONE, TRY 'REFRESH' - " + "(unable to process any saved options, lyrics or 'keep' tags)") # Nothing else is done with this info as yet - ideally we need to refresh and re-run - # for all releases where release_status[release_id]['file_prob'] == True + # for all releases where, say, release_status[release_id]['file_prob'] + # == True TODO? else: if options['log_info']: - write_log(release_id, 'info', 'Found music file: %r', music_file_found) - for section in sections: + write_log( + release_id, + 'info', + 'Found music file: %r', + music_file_found) + for section in all_sections: if options[override[section]]: - if section in file_options and file_options[section]: + parent_section = parent_sections[section] + if parent_section in file_options and file_options[parent_section]: try: - options_dict = file_options[section]['Classical Extras'][sect_text[section] + ' options'] + options_dict = file_options[parent_section]['Classical Extras'][sect_text[parent_section] + ' options'] except TypeError: if options['log_error']: - write_log(release_id, 'error', 'Saved ' + - section + - ' options cannot be read for file %s. Using current settings', - music_file) - append_tag(release_id, tm, '~' + prefix[section] + '_error', '1. Saved ' + - section + - ' options cannot be read. Using current settings') + write_log( + release_id, + 'error', + 'Saved ' + + section + + ' options cannot be read for file %s. Using current settings', + music_file) + append_tag( + release_id, + tm, + '~' + + prefix[parent_section] + + '_error', + '1. Saved ' + + parent_section + + ' options cannot be read. Using current settings') break for opt in options_dict: if isinstance( options_dict[opt], - dict) and options[override['tagmap']]: # for tag line options + dict) and options[override['tag']]: # for tag line options # **NB tag mapping lines are the only entries of type dict** opt_list = [] for opt_item in options_dict[opt]: @@ -680,18 +910,11 @@ def get_options(release_id, album, track): for opt_dict in opt_list: for opt_det in opt_dict: opt_value = opt_dict[opt_det] + addn = [] if section == 'artists': - if options[override['tagmap']]: - included_tag_options = plugin_options('tag') - else: - included_tag_options = [] - addn = included_tag_options + plugin_options('picard') - else: - if options[override['genres']]: - included_genre_options = plugin_options('genres') - else: - included_genre_options = [] - addn = included_genre_options + addn = plugin_options('picard') + if section == 'tag': + addn = plugin_options('tag_detail') for ea_opt in plugin_options(section) + addn: displayed_option = options[ea_opt['option']] if ea_opt['name'] == opt_det: @@ -699,18 +922,25 @@ def get_options(release_id, album, track): if ea_opt['value'] == opt_value: options[ea_opt['option']] = True else: - options[ea_opt['option']] = False + options[ea_opt['option'] + ] = False else: - options[ea_opt['option']] = opt_value + options[ea_opt['option'] + ] = opt_value if options[ea_opt['option'] ] != displayed_option: - if options['log_debug']: - write_log(release_id, 'debug', 'Options overridden for option %s = %s', - ea_opt['option'], opt_value) + if options['log_debug'] or options['log_info']: + write_log( + release_id, + 'info', + 'Options overridden for option %s = %s', + ea_opt['option'], + opt_value) opt_text = str(opt_value) - append_tag(release_id, tm, '003_information:options_overridden', str( - ea_opt['name']) + ' = ' + opt_text) + append_tag( + release_id, tm, '003_information:options_overridden', str( + ea_opt['name']) + ' = ' + opt_text) if orig_metadata: keep_list = options['cea_keep'].split(",") @@ -718,16 +948,29 @@ def get_options(release_id, album, track): keep_list.append(options['cea_lyrics_tag']) if options['cwp_genres_use_file']: if 'genre' in orig_metadata: - append_tag(release_id, tm, '~cwp_candidate_genres', orig_metadata['genre']) + append_tag( + release_id, + tm, + '~cwp_candidate_genres', + orig_metadata['genre']) if options['cwp_genre_tag'] and options['cwp_genre_tag'] in orig_metadata: keep_list.append(options['cwp_genre_tag']) really_keep_list = PRESERVE[:] - really_keep_list.append(options['cwp_options_tag'] + ':workparts_options') - really_keep_list.append(options['cea_options_tag'] + ':artists_options') + really_keep_list.append( + options['cwp_options_tag'] + + ':workparts_options') + really_keep_list.append( + options['cea_options_tag'] + + ':artists_options') for tagx in keep_list: tag = tagx.strip() really_keep_list.append(tag) - write_log(release_id, 'info', 'really_keep_list = %s', really_keep_list) + if options["log_info"]: + write_log( + release_id, + 'info', + 'really_keep_list = %s', + really_keep_list) if tag in orig_metadata: append_tag(release_id, tm, tag, orig_metadata[tag]) if options['cea_clear_tags']: @@ -746,7 +989,11 @@ def get_options(release_id, album, track): tm['~ce_options'] = options_dict tm['~ce_file'] = music_file_found if options['log_info']: - write_log(release_id, 'info', 'Get_options is returning options shown below for file: %s', music_file_found) + write_log( + release_id, + 'info', + 'Get_options is returning options shown below for file: %s', + music_file_found) write_log(release_id, 'info', options_dict) @@ -757,7 +1004,7 @@ def plugin_options(option_type): This function contains all the options data in one place - to prevent multiple repetitions elsewhere """ - # artists options (excluding tag mapping lines) + # artists options artists_options = [ {'option': 'classical_extra_artists', 'name': 'run extra artists', @@ -813,7 +1060,7 @@ def plugin_options(option_type): {'option': 'cea_ra_use', 'name': 'use recording artist', 'type': 'Boolean', - 'default': True + 'default': False }, {'option': 'cea_ra_trackartist', 'name': 'recording artist name style', @@ -831,7 +1078,7 @@ def plugin_options(option_type): 'name': 'recording artist effect on track artist', 'value': 'replace', 'type': 'Boolean', - 'default': True + 'default': False }, {'option': 'cea_ra_noblank_ta', 'name': 'disallow blank recording artist', @@ -842,7 +1089,7 @@ def plugin_options(option_type): 'name': 'recording artist effect on track artist', 'value': 'merge', 'type': 'Boolean', - 'default': False + 'default': True }, {'option': 'cea_composer_album', 'name': 'Album prefix', @@ -850,26 +1097,6 @@ def plugin_options(option_type): 'type': 'Boolean', 'default': True }, - {'option': 'cea_blank_tag', - 'name': 'Tags to blank', - 'type': 'Text', - 'default': 'artist, artistsort' - }, - {'option': 'cea_blank_tag_2', - 'name': 'Tags to blank 2', - 'type': 'Text', - 'default': 'performer:orchestra, performer:choir, performer:choir vocals' - }, - {'option': 'cea_keep', - 'name': 'File tags to keep', - 'type': 'Text', - 'default': 'is_classical' - }, - {'option': 'cea_clear_tags', - 'name': 'Clear previous tags', - 'type': 'Boolean', - 'default': False - }, {'option': 'cea_arrangers', 'name': 'include arrangers', 'type': 'Boolean', @@ -909,7 +1136,7 @@ def plugin_options(option_type): {'option': 'cea_recording_credited', 'name': 'use recording credited-as name', 'type': 'Boolean', - 'default': True + 'default': False }, {'option': 'cea_recording_relationship_credited', 'name': 'use recording relationship credited-as name', @@ -959,7 +1186,7 @@ def plugin_options(option_type): {'option': 'cea_lyricist', 'name': 'lyricist', 'type': 'Text', - 'default': '' + 'default': 'lyrics' }, {'option': 'cea_librettist', 'name': 'librettist', @@ -1010,6 +1237,30 @@ def plugin_options(option_type): 'name': 'track lyrics', 'type': 'Text', 'default': 'tracknotes' + } + ] + + # tag mapping options + tag_options = [ + {'option': 'cea_blank_tag', + 'name': 'Tags to blank', + 'type': 'Text', + 'default': 'artist, artistsort' + }, + {'option': 'cea_blank_tag_2', + 'name': 'Tags to blank 2', + 'type': 'Text', + 'default': 'performer:orchestra, performer:choir, performer:choir vocals' + }, + {'option': 'cea_keep', + 'name': 'File tags to keep', + 'type': 'Text', + 'default': '' + }, + {'option': 'cea_clear_tags', + 'name': 'Clear previous tags', + 'type': 'Boolean', + 'default': False }, {'option': 'cea_tag_sort', 'name': 'populate sort tags', @@ -1017,21 +1268,19 @@ def plugin_options(option_type): 'default': True } ] - - # tag mapping lines + # (tag mapping detail lines) default_list = [ ('album_soloists, album_ensembles, album_conductors', 'artist, artists', False), ('recording_artists', 'artist, artists', True), ('soloist_names, ensemble_names, conductors', 'artist, artists', True), - ('soloists', 'soloists, trackartist', False), + ('soloists', 'soloists, trackartist, involved people', False), ('release', 'release_name', False), - ('work_type', 'genre', False), ('ensemble_names', 'band', False), ('composers', 'artist', True), ('MB_artists', 'composer', True), ('arranger', 'composer', True) ] - tag_options = [] + tag_detail_options = [] for i in range(0, 16): if i < len(default_list): default_source, default_tag, default_cond = default_list[i] @@ -1039,21 +1288,21 @@ def plugin_options(option_type): default_source = '' default_tag = '' default_cond = False - tag_options.append({'option': 'cea_source_' + str(i + 1), - 'name': 'line ' + str(i + 1) + '_source', - 'type': 'Combo', - 'default': default_source - }) - tag_options.append({'option': 'cea_tag_' + str(i + 1), - 'name': 'line ' + str(i + 1) + '_tag', - 'type': 'Text', - 'default': default_tag - }) - tag_options.append({'option': 'cea_cond_' + str(i + 1), - 'name': 'line ' + str(i + 1) + '_conditional', - 'type': 'Boolean', - 'default': default_cond - }) + tag_detail_options.append({'option': 'cea_source_' + str(i + 1), + 'name': 'line ' + str(i + 1) + '_source', + 'type': 'Combo', + 'default': default_source + }) + tag_detail_options.append({'option': 'cea_tag_' + str(i + 1), + 'name': 'line ' + str(i + 1) + '_tag', + 'type': 'Text', + 'default': default_tag + }) + tag_detail_options.append({'option': 'cea_cond_' + str(i + 1), + 'name': 'line ' + str(i + 1) + '_conditional', + 'type': 'Boolean', + 'default': default_cond + }) # workparts options workparts_options = [ @@ -1067,6 +1316,13 @@ def plugin_options(option_type): 'type': 'Boolean', 'default': True }, + {'option': 'cwp_allow_empty_parts', + # allow parts to be blank if there is arrangement or partial text label + # checked = split + 'name': 'allow-empty-parts', + 'type': 'Boolean', + 'default': True + }, {'option': 'cwp_common_chars', # for use in strip_parent_from_work # where no match exists and substring elimination is used @@ -1104,24 +1360,26 @@ def plugin_options(option_type): # it to be considered essentially similar 'name': 'similarity threshold', 'type': 'Integer', - 'default': 66 + 'default': 100 }, {'option': 'cwp_prepositions', 'name': 'prepositions', 'type': 'Text', - 'default': "a, the, in, on, at, of, after, and, de, d'un, d'une, la, le, no" + 'default': "a, the, in, on, at, of, after, and, de, d'un, d'une, la, le, no, from, &, e, ed, et, un," + " une, al, ala, alla" }, {'option': 'cwp_removewords', 'name': 'ignore prefixes', 'type': 'Text', - 'default': ' part, act, scene, movement, movt, no., no , n., n , nr., nr , book , the , a , la , le , un ,' - ' une , el , il , (part), tableau, from ' + 'default': ' part , act , scene, movement, movt, no. , no , n., n , nr., nr , book , the , a , la , le , un ,' + ' une , el , il , tableau, from , KV ,Concerto in, Concerto' }, {'option': 'cwp_synonyms', 'name': 'synonyms', - 'type': 'Text', - 'default': '(1, one) / (2, two) / (3, three) / (&, and) / (Rezitativ, Recitativo) / ' - '(Recitativo, Recitative) / (Arie, Aria)' + 'type': 'PlainText', + 'default': '(1, one) / (2, two) / (3, three) / (&, and) / (Rezitativ, Recitativo, Recitative) / ' + '(Sinfonia, Sinfonie, Symphonie, Symphony) / (Arie, Aria) / ' + '(Minuetto, Menuetto, Minuetta, Menuet, Minuet) / (Bourée, Bouree , Bourrée)' }, {'option': 'cwp_replacements', 'name': 'replacements', @@ -1241,7 +1499,7 @@ def plugin_options(option_type): {'option': 'cwp_medley_text', 'name': 'medley text', 'type': 'Text', - 'default': 'Medley of:' + 'default': 'Medley' } ] # Options on "Genres etc." tab @@ -1277,7 +1535,13 @@ def plugin_options(option_type): 'type': 'Boolean', 'default': False }, - # Note that the "infer from artists" option was in the "artists" section - legacy from v0.9.1 & prior + # Note that the "infer from artists" option was in the "artists" + # section - legacy from v0.9.1 & prior + {'option': 'cwp_genres_filter', + 'name': 'apply filter to genres', + 'type': 'Boolean', + 'default': True + }, {'option': 'cwp_genres_classical_main', 'name': 'classical main genres', 'type': 'PlainText', @@ -1286,7 +1550,7 @@ def plugin_options(option_type): 'Mass, Cantata' }, {'option': 'cwp_genres_classical_sub', - 'name': 'classical main genres', + 'name': 'classical sub-genres', 'type': 'PlainText', 'default': 'Chant, Classical crossover, Minimalism, Avant-garde, Impressionist, Aria, Duet, Trio, Quartet' }, @@ -1401,13 +1665,13 @@ def plugin_options(option_type): 'name': 'use workdate sources sequentially', 'value': 'sequence', 'type': 'Boolean', - 'default': True + 'default': False }, {'option': 'cwp_workdate_use_all', 'name': 'use all workdate sources', 'value': 'all', 'type': 'Boolean', - 'default': False + 'default': True }, {'option': 'cwp_workdate_annotate', 'name': 'annotate dates', @@ -1417,7 +1681,7 @@ def plugin_options(option_type): {'option': 'cwp_workdate_include', 'name': 'include workdate in workname', 'type': 'Boolean', - 'default': False + 'default': True }, {'option': 'cwp_period_tag', 'name': 'period tag', @@ -1437,7 +1701,7 @@ def plugin_options(option_type): '20th Century, 1910,1975; Contemporary, 1975,2525' } ] - # Picard options which are also saved + # Picard options which are also saved (NB only affects plugin processing - not main Picard processing) picard_options = [ {'option': 'standardize_artists', 'name': 'standardize artists', @@ -1549,9 +1813,9 @@ def plugin_options(option_type): 'type': 'Text', 'default': 'Reference.xml' }, - {'option': 'log_error', + {'option': 'log_error', 'type': 'Boolean', - 'default': True + 'default': True }, {'option': 'log_warning', 'type': 'Boolean', @@ -1611,6 +1875,8 @@ def plugin_options(option_type): return artists_options elif option_type == 'tag': return tag_options + elif option_type == 'tag_detail': + return tag_detail_options elif option_type == 'workparts': return workparts_options elif option_type == 'genres': @@ -1629,7 +1895,7 @@ def option_settings(config_settings): :return: a (deep) copy of the Classical Extras options """ options = {} - for option in plugin_options('artists') + plugin_options('tag') + plugin_options( + for option in plugin_options('artists') + plugin_options('tag') + plugin_options('tag_detail') + plugin_options( 'workparts') + plugin_options('genres') + plugin_options('picard') + plugin_options('other'): options[option['option']] = copy.deepcopy( config_settings[option['option']]) @@ -1667,8 +1933,6 @@ def get_aliases(self, release_id, album, options, releaseXmlNode): locale = config.setting["artist_locale"] lang = locale.split("_")[0] # NB this is the Picard code in /util - - # Track and recording aliases/credits are gathered by parsing the # media, track and recording nodes # Do the recording relationship first as it may apply to multiple releases, so release and track data @@ -1678,7 +1942,6 @@ def get_aliases(self, release_id, album, options, releaseXmlNode): # disc_num = int(parse_data(options, m, [], 'position', 'text')[0]) # not currently used tracks = parse_data(release_id, m, [], 'tracks') - # write_log(release_id, 'info', 'tracks for aliases etc = %s', tracks) # TODO remove debug when OK for track in tracks: for t in track: # track_num = int(parse_data(options, t, [], 'number', @@ -1686,23 +1949,48 @@ def get_aliases(self, release_id, album, options, releaseXmlNode): # Recording artists obj = parse_data(release_id, t, [], 'recording') - get_aliases_and_credits(self, options, release_id, album, obj, lang, - options['cea_recording_credited']) + get_aliases_and_credits( + self, + options, + release_id, + album, + obj, + lang, + options['cea_recording_credited']) # Get the release data before the recording relationshiops and track data # Release group artists obj = parse_data(release_id, releaseXmlNode, [], 'release-group') - get_aliases_and_credits(self, options, release_id, album, obj, lang, options['cea_group_credited']) + get_aliases_and_credits( + self, + options, + release_id, + album, + obj, + lang, + options['cea_group_credited']) # Release artists - get_aliases_and_credits(self, options, release_id, album, releaseXmlNode, lang, - options['cea_credited']) + get_aliases_and_credits( + self, + options, + release_id, + album, + releaseXmlNode, + lang, + options['cea_credited']) # Next bit needed to identify artists who are album artists - self.release_artists_sort[album] = parse_data(release_id, releaseXmlNode, [], 'artist-credit', - 'artist', 'sort-name') + self.release_artists_sort[album] = parse_data( + release_id, releaseXmlNode, [], 'artist-credit', 'artist', 'sort-name') # Release relationship artists - get_relation_credits(self, options, release_id, album, releaseXmlNode, lang, - options['cea_release_relationship_credited']) + get_relation_credits( + self, + options, + release_id, + album, + releaseXmlNode, + lang, + options['cea_release_relationship_credited']) # Now get the rest: for m in media: @@ -1710,17 +1998,33 @@ def get_aliases(self, release_id, album, options, releaseXmlNode): for track in tracks: for t in track: # Recording relationship artists - # write_log(release_id, 'info', 'get recording artist relationships') # TODO remove debug when OK obj = parse_data(release_id, t, [], 'recording') - get_relation_credits(self, options, release_id, album, obj, lang, options['cea_recording_relationship_credited']) + get_relation_credits( + self, + options, + release_id, + album, + obj, + lang, + options['cea_recording_relationship_credited']) # Track artists - get_aliases_and_credits(self, options, release_id, album, t, lang, - options['cea_track_credited']) + get_aliases_and_credits( + self, + options, + release_id, + album, + t, + lang, + options['cea_track_credited']) if options['log_info']: write_log(release_id, 'info', 'Alias and credits info for %s', self) write_log(release_id, 'info', 'Aliases :%s', self.artist_aliases) - write_log(release_id, 'info', 'Credits :%s', self.artist_credits[album]) + write_log( + release_id, + 'info', + 'Credits :%s', + self.artist_credits[album]) def get_artists(options, release_id, tm, relations, relation_type): @@ -1735,7 +2039,12 @@ def get_artists(options, release_id, tm, relations, relation_type): :return: """ if options['log_debug'] or options['log_info']: - write_log(release_id, 'debug', 'In get_artists. relation_type: %s, relations: %s', relation_type, relations) + write_log( + release_id, + 'debug', + 'In get_artists. relation_type: %s, relations: %s', + relation_type, + relations) log_options = { 'log_debug': options['log_debug'], 'log_info': options['log_info']} @@ -1743,11 +2052,18 @@ def get_artists(options, release_id, tm, relations, relation_type): instruments = [] artist_types = RELATION_TYPES[relation_type] for artist_type in artist_types: - type_list = parse_data(release_id, relations, [], 'target-type:artist', 'type:' + - artist_type) + type_list = parse_data( + release_id, + relations, + [], + 'target-type:artist', + 'type:' + + artist_type) for type_item in type_list: - artist_name_list = parse_data(release_id, type_item, [], 'artist', 'name') - artist_sort_name_list = parse_data(release_id, type_item, [], 'artist', 'sort-name') + artist_name_list = parse_data( + release_id, type_item, [], 'artist', 'name') + artist_sort_name_list = parse_data( + release_id, type_item, [], 'artist', 'sort-name') if artist_type not in [ 'instrument', 'vocal', @@ -1756,32 +2072,34 @@ def get_artists(options, release_id, tm, relations, relation_type): instrument_list = None credited_inst_list = None else: - instrument_list_list = parse_data(release_id, type_item, [], 'attributes') + instrument_list_list = parse_data( + release_id, type_item, [], 'attributes') if instrument_list_list: instrument_list = instrument_list_list[0] else: instrument_list = [] credited_inst_list = instrument_list[:] - credited_inst_dict_list = parse_data(release_id, type_item, [], 'attribute-credits') # keyed to insts + credited_inst_dict_list = parse_data( + release_id, type_item, [], 'attribute-credits') # keyed to insts if credited_inst_dict_list: credited_inst_dict = credited_inst_dict_list[0] else: credited_inst_dict = {} if log_options['log_info']: - write_log(release_id, 'info', 'instrument_list = %s', instrument_list) - write_log(release_id, 'info', 'credited_inst_dict = %s', credited_inst_dict) + write_log( + release_id, + 'info', + 'instrument_list = %s', + instrument_list) + write_log( + release_id, + 'info', + 'credited_inst_dict = %s', + credited_inst_dict) for i, inst in enumerate(instrument_list): if inst in credited_inst_dict: credited_inst_list[i] = credited_inst_dict[inst] - # for inst_node in att_list: - # # cred_list = parse_data(release_id, inst_node, [], 'credited-as') # TODO Need to fix - # cred_list = [] # temp fix - # inst_list = inst_node # parse_data(release_id, inst_node, [], 'text') - # if cred_list: - # credited_inst_list += cred_list - # else: - # credited_inst_list += inst_list - # instrument_list += inst_list + if artist_type == 'vocal': if not instrument_list: instrument_list = ['vocals'] @@ -1795,7 +2113,11 @@ def get_artists(options, release_id, tm, relations, relation_type): cred_tag = [] if instrument_list: if options['log_info']: - write_log(release_id, 'info', 'instrument_list: %s', instrument_list) + write_log( + release_id, + 'info', + 'instrument_list: %s', + instrument_list) inst_tag = list(set(instrument_list)) if credited_inst_list: cred_tag = list(set(credited_inst_list)) @@ -1867,7 +2189,11 @@ def get_artists(options, release_id, tm, relations, relation_type): else: type_sort = 99 if log_options['log_error']: - write_log(release_id, 'error', "Error in artist type. Type '%s' is not in dictionary", artist_type) + write_log( + release_id, + 'error', + "Error in artist type. Type '%s' is not in dictionary", + artist_type) artist = ( artist_type, @@ -1885,17 +2211,32 @@ def get_artists(options, release_id, tm, relations, relation_type): return artist_dict -def apply_artist_style(options, release_id, lang, a_list, name_style, name_tag, sort_tag, names_tag, names_sort_tag): +def apply_artist_style( + options, + release_id, + lang, + a_list, + name_style, + name_tag, + sort_tag, + names_tag, + names_sort_tag): # Get artist and apply style - write_log(release_id, 'info', 'a_list = %s', a_list) # TODO temp - remove for a_item in a_list: for acs in a_item: artistlist = parse_data(release_id, acs, [], 'name') sortlist = parse_data(release_id, acs, [], 'artist', 'sort-name') names = {} if lang: - names['alias'] = parse_data(release_id, acs, [], 'artist', 'aliases', 'locale:' + - lang, 'primary:True', 'name') + names['alias'] = parse_data( + release_id, + acs, + [], + 'artist', + 'aliases', + 'locale:' + lang, + 'primary:True', + 'name') else: names['alias'] = [] names['credit'] = parse_data(release_id, acs, [], 'name') @@ -1936,7 +2277,7 @@ def set_work_artists(self, release_id, album, track, writerList, tm, count): :param album: the current album :param track: the current track :param writerList: format [(artist_type, [instrument_list], [name list],[sort_name list]),(.....etc] - :param tm: track metatdata + :param tm: track metadata :param count: depth count of recursion in process_work_artists (should equate to part level) :return: """ @@ -1949,9 +2290,14 @@ def set_work_artists(self, release_id, album, track, writerList, tm, count): caller = 'PartLevels' pre = '~cwp' if self.DEBUG or self.INFO: - write_log(release_id, 'debug', - 'Class: %s: in set_work_artists for track %s. Count (level) is %s. Writer list is %s', - caller, track, count, writerList) + write_log( + release_id, + 'debug', + 'Class: %s: in set_work_artists for track %s. Count (level) is %s. Writer list is %s', + caller, + track, + count, + writerList) # tag strings are a tuple (Picard tag, cwp tag, Picard sort tag, cwp sort # tag) (NB this is modelled on set_performer) tag_strings = { @@ -2088,7 +2434,11 @@ def set_work_artists(self, release_id, album, track, writerList, tm, count): sort_name = writer[3][ind] no_credit = True if self.INFO: - write_log(release_id, 'info', 'In set_work_artists. Name before changes = %s', name) + write_log( + release_id, + 'info', + 'In set_work_artists. Name before changes = %s', + name) # change name to as-credited if options['cea_composer_credited']: if album in self.artist_credits and sort_name in self.artist_credits[album]: @@ -2107,7 +2457,11 @@ def set_work_artists(self, release_id, album, track, writerList, tm, count): # performer is in non-latin script annotated_name = name if self.INFO: - write_log(release_id, 'info', 'In set_work_artists. Name after changes = %s', name) + write_log( + release_id, + 'info', + 'In set_work_artists. Name after changes = %s', + name) # add annotations and write performer tags if writer_type in annotations: if annotations[writer_type]: @@ -2128,11 +2482,16 @@ def set_work_artists(self, release_id, album, track, writerList, tm, count): self.append_tag(release_id, tm, sort_tag, sort_name) if options['cea_tag_sort'] and '~' in sort_tag: explicit_sort_tag = sort_tag.replace('~', '') - self.append_tag(release_id, tm, explicit_sort_tag, sort_name) + self.append_tag( + release_id, tm, explicit_sort_tag, sort_name) self.append_tag(release_id, tm, cwp_tag, annotated_name) self.append_tag(release_id, tm, cwp_names_tag, name) if instrumented_name != name: - self.append_tag(release_id, tm, cwp_instrumented_tag, instrumented_name) + self.append_tag( + release_id, + tm, + cwp_instrumented_tag, + instrumented_name) if cwp_sort_tag: self.append_tag(release_id, tm, cwp_sort_tag, sort_name) @@ -2140,16 +2499,31 @@ def set_work_artists(self, release_id, album, track, writerList, tm, count): if caller == 'PartLevels' and ( writer_type == 'lyricist' or writer_type == 'librettist'): self.lyricist_filled[track] = True - write_log(release_id, 'info', 'Filled lyricist for track %s. Not looking further', - track) + if self.INFO: + write_log( + release_id, + 'info', + 'Filled lyricist for track %s. Not looking further', + track) if writer_type == 'composer': composerlast = sort_name.split(",")[0] - self.append_tag(release_id, tm, pre +'_composer_lastnames', composerlast) + self.append_tag( + release_id, + tm, + pre + + '_composer_lastnames', + composerlast) if sort_name in self.release_artists_sort[album]: - self.append_tag(release_id, tm, '~cea_album_composers', name) - self.append_tag(release_id, tm, '~cea_album_composers_sort', sort_name) - self.append_tag(release_id, tm, '~cea_album_track_composer_lastnames', composerlast) + self.append_tag( + release_id, tm, '~cea_album_composers', name) + self.append_tag( + release_id, tm, '~cea_album_composers_sort', sort_name) + self.append_tag( + release_id, + tm, + '~cea_album_track_composer_lastnames', + composerlast) composer_last_names(self, release_id, tm, album) @@ -2273,7 +2647,6 @@ def remove_middle(performer): # Sorting etc. - def unsort(performer): """ To take a sort field and recreate the name @@ -2347,21 +2720,13 @@ def stripsir(performer): def replace_roman_numerals(s): - """Replaces roman numerals include in s, where followed by punctuation, by digits""" - p = re.compile( - r'\b(M{0,4}(CM|CD|D?)?C{0,3}(XC|XL|L?)?X{0,3}(IX|IV|V?)?I{0,3})\b(\.|:|,|;|$)') - # Matches Roman numerals (+ ensure non-Latin chars treated as word chars) - romans = p.findall(s) - if not romans: - p_lower = re.compile( - r'\b(m{0,4}(cm|cd|d?)?c{0,3}(xc|xl|l?)?x{0,3}(ix|iv|v?)?i{0,3})\b(\.|:|,|;|$)',) - # try lower case (but don't mix them) - romans = p_lower.findall(s) + """Replaces roman numerals include in s, where followed by certain punctuation, by digits""" + romans = RE_ROMANS.findall(s) for roman in romans: if roman[0]: numerals = str(roman[0]) digits = str(from_roman(numerals)) - to_replace = roman[0] + r'(\.|:|,|;|$)' + to_replace = r'\b' + roman[0] + r'\b' s = re.sub(to_replace, digits, s) return s @@ -2379,7 +2744,20 @@ def from_roman(s): ('IX', 9), ('V', 5), ('IV', 4), - ('I', 1)) + ('I', 1), + ('m', 1000), + ('cm', 900), + ('d', 500), + ('cd', 400), + ('c', 100), + ('xc', 90), + ('l', 50), + ('xl', 40), + ('x', 10), + ('ix', 9), + ('v', 5), + ('iv', 4), + ('i', 1)) result = 0 index = 0 for numeral, integer in romanNumeralMap: @@ -2389,38 +2767,59 @@ def from_roman(s): return result -def turbo_lcs(release_id, multi_list): +def turbo_lcs(release_id, multi_list, ERROR, DEBUG, INFO): """ Picks the best longest common string method to use Works with lists or strings - :param release_id: + :param release_id: :param multi_list: a list of strings or a list of lists + :param ERROR: Boolean to indicate if log_error set + :param DEBUG: Boolean to indicate if log_debug set + :param INFO: Boolean to indicate if log_info set :return: longest common substring/list """ - write_log(release_id, 'debug', 'In turbo_lcs') + if DEBUG: + write_log(release_id, 'debug', 'In turbo_lcs') if not isinstance(multi_list, list): return None list_sum = sum([len(x) for x in multi_list]) list_len = len(multi_list) if list_len < 2: - write_log(release_id, 'debug', 'Only one item in list - no algo required') + if DEBUG: + write_log( + release_id, + 'debug', + 'Only one item in list - no algo required') return multi_list[0] # Nothing to do! # for big matches, use the generalised suffix tree method if ((list_sum / list_len) ** 2) * list_len > 1000: # heuristic: may need to tweak the 1000 in the light of results lcs_list = suffixtree.multi_lcs(multi_list) + # NB suffixtree may be shown as an unresolved reference in the IDE, + # but it should work provided it is included in the package if "error" not in lcs_list: if "response" in lcs_list: - write_log(release_id, 'debug', 'LCS returned from suffix tree algo') - return lcs_list['response'] + if DEBUG: + write_log( + release_id, + 'debug', + 'LCS returned from suffix tree algo') + return lcs_list['response'] else: - write_log(release_id, 'error', - 'Suffix tree failure for release %s. Error unknown. Using standard lcs algo instead', - release_id) + if ERROR: + write_log( + release_id, + 'error', + 'Suffix tree failure for release %s. Error unknown. Using standard lcs algo instead', + release_id) else: - write_log(release_id, 'debug', - 'Suffix tree failure for release %s. Error message: %s. Using standard lcs algo instead', - release_id, lcs_list['error']) + if DEBUG: + write_log( + release_id, + 'debug', + 'Suffix tree failure for release %s. Error message: %s. Using standard lcs algo instead', + release_id, + lcs_list['error']) # otherwise, or if gst fails, use the standard algorithm first = True common = [] @@ -2432,7 +2831,8 @@ def turbo_lcs(release_id, multi_list): lcs = longest_common_substring( item, common) common = lcs['string'] - write_log(release_id, 'debug', 'LCS returned from standard algo') + if DEBUG: + write_log(release_id, 'debug', 'LCS returned from standard algo') return common @@ -2488,13 +2888,14 @@ def longest_common_sequence(list1, list2, minstart=0, maxstart=0): def substart_finder(mylist, pattern): for i in range(len(mylist)): - if mylist[i] == pattern[0] and mylist[i:i+len(pattern)] == pattern: + if mylist[i] == pattern[0] and mylist[i:i + len(pattern)] == pattern: return i return len(mylist) # if nothing found def map_tags(options, release_id, album, tm): """ + Do the common tag processing - including for the genres and tag-mapping sections :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor :param options: options passed from either Artists or Workparts @@ -2520,8 +2921,27 @@ def map_tags(options, release_id, album, tm): write_log(release_id, 'debug', '... processing tag mapping') if INFO: for ind, opt in enumerate(options): - write_log(release_id, 'info', 'Option %s of %s. Option= %s, Value= %s', ind + 1, len(options), opt, - options[opt]) + write_log( + release_id, + 'info', + 'Option %s of %s. Option= %s, Value= %s', + ind + 1, + len(options), + opt, + options[opt]) + + # blank tags + blank_tags = options['cea_blank_tag'].split( + ",") + options['cea_blank_tag_2'].split(",") + if 'artists_sort' in [x.strip() for x in blank_tags]: + blank_tags.append('~artists_sort') + for tag in blank_tags: + if tag.strip() in tm: + # place blanked tags into hidden variables available for + # re-use + tm['~cea_' + tag.strip()] = tm[tag.strip()] + del tm[tag.strip()] + # album if tm['~cea_album_composer_lastnames']: if isinstance(tm['~cea_album_composer_lastnames'], list): @@ -2539,7 +2959,8 @@ def map_tags(options, release_id, album, tm): tm['album'] = "; ".join(new_last_names) + ": " + tm['album'] # remove lyricists if no vocals, according to option set - if options['cea_no_lyricists'] and not any([x for x in str_to_list(tm['~cea_performers']) if 'vocals' in x]): + if options['cea_no_lyricists'] and not any( + [x for x in str_to_list(tm['~cea_performers']) if 'vocals' in x]): if 'lyricist' in tm: del tm['lyricist'] for lyricist_tag in ['lyricists', 'librettists', 'translators']: @@ -2550,7 +2971,8 @@ def map_tags(options, release_id, album, tm): if config.setting['folksonomy_tags'] and 'genre' in tm: candidate_genres = str_to_list(tm['genre']) append_tag(release_id, tm, '~cea_candidate_genres', candidate_genres) - del tm['genre'] # to avoid confusion as it will contain unmatched folksonomy tags + # to avoid confusion as it will contain unmatched folksonomy tags + del tm['genre'] else: candidate_genres = [] is_classical = False @@ -2581,15 +3003,24 @@ def map_tags(options, release_id, album, tm): composer_index = lc_composer_list.index(composer) orig_composer = composer_list[composer_index] composers_not_found.append(orig_composer) - append_tag(release_id, tm, '~cwp_unrostered_composers', orig_composer) + append_tag( + release_id, + tm, + '~cwp_unrostered_composers', + orig_composer) if composers_not_found: - append_tag(release_id, tm, '003_information:composers', 'Composer(s) ' - + list_to_str( - composers_not_found) + ' not found in reference database of classical composers') + append_tag( + release_id, + tm, + '003_information:composers', + 'Composer(s) ' + + list_to_str(composers_not_found) + + ' not found in reference database of classical composers') # do the same for arrangers, if required if options['cwp_genres_arranger_as_composer'] or options['cwp_periods_arranger_as_composer']: - arranger_list = str_to_list(tm['~cea_arranger_names']) + str_to_list(tm['~cwp_arranger_names']) + arranger_list = str_to_list( + tm['~cea_arranger_names']) + str_to_list(tm['~cwp_arranger_names']) lc_arranger_list = [c.lower() for c in arranger_list] for arranger in lc_arranger_list: for classical_arranger in COMPOSER_DICT: @@ -2606,23 +3037,39 @@ def map_tags(options, release_id, album, tm): arranger_index = lc_arranger_list.index(arranger) orig_arranger = arranger_list[arranger_index] arrangers_not_found.append(orig_arranger) - append_tag(release_id, tm, '~cwp_unrostered_arrangers', orig_arranger) + append_tag( + release_id, + tm, + '~cwp_unrostered_arrangers', + orig_arranger) if arrangers_not_found: - append_tag(release_id, tm, '003_information:arrangers', - 'Arranger(s) ' + list_to_str(arrangers_not_found) + - ' not found in reference database of classical composers') + append_tag( + release_id, + tm, + '003_information:arrangers', + 'Arranger(s) ' + + list_to_str(arrangers_not_found) + + ' not found in reference database of classical composers') else: - append_tag(release_id, tm, '001_errors:8', - '8. No composer reference file. Check log for error messages re path name.') + append_tag( + release_id, + tm, + '001_errors:8', + '8. No composer reference file. Check log for error messages re path name.') if options['cwp_use_muso_refdb'] and options['cwp_muso_genres'] and GENRE_DICT: - main_classical_genres_list = [list_to_str(mg['name']).strip() for mg in GENRE_DICT] + main_classical_genres_list = [list_to_str( + mg['name']).strip() for mg in GENRE_DICT] else: - main_classical_genres_list = [sg.strip() for sg in options['cwp_genres_classical_main'].split(',')] - sub_classical_genres_list = [sg.strip() for sg in options['cwp_genres_classical_sub'].split(',')] - main_other_genres_list = [sg.strip() for sg in options['cwp_genres_other_main'].split(',')] - sub_other_genres_list = [sg.strip() for sg in options['cwp_genres_other_sub'].split(',')] + main_classical_genres_list = [ + sg.strip() for sg in options['cwp_genres_classical_main'].split(',')] + sub_classical_genres_list = [ + sg.strip() for sg in options['cwp_genres_classical_sub'].split(',')] + main_other_genres_list = [ + sg.strip() for sg in options['cwp_genres_other_main'].split(',')] + sub_other_genres_list = [sg.strip() + for sg in options['cwp_genres_other_sub'].split(',')] main_classical_genres = [] sub_classical_genres = [] main_other_genres = [] @@ -2635,47 +3082,85 @@ def map_tags(options, release_id, album, tm): write_log(release_id, 'info', "Candidate genres: %r", candidate_genres) untagged_genres = [] if candidate_genres: - main_classical_genres = [val for val in main_classical_genres_list - if val.lower() in [genre.lower() for genre in candidate_genres]] - sub_classical_genres = [val for val in sub_classical_genres_list - if val.lower() in [genre.lower() for genre in candidate_genres]] + main_classical_genres = [ + val for val in main_classical_genres_list if val.lower() in [ + genre.lower() for genre in candidate_genres]] + sub_classical_genres = [ + val for val in sub_classical_genres_list if val.lower() in [ + genre.lower() for genre in candidate_genres]] if main_classical_genres or sub_classical_genres or options['cwp_genres_classical_all']: is_classical = True main_classical_genres.append('Classical') candidate_genres += str_to_list(tm['~cea_work_type_if_classical']) - # next two are repeated statements, but a separate fn would be clumsy too! - main_classical_genres = [val for val in main_classical_genres_list - if val.lower() in [genre.lower() for genre in candidate_genres]] - sub_classical_genres = [val for val in sub_classical_genres_list - if val.lower() in [genre.lower() for genre in candidate_genres]] + # next two are repeated statements, but a separate fn would be + # clumsy too! + main_classical_genres = [ + val for val in main_classical_genres_list if val.lower() in [ + genre.lower() for genre in candidate_genres]] + sub_classical_genres = [ + val for val in sub_classical_genres_list if val.lower() in [ + genre.lower() for genre in candidate_genres]] if options['cwp_genres_classical_exclude']: - main_classical_genres = [g for g in main_classical_genres if g.lower() != 'classical'] - - main_other_genres = [val for val in main_other_genres_list - if val.lower() in [genre.lower() for genre in candidate_genres]] - sub_other_genres = [val for val in sub_other_genres_list - if val.lower() in [genre.lower() for genre in candidate_genres]] - all_genres = main_classical_genres + sub_classical_genres + main_other_genres + sub_other_genres - untagged_genres = [un for un in candidate_genres - if un.lower() not in [genre.lower() for genre in all_genres]] + main_classical_genres = [ + g for g in main_classical_genres if g.lower() != 'classical'] + + main_other_genres = [ + val for val in main_other_genres_list if val.lower() in [ + genre.lower() for genre in candidate_genres]] + sub_other_genres = [ + val for val in sub_other_genres_list if val.lower() in [ + genre.lower() for genre in candidate_genres]] + all_genres = main_classical_genres + sub_classical_genres + \ + main_other_genres + sub_other_genres + untagged_genres = [ + un for un in candidate_genres if un.lower() not in [ + genre.lower() for genre in all_genres]] if options['cwp_genre_tag']: - append_tag(release_id, tm, options['cwp_genre_tag'], main_classical_genres + main_other_genres) - if options['cwp_subgenre_tag']: - append_tag(release_id, tm, options['cwp_subgenre_tag'], sub_classical_genres + sub_other_genres) + if not options['cwp_genres_filter']: + append_tag( + release_id, + tm, + options['cwp_genre_tag'], + candidate_genres) + else: + append_tag( + release_id, + tm, + options['cwp_genre_tag'], + main_classical_genres + + main_other_genres) + if options['cwp_subgenre_tag'] and options['cwp_genres_filter']: + append_tag( + release_id, + tm, + options['cwp_subgenre_tag'], + sub_classical_genres + + sub_other_genres) if is_classical and options['cwp_genres_flag_text'] and options['cwp_genres_flag_tag']: tm[options['cwp_genres_flag_tag']] = options['cwp_genres_flag_text'] - if not (main_classical_genres + main_other_genres): + if not ( + main_classical_genres + + main_other_genres)and options['cwp_genres_filter']: if options['cwp_genres_default']: - append_tag(release_id, tm, options['cwp_genre_tag'], options['cwp_genres_default']) + append_tag( + release_id, + tm, + options['cwp_genre_tag'], + options['cwp_genres_default']) else: if options['cwp_genre_tag'] in tm: del tm[options['cwp_genre_tag']] - if untagged_genres: - append_tag(release_id, tm, '003_information:genres', - 'Candidate genres found but not matched: ' + list_to_str(untagged_genres)) + if untagged_genres and options['cwp_genres_filter']: + append_tag( + release_id, + tm, + '003_information:genres', + 'Candidate genres found but not matched: ' + + list_to_str(untagged_genres)) append_tag(release_id, tm, '~cwp_untagged_genres', untagged_genres) + # instruments and keys if options['cwp_instruments_MB_names'] and options['cwp_instruments_credited_names'] and tm['~cea_instruments_all']: instruments = tm['~cea_instruments_all'] @@ -2687,7 +3172,8 @@ def map_tags(options, release_id, album, tm): instruments = None if instruments and options['cwp_instruments_tag']: append_tag(release_id, tm, options['cwp_instruments_tag'], instruments) - # need to append rather than over-write as it may be the same as another tag (e.g. genre) + # need to append rather than over-write as it may be the same as + # another tag (e.g. genre) if tm['~cwp_keys'] and options['cwp_key_tag']: append_tag(release_id, tm, options['cwp_key_tag'], tm['~cwp_keys']) # dates @@ -2706,25 +3192,49 @@ def map_tags(options, release_id, album, tm): if tm['~cwp_composed_dates']: composed_dates_list = str_to_list(tm['~cwp_composed_dates']) if len(composed_dates_list) > 1: - composed_dates_list = str_to_list(composed_dates_list[0]) # use dates of lowest-level work - earliest_date = min([int(dates.split(DATE_SEP)[0].strip()) for dates in composed_dates_list]) - append_tag(release_id, tm, options['cwp_workdate_tag'], list_to_str(composed_dates_list) + comp) + composed_dates_list = str_to_list( + composed_dates_list[0]) # use dates of lowest-level work + earliest_date = min([int(dates.split(DATE_SEP)[0].strip()) + for dates in composed_dates_list]) + append_tag( + release_id, + tm, + options['cwp_workdate_tag'], + list_to_str(composed_dates_list) + + comp) found = True - if tm['~cwp_published_dates'] and (not found or options['cwp_workdate_use_all']): + if tm['~cwp_published_dates'] and ( + not found or options['cwp_workdate_use_all']): if not found: published_dates_list = str_to_list(tm['~cwp_published_dates']) if len(published_dates_list) > 1: - published_dates_list = str_to_list(published_dates_list[0]) # use dates of lowest-level work - earliest_date = min([int(dates.split(DATE_SEP)[0].strip()) for dates in published_dates_list]) - append_tag(release_id, tm, options['cwp_workdate_tag'], list_to_str(published_dates_list) + publ) + published_dates_list = str_to_list( + published_dates_list[0]) # use dates of lowest-level work + earliest_date = min([int(dates.split(DATE_SEP)[0].strip()) + for dates in published_dates_list]) + append_tag( + release_id, + tm, + options['cwp_workdate_tag'], + list_to_str(published_dates_list) + + publ) found = True - if tm['~cwp_premiered_dates'] and (not found or options['cwp_workdate_use_all']): + if tm['~cwp_premiered_dates'] and ( + not found or options['cwp_workdate_use_all']): if not found: premiered_dates_list = str_to_list(tm['~cwp_premiered_dates']) if len(premiered_dates_list) > 1: - premiered_dates_list = str_to_list(premiered_dates_list[0]) # use dates of lowest-level work - earliest_date = min([int(dates.split(DATE_SEP)[0].strip()) for dates in premiered_dates_list]) - append_tag(release_id, tm, options['cwp_workdate_tag'], list_to_str(premiered_dates_list) + prem) + premiered_dates_list = str_to_list( + premiered_dates_list[0]) # use dates of lowest-level work + earliest_date = min([int(dates.split(DATE_SEP)[0].strip()) + for dates in premiered_dates_list]) + append_tag( + release_id, + tm, + options['cwp_workdate_tag'], + list_to_str(premiered_dates_list) + + prem) + # periods PERIODS = {} if options['cwp_period_map']: @@ -2741,10 +3251,16 @@ def map_tags(options, release_id, album, tm): list_to_str(mp['end'])) for mp in PERIOD_DICT} for period in PERIODS: - if PERIODS[period][0].lstrip('-').isdigit() and PERIODS[period][1].lstrip('-').isdigit(): - PERIODS[period] = (int(PERIODS[period][0]), int(PERIODS[period][1])) + if PERIODS[period][0].lstrip( + '-').isdigit() and PERIODS[period][1].lstrip('-').isdigit(): + PERIODS[period] = (int(PERIODS[period][0]), + int(PERIODS[period][1])) else: - PERIODS[period] = (9999, 'ERROR - start and/or end of ' + period + ' are not integers') + PERIODS[period] = ( + 9999, + 'ERROR - start and/or end of ' + + period + + ' are not integers') else: periods = [p.strip() for p in options['cwp_period_map'].split(';')] @@ -2754,19 +3270,26 @@ def map_tags(options, release_id, album, tm): period = p[0].strip() start = p[1].strip() end = p[2].strip() - if start.lstrip('-').isdigit() and end.lstrip('-').isdigit(): + if start.lstrip( + '-').isdigit() and end.lstrip('-').isdigit(): PERIODS[period] = (int(start), int(end)) else: - PERIODS[period] = (9999, 'ERROR - start and/or end of ' + period + ' are not integers') + PERIODS[period] = ( + 9999, + 'ERROR - start and/or end of ' + + period + + ' are not integers') else: - PERIODS[p[0]] = (9999, 'ERROR in period map - each item must contain 3 elements') + PERIODS[p[0]] = ( + 9999, 'ERROR in period map - each item must contain 3 elements') if options['cwp_period_tag'] and PERIODS: if earliest_date == 9999: # i.e. no work date found if options['cwp_use_muso_refdb'] and options['cwp_muso_dates']: for composer_born in composer_born_list + arranger_born_list: if composer_born and composer_born.isdigit(): birthdate = int(composer_born) - earliest_date = min(earliest_date, birthdate + 20) # productive age is taken as 20->death as per Muso + # productive age is taken as 20->death as per Muso + earliest_date = min(earliest_date, birthdate + 20) for composer_died in composer_died_list + arranger_died_list: if composer_died and composer_died.isdigit(): deathdate = int(composer_died) @@ -2774,18 +3297,35 @@ def map_tags(options, release_id, album, tm): else: latest_date = datetime.now().year # sort into start date order before writing tags - sorted_periods = collections.OrderedDict(sorted(PERIODS.items(), key=lambda t: t[1])) + sorted_periods = collections.OrderedDict( + sorted(PERIODS.items(), key=lambda t: t[1])) for period in sorted_periods: - if isinstance(sorted_periods[period][1],str) and 'ERROR' in sorted_periods[period][1]: + if isinstance( + sorted_periods[period][1], + str) and 'ERROR' in sorted_periods[period][1]: tm[options['cwp_period_tag']] = '' - append_tag(release_id, tm, '001_errors:9', '9. ' + sorted_periods[period]) + append_tag( + release_id, + tm, + '001_errors:9', + '9. ' + + sorted_periods[period]) break if earliest_date < 9999: if sorted_periods[period][0] <= earliest_date <= sorted_periods[period][1]: - append_tag(release_id, tm, options['cwp_period_tag'], period) + append_tag( + release_id, + tm, + options['cwp_period_tag'], + period) if latest_date > -9999: if sorted_periods[period][0] <= latest_date <= sorted_periods[period][1]: - append_tag(release_id, tm, options['cwp_period_tag'], period) + append_tag( + release_id, + tm, + options['cwp_period_tag'], + period) + # generic tag mapping sort_tags = options['cea_tag_sort'] if sort_tags: @@ -2807,7 +3347,8 @@ def map_tags(options, release_id, album, tm): source_item = source_itemx.strip() source_itema = source_itemx.lstrip() if INFO: - write_log(release_id, 'info', "Source_item: %s", source_item) + write_log( + release_id, 'info', "Source_item: %s", source_item) if "~cea_" + source_item in tm: si = tm['~cea_' + source_item] elif "~cwp_" + source_item in tm: @@ -2827,19 +3368,29 @@ def map_tags(options, release_id, album, tm): no_names_source = re.sub('(_names)$', 's', source) source_sort = sort_suffix(source) if INFO: - write_log(release_id, 'info', - "Tag mapping: Line: %s, Source: %s, Tag: %s, no_names_source: %s, sort: %s, item %s", - i + 1, source, tag, no_names_source, sort, item) + write_log( + release_id, + 'info', + "Tag mapping: Line: %s, Source: %s, Tag: %s, no_names_source: %s, sort: %s, item %s", + i + + 1, + source, + tag, + no_names_source, + sort, + item) if '~cea_' + source in tm or '~cwp_' + source in tm: for prefix in ['~cea_', '~cwp_']: if prefix + source in tm: if INFO: write_log(release_id, 'info', prefix) - append_tag(release_id, tm, tag, tm[prefix + source], ['; ']) + append_tag(release_id, tm, tag, + tm[prefix + source], ['; ']) if sort_tags: if prefix + no_names_source + source_sort in tm: if INFO: - write_log(release_id, 'info', prefix + " sort") + write_log( + release_id, 'info', prefix + " sort") append_tag(release_id, tm, tag + sort, tm[prefix + no_names_source + source_sort], ['; ']) elif source in tm or '~' + source in tm: @@ -2847,18 +3398,24 @@ def map_tags(options, release_id, album, tm): write_log(release_id, 'info', "Picard") for p in ['', '~']: if p + source in tm: - append_tag(release_id, tm, tag, tm[p + source], ['; ', '/ ']) + append_tag(release_id, tm, tag, + tm[p + source], ['; ', '/ ']) if sort_tags: if "~" + source + source_sort in tm: source = "~" + source if source + source_sort in tm: if INFO: - write_log(release_id, 'info', "Picard sort") - append_tag(release_id, tm, tag + sort, tm[source + source_sort], ['; ', '/ ']) + write_log( + release_id, 'info', "Picard sort") + append_tag(release_id, tm, tag + sort, + tm[source + source_sort], ['; ', '/ ']) elif len(source) > 0 and source[0] == "\\": - append_tag(release_id, tm, tag, source[1:], ['; ', '/ ']) + append_tag(release_id, tm, tag, + source[1:], ['; ', '/ ']) else: pass + + # write error messages to tags if ERROR and "~cea_error" in tm: for error in str_to_list(tm['~cea_error']): ecode = error[0] @@ -2867,6 +3424,8 @@ def map_tags(options, release_id, album, tm): for warning in str_to_list(tm['~cea_warning']): wcode = warning[0] append_tag(release_id, tm, '002_warnings:' + wcode, warning) + + # delete unwanted tags if not DEBUG: if '~cea_works_complete' in tm: del tm['~cea_works_complete'] @@ -2878,6 +3437,7 @@ def map_tags(options, release_id, album, tm): del_list.append(t) for t in del_list: del tm[t] + # if options over-write enabled, remove it after processing one album options['ce_options_overwrite'] = False config.setting['ce_options_overwrite'] = False @@ -2885,6 +3445,7 @@ def map_tags(options, release_id, album, tm): # options) if '~ce_options' in tm: del tm['~ce_options'] + # remove any unwanted file tags if '~ce_file' in tm and tm['~ce_file'] != "None": music_file = tm['~ce_file'] @@ -2898,8 +3459,18 @@ def map_tags(options, release_id, album, tm): if delete_item != '002_warnings:7': # to avoid circularity! warn.append(delete_item) if warn and WARNING: - append_tag(release_id, tm, '002_warnings:7', '7. Deleted tags: ' + ', '.join(warn)) - write_log(release_id, 'warning', 'Deleted tags: ' + ', '.join(warn)) + append_tag( + release_id, + tm, + '002_warnings:7', + '7. Deleted tags: ' + + ', '.join(warn)) + write_log( + release_id, + 'warning', + 'Deleted tags: ' + + ', '.join(warn)) + def sort_suffix(tag): """To determine what sort suffix is appropriate for a given tag""" @@ -2912,6 +3483,7 @@ def sort_suffix(tag): def append_tag(release_id, tm, tag, source, separators=None): """ + Update a tag :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor :param tm: track metadata @@ -2925,10 +3497,19 @@ def append_tag(release_id, tm, tag, source, separators=None): separators = [] if tag: if config.setting['log_info']: - write_log(release_id, 'info', 'Appending source: %r to tag: %s (source is type %s) ...', - source, tag, type(source)) + write_log( + release_id, + 'info', + 'Appending source: %r to tag: %s (source is type %s) ...', + source, + tag, + type(source)) if tag in tm: - write_log(release_id, 'info', '... existing tag contents = %r', tm[tag]) + write_log( + release_id, + 'info', + '... existing tag contents = %r', + tm[tag]) if source and len(source) > 0: if isinstance(source, str): source = source.replace(u'\u2010', u'-') @@ -2952,23 +3533,27 @@ def append_tag(release_id, tm, tag, source, separators=None): if source_item not in tm[tag]: if not isinstance(tm[tag], list): if separators: - tag_list = re.split('|'.join(separators), tm[tag]) + tag_list = re.split( + '|'.join(separators), tm[tag]) else: tag_list = [tm[tag]] tag_list.append(source_item) tm[tag] = tag_list - # Picard will have converted it from list to string + # Picard will have converted it from list to + # string else: tm[tag].append(source_item) else: if tag and tag != "": if isinstance(source, list): if tag == 'artists_sort': - # no artists_sort tag in Picard - just a hidden var + # There is no artists_sort tag in Picard - just a + # hidden var ~artists_sort hidden = tm['~artists_sort'] if not isinstance(hidden, list): if separators: - hidden = re.split('|'.join(separators), hidden) + hidden = re.split( + '|'.join(separators), hidden) else: hidden = [hidden] source = add_list_uniquely(source, hidden) @@ -2985,9 +3570,10 @@ def append_tag(release_id, tm, tag, source, separators=None): else: if not isinstance(tm[tag], list): if separators: - tag_list = re.split('|'.join(separators), tm[tag]) + tag_list = re.split( + '|'.join(separators), tm[tag]) else: - tag_list =[tm[tag]] + tag_list = [tm[tag]] tag_list.append(source_item) tm[tag] = tag_list else: @@ -3011,16 +3597,26 @@ def get_artist_credit(options, release_id, obj): if name_credit_list: for name_credits in name_credit_list: for name_credit in name_credits: - credited_artist = parse_data(release_id, name_credit, [], 'name') + credited_artist = parse_data( + release_id, name_credit, [], 'name') if credited_artist: - name = parse_data(release_id, name_credit, [], 'artist', 'name') - sort_name = parse_data(release_id, name_credit, [], 'artist', 'sort-name') + name = parse_data( + release_id, name_credit, [], 'artist', 'name') + sort_name = parse_data( + release_id, name_credit, [], 'artist', 'sort-name') credit_item = (credited_artist, name, sort_name) credit_list.append(credit_item) return credit_list -def get_aliases_and_credits(self, options, release_id, album, obj, lang, credited): +def get_aliases_and_credits( + self, + options, + release_id, + album, + obj, + lang, + credited): """ :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor @@ -3045,13 +3641,21 @@ def get_aliases_and_credits(self, options, release_id, album, obj, lang, credite for name_credit in name_credit_list[0]: credited_artist = parse_data(release_id, name_credit, [], 'name') if credited_artist: - sort_name = parse_data(release_id, name_credit, [], 'artist', 'sort-name') + sort_name = parse_data( + release_id, name_credit, [], 'artist', 'sort-name') if sort_name: self.artist_credits[album][sort_name[0] ] = credited_artist[0] -def get_relation_credits(self, options, release_id, album, obj, lang, credited): +def get_relation_credits( + self, + options, + release_id, + album, + obj, + lang, + credited): """ :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor @@ -3062,60 +3666,54 @@ def get_relation_credits(self, options, release_id, album, obj, lang, credited): :return: None Note that direct recording relationships will over-ride indirect ones (via work) """ - # write_log(release_id, 'info', 'in get_relations_credits.') # TODO remove debug when OK - credited_artists = None - aliases = None - # write_log(release_id, 'info', 'obj = %s', obj) # TODO remove debug when OK + rels = parse_data(release_id, obj, [], 'relations', 'target-type:work', - 'work', 'relations', 'target-type:artist') - # write_log(release_id, 'info', 'rels = %s', rels) # TODO remove debug when OK - # for rel in rels: + 'work', 'relations', 'target-type:artist') + for artist in rels: sort_names = parse_data(release_id, artist, [], 'artist', 'sort-name') if sort_names: - credited_artists = parse_data(release_id, artist, [], 'target-credit') + credited_artists = parse_data( + release_id, artist, [], 'target-credit') if credited_artists and credited_artists[0] != '' and credited: self.artist_credits[album][sort_names[0] - ] = credited_artists[0] - aliases = parse_data(release_id, artist, [], 'artist', 'aliases', 'locale:' + - lang, 'primary:True', 'name') + ] = credited_artists[0] + aliases = parse_data( + release_id, + artist, + [], + 'artist', + 'aliases', + 'locale:' + lang, + 'primary:True', + 'name') if aliases: self.artist_aliases[sort_names[0]] = aliases[0] - # if credited_artists: # TODO remove debug when OK - # ca = credited_artists[0] - # else: - # ca = "NONE" - # if aliases: - # al = aliases[0] - # else: - # al = "NONE" - # write_log(release_id, 'info', 'in get_relations_credits pt1. credited_artists = %s', ca) - # write_log(release_id, 'info', 'in get_relations_credits pt1. aliases = %s', al) rels2 = parse_data(release_id, obj, [], 'relations', 'target-type:artist') - write_log(release_id, 'info', 'rels2 = %s', rels2) - # for rel in rels2: + if options['log_info']: + write_log(release_id, 'info', 'rels2 = %s', rels2) + for artist in rels2: sort_names = parse_data(release_id, artist, [], 'artist', 'sort-name') if sort_names: - credited_artists = parse_data(release_id, artist, [], 'target-credit') + credited_artists = parse_data( + release_id, artist, [], 'target-credit') if credited_artists and credited_artists[0] != '' and credited: self.artist_credits[album][sort_names[0] ] = credited_artists[0] - aliases = parse_data(release_id, artist, [], 'artist', 'aliases', 'locale:' + - lang, 'primary:True', 'name') + aliases = parse_data( + release_id, + artist, + [], + 'artist', + 'aliases', + 'locale:' + lang, + 'primary:True', + 'name') if aliases: self.artist_aliases[sort_names[0]] = aliases[0] - # if credited_artists: # TODO remove debug when OK - # ca = credited_artists[0] - # else: - # ca = "NONE" - # if aliases: - # al = aliases[0] - # else: - # al = "NONE" - # write_log(release_id, 'info', 'in get_relations_credits pt2. credited_artists = %s', ca) - # write_log(release_id, 'info', 'in get_relations_credits pt2. aliases = %s', al) + def composer_last_names(self, release_id, tm, album): """ @@ -3156,14 +3754,25 @@ def composer_last_names(self, release_id, tm, album): 'length': track_length} else: if self.WARNING or self.INFO: - write_log(release_id, 'warning', - "No _cea_album_track_composer_lastnames variable available for recording \"%s\".", tm['title']) + write_log( + release_id, + 'warning', + "No _cea_album_track_composer_lastnames variable available for recording \"%s\".", + tm['title']) if 'composer' in tm: - self.append_tag(release_id, release_id, tm, '~cea_warning', - '1. Composer for this track is not in album artists and will not be available to prefix album') + self.append_tag( + release_id, + release_id, + tm, + '~cea_warning', + '1. Composer for this track is not in album artists and will not be available to prefix album') else: - self.append_tag(release_id, release_id, tm, '~cea_warning', - '1. No composer for this track, but checking parent work.') + self.append_tag( + release_id, + release_id, + tm, + '~cea_warning', + '1. No composer for this track, but checking parent work.') def add_list_uniquely(list_to, list_from): @@ -3207,6 +3816,7 @@ def str_to_list(s): else: return s.split('; ') + def list_to_str(l): """ :param l: @@ -3217,6 +3827,7 @@ def list_to_str(l): else: return '; '.join(l) + def interpret(tag): """ :param tag: @@ -3268,6 +3879,7 @@ def seq_last_names(self, album): ln = ln[::-1] return [a[0] for a in ln] + def year(date): """ Return YYYY portion of date(s) in YYYY-MM-DD format (may be incomplete, string or list) @@ -3281,6 +3893,7 @@ def year(date): date_list = blank_if_none(date).split('-') return date_list[0] + def blank_if_none(val): """ Make NoneTypes strings @@ -3292,6 +3905,55 @@ def blank_if_none(val): else: return val + +def strip_excess_punctuation(s): + """ + remove orphan punctuation, unmatched quotes and brackets + :param s: string + :return: string + """ + if s: + s_prev = '' + counter = 0 + while s != s_prev: + if counter > 100: + break # safety valve + s_prev = s + s = s.replace(' ', ' ') + s = s.strip("&.-:;, ") + s = s.lstrip("!") + if s.count('"') % 2 != 0: + s = s.strip('"') + if s.count("'") % 2 != 0: + s = s.strip("'") + if s: + if s.count("\"") == 1: + s = s.replace('"', '') + if s.count("\'") == 1: + s = s.replace(" '", " ") + s = s.replace("' ", " ") + if "(" in s and ")" not in s: + s = s.replace("(", "") + if ")" in s and "(" not in s: + s = s.replace(")", "") + if "[" in s and "]" not in s: + s = s.replace("[", "") + if "]" in s and "[" not in s: + s = s.replace("]", "") + if "{" in s and "}" not in s: + s = s.replace("{", "") + if "}" in s and "{" not in s: + s = s.replace("}", "") + if s: + match_chars = [("(", ")"), ("[", "]"), ("{", "}")] + last = len(s) - 1 + for char_pair in match_chars: + if char_pair[0] == s[0] and char_pair[1] == s[last]: + s = s.lstrip(char_pair[0]).rstrip(char_pair[1]) + counter += 1 + return s + + ################# ################# # EXTRA ARTISTS # @@ -3332,7 +3994,8 @@ def __init__(self): self.lyricist_filled = collections.defaultdict(dict) # Boolean for each track to indicate if lyricist has been found (don't # want to add more from higher levels) - # NB this last one is for completeness - not actually used by ExtraArtists, but here to remove pep8 error + # NB this last one is for completeness - not actually used by + # ExtraArtists, but here to remove pep8 error def add_artist_info( self, @@ -3355,8 +4018,15 @@ def add_artist_info( release_status[release_id]['lookups'] = 0 release_status[release_id]['name'] = track_metadata['album'] release_status[release_id]['artists'] = True - write_log(release_id, 'debug', 'STARTING ARTIST PROCESSING FOR ALBUM %s, TRACK %s', - track_metadata['album'], track_metadata['tracknumber'] + ' ' + track_metadata['title']) + if config.setting['log_debug'] or config.setting['log_info']: + write_log( + release_id, + 'debug', + 'STARTING ARTIST PROCESSING FOR ALBUM %s, TRACK %s', + track_metadata['album'], + track_metadata['tracknumber'] + + ' ' + + track_metadata['title']) # write_log(release_id, 'info', 'trackXmlNode = %s', trackXmlNode) # NB can crash Picard # write_log('info', 'releaseXmlNode = %s', releaseXmlNode) # NB can crash Picard # Jump through hoops to get track object!! @@ -3365,13 +4035,18 @@ def add_artist_info( # OPTIONS - OVER-RIDE IF REQUIRED if '~ce_options' not in tm: - write_log(release_id, 'debug', 'Artists gets track first...') + if config.setting['log_debug'] or config.setting['log_info']: + write_log(release_id, 'debug', 'Artists gets track first...') get_options(release_id, album, track) options = interpret(tm['~ce_options']) if not options: if config.setting["log_error"]: - write_log(release_id, 'error', 'Artists. Failure to read saved options for track %s. options = %s', - track, tm['~ce_options']) + write_log( + release_id, + 'error', + 'Artists. Failure to read saved options for track %s. options = %s', + track, + tm['~ce_options']) options = option_settings(config.setting) self.options[track] = options @@ -3389,23 +4064,25 @@ def add_artist_info( # continue? if not options["classical_extra_artists"]: return - # NOT USED + # album_files is not used - this is just for logging album_files = album.tagger.get_files_from_objects([album]) if options['log_info']: - write_log(release_id, 'info', 'ALBUM FILENAMES for album %r = %s', album, album_files) - track_files = track.iterfiles(Track(track, album)) - # tf = uniqify(chain(*[track.iterfiles(Track(t, album))for t in [track]])) - # tf = Tagger.get_files_from_objects(Track(track, album), [track]) - # tf = uniqify(chain(*[t.iterfiles(album) for t in [track]])) - # tf = track.tagger.get_files_from_objects([track]) - # write_log('error', 'TRACK FILENAMES for track %r = %s', track, track_files) - # write_log('error', 'TRACK FILENAMES 2 for track %r = %s', track, tf) + write_log( + release_id, + 'info', + 'ALBUM FILENAMES for album %r = %s', + album, + album_files) + if not ( options["ce_no_run"] and ( not tm['~ce_file'] or tm['~ce_file'] == "None")): # continue if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "ExtraArtists - add_artist_info") + write_log( + release_id, + 'debug', + "ExtraArtists - add_artist_info") if album not in self.track_listing or track not in self.track_listing[album]: self.track_listing[album].append(track) # fix odd hyphens in names for consistency @@ -3430,10 +4107,13 @@ def add_artist_info( # xml_type = 'release' # get performers etc who are related at the release level - relation_list = parse_data(release_id, releaseXmlNode, [], 'relations') - album_performerList = get_artists(options, release_id, tm, relation_list, 'release')['artists'] + relation_list = parse_data( + release_id, releaseXmlNode, [], 'relations') + album_performerList = get_artists( + options, release_id, tm, relation_list, 'release')['artists'] self.album_performers[album] = album_performerList - album_instrumentList = get_artists(options, release_id, tm, relation_list, 'release')['instruments'] + album_instrumentList = get_artists( + options, release_id, tm, relation_list, 'release')['instruments'] self.album_instruments[album] = album_instrumentList else: @@ -3448,7 +4128,8 @@ def add_artist_info( # Should be OK to initialise these here as recording artists # yet to be processed - track_artist_list = parse_data(release_id, trackXmlNode, [], 'artist-credit') + track_artist_list = parse_data( + release_id, trackXmlNode, [], 'artist-credit') if track_artist_list: track_artist = [] track_artistsort = [] @@ -3477,9 +4158,21 @@ def add_artist_info( else: name_style = [] if self.INFO: - write_log(release_id, 'info', 'Priority order of naming style for track artists = %s', name_style) - styled_artists = apply_artist_style(options, release_id, lang, track_artist_list, name_style, - track_artist, track_artistsort, track_artists, track_artists_sort) + write_log( + release_id, + 'info', + 'Priority order of naming style for track artists = %s', + name_style) + styled_artists = apply_artist_style( + options, + release_id, + lang, + track_artist_list, + name_style, + track_artist, + track_artistsort, + track_artists, + track_artists_sort) tm['artists'] = styled_artists['artists'] tm['~artists_sort'] = styled_artists['artists_sort'] tm['artist'] = styled_artists['artist'] @@ -3487,20 +4180,24 @@ def add_artist_info( if 'recording' in trackXmlNode: self.globals[track]['is_recording'] = True - write_log(release_id,'debug', 'Getting recording details') + if self.DEBUG: + write_log(release_id, 'debug', 'Getting recording details') recording = trackXmlNode['recording'] if not isinstance(recording, list): recording = [recording] for record in recording: rec_type = type(record) - write_log(release_id, 'info', 'rec-type = %s', rec_type) - write_log(release_id, 'info', record) + if self.INFO: + write_log( + release_id, 'info', 'rec-type = %s', rec_type) + write_log(release_id, 'info', record) # Note that the lists below reflect https://musicbrainz.org/relationships/artist-recording # Any changes to that DB structure will require changes # here # get recording artists data - recording_artist_list = parse_data(release_id, record, [], 'artist-credit') + recording_artist_list = parse_data( + release_id, record, [], 'artist-credit') if recording_artist_list: recording_artist = [] recording_artistsort = [] @@ -3539,16 +4236,42 @@ def add_artist_info( else: name_style = [] if self.INFO: - write_log(release_id, 'info', 'Priority order of naming style for recording artists = %s', - name_style) - - styled_artists = apply_artist_style(options, release_id, lang, recording_artist_list, - name_style, recording_artist, recording_artistsort, - recording_artists, recording_artists_sort) - self.append_tag(release_id, tm, '~cea_recording_artists', styled_artists['artists']) - self.append_tag(release_id, tm, '~cea_recording_artists_sort', styled_artists['artists_sort']) - self.append_tag(release_id, tm, '~cea_recording_artist', styled_artists['artist']) - self.append_tag(release_id, tm, '~cea_recording_artistsort', styled_artists['artistsort']) + write_log( + release_id, + 'info', + 'Priority order of naming style for recording artists = %s', + name_style) + + styled_artists = apply_artist_style( + options, + release_id, + lang, + recording_artist_list, + name_style, + recording_artist, + recording_artistsort, + recording_artists, + recording_artists_sort) + self.append_tag( + release_id, + tm, + '~cea_recording_artists', + styled_artists['artists']) + self.append_tag( + release_id, + tm, + '~cea_recording_artists_sort', + styled_artists['artists_sort']) + self.append_tag( + release_id, + tm, + '~cea_recording_artist', + styled_artists['artist']) + self.append_tag( + release_id, + tm, + '~cea_recording_artistsort', + styled_artists['artistsort']) else: tm['~cea_recording_artists'] = '' @@ -3587,24 +4310,36 @@ def add_artist_info( ' (' + tm['~cea_recording_artistsort'] + ')' # xml_type = 'recording' - relation_list = parse_data(release_id, record, [], 'relations') + relation_list = parse_data( + release_id, record, [], 'relations') performerList = album_performerList + \ - get_artists(options, release_id, tm, relation_list, 'recording')['artists'] + get_artists(options, release_id, tm, relation_list, 'recording')['artists'] # returns # [(artist type, instrument or None, artist name, artist sort name, instrument sort, type sort)] # where instrument sort places solo ahead of additional etc. - # and type sort applies a custom sequencing to the artist types + # and type sort applies a custom sequencing to the artist + # types if performerList: if self.INFO: - write_log(release_id, 'info', "Performers: %s", performerList) - self.set_performer(release_id, album, track, performerList, tm) + write_log( + release_id, 'info', "Performers: %s", performerList) + self.set_performer( + release_id, album, track, performerList, tm) if not options['classical_work_parts']: - work_artist_list = parse_data(release_id, record, [], 'relations', - 'target-type:work', - 'type:performance', 'work', 'relations', - 'target-type:artist') - work_artists = get_artists(options, release_id, tm, work_artist_list, 'work')['artists'] - set_work_artists(self, release_id, album, track, work_artists, tm, 0) + work_artist_list = parse_data( + release_id, + record, + [], + 'relations', + 'target-type:work', + 'type:performance', + 'work', + 'relations', + 'target-type:artist') + work_artists = get_artists( + options, release_id, tm, work_artist_list, 'work')['artists'] + set_work_artists( + self, release_id, album, track, work_artists, tm, 0) # otherwise composers etc. will be set in work parts else: self.globals[track]['is_recording'] = False @@ -3659,12 +4394,16 @@ def process_album(self, release_id, album): :return: """ if self.DEBUG: - write_log(release_id, 'debug', 'ExtraArtists: Starting process_album') + write_log( + release_id, + 'debug', + 'ExtraArtists: Starting process_album') # process lyrics tags if self.DEBUG: write_log(release_id, 'debug', 'Starting lyrics processing') common = [] tmlyrics_dict = {} + tmlyrics_sort = [] for track in self.track_listing[album]: options = self.options[track] if options['cea_split_lyrics'] and options['cea_lyrics_tag']: @@ -3674,11 +4413,9 @@ def process_album(self, release_id, album): # turn text into word lists to speed processing tmlyrics_dict[track] = tm[lyrics_tag].split() if tmlyrics_dict: - for item in tmlyrics_dict: - write_log(release_id, 'info', 'tmlyrics_dict: track = %s, lyrics = %s', item, tmlyrics_dict[item]) # REMOVE - tmlyrics_sort = sorted(tmlyrics_dict.items(), key=operator.itemgetter(1)) - for tup in tmlyrics_sort: - write_log(release_id, 'info', 'tup in tmlyrics_sort = %s', tup) # REMOVE + tmlyrics_sort = sorted( + tmlyrics_dict.items(), + key=operator.itemgetter(1)) prev = None first_track = None unique_lyrics = [] @@ -3689,10 +4426,12 @@ def process_album(self, release_id, album): first_track = lyric_tuple[0] ref_track[lyric_tuple[0]] = first_track prev = lyric_tuple[1] - write_log(release_id, 'info', 'Before turbo_lcs. Unique_lyrics = %r', unique_lyrics) # REMOVE - common = turbo_lcs(release_id, unique_lyrics) - write_log(release_id, 'info', 'After turbo_lcs. Unique_lyrics = %r', unique_lyrics) # REMOVE - write_log(release_id, 'info', 'After turbo_lcs. lcs list = %s', common) # REMOVE + common = turbo_lcs( + release_id, + unique_lyrics, + self.ERROR, + self.DEBUG, + self.INFO) if common: unique = [] @@ -3703,7 +4442,6 @@ def process_album(self, release_id, album): indi = True else: indi = False - write_log(release_id, 'info', 'Ref track = %s, tup in tmlyrics_sort = %s', indi, tup) # REMOVE if track == ref: start = substart_finder(tup[1], common) length = len(common) @@ -3734,19 +4472,20 @@ def process_album(self, release_id, album): options = self.options[track] tm = track.metadata tm['~cea_version'] = PLUGIN_VERSION - blank_tags = options['cea_blank_tag'].split( - ",") + options['cea_blank_tag_2'].split(",") + # set work-type before any tags are blanked # Note that this is now mixed in with other sources of genres in def map_tags # ~cea_work_type_if_classical is used for types that are specifically classical - # and is only applied in map_tags if the track is deemed to be classical + # and is only applied in map_tags if the track is deemed to be + # classical if options['cwp_genres_infer']: if (self.globals[track]['is_recording'] and options['classical_work_parts'] and '~artists_sort' in tm and 'composersort' in tm and any(x in tm['~artists_sort'] for x in tm['composersort']) and 'writer' not in tm and not any(x in tm['~artists_sort'] for x in tm['~cea_performers_sort'])): - self.append_tag(release_id, tm, '~cea_work_type', 'Classical') + self.append_tag( + release_id, tm, '~cea_work_type', 'Classical') if isinstance(tm['~cea_soloists'], str): soloists = re.split( @@ -3770,75 +4509,94 @@ def process_album(self, release_id, album): large = False if 'performer:orchestra' in tm: large = True - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Orchestral') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Orchestral') if '~cea_soloists' in tm: if 'vocals' in tm['~cea_instruments_all']: - self.append_tag(release_id, tm, '~cea_work_type', 'Vocal') + self.append_tag( + release_id, tm, '~cea_work_type', 'Vocal') if len(soloists) == 1: if soloists != vocalists: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Concerto') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Concerto') else: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Aria') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Aria') elif len(soloists) == 2: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Duet') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Duet') if not vocalists: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Concerto') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Concerto') elif len(soloists) == 3: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Trio') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Trio') elif len(soloists) == 4: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Quartet') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Quartet') if 'performer:choir' in tm or 'performer:choir vocals' in tm: large = True - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Choral') - self.append_tag(release_id, tm, '~cea_work_type', 'Vocal') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Choral') + self.append_tag( + release_id, tm, '~cea_work_type', 'Vocal') else: if large and 'soloists' in tm and tm['soloists'].count( 'vocals') > 1: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Opera') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Opera') if not large: if '~cea_soloists' not in tm: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Chamber music') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Chamber music') else: if vocalists: - self.append_tag(release_id, tm, '~cea_work_type', 'Song') - self.append_tag(release_id, tm, '~cea_work_type', 'Vocal') + self.append_tag( + release_id, tm, '~cea_work_type', 'Song') + self.append_tag( + release_id, tm, '~cea_work_type', 'Vocal') else: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Chamber music') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Chamber music') else: if len(soloists) == 1: - if vocalists != soloists: - self.append_tag(release_id, tm, '~cea_work_type', 'Instrumental') - else: - self.append_tag(release_id, tm, '~cea_work_type', 'Song') - self.append_tag(release_id, tm, '~cea_work_type', 'Vocal') + if vocalists != soloists: + self.append_tag( + release_id, tm, '~cea_work_type', 'Instrumental') + else: + self.append_tag( + release_id, tm, '~cea_work_type', 'Song') + self.append_tag( + release_id, tm, '~cea_work_type', 'Vocal') elif len(soloists) == 2: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Duet') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Duet') elif len(soloists) == 3: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Trio') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Trio') elif len(soloists) == 4: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Quartet') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Quartet') else: if not vocalists: - self.append_tag(release_id, tm, '~cea_work_type_if_classical', 'Chamber music') + self.append_tag( + release_id, tm, '~cea_work_type_if_classical', 'Chamber music') else: - self.append_tag(release_id, tm, '~cea_work_type', 'Song') - self.append_tag(release_id, tm, '~cea_work_type', 'Vocal') - # blank tags - if 'artists_sort' in [x.strip() for x in blank_tags]: - blank_tags.append('~artists_sort') - for tag in blank_tags: - if tag.strip() in tm: - # place blanked tags into hidden variables available for - # re-use - tm['~cea_' + tag.strip()] = tm[tag.strip()] - del tm[tag.strip()] + self.append_tag( + release_id, tm, '~cea_work_type', 'Song') + self.append_tag( + release_id, tm, '~cea_work_type', 'Vocal') # album if not options['classical_work_parts']: if 'composer_lastnames' in self.album_artists[album]: last_names = seq_last_names(self, album) - self.append_tag(release_id, tm, '~cea_album_composer_lastnames', last_names) + self.append_tag( + release_id, + tm, + '~cea_album_composer_lastnames', + last_names) # otherwise this is done in the workparts class, which has all # composer info @@ -3852,7 +4610,8 @@ def process_album(self, release_id, album): lambda: collections.defaultdict( lambda: collections.defaultdict(dict))) - for opt in plugin_options('artists') + plugin_options('picard'): + for opt in plugin_options( + 'artists') + plugin_options('tag') + plugin_options('picard'): if 'name' in opt: if 'value' in opt: if options[opt['option']]: @@ -3861,7 +4620,7 @@ def process_album(self, release_id, album): self.cea_options['Classical Extras']['Artists options'][opt['name'] ] = options[opt['option']] - for opt in plugin_options('tag'): + for opt in plugin_options('tag_detail'): if opt['option'] != "": name_list = opt['name'].split("_") self.cea_options['Classical Extras']['Artists options'][name_list[0] @@ -3869,17 +4628,23 @@ def process_album(self, release_id, album): if options['ce_version_tag'] and options['ce_version_tag'] != "": self.append_tag(release_id, tm, options['ce_version_tag'], str( - 'Version ' + - tm['~cea_version'] + - ' of Classical Extras')) + 'Version ' + tm['~cea_version'] + ' of Classical Extras')) if options['cea_options_tag'] and options['cea_options_tag'] != "": - self.append_tag(release_id, tm, options['cea_options_tag'] + - ':artists_options', json.loads( - json.dumps( - self.cea_options))) + self.append_tag( + release_id, + tm, + options['cea_options_tag'] + + ':artists_options', + json.loads( + json.dumps( + self.cea_options))) self.track_listing[album] = [] if self.INFO: - write_log(release_id, 'info', "FINISHED Classical Extra Artists. Album: %s", album) + write_log( + release_id, + 'info', + "FINISHED Classical Extra Artists. Album: %s", + album) def append_tag(self, release_id, tm, tag, source): """ @@ -3891,7 +4656,12 @@ def append_tag(self, release_id, tm, tag, source): :return: """ if self.INFO: - write_log(release_id, 'info', "Extra Artists - appending %s to %s", source, tag) + write_log( + release_id, + 'info', + "Extra Artists - appending %s to %s", + source, + tag) append_tag(release_id, tm, tag, source, self.SEPARATORS) def set_performer(self, release_id, album, track, performerList, tm): @@ -3973,15 +4743,19 @@ def set_performer(self, release_id, album, track, performerList, tm): else: if inst_list == last_inst_list: if self.WARNING or self.INFO: - write_log(release_id, 'warning', 'Duplicated performer information for %s' - ' (may be in Release Relationship as well as Track Relationship).' - ' Duplicates have been ignored.', performer[3]) - self.append_tag(release_id, tm, '~cea_warning', - '2. Duplicated performer information for "' + - '; '.join( - performer[3]) + - '" (may be in Release Relationship as well as Track Relationship).' - ' Duplicates have been ignored.') + write_log( + release_id, 'warning', 'Duplicated performer information for %s' + ' (may be in Release Relationship as well as Track Relationship).' + ' Duplicates have been ignored.', performer[3]) + self.append_tag( + release_id, + tm, + '~cea_warning', + '2. Duplicated performer information for "' + + '; '.join( + performer[3]) + + '" (may be in Release Relationship as well as Track Relationship).' + ' Duplicates have been ignored.') else: artist_inst = [instrument] last_artist = performer[3] @@ -4055,9 +4829,13 @@ def set_performer(self, release_id, album, track, performerList, tm): tag += sub_strings[artist_type] else: if self.WARNING or self.INFO: - write_log(release_id, 'warning', - 'No instrument/sub-key available for artist_type %s. Performer = %s. Track is %s', - artist_type, performer[2], track) + write_log( + release_id, + 'warning', + 'No instrument/sub-key available for artist_type %s. Performer = %s. Track is %s', + artist_type, + performer[2], + track) if tag: if '~ce_tag_cleared_' + \ @@ -4108,9 +4886,14 @@ def set_performer(self, release_id, album, track, performerList, tm): annotated_name += ' (' + annotations[artist_type] + ')' else: if self.WARNING or self.INFO: - write_log(release_id, 'warning', - 'No annotation (instrument) available for artist_type %s.' - ' Performer = %s. Track is %s', artist_type, performer[2], track) + write_log( + release_id, + 'warning', + 'No annotation (instrument) available for artist_type %s.' + ' Performer = %s. Track is %s', + artist_type, + performer[2], + track) if artist_type in insertions and options['cea_arrangers']: self.append_tag(release_id, tm, tag, annotated_name) else: @@ -4122,17 +4905,17 @@ def set_performer(self, release_id, album, track, performerList, tm): self.append_tag(release_id, tm, sort_tag, sort_name) if options['cea_tag_sort'] and '~' in sort_tag: explicit_sort_tag = sort_tag.replace('~', '') - self.append_tag(release_id, tm, explicit_sort_tag, sort_name) + self.append_tag( + release_id, tm, explicit_sort_tag, sort_name) self.append_tag(release_id, tm, cea_tag, annotated_name) - # if artist_type not in [ - # 'instrument', 'vocal', 'performing orchestra']: - # self.append_tag(tm, cea_names_tag, instrumented_name) - # else: - # self.append_tag(tm, cea_names_tag, name) self.append_tag(release_id, tm, cea_names_tag, name) if instrumented_name != name: - self.append_tag(release_id, tm, cea_instrumented_tag, instrumented_name) + self.append_tag( + release_id, + tm, + cea_instrumented_tag, + instrumented_name) if cea_sort_tag: self.append_tag(release_id, tm, cea_sort_tag, sort_name) @@ -4141,26 +4924,40 @@ def set_performer(self, release_id, album, track, performerList, tm): if artist_type == 'performing orchestra' or ( instrument and instrument in self.ENSEMBLE_TYPES) or self.ensemble_type(name): performer_type = 'ensembles' - self.append_tag(release_id, tm, '~cea_ensembles', instrumented_name) - self.append_tag(release_id, tm, '~cea_ensemble_names', name) - self.append_tag(release_id, tm, '~cea_ensembles_sort', sort_name) + self.append_tag( + release_id, tm, '~cea_ensembles', instrumented_name) + self.append_tag( + release_id, tm, '~cea_ensemble_names', name) + self.append_tag( + release_id, tm, '~cea_ensembles_sort', sort_name) elif artist_type in ['performer', 'instrument', 'vocal']: performer_type = 'soloists' - self.append_tag(release_id, tm, '~cea_soloists', instrumented_name) + self.append_tag( + release_id, tm, '~cea_soloists', instrumented_name) self.append_tag(release_id, tm, '~cea_soloist_names', name) - self.append_tag(release_id, tm, '~cea_soloists_sort', sort_name) + self.append_tag( + release_id, tm, '~cea_soloists_sort', sort_name) if artist_type == "vocal": - self.append_tag(release_id, tm, '~cea_vocalists', instrumented_name) - self.append_tag(release_id, tm, '~cea_vocalist_names', name) - self.append_tag(release_id, tm, '~cea_vocalists_sort', sort_name) + self.append_tag( + release_id, tm, '~cea_vocalists', instrumented_name) + self.append_tag( + release_id, tm, '~cea_vocalist_names', name) + self.append_tag( + release_id, tm, '~cea_vocalists_sort', sort_name) elif instrument: - self.append_tag(release_id, tm, '~cea_instrumentalists', instrumented_name) - self.append_tag(release_id, tm, '~cea_instrumentalist_names', name) - self.append_tag(release_id, tm, '~cea_instrumentalists_sort', sort_name) + self.append_tag( + release_id, tm, '~cea_instrumentalists', instrumented_name) + self.append_tag( + release_id, tm, '~cea_instrumentalist_names', name) + self.append_tag( + release_id, tm, '~cea_instrumentalists_sort', sort_name) else: - self.append_tag(release_id, tm, '~cea_other_soloists', instrumented_name) - self.append_tag(release_id, tm, '~cea_other_soloist_names', name) - self.append_tag(release_id, tm, '~cea_other_soloists_sort', sort_name) + self.append_tag( + release_id, tm, '~cea_other_soloists', instrumented_name) + self.append_tag( + release_id, tm, '~cea_other_soloist_names', name) + self.append_tag( + release_id, tm, '~cea_other_soloists_sort', sort_name) # set album artists if performer_type or artist_type == 'conductor': @@ -4173,12 +4970,16 @@ def set_performer(self, release_id, album, track, performerList, tm): if stripsir(name) in tm['~albumartists'] or stripsir( sort_name) in tm['~albumartists_sort']: self.append_tag(release_id, tm, cea_album_tag, name) - self.append_tag(release_id, tm, cea_album_sort_tag, sort_name) + self.append_tag( + release_id, tm, cea_album_sort_tag, sort_name) else: if performer_type: - self.append_tag(release_id, tm, '~cea_support_performers', instrumented_name) - self.append_tag(release_id, tm, '~cea_support_performer_names', name) - self.append_tag(release_id, tm, '~cea_support_performers_sort', sort_name) + self.append_tag( + release_id, tm, '~cea_support_performers', instrumented_name) + self.append_tag( + release_id, tm, '~cea_support_performer_names', name) + self.append_tag( + release_id, tm, '~cea_support_performers_sort', sort_name) ############## ############## @@ -4259,7 +5060,8 @@ def __init__(self): # hierarchical iterative work structure - {album: {id: , children:{id: # , children{}, id: etc}, id: etc} } self.child_listing = collections.defaultdict(list) - # contains list of workIds which are descendants of a given workId, to prevent recursion when adding new ids + # contains list of workIds which are descendants of a given workId, to + # prevent recursion when adding new ids self.work_listing = collections.defaultdict(list) # contains list of workIds for each album self.top = collections.defaultdict(list) @@ -4268,6 +5070,8 @@ def __init__(self): # active Classical Extras options for current track self.synonyms = collections.defaultdict(dict) # active synonym options for current track + self.replacements = collections.defaultdict(dict) + # active synonym options for current track self.file_works = collections.defaultdict(list) # list of works derived from SongKong-style file tags # structure is {(album, track): [{workid: , name: }, {workid: ....}} @@ -4317,26 +5121,42 @@ def add_work_info( release_status[release_id]['lookups'] = 0 release_status[release_id]['name'] = track_metadata['album'] release_status[release_id]['works'] = True - write_log(release_id, 'debug', 'STARTING WORKS PROCESSING FOR ALBUM %s, TRACK %s', - track_metadata['album'], track_metadata['tracknumber'] + ' ' + track_metadata['title']) + if config.setting['log_debug'] or config.setting['log_info']: + write_log( + release_id, + 'debug', + 'STARTING WORKS PROCESSING FOR ALBUM %s, TRACK %s', + track_metadata['album'], + track_metadata['tracknumber'] + + ' ' + + track_metadata['title']) # clear the cache if required (if this is not done, then queue count may get out of sync) # Jump through hoops to get track object!! track = album._new_tracks[-1] tm = track.metadata - if config.setting['log_debug']: - write_log(release_id, 'debug', 'Cache setting for track %s is %s', track, - config.setting['use_cache']) + if config.setting['log_debug'] or config.setting['log_info']: + write_log( + release_id, + 'debug', + 'Cache setting for track %s is %s', + track, + config.setting['use_cache']) # OPTIONS - OVER-RIDE IF REQUIRED if '~ce_options' not in tm: - write_log(release_id, 'debug', 'Workparts gets track first...') + if config.setting['log_debug'] or config.setting['log_info']: + write_log(release_id, 'debug', 'Workparts gets track first...') get_options(release_id, album, track) options = interpret(tm['~ce_options']) if not options: - if config.setting["log_error"]: - write_log(release_id, 'error', 'Work Parts. Failure to read saved options for track %s. options = %s', - track, tm['~ce_options']) + if config.setting['log_error']: + write_log( + release_id, + 'error', + 'Work Parts. Failure to read saved options for track %s. options = %s', + track, + tm['~ce_options']) options = option_settings(config.setting) self.options[track] = options @@ -4349,7 +5169,10 @@ def add_work_info( self.EQ = "EQ_TO_BE_REVERSED" # phrase to indicate that a synonym has been used self.get_sk_tags(release_id, album, track, tm, options) - self.synonyms[track] = self.get_synonyms(release_id, track) + self.synonyms[track] = self.get_text_tuples( + release_id, track, 'synonyms') # a list of tuples + self.replacements[track] = self.get_text_tuples( + release_id, track, 'replacements') # a list of tuples # Continue? if not options["classical_work_parts"]: @@ -4364,17 +5187,22 @@ def add_work_info( ", " + options["cwp_partial_text"] + ' ' else: options["cwp_removewords_p"] = options["cwp_removewords"] - # Explanation: - # If "Partial" is selected then the level 0 work name will have PARTIAL_TEXT appended to it. - # If a recording is split across several tracks then each sub-part (quasi-movement) will have the same name - # (with the PARTIAL_TEXT added). If level 0 is used to source work names then the level 1 work name will be - # changed to be this repeated name and will therefore also include PARTIAL_TEXT. - # So we need to add PARTIAL_TEXT to the prefixes list to ensure it is - # excluded from the level 1 work name. + """ Explanation: + If "Partial" is selected then the level 0 work name will have PARTIAL_TEXT appended to it. + If a recording is split across several tracks then each sub-part (quasi-movement) will have the same name + (with the PARTIAL_TEXT added). If level 0 is used to source work names then the level 1 work name will be + changed to be this repeated name and will therefore also include PARTIAL_TEXT. + So we need to add PARTIAL_TEXT to the prefixes list to ensure it is excluded from the level 1 work name. + """ if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "PartLevels - LOAD NEW TRACK: :%s", track) + write_log( + release_id, + 'debug', + "PartLevels - LOAD NEW TRACK: :%s", + track) # if self.INFO: - # write_log(release_id, 'info', "trackXmlNode:") + # write_log(release_id, 'info', "trackXmlNode:") # warning - may break + # Picard if self.INFO: write_log(release_id, 'info', trackXmlNode) # first time for this album (reloads each refresh) @@ -4402,7 +5230,12 @@ def add_work_info( track_metadata['~cwp_title'] = title_split[1] # now process works - write_log(release_id, 'info', 'PartLevels - add_work_info - metadata load = %r', track_metadata) + if self.INFO: + write_log( + release_id, + 'info', + 'PartLevels - add_work_info - metadata load = %r', + track_metadata) workIds = dict.get(track_metadata, 'musicbrainz_workid', []) if workIds and not (options["ce_no_run"] and ( not tm['~ce_file'] or tm['~ce_file'] == "None")): @@ -4430,13 +5263,22 @@ def add_work_info( 'type:parts', 'direction:backward', 'ordering-key'] - parse_result = parse_data(release_id, rels, [], *match_tree_1) + parse_data(release_id, rels, [], - *match_tree_2) + parse_result = parse_data(release_id, + rels, + [], + *match_tree_1) + parse_data(release_id, + rels, + [], + *match_tree_2) if self.INFO: - write_log(release_id, 'info', 'multi-works - ordering key: %s', parse_result) + write_log( + release_id, + 'info', + 'multi-works - ordering key: %s', + parse_result) if parse_result: if isinstance(parse_result[0], int): - key =parse_result[0] + key = parse_result[0] elif isinstance(parse_result[0], str) and parse_result[0].isdigit(): key = int(parse_result[0]) else: @@ -4447,14 +5289,26 @@ def add_work_info( partial = False for key in sorted(keyed_workIds): workId = keyed_workIds[key] - work_rels = parse_data(release_id, trackXmlNode, [], 'recording', 'relations', - 'target-type:work', 'work.id:' + workId) + work_rels = parse_data( + release_id, + trackXmlNode, + [], + 'recording', + 'relations', + 'target-type:work', + 'work.id:' + workId) if self.INFO: write_log(release_id, 'info', 'work_rels: %s', work_rels) - work_attributes = parse_data(release_id, work_rels, [], 'attributes')[0] + work_attributes = parse_data( + release_id, work_rels, [], 'attributes')[0] if self.INFO: - write_log(release_id, 'info', 'work_attributes: %s', work_attributes) - work_titles = parse_data(release_id, work_rels, [], 'work', 'title') + write_log( + release_id, + 'info', + 'work_attributes: %s', + work_attributes) + work_titles = parse_data( + release_id, work_rels, [], 'work', 'title') work_list_info_item = { 'id': workId, 'attributes': work_attributes, @@ -4477,8 +5331,13 @@ def add_work_info( works.append(partwork) if self.INFO: - write_log(release_id, 'info', "Id %s is PARTIAL RECORDING OF id: %s, name: %s", - workId, parentId, work) + write_log( + release_id, + 'info', + "Id %s is PARTIAL RECORDING OF id: %s, name: %s", + workId, + parentId, + work) work_list_info_item = { 'id': workId, 'attributes': [], @@ -4486,7 +5345,11 @@ def add_work_info( 'parent': parentId} work_list_info.append(work_list_info_item) if self.INFO: - write_log(release_id, 'info', 'work_list_info: %s', work_list_info) + write_log( + release_id, + 'info', + 'work_list_info: %s', + work_list_info) # we now have a list of items, where the id of each is a work id for the track or # (multiple instances of) the recording id (for partial works) # we need to turn this into a usable hierarchy - i.e. just one item @@ -4550,13 +5413,22 @@ def add_work_info( else: self.trackback[album][workId_tuple]['meta'] = [(track, album)] if self.INFO: - write_log(release_id, 'info', "Trackback for %s is %s. Partial = %s", track, - self.trackback[album][workId_tuple], partial) + write_log( + release_id, + 'info', + "Trackback for %s is %s. Partial = %s", + track, + self.trackback[album][workId_tuple], + partial) if workId_tuple in self.works_cache and ( self.USE_CACHE or partial): if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "GETTING WORK METADATA FROM CACHE, for work %s", workId_tuple) + write_log( + release_id, + 'debug', + "GETTING WORK METADATA FROM CACHE, for work %s", + workId_tuple) if workId_tuple not in self.work_listing[album]: self.work_listing[album].append(workId_tuple) not_in_cache = self.check_cache( @@ -4574,8 +5446,16 @@ def add_work_info( else: # no work relation if self.WARNING or self.INFO: - write_log(release_id, 'warning', "WARNING - no works for this track: \"%s\"", title) - self.append_tag(release_id, track_metadata, '~cwp_warning', '3. No works for this track') + write_log( + release_id, + 'warning', + "WARNING - no works for this track: \"%s\"", + title) + self.append_tag( + release_id, + track_metadata, + '~cwp_warning', + '3. No works for this track') if album in self.orphan_tracks: if track not in self.orphan_tracks[album]: self.orphan_tracks[album].append(track) @@ -4585,10 +5465,16 @@ def add_work_info( # last track if self.DEBUG or self.INFO: - write_log(release_id, 'debug', - 'Check for last track. Requests = %s, Tracknumber = %s, Totaltracks = %s,' - ' Discnumber = %s, Totaldiscs = %s', album._requests, track_metadata['tracknumber'], - track_metadata['totaltracks'], track_metadata['discnumber'], track_metadata['totaldiscs']) + write_log( + release_id, + 'debug', + 'Check for last track. Requests = %s, Tracknumber = %s, Totaltracks = %s,' + ' Discnumber = %s, Totaldiscs = %s', + album._requests, + track_metadata['tracknumber'], + track_metadata['totaltracks'], + track_metadata['discnumber'], + track_metadata['totaldiscs']) if album._requests == 0 and track_metadata['tracknumber'] == track_metadata[ 'totaltracks'] and track_metadata['discnumber'] == track_metadata['totaldiscs']: self.process_album(release_id, album) @@ -4620,25 +5506,42 @@ def get_sk_tags(self, release_id, album, track, tm, options): # Picard may have overwritten SongKong tag (top # work id) with bottom work id if self.WARNING or self.INFO: - write_log(release_id, 'warning', - 'File tag musicbrainz_workid incorrect? id = %s. Sourcing from MB', - orig_metadata['musicbrainz_workid']) - self.append_tag(release_id, tm, '~cwp_warning', - '4. File tag musicbrainz_workid incorrect? id = ' + - orig_metadata['musicbrainz_workid'] + - '. Sourcing from MB') + write_log( + release_id, + 'warning', + 'File tag musicbrainz_workid incorrect? id = %s. Sourcing from MB', + orig_metadata['musicbrainz_workid']) + self.append_tag( + release_id, + tm, + '~cwp_warning', + '4. File tag musicbrainz_workid incorrect? id = ' + + orig_metadata['musicbrainz_workid'] + + '. Sourcing from MB') return None if self.INFO: - write_log(release_id, 'info', 'Read from file tag: musicbrainz_work_composition_id: %s', - orig_metadata['musicbrainz_work_composition_id']) + write_log( + release_id, + 'info', + 'Read from file tag: musicbrainz_work_composition_id: %s', + orig_metadata['musicbrainz_work_composition_id']) self.file_works[(album, track)].append({ 'workid': orig_metadata['musicbrainz_work_composition_id'].split('; '), 'name': orig_metadata['musicbrainz_work_composition']}) else: wid = orig_metadata['musicbrainz_work_composition_id'] if self.ERROR or self.INFO: - write_log(release_id, 'error', "No matching work name for id tag %s", wid) - self.append_tag(release_id, tm, '~cwp_error', '2. No matching work name for id tag ' + wid) + write_log( + release_id, + 'error', + "No matching work name for id tag %s", + wid) + self.append_tag( + release_id, + tm, + '~cwp_error', + '2. No matching work name for id tag ' + + wid) return None n = 1 while 'musicbrainz_work_part_level' + \ @@ -4654,9 +5557,14 @@ def get_sk_tags(self, release_id, album, track, tm, options): wid = orig_metadata['musicbrainz_work_part_level' + str(n) + '_id'] if self.ERROR or self.INFO: - write_log(release_id, 'error', "No matching work name for id tag %s", - wid) - self.append_tag(release_id, tm, '~cwp_error', '2. No matching work name for id tag ' + wid) + write_log( + release_id, 'error', "No matching work name for id tag %s", wid) + self.append_tag( + release_id, + tm, + '~cwp_error', + '2. No matching work name for id tag ' + + wid) break if orig_metadata['musicbrainz_work_composition_id'] != orig_metadata[ 'musicbrainz_workid']: @@ -4667,13 +5575,23 @@ def get_sk_tags(self, release_id, album, track, tm, options): else: wid = orig_metadata['musicbrainz_workid'] if self.ERROR or self.INFO: - write_log(release_id, 'error', "No matching work name for id tag %s", wid) - self.append_tag(release_id, tm, '~cwp_error', '2. No matching work name for id tag ' + wid) + write_log( + release_id, 'error', "No matching work name for id tag %s", wid) + self.append_tag( + release_id, + tm, + '~cwp_error', + '2. No matching work name for id tag ' + + wid) return None file_work_levels = len(self.file_works[(album, track)]) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', 'Loaded works from file tags for track %s. Works: %s: ', - track, self.file_works[(album, track)]) + write_log(release_id, + 'debug', + 'Loaded works from file tags for track %s. Works: %s: ', + track, + self.file_works[(album, + track)]) for i, work in enumerate(self.file_works[(album, track)]): workId = tuple(work['workid']) if workId not in self.works_cache: # Use cache in preference to file tags @@ -4730,10 +5648,16 @@ def work_not_in_cache(self, release_id, album, track, workId_tuple): :param workId_tuple: :return: """ - write_log(release_id, 'debug', 'Processing work_not_in_cache for workId %s', workId_tuple) + if self.DEBUG or self.INFO: + write_log( + release_id, + 'debug', + 'Processing work_not_in_cache for workId %s', + workId_tuple) if 'no_parent' in self.parts[workId_tuple] and ( self.USE_CACHE or self.options[track]["cwp_use_sk"]) and self.parts[workId_tuple]['no_parent']: - write_log(release_id, 'info', '%s is top work', workId_tuple) + if self.INFO: + write_log(release_id, 'info', '%s is top work', workId_tuple) self.top_works[(track, album)]['workId'] = workId_tuple if album in self.top: if workId_tuple not in self.top[album]: @@ -4741,10 +5665,20 @@ def work_not_in_cache(self, release_id, album, track, workId_tuple): else: self.top[album] = [workId_tuple] else: - write_log(release_id, 'info', 'Calling work_add_track to look up parents for %s', workId_tuple) + if self.INFO: + write_log( + release_id, + 'info', + 'Calling work_add_track to look up parents for %s', + workId_tuple) for workId in workId_tuple: self.work_add_track(album, track, workId, 0) - write_log(release_id, 'debug', 'End of work_not_in_cache for workId %s', workId_tuple) + if self.DEBUG or self.INFO: + write_log( + release_id, + 'debug', + 'End of work_not_in_cache for workId %s', + workId_tuple) def work_add_track(self, album, track, workId, tries, user_data=True): """ @@ -4758,13 +5692,21 @@ def work_add_track(self, album, track, workId, tries, user_data=True): """ release_id = track.metadata['musicbrainz_albumid'] if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "ADDING WORK TO LOOKUP QUEUE for work %s", workId) + write_log( + release_id, + 'debug', + "ADDING WORK TO LOOKUP QUEUE for work %s", + workId) self.album_add_request(release_id, album) # to change the _requests variable to indicate that there are pending # requests for this item and delay Picard from finalizing the album if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Added lookup request for id %s. Requests = %s", workId, - album._requests) + write_log( + release_id, + 'debug', + "Added lookup request for id %s. Requests = %s", + workId, + album._requests) if self.works_queue.append( workId, (track, @@ -4782,9 +5724,15 @@ def work_add_track(self, album, track, workId, tries, user_data=True): else: login = False tag_type = '' - queryargs = {"inc": "work-rels+artist-rels+label-rels+place-rels+aliases" + tag_type} + queryargs = { + "inc": "work-rels+artist-rels+label-rels+place-rels+aliases" + + tag_type} if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Initiating XML lookup for %s......", workId) + write_log( + release_id, + 'debug', + "Initiating XML lookup for %s......", + workId) if release_id in release_status and 'lookups' in release_status[release_id]: release_status[release_id]['lookups'] += 1 return album.tagger.webservice.get( @@ -4802,12 +5750,16 @@ def work_add_track(self, album, track, workId, tries, user_data=True): queryargs=queryargs) else: if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Work is already in queue: %s", workId) + write_log( + release_id, + 'debug', + "Work is already in queue: %s", + workId) - ##################################################################################### - # SECTION 2 - Works processing # - # NB These functions may operate over multiple albums (as well as multiple tracks) # - ##################################################################################### + ########################################################################## + # SECTION 2 - Works processing # + # NB These functions may operate asynchronously over multiple albums (as well as multiple tracks) # + ########################################################################## def work_process(self, workId, tries, response, reply, error): """ @@ -4825,43 +5777,67 @@ def work_process(self, workId, tries, response, reply, error): for track, album in tuples: release_id = track.metadata['musicbrainz_albumid'] if self.WARNING or self.INFO: - write_log(release_id, 'warning', "%r: Network error retrieving work record. Error code %r", - workId, error) + write_log( + release_id, + 'warning', + "%r: Network error retrieving work record. Error code %r", + workId, + error) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Removed request after network error. Requests = %s", - album._requests) + write_log( + release_id, + 'debug', + "Removed request after network error. Requests = %s", + album._requests) if tries < self.MAX_RETRIES: user_data = True if self.DEBUG or self.INFO: write_log(release_id, 'debug', "REQUEUEING...") if str(error) == '204': # Authentication error if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "... without user authentication") + write_log( + release_id, 'debug', "... without user authentication") user_data = False - self.append_tag(release_id, track.metadata, '~cwp_error', - '3. Authentication failure - data retrieval omits user-specific requests') - self.work_add_track(album, track, workId, tries + 1, user_data) + self.append_tag( + release_id, + track.metadata, + '~cwp_error', + '3. Authentication failure - data retrieval omits user-specific requests') + self.work_add_track( + album, track, workId, tries + 1, user_data) else: if self.ERROR or self.INFO: - write_log(release_id, 'error', "EXHAUSTED MAX RE-TRIES for XML lookup for track %s", track) - self.append_tag(release_id, track.metadata, '~cwp_error', - "4. ERROR: MISSING METADATA due to network errors. Re-try or fix manually.") + write_log( + release_id, + 'error', + "EXHAUSTED MAX RE-TRIES for XML lookup for track %s", + track) + self.append_tag( + release_id, + track.metadata, + '~cwp_error', + "4. ERROR: MISSING METADATA due to network errors. Re-try or fix manually.") self.album_remove_request(release_id, album) return tuples = self.works_queue.remove(workId) - # if self.INFO: - # write_log('session', 'info', 'Found work id %s. Tuples are %r', workId, tuples) if tuples: new_queue = [] prev_album = None for tup_num, (track, album) in enumerate(tuples): release_id = track.metadata['musicbrainz_albumid'] - # Note that this need to be set here as the work may cover multiple albums + # Note that this need to be set here as the work may cover + # multiple albums if album != prev_album: - write_log(release_id, 'debug', - "Work_process. FOUND WORK: %s for album %s", - workId, album) - write_log(release_id, 'debug', "Requests for album %s = %s", album, album._requests) + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', + "Work_process. FOUND WORK: %s for album %s", + workId, album) + write_log( + release_id, + 'debug', + "Requests for album %s = %s", + album, + album._requests) prev_album = album if self.INFO: write_log(release_id, 'info', "RESPONSE = %s", response) @@ -4871,12 +5847,24 @@ def work_process(self, workId, tries, response, reply, error): if workId in w and w not in wid_list: wid_list.append(w) if self.INFO: - write_log(release_id, 'info', 'wid_list for %s is %s', workId, wid_list) + write_log( + release_id, + 'info', + 'wid_list for %s is %s', + workId, + wid_list) for wid in wid_list: # wid is a tuple - write_log(release_id, 'info', 'processing workId tuple: %r', wid) - metaList = self.work_process_metadata(release_id, workId, wid, track, response) + if self.INFO: + write_log( + release_id, + 'info', + 'processing workId tuple: %r', + wid) + metaList = self.work_process_metadata( + release_id, workId, wid, track, response) parentList = metaList[0] - # returns [parent id, parent name] or None if no parent found + # returns [parent id, parent name] or None if no parent + # found arrangers = metaList[1] # not just arrangers - also composers, lyricists etc. if wid in self.parts: @@ -4890,7 +5878,8 @@ def work_process(self, workId, tries, response, reply, error): if parentList: # first fix the sort order of multi-works at the prev level # so that recordings of multiple movements of the same parent work will have the - # movements listed in the correct order (i.e. ordering-key, if available) + # movements listed in the correct order (i.e. + # ordering-key, if available) if len(wid) > 1: for idx in wid: if idx == workId: @@ -4899,12 +5888,18 @@ def work_process(self, workId, tries, response, reply, error): 'target-type:work', 'direction:backward', 'ordering-key'] - parse_result = parse_data(release_id, response, [], *match_tree) + parse_result = parse_data( + release_id, response, [], *match_tree) if self.INFO: - write_log(release_id, 'info', 'multi-works - ordering key for id %s is %s', idx, - parse_result) + write_log( + release_id, + 'info', + 'multi-works - ordering key for id %s is %s', + idx, + parse_result) if parse_result: - if isinstance(parse_result[0], str) and parse_result[0].isdigit(): + if isinstance( + parse_result[0], str) and parse_result[0].isdigit(): key = int(parse_result[0]) elif isinstance(parse_result[0], int): key = parse_result[0] @@ -4915,26 +5910,38 @@ def work_process(self, workId, tries, response, reply, error): parentIds = parentList[0] parents = parentList[1] if self.INFO: - write_log(release_id, 'info', 'Parents - ids: %s, names: %s', parentIds, parents) - # remove any parents that are descendants of wid as they will result in circular references - del_list =[] + write_log( + release_id, + 'info', + 'Parents - ids: %s, names: %s', + parentIds, + parents) + # remove any parents that are descendants of wid as + # they will result in circular references + del_list = [] for i, parentId in enumerate(parentIds): for work_item in wid: - if work_item in self.child_listing and parentId in self.child_listing[work_item]: + if work_item in self.child_listing and parentId in self.child_listing[ + work_item]: del_list.append(i) for i in del_list: removed_id = parentIds.pop(i) removed_name = parents.pop(i) if self.ERROR or self.INFO: - write_log(release_id, 'error', "Found parent which is descendant of child - " - "not using, to prevent circular references. id = %s," - " name = %s", - removed_id, removed_name) + write_log( + release_id, 'error', "Found parent which is descendant of child - " + "not using, to prevent circular references. id = %s," + " name = %s", removed_id, removed_name) tm = track.metadata - self.append_tag(release_id, tm, '~cwp_error', - '5. Found parent which which is descendant of child - not using ' - 'to prevent circular references. id = ' - + removed_id + ', name = ' + removed_name) + self.append_tag( + release_id, + tm, + '~cwp_error', + '5. Found parent which which is descendant of child - not using ' + 'to prevent circular references. id = ' + + removed_id + + ', name = ' + + removed_name) # de-dup parent ids before we start parentIds = list( @@ -4949,8 +5956,11 @@ def work_process(self, workId, tries, response, reply, error): if parentIds: if wid in self.works_cache: - # Make sure we haven't done this relationship before, perhaps for another album - if set(self.works_cache[wid]) != set(parentIds): + # Make sure we haven't done this + # relationship before, perhaps for another + # album + if set( + self.works_cache[wid]) != set(parentIds): prev_ids = tuple(self.works_cache[wid]) prev_name = self.parts[prev_ids]['name'] self.works_cache[wid] = add_list_uniquely( @@ -4971,14 +5981,14 @@ def work_process(self, workId, tries, response, reply, error): else: self.works_cache[wid] = parentIds self.parts[wid]['parent'] = parentIds - self.parts[tuple(parentIds)]['name'] = parents + self.parts[tuple(parentIds) + ]['name'] = parents self.work_listing[album].append( tuple(parentIds)) # de-duplicate the parent names self.parts[tuple(parentIds)]['name'] = list( collections.OrderedDict.fromkeys(self.parts[tuple(parentIds)]['name'])) - # list(set()) won't work as need to retain - # order + # list(set()) won't work as need to retain order # de-duplicate the parent ids also, otherwise they will be treated as a separate parent # in the trackback structure self.parts[wid]['parent'] = list( @@ -4988,27 +5998,37 @@ def work_process(self, workId, tries, response, reply, error): collections.OrderedDict.fromkeys( self.works_cache[wid])) if self.INFO: - write_log(release_id, 'info', - 'Added parent ids to work_listing: %s, [Requests = %s]', - parentIds, album._requests) + write_log( + release_id, + 'info', + 'Added parent ids to work_listing: %s, [Requests = %s]', + parentIds, + album._requests) if self.INFO: - write_log(release_id, 'info', 'work_listing after adding parents: %s', - self.work_listing[album]) - # the higher-level work might already be in cache - # from another album + write_log( + release_id, + 'info', + 'work_listing after adding parents: %s', + self.work_listing[album]) + # the higher-level work might already be in + # cache from another album if tuple( parentIds) in self.works_cache and self.USE_CACHE: not_in_cache = self.check_cache( track.metadata, album, track, tuple(parentIds), []) for workId_tuple in not_in_cache: - new_queue.append((release_id, album, track, workId_tuple)) + new_queue.append( + (release_id, album, track, workId_tuple)) else: if not self.USE_CACHE: - if tuple(parentIds) in self.works_cache: - del self.works_cache[tuple(parentIds)] + if tuple( + parentIds) in self.works_cache: + del self.works_cache[tuple( + parentIds)] for parentId in parentIds: - new_queue.append((release_id, album, track, (parentId,))) + new_queue.append( + (release_id, album, track, (parentId,))) else: # so we remember we looked it up and found none @@ -5017,7 +6037,8 @@ def work_process(self, workId, tries, response, reply, error): if wid not in self.top[album]: self.top[album].append(wid) if self.INFO: - write_log(release_id, 'info', "TOP[album]: %s", self.top[album]) + write_log( + release_id, 'info', "TOP[album]: %s", self.top[album]) else: # so we remember we looked it up and found none self.parts[wid]['no_parent'] = True @@ -5025,29 +6046,45 @@ def work_process(self, workId, tries, response, reply, error): self.top[album].append(wid) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "End of tuple processing for workid %s in album %s, track %s," - " requests remaining = %s, new queue is %r", - workId, album, track, album._requests, new_queue) + write_log( + release_id, + 'debug', + "End of tuple processing for workid %s in album %s, track %s," + " requests remaining = %s, new queue is %r", + workId, + album, + track, + album._requests, + new_queue) self.album_remove_request(release_id, album) for queued_item in new_queue: if self.INFO: - write_log(release_id, 'info', 'Have a new queue: queued_item = %r', queued_item) - write_log(release_id, 'debug', - 'Penultimate end of work_process for %s (subject to parent lookups in "new_queue")', workId) + write_log( + release_id, + 'info', + 'Have a new queue: queued_item = %r', + queued_item) + if self.DEBUG or self.INFO: + write_log( + release_id, + 'debug', + 'Penultimate end of work_process for %s (subject to parent lookups in "new_queue")', + workId) for queued_item in new_queue: - self.work_not_in_cache(queued_item[0], queued_item[1], queued_item[2], queued_item[3]) - write_log(release_id, 'debug', - 'Ultimate end of work_process for %s', workId) + self.work_not_in_cache( + queued_item[0], + queued_item[1], + queued_item[2], + queued_item[3]) + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', + 'Ultimate end of work_process for %s', workId) if album._requests == 0: self.process_album(release_id, album) album._finalize_loading(None) release_status[release_id]['works-done'] = datetime.now() close_log(release_id, 'works') - - - - def work_process_metadata(self, release_id, workId, wid, track, response): """ Process XML node @@ -5064,57 +6101,101 @@ def work_process_metadata(self, release_id, workId, wid, track, response): all_tags = parse_data(release_id, response, [], 'tags', 'name') self.parts[wid]['folks_genres'] = all_tags - self.parts[wid]['worktype_genres'] = parse_data(release_id, response, [], 'type') - key = parse_data(release_id, response, [], 'attributes', 'type:Key', 'value') + self.parts[wid]['worktype_genres'] = parse_data( + release_id, response, [], 'type') + key = parse_data( + release_id, + response, + [], + 'attributes', + 'type:Key', + 'value') self.parts[wid]['key'] = key composed_begin_dates = year( - parse_data(release_id, response, [], 'relations', 'target-type:artist', - 'type:composer', 'begin')) + parse_data( + release_id, + response, + [], + 'relations', + 'target-type:artist', + 'type:composer', + 'begin')) composed_end_dates = year( - parse_data(release_id, response, [], 'relations', 'target-type:artist', - 'type:composer', 'end')) + parse_data( + release_id, + response, + [], + 'relations', + 'target-type:artist', + 'type:composer', + 'end')) if composed_begin_dates == composed_end_dates: composed_dates = composed_begin_dates else: - composed_dates = list(zip(composed_begin_dates, composed_end_dates)) + composed_dates = list( + zip(composed_begin_dates, composed_end_dates)) composed_dates = [y + DATE_SEP + z for y, z in composed_dates] self.parts[wid]['composed_dates'] = composed_dates published_begin_dates = year( - parse_data(release_id, response, [], 'relations', 'target-type:label', - 'type:publishing', 'begin')) + parse_data( + release_id, + response, + [], + 'relations', + 'target-type:label', + 'type:publishing', + 'begin')) published_end_dates = year( - parse_data(release_id, response, [], 'relations', 'target-type:label', - 'type:publishing', 'end')) + parse_data( + release_id, + response, + [], + 'relations', + 'target-type:label', + 'type:publishing', + 'end')) if published_begin_dates == published_end_dates: published_dates = published_begin_dates else: - published_dates = list(zip(published_begin_dates, published_end_dates)) + published_dates = list( + zip(published_begin_dates, published_end_dates)) published_dates = [x + DATE_SEP + y for x, y in published_dates] self.parts[wid]['published_dates'] = published_dates premiered_begin_dates = year( - parse_data(release_id, response, [], 'relations', 'target-type:place', - 'type:premiere', 'begin')) + parse_data( + release_id, + response, + [], + 'relations', + 'target-type:place', + 'type:premiere', + 'begin')) premiered_end_dates = year( - parse_data(release_id, response, [], 'relations', 'target-type:place', - 'type:premiere', 'end')) + parse_data( + release_id, + response, + [], + 'relations', + 'target-type:place', + 'type:premiere', + 'end')) if premiered_begin_dates == premiered_end_dates: premiered_dates = premiered_begin_dates else: - premiered_dates = list(zip(premiered_begin_dates, premiered_end_dates)) + premiered_dates = list( + zip(premiered_begin_dates, premiered_end_dates)) premiered_dates = [x + DATE_SEP + y for x, y in premiered_dates] self.parts[wid]['premiered_dates'] = premiered_dates - - - if 'artist_locale' in config.setting: locale = config.setting["artist_locale"] # NB this is the Picard code in /util lang = locale.split("_")[0] alias = parse_data(release_id, response, [], 'aliases', 'locale:' + lang, 'primary:True', 'name') - user_tags = parse_data(release_id, response, [], 'user-tags', 'name') + user_tags = parse_data( + release_id, response, [], 'user-tags', 'name') if config.setting['cwp_aliases_tags_user']: tags = user_tags else: @@ -5128,10 +6209,16 @@ def work_process_metadata(self, release_id, workId, wid, track, response): self.parts[wid]['alias'][ind] = '; '.join( alias) relation_list = parse_data(release_id, response, [], 'relations') - return self.work_process_relations(release_id, track, workId, wid, relation_list) + return self.work_process_relations( + release_id, track, workId, wid, relation_list) - - def work_process_relations(self, release_id, track, workId, wid, relations): + def work_process_relations( + self, + release_id, + track, + workId, + wid, + relations): """ Find the parents etc. NB track is just the last album/track for this work - used as being @@ -5146,62 +6233,113 @@ def work_process_relations(self, release_id, track, workId, wid, relations): :return: """ if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "In work_process_relations. Relations--> %s", relations) + write_log( + release_id, + 'debug', + "In work_process_relations. Relations--> %s", + relations) if track: options = self.options[track] else: options = config.setting new_workIds = [] new_works = [] - relation_attributes = parse_data(release_id, relations, [], 'target-type:work', - 'type:parts', 'direction:backward', 'attributes') + relation_attributes = parse_data( + release_id, + relations, + [], + 'target-type:work', + 'type:parts', + 'direction:backward', + 'attributes') new_work_list = [] for relation_attribute in relation_attributes: - if ('part of collection' not in relation_attribute) or options['cwp_collections']: - new_work_list += parse_data(release_id, relations, [], 'target-type:work', - 'type:parts', 'direction:backward', 'work') - if ('part of collection' in relation_attribute) and not options['cwp_collections'] and self.INFO: - write_log(release_id, 'info', - 'Not getting parent work because relationship is "part of collection" and option not selected') + if ( + 'part of collection' not in relation_attribute) or options['cwp_collections']: + new_work_list += parse_data(release_id, + relations, + [], + 'target-type:work', + 'type:parts', + 'direction:backward', + 'work') + if ( + 'part of collection' in relation_attribute) and not options['cwp_collections'] and self.INFO: + write_log( + release_id, + 'info', + 'Not getting parent work because relationship is "part of collection" and option not selected') if new_work_list: if self.INFO: - write_log(release_id, 'info', 'new_work_list: %s', new_work_list) + write_log( + release_id, + 'info', + 'new_work_list: %s', + new_work_list) new_workIds = parse_data(release_id, new_work_list, [], 'id') new_works = parse_data(release_id, new_work_list, [], 'title') else: - arrangement_of = parse_data(release_id, relations, [], 'target-type:work', - 'type:arrangement', 'direction:backward', 'work') + arrangement_of = parse_data( + release_id, + relations, + [], + 'target-type:work', + 'type:arrangement', + 'direction:backward', + 'work') if arrangement_of and options['cwp_arrangements']: new_workIds = parse_data(release_id, arrangement_of, [], 'id') new_works = parse_data(release_id, arrangement_of, [], 'title') self.parts[wid]['arrangement'] = True else: - medley_of = parse_data(release_id, relations, [], 'target-type:work', - 'type:medley', 'work') - direction = parse_data(release_id, relations, [], 'target-type:work', - 'type:medley', 'direction') + medley_of = parse_data( + release_id, + relations, + [], + 'target-type:work', + 'type:medley', + 'work') + direction = parse_data( + release_id, + relations, + [], + 'target-type:work', + 'type:medley', + 'direction') if 'backward' not in direction: if self.INFO: - write_log(release_id, 'info', 'Medley_of: %s', medley_of) + write_log( + release_id, 'info', 'Medley_of: %s', medley_of) if medley_of and options['cwp_medley']: medley_list = [] medley_id_list = [] for medley_item in medley_of: medley_list = medley_list + \ - parse_data(release_id, medley_item, [], 'title') + parse_data(release_id, medley_item, [], 'title') medley_id_list = medley_id_list + \ - parse_data(release_id, medley_item, [], 'id') + parse_data(release_id, medley_item, [], 'id') # (parse_data is a list...) new_workIds = medley_id_list new_works = medley_list if self.INFO: - write_log(release_id, 'info', 'Medley_list: %s', medley_list) + write_log( + release_id, 'info', 'Medley_list: %s', medley_list) self.parts[wid]['medley_list'] = medley_list if self.INFO: - write_log(release_id, 'info', 'New works: ids: %s, names: %s', new_workIds, new_works) - - artists = get_artists(options, release_id, {}, relations, 'work')['artists'] + write_log( + release_id, + 'info', + 'New works: ids: %s, names: %s', + new_workIds, + new_works) + + artists = get_artists( + options, + release_id, + {}, + relations, + 'work')['artists'] # artist_types = ['arranger', 'instrument arranger', 'orchestrator', 'composer', 'writer', 'lyricist', # 'librettist', 'revised by', 'translator', 'reconstructed by', 'vocal arranger'] @@ -5221,7 +6359,12 @@ def album_add_request(self, release_id, album): :return: """ album._requests += 1 - write_log(release_id, 'debug', "Added album request - requests: %s", album._requests) + if self.DEBUG or self.INFO: + write_log( + release_id, + 'debug', + "Added album request - requests: %s", + album._requests) def album_remove_request(self, release_id, album): """ @@ -5232,7 +6375,12 @@ def album_remove_request(self, release_id, album): :return: """ album._requests -= 1 - write_log(release_id, 'debug', "Removed album request - requests: %s", album._requests) + if self.DEBUG or self.INFO: + write_log( + release_id, + 'debug', + "Removed album request - requests: %s", + album._requests) ################################################## # SECTION 3 - Organise tracks and works in album # @@ -5260,8 +6408,16 @@ def process_album(self, release_id, album): for workId in self.work_listing[album]: if workId in self.parts: if self.INFO: - write_log(release_id, 'info', 'Processing workid: %s', workId) - write_log(release_id, 'info', 'self.work_listing[album]: %s', self.work_listing[album]) + write_log( + release_id, + 'info', + 'Processing workid: %s', + workId) + write_log( + release_id, + 'info', + 'self.work_listing[album]: %s', + self.work_listing[album]) if len(workId) > 1: # fix the order of names using ordering keys gathered in # work_process @@ -5290,19 +6446,29 @@ def process_album(self, release_id, album): self.parts[workId]['name'] = self.parts[workId]['alias'][:] topId = None if self.INFO: - write_log(release_id, 'info', 'Works_cache: %s', self.works_cache) + write_log( + release_id, + 'info', + 'Works_cache: %s', + self.works_cache) if workId in self.works_cache: parentIds = tuple(self.works_cache[workId]) # for parentId in parentIds: if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Create inverses: %s, %s", workId, parentIds) + write_log( + release_id, + 'debug', + "Create inverses: %s, %s", + workId, + parentIds) if parentIds in self.partof[album]: if workId not in self.partof[album][parentIds]: self.partof[album][parentIds].append(workId) else: self.partof[album][parentIds] = [workId] if self.INFO: - write_log(release_id, 'info', "Partof: %s", self.partof[album][parentIds]) + write_log(release_id, 'info', "Partof: %s", + self.partof[album][parentIds]) if 'no_parent' in self.parts[parentIds]: # to handle case if album includes works already in # cache from a different album @@ -5319,8 +6485,13 @@ def process_album(self, release_id, album): # work out the full hierarchy and part levels height = 0 if self.INFO: - write_log(release_id, 'info', "TOP: %s, \nALBUM: %s, \nTOP[ALBUM]: %s", self.top, album, - self.top[album]) + write_log( + release_id, + 'info', + "TOP: %s, \nALBUM: %s, \nTOP[ALBUM]: %s", + self.top, + album, + self.top[album]) if len(self.top[album]) > 1: single_work_album = 0 else: @@ -5328,17 +6499,39 @@ def process_album(self, release_id, album): for topId in self.top[album]: self.create_trackback(release_id, album, topId) if self.INFO: - write_log(release_id, 'info', "Top id = %s, Name = %s", topId, self.parts[topId]['name']) + write_log( + release_id, + 'info', + "Top id = %s, Name = %s", + topId, + self.parts[topId]['name']) if self.INFO: - write_log(release_id, 'info', "Trackback before levels: %s", self.trackback[album][topId]) + write_log( + release_id, + 'info', + "Trackback before levels: %s", + self.trackback[album][topId]) if self.INFO: - write_log(release_id, 'info', "Trackback before levels: %s", self.trackback[album][topId]) - work_part_levels = self.level_calc(release_id, self.trackback[album][topId], height) + write_log( + release_id, + 'info', + "Trackback before levels: %s", + self.trackback[album][topId]) + work_part_levels = self.level_calc( + release_id, self.trackback[album][topId], height) if self.INFO: - write_log(release_id, 'info', "Trackback after levels: %s", self.trackback[album][topId]) + write_log( + release_id, + 'info', + "Trackback after levels: %s", + self.trackback[album][topId]) if self.INFO: - write_log(release_id, 'info', "Trackback after levels: %s", self.trackback[album][topId]) + write_log( + release_id, + 'info', + "Trackback after levels: %s", + self.trackback[album][topId]) # determine the level which will be the principal 'work' level if work_part_levels >= 3: ref_level = work_part_levels - single_work_album @@ -5353,7 +6546,26 @@ def process_album(self, release_id, album): 'name': self.parts[topId]['name'], 'single': single_work_album} # set the metadata in sequence defined by the work structure - answer = self.process_trackback(release_id, album, self.trackback[album][topId], ref_height, top_info) + answer = self.process_trackback( + release_id, + album, + self.trackback[album][topId], + ref_height, + top_info) + """ + trackback is a tree in the form {album: {id: , children:{id: , children{}, + id: etc}, + id: etc} } + process_trackback uses the trackback tree to derive title and level_0 based hierarchies + from the structure. It also returns a tuple (id, tracks), where tracks has the structure + {'track': [(track, height), (track, height), ...tuples...] + 'work': [[worknames], [worknames], ...lists...] + 'tracknumber': [num, num, ...integers...] + 'title': [title, title, ...strings...]} + each list is the same length - i.e. the number of tracks for the top work + there can be more than one workname for a track + height is the number of part levels for the related track + """ if answer: tracks = answer[1]['track'] if self.INFO: @@ -5366,16 +6578,25 @@ def process_album(self, release_id, album): workIds = interpret(tm['~cwp_workid_0']) if workIds: count = 0 - self.process_work_artists(release_id, album, track_meta, workIds, tm, count) + self.process_work_artists( + release_id, album, track_meta, workIds, tm, count) title_work_levels = 0 if '~cwp_title_work_levels' in tm: title_work_levels = int(tm['~cwp_title_work_levels']) - self.extend_metadata(release_id, top_info, track_meta, ref_height, - title_work_levels) # revise for new data + self.extend_metadata( + release_id, + top_info, + track_meta, + ref_height, + title_work_levels) # revise for new data if track_meta not in self.tracks[album]: self.tracks[album].append(track_meta) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "FINISHED TRACK PROCESSING FOR Top work id: %s", topId) + write_log( + release_id, + 'debug', + "FINISHED TRACK PROCESSING FOR Top work id: %s", + topId) # Need to redo the loop so that all album-wide tm is updated before # publishing for track in self.tracks[album]: @@ -5396,7 +6617,8 @@ def process_album(self, release_id, album): if album in self.orphan_tracks: for track in self.orphan_tracks[album]: self.publish_metadata(release_id, album, track) - write_log(release_id, 'debug', "PROCESS ALBUM function complete") + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', "PROCESS ALBUM function complete") def create_trackback(self, release_id, album, parentId): """ @@ -5411,10 +6633,13 @@ def create_trackback(self, release_id, album, parentId): if parentId in self.partof[album]: # NB parentId is a tuple for child in self.partof[album][parentId]: # NB child is a tuple if child in self.partof[album]: - child_trackback = self.create_trackback(release_id, album, child) - self.append_trackback(release_id, album, parentId, child_trackback) + child_trackback = self.create_trackback( + release_id, album, child) + self.append_trackback( + release_id, album, parentId, child_trackback) else: - self.append_trackback(release_id, album, parentId, self.trackback[album][child]) + self.append_trackback( + release_id, album, parentId, self.trackback[album][child]) return self.trackback[album][parentId] else: return self.trackback[album][parentId] @@ -5438,22 +6663,44 @@ def append_trackback(self, release_id, album, parentId, child): write_log(release_id, 'info', "TRYING TO APPEND...") self.trackback[album][parentId]['children'].append(child) if self.INFO: - write_log(release_id, 'info', "...PARENT %s - ADDED %s as child", self.parts[parentId]['name'], - child) + write_log( + release_id, + 'info', + "...PARENT %s - ADDED %s as child", + self.parts[parentId]['name'], + child) else: if self.INFO: - write_log(release_id, 'info', "Parent %s already has %s as child", parentId, child) + write_log( + release_id, + 'info', + "Parent %s already has %s as child", + parentId, + child) else: self.trackback[album][parentId]['children'] = [child] if self.INFO: - write_log(release_id, 'info', "Existing PARENT %s - ADDED %s as child", - self.parts[parentId]['name'], child) + write_log( + release_id, + 'info', + "Existing PARENT %s - ADDED %s as child", + self.parts[parentId]['name'], + child) else: self.trackback[album][parentId]['id'] = parentId self.trackback[album][parentId]['children'] = [child] if self.INFO: - write_log(release_id, 'info', "New PARENT %s - ADDED %s as child", self.parts[parentId]['name'], child) - write_log(release_id, 'info', "APPENDED TRACKBACK: %s", self.trackback[album][parentId]) + write_log( + release_id, + 'info', + "New PARENT %s - ADDED %s as child", + self.parts[parentId]['name'], + child) + write_log( + release_id, + 'info', + "APPENDED TRACKBACK: %s", + self.trackback[album][parentId]) return self.trackback[album][parentId] def level_calc(self, release_id, trackback, height): @@ -5491,7 +6738,13 @@ def level_calc(self, release_id, trackback, height): # SECTION 4 - Process tracks within album # ########################################### - def process_trackback(self, release_id, album_req, trackback, ref_height, top_info): + def process_trackback( + self, + release_id, + album_req, + trackback, + ref_height, + top_info): """ Set work structure metadata & govern other metadata-setting processes :param release_id: name for log file - usually =musicbrainz_albumid @@ -5503,7 +6756,11 @@ def process_trackback(self, release_id, album_req, trackback, ref_height, top_in :return: """ if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "IN PROCESS_TRACKBACK. Trackback = %s", trackback) + write_log( + release_id, + 'debug', + "IN PROCESS_TRACKBACK. Trackback = %s", + trackback) tracks = collections.defaultdict(dict) process_now = False if 'meta' in trackback: @@ -5519,24 +6776,31 @@ def process_trackback(self, release_id, album_req, trackback, ref_height, top_in workId = tuple(trackback['id']) if depth != 0: if 'children' in trackback: - child_response = self.process_trackback_children(release_id, album_req, trackback, ref_height, - top_info, tracks) + child_response = self.process_trackback_children( + release_id, album_req, trackback, ref_height, top_info, tracks) tracks = child_response[1] if self.INFO: - write_log(release_id, 'info', - 'Bottom level for this trackback is higher level elsewhere - adjusting levels') + write_log( + release_id, + 'info', + 'Bottom level for this trackback is higher level elsewhere - adjusting levels') depth = 0 if self.INFO: write_log(release_id, 'info', "WorkId %s", workId) if self.INFO: - write_log(release_id, 'info', "Work name %s", self.parts[workId]['name']) + write_log( + release_id, + 'info', + "Work name %s", + self.parts[workId]['name']) for track, album in trackback['meta']: if album == album_req: if self.INFO: write_log(release_id, 'info', "Track: %s", track) tm = track.metadata if self.INFO: - write_log(release_id, 'info', "Track metadata = %s", tm) + write_log( + release_id, 'info', "Track metadata = %s", tm) tm['~cwp_workid_' + str(depth)] = workId self.write_tags(release_id, track, tm, workId) self.make_annotations(release_id, track, workId) @@ -5561,7 +6825,8 @@ def process_trackback(self, release_id, album_req, trackback, ref_height, top_in tm['~cwp_work_top'] = toptemp tm['~cwp_single_work_album'] = top_info['single'] if self.INFO: - write_log(release_id, 'info', "Track metadata = %s", tm) + write_log( + release_id, 'info', "Track metadata = %s", tm) if 'track' in tracks: tracks['track'].append((track, height)) else: @@ -5573,18 +6838,46 @@ def process_trackback(self, release_id, album_req, trackback, ref_height, top_in if self.DEBUG or self.INFO: write_log(release_id, 'debug', "LEAVING PROCESS_TRACKBACK") if self.INFO: - write_log(release_id, 'info', "depth %s Response = %s", depth, response) + write_log( + release_id, + 'info', + "depth %s Response = %s", + depth, + response) return response else: return None else: - response = self.process_trackback_children(release_id, album_req, trackback, ref_height, top_info, tracks) + response = self.process_trackback_children( + release_id, album_req, trackback, ref_height, top_info, tracks) return response - def process_trackback_children(self, release_id, album_req, trackback, ref_height, top_info, tracks): + def process_trackback_children( + self, + release_id, + album_req, + trackback, + ref_height, + top_info, + tracks): + """ + TODO add some better documentation! + :param release_id: name for log file - usually =musicbrainz_albumid + unless called outside metadata processor + :param album_req: + :param trackback: + :param ref_height: + :param top_info: + :param tracks: + :return: + """ if 'id' in trackback and 'depth' in trackback and 'height' in trackback: if self.DEBUG or self.INFO: - write_log(release_id, 'debug', 'In process_children_trackback for trackback %s', trackback) + write_log( + release_id, + 'debug', + 'In process_children_trackback for trackback %s', + trackback) depth = trackback['depth'] height = trackback['height'] parentId = tuple(trackback['id']) @@ -5593,8 +6886,13 @@ def process_trackback_children(self, release_id, album_req, trackback, ref_heigh for child in trackback['children']: width += 1 if self.INFO: - write_log(release_id, 'info', "child trackback = %s", child) - answer = self.process_trackback(release_id, album_req, child, ref_height, top_info) + write_log( + release_id, + 'info', + "child trackback = %s", + child) + answer = self.process_trackback( + release_id, album_req, child, ref_height, top_info) if answer: workId = answer[0] child_tracks = answer[1]['track'] @@ -5603,9 +6901,17 @@ def process_trackback_children(self, release_id, album_req, trackback, ref_heigh track_height = track[1] part_level = track_height - height if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Calling set metadata %s", (part_level, workId, - parentId, parent, track_meta)) - self.set_metadata(release_id, part_level, workId, parentId, parent, track_meta) + write_log( + release_id, + 'debug', + "Calling set metadata %s", + (part_level, + workId, + parentId, + parent, + track_meta)) + self.set_metadata( + release_id, part_level, workId, parentId, parent, track_meta) if 'track' in tracks: tracks['track'].append( (track_meta, track_height)) @@ -5618,7 +6924,8 @@ def process_trackback_children(self, release_id, album_req, trackback, ref_heigh tracks['title'].append(title) else: tracks['title'] = [title] - work = tm['~cwp_work_0'] + # to make sure we get it as a list + work = tm.getall('~cwp_work_0') if 'work' in tracks: tracks['work'].append(work) else: @@ -5635,25 +6942,44 @@ def process_trackback_children(self, release_id, album_req, trackback, ref_heigh track = tracks['track'][0][0] # NB this will only be the first track of tracks, but its # options will be used for the structure - self.derive_from_structure(release_id, top_info, tracks, height, depth, width, 'title') + self.derive_from_structure( + release_id, top_info, tracks, height, depth, width, 'title') if self.options[track]["cwp_level0_works"]: # replace hierarchical works with those from work_0 (for # consistency) - self.derive_from_structure(release_id, top_info, tracks, height, depth, width, 'work') + self.derive_from_structure( + release_id, top_info, tracks, height, depth, width, 'work') if self.INFO: - write_log(release_id, 'info', "Trackback result for %s = %s", parentId, tracks) + write_log( + release_id, + 'info', + "Trackback result for %s = %s", + parentId, + tracks) response = parentId, tracks if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "LEAVING PROCESS_CHILD_TRACKBACK depth %s Response = %s", - depth, response) + write_log( + release_id, + 'debug', + "LEAVING PROCESS_CHILD_TRACKBACK depth %s Response = %s", + depth, + response) return response else: return None else: return None - def derive_from_structure(self, release_id, top_info, tracks, height, depth, width, name_type): + def derive_from_structure( + self, + release_id, + top_info, + tracks, + height, + depth, + width, + name_type): """ Derive title (or work level-0) components from MB hierarchical work structure :param release_id: name for log file - usually =musicbrainz_albumid @@ -5677,46 +7003,81 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid # options will be used for the structure single_work_track = False # default if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Deriving info for %s from structure for tracks %s", - name_type, tracks['track']) + write_log( + release_id, + 'debug', + "Deriving info for %s from structure for tracks %s", + name_type, + tracks['track']) + write_log( + release_id, + 'info', + '%ss are %r', + name_type, + tracks[name_type]) if 'tracknumber' in tracks: sorted_tracknumbers = sorted(tracks['tracknumber']) else: sorted_tracknumbers = None if self.INFO: - write_log(release_id, 'info', "SORTED TRACKNUMBERS: %s", sorted_tracknumbers) + write_log( + release_id, + 'info', + "SORTED TRACKNUMBERS: %s", + sorted_tracknumbers) common_len = 0 if name_type in tracks: meta_str = "_title" if name_type == 'title' else "_X0" + # in case of works, could be a list of lists name_list = tracks[name_type] if self.INFO: - write_log(release_id, 'info', "%s list %s", name_type, name_list) - # only one track in this work so try and extract using colons - if len(name_list) == 1: + write_log( + release_id, + 'info', + "%s list %s", + name_type, + name_list) + if len( + name_list) == 1: # only one track in this work so try and extract using colons single_work_track = True track_height = tracks['track'][0][1] - if track_height - height > 0: # part_level + if track_height - height > 0: # track_height - height == part_level if name_type == 'title': if self.DEBUG or self.INFO: - write_log(release_id, 'debug', - "Single track work. Deriving directly from title text: %s", track) + write_log( + release_id, + 'debug', + "Single track work. Deriving directly from title text: %s", + track) ti = name_list[0] - common_subset = self.derive_from_title(release_id, track, ti)[ - 0] + common_subset = self.derive_from_title( + release_id, track, ti)[0] else: common_subset = "" else: common_subset = name_list[0] if self.INFO: - write_log(release_id, 'info', "%s is single-track work. common_subset is set to %s", - tracks['track'][0][0], common_subset) + write_log( + release_id, + 'info', + "%s is single-track work. common_subset is set to %s", + tracks['track'][0][0], + common_subset) if common_subset: common_len = len(common_subset) else: common_len = 0 - else: - compare = name_list[0].split() - for name in name_list: + else: # NB if names are lists of lists, we'll assume they all start the same way + if isinstance(name_list[0], list): + compare = name_list[0][0].split() + else: + # a list of the words in the first name + compare = name_list[0].split() + for name_item in name_list: + if isinstance(name_item, list): + name = name_item[0] + else: + name = name_item lcs = longest_common_sequence(compare, name.split()) compare = lcs['sequence'] if not compare: @@ -5725,15 +7086,25 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid if lcs['length'] > 0: common_subset = " ".join(compare) if self.INFO: - write_log(release_id, 'info', - "Common subset from %ss at level %s, item name %s ..........", name_type, - tracks['track'][0][1] - height, name) + write_log( + release_id, + 'info', + "Common subset from %ss at level %s, item name %s ..........", + name_type, + tracks['track'][0][1] - + height, + name) if self.INFO: - write_log(release_id, 'info', "..........is %s", common_subset) + write_log( + release_id, 'info', "..........is %s", common_subset) common_len = len(common_subset) if self.INFO: - write_log(release_id, 'info', "checked for common sequence - length is %s", common_len) + write_log( + release_id, + 'info', + "checked for common sequence - length is %s", + common_len) for i, track_item in enumerate(tracks['track']): track_meta = track_item[0] tm = track_meta.metadata @@ -5741,10 +7112,18 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid part_level = track_item[1] - height if common_len > 0: if self.INFO: - write_log(release_id, 'info', "Use %s info for track: %s at level %s", name_type, track_meta, - part_level) + write_log( + release_id, + 'info', + "Use %s info for track: %s at level %s", + name_type, + track_meta, + part_level) name = tracks[name_type][i] - work = name[:common_len] + if isinstance(name, list): + work = name[0][:common_len] + else: + work = name[:common_len] work = work.rstrip(":,.;- ") if self.options[track]["cwp_removewords_p"]: removewords = self.options[track]["cwp_removewords_p"].split( @@ -5752,20 +7131,33 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid else: removewords = [] if self.INFO: - write_log(release_id, 'info', "Removewords (in %s) = %s", name_type, removewords) + write_log( + release_id, + 'info', + "Removewords (in %s) = %s", + name_type, + removewords) for prefix in removewords: prefix2 = str(prefix).lower().rstrip() if prefix2[0] != " ": prefix2 = " " + prefix2 if self.INFO: - write_log(release_id, 'info', "checking prefix %s", prefix2) + write_log( + release_id, 'info', "checking prefix %s", prefix2) if work.lower().endswith(prefix2): if len(prefix2) > 0: work = work[:-len(prefix2)] common_len = len(work) work = work.rstrip(":,.;- ") + if work.lower() == prefix2.strip(): + work = '' + common_len = 0 if self.INFO: - write_log(release_id, 'info', "work after prefix strip %s", work) + write_log( + release_id, + 'info', + "work after prefix strip %s", + work) write_log(release_id, 'info', "Prefixes checked") tm['~cwp' + meta_str + '_work_' + @@ -5773,8 +7165,12 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid if part_level > 0 and name_type == "work": if self.INFO: - write_log(release_id, 'info', 'checking if %s is repeated name at part_level = %s', work, - part_level) + write_log( + release_id, + 'info', + 'checking if %s is repeated name at part_level = %s', + work, + part_level) write_log(release_id, 'info', 'lower work name is %s', tm['~cwp' + meta_str + '_work_' + str(part_level - 1)]) # fill in missing names caused by no common string at lower levels @@ -5789,7 +7185,11 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid if fill_level < 0: break if self.INFO: - write_log(release_id, 'info', 'there is/are %s missing level(s)', missing_levels) + write_log( + release_id, + 'info', + 'there is/are %s missing level(s)', + missing_levels) if missing_levels > 0: allow_repeats = True for lev in range( @@ -5802,68 +7202,90 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid meta_str + '_part_' + str(lev - - 1)] = self.strip_parent_from_work(track, release_id, tm['~cwp' + - meta_str + - '_work_' + - str(lev - - 1)], - tm['~cwp' + - meta_str + - '_work_' + - str( - lev)], lev - - 1, False)[0] + 1)] = self.strip_parent_from_work(track, release_id, interpret(tm['~cwp' + + meta_str + + '_work_' + + str(lev - + 1)]), tm['~cwp' + + meta_str + + '_work_' + + str(lev)], lev - + 1, False)[0] else: tm['~cwp' + meta_str + '_work_' + str(lev)] = tm['~cwp_work_' + - str(lev)] + str(lev)] if missing_levels > 0 and self.INFO: - write_log(release_id, 'info', 'lower work name is now %s', - tm['~cwp' + meta_str + '_work_' + str(part_level - 1)]) + write_log(release_id, 'info', 'lower work name is now %r', tm.getall( + '~cwp' + meta_str + '_work_' + str(part_level - 1))) # now fix the repeated work name at this level if work == tm['~cwp' + meta_str + '_work_' + - str(part_level - 1)] and not allow_repeats: + str(part_level - 1)] and not allow_repeats: tm['~cwp' + meta_str + '_work_' + str(part_level)] = tm['~cwp_work_' + - str(part_level)] + str(part_level)] self.level0_warn(release_id, tm, part_level) tm['~cwp' + - meta_str + '_part_' + - str(part_level - 1)] = \ - self.strip_parent_from_work(track, release_id, tm[ - '~cwp' + meta_str + '_work_' + - str(part_level - 1)], tm[ - '~cwp' + meta_str + '_work_' + - str(part_level)], part_level - 1, False)[0] + meta_str + + '_part_' + + str(part_level - + 1)] = self.strip_parent_from_work(track, release_id, tm.getall('~cwp' + + meta_str + + '_work_' + + str(part_level - + 1)), tm['~cwp' + + meta_str + + '_work_' + + str(part_level)], part_level - + 1, False)[0] if part_level == 1: - movt = name[common_len:].strip().lstrip(":,.;- ") + if isinstance(name, list): + movt = [x[common_len:].strip().lstrip(":,.;- ") + for x in name] + else: + movt = name[common_len:].strip().lstrip(":,.;- ") if self.INFO: - write_log(release_id, 'info', "%s - movt = %s", name_type, movt) + write_log( + release_id, 'info', "%s - movt = %s", name_type, movt) tm['~cwp' + meta_str + '_part_0'] = movt if self.INFO: - write_log(release_id, 'info', "%s Work part_level = %s", name_type, part_level) + write_log( + release_id, + 'info', + "%s Work part_level = %s", + name_type, + part_level) if name_type == 'title': if '~cwp_title_work_' + str(part_level - 1) in tm and tm['~cwp_title_work_' + str( part_level)] == tm['~cwp_title_work_' + str(part_level - 1)] and width == 1: pass # don't count higher part-levels which are not distinct from lower ones - # when the parent work has only one child + # when the parent work has only one child else: tm['~cwp_title_work_levels'] = depth tm['~cwp_title_part_levels'] = part_level if self.INFO: - write_log(release_id, 'info', "Set new metadata for %s OK", name_type) + write_log( + release_id, + 'info', + "Set new metadata for %s OK", + name_type) else: # (no common substring at this level) if name_type == 'work': if self.INFO: - write_log(release_id, 'info', - 'single track work - indicator = %s. track = %s, part_level = %s, top_level = %s', - single_work_track, track_item, part_level, top_level) + write_log( + release_id, + 'info', + 'single track work - indicator = %s. track = %s, part_level = %s, top_level = %s', + single_work_track, + track_item, + part_level, + top_level) if part_level >= top_level: # so it won't be covered by top-down action for level in range( 0, part_level + 1): # fill in the missing work names from the canonical list @@ -5873,7 +7295,7 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid meta_str + '_work_' + str(level)] = tm['~cwp_work_' + - str(level)] + str(level)] if level > 0: self.level0_warn(release_id, tm, level) if '~cwp' + meta_str + '_part_' + \ @@ -5882,7 +7304,7 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid meta_str + '_part_' + str(level)] = tm['~cwp_part_' + - str(level)] + str(level)] if level > 0: self.level0_warn(release_id, tm, level) @@ -5893,9 +7315,20 @@ def derive_from_structure(self, release_id, top_info, tracks, height, depth, wid curr_num = tracks['tracknumber'][i] posn = sorted_tracknumbers.index(curr_num) + 1 if self.INFO: - write_log(release_id, 'info', "posn %s", posn) + write_log( + release_id, + 'info', + "Sorted track numbers. Track %s: Sequence in work is %s", + track_meta, + posn) else: posn = i + 1 + write_log( + release_id, + 'info', + "Unable to sort track numbers. Track %s: Sequence in work is %s", + track_meta, + posn) tm['~cwp_movt_num'] = str(posn) def level0_warn(self, release_id, tm, level): @@ -5908,13 +7341,27 @@ def level0_warn(self, release_id, tm, level): :return: """ if self.WARNING or self.INFO: - write_log(release_id, 'warning', - 'Unable to use level 0 as work name source in level %s - using hierarchy instead', level) - self.append_tag(release_id, tm, '~cwp_warning', '5. Unable to use level 0 as work name source in level ' + - str(level) + - ' - using hierarchy instead') - - def set_metadata(self, release_id, part_level, workId, parentId, parent, track): + write_log( + release_id, + 'warning', + 'Unable to use level 0 as work name source in level %s - using hierarchy instead', + level) + self.append_tag( + release_id, + tm, + '~cwp_warning', + '5. Unable to use level 0 as work name source in level ' + + str(level) + + ' - using hierarchy instead') + + def set_metadata( + self, + release_id, + part_level, + workId, + parentId, + parent, + track): """ Set the names of works and parts :param release_id: name for log file - usually =musicbrainz_albumid @@ -5927,8 +7374,13 @@ def set_metadata(self, release_id, part_level, workId, parentId, parent, track): :return: """ if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "SETTING METADATA FOR TRACK = %r, parent = %s, part_level = %s", track, - parent, part_level) + write_log( + release_id, + 'debug', + "SETTING METADATA FOR TRACK = %r, parent = %s, part_level = %s", + track, + parent, + part_level) tm = track.metadata if parentId: self.write_tags(release_id, track, tm, parentId) @@ -5943,10 +7395,10 @@ def set_metadata(self, release_id, part_level, workId, parentId, parent, track): else: parent_annotations = [] if parent_annotations: - work_annotations = [z for z in work_annotations if z not in parent_annotations] + work_annotations = [ + z for z in work_annotations if z not in parent_annotations] self.parts[workId]['stripped_annotations'] = work_annotations - tm['~cwp_workid_' + str(part_level)] = parentId tm['~cwp_work_' + str(part_level)] = parent # maybe more than one work name @@ -5961,38 +7413,18 @@ def set_metadata(self, release_id, part_level, workId, parentId, parent, track): works = work[:] stripped_works = [] for work in works: - # partials (and often) arrangements will have the same name as - # the "parent" and not be an extension - if 'arrangement' in self.parts[workId] and self.parts[workId]['arrangement'] \ - or 'partial' in self.parts[workId] and self.parts[workId]['partial']: - if not isinstance(parent, str): - # in case it is a list - make sure it is a string - parent = '; '.join(parent) - if not isinstance(work, str): - work = '; '.join(work) - diff = self.diff_pair(release_id, track, tm, parent, work) - if diff is None: - diff = "" - strip = [diff, parent] - # but don't leave name of arrangement blank unless it is - # virtually the same as the parent... - clean_work = re.sub("(?u)[\W]", ' ', work) - clean_work_list = clean_work.split() - extra_words = False - for work_word in clean_work_list: - if work_word not in parent: - extra_words = True - break - if extra_words: - if not diff and 'arrangement' in self.parts[ - workId] and self.parts[workId]['arrangement']: - strip = self.strip_parent_from_work(track, release_id, work, parent, part_level, False) - else: - extend = True - strip = self.strip_parent_from_work(track, release_id, work, parent, part_level, extend, parentId) + extend = True + strip = self.strip_parent_from_work( + track, release_id, work, parent, part_level, extend, parentId, workId) + stripped_works.append(strip[0]) if self.INFO: - write_log(release_id, 'info', "Parent: %s", parent) + write_log( + release_id, + 'info', + "Parent: %s, Stripped works = %s", + parent, + stripped_works) # now == parent, after removing full_parent logic full_parent = strip[1] if full_parent != parent: @@ -6009,7 +7441,7 @@ def set_metadata(self, release_id, part_level, workId, parentId, parent, track): def write_tags(self, release_id, track, tm, workId): """ - write genre-realated tags from internal variables + write genre-related tags from internal variables :param track: :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor @@ -6023,11 +7455,18 @@ def write_tags(self, release_id, track, tm, workId): candidate_genres += self.parts[workId]['folks_genres'] if options['cwp_genres_use_worktype'] and 'worktype_genres' in self.parts[workId]: candidate_genres += self.parts[workId]['worktype_genres'] - self.append_tag(release_id, tm, '~cwp_candidate_genres', candidate_genres) + self.append_tag( + release_id, + tm, + '~cwp_candidate_genres', + candidate_genres) self.append_tag(release_id, tm, '~cwp_keys', self.parts[workId]['key']) - self.append_tag(release_id, tm, '~cwp_composed_dates', self.parts[workId]['composed_dates']) - self.append_tag(release_id, tm, '~cwp_published_dates', self.parts[workId]['published_dates']) - self.append_tag(release_id, tm, '~cwp_premiered_dates', self.parts[workId]['premiered_dates']) + self.append_tag(release_id, tm, '~cwp_composed_dates', + self.parts[workId]['composed_dates']) + self.append_tag(release_id, tm, '~cwp_published_dates', + self.parts[workId]['published_dates']) + self.append_tag(release_id, tm, '~cwp_premiered_dates', + self.parts[workId]['premiered_dates']) def make_annotations(self, release_id, track, wid): """ @@ -6039,7 +7478,11 @@ def make_annotations(self, release_id, track, wid): :return: """ if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Starting module %s", 'make_annotations') + write_log( + release_id, + 'debug', + "Starting module %s", + 'make_annotations') options = self.options[track] if options['cwp_workdate_include']: if options['cwp_workdate_source_composed'] and 'composed_dates' in self.parts[wid] and self.parts[wid]['composed_dates']: @@ -6052,14 +7495,32 @@ def make_annotations(self, release_id, track, wid): workdates = [] else: workdates = [] - keys=[] + keys = [] if options['cwp_key_include'] and 'key' in self.parts[wid] and self.parts[wid]['key']: keys = self.parts[wid]['key'] elif options['cwp_key_contingent_include'] and 'key' in self.parts[wid] and self.parts[wid]['key']\ and 'name' in self.parts[wid]: - write_log(release_id,'info', 'checking for key. keys = %s, names = %s', self.parts[wid]['key'], self.parts[wid]['name']) + if self.INFO: + write_log( + release_id, + 'info', + 'checking for key. keys = %s, names = %s', + self.parts[wid]['key'], + self.parts[wid]['name']) + # add all the parent names to the string for checking - + work_name = list_to_str(self.parts[wid]['name']) + work_chk = wid + while work_chk in self.works_cache: + parent_chk = tuple(self.works_cache[work_chk]) + if parent_chk in self.parts and 'name' in self.parts[parent_chk]: + work_name = list_to_str( + self.parts[parent_chk]['name']) + ': ' + work_name + work_chk = parent_chk + # now see if the key has been mentioned in the work or its parents for key in self.parts[wid]['key']: - if not any([key.lower() in x.lower() for x in str_to_list(self.parts[wid]['name'])]): + # if not any([key.lower() in x.lower() for x in + # str_to_list(work_name)]): # TODO remove + if not key.lower() in work_name.lower(): keys.append(key) annotations = keys + workdates if annotations: @@ -6067,10 +7528,20 @@ def make_annotations(self, release_id, track, wid): else: if 'annotations' in self.parts[wid]: del self.parts[wid]['annotations'] - write_log(release_id, 'info', 'make annotations has set id %s on track %s with annotation %s', wid, track, - annotations) + if self.INFO: + write_log( + release_id, + 'info', + 'make annotations has set id %s on track %s with annotation %s', + wid, + track, + annotations) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Ending module %s", 'make_annotations') + write_log( + release_id, + 'debug', + "Ending module %s", + 'make_annotations') def derive_from_title(self, release_id, track, title): """ @@ -6082,7 +7553,11 @@ def derive_from_title(self, release_id, track, title): :return: """ if self.INFO: - write_log(release_id, 'info', "DERIVING METADATA FROM TITLE for track: %s", track) + write_log( + release_id, + 'info', + "DERIVING METADATA FROM TITLE for track: %s", + track) tm = track.metadata movt = title work = "" @@ -6104,7 +7579,14 @@ def derive_from_title(self, release_id, track, title): write_log(release_id, 'info', "Work %s, Movt %s", work, movt) return work, movt - def process_work_artists(self, release_id, album, track, workIds, tm, count): + def process_work_artists( + self, + release_id, + album, + track, + workIds, + tm, + count): """ Carry out the artist processing that needs to be done in the PartLevels class as it requires XML lookups of the works @@ -6119,14 +7601,33 @@ def process_work_artists(self, release_id, album, track, workIds, tm, count): """ if not self.options[track]['classical_extra_artists']: if self.DEBUG or self.INFO: - write_log(release_id, 'debug', 'Not processing work_artists as ExtraArtists not selected to be run') + write_log( + release_id, + 'debug', + 'Not processing work_artists as ExtraArtists not selected to be run') return None if self.DEBUG or self.INFO: - write_log(release_id, 'debug', 'In process_work_artists for track: %s, workIds: %s', track, workIds) + write_log( + release_id, + 'debug', + 'In process_work_artists for track: %s, workIds: %s', + track, + workIds) if workIds in self.parts and 'arrangers' in self.parts[workIds]: if self.INFO: - write_log(release_id, 'info', 'Arrangers = %s', self.parts[workIds]['arrangers']) - set_work_artists(self, release_id, album, track, self.parts[workIds]['arrangers'], tm, count) + write_log( + release_id, + 'info', + 'Arrangers = %s', + self.parts[workIds]['arrangers']) + set_work_artists( + self, + release_id, + album, + track, + self.parts[workIds]['arrangers'], + tm, + count) if workIds in self.works_cache: count += 1 self.process_work_artists(release_id, album, track, tuple( @@ -6153,13 +7654,22 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): options = self.options[track] if '~cwp_part_levels' not in tm: if self.DEBUG or self.INFO: - write_log(release_id, 'debug', 'NO PART LEVELS. Metadata = %s', tm) + write_log( + release_id, + 'debug', + 'NO PART LEVELS. Metadata = %s', + tm) return part_levels = int(tm['~cwp_part_levels']) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', - "Extending metadata for track: %s, ref_height: %s, depth: %s, part_levels: %s", - track, ref_height, depth, part_levels) + write_log( + release_id, + 'debug', + "Extending metadata for track: %s, ref_height: %s, depth: %s, part_levels: %s", + track, + ref_height, + depth, + part_levels) if self.INFO: write_log(release_id, 'info', "Metadata = %s", tm) @@ -6201,19 +7711,26 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): if options["cwp_level0_works"] and '~cwp_X0_work_0' in tm: update_list += ['~cwp_X0_work_0', '~cwp_X0_part_0'] for item in update_list: - if len(work0_id) > 1 and isinstance( - tm[item], str): - meta_item = re.split( - '|'.join(self.SEPARATORS), (tm[item])) - else: - meta_item = tm[item] - if isinstance(meta_item, list): - for ind, w in enumerate(meta_item): - meta_item[ind] = options["cwp_partial_text"] + ' ' + w + write_log( + release_id, 'info', 'update item is %s', item) + meta_item = tm.getall(item) + write_log( + release_id, 'info', 'meta item is %s', meta_item) + if isinstance( + meta_item, list): # it should be a list as I think getall always returns a list + if meta_item == []: + meta_item.append(options["cwp_partial_text"]) + else: + for ind, w in enumerate(meta_item): + meta_item[ind] = options["cwp_partial_text"] + ' ' + w + write_log( + release_id, 'info', 'now meta item is %s', meta_item) tm[item] = meta_item else: tm[item] = options["cwp_partial_text"] + \ ' ' + tm[item] + write_log( + release_id, 'info', 'meta item is not a list') # fix "type 1" medley text if options['cwp_medley']: @@ -6225,7 +7742,8 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): tm['~cwp_work_' + str(lev)] += " (" + options["cwp_medley_text"] + \ ': ' + ', '.join(medley_list) + ")" if '~cwp_part_' + str(lev) in tm: - tm['~cwp_part_' + str(lev)] = "(" + options["cwp_medley_text"] + ") " + tm['~cwp_part_' + str(lev)] + tm['~cwp_part_' + str( + lev)] = "(" + options["cwp_medley_text"] + ") " + tm['~cwp_part_' + str(lev)] # add any annotations for dates and keys if options['cwp_workdate_include'] or options['cwp_key_include'] or options['cwp_key_contingent_include']: @@ -6236,25 +7754,37 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): if '~cwp_workid_' + str(lev) in tm: tup_id = interpret(tm['~cwp_workid_' + str(lev)]) if 'annotations' in self.parts[tup_id]: - write_log(release_id, 'info', 'in extend_metadata, annotations for id %s on track %s are %s', - tup_id, track, self.parts[tup_id]['annotations']) - tm['~cwp_work_' + str(lev)] += " (" + ', '.join(self.parts[tup_id]['annotations']) + ")" - if options["cwp_level0_works"] and '~cwp_X0_work_' + str(lev) in tm: - tm['~cwp_X0_work_' + str(lev)] += " (" + ', '.join(self.parts[tup_id]['annotations']) + ")" - if options["cwp_titles"] and '~cwp_title_work_' + str(lev) in tm: - tm['~cwp_title_work_' + str(lev)] += " (" + ', '.join(self.parts[tup_id]['annotations']) + ")" + if self.INFO: + write_log( + release_id, + 'info', + 'in extend_metadata, annotations for id %s on track %s are %s', + tup_id, + track, + self.parts[tup_id]['annotations']) + tm['~cwp_work_' + str(lev)] += " (" + \ + ', '.join(self.parts[tup_id]['annotations']) + ")" + if options["cwp_level0_works"] and '~cwp_X0_work_' + \ + str(lev) in tm: + tm['~cwp_X0_work_' + str(lev)] += " (" + ', '.join( + self.parts[tup_id]['annotations']) + ")" + if options["cwp_titles"] and '~cwp_title_work_' + \ + str(lev) in tm: + tm['~cwp_title_work_' + str(lev)] += " (" + ', '.join( + self.parts[tup_id]['annotations']) + ")" if lev < part_levels: if 'stripped_annotations' in self.parts[tup_id]: if self.parts[tup_id]['stripped_annotations']: tm['~cwp_part_' + str(lev)] += " (" + ', '.join( self.parts[tup_id]['stripped_annotations']) + ")" - if options["cwp_level0_works"] and '~cwp_X0_part_' + str(lev) in tm: + if options["cwp_level0_works"] and '~cwp_X0_part_' + \ + str(lev) in tm: tm['~cwp_X0_part_' + str(lev)] += " (" + ', '.join( self.parts[tup_id]['stripped_annotations']) + ")" - if options["cwp_titles"] and '~cwp_title_part_' + str(lev) in tm: - tm['~cwp_title_part' + str(lev)] += " (" + ', '.join( - self.parts[tup_id]['stripped_annotations']) + ")" - + if options["cwp_titles"] and '~cwp_title_part_' + \ + str(lev) in tm: + tm['~cwp_title_part' + str(lev)] += " (" + ', '.join( + self.parts[tup_id]['stripped_annotations']) + ")" part = [] work = [] @@ -6303,19 +7833,30 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): tm['~cwp_part'] = part_main # fix medley text for "type 2" medleys + type2_medley = False if self.parts[interpret(tm['~cwp_workid_0']) ]['medley'] and options['cwp_medley']: if options["cwp_medley_text"]: - groupheading = groupheading + ' (' + options["cwp_medley_text"] + ')' + groupheading = groupheading + \ + ' (' + options["cwp_medley_text"] + ')' + type2_medley = True - - write_log(release_id, 'info', 'groupheading check 3: %s', groupheading) + if self.INFO: + write_log( + release_id, + 'info', + 'groupheading check 3: %s', + groupheading) tm['~cwp_groupheading'] = groupheading tm['~cwp_work'] = work_main tm['~cwp_inter_work'] = inter_work tm['~cwp_title_work'] = work_titles if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Groupheading set to: %s", groupheading) + write_log( + release_id, + 'debug', + "Groupheading set to: %s", + groupheading) # extend group heading from title metadata if groupheading: ext_groupheading = groupheading @@ -6328,11 +7869,17 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): title_depth = int(tm['~cwp_title_work_levels']) if self.INFO: - write_log(release_id, 'info', "Title_depth: %s", title_depth) + write_log( + release_id, + 'info', + "Title_depth: %s", + title_depth) diff_work = [""] * ref_level diff_part = [""] * ref_level title_tag = [""] - tw_str_lower = 'title' # level 0 work for title # was 'x' # to avoid errors, reset before used + # level 0 work for title # was 'x' # to avoid errors, reset + # before used + tw_str_lower = 'title' max_d = min(ref_level, title_depth) + 1 for d in range(1, max_d): tw_str = '~cwp_title_work_' + str(d) @@ -6341,22 +7888,25 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): if tw_str in tm: title_tag.append(tm[tw_str]) title_work = title_tag[d] - work_main = work[d] - diff_work[d - 1] = self.diff_pair(release_id, track, tm, groupheading, title_work) + work_main = '' + for w in range(d, ref_level + 1): + work_main += (work[w] + ' ') + diff_work[d - 1] = self.diff_pair( + release_id, track, tm, work_main, title_work) if diff_work[d - 1]: diff_work[d - 1] = diff_work[d - 1].strip('.;:-,') if diff_work[d - 1] == '…': diff_work[d - 1] = '' if d > 1 and tw_str_lower in tm: - title_part = self.strip_parent_from_work(track, release_id, tm[tw_str_lower], tm[tw_str], 0, - False)[0] + title_part = self.strip_parent_from_work( + track, release_id, tm[tw_str_lower], tm[tw_str], 0, False)[0] if title_part: title_part = title_part.strip(' .;:-,') tm['~cwp_title_part_' + str(d - 1)] = title_part part_n = part[d - 1] - diff_part[d - - 1] = self.diff_pair(release_id, track, tm, part_n, title_part) or "" + diff_part[d - 1] = self.diff_pair( + release_id, track, tm, part_n, title_part) or "" if diff_part[d - 1] == '…': diff_part[d - 1] = '' else: @@ -6364,44 +7914,89 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): tw_str_lower = tw_str # remove duplicate items at lower levels in diff_work: for w in range(ref_level - 2, -1, -1): - if diff_work[w] and diff_work[w+1]: - diff_work[w] = diff_work[w].replace(diff_work[w+1], '').strip(' .;:-,') - if diff_work[w] == '…': - diff_work[w] = '' + for higher in range(1, ref_level - w): + if diff_work[w] and diff_work[w + higher]: + diff_work[w] = diff_work[w].replace( + diff_work[w + higher], '').strip(' .;:-,\u2026') + # if diff_work[w] == '…': + # diff_work[w] = '' if self.INFO: - write_log(release_id, 'info', "diff list for works: %s", diff_work) + write_log( + release_id, + 'info', + "diff list for works: %s", + diff_work) if self.INFO: - write_log(release_id, 'info', "diff list for parts: %s", diff_part) + write_log( + release_id, + 'info', + "diff list for parts: %s", + diff_part) if not diff_work or len(diff_work) == 0: if part_levels > 0: ext_groupheading = groupheading else: if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Now calc extended groupheading...") + write_log( + release_id, + 'debug', + "Now calc extended groupheading...") if self.INFO: - write_log(release_id, 'info', "depth = %s, ref_level = %s, title_depth = %s", depth, ref_level, - title_depth) + write_log( + release_id, + 'info', + "depth = %s, ref_level = %s, title_depth = %s", + depth, + ref_level, + title_depth) if self.INFO: - write_log(release_id, 'info', "diff_work = %s, diff_part = %s", diff_work, diff_part) + write_log( + release_id, + 'info', + "diff_work = %s, diff_part = %s", + diff_work, + diff_part) + # remove duplications: + for lev in range(1, ref_level): + for diff_list in [diff_work, diff_part]: + if diff_list[lev] and diff_list[lev - 1]: + diff_list[lev - 1] = self.diff_pair( + release_id, track, tm, diff_list[lev], diff_list[lev - 1]) + if diff_list[lev - 1] == '…': + diff_list[lev - 1] = '' + if self.INFO: + write_log( + release_id, + 'info', + "Removed duplication. Revised diff_work = %s, diff_part = %s", + diff_work, + diff_part) if part_levels > 0 and depth >= 1: addn_work = [] addn_part = [] for stripped_work in diff_work: if stripped_work: if self.INFO: - write_log(release_id, 'info', "Stripped work = %s", stripped_work) + write_log( + release_id, 'info', "Stripped work = %s", stripped_work) addn_work.append(" {" + stripped_work + "}") else: addn_work.append("") for stripped_part in diff_part: if stripped_part and stripped_part != "": if self.INFO: - write_log(release_id, 'info', "Stripped part = %s", stripped_part) + write_log( + release_id, 'info', "Stripped part = %s", stripped_part) addn_part.append(" {" + stripped_part + "}") else: addn_part.append("") if self.INFO: - write_log(release_id, 'info', "addn_work = %s, addn_part = %s", addn_work, addn_part) + write_log( + release_id, + 'info', + "addn_work = %s, addn_part = %s", + addn_work, + addn_part) ext_groupheading = work[1] + addn_work[0] ext_work = work[ref_level] + addn_work[ref_level - 1] ext_inter_work = "" @@ -6412,7 +8007,7 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): if ext_inter_work: ext_inter_work = ': ' + ext_inter_work ext_inter_work = part[r] + \ - addn_work[r-1] + ext_inter_work + addn_work[r - 1] + ext_inter_work ext_groupheading = work[ref_level] + \ addn_work[ref_level - 1] + ':: ' + ext_inter_work if title_depth > 1 and ref_level > 1: @@ -6431,11 +8026,16 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): inter_title_work = "" if self.DEBUG or self.INFO: - write_log(release_id, 'debug', ".... ext_groupheading done") + write_log( + release_id, 'debug', ".... ext_groupheading done") if ext_groupheading: if self.INFO: - write_log(release_id, 'info', "EXTENDED GROUPHEADING: %s", ext_groupheading) + write_log( + release_id, + 'info', + "EXTENDED GROUPHEADING: %s", + ext_groupheading) tm['~cwp_extended_groupheading'] = ext_groupheading tm['~cwp_extended_work'] = ext_work if ext_inter_work: @@ -6445,83 +8045,106 @@ def extend_metadata(self, release_id, top_info, track, ref_height, depth): if title_groupheading: tm['~cwp_title_groupheading'] = title_groupheading if self.INFO: - write_log(release_id, 'info', "title_groupheading = %s", title_groupheading) + write_log( + release_id, + 'info', + "title_groupheading = %s", + title_groupheading) # extend part from title metadata if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "Now extend part...(part = %s)", part_main) + write_log( + release_id, + 'debug', + "NOW EXTEND PART...(part = %s)", + part_main) if part_main: if '~cwp_title_part_0' in tm: movement = tm['~cwp_title_part_0'] else: movement = tm['~cwp_title_part_0'] or tm['~cwp_title'] or tm['title'] - diff = self.diff_pair(release_id, track, tm, work[0], movement) - # compare with the full work name, not the stripped one unless it - # results in nothing - if not diff and not vanilla_part: - diff = self.diff_pair(release_id, track, tm, part_main, movement) + if '~cwp_extended_groupheading' in tm: + work_compare = tm['~cwp_extended_groupheading'] + \ + ': ' + part_main + elif '~cwp_work_1' in tm: + work_compare = work[1] + ': ' + part_main + else: + work_compare = work[0] + diff = self.diff_pair( + release_id, track, tm, work_compare, movement) + # compare with the fullest possible work name, not the stripped one + # - to maximise the duplication elimination + unimplemented_option = True + # Possible future option to (not) fill part with title text if it + # would otherwise have no text other than arrangement or partial + # annotations + # TODO - consider implementing option + if not diff and not vanilla_part and part_levels > 0 and unimplemented_option: + # In other words the movement will have no text other than + # arrangement or partial annotations + diff = movement if self.INFO: write_log(release_id, 'info', "DIFF PART - MOVT. ti =%s", diff) - diff2 = diff if diff: - if '~cwp_work_1' in tm: - if self.parts[interpret(tm['~cwp_workid_0'])]['partial']: - no_diff = False - else: - diff2 = self.diff_pair(release_id, track, tm, work[1], diff) - if diff2: - no_diff = False - else: - no_diff = True - else: - no_diff = False + no_diff = False else: no_diff = True if self.INFO: - write_log(release_id, 'info', 'Set no_diff for %s = %s', tm['~cwp_workid_0'], no_diff) - write_log(release_id, 'info', 'medley indicator for %s is %s', tm['~cwp_workid_0'], + write_log( + release_id, + 'info', + 'Set no_diff for %s = %s', + tm['~cwp_workid_0'], + no_diff) + write_log(release_id, + 'info', + 'medley indicator for %s is %s', + tm['~cwp_workid_0'], self.parts[interpret(tm['~cwp_workid_0'])]['medley']) if self.parts[interpret(tm['~cwp_workid_0']) ]['medley'] and options['cwp_medley']: no_diff = False if self.INFO: - write_log(release_id, 'info', 'setting no_diff = %s', no_diff) - if no_diff: - if part_levels > 0: - tm['~cwp_extended_part'] = part_main - else: - tm['~cwp_extended_part'] = work[0] - if tm['~cwp_extended_groupheading']: - del tm['~cwp_extended_groupheading'] + write_log( + release_id, + 'info', + 'setting no_diff = %s', + no_diff) + + if type2_medley: + tm['~cwp_extended_part'] = "{" + movement + "}" else: - if part_levels > 0: - stripped_movt = diff2.strip() + if diff: tm['~cwp_extended_part'] = part_main + \ - " {" + stripped_movt + "}" + " {" + diff.strip() + "}" else: - # title will be in part - tm['~cwp_extended_part'] = movement + tm['~cwp_extended_part'] = part_main + if part_levels == 0: + if tm['~cwp_extended_groupheading']: + del tm['~cwp_extended_groupheading'] + # remove unwanted groupheadings (needed them up to now for adding # extensions) if '~cwp_groupheading' in tm and tm['~cwp_groupheading'] == tm['~cwp_part']: del tm['~cwp_groupheading'] if '~cwp_title_groupheading' in tm and tm['~cwp_title_groupheading'] == tm['~cwp_title_part']: del tm['~cwp_title_groupheading'] - # clean up groupheadings (may be stray separators if level 0 or title options used) + # clean up groupheadings (may be stray separators if level 0 or title + # options used) if '~cwp_groupheading' in tm: tm['~cwp_groupheading'] = tm['~cwp_groupheading'].strip( ':').strip( options['cwp_single_work_sep']).strip( options['cwp_multi_work_sep']) if '~cwp_extended_groupheading' in tm: - tm['~cwp_extended_groupheading'] = tm['~cwp_extended_groupheading'].strip( - ':').strip( - options['cwp_single_work_sep']).strip( - options['cwp_multi_work_sep']) + tm['~cwp_extended_groupheading'] = tm['~cwp_extended_groupheading'].strip( + ':').strip( + options['cwp_single_work_sep']).strip( + options['cwp_multi_work_sep']) if '~cwp_title_groupheading' in tm: - tm['~cwp_title_groupheading'] = tm['~cwp_title_groupheading'].strip( - ':').strip( - options['cwp_single_work_sep']).strip( - options['cwp_multi_work_sep']) + tm['~cwp_title_groupheading'] = tm['~cwp_title_groupheading'].strip( + ':').strip( + options['cwp_single_work_sep']).strip( + options['cwp_multi_work_sep']) if self.DEBUG or self.INFO: write_log(release_id, 'debug', "....done") return None @@ -6547,7 +8170,11 @@ def publish_metadata(self, release_id, album, track): # album composers needed by map_tags (set in set_work_artists) if 'composer_lastnames' in self.album_artists[album]: last_names = seq_last_names(self, album) - self.append_tag(release_id, tm, '~cea_album_composer_lastnames', last_names) + self.append_tag( + release_id, + tm, + '~cea_album_composer_lastnames', + last_names) if self.INFO: write_log(release_id, 'info', "Check options") @@ -6577,9 +8204,7 @@ def publish_metadata(self, release_id, album, track): inter_work = tm['~cwp_extended_inter_work'] or "" if self.INFO: write_log(release_id, 'info', "Done options") - p1 = re.compile( - r'^\W*\bM{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})\b[\s|\.|:|,|;]', - re.IGNORECASE) # Matches Roman numerals with punctuation + p1 = RE_ROMANS_AT_START # Matches positive integers with punctuation p2 = re.compile(r'^\W*\d+[.):-]') movt = part @@ -6609,9 +8234,15 @@ def publish_metadata(self, release_id, album, track): top_tags = [x.strip(' ') for x in top_tags] if self.INFO: - write_log(release_id, 'info', - "Done splits. gh_tags: %s, work_tags: %s, movt_inc_tags: %s, movt_exc_tags: %s, movt_no_tags: %s", - gh_tags, work_tags, movt_inc_tags, movt_exc_tags, movt_no_tags) + write_log( + release_id, + 'info', + "Done splits. gh_tags: %s, work_tags: %s, movt_inc_tags: %s, movt_exc_tags: %s, movt_no_tags: %s", + gh_tags, + work_tags, + movt_inc_tags, + movt_exc_tags, + movt_no_tags) for tag in gh_tags + work_tags + movt_inc_tags + movt_exc_tags + movt_no_tags: tm[tag] = "" @@ -6626,7 +8257,11 @@ def publish_metadata(self, release_id, album, track): else: self.append_tag(release_id, tm, tag, work) if '~cwp_part_levels' in tm and int(tm['~cwp_part_levels']) > 0: - self.append_tag(release_id, tm, 'show work movement', '1') # for iTunes + self.append_tag( + release_id, + tm, + 'show work movement', + '1') # for iTunes for tag in top_tags: if '~cwp_work_top' in tm: self.append_tag(release_id, tm, tag, tm['~cwp_work_top']) @@ -6650,11 +8285,22 @@ def publish_metadata(self, release_id, album, track): if inter_work and inter_work != "": if tag in movt_exc_tags + movt_inc_tags and tag != "": if self.WARNING or self.INFO: - write_log(release_id, 'warning', "Tag %s will have multiple contents", tag) + write_log( + release_id, + 'warning', + "Tag %s will have multiple contents", + tag) self.append_tag(release_id, tm, '~cwp_warning', '6. Tag ' + tag + ' has multiple contents') - self.append_tag(release_id, tm, tag, inter_work + work_sep + " " + pt) + self.append_tag( + release_id, + tm, + tag, + inter_work + + work_sep + + " " + + pt) else: self.append_tag(release_id, tm, tag, pt) @@ -6673,24 +8319,35 @@ def publish_metadata(self, release_id, album, track): if '~cwp_work_' + \ str(n) in tm and '~cwp_workid_' + str(n) in tm: source = tm['~cwp_work_' + str(n)] - source_id = list(interpret(tm['~cwp_workid_' + str(n)])) + source_id = list( + interpret(tm['~cwp_workid_' + str(n)])) if n == 0: - self.append_tag(release_id, tm, 'musicbrainz_work_composition', source) + self.append_tag( + release_id, tm, 'musicbrainz_work_composition', source) for source_id_item in source_id: - self.append_tag(release_id, tm, 'musicbrainz_work_composition_id', source_id_item) + self.append_tag( + release_id, tm, 'musicbrainz_work_composition_id', source_id_item) if n == part_levels: - self.append_tag(release_id, tm, 'musicbrainz_work', source) + self.append_tag( + release_id, tm, 'musicbrainz_work', source) if 'musicbrainz_workid' in tm: del tm['musicbrainz_workid'] # Delete the Picard version of this tag before # replacing it with the SongKong version for source_id_item in source_id: - self.append_tag(release_id, tm, 'musicbrainz_workid', source_id_item) + self.append_tag( + release_id, tm, 'musicbrainz_workid', source_id_item) if n != 0 and n != part_levels: - self.append_tag(release_id, tm, 'musicbrainz_work_part_level' + str(n), source) + self.append_tag( + release_id, tm, 'musicbrainz_work_part_level' + str(n), source) for source_id_item in source_id: - self.append_tag(release_id, tm, 'musicbrainz_work_part_level' + str(n) + '_id', - source_id_item) + self.append_tag( + release_id, + tm, + 'musicbrainz_work_part_level' + + str(n) + + '_id', + source_id_item) # carry out tag mapping tm['~cea_works_complete'] = "Y" @@ -6742,23 +8399,42 @@ def append_tag(self, release_id, tm, tag, source, sep=None): :return: """ if self.INFO: - write_log(release_id, 'info', "In append_tag (Work parts). tag = %s, source = %s, sep =%s", tag, source, - sep) + write_log( + release_id, + 'info', + "In append_tag (Work parts). tag = %s, source = %s, sep =%s", + tag, + source, + sep) append_tag(release_id, tm, tag, source, self.SEPARATORS) if self.INFO: - write_log(release_id, 'info', "Appended. Resulting contents of tag: %s are: %s", tag, tm[tag]) + write_log( + release_id, + 'info', + "Appended. Resulting contents of tag: %s are: %s", + tag, + tm[tag]) ################################################ # SECTION 7 - Common string handling functions # ################################################ - def strip_parent_from_work(self, track, release_id, work, parent, part_level, extend, parentId=None): + def strip_parent_from_work( + self, + track, + release_id, + work, + parent, + part_level, + extend, + parentId=None, + workId=None): """ Remove common text :param track: :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor - :param work: + :param work: could be a list of works, all of which require stripping :param parent: :param part_level: :param extend: @@ -6766,13 +8442,37 @@ def strip_parent_from_work(self, track, release_id, work, parent, part_level, ex :return: """ # extend=True is used [ NO LONGER to find "full_parent" names] + (with parentId) - # to trigger recursion if unable to strip parent name from work + # to trigger recursion if unable to strip parent name from work and also to look for common subsequences # extend=False is used when this routine is called for other purposes # than strict work: parent relationships options = self.options[track] if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "STRIPPING HIGHER LEVEL WORK TEXT FROM PART NAMES") - write_log(release_id, 'info', 'PARAMS: WORK = %s, PARENT = %s, PART_LEVEL = %s, EXTEND= %s', work, parent, part_level, extend) + write_log( + release_id, + 'debug', + "STRIPPING HIGHER LEVEL WORK TEXT FROM PART NAMES") + write_log( + release_id, + 'info', + 'PARAMS: WORK = %r, PARENT = %s, PART_LEVEL = %s, EXTEND= %s', + work, + parent, + part_level, + extend) + if isinstance(work, list): + result = [] + for w, work_item in enumerate(work): + result.append( + self.strip_parent_from_work( + track, + release_id, + work_item, + parent, + part_level, + extend, + parentId, + workId[w])[0]) + return result, parent if not isinstance(parent, str): # in case it is a list - make sure it is a string parent = '; '.join(parent) @@ -6786,12 +8486,16 @@ def strip_parent_from_work(self, track, release_id, work, parent, part_level, ex # now allow the spaces to be filled with up to 2 non-letters pattern_parent = clean_parent.replace(" ", "\W{0,2}") if extend: - pattern_parent = "(.*\s|^)(\W*" + \ - pattern_parent + "\w*)(\W*\s)(.*)" + pattern_parent = "(.*\s|^)(\W*" + pattern_parent + ")(\W*\s)(.*)" else: - pattern_parent = "(.*\s|^)(\W*" + pattern_parent + "\w*\W?)(.*)" + pattern_parent = "(.*\s|^)(\W*" + pattern_parent + "\W?)(.*)" if self.INFO: - write_log(release_id, 'info', "Pattern parent: %s, Work: %s", pattern_parent, work) + write_log( + release_id, + 'info', + "Pattern parent: %s, Work: %s", + pattern_parent, + work) p = re.compile(pattern_parent, re.IGNORECASE | re.UNICODE) m = p.search(work) if m: @@ -6809,75 +8513,168 @@ def strip_parent_from_work(self, track, release_id, work, parent, part_level, ex stripped_work = m.group(3) # may not have a full work name in the parent (missing op. no. # etc.) - # HOWEVER, this next section has been removed, because it can cause incorrect answers if lower level - # works are inconsistently named. Use of level_0 naming can achieve result better and - # We want top work to be MB-canonical, regardless - # Nevertheless, this code is left in comments in case it proves useful again - # if m.group(3) != ": " and extend: - # # no. of colons is consistent with "work: part" structure - # if work.count(": ") >= part_level: - # split_work = work.split(': ', 1) - # stripped_work = split_work[1] - # full_parent = split_work[0] - # if len(full_parent) < len( - # parent): # don't shorten parent names! (in case colon is mis-placed) - # full_parent = parent - # stripped_work = m.group(4) else: if self.INFO: write_log(release_id, 'info', "No match...") stripped_work = work if extend and options['cwp_common_chars'] > 0: - # try stripping out a common substring - words = re.compile(r"\W?\b.*?\s|\W?\b.*$|\W?\B.*?\b|\W?\B.*?$") - parent_words = words.findall(parent) - clean_parent_words = parent_words[:] - for w, word in enumerate(parent_words): - syn_word = self.synonymize(release_id, word, self.synonyms[track]) - clean_parent_words[w] = self.boil(release_id, syn_word) - work_words = words.findall(stripped_work) - clean_work_words = work_words[:] - for w, word in enumerate(work_words): - syn_word = self.synonymize(release_id, word, self.synonyms[track]) - clean_work_words[w] = self.boil(release_id, syn_word) - common_dets = longest_common_substring(clean_work_words, clean_parent_words) - common_seq = common_dets['string'] - seq_length = common_dets['length'] - seq_start = common_dets['start'] - if self.INFO: - write_log(release_id, 'info', - 'Checking common sequence between parent and work. ... parent_words = %s', - parent_words) - write_log(release_id, 'info', '... work_words = %s', work_words) - write_log(release_id, 'info', '... clean_parent_words = %s', clean_parent_words) - write_log(release_id, 'info', '... clean_work_words = %s', clean_work_words) - write_log(release_id, 'info', '... longest common sequence = %s', common_seq) - if seq_length >= options['cwp_common_chars']: # Make sure it is more than the required min - if seq_start > 0: - ellipsis = [u"\u2026" + ' '] - else: - ellipsis = [] - stripped_work = ''.join(work_words[:seq_start] + ellipsis + work_words[seq_start + seq_length:]).lstrip(' :,-') - + # try stripping out a common substring (multiple times until + # nothing more stripped) + prev_stripped_work = '' + counter = 1 + while prev_stripped_work != stripped_work: + if counter > 20: + break # in case something went awry + prev_stripped_work = stripped_work + parent_tuples = self.listify(release_id, track, parent) + parent_words = parent_tuples['s_tuple'] + clean_parent_words = list(parent_tuples['s_test_tuple']) + for w, word in enumerate(clean_parent_words): + clean_parent_words[w] = self.boil(release_id, word) + work_tuples = self.listify( + release_id, track, stripped_work) + work_words = work_tuples['s_tuple'] + clean_work_words = list(work_tuples['s_test_tuple']) + for w, word in enumerate(clean_work_words): + clean_work_words[w] = self.boil(release_id, word) + common_dets = longest_common_substring( + clean_work_words, clean_parent_words) + # this is actually a list, not a string, since list + # arguments were supplied + common_seq = common_dets['string'] + seq_length = common_dets['length'] + seq_start = common_dets['start'] + # the original items (before 'cleaning') + full_common_seq = [ + x.group() for x in work_words[seq_start:seq_start + seq_length]] + # number of words in common_seq + full_seq_length = sum([len(x.split()) + for x in full_common_seq]) + if self.INFO: + write_log( + release_id, + 'info', + 'Checking common sequence between parent and work, iteration %s ... parent_words = %s', + counter, + parent_words) + write_log( + release_id, + 'info', + '... work_words = %s', + work_words) + write_log( + release_id, + 'info', + '... clean_parent_words = %s', + clean_parent_words) + write_log( + release_id, + 'info', + '... clean_work_words = %s', + clean_work_words) + write_log( + release_id, + 'info', + '... longest common sequence = %s', + common_seq) + write_log( + release_id, + 'info', + '... number of words in common sequence = %s', + full_seq_length) + write_log( + release_id, + 'info', + '... sequence start = %s', + seq_start) + write_log( + release_id, + 'info', + '... sequence end = %s', + seq_start + seq_length) + if full_seq_length > 0: + potential_stripped_work = stripped_work + if seq_start > 0: + ellipsis = ' ' + u"\u2026" + ' ' + else: + ellipsis = '' + if counter > 1: + potential_stripped_work = stripped_work.rstrip( + ' :,-\u2026') + potential_stripped_work = potential_stripped_work.replace( + '(\u2026)', '').rstrip() + potential_stripped_work = potential_stripped_work[:work_words[seq_start].start( + )] + ellipsis + potential_stripped_work[work_words[seq_start + seq_length - 1].end():] + potential_stripped_work = potential_stripped_work.lstrip( + ' :,-') + potential_stripped_work = re.sub( + r'(\W*…\W*)(\W*…\W*)', ' … ', potential_stripped_work) + potential_stripped_work = strip_excess_punctuation( + potential_stripped_work) + + if full_seq_length >= options['cwp_common_chars'] \ + or potential_stripped_work == '' and options['cwp_allow_empty_parts']: + # Make sure it is more than the required min (it will be > 0 anyway) + # unless a full strip will result anyway (and blank + # part names are allowed) + stripped_work = potential_stripped_work + if not stripped_work or stripped_work == '': + if workId and \ + ('arrangement' in self.parts[workId] and self.parts[workId]['arrangement'] + and options['cwp_arrangements'] and options['cwp_arrangements_text']) \ + or ('partial' in self.parts[workId] and self.parts[workId]['partial'] + and options['cwp_partial'] and options['cwp_partial_text']) \ + and options['cwp_allow_empty_parts']: + pass + else: + stripped_work = prev_stripped_work # do not allow empty parts + counter += 1 + if self.INFO: + write_log( + release_id, + 'info', + 'stripped_work = %s', + stripped_work) if extend and parentId and parentId in self.works_cache: if self.INFO: - write_log(release_id, 'info', "Looking for match at next level up") + write_log( + release_id, + 'info', + "Looking for match at next level up") grandparentIds = tuple(self.works_cache[parentId]) grandparent = self.parts[grandparentIds]['name'] - stripped_work = self.strip_parent_from_work(track, release_id, stripped_work, grandparent, part_level, True, - grandparentIds)[0] + stripped_work = self.strip_parent_from_work( + track, + release_id, + stripped_work, + grandparent, + part_level, + True, + grandparentIds, + workId)[0] if self.INFO: - write_log(release_id, 'info', "Work: %s", work) + write_log( + release_id, + 'info', + "Finished strip_parent_from_work, Work: %s", + work) if self.INFO: write_log(release_id, 'info', "Stripped work: %s", stripped_work) # Changed full_parent to parent after removal of 'extend' logic above return stripped_work, parent - def diff_pair(self, release_id, track, tm, mb_item, title_item): + def diff_pair( + self, + release_id, + track, + tm, + mb_item, + title_item, + remove_numbers=True): """ - Removes common text from title item + Removes common text (or synonyms) from title item :param release_id: name for log file - usually =musicbrainz_albumid unless called outside metadata processor :param track: @@ -6895,7 +8692,11 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): write_log(release_id, 'info', "title_item = %s", title_item) if not mb: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None ti = title_item.strip(" :;-.,") if ti.count('"') == 1: @@ -6906,14 +8707,13 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): write_log(release_id, 'info', "ti (amended) = %s", ti) if not ti: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None - p1 = re.compile( - r'^\W*\bM{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})\b[\s|\.|:|,|;]', - re.IGNORECASE) # Matches Roman numerals with punctuation - # Matches positive integers with punctuation - p2 = re.compile(r'^\W*\d+[.):-]') - # remove certain words from the comparison + if self.options[track]["cwp_removewords_p"]: removewords = self.options[track]["cwp_removewords_p"].split(',') else: @@ -6924,165 +8724,117 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): # start if self.INFO: write_log(release_id, 'info', "checking prefixes") - for i in range( - 0, 5): # in case of multiple levels - mb = p2.sub('', p1.sub('', mb)).strip() - ti = p2.sub('', p1.sub('', ti)).strip() + found_prefix = True + i = 0 + while found_prefix: + if i > 20: + break # safety valve + found_prefix = False for prefix in removewords: if prefix[0] != " ": prefix2 = str(prefix).lower().lstrip() if self.INFO: - write_log(release_id, 'info', "checking prefix %s", prefix2) + write_log( + release_id, 'info', "checking prefix %s", prefix2) if mb.lower().startswith(prefix2): + found_prefix = True mb = mb[len(prefix2):] if ti.lower().startswith(prefix2): + found_prefix = True ti = ti[len(prefix2):] mb = mb.strip() ti = ti.strip() + i += 1 if self.INFO: - write_log(release_id, 'info', "pairs after prefix strip iteration %s. mb = %s, ti = %s", i, mb, ti) + write_log( + release_id, + 'info', + "pairs after prefix strip iteration %s. mb = %s, ti = %s", + i, + mb, + ti) if self.INFO: write_log(release_id, 'info', "Prefixes checked") # replacements - strreps = self.options[track]["cwp_replacements"].split('/') - replacements = [] - for rep in strreps: - tupr = rep.strip(' ()').split(',') - if len(tupr) == 2: - for i, tr in enumerate(tupr): - tupr[i] = tr.strip("' ").strip('"') - tupr = tuple(tupr) - replacements.append(tupr) - else: - if self.ERROR or self.INFO: - write_log(release_id, 'error', 'Error in replacement format for replacement %s', rep) - self.append_tag(release_id, tm, '~cwp_error', '6. Error in replacement format for replacement ' + - rep) + replacements = self.replacements[track] if self.INFO: write_log(release_id, 'info', "Replacement: %s", replacements) + for tup in replacements: + for ind in range(0, len(tup) - 1): + ti = re.sub(tup[ind], tup[-1], ti, flags=re.IGNORECASE) - # fix replacements - for key, equiv in replacements: - if self.INFO: - write_log(release_id, 'info', "key %s, equiv %s", key, equiv) - if key[0] == "!" and key[1] == "!" and key[-1] == "!" and key[-2] == "!": # we have a reg ex inside {{ }} - key_pattern = key[2:-2] - else: - esc_key = re.escape(key) - key_pattern = '\\b' + esc_key + '\\b' - ti = re.sub(key_pattern, equiv, ti) - if self.INFO: - write_log(release_id, 'info', "Replaced replacements, ti = %s", ti) - - # synonyms - mb_test = self.synonymize(release_id, mb, self.synonyms[track]) - ti_test = self.synonymize(release_id, ti, self.synonyms[track]) - - - # check if the title item is wholly part of the mb item + if self.DEBUG or self.INFO: + write_log( + release_id, + 'debug', + 'Looking for any new words in the title') if self.INFO: - write_log(release_id, 'info', "Testing if ti in mb. ti_test = %s, mb_test = %s", ti_test, mb_test) - nopunc_ti = self.boil(release_id, ti_test) - if self.INFO: - write_log(release_id, 'info', "nopunc_ti =%s", nopunc_ti) - nopunc_mb = self.boil(release_id, mb_test) - if self.INFO: - write_log(release_id, 'info', "nopunc_mb =%s", nopunc_mb) - ti_len = len(nopunc_ti) - if self.INFO: - write_log(release_id, 'info', "ti len %s", ti_len) - if self.INFO: - write_log(release_id, 'info', "Initial test. nopunc_mb = %s, nopunc_ti = %s, ti_len = %s", nopunc_mb, - nopunc_ti, ti_len) - if self.INFO: - write_log(release_id, 'info', "test sub....") - lcs = longest_common_substring(nopunc_mb, nopunc_ti)['string'] - if self.INFO: - write_log(release_id, 'info', "Longest common substring is: %s. ti_len is %s", lcs, ti_len) - if len(lcs) >= ti_len: - if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) - return None + write_log( + release_id, + 'info', + "Check before splitting: mb = %s, ti = %s", + mb, + ti) - # try and strip the canonical item from the title item (only a full - # strip affects the outcome) - if len(nopunc_mb) > 0: - ti_new = self.strip_parent_from_work(track, release_id, ti_test, mb_test, 0, False)[0] - # extend = False to ensure that partial elimination not done - if ti_new == ti_test: - mb_list = re.split( - r';\s|:\s|\.\s|\-\s', mb_test, 1) - # was self.options[track]["cwp_granularity"] instead of just 1, but hardly used and doubtful value - if self.INFO: - write_log(release_id, 'info', "mb_list = %s", mb_list) - if mb_list: - for mb_bit in mb_list: - ti_new = self.strip_parent_from_work(track, release_id, ti_new, mb_bit, 0, False)[0] - if self.INFO: - write_log(release_id, 'info', "MB_BIT: %s, TI_NEW: %s", mb_bit, ti_new) - else: - if len(ti_new) > 0: - retn = self.reverse_syn(release_id, ti_new, self.synonyms[track]) - if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', retn) - return retn - else: - if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) - return None - if len(ti_new) == 0: - if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) - return None - # return any significant new words in the title - if self.DEBUG: - write_log(release_id, 'debug', 'Looking for any new words in the title') - words = 0 - # nonWords = [ - # "a", - # "the", - # "in", - # "on", - # "at", - # "of", - # "after", - # "and", - # "de", - # "d'un", - # "d'une", - # "la", - # "le"] + ti_tuples = self.listify(release_id, track, ti) + ti_tuple = ti_tuples['s_tuple'] + ti_test_tuple = ti_tuples['s_test_tuple'] + + mb_tuples = self.listify(release_id, track, mb) + mb_test_tuple = mb_tuples['s_test_tuple'] if self.INFO: - write_log(release_id, 'info', "Check before splitting: mb_test = %s, ti_test = %s", mb_test, ti_test) - if self.options[track]["cwp_split_hyphenated"]: - regex = r"(?=\w*['])([\w']+)|(\b\w+\b)|(\B\&\B)" - # allow ampersands and non-latin characters as word characters. Treat apostrophes as part of words. - # TODO fix for right qupte marks as well as apostrophes? - else: - regex = r"(?=\w*['-])([\w'-]+)|(\b\w+\b)|(\B\&\B)" - # allow ampersands and non-latin characters as word characters. Treat embedded hyphens and apostrophes as part of words. - ti_tuple = tuple(re.finditer(regex, ti, re.UNICODE)) # to make it re-usable and countable - ti_test_tuple = tuple(re.finditer(regex, ti_test, re.UNICODE)) + write_log( + release_id, + 'info', + "Check after splitting: mb_test = %s, ti = %s, ti_test = %s", + mb_test_tuple, + ti_tuple, + ti_test_tuple) ti_stencil = self.stencil(release_id, ti_tuple, ti) ti_list = ti_stencil['match list'] ti_list_punc = ti_stencil['gap list'] - ti_test_list = self.stencil(release_id, ti_test_tuple, ti_test)['match list'] + ti_test_list = list(ti_test_tuple) + if ti_stencil['dummy']: + # to deal with case where stencil has added a dummy item at the + # start + ti_test_list.insert(0, '') if self.INFO: write_log(release_id, 'info', 'ti_test_list = %r', ti_test_list) - ti_zip_list = list(zip(ti_list, ti_list_punc)) # zip is an iterable, not a list in Python 3, so make it re-usable + # zip is an iterable, not a list in Python 3, so make it re-usable + ti_zip_list = list(zip(ti_list, ti_list_punc)) # len(ti_list) should be = len(ti_test_list) as only difference should - # be synonyms which are each one word - mb_test_tuple = tuple(re.finditer(regex, mb_test, re.UNICODE)) - mb_list2 = self.stencil(release_id, mb_test_tuple, mb_test)['match list'] + # be synonyms which are each one 'word' + # However, because of the grouping of some words via regex, it is possible that inconsistencies might arise + # Therefore, there is a test here to check for equality and produce an + # error message (but continue processing) + if len(ti_list) != len(ti_test_list): + if self.ERROR: + write_log( + release_id, + 'error', + 'Mismatch in title list after canonization/synonymization') + write_log( + release_id, + 'error', + 'Orig. title list = %r. Test list = %r', + ti_list, + ti_test_list) + # mb_test_tuple = self.listify(release_id, track, mb_test) + mb_list2 = list(mb_test_tuple) for index, mb_bit2 in enumerate(mb_list2): mb_list2[index] = self.boil(release_id, mb_bit2) if self.INFO: - write_log(release_id, 'info', "mb_list2[%s] = %s", index, mb_list2[index]) + write_log( + release_id, + 'info', + "mb_list2[%s] = %s", + index, + mb_list2[index]) ti_new = [] ti_rich_list = [] for i, ti_bit_test in enumerate(ti_test_list): @@ -7093,26 +8845,45 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): else: ti_bit = ('', '') if self.INFO: - write_log(release_id, 'info', "i = %s, ti_bit_test = %s, ti_bit = %s", i, ti_bit_test, ti_bit) - # Boolean to indicate whether ti_bit is a new word + write_log( + release_id, + 'info', + "i = %s, ti_bit_test = %s, ti_bit = %s", + i, + ti_bit_test, + ti_bit) ti_rich_list.append((ti_bit, True)) - if self.boil(release_id, ti_bit_test) in mb_list2: + # Boolean to indicate whether ti_bit is a new word + + if ti_bit_test == '': ti_rich_list[i] = (ti_bit, False) - # remove prepositions - if self.INFO: - write_log(release_id, 'info', "ti_rich_list before removing prepositions = %s. length = %s", ti_rich_list, - len(ti_rich_list)) - if self.options[track]["cwp_prepositions"]: - prepositions_fat = self.options[track]["cwp_prepositions"].split(',') - prepositions = [w.strip() for w in prepositions_fat] - for i, ti_bit_test in enumerate(reversed(ti_test_list)): # Need to reverse it to check later prepositions first - if ti_bit_test.lower() in prepositions: - j = len(ti_rich_list) - i - 1 # NB i is counting up while traversing the list backwards - if i == 0 or not ti_rich_list[j + 1][1]: - ti_rich_list[j] = (ti_rich_list[j][0], False) + else: + if self.boil(release_id, ti_bit_test) in mb_list2: + ti_rich_list[i] = (ti_bit, False) + + if remove_numbers: # Only remove numbers at the start if they are not new items + p0 = re.compile(r'\b\w+\b') + p1 = RE_ROMANS + p2 = re.compile(r'^\d+') # Matches positive integers + starts_with_numeral = True + while starts_with_numeral: + starts_with_numeral = False + if ti_rich_list and p0.match(ti_rich_list[0][0][0]): + start_word = p0.match(ti_rich_list[0][0][0]).group() + if p1.match(start_word) or p2.match(start_word): + if not ti_rich_list[0][1]: + starts_with_numeral = True + ti_rich_list.pop(0) + ti_test_list.pop(0) + if self.INFO: - write_log(release_id, 'info', "ti_rich_list before removing singletons = %s. length = %s", ti_rich_list, - len(ti_rich_list)) + write_log( + release_id, + 'info', + "ti_rich_list before removing singletons = %s. length = %s", + ti_rich_list, + len(ti_rich_list)) + s = 0 index = 0 change = () @@ -7128,20 +8899,72 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): if s == 1: if 0 < index < len(ti_rich_list) - 1: - # ignore singleton new words in middle of title unless they are within "cwp_end_proximity" from the start or end - if ep < index < len(ti_rich_list) - ep - 1: + # ignore singleton new words in middle of title unless they are + # within "cwp_end_proximity" from the start or end + write_log( + release_id, 'info', 'item length is %s', len( + change[0].split())) + # also make sure that the item is just one word before + # eliminating + if ep < index < len(ti_rich_list) - ep - \ + 1 and len(change[0].split()) == 1: ti_rich_list[index] = (change, False) s = 0 + + # remove prepositions if self.INFO: - write_log(release_id, 'info', "ti_rich_list before gapping (True indicates a word in title not in MB work) = %s. length = %s", ti_rich_list, - len(ti_rich_list)) + write_log( + release_id, + 'info', + "ti_rich_list before removing prepositions = %s. length = %s", + ti_rich_list, + len(ti_rich_list)) + if self.options[track]["cwp_prepositions"]: + prepositions_fat = self.options[track]["cwp_prepositions"].split( + ',') + prepositions = [w.strip() for w in prepositions_fat] + for i, ti_bit_test in enumerate( + reversed(ti_test_list)): # Need to reverse it to check later prepositions first + if ti_bit_test.lower().strip() in prepositions: + # NB i is counting up while traversing the list backwards + j = len(ti_rich_list) - i - 1 + if i == 0 or not ti_rich_list[j + 1][1]: + # Don't make it false if it is preceded by a + # non-preposition new word + if not (j > 0 and ti_rich_list[j - + 1][1] and ti_test_list[j - + 1].lower() not in prepositions): + ti_rich_list[j] = (ti_rich_list[j][0], False) + + # create comparison for later usage + compare_string = '' + for item in ti_rich_list: + if item[1]: + compare_string += item[0][0] + ti_compare = self.boil(release_id, compare_string) + compare_length = len(ti_compare) + + if self.INFO: + write_log( + release_id, + 'info', + "ti_rich_list before gapping (True indicates a word in title not in MB work) = %s. length = %s", + ti_rich_list, + len(ti_rich_list)) if s > 0: d = p - ep start = True # To keep track of new words at the start of the title for i, (ti_bit, new) in enumerate(ti_rich_list): if not new: if self.INFO: - write_log(release_id, 'info', "item(i = %s) val = %s - not new. proximity param = %s, end_proximity param = %s", i, ti_bit, p, ep) + write_log( + release_id, + 'info', + "item(i = %s) val = %s - not new. proximity param = %s, end_proximity param = %s", + i, + ti_bit, + p, + ep) if start: prox_test = ep else: @@ -7149,14 +8972,17 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): if prox_test > 0: for j in range(0, prox_test + 1): if self.INFO: - write_log(release_id, 'info', "item(i) = %s, look-ahead(j) = %s", i, j) + write_log( + release_id, 'info', "item(i) = %s, look-ahead(j) = %s", i, j) if i + j < len(ti_rich_list): if ti_rich_list[i + j][1]: if self.INFO: - write_log(release_id, 'info', "Set to true..") + write_log( + release_id, 'info', "Set to true..") ti_rich_list[i] = (ti_bit, True) if self.INFO: - write_log(release_id, 'info', "...set OK") + write_log( + release_id, 'info', "...set OK") else: if j <= p - d: ti_rich_list[i] = (ti_bit, True) @@ -7165,9 +8991,13 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): start = False if not ti_rich_list[i][1]: p -= 1 - ep -=1 + ep -= 1 if self.INFO: - write_log(release_id, 'info', "ti_rich_list after gapping (True indicates new words plus infills) = %s", ti_rich_list) + write_log( + release_id, + 'info', + "ti_rich_list after gapping (True indicates new words plus infills) = %s", + ti_rich_list) nothing_new = True for (ti_bit, new) in ti_rich_list: if new: @@ -7176,7 +9006,11 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): break if nothing_new: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None else: new_prev = False @@ -7205,7 +9039,12 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): else: ti_new.append(ti_bit[1]) if self.INFO: - write_log(release_id, 'info', "appended %s. ti_new is now %s", ti_bit, ti_new) + write_log( + release_id, + 'info', + "appended %s. ti_new is now %s", + ti_bit, + ti_new) else: if self.INFO: write_log(release_id, 'info', "Not for %s", ti_bit) @@ -7223,24 +9062,41 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): if self.INFO: write_log(release_id, 'info', "New text empty") if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None # see if there is any significant difference between the strings if ti: - nopunc_ti = self.boil(release_id, ti) + nopunc_ti = ti_compare # was = self.boil(release_id, ti) + # not necessary as already set? nopunc_mb = self.boil(release_id, mb) - ti_len = len(nopunc_ti) - sub_len = ti_len * \ - float(self.options[track]["cwp_substring_match"]) / 100 - if self.INFO: - write_log(release_id, 'info', "test sub....") - lcs = longest_common_substring(nopunc_mb, nopunc_ti)['string'] - if self.INFO: - write_log(release_id, 'info', "Longest common substring is: %s. Threshold length is %s", lcs, sub_len) - if len(lcs) >= sub_len: + # ti_len = len(nopunc_ti) use compare_length instead (= len before + # removals and additions) + substring_proportion = float( + self.options[track]["cwp_substring_match"]) / 100 + sub_len = compare_length * substring_proportion + if substring_proportion < 1: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) - return None + write_log(release_id, 'info', "test sub....") + lcs = longest_common_substring(nopunc_mb, nopunc_ti)['string'] + if self.INFO: + write_log( + release_id, + 'info', + "Longest common substring is: %s. Threshold length is %s", + lcs, + sub_len) + if len(lcs) >= sub_len: + if self.INFO: + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) + return None if self.INFO: write_log(release_id, 'info', "...done, ti =%s", ti) # remove duplicate successive words (and remove first word of title @@ -7253,131 +9109,454 @@ def diff_pair(self, release_id, track, tm, mb_item, title_item): if ti_bit != "...": if i > 1: - if self.boil(release_id, ti_bit) == self.boil(release_id, ti_bit_prev): + if self.boil( + release_id, ti_bit) == self.boil( + release_id, ti_bit_prev): dup = ti_list_new.pop(i) if self.INFO: - write_log(release_id, 'info', "...removed dup %s", dup) + write_log( + release_id, 'info', "...removed dup %s", dup) ti_bit_prev = ti_bit if self.INFO: - write_log(release_id, 'info', "1st word of ti = %s. Last word of mb = %s", ti_list_new[0], mb_list2[-1]) + write_log(release_id, + 'info', + "1st word of ti = %s. Last word of mb = %s", + ti_list_new[0], + mb_list2[-1]) if ti_list_new and mb_list2: if self.boil(release_id, ti_list_new[0]) == mb_list2[-1]: if self.INFO: - write_log(release_id, 'info', "Removing 1st word from ti...") + write_log( + release_id, 'info', "Removing 1st word from ti...") first = ti_list_new.pop(0) if self.INFO: write_log(release_id, 'info', "...removed %s", first) else: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None if ti_list_new: if self.INFO: - write_log(release_id, 'info', "rejoin list %s", ti_list_new) + write_log( + release_id, + 'info', + "rejoin list %s", + ti_list_new) ti = ' '.join(ti_list_new) else: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None # remove excess brackets and punctuation if ti: - ti = ti.strip("!&.-:;, ") - if ti.count('"') == 1: - ti = ti.strip('"') - if ti.count("'") == 1: - ti = ti.strip("'") + ti = strip_excess_punctuation(ti) if self.INFO: write_log(release_id, 'info', "stripped punc ok. ti = %s", ti) - if ti: - if ti.count("\"") == 1: - ti = ti.strip("\"") - if ti.count("\'") == 1: - ti = ti.strip("\'") - if "(" in ti and ")" not in ti: - ti = ti.replace("(", "") - if ")" in ti and "(" not in ti: - ti = ti.replace(")", "") - if "[" in ti and "]" not in ti: - ti = ti.replace("[", "") - if "]" in ti and "[" not in ti: - ti = ti.replace("]", "") - if "{" in ti and "}" not in ti: - ti = ti.replace("{", "") - if "}" in ti and "{" not in ti: - ti = ti.replace("}", "") - if ti: - match_chars = [("(", ")"), ("[", "]"), ("{", "}")] - last = len(ti) - 1 - for char_pair in match_chars: - if char_pair[0] == ti[0] and char_pair[1] == ti[last]: - ti = ti.lstrip(char_pair[0]).rstrip(char_pair[1]) if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "DIFF_PAIR is returning ti = %s", ti) + write_log( + release_id, + 'debug', + "DIFF_PAIR is returning ti = %s", + ti) if ti and len(ti) > 0: - retn = self.reverse_syn(release_id, ti, self.synonyms[track]) if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', retn) - return retn + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + ti) + return ti else: if self.INFO: - write_log(release_id, 'info', 'End of DIFF_PAIR. Returning %s', None) + write_log( + release_id, + 'info', + 'End of DIFF_PAIR. Returning %s', + None) return None + def canonize_opus(self, release_id, track, s): + """ + make opus numbers etc. into one-word items + :param release_id: + :param s: A string + :return: + """ + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', 'Canonizing: %s', s) + # Canonize catalogue & opus numbers (e.g. turn K. 126 into K126 or K + # 345a into K345a or op. 144 into op144): + regex = re.compile( + r'\b((?:op|no|k|kk|kv|L|B|Hob|S|D|M)|\w+WV)\W?\s?(\d+\-?\u2013?\u2014?\d*\w*)\b', + re.IGNORECASE) + regex_match = regex.search(s) + s_canon = s + if regex_match and len(regex_match.groups()) == 2: + pt1 = regex_match.group(1) or '' + pt2 = regex_match.group(2) or '' + if regex_match.group(1) and regex_match.group(2): + pt1 = re.sub( + r'^\W*no\b', + '', + regex_match.group(1), + flags=re.IGNORECASE) + s_canon = pt1 + pt2 + write_log(release_id, 'info', 'canonized item = %s', s_canon) + return s_canon + + def canonize_key(self, release_id, track, s): + """ + make keys into standardized one-word items + :param release_id: + :param s: A string + :return: + """ + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', 'Canonizing: %s', s) + match = RE_KEYS.search(s) + s_canon = s + if match: + key = match.group() + if match.group(2): + k2 = re.sub( + r'\-sharp|\u266F', + 'sharp', + match.group(2), + flags=re.IGNORECASE) + k2 = re.sub(r'\-flat|\u266D', 'flat', k2, flags=re.IGNORECASE) + k2 = k2.replace('-', '') + else: + k2 = '' + if not match.group(3) or match.group( + 3).strip() == '': # if the scale is not given, assume it is the major key + if match.group(1).isupper( + ) or k2 != '': # but only if it is upper case or has an accent + k3 = 'major' + else: + k3 = '' + else: + k3 = match.group(3).strip() + s_canon = match.group(1).strip() + k2.strip() + k3 + write_log(release_id, 'info', 'canonized item = %s', s_canon) + return s_canon - def get_synonyms(self, release_id, track): + def canonize_synonyms(self, release_id, tuples, s): + """ + make synonyms equal + :param release_id: + :param tuples + :param s: A string + :return: + """ + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', 'Canonizing: %s', s) + s_canon = s + syn_patterns = [] + syn_subs = [] + for syn_tup in tuples: + syn_pattern = r'((?:^|\W)' + \ + r'(?:$|\W)|(?:^|\W)'.join(syn_tup) + r'(?:$|\W))' + syn_patterns.append(syn_pattern) + # to get the last synonym in the tuple - the canonical form + syn_sub = syn_tup[-1:][0] + syn_subs.append(syn_sub) + for syn_ind, pattern in enumerate(syn_patterns): + regex = re.compile(pattern, re.IGNORECASE) + regex_match = regex.search(s) + if regex_match: + test_reg = regex_match.group().strip() + ok = test_reg + s_canon = s_canon.replace(test_reg, syn_subs[syn_ind]) + + write_log(release_id, 'info', 'canonized item = %s', s_canon) + return s_canon + + def find_synonyms(self, release_id, track, reg_item): + """ + extend regex item to include synonyms + :param release_id: + :param track: + :param reg_item: A regex portion + :return: reg_new: A replacement for reg_item that includes all its synonyms + (if reg_item matches the last in a synonym tuple) + """ + if self.DEBUG or self.INFO: + write_log(release_id, 'debug', 'Finding synonyms of: %s', reg_item) + syn_others = [] + syn_all = [] + for syn_tup in self.synonyms[track]: + # to get the last synonym in the tuple - the canonical form + syn_last = syn_tup[-1:][0] + if re.match(r'^\s*' + reg_item + r'\s*$', syn_last, re.IGNORECASE): + syn_others += syn_tup[:-1] + syn_all += syn_tup + if syn_others: + reg_item = '(?:' + ')|(?:'.join(syn_others) + \ + ')|(?:' + reg_item + ')' + + write_log(release_id, 'info', 'new regex item = %s', reg_item) + return reg_item, syn_all + + def listify(self, release_id, track, s): + """ + Turn a string into a list of 'words', where words may also be phrases which + are then 'canonized' - i.e. turned into equivalents for comparison purposes + :param release_id: + :param s: string + :return: s_tuple: a tuple of all the **match objects** (re words and defined phrases) + s_test_tuple: a tuple of the matched and canonized words and phrases (i.e. a tuple of strings, not objects) + """ + tuples = self.synonyms[track] + # just list anything that is a synonym (with word boundary markers) + syn_pattern = '|'.join( + [r'(?:^|\W|\b)' + x + r'(?:$|\W)' for y in self.synonyms[track] for x in y]) + op = self.find_synonyms( + release_id, + track, + r'(?:op|no|k|kk|kv|L|B|Hob|S|D|M|\w+WV)') + op_groups = op[0] + op_all = op[1] + notes = self.find_synonyms(release_id, track, r'[ABCDEFG]') + notes_groups = notes[0] + notes_all = notes[1] + sharp = self.find_synonyms(release_id, track, r'sharp') + sharp_groups = sharp[0] + sharp_all = sharp[1] + flat = self.find_synonyms(release_id, track, r'flat') + flat_groups = flat[0] + flat_all = flat[1] + major = self.find_synonyms(release_id, track, r'major') + major_groups = major[0] + major_all = major[1] + minor = self.find_synonyms(release_id, track, r'minor') + minor_groups = minor[0] + minor_all = minor[1] + opus_pattern = r"(?:\b((?:(" + op_groups + \ + r"))\W?\s?\d+\-?\u2013?\u2014?\d*\w*)\b)" + note_pattern = r"(\b" + notes_groups + r")" + accent_pattern = r"(?:\-(" + sharp_groups + r")(?:\s+|\b)|\-(" + flat_groups + r")(?:\s+|\b)|\s(" + sharp_groups + \ + r")(?:\s+|\b)|\s(" + flat_groups + r")(?:\s+|\b)|\u266F(?:\s+|\b)|\u266D(?:\s+|\b)|(?:[:,.]?\s+|$|\-))" + scale_pattern = r"(?:((" + major_groups + \ + r")|(" + minor_groups + r"))?\b)" + key_pattern = note_pattern + accent_pattern + scale_pattern + hyphen_split_pattern = r"(?:\b|\"|\')(\w+['’]?\w*)|(?:\b\w+\b)|(\B\&\B)" + # treat em-dash and en-dash as hyphens + hyphen_embed_pattern = r"(?:\b|\"|\')(\w+['’\-\u2013\u2014]?\w*)|(?:\b\w+\b)|(\B\&\B)" + + # The regex is split into two iterations as putting it all together can have unpredictable consequences + # - may match synonyms before op's even though that is later in the string + + # First match the op's and keys + regex_1 = opus_pattern + r"|(" + key_pattern + r")" + matches_1 = re.finditer(regex_1, s, re.UNICODE | re.IGNORECASE) + s_list = [] + s_test_list = [] + s_scrubbed = s + all_synonyms_lists = [ + op_all, + notes_all, + sharp_all, + flat_all, + sharp_all, + flat_all, + major_all, + minor_all] + matches_list = [2, 4, 5, 6, 7, 8, 10, 11] + for match in matches_1: + test_a = match.group() + match_a = [] + match_a.append(match.group()) + for j in range(1, 12): + match_a.append(match.group(j)) + # 0. overall match + # 1. overall opus match + # 2. 2-char op match + # 3. overall key match + # 4. note match + # 5. hyphenated sharp match + # 6. hyphenated flat match + # 7. non-hyphenated sharp match + # 8. non-hyphenated flat match + # 9. overall scale match + # 10. major match + # 11. minor match + for i, all_synonyms_list in enumerate(all_synonyms_lists): + if all_synonyms_list and match_a[matches_list[i]]: + match_regex = [re.match(pattern, match_a[matches_list[i]], re.IGNORECASE).group() + for pattern in all_synonyms_list + if re.match(pattern, match_a[matches_list[i]], re.IGNORECASE)] + if match_regex: + match_a[matches_list[i]] = self.canonize_synonyms( + release_id, tuples, match_a[matches_list[i]]) + test_a = re.sub(r"\b" + match_regex[0] + r"(?:\b|$|\s|\.)", + match_a[matches_list[i]], + test_a, flags=re.IGNORECASE) + if match_a[1]: + clean_opus = test_a.strip(' ,.:;/-?"') + test_a = re.sub( + re.escape(clean_opus), + self.canonize_opus( + release_id, + track, + clean_opus), + test_a, + flags=re.IGNORECASE) + if match_a[3]: + clean_key = test_a.strip(' ,.:;/-?"') + test_a = re.sub( + re.escape(clean_key), + self.canonize_key( + release_id, + track, + clean_key), + test_a, + flags=re.IGNORECASE) + + s_test_list.append(test_a) + s_list.append(match) + s_scrubbed_list = list(s_scrubbed) + for char in range(match.start(), match.end()): + if len(s_scrubbed_list) >= match.end(): # belt and braces + s_scrubbed_list[char] = '#' + s_scrubbed = ''.join(s_scrubbed_list) + + # Then match the synonyms and remaining words + if self.options[track]["cwp_split_hyphenated"]: + regex_2 = r"(" + syn_pattern + r")|" + hyphen_split_pattern + # allow ampersands and non-latin characters as word characters. Treat apostrophes as part of words. + # Treat opus and catalogue entries - e.g. K. 657 or OP.5 or op. 35a or CD 144 or BWV 243a - as one word + # also treat ranges of opus numbers (connected by dash, en dash or + # em dash) as one word + else: + regex_2 = r"(" + syn_pattern + r")|" + hyphen_embed_pattern + # as previous but also treat embedded hyphens as part of words. + matches_2 = re.finditer( + regex_2, s_scrubbed, re.UNICODE | re.IGNORECASE) + for match in matches_2: + test_b = match.group() + test_b1 = match.group(1) + test_b2 = match.group(2) + # The above are just for debugging in an IDE) + if match.group(1) and match.group(1) == match.group(): + s_test_list.append( + self.canonize_synonyms( + release_id, + tuples, + match.group(1))) # synonym + else: + s_test_list.append(match.group()) + s_list.append(match) + if s_list: + s_zip = list(zip(s_list, s_test_list)) + s_list, s_test_list = zip( + *sorted(s_zip, key=lambda tup: tup[0].start())) + s_tuple = tuple(s_list) + s_test_tuple = tuple(s_test_list) + return {'s_tuple': s_tuple, 's_test_tuple': s_test_tuple} + + def get_text_tuples(self, release_id, track, text_type): + """ + Return synonym or 'replacement' tuples + :param release_id: + :param track: + :param text_type: 'replacements' or 'synonyms' + Note that code in this method refers to synonyms (as that was written first), but applies eqaully to replacements + :return: + """ tm = track.metadata - strsyns = self.options[track]["cwp_synonyms"].split('/') + strsyns = re.split(r'(?= 2: for i, ts in enumerate(tup): tup[i] = ts.strip("' ").strip('"') - if not tup[i]: - if self.ERROR or self.INFO: - write_log(release_id, 'error', 'Synonym entries must not be blank - error in %s', syn) - self.append_tag(release_id, tm, '~cwp_error', - '7. Synonym entries must not be blank - error in ' + - syn) - tup[i] = "**BAD**" - elif re.findall(r'[^\w|\&]+', tup[i], re.UNICODE): - if self.ERROR or self.INFO: - write_log(release_id, 'error', - 'Synonyms must be single words without punctuation - error in %s', syn) - self.append_tag(release_id, tm, '~cwp_error', - '7. Synonyms must be single words without punctuation - error in ' + - syn) + if len( + tup[i]) > 4 and tup[i][0] == "!" and tup[i][1] == "!" and tup[i][-1] == "!" and tup[i][-2] == "!": + # we have a reg ex inside - this deals with legacy + # replacement text where enclosure in double-shouts was + # required + tup[i] = tup[i][2:-2] + if (i < len(tup) - 1 or text_type == + 'synonyms') and not tup[i]: + if self.WARNING or self.INFO: + write_log( + release_id, + 'warning', + '%s: entries must not be blank - error in %s', + text_type, + syn) + self.append_tag( + release_id, + tm, + '~cwp_warning', + '7. ' + text_type + ': entries must not be blank - error in ' + syn) tup[i] = "**BAD**" elif [tup for t in synonyms if tup[i] in t]: - if self.ERROR or self.INFO: - write_log(release_id, 'error', - 'Synonym keys cannot duplicate any in existing synonyms - error in %s ' - '- omitted from synonyms', syn) - self.append_tag(release_id, tm, '~cwp_error', - '7. Synonym keys cannot duplicate any in existing synonyms - error in ' + - syn + ' - omitted from synonyms') + if self.WARNING or self.INFO: + write_log( + release_id, + 'warning', + '%s: keys cannot duplicate any in existing %s - error in %s ' + '- omitted from %s. To fix, place all %s in one tuple.', + text_type, + text_type, + syn, + text_type, + text_type) + self.append_tag(release_id, tm, '~cwp_warning', + '7. ' + text_type + ': keys cannot duplicate any in existing ' + text_type + ' - error in ' + + syn + ' - omitted from ' + text_type + '. To fix, place all ' + text_type + ' in one tuple.') tup[i] = "**BAD**" if "**BAD**" in tup: continue else: - tup = tuple(tup) synonyms.append(tup) else: - if self.ERROR or self.INFO: - write_log(release_id, 'error', 'Error in synonym format for synonym %s', syn) - self.append_tag(release_id, tm, '~cwp_error', '7. Error in synonym format for synonym ' + - syn) + if self.WARNING or self.INFO: + write_log( + release_id, + 'warning', + 'Error in %s format for %s', + text_type, + syn) + self.append_tag( + release_id, + tm, + '~cwp_warning', + '7. Error in ' + + text_type + + ' format for ' + + syn) if self.INFO: - write_log(release_id, 'info', "Synonyms: %s", synonyms) + write_log(release_id, 'info', "%s: %s", text_type, synonyms) return synonyms def synonymize(self, release_id, str, synonyms): # Replace Roman numerals as per synonyms str_test = replace_roman_numerals(str) if self.INFO: - write_log(release_id, 'info', 'Replaced Roman numerals. string = %s', str_test) + write_log( + release_id, + 'info', + 'Replaced Roman numerals. string = %s', + str_test) for key, equiv in synonyms: if self.INFO: write_log(release_id, 'info', "key %s, equiv %s", key, equiv) @@ -7394,9 +9573,13 @@ def synonymize(self, release_id, str, synonyms): str_test, re.IGNORECASE | re.UNICODE) str_test = re.sub(key_pattern, syno, str_test, - re.IGNORECASE | re.UNICODE) + re.IGNORECASE | re.UNICODE) if self.INFO: - write_log(release_id, 'info', "Replaced synonyms str_test = %s", str_test) + write_log( + release_id, + 'info', + "Replaced synonyms str_test = %s", + str_test) return str_test def stencil(self, release_id, matches_tuple, test_string): @@ -7407,50 +9590,41 @@ def stencil(self, release_id, matches_tuple, test_string): :param test_string: original string used in regex :return: 'match list' - list of matched strings, 'gap list' - list of strings in gaps between matches """ - match_items=[] + match_items = [] gap_items = [] + dummy = False pointer = 0 if self.DEBUG or self.INFO: - write_log(release_id, 'debug', 'In fn stencil. test_string = %s. matches_tuple = %s', test_string, matches_tuple) + write_log( + release_id, + 'debug', + 'In fn stencil. test_string = %s. matches_tuple = %s', + test_string, + matches_tuple) for match_num, match in enumerate(matches_tuple): start = match.start() end = match.end() if start > pointer: if pointer == 0: - match_items.append('') # add a null word item at start to keep the lists the same length + # add a null word item at start to keep the lists the same + # length + match_items.append('') + dummy = True gap_items.append(test_string[pointer:start]) else: if pointer > 0: - gap_items.append('') # shouldn't happen, but just in case there are two word items with no gap + # shouldn't happen, but just in case there are two word + # items with no gap + gap_items.append('') match_items.append(test_string[start:end]) pointer = end if match_num + 1 == len(matches_tuple): - gap_items.append(test_string[pointer:]) # pick up any punc items at end - return {'match list': match_items, 'gap list': gap_items} - - - def reverse_syn(self, release_id, term, synonyms): - """ - reverse any synonyms left in the title item - :param release_id: name for log file - usually =musicbrainz_albumid - unless called outside metadata processor - :param term: the title item - :param synonyms: tuples - :return: title item without synonyms - """ - if self.DEBUG or self.INFO: - write_log(release_id, 'info', "REVERSING SYNONYM REPLACEMENT") - for key, equiv in synonyms: - if self.INFO: - write_log(release_id, 'info', "equiv %s, key %s", equiv, key) - equiv = self.EQ + equiv - esc_equiv = re.escape(equiv) - equiv_pattern = '\\b' + esc_equiv + '\\b' - term = re.sub(equiv_pattern, key, term) - term = term.replace(self.EQ, '') - if self.INFO: - write_log(release_id, 'info', "Returning term = %s",term) - return term + # pick up any punc items at end + gap_items.append(test_string[pointer:]) + return { + 'match list': match_items, + 'gap list': gap_items, + 'dummy': dummy} def boil(self, release_id, s): """ @@ -7463,6 +9637,7 @@ def boil(self, release_id, s): if self.DEBUG or self.INFO: write_log(release_id, 'debug', "boiling %s", s) s = s.lower() + s = replace_roman_numerals(s) s = s.replace(self.EQ.lower(), '')\ .replace('sch', 'sh')\ .replace(u'\xdf', 'ss')\ @@ -7471,7 +9646,12 @@ def boil(self, release_id, s): .replace('oe', 'o')\ .replace(u'\u00fc', 'ue')\ .replace('ue', 'u')\ - .replace('ae', 'a') + .replace(u'\u00e6', 'ae')\ + .replace('ae', 'a')\ + .replace(u'\u266F', 'sharp')\ + .replace(u'\u266D', 'flat')\ + .replace(u'\u2013', '-')\ + .replace(u'\u2014', '-') # first term above is to remove the markers used for synonyms, to # enable a true comparison punc = re.compile(r'\W*', re.ASCII) @@ -7484,30 +9664,17 @@ def boil(self, release_id, s): write_log(release_id, 'debug', "boiled result = %s", boiled) return boiled - # Remove certain keywords - def remove_words(self, release_id, query, stopwords): - if self.DEBUG or self.INFO: - write_log(release_id, 'debug', "INSIDE REMOVE_WORDS") - querywords = query.split() - resultwords = [] - for word in querywords: - if word.lower() not in stopwords: - resultwords.append(word) - return ' '.join(resultwords) - - ################ # OPTIONS PAGE # ################ - class ClassicalExtrasOptionsPage(OptionsPage): NAME = "classical_extras" TITLE = "Classical Extras" PARENT = "plugins" - opts = plugin_options('artists') + plugin_options('tag') +\ - plugin_options('workparts') + plugin_options('genres') + plugin_options('other') + opts = plugin_options('artists') + plugin_options('tag') + plugin_options('tag_detail') +\ + plugin_options('workparts') + plugin_options('genres') + plugin_options('other') options = [] # custom logging for non-album-related messages is written to startup.log @@ -7524,7 +9691,11 @@ class ClassicalExtrasOptionsPage(OptionsPage): elif opt['type'] == 'Integer': options.append(IntOption("setting", opt['option'], default)) else: - write_log("session", 'error', "Error in setting options for option = %s", opt['option']) + write_log( + "session", + 'error', + "Error in setting options for option = %s", + opt['option']) def __init__(self, parent=None): super(ClassicalExtrasOptionsPage, self).__init__(parent) @@ -7536,8 +9707,8 @@ def load(self): Load the options - NB all options are set in plugin_options, so this just parses that :return: """ - opts = plugin_options('artists') + plugin_options('tag') + \ - plugin_options('workparts') + plugin_options('genres') + plugin_options('other') + opts = plugin_options('artists') + plugin_options('tag') + plugin_options('tag_detail') +\ + plugin_options('workparts') + plugin_options('genres') + plugin_options('other') # To force a toggle so that signal given toggle_list = ['use_cwp', @@ -7549,11 +9720,14 @@ def load(self): 'cwp_partial', 'cwp_arrangements', 'cwp_medley', - 'cwp_use_muso_refdb',] + 'cwp_use_muso_refdb', ] # open at last used tab if 'ce_tab' in config.setting: - cfg = config.setting.__dict__['_ConfigSection__config']['setting/ce_tab'] # seems to be the only way to get the value - config.setting['ce_tab'] returns None + # seems to be the only way to get the value - + # config.setting['ce_tab'] returns None + cfg = config.setting.__dict__[ + '_ConfigSection__config']['setting/ce_tab'] if isinstance(cfg, str) and cfg.isdigit(): cfg_val = int(cfg) elif isinstance(cfg, int): @@ -7592,14 +9766,19 @@ def load(self): self.ui.__dict__[ui_name].setValue( self.config.setting[opt['option']]) else: - write_log('session', 'error', "Error in loading options for option = %s", opt['option']) + write_log( + 'session', + 'error', + "Error in loading options for option = %s", + opt['option']) def save(self): - opts = plugin_options('artists') + plugin_options('tag') + \ - plugin_options('workparts') + plugin_options('genres') + plugin_options('other') + opts = plugin_options('artists') + plugin_options('tag') + plugin_options('tag_detail') +\ + plugin_options('workparts') + plugin_options('genres') + plugin_options('other') # save tab setting - config.setting['ce_tab'] = self.ui.tabWidget.currentIndex() # works for setting, but not for getting + # works for setting, but not for getting + config.setting['ce_tab'] = self.ui.tabWidget.currentIndex() for opt in opts: if opt['option'] == 'classical_work_parts': @@ -7624,7 +9803,11 @@ def save(self): self.config.setting[opt['option'] ] = self.ui.__dict__[ui_name].value() else: - write_log('session', 'error', "Error in saving options for option = %s", opt['option']) + write_log( + 'session', + 'error', + "Error in saving options for option = %s", + opt['option']) ################# @@ -7639,8 +9822,12 @@ def save(self): config.setting['release_ars'] = True # custom logging for non-album-related messages is written to startup.log write_log('session', 'basic', 'Loading ' + PLUGIN_NAME) -REF_DICT = get_references_from_file('session', - config.setting['cwp_muso_path'], config.setting['cwp_muso_refdb']) + +# REFERENCE DATA +REF_DICT = get_references_from_file( + 'session', + config.setting['cwp_muso_path'], + config.setting['cwp_muso_refdb']) write_log('session', 'info', 'External references (Muso):') write_log('session', 'info', REF_DICT) COMPOSER_DICT = REF_DICT['composers'] @@ -7650,14 +9837,17 @@ def save(self): cd['lc_name'] = [c.lower() for c in cd['name']] cd['lc_sort'] = [c.lower() for c in cd['sort']] PERIOD_DICT = REF_DICT['periods'] -if (config.setting['cwp_muso_dates'] or config.setting['cwp_muso_periods']) and not PERIOD_DICT: +if (config.setting['cwp_muso_dates'] + or config.setting['cwp_muso_periods']) and not PERIOD_DICT: write_log('session', 'error', 'No period map found') GENRE_DICT = REF_DICT['genres'] if config.setting['cwp_muso_genres'] and not GENRE_DICT: write_log('session', 'error', 'No classical genre list found') + +# API CALLS register_track_metadata_processor(PartLevels().add_work_info) register_track_metadata_processor(ExtraArtists().add_artist_info) register_options_page(ClassicalExtrasOptionsPage) + +# END write_log('session', 'basic', 'Finished intialisation') -# config.setting['log_debug'] = False -# config.setting['log_info'] = False diff --git a/plugins/classical_extras/options_classical_extras.ui b/plugins/classical_extras/options_classical_extras.ui index 143cac1a..fa035006 100644 --- a/plugins/classical_extras/options_classical_extras.ui +++ b/plugins/classical_extras/options_classical_extras.ui @@ -6,7 +6,7 @@ 0 0 - 1148 + 1145 918 @@ -87,6 +87,15 @@ Qt::WheelFocus + + QFrame::NoFrame + + + QFrame::Plain + + + 1 + true @@ -95,8 +104,8 @@ 0 0 - 1087 - 1044 + 1086 + 1071 @@ -108,61 +117,88 @@ + + + + 255 + 255 + 222 + + + 255 255 - 255 + 222 - 236 + 255 255 - 171 + 222 + + + + 255 + 255 + 222 + + + 255 255 - 255 + 222 - 236 + 255 255 - 171 + 222 + + + + 255 + 255 + 222 + + + - 236 + 255 255 - 171 + 222 - 236 + 255 255 - 171 + 222 @@ -170,10 +206,10 @@ - true + false - + background-color: rgb(255, 255, 222); QFrame::StyledPanel @@ -186,9 +222,93 @@ - - - + + + + + 255 + 255 + 222 + + + + + + + 255 + 255 + 222 + + + + + + + 255 + 255 + 222 + + + + + + + + + 255 + 255 + 222 + + + + + + + 255 + 255 + 222 + + + + + + + 255 + 255 + 222 + + + + + + + + + 255 + 255 + 222 + + + + + + + 255 + 255 + 222 + + + + + + + 255 + 255 + 222 + + + + @@ -205,7 +325,7 @@ - (Note that the "infer work-types option has moved to the "genres" tab) + (Note that the "infer work-types" option has moved to the "genres" tab) @@ -219,13 +339,6 @@ - - - - <html><head/><body><p>The next section does not change the contents of &quot;artist&quot; or &quot;album artist&quot; tags - it only affects writer (composer etc.) and peformer tags, by using as-credited/alias names from the artist data for the release.</p></body></html> - - - @@ -234,305 +347,421 @@ - - - - - - - - 255 - 255 - 255 - - - - - - - 19 - 186 - 161 - - - - - - - - - 255 - 255 - 255 - - - - - - - 19 - 186 - 161 - - - - - - - - - 19 - 186 - 161 - - - - - - - 19 - 186 - 161 - - - - - - - - <html><head/><body><p>&quot;Work-artists&quot; are types such as composer, writer, arranger and lyricist who belong to the MusicBrainz Work-Artist relationship</p><p>&quot;Performers&quot; are types such as performer and conductor who belong to the MusicBrainz Recording-Artist relationship</p></body></html> - - - true - - - + + + QFrame::StyledPanel - - Work-artist/performer naming options + + QFrame::Raised - - - QFormLayout::AllNonFixedFieldsGrow + + + 0 - - - - MusicBrainz standard names and Aliases + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(176, 220, 192); - - - - - <html><head/><body><p>Do not use aliases (but may be replaced by as-credited name)</p></body></html> - - - Use MB standard names - - - - - - - <html><head/><body><p>Alias will only be available for use if the work-artist/performer is also a release artist, recording artist or track artist.</p></body></html> - - - Use alias for all work-artists/performers - - - - - - - <html><head/><body><p>Only use alias (if available) for work-artists (writers, composers, arrangers, lyricists etc.)</p></body></html> - - - Use alias for work-artists only - - - - - - - - - - Sub-options + + <html><head/><body><p><span style=" font-weight:600;">Work-artist / performer naming options</span></p></body></html> - - - - - <html><head/><body><p>Alias (if it exists) will over-ride as-credited</p></body></html> - - - Alias over-rides credited-as - - - - - - - <html><head/><body><p>As-credited (if it exists) will over-ride alias</p></body></html> - - - Credited-as over-rides MB/Alias - - - - - - - <html><head/><body><p>Will be based on sort names. For cyrillic script names, patronyms will be removed.</p></body></html> - - - Fix non-Latin text in names (where possible and if not fixed by other naming options) - - - - - - - - <html><head/><body><p><br/></p></body></html> - - - Credited-as options:- - - - - - - <html><head/><body><p>Select the source for 'as-credited' names - whether these are applied depends on the sub-options choices.</p></body></html> - - - border-color: rgb(116, 116, 116); - - - Names to use... - - - - - - Qt::RightToLeft - - - Use "credited as" name for work-artists/performers who are recording artists - - - - - - - Qt::RightToLeft - - - false - - - - - - and/or release group artists - - - + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + UpArrowCursor + + + <html><head/><body><p>&quot;Work-artists&quot; are types such as composer, writer, arranger and lyricist who belong to the MusicBrainz Work-Artist relationship</p><p>&quot;Performers&quot; are types such as performer and conductor who belong to the MusicBrainz Recording-Artist relationship</p></body></html> + + + false + + + background-color: rgb(211, 248, 224); + + + QFrame::NoFrame + + + + QFormLayout::AllNonFixedFieldsGrow + + + 6 + + + 9 + + + 0 + + + 9 + + + + + <html><head/><body><p>This section does not change the contents of &quot;artist&quot; or &quot;album artist&quot; tags - it only affects writer (composer etc.) and peformer tags, by using as-credited/alias names from the artist data for the release.</p></body></html> + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Credited-as options:- + + - + - <html><head/><body><p><br/></p></body></html> - - - Qt::RightToLeft + <html><head/><body><p>Select the source for 'as-credited' names - whether these are applied depends on the sub-options choices.</p></body></html> - - and/or release artists + + false - - - - - - Qt::RightToLeft + + background-color: rgb(250, 250, 250); - - and/or release relationship artists + + Names to use... + + + + + Qt::RightToLeft + + + Use "credited as" name for work-artists/performers who are recording artists + + + + + + + Qt::RightToLeft + + + false + + + + + + and/or release group artists + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Qt::RightToLeft + + + and/or release artists + + + + + + + Qt::RightToLeft + + + and/or release relationship artists + + + + + + + Qt::RightToLeft + + + and/or recording relationship artists + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Qt::RightToLeft + + + and/or track artists + + + + + + + Qt::RightToLeft + + + The above are applied in sequence - e.g. track artist credit will over-ride release artist credit. + + + + + + + Names are cached. A restart is necessary if any of the above name sources are removed. + + + + - - - Qt::RightToLeft + + + <html><head/><body><p>Select the tag types where any 'as-credited' names will be applied - whether these are applied depends on the sub-options choices.</p></body></html> - - and/or recording relationship artists + + background-color: rgb(250, 250, 250); + + + Places to use them ... + + + QLayout::SetDefaultConstraint + + + 9 + + + + + Qt::RightToLeft + + + Use for performing artists + + + + + + + Qt::RightToLeft + + + Use for work-artists + + + + + + + + + + + background-color: rgb(250, 250, 250); + + + Sub-options + + - + - <html><head/><body><p><br/></p></body></html> - - - Qt::RightToLeft + <html><head/><body><p>Alias (if it exists) will over-ride as-credited</p></body></html> - and/or track artists + Alias over-rides credited-as - - - Qt::RightToLeft + + + <html><head/><body><p>As-credited (if it exists) will over-ride alias</p></body></html> - The above are applied in sequence - e.g. track artist credit will over-ride release artist credit. + Credited-as over-rides MB/Alias - + + + <html><head/><body><p>Will be based on sort names. For cyrillic script names, patronyms will be removed.</p></body></html> + - Names are cached. A restart is necessary if any of the above name sources are removed. + Fix non-Latin text in names (where possible and if not fixed by other naming options) - - - - <html><head/><body><p>Select the tag types where any 'as-credited' names will be applied - whether these are applied depends on the sub-options choices.</p></body></html> + + + + background-color: rgb(211, 248, 224); - Places to use them ... + MusicBrainz standard names and Aliases - - - - - Qt::RightToLeft - - - Use for performing artists - - - + - - - Qt::RightToLeft + + + background-color: rgb(250, 250, 250); - - Use for work-artists + + + + + + + <html><head/><body><p>Do not use aliases (but may be replaced by as-credited name)</p></body></html> + + + Use MB standard names + + + + + + + <html><head/><body><p>Alias will only be available for use if the work-artist/performer is also a release artist, recording artist or track artist.</p></body></html> + + + Use alias for all work-artists/performers + + + + + + + <html><head/><body><p>Only use alias (if available) for work-artists (writers, composers, arrangers, lyricists etc.)</p></body></html> + + + Use alias for work-artists only + + + + @@ -545,317 +774,171 @@ - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 162 - 116 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 162 - 116 - - - - - - - - - 255 - 162 - 116 - - - - - - - 255 - 162 - 116 - - - - - - - - <html><head/><body><p>Select recording artist options (see &quot;What's this&quot;)</p></body></html> - - - <html><head/><body><p>In MusicBrainz, the recording artist may be different from the track artist. For classical music, the MusicBrainz guidelines state that the track artist should be the composer; however the recording artist(s) is/are usually the principal performer(s).</p><p>Classical Extras puts the recording artists into 'hidden variables' (as a minimum) using the chosen naming convention.</p><p>There is also option to allow you to replace the track artist by the recording artist (or to merge them). The chosen action will be applied to the 'artist', 'artists', 'artistsort' and 'artists_sort' tags. Note that 'artist' is a single-valued string whereas 'artists' is a list and may be multi-valued. Lists are properly merged, but because the 'artist' string may have different join-phrases etc, a merged tag may have the recording artist(s) in brackets after the track artist(s).</p></body></html> - - - true - - - + + + QFrame::StyledPanel - - Recording artist options + + QFrame::Raised - - - - - <html><head/><body><p>In classical music (in MusicBrainz), recording artists will usually be performers whereas track artists are composers. By default, the naming convention for performers (set in the previous section) will be used (although only the as-credited name set for the recording artist will be applied). Alternatively, the naming convention for track artists can be used - which is determined by the main Picard metadat options.</p></body></html> - - - Naming convention as for ... - - - - - - ...track artist (set in Picard options) - - - - - - - ... perfomers (set above) - - - - - - - - - - - 1 - 0 - - - - false - - - 1 - - - 1 - - - Qt::Vertical - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - Qt::Vertical + + + background-color: rgb(176, 220, 192); - - - - - Use recording artists to update track artists -> + <html><head/><body><p><span style=" font-weight:600;">Recording artist options</span></p></body></html> - - - - 0 - 0 - - - - Replace/merge options + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + - - - - - Replace track artist by recording artist - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Only replace if rec. artist exists - - - - - - - Merge track artist and recording artist - - - - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 255 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 255 - - - - - - - - - 170 - 170 - 255 - - - - - - - 170 - 170 - 255 - - - - - - - - true - - - - - - Other artist options - - - - - <html><head/><body><p>Enter text to appear in annotations. Do not use any quotation marks.</p></body></html> + <html><head/><body><p>Select recording artist options (see &quot;What's this&quot;)</p></body></html> + + + <html><head/><body><p>In MusicBrainz, the recording artist may be different from the track artist. For classical music, the MusicBrainz guidelines state that the track artist should be the composer; however the recording artist(s) is/are usually the principal performer(s).</p><p>Classical Extras puts the recording artists into 'hidden variables' (as a minimum) using the chosen naming convention.</p><p>There is also option to allow you to replace the track artist by the recording artist (or to merge them). The chosen action will be applied to the 'artist', 'artists', 'artistsort' and 'artists_sort' tags. Note that 'artist' is a single-valued string whereas 'artists' is a list and may be multi-valued. Lists are properly merged, but because the 'artist' string may have different join-phrases etc, a merged tag may have the recording artist(s) in brackets after the track artist(s).</p></body></html> + + + false + + + background-color: rgb(211, 248, 224); - Annotations - performers and lyricists + - + - - - QFrame::StyledPanel + + + <html><head/><body><p>In classical music (in MusicBrainz), recording artists will usually be performers whereas track artists are composers. By default, the naming convention for performers (set in the previous section) will be used (although only the as-credited name set for the recording artist will be applied). Alternatively, the naming convention for track artists can be used - which is determined by the main Picard metadat options.</p></body></html> + + + background-color: rgb(250, 250, 250); - - QFrame::Raised + + Naming convention as for ... - - - 6 - - - QLayout::SetDefaultConstraint - - - 1 - - - 9 - + - - - <html><head/><body><p>Annotation to include with &quot;chorus master&quot; in conductor tag.</p></body></html> - + - Chorus Master + ...track artist (set in Picard options) - - - background-color: rgb(229, 229, 255); + + + ... perfomers (set above) @@ -863,118 +946,61 @@ - - - QFrame::StyledPanel - - - QFrame::Raised + + + Qt::Vertical - - - 1 - - - - - <html><head/><body><p>Annotation to include for &quot;concert master&quot; in performer tag.</p></body></html> - - - Concert Master - - - - - - - background-color: rgb(229, 229, 255); - - - - - - - QFrame::StyledPanel - - - QFrame::Raised + + + Use recording artists to update track artists -> - - - - - <html><head/><body><p>Annotation for lyricist, to include in lyricist tag</p></body></html> - - - Lyricist - - - - - - - background-color: rgb(229, 229, 255); - - - - - - - QFrame::StyledPanel + + + + 0 + 0 + - - QFrame::Raised + + background-color: rgb(250, 250, 250); + + + Replace/merge options - + - - - <html><head/><body><p>Annotation for librettist, to include in lyricist tag</p></body></html> - + - Librettist + Replace track artist by recording artist - - - background-color: rgb(229, 229, 255); + + + + 0 + 0 + - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - <html><head/><body><p>Annotation for translator, to include in lyricist tag</p></body></html> + + Qt::LeftToRight - Translator + Only replace if rec. artist exists - - - background-color: rgb(229, 229, 255); + + + Merge track artist and recording artist @@ -984,220 +1010,580 @@ - - - - <html><head/><body><p>Select as required. See &quot;What's this&quot; for details.</p></body></html> - - - - - - - - - <html><head/><body><p><br/></p></body></html> - - - <html><head/><body><p>This will gather together, for example, any arranger-type information from the recording, work or parent works and place it in the &quot;arranger&quot; tag ('host' tag), with the annotation (see details to right of this box) in brackets. All arranger types will also be put in a hidden variable, e.g. _cwp_orchestrators. The table below shows the artist types, host tag and hidden variable for each artist type.</p><p>| Artist type | Host tag | Hidden variable |</p><p>| ----------------- | ------------------| -------------------------------------- |</p><p>| writer | composer | writers |</p><p>| lyricist | lyricist | lyricists |</p><p>| librettist | lyricist | librettists |</p><p>| revised by | arranger | revisors |</p><p>| translator | lyricist | translators |</p><p>| arranger | arranger | arrangers |</p><p>| reconstructed by | arranger | reconstructors |</p><p>| orchestrator | arranger | orchestrators |</p><p>| instrument arranger | arranger | arrangers (with instrument type in brackets) |</p><p>| vocal arranger | arranger | arrangers (with voice type in brackets) |</p><p>| chorus master | conductor | chorusmasters |</p><p>| concertmaster | performer (with annotation as a sub-key) | leaders |</p></body></html> - - - Modify host tags and include annotations (see =>) - - - - - - - <html><head/><body><p><br/></p></body></html> - - - <html><head/><body><p>This will add the composer(s) last name(s) before the album name, if they are listed as album artists. If there is more than one composer, they will be listed in the descending order of the length of their music on the release.</p></body></html> - - - Name album as "Composer Last Name(s): Album Name" - - - - - - - <html><head/><body><p>This applies to both the Picard 'lyricist' tag and the related internal plugin hidden variables '_cwp_lyricists' etc.</p></body></html> - - - Do not write 'lyricist' tag if no vocal performers - - - - - - - Use "credited-as" name for instrument - - - - - - - <html><head/><body><p>Select to eliminate &quot;additional&quot;, &quot;solo&quot; or &quot;guest&quot; from instrument description</p></body></html> - - - <html><head/><body><p>MusicBrainz permits the use of &quot;solo&quot;, &quot;guest&quot; and &quot;additional&quot; as instrument attributes although, for classical music, its use should be fairly rare - usually only if explicitly stated as a &quot;solo&quot; on the the sleevenotes. Classical Extras provides the option to exclude these attributes (the default), but you may wish to enable them for certain releases or non-Classical / cross-over releases.</p></body></html> - - - Do not include attributes (e.g. 'solo') in an instrument type - - - - + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(176, 220, 192); + + + <html><head/><body><p><span style=" font-weight:600;">Other artist options</span></p></body></html> + - - - - <html><head/><body><p>Enter text to appear in annotations. Do not use any quotation marks.</p></body></html> + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + 211 + 248 + 224 + + + + + + + + false + + + background-color: rgb(211, 248, 224); - Annotations - writers and arrangers + - - - - - QFrame::StyledPanel + + + 6 + + + + + <html><head/><body><p>Enter text to appear in annotations. Do not use any quotation marks.</p></body></html> + + + background-color: rgba(250, 250, 250, 250); - - QFrame::Raised + + Annotations - performers and lyricists - + - - - Writer + + + background-color: rgb(211, 248, 224); + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + QLayout::SetDefaultConstraint + + + 1 + + + 9 + + + + + <html><head/><body><p>Annotation to include with &quot;chorus master&quot; in conductor tag.</p></body></html> + + + Chorus Master + + + + + + + background-color: rgb(250, 250, 250); + + + + - + - background-color: rgb(229, 229, 255); + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + + QFrame::Raised + + + 1 + + + + + <html><head/><body><p>Annotation to include for &quot;concert master&quot; in performer tag.</p></body></html> + + + Concert Master + + + + + + + background-color: rgb(250, 250, 250); + + + + - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - Arranger + + + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + <html><head/><body><p>Annotation for lyricist, to include in lyricist tag</p></body></html> + + + Lyricist + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + + + + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + QFrame::Raised + + + + + + <html><head/><body><p>Annotation for librettist, to include in lyricist tag</p></body></html> + + + Librettist + + + + + + + background-color: rgb(250, 250, 250); + + + + - + - background-color: rgb(229, 229, 255); + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + QFrame::Raised + + + + + + <html><head/><body><p>Annotation for translator, to include in lyricist tag</p></body></html> + + + Translator + + + + + + + background-color: rgb(250, 250, 250); + + + + - - - - QFrame::StyledPanel + + + + <html><head/><body><p>Select as required. See &quot;What's this&quot; for details.</p></body></html> - - QFrame::Raised + + background-color: rgba(250, 250, 250, 250); - - - 1 + + + + + + 6 - + - <html><head/><body><p>Text with which to annotate orchestrator in the arranger tag.</p></body></html> + <html><head/><body><p><br/></p></body></html> + + + <html><head/><body><p>This will gather together, for example, any arranger-type information from the recording, work or parent works and place it in the &quot;arranger&quot; tag ('host' tag), with the annotation (see details to right of this box) in brackets. All arranger types will also be put in a hidden variable, e.g. _cwp_orchestrators. The table below shows the artist types, host tag and hidden variable for each artist type.</p><p>| Artist type | Host tag | Hidden variable |</p><p>| ----------------- | ------------------| -------------------------------------- |</p><p>| writer | composer | writers |</p><p>| lyricist | lyricist | lyricists |</p><p>| librettist | lyricist | librettists |</p><p>| revised by | arranger | revisors |</p><p>| translator | lyricist | translators |</p><p>| arranger | arranger | arrangers |</p><p>| reconstructed by | arranger | reconstructors |</p><p>| orchestrator | arranger | orchestrators |</p><p>| instrument arranger | arranger | arrangers (with instrument type in brackets) |</p><p>| vocal arranger | arranger | arrangers (with voice type in brackets) |</p><p>| chorus master | conductor | chorusmasters |</p><p>| concertmaster | performer (with annotation as a sub-key) | leaders |</p></body></html> - Orchestrator + Modify host tags and include annotations (see =>) - - - background-color: rgb(229, 229, 255); + + + <html><head/><body><p><br/></p></body></html> + + + <html><head/><body><p>This will add the composer(s) last name(s) before the album name, if they are listed as album artists. If there is more than one composer, they will be listed in the descending order of the length of their music on the release.</p></body></html> + + + Name album as "Composer Last Name(s): Album Name" - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - <html><head/><body><p>Annotation for &quot;reconstructed by&quot;, to include in arranger tag</p></body></html> + + + <html><head/><body><p>This applies to both the Picard 'lyricist' tag and the related internal plugin hidden variables '_cwp_lyricists' etc.</p></body></html> - Reconstructed by + Do not write 'lyricist' tag if no vocal performers - - - background-color: rgb(229, 229, 255); + + + Use "credited-as" name for instrument + + + + + + + <html><head/><body><p>Select to eliminate &quot;additional&quot;, &quot;solo&quot; or &quot;guest&quot; from instrument description</p></body></html> + + + <html><head/><body><p>MusicBrainz permits the use of &quot;solo&quot;, &quot;guest&quot; and &quot;additional&quot; as instrument attributes although, for classical music, its use should be fairly rare - usually only if explicitly stated as a &quot;solo&quot; on the the sleevenotes. Classical Extras provides the option to exclude these attributes (the default), but you may wish to enable them for certain releases or non-Classical / cross-over releases.</p></body></html> + + + Do not include attributes (e.g. 'solo') in an instrument type - - - - QFrame::StyledPanel + + + + <html><head/><body><p>Enter text to appear in annotations. Do not use any quotation marks.</p></body></html> + + + background-color: rgb(250, 250, 250); - - QFrame::Raised + + Annotations - writers and arrangers - + - - - <html><head/><body><p>Annotation for &quot;revised by&quot;, to include in arranger tag tag</p></body></html> + + + background-color: rgb(211, 248, 224); - - Revised by + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Writer + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + + + + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + Arranger + + + + + + + background-color: rgb(250, 250, 250); + + + + - + - background-color: rgb(229, 229, 255); + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 1 + + + + + <html><head/><body><p>Text with which to annotate orchestrator in the arranger tag.</p></body></html> + + + Orchestrator + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + + + + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + <html><head/><body><p>Annotation for &quot;reconstructed by&quot;, to include in arranger tag</p></body></html> + + + Reconstructed by + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + + + + background-color: rgb(211, 248, 224); + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + <html><head/><body><p>Annotation for &quot;revised by&quot;, to include in arranger tag tag</p></body></html> + + + Revised by + + + + + + + background-color: rgb(250, 250, 250); + + + + @@ -1220,185 +1606,258 @@ - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 110 - 98 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 110 - 98 - - - - - - - - - 170 - 110 - 98 - - - - - - - 170 - 110 - 98 - - - - - - - - <html><head/><body><p><span style=" font-weight:600;">Please note that this section operates on the underlying input file tags, not the Picard-generated tags (MusicBrainz does not have lyrics)</span></p><p> Sometimes &quot;lyrics&quot; tags can contain album notes (repeated for every track in an album) as well as track notes and lyrics. This section will filter out the common text and place it in a different tag from the text which is unique to each track.</p></body></html> - - - true - - - + + + QFrame::StyledPanel - - Lyrics + + QFrame::Raised - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - <html><head/><body><p>enables this section</p></body></html> + + + background-color: rgb(204, 168, 161); - Split lyrics tag into track and album levels + <html><head/><body><p><span style=" font-weight:600;">Lyrics</span></p></body></html> - - - <html><head/><body><p>Enter a valid tag name (no spaces, punctuation or special charcaters other than underline; use lower case)</p></body></html> + + + + + + + + 230 + 215 + 211 + + + + + + + 230 + 215 + 211 + + + + + + + 230 + 215 + 211 + + + + + + + + + 230 + 215 + 211 + + + + + + + 230 + 215 + 211 + + + + + + + 230 + 215 + 211 + + + + + + + + + 230 + 215 + 211 + + + + + + + 230 + 215 + 211 + + + + + + + 230 + 215 + 211 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Please note that this section operates on the underlying input file tags, not the Picard-generated tags (MusicBrainz does not have lyrics)</span></p><p> Sometimes &quot;lyrics&quot; tags can contain album notes (repeated for every track in an album) as well as track notes and lyrics. This section will filter out the common text and place it in a different tag from the text which is unique to each track.</p></body></html> + + + false + + + background-color: rgb(230, 215, 211); - + - - - QFrame::StyledPanel + + + <html><head/><body><p>enables this section</p></body></html> - - QFrame::Raised + + Split lyrics tag into track and album levels - - - - - <html><head/><body><p>The name of the lyrics file tag in the input file (normally just 'lyrics')</p></body></html> - - - Incoming lyrics tag (i.e. file tag) - - - - - - - background-color: rgb(255, 233, 181); - - - - - - - QFrame::StyledPanel + + + <html><head/><body><p>Enter a valid tag name (no spaces, punctuation or special charcaters other than underline; use lower case)</p></body></html> - - QFrame::Raised + + - + - - - <html><head/><body><p>The name of the tag where common text should be placed</p></body></html> + + + background-color: rgb(204, 168, 161); - - Tag for album notes / lyrics + + QFrame::StyledPanel + + + QFrame::Raised + + + + + <html><head/><body><p>The name of the lyrics file tag in the input file (normally just 'lyrics')</p></body></html> + + + Incoming lyrics tag (i.e. file tag) + + + + + + + background-color: rgb(250, 250, 250); + + + + - + - background-color: rgb(255, 233, 181); + background-color: rgb(204, 168, 161); - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - <html><head/><body><p>The name of the tag where notes/lyrics unique to a track should be placed</p></body></html> + + QFrame::StyledPanel - - Tag for track notes / lyrics + + QFrame::Raised + + + + + <html><head/><body><p>The name of the tag where common text should be placed</p></body></html> + + + Tag for album notes / lyrics + + + + + + + background-color: rgb(250, 250, 250); + + + + - + - background-color: rgb(255, 233, 181); + background-color: rgb(204, 168, 161); + + + QFrame::StyledPanel + + QFrame::Raised + + + + + + <html><head/><body><p>The name of the tag where notes/lyrics unique to a track should be placed</p></body></html> + + + Tag for track notes / lyrics + + + + + + + background-color: rgb(250, 250, 250); + + + + @@ -1429,13 +1888,13 @@ - + - Tag mapping + Works and parts - + - + 0 @@ -1445,164 +1904,296 @@ true - + 0 0 - 1104 - 854 + 1084 + 1022 - + + + 0 + 0 + + + - - - - - - <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">N.B. Artist options (1st tab) need to be enabled for this section to run</span></p></body></html> + + + + 0 + 0 + - - - - + + + + 255 + 255 + 222 + + + 255 255 - 255 + 222 - 185 - 135 - 188 + 255 + 255 + 222 + + + + 255 + 255 + 222 + + + 255 255 - 255 + 222 - 185 - 135 - 188 + 255 + 255 + 222 + + + + 255 + 255 + 222 + + + - 185 - 135 - 188 + 255 + 255 + 222 - 185 - 135 - 188 + 255 + 255 + 222 - - <html><head/><body><p><br/></p></body></html> - - true + false - + background-color: rgb(255, 255, 222); + + + QFrame::StyledPanel - - Initial tag processing + + QFrame::Raised - + - - - - - - - - 255 - 255 - 255 - - - - - - - 205 - 230 - 255 - - - - - - - - - 255 - 255 - 255 - - - - - - - 205 - 230 - 255 - - - - - - - - - 205 - 230 - 255 - - - - + + + <html><head/><body><p>&quot;Include all work levels&quot; should be selected otherwise this section will not run.</p></body></html> + + + Include all work levels (MUST BE TICKED FOR THIS SECTION TO RUN)* + + + + + + + <html><head/><body><p>This will include parent works where the relationship has the attribute 'part of collection'.<br/>PLEASE BE CONSISTENT and do not use different options on albums with the same works, or the results may be unexpected.</p></body></html> + + + Include collection relationships (but not "series") + + + + + + + <html><head/><body><p>Select to use cached works. Deselect to refesh from MusicBrainz.</p></body></html> + + + <html><head/><body><p>&quot;Use cache&quot; prevents excessive look-ups of the MB database. Every look-up of a parent work needs to be performed separately (hopefully the MB database might make this easier some day). Network usage constraints by MB means that each look-up takes a minimum of 1 second. Once a release has been looked-up, the works are retained in cache, significantly reducing the time required if, say, the options are changed and the data refreshed. However, if the user edits the works in the MB database then the cache will need to be turned off temporarily for the refresh to find the new/changed works. Also some types of work (e.g. arrangements) will require a full look-up if options have been changed.</p></body></html> + + + Use cache (if available)* + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(205, 230, 255); + + + <html><head/><body><p><span style=" font-weight:600;">Tagging style</span></p></body></html> + + + + + + + + 0 + 0 + + + + + + + + + 225 + 240 + 255 + + + + + + + 225 + 240 + 255 + + + + + + + 225 + 240 + 255 + + + + + + + + + 225 + 240 + 255 + + + + + + + 225 + 240 + 255 + + + + + + + 225 + 240 + 255 + + + + + + + + + 225 + 240 + 255 + + + + + + + 225 + 240 + 255 + + + + - 205 - 230 + 225 + 240 255 @@ -1611,3579 +2202,3834 @@ - <html><head/><body><p>Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options-&gt;Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tagging style&quot;. This section determines how the hierarchy of works will be sourced. </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Works source</span>: There are 3 options for determing the principal source of the works metadata </p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided. </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below). </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles <span style=" font-weight:600;">where it is significantly different</span>. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Source of canonical work text</span>. Where either of the second two options above are chosen, there is a further choice to be made: </p> +<p style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. E.g. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose. </p> +<p style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag. </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Strategy for setting style:</span> <span style=" font-style:italic;">It is suggested that you start with &quot;extended/enhanced&quot; style and the &quot;Consistent with lowest level work description&quot; as the source (this is the default). If this does not give acceptable results, try switching to &quot;Full MusicBrainz work hierarchy&quot;. If the &quot;enhanced&quot; details in curly brackets (from the track title) give odd results then switch the style to &quot;canonical works&quot; only. Any remaining oddities are then probably in the MusicBrainz data, which may require editing.</span> </p></body></html> - true + false - + background-color: rgb(225, 240, 255); - Remove Picard-generated tags before applying subsequent actions? (NB existing LOCAL FILE tags will remain unless cleared using standard Picard options - to remove these, overwrite them in the next section) + - - - - - <html><head/><body><p><span style=" text-decoration: underline;">Picard-generated</span> tags to blank (comma-separated, case-sensitive):</p></body></html> - - - + - - - <html><head/><body><p>Enter file tag names, separated by commas</p></body></html> + + + <html><head/><body><p>There are 3 options for determing the principal source of the works metadata</p><p> - &quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.</p><p> - &quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will be in the language of the release.</p><p> - &quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles <span style=" font-weight:600;">where it is significantly different</span>. The supplementary data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager).</p></body></html> - - background-color: rgb(226, 237, 255); + + Works source + + + + + <html><head/><body><p>&quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.</p></body></html> + + + Use only metadata from title text + + + + + + + <html><head/><body><p>&quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).</p></body></html> + + + Use only metadata from canonical works + + + + + + + <html><head/><body><p>&quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles **where it is significantly different**. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations</p></body></html> + + + Use canonical work metadata enhanced with title text + + + + - - - <html><head/><body><p>Enter file tag names, separated by commas</p></body></html> + + + true - - background-color: rgb(226, 237, 255); + + <html><head/><body><p>Where either of the second two options above are chosen, there is a further choice to be made:</p><p> - &quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose.</p><p> - &quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag.</p></body></html> + + + Source of canonical work text (if applicable) + + + + + <html><head/><body><p>&quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. E.g. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose.</p></body></html> + + + Full MusicBrainz work hierarchy (may be more verbose) + + + + + + + <html><head/><body><p>&quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag.</p></body></html> + + + Consistent with lowest level work description (may be less verbose, but not always complete) + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - <html><head/><body><p><br/></p></body></html> - - - <html><head/><body><p>List <span style=" text-decoration: underline;">existing file tags</span> which will be appended to rather than over-written by tag mapping (NB this will keep tags even if &quot;Clear existing tags&quot; is selected on main options):-</p></body></html> - - - - - - - <html><head/><body><p>Enter file tag names, separated by commas</p></body></html> - - - <html><head/><body><p>This refers to the tags which already exist on files which have been matched to MusicBrainz in the right-hand panel, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if 'Clear existing tags' is specified in the main Options-&gt;Tags screen) or keep them (if 'Preserve these tags...' is specified after the 'Clear existing tags' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below). List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if &quot;Clear existing tags&quot; is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular genre-related tags (see the Genres etc. options tab for more).</p><p>Note that if &quot;Split lyrics tag&quot; is specified (see the Artists tab), then the tag named there will be included in the 'Keep file tags' list and does not need to be added in this section.</p></body></html> - + - background-color: rgb(248, 219, 255); - - - - - - - <html><head/><body><p>Note that the main Picard option &quot;Clear existing tags&quot; should be <span style=" text-decoration: underline;">unchecked</span> for this option to operate in preference to that Picard option. The difference is that this option <span style=" text-decoration: underline;">will not intefere with cover art</span>, whereas the main Picard option will remove previous cover art.</p></body></html> - - - <html><head/><body><p>If selected: the bottom pane of Picard will only show tags which have been generated from the MusicBrainz lookups plus any existing file tags which are listed above or in the main options &quot;Preserve tags...&quot;.</p><p>This does not mean that the file tags will be removed when saving the file. For that to happen, &quot;Clear existing tags&quot; needs to be selected in the main options.</p></body></html> + background-color: rgb(255, 186, 189); - Do not show any file tags that are NOT listed above AND NOT listed in the main Picard "Preserve tags..." option (Options->Tags), even if "Clear existing tags" is not selected. + <html><head/><body><p><span style=" font-weight:600;">Aliases</span> (NB Use a consistent approach throughout the library otherwise duplicate works may occur - see the Readme)*</p></body></html> - - - - - - - - 0 - 0 - - - - - - - - - 236 - 255 - 171 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 194 - 158 - - - - - - - - - 198 - 198 - 148 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 194 - 158 - - - - - - - - - 170 - 170 - 164 - - - - - - - 255 - 194 - 158 - - - - - - - 255 - 194 - 158 - - - - - - - - Qt::NoFocus - - - <html><head/><body><p>Enter tags, separated by commas.</p></body></html> - - - true - - - font: 75 8pt "MS Shell Dlg 2"; - - - Tag map details - - - + + + + + + + + 255 + 220 + 222 + + + + + + + 255 + 220 + 222 + + + + + + + 255 + 220 + 222 + + + + + + + + + 255 + 220 + 222 + + + + + + + 255 + 220 + 222 + + + + + + + 255 + 220 + 222 + + + + + + + + + 255 + 220 + 222 + + + + + + + 255 + 220 + 222 + + + + + + + 255 + 220 + 222 + + + + + + + + <html><head/><body><p>&quot;Replace work names by aliases&quot; will use <span style=" font-weight:600;">primary</span> aliases for the chosen locale instead of standard MusicBrainz work names. To choose the locale, use the drop-down under &quot;translate artist names&quot; in the main Picard Options--&gt;Metadata page. Note that this option is not saved as a file tag since, if different choices are made for different releases, different work names may be stored and therefore cannot be grouped together in your player/library manager. </p><p>The sub-options allow either the replacement of all work names, where a primary alias exists, just the replacement of work names which are in non-Latin script, or only replace those which are flagged with user &quot;Folksonomy&quot; tags. The tag text needs to be included in the text box, in which case flagged works will be 'aliased' as well as non-Latin script works, if the second sub-option is chosen. Note that the tags may either be anyone's tags (&quot;Look in all tags&quot;) or the user's own tags. If selecting &quot;Look in user's own tags only&quot; you <span style=" font-weight:600;">must</span> be logged in to your MusicBrainz user account (in the Picard Options-&gt;General page), otherwise repeated dialogue boxes may be generated and you may need to force restart Picard.</p></body></html> + + + false + - background-color: rgb(255, 232, 184); + background-color: rgb(255, 220, 222); - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:72; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; text-decoration: underline;">Notes: </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click &quot;Source from:&quot; button to edit source tags.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Any valid Picard-generated tag can be entered in the &quot;source&quot; box, as well as Classical Extras sources, and mapped into other tags - not just restricted to artists.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To put a constant in a tag, type it into the source box preceded by a backslash \.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">In all cases, the source will be APPENDED to the Picard tag. To replace the standard tag, first blank it in the section above - add it back later in the list below if required (e.g. artist -&gt; artist).</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">BUT note that any existing LOCAL FILE tag will be replaced by (not appended with) any Picard/Classical Extras tag UNLESS specified in the list box above.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">These tag-mapping options may be omitted from the over-riding of artist options - see advanced tab</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For more help seethe readme.</p></body></html> + + - - - - - - - 6 - - - QLayout::SetDefaultConstraint - - - 1 - - - 1 - - - 1 - - - 1 - + - - - Click to edit sources - - - - - - Source from: - - - true + + + Replace MB work names? + + + + + <html><head/><body><p>Use primary aliases for the chosen locale instead of standard MusicBrainz work names.</p></body></html> + + + Replace work names by aliases. Select method --> + + + + + + + Do not replace work names + + + + - - - false - - - - 0 - 0 - - - - false - - - Qt::StrongFocus - - - Click button to edit. See notes above. - + - background-color: rgb(255, 221, 116); - - - true + - - QComboBox::AdjustToContents - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: + + What to replace? + + + + + + + + + + + All work names + + + + + + + Non-latin work names + + + + + + + Only tagged works + + + + + + + + + + Tags ("Folksonomy") identifying works to be replaced by aliases + + + + + + Look in all tags + + + + + + + Look in user's own tags only (MUST BE LOGGED IN!) + + + + + + + <html><head/><body><p>Separate multiple tags by commas</p></body></html> + + + background-color: rgb(250, 250, 250); + + + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(255, 194, 158); + + + <html><head/><body><p><span style=" font-weight:600;">Tags to create</span> - Use commas to separate multiple tags or leave blank to omit</p></body></html> + + + + + + + + 0 + 0 + + + + + + + + + 255 + 221 + 201 + + + + + + + 255 + 221 + 201 + + + + + + + 255 + 221 + 201 + + + + + + + + + 255 + 221 + 201 + + + + + + + 255 + 221 + 201 + + + + + + + 255 + 221 + 201 + + + + + + + + + 255 + 221 + 201 + + + + + + + 255 + 221 + 201 + + + + + + + 255 + 221 + 201 + + + + + + + + <html><head/><body><p>Separate multiple tags by commas.</p></body></html> + + + <html><head/><body><p>&quot;Tags to create&quot; sets the names of the tags that will be created from the sources described above. All these tags will be blanked before filling as specified. Tags specified against more than one source will have later sources appended in the sequence specified, separated by separators as specified.</p></body></html> + + + false + + + background-color: rgb(255, 221, 201); + + + + + - - - Enter comma-separated list of tags + + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + + false - background-color: rgb(255, 221, 116); + background-color: rgb(255, 209, 182); + + Work tags + + + + + + Separator + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Some software (notably Muso) can display a 2-level work hierarchy as well as the work-movement hierarchy. This tag can be use to store the 2-level work name (a double colon :: is used to separate the levels within the tag).</p></body></html> + + + <html><head/><body><p>Tags for Work - for software with 2-level capability (e.g. Muso)</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + true + + + + + + + + + ; + + + + + : + + + + + . + + + + + , + + + + + - + + + + + + + + + + (In this format, intermediate works will be displayed after a double colon :: ) + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Software which can display a movement and work (but no higher levels) could use any tags specified here. Note that if there are multiple work levels, the intermediate levels will not be tagged. Users wanting all the information should use the tags from the previous option (but it may cause some breaks in the display if levels change) - alternatively the missing work levels can be included in a movement tag (see below).</p></body></html> + + + <html><head/><body><p>Tags for Work - for software with 1-level capability (e.g. iTunes)</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + true + + + + + + + + + ; + + + + + : + + + + + . + + + + + , + + + + + - + + + + + + + + + + (Intermediate works will not be displayed:- Either 1. use the 2-level format if you wish to display them, but note that this will ceate new work, or 2. include them in the movement [see below]) + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>This is the top-level work held in MB. This can be useful for cataloguing and searching (if the library software is capable). Note that this will always be the &quot;canonical&quot; MB name, not one derived from titles or the lowest level work name and that no annotations (e.g. key or work year) will be added. However, if &quot;replace work names by aliases&quot; has been selected and is applicable, the relevant alias will be used.</p></body></html> + + + <html><head/><body><p>Tags for top-level (canonical) work (for capable library managers)</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + N/A + + + + + + - - - Qt::RightToLeft + + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + 255 + 209 + 182 + + + + + + + + false - - Conditional? + + background-color: rgb(255, 209, 182); + + Movement/Part tags + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>This is not necessarily the embedded movt/part number, but is the sequence number of the movement within its parent work <span style=" font-weight:600;">on the current release</span>.</p></body></html> + + + <html><head/><body><p>Tags for(computed) movement number</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + true + + + + + + + + + ; + + + + + : + + + + + . + + + + + , + + + + + - + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Use different movement tags if required... + + + + + + + for use with multi-level work tags + + + + + + + for use with1-level work tags (intermediate works will prefix movement) + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>As below, but without the movement part/number prefix (if applicable)</p></body></html> + + + <html><head/><body><p>Tags for Movement - excluding embedded movt/part numbers</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + N/A + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>This tag(s) will contain the full lowest-level part name extracted from the lowest-level work name, according to the chosen tagging style.</p></body></html> + + + <html><head/><body><p>Tags for Movement - including embedded movt/part numbers</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + N/A + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + background-color: rgb(195, 168, 179); + + + <html><head/><body><p><span style=" font-weight:600;">Partial recordings, arrangements and medleys</span></p></body></html> + + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers + + + + + + + + 221 + 209 + 221 + + + + + + + 221 + 209 + 221 + + + + + + + 221 + 209 + 221 + + + + + + + + + 221 + 209 + 221 + + + + + + + 221 + 209 + 221 + + + + + + + 221 + 209 + 221 + + + + + + + + + 221 + 209 + 221 + + + + + + + 221 + 209 + 221 + + + + + + + 221 + 209 + 221 + + + + + + + + <html><head/><body><p>Enter text - do not use any quotation marks</p></body></html> + + + false + + + background-color: rgb(221, 209, 221); + + + + + + + + + font: 75 8pt "MS Shell Dlg 2"; +text-decoration: underline; - - - orchestrators + N.B. If these options are selected or deselected, quit and restart Picard before proceeding - - - - conductors + + + + + + <html><head/><body><p>If this option is selected, partial recordings will be treated as a sub-part of the whole recording and will have the related text included in its name. Note that this text is at the end of the canonical name, but the latter will probably be stripped from the sub-part as it duplicates the recording work name; any title text will be appended to the whole. Note that, if &quot;Consistent with lowest level work description&quot; is chosen in section 2, the text may be treated as a &quot;prefix&quot; similar to those in the &quot;Advanced&quot; tab. If this eliminates other similar prefixes and has unwanted effects, then either change the desired text slightly (e.g. surround with brackets) or use the &quot;Full MusicBrainz work hierarchy&quot; option in section 2.</p></body></html> - - - - chorusmasters + + Partial recordings - - - - leaders + + + + + Show partial recordings as separate sub-part, labelled with -> + + + + + + + false + + + background-color: rgb(250, 250, 250); + + + + + + + + + + <html><head/><body><p>If this option is selected, works which are arrangements of other works will have the latter treated in the same manner as &quot;parent&quot; works, except that the arrangement work name will be prefixed by the text provided.</p></body></html> - - - - support_performers + + Arrangements - - - - work_type + + + + + Show arrangements as parts of original works, labelled with -> + + + + + + + false + + + background-color: rgb(250, 250, 250); + + + + + + + + + + Medleys - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + + + <html><head/><body><p><span style=" font-weight:600;">Medleys</span></p><p> These can occur in two ways in MusicBrainz: (a) the recording is described as a &quot;medley of&quot; a number of works and (b) the track is described as (more than one) &quot;medley including a recording of&quot; a work. In the first case, the specified text will be included in brackets after the work name, whereas in the second case, the track will be treated as a recording of multiple works and the specified text will appear in the parent work name.</p><p><br/></p></body></html> + + + Include medley list, labelled with -> + + + + + + + false + + + background-color: rgb(250, 250, 250); + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + background-color: rgb(182, 182, 62); + + + <html><head/><body><p><span style=" font-weight:600;">SongKong-compatible tag usage</span></p></body></html> + + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - + + + + + + + + 227 + 227 + 143 + + + + + + + 227 + 227 + 143 + + + + + + + 227 + 227 + 143 + + + + + + + + + 227 + 227 + 143 + + + + + + + 227 + 227 + 143 + + + + + + + 227 + 227 + 143 + + + + + + + + + 227 + 227 + 143 + + + + + + + 227 + 227 + 143 + + + + + + + 227 + 227 + 143 + + + + + + + + <html><head/><body><p>See &quot;What's this&quot;</p></body></html> + + + <html><head/><body><p> &quot;Use work tags on file (no look up on MB) if Use Cache selected&quot;: This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, &quot;Use cache&quot; also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. **In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.**</p><p><br/></p><p> &quot;Write SongKong-compatible work tags&quot; does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data).</p><p><br/></p><p>Note that Picard and SongKong use the tag musicbrainz_workid to mean different things. If Picard has overwritten the SongKong tag (not a problem if this plugin is used) then a warning will be given and the works will be looked up on MusicBrainz. Also note that once a release is loaded, subsequent refreshes will use the cache (if option is ticked) in preference to the file tags.</p></body></html> + + + false + + + background-color: rgb(227, 227, 143); + + + + + + + - support_performers + Use work tags on file (no look up on MB) if Use Cache selected* (NOT RECOMMENDED - SEE README) - - + + + + - work_type + Write SongKong-compatible work tags* - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + + + + + + + + + + * ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS + + + + + + + + + + + + Genres etc. + + + + + + true + + + + + 0 + 0 + 1084 + 1310 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(138, 222, 187); + + + <html><head/><body><p><span style=" font-weight:600;">Genre tags</span></p></body></html> + + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + false + + + background-color: rgb(207, 236, 225); + + + + + + + - album_ensembles + Name of genre tag - - + + + + - album_composers + Name of sub-genre tag - - - - album_composer_lastnames + + + + + + background-color: rgb(0, 236, 173); - - - - soloists + + + + + + background-color: rgb(0, 236, 173); - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + background-color: rgb(138, 222, 187); + + + <html><head/><body><p><span style=" font-weight:600;">&quot;Classical&quot; genre </span></p></body></html> + + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + false + + + background-color: rgb(207, 236, 225); + + + + + + + - ensembles + Exclude the text "classical" from main genre tag even if listed above - - + + + + - ensemble_names + Make track "classical" only if there is a classical-specific genre (or do nothing if there is no filter) - - - - composers + + + + + + + 75 + true + - - - arrangers + Use Muso composer list to determine if classical* - - + + + + - orchestrators + Make all tracks "classical" - - + + + + - conductors + Write a flag with text = - - - - chorusmasters + + + + + + background-color: rgb(250, 250, 250); - - + + + + - leaders + in the following tag if the track is classical - - - - support_performers + + + + + + background-color: rgb(250, 250, 250); - - + + + + - work_type + (Treat arrangers as for composers) - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + + + + + + + + + * ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS + + + + + + + + 75 + true + + + + Use Muso reference database (default path is set on "advanced" tab)* + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + background-color: rgb(225, 168, 171); + + + <html><head/><body><p><span style=" font-weight:600;">Instruments and keys</span></p></body></html> + + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators + + + + + + + + 245 + 224 + 226 + + + + + + + 245 + 224 + 226 + + + + + + + 245 + 224 + 226 + + + + + + + + + 245 + 224 + 226 + + + + + + + 245 + 224 + 226 + + + + + + + 245 + 224 + 226 + + + + + + + + + 245 + 224 + 226 + + + + + + + 245 + 224 + 226 + + + + + + + 245 + 224 + 226 + + + + + + + + false + + + background-color: rgb(245, 224, 226); + + + + + + + + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + + false - - - - conductors + + background-color: rgb(245, 210, 213); - - - - chorusmasters + + Instruments - - - - leaders + + + + + Tag name for instruments (will hold all instruments for a track) + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + Name sources to use for instruments (select at least one, otherwise no instruments will be included in tag) + + + + + + MusicBrainz standard names + + + + + + + "Credited-as" names + + + + + + + + + + + + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + 245 + 210 + 213 + + + + + + + + false - - - - support_performers + + background-color: rgb(245, 210, 213); - - - - work_type + + Keys - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + + + Tag name for key(s) + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + Include key(s) in work name? + + + + + + Never + + + + + + + Only if key not already mentioned in work name + + + + + + + <html><head/><body><p>&quot;Include key(s) in work names&quot; gives the option to include the key signature for a work in brackets after the name of the work in the metadata. Keys will be added in the appropriate levels: e.g. Dvořák's New World Symphony will get (E minor) at the work level, but only movements with different keys will be annotated viz. &quot;II. Largo (D-flat major, C-Sharp minor)&quot;</p></body></html> + + + Always + + + + + + + + + + + - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(138, 222, 187); + + + <html><head/><body><p><span style=" font-weight:600;">Allowed genres (filter)</span></p></body></html> + + + + + + + Qt::LeftToRight + + + background-color: rgb(138, 222, 187); + + + Only apply genres to tags if they match pre-defined names: + + + + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Explanation of genre-matching:</span></p><p><br/></p><p>Only genres matching those in the boxes will be placed in the genre or sub-genre tags.</p><p><br/></p><p>If there is a matching genre found in the &quot;classical main genres&quot; or &quot;classical sub-genres&quot; box, then the track will be treated as being classical.</p></body></html> + + + false + + + background-color: rgb(207, 236, 225); + + + + + + + + + background-color: rgb(190, 236, 219); - - - - album_composers + + Classical genres (i.e. specific to classical music) - List separated by commas - - - - album_composer_lastnames + + + + + Main genres: + + + + + + + + 16777215 + 50 + + + + background-color: rgb(156, 250, 211); + + + + + + + Sub-genres: + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + background-color: rgb(156, 250, 211); + + + + + + + + 75 + true + + + + <html><head/><body><p>Select this to use the &quot;classical genres&quot; in Muso options as the &quot;Main classical genres&quot; here.</p></body></html> + + + Use Muso classical genres* + + + + + + + + + + background-color: rgb(190, 236, 219); - - - - soloists + + General genres (may be associated with classical music, but not necessarily, e.g. "instrumental") - List separated by commas - - + + + + + Main genres + + + + + + + + 16777215 + 50 + + + + background-color: rgb(156, 250, 211); + + + + + + + Sub-genres + + + + + + + + 16777215 + 50 + + + + background-color: rgb(156, 250, 211); + + + + + cwp_genres_other_main + cwp_genres_other_sub + label_62 + label_76 + + + + - soloist_names + Genre name to use if none of the above main genres apply (leave blank if not required) - - - - ensembles + + + + + + background-color: rgb(156, 250, 211); - - + + + + - ensemble_names + List genres, separated by commas. Only those genres listed will be included in tags. - - + + + + - composers + See "what's this" for more details. - - - - arrangers - - - - - orchestrators + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(138, 222, 187); + + + <html><head/><body><p><span style=" font-weight:600;">Source of genres</span> - Note: if &quot;existing file tag&quot; is selected, information from the tag &quot;genre&quot; and the genre tag name specified above (if different) will be used</p></body></html> + + + + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + 207 + 236 + 225 + + + + + + + + false + + + background-color: rgb(207, 236, 225); + + + + + + + + + <html><head/><body><p>NB: This will use the contents of the file tag with the name given above (usually 'genre').</p></body></html> - - - conductors + Existing file tag (see note above) - - - - chorusmasters + + + + + + <html><head/><body><p>This will use the folksonomy tags for <span style=" font-weight:600;">works</span> as a possible source of genres (if they match one of the lists below).</p><p><br/></p><p>To use the folksonomy tags for <span style=" font-weight:600;">releases/tracks</span>, select the main Picard option in Options-&gt;Metadata-&gt;&quot;Use folksonomy tags as genre&quot;. Again (unlike vanilla Picard) they will only be used by this plugin if they match one of the lists below.</p></body></html> - - - leaders + Folksonomy work tags - - + + + + - support_performers + Work-type - - + + + + - work_type + Infer from artist metadata - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - + + + background-color: rgb(195, 183, 213); + + + <html><head/><body><p><span style=" font-weight:600;">Periods and dates</span></p></body></html> + + + + + + + + + + + + 229 + 224 + 236 + + + + + + + 229 + 224 + 236 + + + + + + + 229 + 224 + 236 + + + + + + + + + 229 + 224 + 236 + + + + + + + 229 + 224 + 236 + + + + + + + 229 + 224 + 236 + + + + + + + + + 229 + 224 + 236 + + + + + + + 229 + 224 + 236 + + + + + + + 229 + 224 + 236 + + + + + + + + false + + + background-color: rgb(229, 224, 236); + + + + + + + + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + + false - - - - album_soloists, album_conductors, album_ensembles + + background-color: rgb(222, 212, 236); - - - - soloists, conductors, ensembles, album_composers, composers + + Work dates - - - - album_soloists + + + + + Source of work dates for above tag + + + + + + Composed date (or parent composed date) + + + + + + + Published date + + + + + + + Premiered date + + + + + + + Qt::Horizontal + + + + + + + Use first available of above (in listed order) + + + + + + + Include all sources + + + + + + + Annotate dates using source name + + + + + + + + + + Tag name for work date + + + + + + + <html><head/><body><p>&quot;Includeworkdate in work names&quot; gives the option to include the 'work year' for a work in brackets after the name of the work in the metadata. Dates (years) will be added in the appropriate levels: e.g. Smetana's 'Má vlast' will get (1874-1879) at the work level, but the movements with different dates will be annotated viz. &quot;Vyšehrad, JB 1:112/1 (1874)&quot;. If the dates are the same, there should be no repitetion at the movement level. (Work dates will be used in preference order, i.e. composed - published - premiered, with only the first available date being shown).</p></body></html> + + + Include workdate in work name (in preference order listed above, with no annotation) + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + + + + 0 - - - - album_conductors + + Qt::Horizontal - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders - - - - - support_performers - - - - - work_type - - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Click to edit sources - - - - - - Source from: - - - true - - - - - - - false - - - Click button to edit. See notes above. - - - background-color: rgb(255, 221, 116); - - - true - - - - - - - - - album_soloists, album_conductors, album_ensembles - - - - - soloists, conductors, ensembles, album_composers, composers - - - - - album_soloists - - - - - album_conductors - - - - - album_ensembles - - - - - album_composers - - - - - album_composer_lastnames - - - - - soloists - - - - - soloist_names - - - - - ensembles - - - - - ensemble_names - - - - - composers - - - - - arrangers - - - - - orchestrators - - - - - conductors - - - - - chorusmasters - - - - - leaders + + + + + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + 222 + 212 + 236 + + + + + + + + false - - - - support_performers + + background-color: rgb(222, 212, 236); - - - - work_type + + Periods - - - - release - - - - - - - - into tags: - - - - - - - Enter comma-separated list of tags - - - background-color: rgb(255, 221, 116); - - - - - - - Qt::RightToLeft - - - Conditional? - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - 1 - - - (If source is empty, tag will be left unchanged) - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - (Conditional tags will only be filled if previously empty) - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - <html><head/><body><p>Select to include sort-tags, where available. See<span style=" font-style:italic;"> &quot;What's this?&quot;</span> for more details.</p></body></html> - - - <html><head/><body><p>If a sort tag is associated with the source tag then the sort names will be placed in a sort tag corresponding to the destination tag. Note that the only explicit sort tags written by Picard are for artist, albumartist and composer. Piacrd also writes hidden variables '_artists_sort' and 'albumartists_sort' (note the plurals - these are the sort tags for multi-valued alternatives 'artists' and '_albumartists'). To be consistent with this approach, the plugin writes hidden variables for other tags - e.g. '_arranger_sort'. The plugin also writes hidden sort variables for the various hidden artist variables - e.g. '_cwp_librettists' has a matching sort variable '_cwp_librettists_sort'. Therefore most artist-type sources <span style=" font-weight:600;">will</span> have a sort tag/variable associated with them and these will be placed in a destination sort tag if this option is selected -<span style=" font-weight:600;"> in other words, selecting this option will cause most destination tags to have associated sort tags</span>.<span style=" font-weight:600;"> Furthermore, any hidden sort variables associated with tags which are not listed explicitly in the tag mapping section will also be written out as tags</span> (i.e. even if the related tags are not included as destination tags). Note, however, that composite sources (e.g. &quot; ensemble_names + \; + conductors&quot;) do not have sort tags associated with them.</p><p><br/></p><p>If this option is not selected, no additional sort tags will be written, but the hidden variables will still be available, so if a sort tag is required explicitly, just map the sort tag directly - e.g. map 'conductors_sort' to 'conductor_sort'.</p></body></html> - - - Qt::LeftToRight - - - Also populate sort tags - + + + + + Tag name for period + + + + + + + + 75 + true + + + + Use Muso map* + + + + + + + + 75 + true + + + + Use Muso composer dates (if no work date) to determine period* + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + Period map: + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + (Period name, Start year, End year; Period name2, ... etc.) - periods may overlap [Do not use commas or semi-colons within period name] + + + + + + + (Treat arrangers as for composers) + + + + + + + + + + + <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">N.B. At least one of the first two tabs (Artists: &quot;Create extra artist metadata&quot;, or Works and parts: &quot;Include all work levels&quot;) </span><span style=" font-size:9pt; font-weight:600; text-decoration: underline;">must</span><span style=" font-size:9pt; font-weight:600;"> be enabled for this section to run.<br/></span><span style=" font-weight:600; font-style:italic;">(Functionality will be reduced unless both the first two tabs are enabled.) </span></p></body></html> + + + - + - Works and parts + Tag mapping - + - + 0 0 + + Qt::ScrollBarAsNeeded + true - + 0 0 - 1087 - 1012 + 1084 + 885 - - - 0 - 0 - - - + - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 236 - 255 - 171 - - - - - - - - - 255 - 255 - 255 - - - - - - - 236 - 255 - 171 - - - - - - - - - 236 - 255 - 171 - - - - - - - 236 - 255 - 171 - - - - - - - - true - + + + <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">N.B. At least one of the first two tabs (Artists: &quot;Create extra artist metadata&quot;, or Works and parts: &quot;Include all work levels&quot;) </span><span style=" font-size:9pt; font-weight:600; text-decoration: underline;">must</span><span style=" font-size:9pt; font-weight:600;"> be enabled for this section to run. </span></p></body></html> + + + + + QFrame::StyledPanel QFrame::Raised - - - - - <html><head/><body><p>&quot;Include all work levels&quot; should be selected otherwise this section will not run.</p></body></html> - - - Include all work levels (MUST BE TICKED FOR THIS SECTION TO RUN)* - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - <html><head/><body><p>This will include parent works where the relationship has the attribute 'part of collection'.<br/>PLEASE BE CONSISTENT and do not use different options on albums with the same works, or the results may be unexpected.</p></body></html> + + + background-color: rgb(209, 171, 222); + - Include collection relationships (but not "series") + <html><head/><body><p><span style=" font-weight:600;">Initial tag processing</span></p></body></html> - - - <html><head/><body><p>Select to use cached works. Deselect to refesh from MusicBrainz.</p></body></html> + + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + - <html><head/><body><p>&quot;Use cache&quot; prevents excessive look-ups of the MB database. Every look-up of a parent work needs to be performed separately (hopefully the MB database might make this easier some day). Network usage constraints by MB means that each look-up takes a minimum of 1 second. Once a release has been looked-up, the works are retained in cache, significantly reducing the time required if, say, the options are changed and the data refreshed. However, if the user edits the works in the MB database then the cache will need to be turned off temporarily for the refresh to find the new/changed works. Also some types of work (e.g. arrangements) will require a full look-up if options have been changed.</p></body></html> + <html><head/><body><p><br/></p></body></html> - - Use cache (if available)* + + false - - - - - - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 205 - 230 - 255 - - - - - - - - - 255 - 255 - 255 - - - - - - - 205 - 230 - 255 - - - - - - - - - 205 - 230 - 255 - - - - - - - 205 - 230 - 255 - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tagging style&quot;. This section determines how the hierarchy of works will be sourced. </p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Works source</span>: There are 3 options for determing the principal source of the works metadata </p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided. </p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below). </p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles <span style=" font-weight:600;">where it is significantly different</span>. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Source of canonical work text</span>. Where either of the second two options above are chosen, there is a further choice to be made: </p> -<p style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. E.g. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose. </p> -<p style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag. </p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Strategy for setting style:</span> <span style=" font-style:italic;">It is suggested that you start with &quot;extended/enhanced&quot; style and the &quot;Consistent with lowest level work description&quot; as the source (this is the default). If this does not give acceptable results, try switching to &quot;Full MusicBrainz work hierarchy&quot;. If the &quot;enhanced&quot; details in curly brackets (from the track title) give odd results then switch the style to &quot;canonical works&quot; only. Any remaining oddities are then probably in the MusicBrainz data, which may require editing.</span> </p></body></html> - - - true - - - - - - Tagging style - - - - - - <html><head/><body><p>There are 3 options for determing the principal source of the works metadata</p><p> - &quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.</p><p> - &quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will be in the language of the release.</p><p> - &quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles <span style=" font-weight:600;">where it is significantly different</span>. The supplementary data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager).</p></body></html> + + background-color: rgb(242, 221, 245); - Works source + - + - + + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + + 242 + 221 + 245 + + + + + + - <html><head/><body><p>&quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.</p></body></html> + <html><head/><body><p>Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options-&gt;Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive.</p></body></html> - - Use only metadata from title text + + false - - - - - - <html><head/><body><p>&quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).</p></body></html> + + - - Use only metadata from canonical works + + Remove Picard-generated tags before applying subsequent actions? (NB existing LOCAL FILE tags will remain unless cleared using standard Picard options - to remove these, overwrite them in the next section) - + + + + + <html><head/><body><p><span style=" text-decoration: underline;">Picard-generated</span> tags to blank (comma-separated, case-sensitive):</p></body></html> + + + + + + + <html><head/><body><p>Enter file tag names, separated by commas</p></body></html> + + + background-color: rgb(250, 250, 250); + + + + + + + <html><head/><body><p>Enter file tag names, separated by commas</p></body></html> + + + background-color: rgb(250, 250, 250); + + + + + - + - <html><head/><body><p>&quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles **where it is significantly different**. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations</p></body></html> + <html><head/><body><p><br/></p></body></html> - Use canonical work metadata enhanced with title text + <html><head/><body><p>List <span style=" text-decoration: underline;">existing file tags</span> which will be appended to rather than over-written by tag mapping (this will keep tags even if &quot;Clear existing tags&quot; is selected on main options)<br/>NB To allow appending to happen, <span style=" text-decoration: underline;">do not also include these tags in &quot;Preserve tags&quot;</span> on the main options.</p></body></html> - - - - - - - true - - - <html><head/><body><p>Where either of the second two options above are chosen, there is a further choice to be made:</p><p> - &quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose.</p><p> - &quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag.</p></body></html> - - - Source of canonical work text (if applicable) - - - + + + <html><head/><body><p>Enter file tag names, separated by commas</p></body></html> + - <html><head/><body><p>&quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. E.g. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose.</p></body></html> + <html><head/><body><p>This refers to the tags which already exist on files which have been matched to MusicBrainz in the right-hand panel, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if 'Clear existing tags' is specified in the main Options-&gt;Tags screen) or keep them (if 'Preserve these tags...' is specified after the 'Clear existing tags' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below). List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if &quot;Clear existing tags&quot; is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular genre-related tags (see the Genres etc. options tab for more).</p><p>Note that if &quot;Split lyrics tag&quot; is specified (see the Artists tab), then the tag named there will be included in the 'Keep file tags' list and does not need to be added in this section.</p></body></html> - - Full MusicBrainz work hierarchy (may be more verbose) + + background-color: rgb(250, 250, 250); - + + + <html><head/><body><p>Note that the main Picard option &quot;Clear existing tags&quot; should be <span style=" text-decoration: underline;">unchecked</span> for this option to operate in preference to that Picard option. The difference is that this option <span style=" text-decoration: underline;">will not intefere with cover art</span>, whereas the main Picard option will remove previous cover art.</p></body></html> + - <html><head/><body><p>&quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag.</p></body></html> + <html><head/><body><p>If selected: the bottom pane of Picard will only show tags which have been generated from the MusicBrainz lookups plus any existing file tags which are listed above or in the main options &quot;Preserve tags...&quot;.</p><p>This does not mean that the file tags will be removed when saving the file. For that to happen, &quot;Clear existing tags&quot; needs to be selected in the main options.</p></body></html> - Consistent with lowest level work description (may be less verbose, but not always complete) + Do not show any file tags that are NOT listed above AND NOT listed in the main Picard "Preserve tags..." option (Options->Tags), even if "Clear existing tags" is not selected. @@ -5194,287 +6040,65 @@ p, li { white-space: pre-wrap; } - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 161 - 163 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 161 - 163 - - - - - - - - - 255 - 161 - 163 - - - - - - - 255 - 161 - 163 - - - - - - - - <html><head/><body><p>&quot;Replace work names by aliases&quot; will use <span style=" font-weight:600;">primary</span> aliases for the chosen locale instead of standard MusicBrainz work names. To choose the locale, use the drop-down under &quot;translate artist names&quot; in the main Picard Options--&gt;Metadata page. Note that this option is not saved as a file tag since, if different choices are made for different releases, different work names may be stored and therefore cannot be grouped together in your player/library manager. </p><p>The sub-options allow either the replacement of all work names, where a primary alias exists, just the replacement of work names which are in non-Latin script, or only replace those which are flagged with user &quot;Folksonomy&quot; tags. The tag text needs to be included in the text box, in which case flagged works will be 'aliased' as well as non-Latin script works, if the second sub-option is chosen. Note that the tags may either be anyone's tags (&quot;Look in all tags&quot;) or the user's own tags. If selecting &quot;Look in user's own tags only&quot; you <span style=" font-weight:600;">must</span> be logged in to your MusicBrainz user account (in the Picard Options-&gt;General page), otherwise repeated dialogue boxes may be generated and you may need to force restart Picard.</p></body></html> - - - true - - - + + + QFrame::StyledPanel - - Aliases (NB Use a consistent approach throughout the library otherwise duplicate works may occur - see the Readme)* + + QFrame::Raised - - - - - Replace MB work names? - - - - - - <html><head/><body><p>Use primary aliases for the chosen locale instead of standard MusicBrainz work names.</p></body></html> - - - Replace work names by aliases. Select method --> - - - - - - - Do not replace work names - - - - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - + - + background-color: rgb(229, 174, 142); - - What to replace? + + <html><head/><body><p><span style=" font-weight:600;">Tag map details</span></p></body></html> - - - - - - - - - - - All work names - - - - - - - Non-latin work names - - - - - - - Only tagged works - - - - - - - - - - Tags ("Folksonomy") identifying works to be replaced by aliases - - - - - - Look in all tags - - - - - - - Look in user's own tags only (MUST BE LOGGED IN!) - - - - - - - <html><head/><body><p>Separate multiple tags by commas</p></body></html> - - - background-color: rgb(255, 206, 207); - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 194 - 158 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 194 - 158 - - - - - - - - - 255 - 194 - 158 - - - - - - - 255 - 194 - 158 - - - - - - - - <html><head/><body><p>Separate multiple tags by commas.</p></body></html> - - - <html><head/><body><p>&quot;Tags to create&quot; sets the names of the tags that will be created from the sources described above. All these tags will be blanked before filling as specified. Tags specified against more than one source will have later sources appended in the sequence specified, separated by separators as specified.</p></body></html> - - - true - - - - - - Tags to create - Use commas to separate multiple tags or leave blank to omit - - - + + + + 0 + 0 + + + + + + 255 + 229 + 214 + + + 255 - 255 - 255 + 229 + 214 @@ -5482,19 +6106,28 @@ p, li { white-space: pre-wrap; } 255 - 162 - 116 + 229 + 214 + + + + 255 + 229 + 214 + + + 255 - 255 - 255 + 229 + 214 @@ -5502,19 +6135,28 @@ p, li { white-space: pre-wrap; } 255 - 162 - 116 + 229 + 214 + + + + 255 + 229 + 214 + + + 255 - 162 - 116 + 229 + 214 @@ -5522,36 +6164,278 @@ p, li { white-space: pre-wrap; } 255 - 162 - 116 + 229 + 214 + + Qt::NoFocus + + + <html><head/><body><p>Enter tags, separated by commas.</p></body></html> + - true + false - + font: 75 8pt "MS Shell Dlg 2"; +background-color: rgb(255, 229, 214); - Work tags + - - - - - Separator + + + + + + 0 + 0 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 16777215 + 100 + + + + background-color: rgb(249, 215, 187); + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:72; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; text-decoration: underline;">Notes: </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click &quot;Source from:&quot; button to edit source tags.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Any valid Picard-generated tag can be entered in the &quot;source&quot; box, as well as Classical Extras sources, and mapped into other tags - not just restricted to artists.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To put a constant in a tag, type it into the source box preceded by a backslash \.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">In all cases, the source will be APPENDED to the Picard tag. To replace the standard tag, first blank it in the section above - add it back later in the list below if required (e.g. artist -&gt; artist).</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">BUT note that any existing LOCAL FILE tag will be replaced by (not appended with) any Picard/Classical Extras tag UNLESS specified in the list box above.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">These tag-mapping options may be omitted from the over-riding of artist options - see advanced tab</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For more help seethe readme.</p></body></html> - - + + + + + 6 + + + QLayout::SetDefaultConstraint + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Click to edit sources + + + false + + + + + + Source from: + + + true + + + Qt::ToolButtonIconOnly + + + false + + + + + + + false + + + + 0 + 0 + + + + false + + + Qt::StrongFocus + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + QComboBox::AdjustToContents + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + + + + + 6 @@ -5568,30 +6452,32 @@ p, li { white-space: pre-wrap; } 0 - - - - 0 - 0 - + + + Click to edit sources - - <html><head/><body><p>Some software (notably Muso) can display a 2-level work hierarchy as well as the work-movement hierarchy. This tag can be use to store the 2-level work name (a double colon :: is used to separate the levels within the tag).</p></body></html> + + - <html><head/><body><p>Tags for Work - for software with 2-level capability (e.g. Muso)</p></body></html> + Source from: + + + true - + + + false + + + Click button to edit. See notes above. + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); - - - - true @@ -5602,42 +6488,137 @@ p, li { white-space: pre-wrap; } - ; + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters - : + leaders - . + support_performers - , + work_type - - + release + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + - - - - (In this format, intermediate works will be displayed after a double colon :: ) - - - - - + + 6 @@ -5654,30 +6635,32 @@ p, li { white-space: pre-wrap; } 0 - - - - 0 - 0 - + + + Click to edit sources - - <html><head/><body><p>Software which can display a movement and work (but no higher levels) could use any tags specified here. Note that if there are multiple work levels, the intermediate levels will not be tagged. Users wanting all the information should use the tags from the previous option (but it may cause some breaks in the display if levels change) - alternatively the missing work levels can be included in a movement tag (see below).</p></body></html> + + - <html><head/><body><p>Tags for Work - for software with 1-level capability (e.g. iTunes)</p></body></html> + Source from: + + + true - + + + false + + + Click button to edit. See notes above. + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); - - - - true @@ -5688,170 +6671,137 @@ p, li { white-space: pre-wrap; } - ; + album_soloists, album_conductors, album_ensembles - : + soloists, conductors, ensembles, album_composers, composers - . + album_soloists - , + album_conductors - - + album_ensembles - - - - - - - - (Intermediate works will not be displayed:- Either 1. use the 2-level format if you wish to display them, but note that this will ceate new work, or 2. include them in the movement [see below]) - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + - - - - 0 - 0 - - - - <html><head/><body><p>This is the top-level work held in MB. This can be useful for cataloguing and searching (if the library software is capable). Note that this will always be the &quot;canonical&quot; MB name, not one derived from titles or the lowest level work name and that no annotations (e.g. key or work year) will be added. However, if &quot;replace work names by aliases&quot; has been selected and is applicable, the relevant alias will be used.</p></body></html> - + - <html><head/><body><p>Tags for top-level (canonical) work (for capable library managers)</p></body></html> + into tags: - + + + Enter comma-separated list of tags + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); - + + + Qt::RightToLeft + - N/A + Conditional? - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 162 - 116 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 162 - 116 - - - - - - - - - 255 - 162 - 116 - - - - - - - 255 - 162 - 116 - - - - - - - - true - - - - - - Movement/Part tags - - - - + + 6 @@ -5868,30 +6818,32 @@ p, li { white-space: pre-wrap; } 0 - - - - 0 - 0 - + + + Click to edit sources - - <html><head/><body><p>This is not necessarily the embedded movt/part number, but is the sequence number of the movement within its parent work <span style=" font-weight:600;">on the current release</span>.</p></body></html> + + - <html><head/><body><p>Tags for(computed) movement number</p></body></html> + Source from: + + + true - + + + false + + + Click button to edit. See notes above. + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); - - - - true @@ -5902,68 +6854,137 @@ p, li { white-space: pre-wrap; } - ; + album_soloists, album_conductors, album_ensembles - : + soloists, conductors, ensembles, album_composers, composers - . + album_soloists - , + album_conductors - - + album_ensembles - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - + - Use different movement tags if required... + album_composers - - - - + + - for use with multi-level work tags + album_composer_lastnames - - - - + + - for use with1-level work tags (intermediate works will prefix movement) + soloists - - - - + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - + + 6 @@ -5980,46 +7001,173 @@ p, li { white-space: pre-wrap; } 0 - - - - 0 - 0 - + + + Click to edit sources - - <html><head/><body><p>As below, but without the movement part/number prefix (if applicable)</p></body></html> + + - <html><head/><body><p>Tags for Movement - excluding embedded movt/part numbers</p></body></html> + Source from: + + + true - + + + false + + + Click button to edit. See notes above. + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: - + + + Enter comma-separated list of tags + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); - + + + Qt::RightToLeft + - N/A + Conditional? - - + + 6 @@ -6036,1694 +7184,2075 @@ p, li { white-space: pre-wrap; } 0 - - - - 0 - 0 - + + + Click to edit sources - - <html><head/><body><p>This tag(s) will contain the full lowest-level part name extracted from the lowest-level work name, according to the chosen tagging style.</p></body></html> + + - <html><head/><body><p>Tags for Movement - including embedded movt/part numbers</p></body></html> + Source from: + + + true - + + + false + + + Click button to edit. See notes above. + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: - + + + Enter comma-separated list of tags + - background-color: rgb(255, 221, 116); + background-color: rgb(250, 250, 250); - + + + Qt::RightToLeft + - N/A + Conditional? - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 195 - 168 - 179 - - - - - - - - - 255 - 255 - 255 - - - - - - - 195 - 168 - 179 - - - - - - - - - 195 - 168 - 179 - - - - - - - 195 - 168 - 179 - - - - - - - - <html><head/><body><p>Enter text - do not use any quotation marks</p></body></html> - - - true - - - - - - Partial recordings, arrangements and medleys - - - - - - font: 75 8pt "MS Shell Dlg 2"; -text-decoration: underline; - - - N.B. If these options are selected or deselected, quit and restart Picard before proceeding - - - - - - - <html><head/><body><p>If this option is selected, partial recordings will be treated as a sub-part of the whole recording and will have the related text included in its name. Note that this text is at the end of the canonical name, but the latter will probably be stripped from the sub-part as it duplicates the recording work name; any title text will be appended to the whole. Note that, if &quot;Consistent with lowest level work description&quot; is chosen in section 2, the text may be treated as a &quot;prefix&quot; similar to those in the &quot;Advanced&quot; tab. If this eliminates other similar prefixes and has unwanted effects, then either change the desired text slightly (e.g. surround with brackets) or use the &quot;Full MusicBrainz work hierarchy&quot; option in section 2.</p></body></html> - - - Partial recordings - - - - - - Show partial recordings as separate sub-part, labelled with -> - - - - - - - false + + + + 6 - - background-color: rgb(225, 225, 169); + + 0 - - - - - - - - - <html><head/><body><p>If this option is selected, works which are arrangements of other works will have the latter treated in the same manner as &quot;parent&quot; works, except that the arrangement work name will be prefixed by the text provided.</p></body></html> - - - Arrangements - - - - - - Show arrangements as parts of original works, labelled with -> + + 0 - - - - - - false + + 0 - - background-color: rgb(225, 225, 169); + + 0 - + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - - - - Medleys - - - - - - <html><head/><body><p><span style=" font-weight:600;">Medleys</span></p><p> These can occur in two ways in MusicBrainz: (a) the recording is described as a &quot;medley of&quot; a number of works and (b) the track is described as (more than one) &quot;medley including a recording of&quot; a work. In the first case, the specified text will be included in brackets after the work name, whereas in the second case, the track will be treated as a recording of multiple works and the specified text will appear in the parent work name.</p><p><br/></p></body></html> + + + + 6 - - Include medley list, labelled with -> + + 0 - - - - - - false + + 0 - - background-color: rgb(225, 225, 169); + + 0 - - - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 0 - - - - - - - - - 170 - 170 - 0 - - - - - - - 170 - 170 - 0 - - - - - - - - <html><head/><body><p>See &quot;What's this&quot;</p></body></html> - - - <html><head/><body><p> &quot;Use work tags on file (no look up on MB) if Use Cache selected&quot;: This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, &quot;Use cache&quot; also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. **In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.**</p><p><br/></p><p> &quot;Write SongKong-compatible work tags&quot; does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data).</p><p><br/></p><p>Note that Picard and SongKong use the tag musicbrainz_workid to mean different things. If Picard has overwritten the SongKong tag (not a problem if this plugin is used) then a warning will be given and the works will be looked up on MusicBrainz. Also note that once a release is loaded, subsequent refreshes will use the cache (if option is ticked) in preference to the file tags.</p></body></html> - - - true - - - - - - SongKong-compatible tag usage - - - - - - Use work tags on file (no look up on MB) if Use Cache selected* (NOT RECOMMENDED - SEE README) - - - - - - - Write SongKong-compatible work tags* - - - - - - - - - - * ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS - - - - - - - - - - - - Genres etc. - - - - - - true - - - - - 0 - 0 - 1087 - 1248 - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 185 - 135 - 188 - - - - - - - - - 255 - 255 - 255 - - - - - - - 185 - 135 - 188 - - - - - - - - - 185 - 135 - 188 - - - - - - - 185 - 135 - 188 - - - - - - - - true - - - - - - Periods and dates - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 195 - 168 - 179 - - - - - - - - - 255 - 255 - 255 - - - - - - - 195 - 168 - 179 - - - - - - - - - 195 - 168 - 179 - - - - - - - 195 - 168 - 179 - - - - - - - - true - - - - - - Work dates - - - - - - Source of work dates for above tag + + 0 - - - + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + - Composed date (or parent composed date) + - - - - + + - Published date + album_soloists, album_conductors, album_ensembles - - - - + + - Premiered date + soloists, conductors, ensembles, album_composers, composers - - - - - - Qt::Horizontal + + + + album_soloists - - - - + + - Use first available of above (in listed order) + album_conductors - - - - + + - Include all sources + album_ensembles - - - - + + - Annotate dates using source name + album_composers - - - - - - - - - Tag name for work date - - + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - <html><head/><body><p>&quot;Includeworkdate in work names&quot; gives the option to include the 'work year' for a work in brackets after the name of the work in the metadata. Dates (years) will be added in the appropriate levels: e.g. Smetana's 'Má vlast' will get (1874-1879) at the work level, but the movements with different dates will be annotated viz. &quot;Vyšehrad, JB 1:112/1 (1874)&quot;. If the dates are the same, there should be no repitetion at the movement level. (Work dates will be used in preference order, i.e. composed - published - premiered, with only the first available date being shown).</p></body></html> + + + + 6 - - Include workdate in work name (in preference order listed above, with no annotation) + + 0 - - - - - - background-color: rgb(183, 174, 225); + + 0 - - - - - - - - - 0 - - - Qt::Horizontal - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 195 - 168 - 179 - - - - - - - - - 255 - 255 - 255 - - - - - - - 195 - 168 - 179 - - - - - - - - - 195 - 168 - 179 - - - - - - - 195 - 168 - 179 - - - - - - - - true - - - - - - Periods - - - - - - Tag name for period + + 0 - + + 0 + + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - - 75 - true - + + + + 6 + + + 0 + + + 0 + + + 0 - - Use Muso map* + + 0 - + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - - 75 - true - + + + + 6 - - Use Muso composer dates (if no work date) to determine period* + + 0 - - - - - - background-color: rgb(183, 174, 225); + + 0 - + + 0 + + + 0 + + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - Period map: + + + + 6 - - - - - - background-color: rgb(183, 174, 225); + + 0 - - - - - - (Period name, Start year, End year; Period name2, ... etc.) - periods may overlap [Do not use commas or semi-colons within period name] + + 0 - - - - - - (Treat arrangers as for composers) + + 0 - - - - - - - - - - - - - 75 - true - - - - Use Muso reference database (default path is set on "advanced" tab)* - - - - - - - * ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 19 - 186 - 161 - - - - - - - - - 255 - 255 - 255 - - - - - - - 19 - 186 - 161 - - - - - - - - - 19 - 186 - 161 - - - - - - - 19 - 186 - 161 - - - - - - - - true - - - - - - Genres - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 138 - 222 - 187 - - - - - - - - - 255 - 255 - 255 - - - - - - - 138 - 222 - 187 - - - - - - - - - 138 - 222 - 187 - - - - - - - 138 - 222 - 187 - - - - - - - - true - - - - - - "Classical" genre - - - - - - Exclude "classical" from main genre tag even if listed above + + 0 - + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - Make track "classical" only if there is a classical-specific genre + + + + 6 - - - - - - - 75 - true - + + 0 - - Use Muso composer list to determine if classical* + + 0 - - - - - - Make all tracks "classical" + + 0 - + + 0 + + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - Write a flag with text = + + + + 6 - + + 0 + + + 0 + + + 0 + + + 0 + + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + + + arrangers + + + + + orchestrators + + + + + conductors + + + + + chorusmasters + + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - background-color: rgb(156, 250, 211); + + + + 6 - - - - - - in the following tag if the track is classical + + 0 - - - - - - background-color: rgb(156, 250, 211); + + 0 - - - - - - (Treat arrangers as for composers) + + 0 - - - - - - - - - Name of genre tag - - - - - - - background-color: rgb(0, 236, 173); - - - - - - - background-color: rgb(0, 236, 173); - - - - - - - Name of sub-genre tag - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 138 - 222 - 187 - - - - - - - - - 255 - 255 - 255 - - - - - - - 138 - 222 - 187 - - - - - - - - - 138 - 222 - 187 - - - - - - - 138 - 222 - 187 - - - - - - - - <html><head/><body><p><span style=" font-weight:600;">Detailed explanation of genre-matching:</span></p><p>If none of the boxes are filled, then all genres found will be included.</p><p>If one box is filled (e.g. &quot;classical main genres&quot;) and there is a matching genre found, then remaining genres will only be included if they match genres in that box or the related sub-genre box. So in this case if &quot;classical sub-genres&quot; is filled and &quot;general sub-genres&quot; is not, then only matching genres will be included.</p><p>If both &quot;classical&quot; and &quot;general&quot; main genre boxes are filled, then only genres matching those boxes or &quot;classical sub-genres&quot; will be included.</p></body></html> - - - true - - - - - - Allowed genres - - - - - - Classical genres (i.e. specific to classical music) - List separated by commas + + 0 - - - + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + - Main genres: + album_soloists, album_conductors, album_ensembles - - - - - - - 16777215 - 50 - + + + + soloists, conductors, ensembles, album_composers, composers - - background-color: rgb(156, 250, 211); + + + + album_soloists - - - - + + - Sub-genres: + album_conductors - - - - - - - 0 - 0 - + + + + album_ensembles - - - 16777215 - 50 - + + + + album_composers - - background-color: rgb(156, 250, 211); + + + + album_composer_lastnames - - - - - - - 75 - true - + + + + soloists - - <html><head/><body><p>Select this to use the &quot;classical genres&quot; in Muso options as the &quot;Main classical genres&quot; here.</p></body></html> + + + + soloist_names + + - Use Muso classical genres* + ensembles - - - - - - - - - General genres (may be associated with classical music, but not necessarily, e.g. "instrumental") - List separated by commas - - - - + + - Main genres + ensemble_names - - - - - - - 16777215 - 50 - + + + + composers - - background-color: rgb(156, 250, 211); + + + + arrangers - - - - + + - Sub-genres + orchestrators - - - - - - - 16777215 - 50 - + + + + conductors - - background-color: rgb(156, 250, 211); + + + + chorusmasters - - - - cwp_genres_other_main - cwp_genres_other_sub - label_62 - label_76 - - - - - - Genre name to use if none of the above main genres apply (leave blank if not required) - - - - - - - background-color: rgb(156, 250, 211); - - - - - - - List genres, separated by commas. Only those genres listed will be included in tags. If no genres are listed in a box, then any genres found will be included. - - - - - - - See "what's this" for more details. - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 138 - 222 - 187 - - - - - - - - - 255 - 255 - 255 - - - - - - - 138 - 222 - 187 - - - - - - - - - 138 - 222 - 187 - - - - - - - 138 - 222 - 187 - - - - - - - - true - - - - - - Source of genres - Note: if "existing file tag" is selected, information from the tag "genre" and the genre tag name specified above (if different) will be used - - - - - - <html><head/><body><p>NB: This will use the contents of the file tag with the name given above (usually 'genre').</p></body></html> - - - Existing file tag (see note above) - - - - - - - <html><head/><body><p>This will use the folksonomy tags for <span style=" font-weight:600;">works</span> as a possible source of genres (if they match one of the lists below).</p><p><br/></p><p>To use the folksonomy tags for <span style=" font-weight:600;">releases/tracks</span>, select the main Picard option in Options-&gt;Metadata-&gt;&quot;Use folksonomy tags as genre&quot;. Again (unlike vanilla Picard) they will only be used by this plugin if they match one of the lists below.</p></body></html> - - - Folksonomy work tags - - + + + + leaders + + + + + support_performers + + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + - - - - Work-type + + + + 6 - - - - - - Infer from artist metadata + + 0 - - - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 199 - 93 - 95 - - - - - - - - - 255 - 255 - 255 - - - - - - - 199 - 93 - 95 - - - - - - - - - 199 - 93 - 95 - - - - - - - 199 - 93 - 95 - - - - - - - - true - - - - - - Instruments and keys - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 161 - 163 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 161 - 163 - - - - - - - - - 255 - 161 - 163 - - - - - - - 255 - 161 - 163 - - - - - - - - true - - - - - - Instruments - - - - - - Tag name for instruments (will hold all instruments for a track) + + 0 - - - - - - background-color: rgb(248, 116, 120); + + 0 - - - - - - Name sources to use for instruments (select at least one, otherwise no instruments will be included in tag) + + 0 - - - + + + + Click to edit sources + + + + + + Source from: + + + true + + + + + + + false + + + Click button to edit. See notes above. + + + background-color: rgb(250, 250, 250); + + + true + + + + + + + + + album_soloists, album_conductors, album_ensembles + + + + + soloists, conductors, ensembles, album_composers, composers + + + + + album_soloists + + + + + album_conductors + + + + + album_ensembles + + + + + album_composers + + + + + album_composer_lastnames + + + + + soloists + + + + + soloist_names + + + + + ensembles + + + + + ensemble_names + + + + + composers + + + - MusicBrainz standard names + arrangers - - - - + + - "Credited-as" names + orchestrators - - - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 161 - 163 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 161 - 163 - - - - - - - - - 255 - 161 - 163 - - - - - - - 255 - 161 - 163 - - - - - - - - true - - - - - - Keys - - - - - - Tag name for key(s) - - - - - - - background-color: rgb(248, 116, 120); - - - - - - - Include key(s) in work name? - - - - + + - Never + conductors - - - - + + - Only if key not already mentioned in work name + chorusmasters - - - - - - <html><head/><body><p>&quot;Include key(s) in work names&quot; gives the option to include the key signature for a work in brackets after the name of the work in the metadata. Keys will be added in the appropriate levels: e.g. Dvořák's New World Symphony will get (E minor) at the work level, but only movements with different keys will be annotated viz. &quot;II. Largo (D-flat major, C-Sharp minor)&quot;</p></body></html> + + + + leaders + + - Always + support_performers - - - + + + + work_type + + + + + release + + + + + + + + into tags: + + + + + + + Enter comma-separated list of tags + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Conditional? + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + 1 + + + (If source is empty, tag will be left unchanged) + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + (Conditional tags will only be filled if previously empty) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + <html><head/><body><p>Select to include sort-tags, where available. See<span style=" font-style:italic;"> &quot;What's this?&quot;</span> for more details.</p></body></html> + + + <html><head/><body><p>If a sort tag is associated with the source tag then the sort names will be placed in a sort tag corresponding to the destination tag. Note that the only explicit sort tags written by Picard are for artist, albumartist and composer. Piacrd also writes hidden variables '_artists_sort' and 'albumartists_sort' (note the plurals - these are the sort tags for multi-valued alternatives 'artists' and '_albumartists'). To be consistent with this approach, the plugin writes hidden variables for other tags - e.g. '_arranger_sort'. The plugin also writes hidden sort variables for the various hidden artist variables - e.g. '_cwp_librettists' has a matching sort variable '_cwp_librettists_sort'. Therefore most artist-type sources <span style=" font-weight:600;">will</span> have a sort tag/variable associated with them and these will be placed in a destination sort tag if this option is selected -<span style=" font-weight:600;"> in other words, selecting this option will cause most destination tags to have associated sort tags</span>.<span style=" font-weight:600;"> Furthermore, any hidden sort variables associated with tags which are not listed explicitly in the tag mapping section will also be written out as tags</span> (i.e. even if the related tags are not included as destination tags). Note, however, that composite sources (e.g. &quot; ensemble_names + \; + conductors&quot;) do not have sort tags associated with them.</p><p><br/></p><p>If this option is not selected, no additional sort tags will be written, but the hidden variables will still be available, so if a sort tag is required explicitly, just map the sort tag directly - e.g. map 'conductors_sort' to 'conductor_sort'.</p></body></html> + + + Qt::LeftToRight + + + Also populate sort tags + @@ -7758,240 +9287,153 @@ text-decoration: underline; 0 - 0 - 1087 - 1205 + -33 + 1084 + 1408 - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 170 - 170 - 127 - - - - - - - 170 - 170 - 127 - - - - - - - - true - - - + + + + + + QFrame::StyledPanel - - General + + QFrame::Raised - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - + + + background-color: rgb(208, 208, 156); + - Do not run Classical Extras for tracks where no pre-existing file is detected (warning tag will be written) + <html><head/><body><p><span style=" font-weight:600;">General</span></p></body></html> - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 170 - 170 - 127 - - - - - - - 170 - 170 - 127 - - - - - - - - <html><head/><body><p>Separate multiple names by commas. Do not use any quotation marks.</p></body></html> - - - <html><head/><body><p>Permits the listing of strings by which ensembles of different types may be identified. This is used by the plugin to place performer details in the relevant hidden variables and thus make them available for use in the &quot;Tag mapping&quot; tab as sources for any required tags. </p><p>If it is important that only whole words are to be matched, be sure to include a space after the string.</p></body></html> - - - true - - - - - - Artists - - - + + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + + false + + + background-color: rgb(229, 229, 197); + - Ensemble strings (separate names by commas) + - - - 2 - - - 9 - - - 9 - - - 9 - - - 9 - - - - - Orchestras - - - + - - - background-color: rgb(203, 182, 175); - - - - - - - Choirs - - - - - - - background-color: rgb(203, 182, 175); - - - - - + - Groups (i.e. other ensembles such as quartets etc.) - - - - - - - background-color: rgb(203, 182, 175); + Do not run Classical Extras for tracks where no pre-existing file is detected (warning tag will be written) @@ -8002,401 +9444,372 @@ text-decoration: underline; - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 170 - 170 - 127 - - - - - - - 170 - 170 - 127 - - - - - - - - true - - - + + + QFrame::StyledPanel - - Work levels + + QFrame::Raised - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - <html><head/><body><p>Sometimes MB lookups fail. Unfortunately Picard (currently) has no automatic &quot;retry&quot; function. The plugin will attempt to retry for the specified number of attempts. If it still fails, the hidden variable _cwp_error will be set with a message; if error logging is checked in section 4, an error message will be written to the log and the contents of _cwp_error will be written out to a special tag called &quot;An_error_has_occurred&quot; which should appear prominently in the bottom pane of Picard. The problem may be resolved by refreshing, otherwise there may be a problem with the MB database availability. It is unlikely to be a software problem with the plugin.</p></body></html> - - - <html><head/><body><p>Max number of re-tries to access works (in case of server errors)*</p></body></html> - - - cwp_retries - - - - - - - background-color: rgb(203, 182, 175); - - - - - - 0 - - - 20 - - - - + + + background-color: rgb(208, 208, 156); + + + <html><head/><body><p><span style=" font-weight:600;">Artists (only effective if "Artists" section enabled)</span></p></body></html> + + - + + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 198 - 198 - 148 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 198 - 198 - 148 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 198 - 198 - 148 + 229 + 229 + 197 - 198 - 198 - 148 + 229 + 229 + 197 + + <html><head/><body><p>Separate multiple names by commas. Do not use any quotation marks.</p></body></html> + + + <html><head/><body><p>Permits the listing of strings by which ensembles of different types may be identified. This is used by the plugin to place performer details in the relevant hidden variables and thus make them available for use in the &quot;Tag mapping&quot; tab as sources for any required tags. </p><p>If it is important that only whole words are to be matched, be sure to include a space after the string.</p></body></html> + - true + false + + + background-color: rgb(229, 229, 197); - Removal of common text between parent and child works + - + - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 + + + <html><head/><body><p><span style=" font-weight:600;">Ensemble strings</span> (separate names by commas)</p></body></html> - - - - - 0 - 0 - - - - <html><head/><body><p>Minimum number of similar words required before eliminating. Use zero for no elimination.<br/>(Punctuation and accents etc. will be ignored in word comparison)</p></body></html> - - - cwp_retries - - - - - - - background-color: rgb(203, 182, 175); - - - - - - 0 - - - 99 - - - - + - - - NB Parent name text at the start of a work which is followed by punctuation in the work name will always be stripped regardless of this setting. + + + + + + 2 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + Orchestras + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + Choirs + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + Groups (i.e. other ensembles such as quartets etc.) + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(208, 208, 156); + + + <html><head/><body><p><span style=" font-weight:600;">Works and parts (only effective if "Works and parts" section enabled)</span></p></body></html> + + + - + + + + 0 + 0 + + + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 198 - 198 - 148 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 198 - 198 - 148 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 198 - 198 - 148 + 229 + 229 + 197 - 198 - 198 - 148 + 229 + 229 + 197 - - <html><head/><body><p>This subsection contains various parameters affecting the processing of strings in titles. Because titles are free-form, not all circumstances can be anticipated. Detailed documentation of these is beyond the scope of this Readme as the effects can be quite complex and subtle and may require an understanding of the plugin code (which is of course open-source) to acsertain them. If pure canonical works are used (&quot;Use only metadata from canonical works&quot; and, if necessary, &quot;Full MusicBrainz work hierarchy&quot; on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below:</p><p> &quot;Proximity of new words&quot;. When using extended metadata - i.e. &quot;metadata enhanced with title text&quot;, the plugin will attempt to remove similar words between the canonical work name (in MusicBrainz) and the title before extending the canonical name. After removing such words, a rather &quot;bitty&quot; result may occur. To avoid this, any new words with the specified proximity will have the words between them (or up to the end) included even if they repeat words in the work name.</p><p> &quot;Prefixes&quot;. When using &quot;metadata from titles&quot; or extended metadata, the structure of the works in MusicBrainz is used to infer the structure in the title text, so strings that are repeated between tracks which are part of the same MusicBrainz work will be treated as &quot;higher level&quot;. This can lead to anomolies if, for instance, the titles are &quot;Work name: Part 1&quot;, &quot;Work name: Part 2&quot;, &quot;Part&quot; will be treated as part of the parent work name. Specifying such words in &quot;Prefixes&quot; will prevent this.</p><p> &quot;Synonyms&quot;. These words will be considered equivalent when comparing work name and title text. Thus if one word appears in the work name, that and its synonym will be removed from the title in extending the metadata (subject to the proximity setting above).</p><p> &quot;Replacements&quot;. These words/phrases will be replaced in the title text in extended metadata, regardless of the text in the work name.</p></body></html> - - true + false + + + background-color: rgb(229, 229, 197); - How title metadata should be included in extended metadata (use cautiously - read documentation) + - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - <html><head/><body><p>Proximity of new words (to each other) to trigger in-fill with existing words (default = 2)</p></body></html> - - - cwp_retries - - - - - - - background-color: rgb(203, 182, 175); - - - - - - 20 - - - - - + - + 6 @@ -8413,63 +9826,18 @@ text-decoration: underline; 0 - + 0 0 - - <html><head/><body><p>Proximity of new words (to start or end) to trigger in-fill with existing words (default =1)</p></body></html> - - - cwp_retries - - - - - - - background-color: rgb(203, 182, 175); - - - - - - 20 - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - + + <html><head/><body><p>Sometimes MB lookups fail. Unfortunately Picard (currently) has no automatic &quot;retry&quot; function. The plugin will attempt to retry for the specified number of attempts. If it still fails, the hidden variable _cwp_error will be set with a message; if error logging is checked in section 4, an error message will be written to the log and the contents of _cwp_error will be written out to a special tag called &quot;An_error_has_occurred&quot; which should appear prominently in the bottom pane of Picard. The problem may be resolved by refreshing, otherwise there may be a problem with the MB database availability. It is unlikely to be a software problem with the plugin.</p></body></html> - <html><head/><body><p>Treat hyphenated words as two words for comparison purposes</p></body></html> + <html><head/><body><p>Max number of re-tries to access works (in case of server errors)*</p></body></html> cwp_retries @@ -8477,19 +9845,25 @@ text-decoration: underline; - - - Qt::RightToLeft + + + background-color: rgb(250, 250, 250); - + + + 0 + + + 20 + - + 6 @@ -8506,7 +9880,7 @@ text-decoration: underline; 0 - + 0 @@ -8514,7 +9888,7 @@ text-decoration: underline; - <html><head/><body><p>Proportion of a string to be matched to a (usually larger) string for it to be considered essentially similar (default = 66%)</p></body></html> + <html><head/><body><p>Allow blank part names for arrangements and part recordings if arrangement/partial label is provided</p></body></html> cwp_retries @@ -8522,346 +9896,757 @@ text-decoration: underline; - - - background-color: rgb(203, 182, 175); - - - % + + + Qt::RightToLeft - - 100 + + - - - Qt::Horizontal - - - - - - - <html><head/><body><p><span style=" font-weight:600;">Prepositions and prefixes<br/></span>DO NOT USE ANY COMMAS OR QUOTE MARKS (apostophes in words are acceptable)</p></body></html> - - - - - - - Prepositions: these are words that will not be regarded as providing additional information (not treated as 'new' words) unless they precede a new word. Use lower case only, comma separated + + + + + + + + 241 + 241 + 167 + + + + + + + 241 + 241 + 167 + + + + + + + 241 + 241 + 167 + + + + + + + + + 241 + 241 + 167 + + + + + + + 241 + 241 + 167 + + + + + + + 241 + 241 + 167 + + + + + + + + + 241 + 241 + 167 + + + + + + + 241 + 241 + 167 + + + + + + + 241 + 241 + 167 + + + + + + + + false - - - - - background-color: rgb(203, 182, 175); - - - - - - - <html><head/><body><p>Prefixes to be ignored in comparison (case insensitive, comma separated)<br/>To prevent a prefix from being ignored when extending metadata with title info, precede it with a space. <br/>To ensure only whole words are removed, follow with a space.</p></body></html> + background-color: rgb(241, 241, 167); - - - - - - <html><head/><body><p>Separate multiple names by commas. Do not use any quotation marks.</p></body></html> - - - background-color: rgb(203, 182, 175); + + + + + + + <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">Removal of common text between parent and child works</span></p></body></html> + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Minimum number of similar words required before eliminating. Use zero for no elimination.<br/>(Punctuation and accents etc. will be ignored in word comparison)</p></body></html> + + + cwp_retries + + + + + + + background-color: rgb(250, 250, 250); + + + + + + 0 + + + 99 + + + + + + + + + NB Parent name text at the start of a work which is followed by punctuation in the work name will always be stripped regardless of this setting. + + + + - - - Qt::Horizontal + + + + + + + + 202 + 202 + 140 + + + + + + + 202 + 202 + 140 + + + + + + + 202 + 202 + 140 + + + + + + + + + 202 + 202 + 140 + + + + + + + 202 + 202 + 140 + + + + + + + 202 + 202 + 140 + + + + + + + + + 202 + 202 + 140 + + + + + + + 202 + 202 + 140 + + + + + + + 202 + 202 + 140 + + + + + - - - - - - <html><head/><body><p><span style=" font-weight:600;">Synonyms and replacements</span> - must be written as tuples separated by forward slashes - e.g (a,b) / (c,d)</p><p>DO NOT USE COMMAS, BRACKETS or FORWARD SLASHES IN ANY PHRASE. <br/>N.B. The matching of 'a' is case-sensitive</p><p>As SYNONYMS 'a' and 'b' will be treated as similar when comparing works/parts and titles. The text in tags will be unaltered.<br/>Both a and b must be single words with no punctuation. No word may appear in more than one synonym (case sensitive):-</p></body></html> + + <html><head/><body><p>This subsection contains various parameters affecting the processing of strings in titles. Because titles are free-form, not all circumstances can be anticipated. Detailed documentation of these is beyond the scope of this Readme as the effects can be quite complex and subtle and may require an understanding of the plugin code (which is of course open-source) to acsertain them. If pure canonical works are used (&quot;Use only metadata from canonical works&quot; and, if necessary, &quot;Full MusicBrainz work hierarchy&quot; on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below:</p><p> &quot;Proximity of new words&quot;. When using extended metadata - i.e. &quot;metadata enhanced with title text&quot;, the plugin will attempt to remove similar words between the canonical work name (in MusicBrainz) and the title before extending the canonical name. After removing such words, a rather &quot;bitty&quot; result may occur. To avoid this, any new words with the specified proximity will have the words between them (or up to the end) included even if they repeat words in the work name.</p><p> &quot;Prefixes&quot;. When using &quot;metadata from titles&quot; or extended metadata, the structure of the works in MusicBrainz is used to infer the structure in the title text, so strings that are repeated between tracks which are part of the same MusicBrainz work will be treated as &quot;higher level&quot;. This can lead to anomolies if, for instance, the titles are &quot;Work name: Part 1&quot;, &quot;Work name: Part 2&quot;, &quot;Part&quot; will be treated as part of the parent work name. Specifying such words in &quot;Prefixes&quot; will prevent this.</p><p> &quot;Synonyms&quot;. These words will be considered equivalent when comparing work name and title text. Thus if one word appears in the work name, that and its synonym will be removed from the title in extending the metadata (subject to the proximity setting above).</p><p> &quot;Replacements&quot;. These words/phrases will be replaced in the title text in extended metadata, regardless of the text in the work name.</p></body></html> - - - - - - <html><head/><body><p>Separate multiple names by forward slash. Entries must be 2-tuples, e.g. (Replace_this, with_this). Do not use any quotation marks or spaces.</p></body></html> + + false - background-color: rgb(203, 182, 175); - - - - - - - <html><p>For REMOVALS/REPLACEMENTS - These will result in the "extended" text in tags being changed<br/>Put the word or phrase in the first part of the tuple and leave the second blank - e.g. (I don't want to see this phrase, ) <br/>or put the replacement word(s) in the second part:-</p></html> - - - - - - - <html><head/><body><p>Entries must be 2-tuples, e.g. (Replace this, with this). Separate multiple tuples by forward slash. Do not use any quotation marks. Spaces are acceptable. The first item of a tuple may be a regular expression - enclose it with double exclamation marks - e.g.(!!regex here!!, replacement text here).</p></body></html> + background-color: rgb(202, 202, 140); - - background-color: rgb(203, 182, 175); + + - - - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 170 - 170 - 127 - - - - - - - 170 - 170 - 127 - - - - - - - - true - - - - - - Genres etc. (only required if Muso-specific options are used for genres/periods) - - - - - - Path to Muso reference database: - - - - - - - background-color: rgb(203, 182, 175); - - - - - - - Name of Muso reference database - - - - - - - background-color: rgb(203, 182, 175); - - - - - - - <html><head/><body><p><span style=" font-weight:600;">RESTART PICARD AFTER CHANGING THESE</span></p></body></html> - + + + + + <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">How title metadata should be included in extended metadata</span><span style=" font-size:10pt;"> (use cautiously - read documentation)</span><br/><span style=" font-style:italic;">(Only applies if &quot;Use canonical work metadata enhanced with title text&quot; selected on &quot;Works and parts&quot; tab)</span></p></body></html> + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Proximity of new words (to each other) to trigger in-fill with existing words (default = 2)</p></body></html> + + + cwp_retries + + + + + + + background-color: rgb(250, 250, 250); + + + + + + 99 + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Proximity of new words (to start or end) to trigger in-fill with existing words (default =1)</p></body></html> + + + cwp_retries + + + + + + + background-color: rgb(250, 250, 250); + + + + + + 99 + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Treat hyphenated words as two words for comparison purposes</p></body></html> + + + cwp_retries + + + + + + + Qt::RightToLeft + + + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + <html><head/><body><p>Proportion of a string to be matched to a (usually larger) string for it to be considered essentially similar (default = 66%)</p></body></html> + + + cwp_retries + + + + + + + background-color: rgb(250, 250, 250); + + + % + + + 100 + + + + + + + + + Qt::Horizontal + + + + + + + <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Prepositions/conjunctions and prefixes</span><span style=" font-weight:600;"><br/></span>DO NOT USE ANY COMMAS OR QUOTE MARKS (apostophes in words are acceptable)</p></body></html> + + + + + + + <html><head/><body><p>Prepositions &amp; conjunctions: these are words that will not be regarded as providing additional information (not treated as 'new' words) unless they precede a new word.<br/>Use lower case only, comma separated</p></body></html> + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + <html><head/><body><p>Prefixes to be ignored in comparison (case insensitive, comma separated)<br/>To prevent a prefix from being ignored when extending metadata with title info, precede it with a space. <br/>To ensure only whole words are removed, follow with a space.</p></body></html> + + + + + + + <html><head/><body><p>Separate multiple names by commas. Do not use any quotation marks.</p></body></html> + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::Horizontal + + + + + + + <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Synonyms and replacements</span> - must be written as tuples separated by forward slashes - e.g (a,b) / (c,d,e) - a tuple may have two or more synonyms.</p><p>N.B. The matching is case-insensitive. Roman numerals will be treated as synonyms of arabic numerals in any event, so no need to enter these.</p><p>The last member of the tuple should be the canonical form (i.e. the one to which others are converted for comparison or replacement) and <span style=" text-decoration: underline;">must be a normal strin</span>g (not a regex). <span style=" font-weight:600;">See readme for full details</span>.<br/><span style=" font-weight:600; font-style:italic;">Unless entering a regular expression, use backslash \ to escape any regex metacharacters, namely \ ^ $ . | ? * + ( ) [ ] { <br/>Also escape commas , and forward slashes /. Do not enclose strings in quote marks.</span></p><p>Enter SYNONYM tuples below - each item in a tuple will be treated as similar when comparing works/parts and titles. The text in tags will be unaltered.</p></body></html> + + + + + + + + 16777215 + 120 + + + + background-color: rgb(250, 250, 250); + + + + + + + <html><head/><body><p>Enter REMOVALS/REPLACEMENTS below - these will result in the &quot;extended&quot; text in tags being changed<br/>Put the word(s), phrase(s), or regular exprerssion(s) in the first part(s) of the tuple. The replacement text (or nothing - to remove) goes in the last member of the tuple.</p><p>N.B. Replacement text will operate BEFORE synonyms are considered.</p></body></html> + + + + + + + <html><head/><body><p>Entries must be 2-tuples, e.g. (Replace this, with this). Separate multiple tuples by forward slash. Do not use any quotation marks. Spaces are acceptable. The first item of a tuple may be a regular expression - enclose it with double exclamation marks - e.g.(!!regex here!!, replacement text here).</p></body></html> + + + background-color: rgb(250, 250, 250); + + + + + + + - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 127 - - - - - - - - - 170 - 170 - 127 - - - - - - - 170 - 170 - 127 - - - - - - - - <html><head/><body><p>These options are in addition to the options chosen in Picard's &quot;Help-&gt;View error/debug log&quot; settings. They only affect messages written by this plugin. To enable debug messages to be shown, the flag needs to be set here and &quot;Debug mode&quot; needs to be turned on in the log. It is strongly advised to keep the &quot;debug&quot; and &quot;info&quot; flags unchecked unless debugging is required as they slow up processing significantly and may even cause Picard to crash on large releases. The &quot;error&quot; and &quot;warning&quot; flags should be left checked, unless it is required to suppress messages written out to tags (the default is to write messages to the tags 001_errors and 002_warnings).</p></body></html> - - - Qt::LeftToRight - - - true - - - + + + QFrame::StyledPanel - - Logging options* + + QFrame::Raised - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + - - - Error + + + background-color: rgb(208, 208, 156); - - - - - Warning + <html><head/><body><p><span style=" font-weight:600;">Genres etc.</span> (only required if Muso-specific options are used for genres/periods)</p></body></html> - - - Debug + + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + 229 + 229 + 197 + + + + + + + + false + + + background-color: rgb(229, 229, 197); - - - - - Custom logging + - - - + + + - Basic + Path to Muso reference database: - - + + + + background-color: rgb(250, 250, 250); + + + + + + + Name of Muso reference database + + + + + + + background-color: rgb(250, 250, 250); + + + + + - Full + <html><head/><body><p><span style=" font-weight:600;">RESTART PICARD AFTER CHANGING THESE</span></p></body></html> @@ -8872,144 +10657,126 @@ text-decoration: underline; - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 170 - 170 - 0 - - - - - - - - - 170 - 170 - 0 - - - - - - - 170 - 170 - 0 - - - - - - - - true - - - - + QFrame::StyledPanel QFrame::Raised - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(208, 208, 156); + + + <html><head/><body><p><span style=" font-weight:600;">Logging options</span>*</p></body></html> + + + - + + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 170 - 170 - 164 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 170 - 170 - 164 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 170 - 170 - 164 + 229 + 229 + 197 - 170 - 170 - 164 + 229 + 229 + 197 @@ -9017,199 +10784,544 @@ text-decoration: underline; - <html><head/><body><p>This can be used so that the user has a record of the version of Classical Extras which generated the tags and which options were selected to achieve the resulting tags. Note that the tags will be blanked first so this will only show the last options used on a particular file. The same tag can be used for both sets of options, resulting in a multi-valued tag. </p></body></html> + <html><head/><body><p>These options are in addition to the options chosen in Picard's &quot;Help-&gt;View error/debug log&quot; settings. They only affect messages written by this plugin. To enable debug messages to be shown, the flag needs to be set here and &quot;Debug mode&quot; needs to be turned on in the log. It is strongly advised to keep the &quot;debug&quot; and &quot;info&quot; flags unchecked unless debugging is required as they slow up processing significantly and may even cause Picard to crash on large releases. The &quot;error&quot; and &quot;warning&quot; flags should be left checked, unless it is required to suppress messages written out to tags (the default is to write messages to the tags 001_errors and 002_warnings).</p></body></html> + + + Qt::LeftToRight - true + false - + background-color: rgb(229, 229, 197); - Save plugin details and options in a tag?* + - + - + - Tag name for plugin version + Error - - - - - - Qt::RightToLeft - + - Tag name for artist/mapping/misc. options - - - - - - - Qt::RightToLeft + Warning - - - Qt::RightToLeft - + - Tag name for work/genre options + Debug - - - Qt::RightToLeft + + + background-color: rgb(229, 229, 159); + + Custom logging + + + + + + Basic + + + + + + + Full + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + background-color: rgb(208, 208, 156); + + + <html><head/><body><p><span style=" font-weight:600;">Classical Extras Special Tags</span></p></body></html> + + + - + + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 170 - 170 - 164 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 255 - 255 - 255 + 229 + 229 + 197 - 170 - 170 - 164 + 229 + 229 + 197 + + + + 229 + 229 + 197 + + + - 170 - 170 - 164 + 229 + 229 + 197 - 170 - 170 - 164 + 229 + 229 + 197 - - <html><head/><body><p>If options have previously been saved (see above), selecting these will cause the saved options to be used in preference to the displayed options. The displayed options will not be affected and will be used if no saved options are present. The default is for no over-ride. If artist options over-ride is chosen, then tag map detail options may be included or not in the override.</p><p><br/></p><p>The last checkbox, &quot;Overwrite options in Options Pages&quot;, is for <span style=" font-weight:600;">VERY CAREFUL USE ONLY</span>. It will cause any options read from the saved tags (if the relevant box has been ticked) to over-write the options on the plugin Options Page UI. The intended use of this is if for some reason the user's preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, to prevent inadvertant use. Far better is to make a <span style=" font-weight:600;">backup copy</span> of the picard.ini file.</p></body></html> - - true + false - - Over-ride plugin options displayed in Options Pages with options from local file tags (previously saved using method in box above)?* + + background-color: rgb(229, 229, 197); + + + QFrame::StyledPanel - + + QFrame::Raised + + - - - Artist options + + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + - - - - - + + <html><head/><body><p>This can be used so that the user has a record of the version of Classical Extras which generated the tags and which options were selected to achieve the resulting tags. Note that the tags will be blanked first so this will only show the last options used on a particular file. The same tag can be used for both sets of options, resulting in a multi-valued tag. </p></body></html> + + false - - <html><head/><body><p>Will not over-ride displayed options unless artist options over-ride is also selected</p></body></html> + + background-color: rgb(229, 229, 159); - - Tag map detail options + + Save plugin details and options in a tag?* + + + + + Tag name for plugin version + + + + + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Tag name for artist/mapping/misc. options + + + + + + + Qt::RightToLeft + + + background-color: rgb(250, 250, 250); + + + + + + + Qt::RightToLeft + + + Tag name for work/genre options + + + + + + + Qt::RightToLeft + + + background-color: rgb(250, 250, 250); + + + + - - - Qt::Vertical + + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + + + 229 + 229 + 159 + + + + + - - - - - - Work options + + <html><head/><body><p>If options have previously been saved (see above), selecting these will cause the saved options to be used in preference to the displayed options. The displayed options will not be affected and will be used if no saved options are present. The default is for no over-ride. If artist options over-ride is chosen, then tag map detail options may be included or not in the override.</p><p><br/></p><p>The last checkbox, &quot;Overwrite options in Options Pages&quot;, is for <span style=" font-weight:600;">VERY CAREFUL USE ONLY</span>. It will cause any options read from the saved tags (if the relevant box has been ticked) to over-write the options on the plugin Options Page UI. The intended use of this is if for some reason the user's preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, to prevent inadvertant use. Far better is to make a <span style=" font-weight:600;">backup copy</span> of the picard.ini file.</p></body></html> - - - - - + false - - Genres etc. options + + background-color: rgb(229, 229, 159); - - - - - - Qt::Vertical + + Over-ride plugin options displayed in Options Pages with options from local file tags (previously saved using method in box above)?* + + + + + Artist options + + + + + + + Work options + + + + + + + true + + + Genres etc. options + + + + + + + true + + + <html><head/><body><p>Will not over-ride displayed options unless artist options over-ride is also selected</p></body></html> + + + Tag mapping options + + + + + + + Qt::Vertical + + + + + + + background-color: rgb(255, 0, 0); + + + Overwrite options in Options Pages (READ WARNINGS in Readme) + + + + - - - background-color: rgb(255, 0, 0); - + - Overwrite options in Options Pages (READ WARNINGS in Readme) + Note that the above saved options include the related "advanced" options on this tab as well as the options on each of the main tabs. @@ -9286,450 +11398,8 @@ text-decoration: underline; <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">General Information</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is the documentation for version 2.0 of &quot;classical_extras&quot;. There may be beta versions later than this - check </span><a href="https://github.com/MetaTunes/picard-plugins"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">my github site</span></a><span style=" font-size:8pt;"> for newer releases (make sure that the &quot;metabrainz/2.0&quot; branch is selected). For further help, please review </span><a href="https://community.metabrainz.org/t/classical-extras-plugin/300217"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">the forum thread</span></a><span style=" font-size:8pt;"> or post any new questions there. It only works with Picard version 2.0, </span><span style=" font-size:8pt; font-weight:600;">NOT</span><span style=" font-size:8pt;"> earlier versions. If you are using Picard 1.4.x, please choose the &quot;1.0&quot; branch </span><a href="https://github.com/MetaTunes/picard-plugins"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">on github</span></a><span style=" font-size:8pt;"> and use the latest release there - also use the </span><a href="https://community.metabrainz.org/t/classical-extras-plugin/300217"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">earlier forum thread</span></a><span style=" font-size:8pt;">.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This version has only been tested with FLAC and mp3 files. It does work with m4a files, but Picard does not write all m4a tags (see further notes for iTunes users at the end of the &quot;works and parts tab&quot; section). &quot;Classical Extras&quot; populates hidden variables in Picard with information from the MusicBrainz database about the recording, artists and work(s), and of any containing works, passing up through mutiple work-part levels until the top is reached. The &quot;Options&quot; page (Options-&gt;Options-&gt;Plugins-&gt;Classical Extras) allows the user to determine how these hidden variables are written to file tags, as well as a variety of other options.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This plugin is particularly designed to assist with tagging of classical music so that player or library manager software which can display multiple work levels, different artist types and custom tags can have access to these details.<br />It has two main components &quot;Extra Artists&quot; and &quot;Work Parts&quot; which can be used independently or together. &quot;Work Parts&quot; will take at least as many seconds to process as there are works to look up (owing to MB throttling) so users who only want the extra artist information and the tag-mapping feature, but not the work details, may turn it off (e.g. perhaps for 'popular' music).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Hidden metadata variables produced by this plugin are (mostly) prefixed with &quot;_cwp_&quot; or &quot;_cea_&quot; depending on which component of the plugin created them. Full details of these variables are given in a later section. Tags are output depending on the choices specified by the user in the Options Page. Defaults are provided for these tags which can be added to / modified / deleted according to user requirements. If the Options Page does not provide sufficient flexibility, users familiar with scripting can write Tagger Scripts to access the hidden variables directly.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Updates</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 2.0: Major overhaul of version 0.9.4 to achieve Picard 2.0 and Python 3.7 compatibility. All webservice calls re-written for JSON rather than XmlNode responses. Periods are written to tag in date order. Addition of sub-options for inclusion of key signature information in work names. If the MB database has circular work references (i.e a parent is a descendant of itself) then these will be trapped, ignored and reported. Numerous small refinements, especially of text comparison algorithms (e.g. option to control removal of prepositions - see advanced tab). Bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For a list of previous version changes, see the end of this document.</span></p> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Installation</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Install the zip file in your plugins folder in the usual fashion</span></p> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Usage</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">After installation, go to the Options Page and modify choices as required. There are 4 tabs - &quot;Artists&quot;, &quot;Tag mapping&quot;, Works and parts&quot; and &quot;Advanced&quot;. The sections below describe each of these. If the options provided do not allow sufficient flexibility for a user's need (hopefully unlikely!) then Tagger Scripts may be used to process the hidden variables or other tags. Alternatively, it may be possible to achieve the required result by running and saving twice (or more!) with different options each time. This is not recommended for more than a one-off - a script would be better.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Important</span><span style=" font-size:8pt;">: </span></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The plugin <span style=" font-weight:600;">will not work fully unless</span> &quot;Use release relationships&quot; and &quot;Use track relationships&quot; are enabled in Picard-&gt;Options-&gt;Metadata. The plugin will enable these options by default when starting Picard. However, it may be that the MusicBrainz database has conflicting data between track and release relationships, in which case you may wish to temporarily turn off one of these options, but it is better to fix the incorrect data using &quot;Edit relationships&quot; in MusicBrainz. </li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">It is recommended only to use the plugin on one or a few release(s) at a time, particularly for initial tagging if the &quot;Works and parts&quot; function is being used. The plugin is not designed to do &quot;bulk tagging&quot; of untagged files - it may be better to use a tool such as SongKong for that and then use the plugin to enhance the results as required. However, once you have tagged files (either in Picard or another tool) such that they all have MusicBrainz IDs, you should be able to re-tag multiple releases by dragging the containing folder into Picard; this is useful to pick up changed MusicBrainz data or if you change the Classical Extras version or options (but bear in mind that the &quot;Works and parts&quot; function will still take at least 1 second per track. </li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Check for error messages before saving a release</span>. The plugin will write out special &quot;error message&quot; tags which should appear prominently in the bottom Picard pane. In particular, look for &quot;000_major_warning&quot; and &quot;001_errors&quot;. If you get &quot;000_major_warning&quot; with the message &quot;WARNING: Classical Extras not run for this track as no file present - deselect the option on the advanced tab to run. If there is a file, then try 'Refresh'.&quot; then do what it says (this will only occur if you have opted to not run Classical Extras for tracks where no pre-existing file is detected)! </li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Also, watch out for &quot;002_important_warning&quot; - &quot;No file with matching trackid - IF THERE SHOULD BE ONE, TRY 'REFRESH' - (unable to process any saved options, lyrics or 'keep' tags)&quot;; this will always occur if you load a file without MusicBrainz ids - just refresh it to pick up any existing file tags such as lyrics, if required.</span><span style=" font-size:8pt;"> This will occur if you have manually matched files rather than used Picard's &quot;lookup&quot; or &quot;scan&quot; functions. It may also be due to Picard processing issues - more likely if the files are on a network server; if you are getting it a lot then it may be better to move the files onto local storage to do the updates. </span></p> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you are just changing option settings then you can usually &quot;use cache&quot; (see &quot;work and parts&quot; tab section 1) to avoid the 1-second per work delay. However, if the works data in MusicBrainz has been changed then obviously you will need to do a full look-up, so disable cache. If the work structure has been fundamentally changed (i.e. a different hierarchy of existing works) - either within the MusicBrainz database or by selecting/deselecting the &quot;include collection relations&quot;, partial&quot; or &quot;arrangements&quot; options - then you will need to quit and restart Picard to correctly pick up the new structure. </li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Keep a backup of your picard.ini file (AppData-&gt;Roaming-&gt;MusicBrainz in Windows) in case you erase your settings or Picard crashes and loses them for you.</li></ol> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Artists tab</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">There are five coloured sections as shown in the screen image below:</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src="http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/artists/" /></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Create extra artist metadata&quot; should be selected otherwise this section (and the tag mapping section) will not run. This is the default.</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">(Note that the option &quot;Infer work types...&quot; in version 0.9.1 and prior has moved to the Genres tab and changed somewhat.)</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Work-artist/performer naming options&quot;. This section deals primarily with the application of aliases and credited_as names to replace the MusicBrainz standard names. The first box allows you to choose whether to replace MusicBrainz standard names by aliases - either for all work-artists/performers or only work-artists (writers, composers, arrangers, lyricists etc.). The second box sets the usage of &quot;as-credited&quot; names: the first part of this lists all the places where as-credited names can occur (really!) and the second part allows you to apply these to performing artists and/or work-artists. </li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Please note that, in the current version of this plugin, only aliases and credited_as names which are in the &quot;release XML node&quot; are available (i.e. roughly those relating to the metadata shown in the release overview page in MusicBrainz). So, for example, if a recording is an arrangement of another work and that other work (but not the arrangement) has a composer linked to it, then the composer's alias will not be available (nor is the composer shown on the MB release overview page). In some cases (if appropriate) this can be remedied by adding the relevant link to the lowest-level work.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:40px; margin-right:40px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note regarding aliases and credited-as names:<br />In a MB release, an artist can appear in one of seven contexts. Each of these is accessible in releaseXmlNode and the track and recording contexts are also accessible in trackXmlNode.<br />(They are applied in sequence - e.g. track artist credit will over-ride release artist credit)<br />The seven contexts are:<br />Recording: credited-as and alias (this is applied first as it is the most general - i.e. it may apply to more than one release)<br />Release-group: credited-as and alias<br />Release: credited-as and alias<br />Release relationship: credited-as only (see note)<br />Recording relationship (direct): credited-as only (see note)<br />Recording relationship (via work): credited-as only (see note)<br />Track: credited-as and alias<br />Note: Aliases </span><span style=" font-size:8pt; font-weight:600;">may</span><span style=" font-size:8pt;"> be retrieved for &quot;relationship&quot; artists, but the retrieval is not reliable (MusicBrainz webservice issue)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">N.B. if more than one release is loaded in Picard, any available alias names loaded so far will be available and used. However, as-credited names will only be used from the current release. If you do not want these names to be available then you may need to restart Picard after changing the option settings (otherwise they will still be cached).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">In addition to the above, the main Picard options have an effect on how 'track artists' (or any tags derived from them through tag-mapping) are displayed. In Options-&gt;Metadata, if &quot;Translate artist names...&quot; is selected then the alias will be used for the track artist (or failing that, a name based on the sort-name), rather than the 'as-credited' name. If &quot;Use standardized artist names&quot; is selected then neither the alis nor the 'as-credited' name will be used. In order to facilitate consistency, Classical Extras will save these Picard options along with its own options in specific tags (see &quot;Advanced options&quot; section 5).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">The bottom box then (a) allows a choice as to whether aliases will over-ride as-credited names or vice versa and (b) whether if there are still some names in non-Latin script, whether these should be replaced (this will always remove middle [patronymic] names from Cyrillic-script names [but does not deal fully with other non-Latin scripts]; it is based on the sort names wherever possible).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note that </span><span style=" font-size:8pt; font-weight:600;">none of this processing affects the contents of the &quot;artist or &quot;album_artist&quot; tags</span><span style=" font-size:8pt;">. These tags may be either work-artists or performing artists. Their contents are determined by the standard Picard options &quot;translate artist names&quot; and &quot;use standardized artist names&quot; in Options--&gt;Metadata. If &quot;translate name&quot; is selected, the name will be the alias or (if no alias) the 'unsorted' sort-name; otherwise the name will be the MusicBrainz name if &quot;use standardized artist names&quot; is selected or the as-credited name (if available) if it is not selected.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Recording artist options&quot;. In MusicBrainz, the recording artist may be different from the track artist. For classical music, the MusicBrainz guidelines state that the track artist should be the composer; however the recording artist(s) is/are usually the principal performer(s).<br />Because, in classical music (in MusicBrainz), recording artists will usually be performers whereas track artists are composers, by default, the naming convention for performers (set in the previous section) will be used (although only the as-credited name set for the recording artist will be applied). Alternatively, the naming convention for track artists can be used - which is determined by the main Picard metadata options.</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Classical Extras puts the recording artists into 'hidden variables' (as a minimum) using the chosen naming convention. There is also option to allow you to replace the track artist by the recording artist (or to merge them). The chosen action will be applied to the 'artist', 'artists', 'artistsort' and 'artists_sort' tags. Note that 'artist' is a single-valued string whereas 'artists' is a list and may be multi-valued. Lists are properly merged, but because the 'artist' string may have different join-phrases etc, a merged tag may have the recording artist(s) in brackets after the track artist(s). Obviously, for classical music, if you use &quot;merge&quot; then the artist tag will have both the composer and the recording artists: this may be desirable for simple players (with no composer recognition) but otherwise may look odd.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note that, if the original track artist is required in tag mapping (i.e. as it was before replacement/merge with recording artist), it is available through the hidden variable _cea_MB_artists.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note also that, if @loujin's browser script has been used to fill the recording artist data, this will be the same as the performing artists in the Recording-Artist relationship - i.e. it may be a lengthy list rather than the principal artist for the track.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Other artist options&quot;:</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Modify host tags and include annotations&quot; (Previously called &quot;Include arrangers from all work levels&quot;). This will gather together, for example, any arranger-type information from the recording, work or parent works and place it in the &quot;arranger&quot; tag ('host' tag), with the annotation (see below) in brackets. All arranger types will also be put in a hidden variable, e.g. _cwp_orchestrators. The table below shows the artist types, host tag and hidden variable for each artist type.</span></p> -<table border="0" style=" margin-top:0px; margin-bottom:0px; margin-left:80px; margin-right:0px;" cellspacing="2" cellpadding="0"> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Artist type</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Host tag</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Hidden variable</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">writer</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">composer</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">writers</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">lyricist</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">lyricist</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">lyricists</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">revised by</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">revisors</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">translator</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">lyricist</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">translators</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arrangers</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">reconstructed by</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">reconstructors</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">orchestrator</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">orchestrators</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">instrument arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arrangers (with instrument type in brackets)</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">vocal arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arranger</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">arrangers (with voice type in brackets)</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">chorus master</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">conductor</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">chorusmasters</span></p></td></tr> -<tr> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">concertmaster</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">performer (with annotation as a sub-key)</span></p></td> -<td> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">leaders</span></p></td></tr></table> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">If you want to be more selective in what is included in host tags, then disable this option and use the tag mapping section to get the data from the hidden variables. If you want to add arrangers as composers, do so in the tag mapping section also. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">(Note that Picard does not natively pick up all arrangers, but that the plugin will do so, provided the &quot;Works and parts&quot; section is run.)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Name album as 'Composer Last Name(s): Album Name'&quot; will add the composer(s) last name(s) before the album name, if they are listed as album artists. If there is more than one composer, they will be listed in the descending order of the length of their music on the release. MusicBrainz style is to exclude the composer name unless it is actually part of the album name, but it can be useful to add it for library organisation. The default is checked.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Do not write 'lyricist' tag if no vocal performers&quot;. Hopefully self-evident. This applies to both the Picard 'lyricist' tag and the related internal plugin hidden variables '_cwp_lyricists' etc. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note that the plugin will search for lyricists at all work levels (bottom up), but will stop after finding the first one (unless that was just a translator).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Do not include attributes in an instrument type&quot; (previously just referred to the attribute 'solo'). MusicBrainz permits the use of &quot;solo&quot;, &quot;guest&quot; and &quot;additional&quot; as instrument attributes although, for classical music, its use should be fairly rare - usually only if explicitly stated as a &quot;solo&quot; on the the sleevenotes. Classical Extras provides the option to exclude these attributes (the default), but you may wish to enable them for certain releases or non-Classical / cross-over releases.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Annotations&quot;: The chosen text will be used to annotate the artist type within the host tag (see table above for host tags), but only if &quot;Modify host tags&quot; is selected.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Please note that the use of the word &quot;master&quot; is the MusicBrainz term and is not intended to be gender-specific. Users can specify whatever text they please.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Lyrics&quot;. <span style=" font-weight:600;">Please note that this section operates on the underlying input file tags, not the Picard-generated tags (MusicBrainz does not have lyrics)</span> Sometimes &quot;lyrics&quot; tags can contain album notes (repeated for every track in an album) as well as track notes and lyrics. This section will filter out the common text for a release and place it in a different tag from the text which is unique to each track.</li></ol> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Split lyrics tag&quot;: enables this section.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Incoming lyrics tag&quot;: The name of the lyrics file tag in the input file (normally just 'lyrics').</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Tag for album notes&quot;: The name of the tag where common text should be placed.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Tag for track notes&quot;: The name of the tag where notes/lyrics unique to a track should be placed.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note that if the 'output' tags are not specified, then internal 'hidden' variables will still be available for use in the tag-mapping section (called album_notes and track_notes).</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Tag mapping tab</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">There are two coloured sections as shown in the screen image below:</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src="http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/tag-mapping/" /></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Note that the &quot;Create extra artist metadata&quot; option needs to be selected on the Artist tab for these sections to run.</span></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Initial tag processing&quot;: This takes place before any of the detailed tag mapping in the second section.</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Remove Picard-generated tags before applying subsequent actions?&quot;. Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options-&gt;Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;List existing file tags which will be appended ...&quot;: This refers to the tags which already exist on files which have been matched to MusicBrainz, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if 'Clear existing tags' is specified in the main Options-&gt;Tags screen) or keep them (if 'Preserve these tags...' is specified after the 'Clear existing tags' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below) or otherwise used. List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if &quot;Clear existing tags&quot; is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular &quot;is_classical&quot; (which is set by SongKong to '1' if the track is deemed to be classical, based on an extensive database) is used to add 'classical' to the variable &quot;_cea_worktype&quot;, if &quot;Infer work types&quot; is selected in the first section of the Artists tab. If you include &quot;is_classical&quot; in this list then any files which have &quot;is_classical&quot; = 1 will be treated as being classical, regardless of genre.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note that if &quot;Split lyrics tag&quot; is specified (see the Artists tab), then the tag named there will be included in the &quot;...existing file tags...&quot; list and does not need to be added in this section.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Clear any previous file tags...&quot;: This operates in an almost similar way to the main Picard option (Options-&gt;Tags-&gt;&quot;Clear existing tags&quot;). All existing file tags will be cleared </span><span style=" font-size:8pt; font-weight:600;">unless</span><span style=" font-size:8pt;"> they are in the main Picard &quot;Preserve tags...&quot; option or the &quot;...existing file tags...&quot; list. The main differences from the basic Picard option are that (a) artwork is always preseved - i.e. this largely addresses </span><a href="https://tickets.metabrainz.org/browse/PICARD-257"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">PICARD-257</span></a><span style=" font-size:8pt;"> and (b) the tags that are not kept are </span><span style=" font-size:8pt; font-weight:600;">not shown as deleted</span><span style=" font-size:8pt;"> in the bottom pane of Picard; however, a warning tag is written.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tag map details&quot;. This section permits the contents of any hidden variable or tag to be written to one or more tags.</li></ol> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Sources</span>: Some of the most useful sources are available from the drop-down list. Otherwise they can simply be typed in the box. Click on the &quot;source from&quot; button to enable entry. Some useful names are:</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 3;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">soloists : List of performers (with instruments in brackets), who are NOT ensembles or conductors, separated by semi-colons. Note they may not strictly be &quot;soloists&quot; in that they may be part of an ensemble.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">soloist_names : Names of the above (i.e. no instruments).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">vocalists / instrumentalists / other_soloists : Soloists who are vocalists, instrumentalists or not specified respectively.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">vocalist_names / instrumentalist_names : Names of vocalists / instrumentalists (i.e. no instrument / voice).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ensembles : List of performers who are ensembles (with type / instruments - e.g. &quot;orchestra&quot; - in brackets), separated by semi-colons.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ensemble_names : Names of the above (i.e. no instruments).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">album_soloists : Sub-list of soloist_names who are also album artists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">album_conductors : List of conductors who are also album artists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">album_ensembles: Sub-list of ensemble_names who are also album artists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">album_composers : List of composers who are also album artists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">album_composer_lastnames : Last names of composers of ANY track on the album who are also album artists. This is the source used to prefix the album name (when that option is selected).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">support_performers : Sub-list of soloist_names who are NOT album artists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">composers : Note that, if &quot;Fix cyrillic names&quot; in the last section is checked, this is based on sort name, to avoid non-latin language problems (if translation is not already made via locale choices).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">conductors : Note that, if &quot;Fix cyrillic names&quot; in the last section is checked, this is based on sort name, to avoid non-latin language problems (if translation is not already made via locale choices).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">arrangers : Includes all arrangers and instrument arrangers (except orchestrators) - if option above selected - standard Picard tag omits some.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">orchestrators : Arrangers who are orchestrators.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">leaders : AKA concertmasters.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">chorusmasters : as distinct from conductors (chorus masters may rehearse the choir but not conduct the performance).</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">Note that the Classical Extras sources for all artist types are spelled in the plural (to differentiate from the native Picard tags). Most of the names are for artist data and are sourced from hidden variables (prefixed with &quot;_cea_&quot; or &quot;_cwp_&quot;). In specifying the source, the prefix is not necessary - e.g. &quot;arrangers&quot; will pick up all data in _cea_arrangers and _cwp_arrangers (covering those with recording and work relationships respectively). Using the prefix will only get the specific variable.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">In addition, the drop-down contains some typical combinations of multiple sources (see note on multiple sources below).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">Any Picard tag names can also be typed in as sources. Any hidden variables may also be used. Any source names which are prefixed by a backslash will be treated as string constants; blanks may also be used.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">It is possible to specify multiple sources. If these are separated by a comma, then each will be appended to the mapped tag(s) (if not already filled or if not &quot;conditional&quot;). So, for example, a source of &quot;album_soloists, album_conductors, album_ensembles&quot; mapped to a tag of &quot;artist&quot; with &quot;conditional&quot; ticked will fill artist (if blanked) by album_soloists, if any, otherwise album_conductors etc. Sources separated by a + will be concatenated before being used to fill the mapped tags. The concatenated result </span><span style=" font-size:8pt; font-weight:600;">will only be applied if the contents of each of the sources to be concatenated is non-blank</span><span style=" font-size:8pt;"> (note that this constraint only applies to </span><span style=" font-size:8pt; font-weight:600;">concatenation</span><span style=" font-size:8pt;"> of multiple sources). No spaces will be added on concatenation, so these have to be added explicitly by concatenating &quot;\ &quot;. So, for example &quot;ensemble_names + \ (conducted by + conductors +\), ensemble_names&quot;, with &quot;Conditional&quot; selected, will yield something like &quot;BBC Symphony Orchestra (conducted by Walter Weller)&quot; or just &quot;BBC Symphony Orchestra&quot; if there is no conductor. </span><span style=" font-size:8pt; font-weight:600;">Do not use any commas in text strings</span><span style=" font-size:8pt;">.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">Another example: to add the leader's name in brackets to the tag with the performing orchestra, put &quot;\ (leader +leaders+\)&quot; in the source box and the tag containing the orchestra in the tag box. If there is no leader, the text will not be appended.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">The tag mapping section is not restricted to artist metadata. For example, in the default drop-down list are &quot;work_type&quot; which only has content if the &quot;Infer work types&quot; box in the first section of the Artists tab is checked, and &quot;release&quot; which contains the album name before prefixing with composer last names if that option was chosen.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Tags</span>: Enter the (comma-separated) &quot;destination&quot; tag names into which the sources should be written (case sensitive). Note that this will result in the source data being APPENDED in the tag - it will not overwrite the existing contents. Check &quot;Conditional?&quot; if the tag is only to be updated if it is previously blank (all non-empty sources in the current line will be applied). The lines will be applied in the order shown. Users should be able to achieve most requirements via a combination of blanking tags, using the right source order and &quot;conditional&quot; flags. For example, to overwrite a tag sourced from &quot;composer&quot; with &quot;conductor&quot;, specify &quot;conductor&quot; first, then &quot;composer&quot; as conditional. Note that, for example, to demote the MB-supplied artist to only appear if no other listed choices are present, blank the artist tag and then add it as a conditional source at the end of the list.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">&quot;Also populate sort tags&quot;</span>: If a sort tag is associated with the source tag then the sort names will be placed in a sort tag corresponding to the destination tag. Note that the only explicit sort tags written by Picard are for artist, albumartist and composer. Piacrd also writes hidden variables '_artists_sort' and 'albumartists_sort' (note the plurals - these are the sort tags for multi-valued alternatives 'artists' and '_albumartists'). To be consistent with this approach, the plugin writes hidden variables for other tags - e.g. '_arranger_sort'. The plugin also writes hidden sort variables for the various hidden artist variables - e.g. '_cwp_librettists' has a matching sort variable '_cwp_librettists_sort'. Therefore most artist-type sources <span style=" font-weight:600;">will</span> have a sort tag/variable associated with them and these will be placed in a destination sort tag if this option is selected - <span style=" font-weight:600;">in other words, selecting this option will cause most destination tags to have associated sort tags. Furthermore, any hidden sort variables associated with tags which are not listed explicitly in the tag mapping section will also be written out as tags</span> (i.e. even if the related tags are not included as destination tags). Note, however, that composite sources (e.g. &quot; ensemble_names + \; + conductors&quot;) do not have sort tags associated with them.</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">If this option is not selected, no additional sort tags will be written, but the hidden variables will still be available, so if a sort tag is required explicitly, just map the sort tag directly - e.g. map 'conductors_sort' to 'conductor_sort'.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">More complex operations can be built using tagger scripts. If required, these can be set to run conditionally by setting a tag or hidden variable in this section and then testing it in the script.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Work and parts tab</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">There six coloured sections as shown in the screen print below:</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src="http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/work-parts/" /></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Include all work levels&quot; should be selected otherwise this section will not run. This is the default.</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Include collection relationships&quot; (selected by default) will include parent works where the relationship has the attribute 'part of collection'. See </span><a href="https://community.metabrainz.org/t/levels-in-the-structure-of-works/293047/109"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">Discussion</span></a><span style=" font-size:8pt;"> for the background to this. Note that only &quot;work&quot; entity types will be included, not &quot;series&quot; entities. If this option is changed, it will not take effect on releases already loaded in Picard - you will need to quit and restart. PLEASE BE CONSISTENT and do not use different options on albums with the same works, or the results may be unexpected.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Use cache (if available)&quot; prevents excessive look-ups of the MB database. Every look-up of a parent work needs to be performed separately (hopefully the MB database might make this easier some day). Network usage constraints by MB means that each look-up takes a minimum of 1 second. Once a release has been looked-up, the works are retained in cache, significantly reducing the time required if, say, the options are changed and the data refreshed. However, if the user edits the works in the MB database then the cache will need to be turned off temporarily for the refresh to find the new/changed works. Also some types of work (e.g. arrangements) will require a full look-up if options have been changed. </span><span style=" font-size:8pt; font-weight:600;">Do not leave this option turned off</span><span style=" font-size:8pt;"> as it will make the plugin slower and may cause problems. This option will always be set on when Picard is started, regardless of how it was left when it was last closed.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tagging style&quot;. This section determines how the hierarchy of works will be sourced.</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Works source</span>: There are 3 options for determing the principal source of the works metadata</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 3;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use only metadata from title text&quot;. The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use only metadata from canonical works&quot;. The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use canonical work metadata enhanced with title text&quot;. This supplements the canonical data with text from the titles <span style=" font-weight:600;">where it is significantly different</span>. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager).</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><img src="http://music.highmossergate.co.uk/images/Respighi.jpg" /></p> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Source of canonical work text</span>. Where either of the second two options above are chosen, there is a further choice to be made: </li></ul> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 3;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Full MusicBrainz work hierarchy&quot;. The names of each level of work are used to populate the relevant tags. E.g. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast, JB 1:112&quot;, not &quot;Má vlast&quot;. So, while accurate, this option might be more verbose.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Consistent with lowest level work description&quot;. The names of the level 0 work are used to populate the relevant tags. I.e. if &quot;Má vlast: I. Vyšehrad, JB 1:112/1&quot; (level 0) is part of &quot;Má vlast, JB 1:112&quot; (level 1) then the parent work will be tagged as &quot;Má vlast&quot;, not &quot;Má vlast, JB 1:112&quot;. This frequently looks better, but not always, <span style=" font-weight:600;">particularly if the level 0 work name does not contain all the parent work detail</span>. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the &quot;warning&quot; tag.</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Strategy for setting style:</span><span style=" font-size:8pt;"> </span><span style=" font-size:8pt; font-style:italic;">It is suggested that you start with &quot;extended/enhanced&quot; style and the &quot;Consistent with lowest level work description&quot; as the source (this is the default). If this does not give acceptable results, try switching to &quot;Full MusicBrainz work hierarchy&quot;. If the &quot;enhanced&quot; details in curly brackets (from the track title) give odd results then switch the style to &quot;canonical works&quot; only. Any remaining oddities are then probably in the MusicBrainz data, which may require editing.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Aliases&quot;</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Replace work names by aliases&quot; will use </span><span style=" font-size:8pt; font-weight:600;">primary</span><span style=" font-size:8pt;"> aliases for the chosen locale instead of standard MusicBrainz work names. To choose the locale, use the drop-down under &quot;translate artist names&quot; in the main Picard Options--&gt;Metadata page. Note that this option is not saved as a file tag since, if different choices are made for different releases, different work names may be stored and therefore cannot be grouped together in your player/library manager. The sub-options then allow either the replacement of all work names, where a primary alias exists, just the replacement of work names which are in non-Latin script, or only replace those which are flagged with user &quot;Folksonomy&quot; tags. The tag text needs to be included in the text box, in which case flagged works will be 'aliased' as well as non-Latin script works, if the second sub-option is chosen. Note that the tags may either be anyone's tags (&quot;Look in all tags&quot;) or the user's own tags. If selecting &quot;Look in user's own tags only&quot; you </span><span style=" font-size:8pt; font-weight:600;">must</span><span style=" font-size:8pt;"> be logged in to your MusicBrainz user account (in the Picard Options-&gt;General page), otherwise repeated dialogue boxes may be generated and you may need to force restart Picard.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tags to create&quot; sets the names of the tags that will be created from the sources described above. All these tags will be blanked before filling as specified. Tags specified against more than one source will have later sources appended in the sequence specified, separated by separators as specified.</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Work tags</span>:</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 3;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tags for Work - for software with 2-level capability&quot;. Some software (notably Muso) can display a 2-level work hierarchy as well as the work-movement hierarchy. This tag can be use to store the 2-level work name (a double colon :: is used to separate the levels within the tag).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tags for Work - for software with 1-level capability&quot;. Software which can display a movement and work (but no higher levels) could use any tags specified here. Note that if there are multiple work levels, the intermediate levels will not be tagged. Users wanting all the information should use the tags from the previous option (but it may cause some breaks in the display if levels change) - alternatively the missing work levels can be included in a movement tag (see below).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Tags for top-level (canonical) work&quot;. This is the top-level work held in MB. This can be useful for cataloguing and searching (if the library software is capable).</li></ul> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Movement/Part tags</span>: (a) &quot;Tags for(computed) movement number&quot;. This is not necessarily the embedded movt/part number, but is the sequence number of the movement within its parent work <span style=" font-weight:600;">on the current release</span>.<br />(b) &quot;Tags for Movement - excluding embedded movt/part numbers&quot;. As below, but without the movement part/number prefix (if applicable)<br />(c) &quot;Tags for Movement - including embedded movt/part numbers&quot;. This tag(s) will contain the full lowest-level part name extracted from the lowest-level work name, according to the chosen tagging style.<br />For options (b) and (c), the tags can either be filled &quot;for use with multi-level work tags&quot; or &quot;for use with 1-level work tags (intermediate works will prefix movement)&quot; - or different tags for each column. The latter option will include any intermediate work levels which are missing from a single-level work tag. Use different tag names for these, from the multi-level version, otherwise both versions will be appended, creating a multi-valued tag (a warning will be given).<br />Note that if a tag is included in (a) and either of (b) or (c), the movement number will be prepended at the beginning of the tag, followed by the selected separator.</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Strategy for setting tags:</span><span style=" font-size:8pt;"> </span><span style=" font-size:8pt; font-style:italic;">It is suggested that initially you just use the multi-level work tag and related movement tags, even if your software only has a single-level work capability. This may result in work names being repeated in work headings, but may look better than the alternative of having work names repeated in movement names. This is the default.</span><span style=" font-size:8pt;"> </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt; font-style:italic;">If this does not look good you can then compare it with the alternative approach and change as required for specific releases. If your software does not have any &quot;work&quot; capability, then you can still get the full work details by, for example, specifying &quot;title&quot; as both a work and a movement tag.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Partial recordings, arrangements and medleys&quot; gives various options where recordings are not just simply of a named complete work.</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Partial recordings</span>: If this option is selected, partial recordings will be treated as a sub-part of the whole recording and will have the related text (in the adjacent box) included in its name. Note that this text is at the end of the canonical name, but the latter will probably be stripped from the sub-part as it duplicates the recording work name; any title text will be appended to the whole. Note that, if &quot;Consistent with lowest level work description&quot; is chosen in section 2, the text may be treated as a &quot;prefix&quot; similar to those in the &quot;Advanced&quot; tab. If this eliminates other similar prefixes and has unwanted effects, then either change the desired text slightly (e.g. surround with brackets) or use the &quot;Full MusicBrainz work hierarchy&quot; option in section 2. Note that similar text between the partial work and its 'parent' will be removed which will frequently result in no text other than the specified 'partial text', unless extended metadata is used resulting in appended text in {}.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Arrangements</span>: If this option is selected, works which are arrangements of other works will have the latter treated in the same manner as &quot;parent&quot; works, except that the arrangement work name will be prefixed by the text provided. Note that similar text between the arranged work and its parent will be removed unless this results in no text, in which case a stricter comparison (as for the derivation of 'part' names from works) will be used.</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Important note:</span><span style=" font-size:8pt;"> </span><span style=" font-size:8pt; font-style:italic;">If the Partial or Arrangement options are changed (i.e. selected/deselected) then quit and restart Picard as the work structure is fundamentally different. If the related text (only) is changed then the release can simply be refreshed.</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Medleys</span> These can occur in two ways in MusicBrainz: (a) the recording is described as a &quot;medley of&quot; a number of works and (b) the track is described as (more than one) &quot;medley including a recording of&quot; a work. See <a href="https://musicbrainz.org/release/393913a2-7fde-4ed5-8be6-ca5c2c0ccf0d"><span style=" text-decoration: underline; color:#0000ff;">Homecoming</span></a> for examples of both (tracks 8, 9 and 11). In the first case, the specified text will be included in brackets after the work name, whereas in the second case, the track will be treated as a recording of multiple works and the specified text will appear in the parent work name.</li></ul> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;SongKong-compatible tag usage&quot;.</li></ol> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Use work tags on file (no look up on MB) if Use Cache selected&quot;: This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, &quot;Use cache&quot; also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. </span><span style=" font-size:8pt; font-weight:600;">In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Write SongKong-compatible work tags&quot; does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">The default for both these options is unchecked.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Note that Picard and SongKong use the tag musicbrainz_workid to mean different things. If Picard has overwritten the SongKong tag (not a problem if this plugin is used) then a warning will be given and the works will be looked up on MusicBrainz. Also note that once a release is loaded, subsequent refreshes will use the cache (if option is ticked) in preference to the file tags.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Note for iTunes users:</span><span style=" font-size:8pt;"> </span><span style=" font-size:8pt; font-style:italic;">iTunes and Picard do not work well together. iTunes can display work and movement for m4a(mp4) files, but Picard does not write the movement tag. To work round this, write the movement to the &quot;subtitle&quot; tag assuming that is not otherwise used, and use a simple Mp3tag action to convert it to MOVEMENTNAME before importing to iTunes. If you are writing to a FLAC file which will subsequently be converted to m4a then different tag names may be required; e.g. using dBpoweramp, write the movement to &quot;movement name&quot;. In both cases use &quot;work&quot; for the work. To store the top_work, use &quot;grouping&quot; if writing directly to m4a, but &quot;style&quot; if writing to FLAC followed by dBpoweramp conversion. You can put multiple tags into the boxes described above so that your options are multi-purpose. N.B. if work tags are specified and the work has at least one level (i.e. at least work: movement), then the tag &quot;show work movement&quot; will be set to 1. This is used by iTunes to trigger the hierarchical display and should work both directly with m4a files and indirectly via files which are subsequently converted.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Genres etc. tab</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This section is dependent on both the artists and workparts sections. If either of those sections are not run then this section will not operate correctly. At the very top of the tab is a checkbox &quot;Use Muso reference database...&quot;. For Muso users, selecting this enables you to use reference data for genres, composers and periods which have been entered in Muso's &quot;Options-&gt;Classical Music&quot; section. Regardless as to whether this is selected, there are then three main coloured sections, each with a number of subsections. The details in each section differ depending on whether the &quot;Muso&quot; option is selected. The screen print below shows the options assuming it is not selected (differences occurring when &quot;Muso&quot; is selected are discussed later):</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src="http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/genres-plain/" /></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Genres&quot;. Two separate tags may be used to store genre information, a main genre tage (usually just &quot;genre&quot;) and a sub-genre tag. These need to be specified at the top of the section. If either is left blank then the related processing will not run.</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Source of genres</span> Any or all of four sources may be selected. In each case, any values found are treated as &quot;candidate genres&quot; - they will only be applied to the specified genre and sub-genre tags in accordance with the criteria in the &quot;allowed genres&quot; section (see below).</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">(a) &quot;Existing file tag&quot;. The contents of the existing file tag (as specified above - main genre tag only) will be included as candidate genres.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">(b) &quot;Folksonomy work tags&quot;. This will use the folksonomy tags for </span><span style=" font-size:8pt; font-weight:600;">works</span><span style=" font-size:8pt;"> (including parent works) as a possible source of genres. To use the folksonomy tags for </span><span style=" font-size:8pt; font-weight:600;">releases/tracks</span><span style=" font-size:8pt;">, select the main Picard option in Options-&gt;Metadata-&gt;&quot;Use folksonomy tags as genre&quot;. Again (unlike vanilla Picard) these are candidate genres, subject to matching allowed genres.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">(c) &quot;Work-type&quot;. The work-type attribute of works or parent works will be used.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">(d) &quot;Infer from artist metadata&quot;. This option was on the artist tab in version 0.9.1 and prior. Owing to the additional genre processing now available, the operation of this option is slightly restricted compared to the earlier versions. It attempts to create candidate genres based on information in the artist-related tags. Values provided are: Orchestral, Concerto, Choral, Opera, Duet, ,Trio, Quartet, Chamber music, Aria ('classical values') and Vocal, Song, Instrumental ('generic values'). If the track is a recorded work and the track artist is the composer (i.e. MusicBrainz 'classical style'), the candidate genre values will also include &quot;Classical&quot;. The 'classical values' will only be included as candidate genres if the track is deemed to be 'classical' by some part of the genre processing section.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Allowed genres</span> Four boxes are provided for lists of genres which are &quot;allowed&quot; to appear in the specified tags. Each list should be comma-separated (and no commas in any genre name). Candidate genres matching those in a &quot;main genre&quot; box will be added to the specified main genre tag. Similarly for sub-genres. If a candidate genre matches a 'classical genre' (in one of the top two boxes), then the track will be deemed to be &quot;Classical&quot; (see next part for more details). </li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">If none of the boxes are filled, then all genres found will be included.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">If one box is filled (e.g. &quot;classical main genres&quot;) and there is a matching genre found, then remaining genres will only be included if they match genres in that box or the related sub-genre box. So in this case if &quot;classical sub-genres&quot; is filled and &quot;general sub-genres&quot; is not, then only matching genres will be included.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">If both &quot;classical&quot; and &quot;general&quot; main genre boxes are filled, then only genres matching those boxes or &quot;classical sub-genres&quot; will be included.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">You may also enter a genre name to be used if no matching main genre is found (otherwise the tag will be blank). </span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 3;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">&quot;Classical&quot; genre</span> Normally (i.e. the default) a work will only be deemed to be 'classical' if it is inferred from the MusicBrainz style (see &quot;source of genres&quot;) or if a candidate genre matches a &quot;Classical&quot; genre or sub-genre list. However, you may select that all tracks are 'classical' regardless. There is also an option to exclude the word &quot;Classical&quot; from any genre tag, but still treat the work as classical. If a work is deemed to be classical, a tag may be written with a specified value as set out in the last two boxes of this section. For example, to be consistent with SonKong/Jaikoz, you could set &quot;is_classical&quot; to &quot;1&quot;.</li></ul> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Instruments and keys&quot;.</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Instruments</span> Specify the tag name you wish instrument names to appear in. Instruments will be sourced from performer relationships. Instrument names may either be the standard MusicBrainz names or the &quot;credited as&quot; names in the performer relationship, or both. Vocal types are treated similarly to instruments. (Note that, in v0.9.1 and prior, instruments were written to the same tag as inferred genres. If you wish to continue this, then you may use the same tag name here as for the genre tag.)</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Keys</span> Specify the tag name in which you wish the key signatures of works to appear. Keys will be obtained from all work levels (assuming these have been looked up): for example, Dvořák's Largo From the New World will be shown as D♭ major, C# minor (the main keys of the movement) and E minor (the home key of the overall work).</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Include key(s) in work names&quot; gives the option to include the key signature for a work in brackets after the name of the work in the metadata. Keys will be added in the appropriate levels: e.g. Dvořák's New World Symphony will get (E minor) at the work level, but only movements with different keys will be annotated viz. &quot;II. Largo (D-flat major, C-Sharp minor)&quot;. The default sub-option is to only add these details if the key signature is missing from the work title (other sub-options are to never or to always include the information).</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Periods and dates&quot;.</li></ol> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Work dates</span> Specify the tag name to hold work dates. Work dates will be given as a &quot;year&quot; value only, e.g. &quot;1808&quot; or a range: &quot;1808-1810&quot;. The sources of these dates is specified in the next part. Only work dates for the lowest-level work will be used - i.e. if the movement has a composed date(s), this will be used, otherwise the the dates from the parent work will be used (if available).</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Source of work dates&quot;. Select which sources to use - from composed, published and premiered, then decide whether to use them in preferential order (e.g. if &quot;composed date&quot; exists, then the others will not be used) or to show them all.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">&quot;Include workdate in work name ...&quot; operates analogously to &quot;Include key(s) in work names&quot; described above. (Work dates will be used in preference order, i.e. composed - published - premiered, with only the first available date being shown).</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Periods</span> This section will use work dates, where available, to determine the &quot;classical period&quot; to which it belongs, by means of a &quot;period map&quot; (Muso users can also use composer dates - see below). </li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Specify the tag name to hold the period data. The period map should then be entered in the format &quot;Period name, Start_year, End_year; Period name2, Start_year, End_year;&quot; etc. Periods may overlap. Do not use commas or semi-colons within period names. Start and end years must be integers.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Genres etc. tab - Muso-specific processing</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Users of </span><a href="http://klarita.net/muso.html"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">Muso</span></a><span style=" font-size:8pt;"> have additional capabilities, illustrated in the following screen, which appear when the option &quot;Use Muso reference database ...&quot; is selected at the top of the tab.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src="http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/genres-muso/" /></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For these options to work, the path/name of the Muso reference database needs to be specified on the advanced tab. The default path is &quot;C:\Users\Public\Music\muso\database&quot; and the default filename is &quot;Reference.xml&quot;. The additional options are as follows.</span></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use Muso classical genres&quot;. If this is selected, the box for classical main genres is eliminated and the genre list from Muso's &quot;Tools-&gt;Options-&gt;Classical Music-&gt;Classical Music Genres&quot; is used instead.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use Muso composer list to determine if classical&quot;. If the composer name is in Muso's list &quot;Tools-&gt;Options-&gt;Classical Music-&gt;Composer Roster&quot;, then the work will be deemed to be classical. If this option is selected, a further option appears to &quot;Treat arrangers as for composers&quot; - if selected then arrangers will also be looked up in the roster.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use Muso composer dates (if no work date) to determine period&quot;. The birth date + 20 -&gt; death dates of Muso's composer roster will be used to assign periods if no work date is available. If this option is selected, a further option appears to &quot;Treat arrangers as for composers&quot; - if selected then arrangers' working lives will also be used to determine periods.</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">(This might be replaced / supplemented by MusicBrainz in the future, but would involve another 1-second lookup per composer).</span></p> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Use Muso map&quot;. Replace the period map with the one in Muso at &quot;Tools-&gt;Options-&gt;Classical Music-&gt;Classical Music Periods&quot;</li></ol> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Note that non-Muso users may also use this functionality, if they wish, by manually creating a reference xml file with the relevant tags, e.g.:</span></p> -<p style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;ClassicalGenre&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Name&gt;Cantata&lt;/Name&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;/ClassicalGenre&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Composer&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Name&gt;Max REGER&lt;/Name&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Birth&gt;1873&lt;/Birth&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Death&gt;1916&lt;/Death&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;/Composer&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;ClassicalPeriod&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Name&gt;Early Romantic&lt;/Name&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;Start_x0020_Date&gt;1800&lt;/Start_x0020_Date&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;End_x0020_Date&gt;1850&lt;/End_x0020_Date&gt; </span></p> -<p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:8pt;">&lt;/ClassicalPeriod&gt; </span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Advanced tab</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Hopefully, this tab should not be much used. In any case, it should not need to be changed frequently. There are seven sections as shown in the sceeen print below:</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src="http://highmossergate.co.uk/digitalsymphony/classical-extras-screenshots/advanced/" /></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;General&quot;. There is only one checkbox - &quot;Do not run Classical Extras for tracks where no pre-existing file is detected (warning tag will be written)&quot;. This option will disable Classical Extras processing if no file is present; this means (for example) that single discs from box sets can be loaded without incurring the additional processing overhead (work look-ups etc.) for all the other discs. Also if a compilation album is loaded, where the tracks are on multiple releases, the plugin will only process the release tracks which match. If a file is present but it does not yet have a MusicBrainz trackid tag, then it will initally be treated in the same way as a non-existent file; however, after the initial loading it will (if matched by Picard) be given a MB trackid and &quot;refreshing&quot; the release will result in any such tracks being processed by Classical Extras, while the unmatched tracks are left untouched.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Artists&quot;. This has only one subsection - &quot;Ensemble strings&quot; - which permits the listing of strings by which ensembles of different types may be identified. This is used by the plugin to place performer details in the relevant hidden variables and thus make them available for use in the &quot;Tag mapping&quot; tab as sources for any required tags. If it is important that only whole words are to be matched, be sure to include a space after the string.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Work levels&quot;. This section has parameters applicable to the &quot;works and parts&quot; functions.</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Max number of re-tries to access works (in case of server errors)</span>. Sometimes MB lookups fail. Unfortunately Picard (currently) has no automatic &quot;retry&quot; function. The plugin will attempt to retry for the specified number of attempts. If it still fails, the hidden variable _cwp_error will be set with a message; if error logging is checked in section 4, an error message will be written to the log and the contents of _cwp_error will be written out to a special tag called &quot;001_errors&quot; which should appear prominently in the bottom pane of Picard. The problem may be resolved by refreshing, otherwise there may be a problem with the MB database availability. It is unlikely to be a software problem with the plugin.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Removal of common text between parent and child works</span>. This section controls the naming of parts, by stripping parent text from the work name. If the work begins with the parent name followed by punctuation then the common text will always be stripped to give the part name (even if there are punctuation or some other minor differences). </li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:2; text-indent:0px;"><span style=" font-size:8pt;">However, common text which is not followed by punctuation or which is not at the start may also be stripped: to prevent this, set &quot;Minimum number of similar words required before eliminating (other than at start)&quot; to zero. Otherwise common text longer than the specified number of words (default = 2) will be stripped. </span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">How title metadata should be included in extended metadata</span>. This subsection contains various parameters affecting the processing of strings in titles. (Some of it also affects the elimination of common text between parent and child works referred to above). Because titles are free-form, not all circumstances can be anticipated. If pure canonical works are used (&quot;Use only metadata from canonical works&quot; and, if necessary, &quot;Full MusicBrainz work hierarchy&quot; on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below:</li></ul> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 3;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Proximity of new words&quot;. When using extended metadata - i.e. &quot;metadata enhanced with title text&quot;, the plugin will attempt to remove similar words in the canonical work name (in MusicBrainz) and the title before extending the canonical name. After removing such words, a rather &quot;bitty&quot; result may occur. To avoid this, any new words with the specified proximity will have the words between them (or up to the start/end) included even if they repeat words in the work name.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Treat hyphenated words as two words for comparison purposes&quot; (default = True). In comparing words, hyphenated words will be considered as separte words unless this option is deselected.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Proportion of a string to be matched ... for it to be considered essentially similar...&quot; (default = 66%). If the title and work descriptions are largely the same then the title text will not be used to extend the work text, even if there are some new words.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Prepositions&quot;. Words listed here will not generally be treated as &quot;new&quot; (i.e. if they are in the title text but not in the work text, they will not be included in the &quot;extended&quot; text) unless they precede a new word which is not itself a preposition. Note that, although the term &quot;preposition&quot; is used here, because that is the obvious usage, any word can be listed. A group of more than one such words will be treated as new if they are not in the work text and they precede a new word which is not listed as a preposition.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Prefixes&quot;. When using &quot;metadata from titles&quot; or extended metadata, the structure of the works in MusicBrainz is used to infer the structure in the title text, so strings that are repeated between tracks which are part of the same MusicBrainz work will be treated as &quot;higher level&quot;. This can lead to anomalies if, for instance, the titles are &quot;Work name: Part 1&quot;, &quot;Work name: Part 2&quot;, &quot;Part&quot; is repeated and so will be treated as part of the parent work name. Specifying such words in &quot;Prefixes&quot; will prevent this.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Synonyms&quot;. These words will be considered equivalent when comparing work name and title text. Thus if one word appears in the work name, that and its synonym will be removed from the title in extending the metadata (subject to the proximity setting above). Note that only single word synonyms are allowed, with no duplicates or punctuation. Each entry should be a pair (2-tuple) in the form <span style=" font-style:italic;">(key word, equivalent word)</span> - no quote marks are necessary. Each pair should be separated by a forward slash - /.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Replacements&quot;. These words/phrases will be replaced in the title text in extended metadata, regardless of the text in the work name. Each entry should be a pair (2-tuple) in the form <span style=" font-style:italic;">(original text, replacement text)</span> - no quote marks are necessary. Each pair should be separated by a forward slash - /. If required the original text (to be replaced) can be a regular expression, in which case it must be surrounded by double exclamation marks, thus: <span style=" font-style:italic;">(!!regex here!!, replacement text here)</span></li></ul> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Genres etc. ...&quot;. This is only required if Muso-specific options are used for genres/periods. Specify the path and file name for the reference database (the default is the Muso default for a shared database). Note that the database is only loaded when Picard starts so you will need to restart Picard is these options are changed.</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Logging options&quot;. These options are in addition to the options chosen in Picard's &quot;Help-&gt;View error/debug log&quot; settings. They only affect messages written by this plugin. To enable debug messages to be shown in the Picard log, the flag needs to be set here and &quot;Debug mode&quot; needs to be turned on in the log. <span style=" font-weight:600;">It is strongly advised to keep the &quot;debug&quot; flag unchecked unless debugging is required</span> as it slows up processing and may even cause Picard to hang if there is a large number of files (better to use the 'info' option - see below). The &quot;error&quot; and &quot;warning&quot; flags should be left checked, unless it is required to suppress messages (the messages are also written to the tags 001_errors and 002_warnings).</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">As well as the main Picard log, a custom logging function is provided. This may be either &quot;basic&quot; or &quot;full&quot;. If &quot;basic&quot; is selected, a file &quot;session.log&quot; will be written (over-written each session) to a &quot;Classical Extras&quot; directory inside the same directory as the plugins folder. (Note - the easy way to find this directory is to select options--&gt;plugins in Picard and the &quot;Open plugins folder&quot;, then go up one level). The session log gives a processing summary for each release and includes errors, warnings and debug messages if those options have been selected.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">If &quot;full&quot; is selected, all errors, warnings and debugs will be written, along with additional debugging messages, to a custom log file for each release processed. These files are stored in the &quot;Classical Extras&quot; directory inside the same directory as the plugins folder. The log file for a release is named using the release MBID. Debugging from these files requires an understanding of the source code.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">Selecting &quot;full&quot; will slow Picard, but should not normally result in hanging or crashing.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Save plugin details and options in a tag?&quot; can be used so that the user has a record of the version of Classical Extras which generated the tags and which options were selected to achieve the resulting tags. Note that the tags will be blanked first so this will only show the last options used on a particular file. The same tag can be used for both sets of options, resulting in a multi-valued tag. All the options in the Classical Extras UI are saved <span style=" font-weight:600;">except</span> those which are asterisked.</li> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">The tag contents are in dict format. The options in these tags can then be used to over-ride the displayed options subsequently (see below).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">N.B. The &quot;Tag name for artist/misc. options&quot; also saves the Picard options for 'translate_artist_names' and 'standardize_artists' as these interact with the Classical Extras options.</span></p> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&quot;Over-ride plugin options displayed in UI with options from local file tags&quot;. If options have previously been saved (see above), selecting these will cause the saved options to be used in preference to the displayed options. The displayed options will not be affected and will be used if no saved options are present. The default is for no over-ride. If &quot;Artists options&quot; over-ride is selected then a sub-option to over-ride (or not) the &quot;Tag details options&quot; is available; this refers to just the detailed tag map in the second box in the tag-mapping tab.</li></ol> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt; font-weight:600; font-style:italic;">Note that</span><span style=" font-size:8pt; font-weight:600;"> </span><span style=" font-size:8pt; font-weight:600; font-style:italic;">very occasionally (if the tag containing the options has been corrupted) use of this option may cause an error. In such a case you will need to deselect the &quot;over-ride&quot; option and set the required options manually; then save the resulting tags and the corrupted tag should be over-written</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;"><span style=" font-size:8pt;">The last checkbox, &quot;Overwrite options in Options Pages&quot;, is for </span><span style=" font-size:8pt; font-weight:600;">VERY CAREFUL USE ONLY</span><span style=" font-size:8pt;">. It will cause any options read from the saved tags (if the relevant box has been ticked) to over-write the options on the plugin Options Page UI. The intended use of this is if for some reason the user's preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, and will always be turned off when starting Picard, to prevent inadvertant use. Far better is to make a </span><span style=" font-size:8pt; font-weight:600;">backup copy</span><span style=" font-size:8pt;"> of the picard.ini file.</span></p> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Information on hidden variables</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This section is for users who want to write their own scripts, or add additional tags (in the tag mapping section) based on hidden variables. The definition and source of each hidden variable is listed. Apologies if there are errors and omissions in this section - to double check the actual hidden variables fgr any track, use the plugin &quot;View script variables&quot;.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Works and parts</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work_n, where n is an integer &gt;=0 : The MB work name at level n. For n=0, the tag is the same as the current standard Picard tag &quot;work&quot;</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work_top : The top work name (i.e. for maximal n). Thus, if max n = N, _cwp_work_top will be equivalent to _cwp_work_N. Note, however, that this will always be the &quot;canonical&quot; MB name, not one derived from titles or the lowest level work name and that no annotations (e.g. key or work year) will be added (whereas they will be added to _cwp_work_N). Nevertheless, if &quot;replace work names by aliases&quot; has been selected and is applicable, the relevant alias will be used.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_workid_n : The matching work id for each work name. For n=0, the tag is the same as the standard Picard tag &quot;MusicBrainz Work Id&quot;</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_workid_top : The matching work id for the top work name.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_part_n : A &quot;stripped&quot; version of _cwp_work_n, where higher-level work text has been removed wherever possible, to avoid duplication on display. Thus in theory, _cwp_work_0 will be the same as &quot;_cwp_work_top: _cwp_part_(N-1): ...: _cwp_part_0&quot; (punctuation excepted), but may differ in more complex situations where there is not an exact hierarchy of text as the work levels are traversed. (See below for the &quot;_X0&quot; series which attempts to address any such inconsistencies)</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_part_levels : The number of work levels attached to THIS TRACK. Should be equal to N = max(n) referred to above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work_part_levels : The maximum number of levels for ANY TRACK in the album which has the same top work as this track.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_single_work_album : A flag = 1 if there is only one top work in this album, else = 0.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work : the level selected by the plugin to be the source of the single-level work name if &quot;Use only metadata from canonical works&quot; is selected (usually the top level, but one lower in the case of a single work album).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_groupheading : the level selected by the plugin to be the source of the multi-level work name if &quot;Use only metadata from canonical works&quot; is selected.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_part : The movement name derived from the MB work names (generally = _cwp_part_0) and used as the source for the movement name used for &quot;Tags for Movement - including embedded movt/part numbers&quot;.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_inter_work : Intermediate works between _cwp_part and _cwp_work (if any).</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If there is more than one work any level, then _cwp_work_n and _cwp_workid_n will have multiple entries. Another common situation is that a &quot;bottom level&quot; work is spread across more than one track. Rather than artificially split the work into sub-parts, this is often shown in MusicBrainz as a track being a &quot;partial recording of&quot; a work. The plugin deals with this by creating a notional lowest-level with the suffix &quot; (part)&quot; appended to the work it is a partial recording of. In order that this notional part can be separately identified from the full work, the musicbrainz_recordingid is used as the identifier rather than the workid. If there is more than one &quot;parent&quot; work of a lower level work, multi-valued tags are generated.</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_X0_part_0 : A &quot;stripped&quot; version of _cwp_work_0 (see above), where elements of _cwp_work_0 which repeat within level 1 have been stripped.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_X0_work_n : The elements of _cwp_work_0 which repeat within level n</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">As well as variables derived from MB's work structure, some variables are produced which are derived from the track title. Typically titles may be in the format &quot;Work: Movement&quot;, but not always. Sometimes the title is prefixed by the name of the composer; in this case the variable</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title is provided which excludes the composer name and subsequent processing is carried out using this rather than the full title. </li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The plugin uses a number of methods attempt to extract the works and movement from the title. The resulting variables are:</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title_work_n, and</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title_part_n which mirror those for the ones based on MB works described above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title_part_levels which similarly mirrors _cwp_part_levels</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title_work_levels which similarly mirrors _cwp_work_part_levels</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title_work is the level selected by the plugin to be the source of the single-level work name if &quot;Use only metadata from title text&quot; is selected (usually the top level, but one lower in the case of a single work album).</li> -<li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_title_groupheading is similarly the level selected by the plugin to be the source of the multi-level work name if &quot;Use only metadata from title text&quot; is selected.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_extended_part : = _cwp_part with additional movement information from the title - given in {}.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_extended_groupheading : = _cwp_groupheading with additional work information from the title - given in {}.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_extended_work : = _cwp_work with additional work information from the title - given in {}.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_extended_inter_work : = _cwp_inter_work with additional work information from the title - given in {}. The &quot;extended&quot; variables can be useful where the &quot;canonical&quot; work names in MB are in the original language and the titles are in English (say). Various heuristics are used to try and add (and only add) meaningful additional information, but oddities may occur which require manual editing.</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Artist tags which derive from work-artist relationships are also set in this section:</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_composers</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_writers</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_arrangers : This is for arrangers of the work and also &quot;instrument arrangers&quot; and &quot;vocal arrangers&quot; with appropriate annotation for instrument and voice types. (Picard does not currently write the latter to the Arranger tag if they are part of the work-artists relationship, despite style guidance saying to use specific instrument types instead of generic arranger.) </li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_arranger_names : Just the names of the above (no annotations)</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_orchestrators</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_reconstructors - 'reconstructed by' relationships</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_revisors - 'revised by' relationships</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_lyricists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_librettists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_translators</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Finally, the tags _cwp_error and_cwp_warning are provided to supply warnings and error messages to the user.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Artists</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">All the additional hidden variables for artists written by Classical Extras are prefixed by _cea_. Note that these are generally in the plural, whereas the standard tags are singular. If the user blanks a tag then the original value is stored in the singular with the _cea_ prefix. Thus _cea_arranger would be the contents of the Picard tag &quot;arranger&quot; before blanking, whereas _cea_arrangers is hidden variable created by Classical Extras.</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_recording_artist : The artist credited with the recording (not necessarily the track artist). Note that this is the only &quot;_cea_&quot; tag which is singular, because it is in the same format as the 'artist' tag, whereas...</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_recording_artists : The list/multiple value version of the above. (This follows the approach in Picard for 'artist' and 'artists', being the track artists.)</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_MB_artists: The original track artists per MusicBrainz before any replacement by / merging with recording artists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_soloists : List of performers (with instruments in brackets), who are NOT ensembles or conductors, separated by semi-colons. Note they may not strictly be &quot;soloists&quot; in that they may be part of an ensemble.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_recording_artistsort : Sort names of _cea_recording_artist</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_recording_artists_sort : Sort names of _cea_recording_artists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_soloist_names : Names of the above (i.e. no instruments).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_soloists_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_vocalists : Soloists who are vocalists (with voice in brackets).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_vocalist_names : Names of the above (no voice).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_instrumentalists : Soloists who have instruments but are not vocalists.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_instrumentalist_names : Names of the above (no instrument).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_other_soloists : Soloists who do not have specified instrument/voice.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_ensembles : List of performers which are ensembles (with type / instruments - e.g. &quot;orchestra&quot; - in brackets), separated by semi-colons.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_ensemble_names : Names of the above (i.e. no instruments).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_ensembles_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_soloists : Sub-list of soloist_names who are also album artists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_soloists_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_conductors : List of conductors whao are also album artists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_conductors_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_ensembles: Sub-list of ensemble_names who are also album artists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_ensembles_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_composers : List of composers who are also album artists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_composers_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_track_composer_lastnames : Last names of the above. (N.B. This only includes the composers of the current track - compare with _cea_album_composer_lastnames below).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_album_composer_lastnames : Last names of composers of ANY track on the album who are also album artists. This can be used to prefix the album name if required. (cf _cea_album_track_composer_lastnames)</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_support_performers : Sub-list of soloist_names who are NOT album artists</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_support_performers_sort : Sort_names of the above.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_composers : Alternative composer name, based on sort name, to avoid non-latin language problems.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_conductors : Alternative conductor name, based on sort name, to avoid non-latin language problems.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_performers : An alternative to performer, based on the sort name (see note re non-Latin script below).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_arrangers : All arrangers for the <span style=" font-weight:600;">recording</span> with instrument/voice type in brackets, if provided. If the work and parts functionality has also been selected, the arrangers of works, which Picard also currently omits will be put in _cwp_arrangers.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_orchestrators : Arrangers (per Picard) included in the MB database as type &quot;orchestrator&quot;.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_chorusmasters : A person who (per Picard) is a conductor, but is &quot;chorus master&quot; in the MB database (i.e. not necessarily conducting the performance).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_leaders : The leader of the orchestra (&quot;concertmaster&quot; in MusicBrainz) - not created by Picard as standard. </li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_work_type : Although not strictly an artist field, this is derived from artist and performer metadata. This is the variable populated if &quot;Infer work types&quot; is selected on the Artists tab.</li></ul> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Genres etc.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Most of the genres, keys and date information requires the works and parts section to have been run, and so the variables are prefixed _cwp_, but instruments and genres which are derived from artist information and are prefixed _cea_.</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_instruments : Names of all instruments on the track (MusicBrainz names)</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_instuments_credited : As above, but MB names replaced by as-credited names, if any</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_instruments_all : MB and as-credited names</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_work_type : The genre(s) inferred from artist information</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cea_work_type_if_classical : As above, but only of relevance if the work is classical</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_candidate_genres : List of all tags, work types etc. found (depending on specified sources) before filtering for &quot;allowed&quot; genres.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_keys : keys associated with this track (from all work levels).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_composed_dates : Date composed (integer) or range (integer-integer).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_published_dates : Date published (integer) or range (integer-integer).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_premiered_dates : Date premiered (integer) or range (integer-integer).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_untagged_genres : Genres in _cwp_candidate_genres which have been filtered out as they are not in any &quot;allowed&quot; list.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_unrostered_composers : For Muso users: composers who are not in Muso's classical composers roster.</li></ul> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Software-specific notes</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Note that _cwp_part_levels &gt; 0 will indicate that the track recording is part of a work and so could be used to set other software-specific flags (e.g. for iTunes &quot;show work movement&quot;) to indicate a multi-level &quot;work: movement&quot;.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">SongKong</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">SongKong users may wish to map the _cwp variables to tags produced by SongKong if consistency is desired, in which case the mappings are principally:</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work_0 =&gt; musicbrainz_work_composition</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_workid_0 =&gt; musicbrainz_work_composition_id</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work_n =&gt; musicbrainz_work_part_leveln, for n = 1..6</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_workid_n =&gt; musicbrainz_work_part_leveln_id, for n = 1..6</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">_cwp_work_top =&gt; musicbrainz_work</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">These mappings are carried out automatically if the relevant options on the &quot;Work and parts&quot; tab are selected.<br />In addition, _cwp_title_work and _cwp_title_part_0 are intended to be equivalent to SongKong's work and part tags. (N.B. Full consistency between SongKong and Picard may also require the modification of Artist and related tags via a script, or the preservation of the related file tags)</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Muso</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The tag &quot;groupheading&quot; should be set as the &quot;Tags for Work - for software with 2-level capability&quot;. Muso will use this directly and extract the levels from it (split by the double colon). Muso permits a variety of import options which should be capable of combination with the tagging options in this plugin to achieve most desired effects. To avoid the use of import options in Muso, set the output tags from the plugin to be the native ones used by Muso (NB &quot;title&quot; may include or exclude groupheading - Muso should recognise it and extract it).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To make use of Muso's in-built classical music processing to set explicit tags in Picard, enable the &quot;Use Muso reference database ...&quot; option on the &quot;Genres etc.&quot; tab.</span></p> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Players with no &quot;work&quot; capability</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Leave the &quot;title&quot; tag unchanged or make it a combination of work and movement.</span></p> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Possible Enhancements</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Planned enhancements (among others) are </span></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Include information regarding dates (e.g. date composed, recording date).</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Improved genre capability, possibly with specific tags for different aspects of genre, e.g. periods.</li></ol> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Technical Matters</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Issues were encountered with the Picard API in that there is not a documented way to let Picard know that it is still doing asynchronous tasks in the background and has not finished processing metadata. Many thanks to @dns_server for assistance in dealing with this and to @sophist for the albumartist_website code which I have borrowed from. I have tried to add some more comments to help any others trying the same techniques.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Also, the documentation of the XML lookup is virtually non-existent. The response is an XmlNode object (not a dict, although it is represented as one). Each node has a name with {attribs, text, children} values. The structure is more clearly understood if the web-based lookup is used (which is well documented at </span><a href="https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2</span></a><span style=" font-size:8pt;"> ) as this gives an XML response. I wrote a function (parse_data) to parse XmlNode objects, or lists thereof, for a (parameterised) hierarchy of nodes (and optional attribs value tests) in order to extract required data from the response. This may be of use to other plugin authors. The situation has been made slightly better in Picard 2.0 as the objects returned from register_track_metadata_processor are standard JSON. See </span><a href="https://musicbrainz.org/doc/Development/JSON_Web_Service"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">https://musicbrainz.org/doc/Development/JSON_Web_Service</span></a><span style=" font-size:8pt;">. My parse_data function works with JSON as well as XmlNode formats, but the arguments you need to provide to it are different as the formats are structured differently. Picard 2.0 allows tagger.webservice calls to specify XML or default to JSON. This plugin has now migrated over to JSON-only.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To get the whole picture, in XML, for a release, use (for example) </span><a href="https://musicbrainz.org/ws/2/release/f3bb4fdd-5db0-43a8-be73-7a1747f6c2ef?inc=release-groups+media+recordings+artist-credits+artists+aliases+labels+isrcs+collections+artist-rels+release-rels+url-rels+recording-rels+work-rels+recording-level-rels+work-level-rels"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">https://musicbrainz.org/ws/2/release/f3bb4fdd-5db0-43a8-be73-7a1747f6c2ef?inc=release-groups+media+recordings+artist-credits+artists+aliases+labels+isrcs+collections+artist-rels+release-rels+url-rels+recording-rels+work-rels+recording-level-rels+work-level-rels</span></a><span style=" font-size:8pt;">. (Add &amp;fmt=JSON at the end to get the JSON response). This simulates the response given by Picard with the &quot;Use release relationships&quot; and &quot;Use track relationships&quot; options selected. Note that the Picard album_metadata_processor returns releaseXmlNode which is everything from the Release node of the XML downwards, whereas track_metadata_processor returns trackXmlNode which is everything from a Track node downwards (release -&gt; medium-list -&gt; medium -&gt; track-list is above track). Replace all hyphens in XML with underscores when parsing the Python object (JSON uses hyphens).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">A large variety of releases were used to test this plugin, but there may still be bugs, so further testing is welcome. The following release was particularly complex and useful for testing: </span><a href="https://musicbrainz.org/release/ec519fde-94ee-4812-9717-659d91be11d4"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">https://musicbrainz.org/release/ec519fde-94ee-4812-9717-659d91be11d4</span></a><span style=" font-size:8pt;">. Also this release was a bit tricky - a large box set with some works appearing as originals and in arrangements: </span><a href="https://musicbrainz.org/release/5288f266-bab8-45bd-83e4-555730f02fa0"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">https://musicbrainz.org/release/5288f266-bab8-45bd-83e4-555730f02fa0</span></a><span style=" font-size:8pt;">.</span></p> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Very technical matters</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">I've done a bit of research and observed the following behaviour in Picard when using the </span><span style=" font-family:'Courier New'; font-size:8pt;">register_track_metadata_processor()</span><span style=" font-size:8pt;"> API:</span></p> -<ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:8pt;" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If a new album (i.e. not yet tagged by Picard and with no MBIDs) is loaded, clustered and looked-up/scanned, resulting in the matched files being shown in the right-hand pane, then:</li> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">track.metadata</span> gives the lookup result from MB - i.e. no file information, just the track info.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">album.tagger.files[xxxx].metadata</span> (where xxxx is the path/filename of the track) gives the file information (dirname etc.) and the tags on the original file.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">album.tagger.files[xxxx].orig_metadata</span> gives the same as <span style=" font-family:'Courier New';">album.tagger.files[xxxx].metadata</span></li></ul> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">However, if the album is then &quot;refreshed&quot;, this does not just carry out a repeat operation, instead: </li></ol> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">track.metadata</span> gives the same as before</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">album.tagger.files[xxxx].metadata</span> gives the metadata as in <span style=" font-family:'Courier New';">track.metadata</span> and also includes all the file information as before.</li> -<li style=" font-size:8pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">album.tagger.files[xxxx].orig_metadata</span> gives the same as before (i.e. the original tags).</li></ul> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">So, the 'post-refreshment' metadata is actually usable - by matching the </span><span style=" font-family:'Courier New'; font-size:8pt;">musicbrainz_trackid</span><span style=" font-size:8pt;"> of </span><span style=" font-family:'Courier New'; font-size:8pt;">track.metadata</span><span style=" font-size:8pt;"> and </span><span style=" font-family:'Courier New'; font-size:8pt;">album.tagger.files[xxxx].metadata</span><span style=" font-size:8pt;">, you can get the file details of the track. Not elegant, but it works. But why is it necessary to &quot;refresh&quot; to get what seems like a logical data set? Basically, the metadata process is not complete when the plugin is called, so not all metadata is available to it. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">I don't know why </span><span style=" font-family:'Courier New'; font-size:8pt;">get_files_from_objects(objs)</span><span style=" font-size:8pt;"> doesn't work in the </span><span style=" font-family:'Courier New'; font-size:8pt;">register_track_metadata_processor()</span><span style=" font-size:8pt;"> API when </span><span style=" font-family:'Courier New'; font-size:8pt;">objs</span><span style=" font-size:8pt;"> is a list of tracks, but does provide a list of file objects when </span><span style=" font-family:'Courier New'; font-size:8pt;">objs</span><span style=" font-size:8pt;"> is a list of albums. Also it does work in the </span><span style=" font-family:'Courier New'; font-size:8pt;">register_file_action(xxx)</span><span style=" font-size:8pt;">/</span><span style=" font-family:'Courier New'; font-size:8pt;">register_track_action(xxx)</span><span style=" font-size:8pt;"> API, but I assume that is because </span><span style=" font-family:'Courier New'; font-size:8pt;">itemsview.py</span><span style=" font-size:8pt;"> can identify that you have clicked on an item that is a track and a file (this is after the metadata processing has run).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">In order to get round these problems, I have adopted a 'hack' which looks up the files for an album and finds which file matches the required track on tracknumber and discnumber. This seems to work, but there may be circumstances when it does not. As a consequence, refreshment should only be required if </span><span style=" font-family:'Courier New'; font-size:8pt;">get_files_from_objects(objs)</span><span style=" font-size:8pt;"> doesn't get all the album files.</span></p> -<p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">List of previous updates</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 1.0: Final version with Picard 1.4.2 compatibility</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.9.4: Bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.9.3: Custom logging if enabled on &quot;Advanced&quot; tab - writes one log file per album in addition to standard Picard log. Also a session log with basic data is written. Performance improvements (especially for lyrics processing) and various bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.9.2: A new tab in the options page has been created - &quot;Genres etc.&quot; This includes special processing for genres, instruments, keys, work dates and periods. The &quot;Infer work types&quot; option on the &quot;Artists&quot; tab has been moved to this tab (but will be reset to the default of &quot;False&quot; and will need to be reset as required, because it operates slightly differently now that there is more control over the setting of genres). A separate section has ben added to this readme giving more details on the options in this tab.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.9.1: Bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.9: Additional option to clear previous file tags which are not wanted, without interfering with cover art. Additional option to replace instrument names with as-credited names. Also instrument names are now saved to hidden variables tags (instruments, instruments_credited and instruments_all) which can be mapped to file tags as required. Sub-option added to the 'override artist options' option on the &quot;advanced&quot; tab - to allow tag map details to be included or not in this over-ride. Minor bug fixes and UI improvements. This is the next 'official' version after 0.7.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.9: Provide option (in advanced tab) to disable Classical Extras processing if no file is present; this enables (for example) single discs from box sets to be loaded without incurring the additional processing overhead for all the other discs. The settings of the main Picard options &quot;translate_artist_names&quot; and &quot;standardize_artists&quot; is now saved along with the Classical Extras options so that they can be used to over-ride the displayed options. This is because they interact with the Classical Extras options in certain cases. Also:- graceful recovery from authentication failure; improved UI - more scalable; minor bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.8: Fixes to allow for (1) disabling of 'use_cache' across releases which may share works and (2) works which might appear in their own right and also have arrangements. Also, re-set certain important options to defaults on starting Picard, namely: 'use_cache' set to True, 'log_debug', 'log_info' and 'options_overwrite' set to False; the user will need to deliberately re-set these on starting Picard if required - this is to prevent inadvertently leaving these flags in an abnormal state.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.7: Revised treatment of &quot;conditional&quot; tag mapping. Previously, if multiple sources were specified for a tag mapping and the &quot;conditional&quot; flag set, only the first non-empty source was used. Now all sources will be mapped to a tag if it was empty before executing the current tag mapping line. This is considered to be more intuitive and leads to less complex mapping lines. However, it some cases it may be necessary to split a line from a previous version if the previous behaviour was specifically desired. Improved algorithms for extending metadata with title info. Bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.6: More consistent approach to sort tags and hidden variables. Bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.5: Improved handling of instruments. Bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.4: Improved UI, bug fixes and code improvements.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.3: Ability to use aliases of work names instead of the MusicBrainz standard names. Various options to use aliases and as-credited names for performers and writers (i.e. relationship artists). Option to use recording artists as well as (or instead of) track artists. All relationship artists are now tagged for all work levels (with some special processing for lyricists). New UI layout to separate tag mapping from artist options. Option to split lyrics into common (release) and unique (track) components. Various code improvements as well as bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.2: Improved algorithms and a few minor bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8.1: Bug fixes and minor enhancements - including revison and extension of &quot;synonyms&quot; section on the &quot;advanced&quot; tab.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.8: Handle multiple recordings and/or multiple parents of a work. Handle multiple albumartist composers for one track. Option to use &quot;credited as&quot; name for artists (inc. performers and composers) who are &quot;release artist&quot; or &quot;track artist&quot;. Option to exclude &quot;solo&quot; from instrument types. Option to over-ride plugin options with those used when the album was last saved. Option to keep (and append to) specified existing file tags - furthermore if &quot;is_classical&quot; is present, the work-type variable will include &quot;Classical&quot;. Option to use (and write) SongKong-compatible work tags (saves processing time if SongKong is used to pre-process large numbers of files). Include the work (and its parents) of which a work is an arrangement (as a &quot;pseudo-parent&quot;). Include medleys in movement/part description (as [Medley of:...] or other descriptor specified in options). Allow for multiple parents of recordings and works (and multiple parents of those) - multiples are given as multiple tag instances, where permitted, otherwise separated by semi-colons. Option as to whether to include parent works where the relationship attribute is &quot;part of collection&quot;. Plus minor enhancements and bug fixes too numerous to mention! (Note that some old versions of the plugin may say v0.8 when they are only v0.7)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.7: Bug fixes. Pull request issued for this version.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.6.6: Bug fixes and algorithm improvements. Allow multiple sources for a tag (each will be appended) and use + as separator for concantenating sources, not a comma, to distinguish from the use of comma to separate multiples. Provide additional hidden variables (&quot;sources&quot;) for vocalists and instrumentalists. Option to include intermediate work levels in a movement tag (for systems which cannot display multiple work levels).</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.6.5: Include ability to use multiple (concatenated) sources in tag mapping (see notes under &quot;tag mapping&quot;). All artist &quot;sources&quot; using hidden variables (_cea_...) are now consistently in the plural, to distinguish from standard tags. Note that if &quot;album&quot; is used as a source in tag mapping, it will now be the revised name, where composer prefixing is used. To use the original name, use &quot;release&quot; as the source. Also various bug fixes, notably to ensure that all arrangers get picked up for use in tag mapping.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.6.4: Write out version number to user-specified tag. Provide default comment tags for writing ui options. Provide better m4a and iTunes compatibility (see notes). Added functionality for chorus master, orchestrator and concert master (leader). Re-arranged artist tab in ui. Various bug fixes.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.6.3: Bug fixes. Modified ui default options.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.6.2: Bug fixes. More flexible handling of artists (can blank and then add back later). Modified ui default options.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version 0.6.1: Amended regex to permit non-Latin characters in work text.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600; text-decoration: underline;">General description</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Classical Extras provides tagging enhancements for Picard and, in particular, utilises MusicBrainz’s hierarchy of works to provide work/movement tags. All options are set through a user interface in Picard options-&gt;plugins. This interface provides separate sections to enhance artist/performer tags, works and parts, genres and also allows for a generalised &quot;tag mapping&quot; (simple scripting). While it is designed to cater for the complexities of classical music tagging, it may also be useful for other music which has more than just basic song/artist/album data. <br /><br />The options screen provides five tabs for users to control the tags produced: <br /><br />1. Artists: Options as to whether artist tags will contain standard MB names, aliases or as-credited names. Ability to include and annotate names for specialist roles (chorus master, arranger, lyricist etc.). Ability to read lyrics tags on the file which has been loaded and assign them to track and album levels if required. (Note: Picard will not normally process incoming file tags). <br /><br />2. Works and parts: The plugin will build a hierarchy of works and parts (e.g. Work -&gt; Part -&gt; Movement or Opera -&gt; Act -&gt; Number) based on the works in MusicBrainz's database. These can then be displayed in tags in a variety of ways according to user preferences. Furthermore partial recordings, medleys, arrangements and collections of works are all handled according to user choices. There is a processing overhead for this at present because MusicBrainz limits look-ups to one per second. <br /><br />3. Genres etc.: Options are available to customise the source and display of information relating to genres, instruments, keys, work dates and periods. Additional capabilities are provided for users of Muso (or others who provide the relevant XML files) to use pre-existing databases of classical genres, classical composers and classical periods. <br /><br />4. Tag mapping: in some ways, this is a simple substitute for some of Picard's scripting capability. The main advantage is that the plugin will remember what tag mapping you use for each release (or even track). <br /><br />5. Advanced: Various options to control the detailed processing of the above. <br /><br />All user options can be saved on a per-album (or even per-track) basis so that tweaks can be used to deal with inconsistencies in the MusicBrainz data (e.g. include English titles from the track listing where the MusicBrainz works are in the composer's language and/or script). Also existing file tags can be processed (not possible in native Picard). <br /><br />See the readme file </span><a href="https://github.com/MetaTunes/picard-plugins/tree/metabrainz/2.0.2/plugins/classical_extras"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">on GitHub here</span></a><span style=" font-size:8pt;"> for full details.</span></p></body></html> true @@ -9760,7 +11430,7 @@ p, li { white-space: pre-wrap; } 9 28 641 - 71 + 81 @@ -9774,8 +11444,9 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:12pt; font-weight:600;">Please see</span><span style=" font-size:14pt; font-weight:600;"> </span><a href="https://highmossergate.co.uk/digitalsymphony/"><span style=" font-size:14pt; text-decoration: underline; color:#0000ff;">my website</span></a><span style=" font-size:14pt; font-weight:600;"> </span><span style=" font-size:12pt; font-weight:600;">for a more readable version and also a lot of other help.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:12pt; font-weight:600;">The text below may be slightly out of date and lacks images.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:12pt; font-weight:600;">Please see the </span><a href="https://github.com/MetaTunes/picard-plugins/tree/metabrainz/2.0.2/plugins/classical_extras"><span style=" font-size:12pt; font-weight:600; text-decoration: underline; color:#0000ff;">readme file</span></a><span style=" font-size:12pt; font-weight:600;"> or</span><span style=" font-size:14pt; font-weight:600;"> </span><a href="https://highmossergate.co.uk/digitalsymphony/"><span style=" font-size:14pt; text-decoration: underline; color:#0000ff;">my website</span></a><span style=" font-size:14pt; font-weight:600;"> </span><span style=" font-size:12pt; font-weight:600;">for full details of this plugin and how to use it.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">This help page now has only general information.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">There are extensive tooltips and &quot;What's This&quot; popups (right click for them)</span></p></body></html> true @@ -9791,866 +11462,818 @@ p, li { white-space: pre-wrap; } - cwp_titles - clicked(bool) - groupBox_16 - setDisabled(bool) - - - 211 - 139 - - - 1088 - 116 - - - - - cea_no_aliases + cea_ra_use toggled(bool) - cea_credited_overrides - toggle() - - - 260 - 210 - - - 594 - 407 - - - - - cea_aliases_composer - clicked(bool) - cea_alias_overrides + groupBox_30 setEnabled(bool) - 260 - 308 + 368 + 552 - 184 - 407 + 621 + 526 - cea_aliases - clicked(bool) - cea_alias_overrides + use_cea + toggled(bool) + groupBox_29 setEnabled(bool) - 260 - 251 + 107 + 73 - 153 - 407 + 77 + 499 - cwp_works - clicked(bool) - groupBox_16 + cea_ra_replace_ta + toggled(bool) + cea_ra_noblank_ta setEnabled(bool) - 211 - 162 + 631 + 536 - 1088 - 116 + 631 + 559 - cwp_aliases - clicked(bool) - groupBox_20 + use_cea + toggled(bool) + groupBox_31 setEnabled(bool) - 211 - 288 + 138 + 75 - 742 - 251 + 128 + 938 - cwp_extended - clicked(bool) - groupBox_16 + use_cwp + toggled(bool) + cwp_collections setEnabled(bool) - 211 - 185 + 94 + 60 - 1088 - 116 + 508 + 76 - cea_ra_use + cea_split_lyrics toggled(bool) - groupBox_30 + groupBox_32 setEnabled(bool) - 524 - 496 - - - 1040 - 457 - - - - - cwp_no_aliases - clicked(bool) - groupBox_20 - setDisabled(bool) - - - 193 - 325 + 139 + 1016 - 742 - 251 + 364 + 962 - cea_no_aliases - clicked(bool) - cea_alias_overrides - setDisabled(bool) + use_cwp + toggled(bool) + use_cache + setEnabled(bool) - 222 - 210 + 124 + 60 - 208 - 407 + 850 + 76 use_cea toggled(bool) - groupBox_23 + groupBox_5 setEnabled(bool) - 116 - 60 + 138 + 75 - 85 - 147 + 128 + 639 use_cea toggled(bool) - groupBox_29 + artist_tags setEnabled(bool) - 87 - 60 + 138 + 75 - 66 - 434 + 129 + 99 - cea_ra_replace_ta + cea_arrangers toggled(bool) - cea_ra_noblank_ta + groupBox_9 setEnabled(bool) - 1030 - 480 + 149 + 680 - 713 - 503 + 468 + 663 - use_cea + cea_arrangers toggled(bool) - groupBox_31 + groupBox_28 setEnabled(bool) - 178 - 60 + 149 + 680 - 163 - 866 + 830 + 663 - use_cwp + toolButton_1 toggled(bool) - cwp_collections + cea_source_1 setEnabled(bool) - 94 - 60 + 116 + 439 - 742 - 60 + 450 + 440 - cea_split_lyrics + toolButton_2 toggled(bool) - groupBox_32 + cea_source_2 setEnabled(bool) - 132 - 956 + 116 + 467 - 1004 - 889 + 191 + 468 - use_cwp + toolButton_3 toggled(bool) - use_cache + cea_source_3 setEnabled(bool) - 124 - 60 + 116 + 495 - 1088 - 60 + 191 + 496 - use_cea + toolButton_4 toggled(bool) - groupBox_5 + cea_source_4 setEnabled(bool) - 160 - 60 + 116 + 523 - 115 - 578 + 191 + 524 - cea_override + toolButton_5 toggled(bool) - ce_tagmap_override + cea_source_5 setEnabled(bool) 116 - 987 + 551 - 378 - 987 + 191 + 552 - toolButton_3 + toolButton_6 toggled(bool) - cea_source_3 + cea_source_6 setEnabled(bool) - 92 - 438 + 116 + 579 - 160 - 449 + 191 + 580 - toolButton_11 + toolButton_7 toggled(bool) - cea_source_11 + cea_source_7 setEnabled(bool) - 104 - 678 + 116 + 607 - 168 - 682 + 191 + 608 - toolButton_6 + toolButton_8 toggled(bool) - cea_source_6 + cea_source_8 setEnabled(bool) - 95 - 535 + 116 + 635 - 145 - 538 + 191 + 636 - use_cea + toolButton_9 toggled(bool) - artist_tags + cea_source_9 setEnabled(bool) - 332 - 60 + 116 + 663 - 573 - 192 + 191 + 664 - toolButton_8 + toolButton_10 toggled(bool) - cea_source_8 + cea_source_10 setEnabled(bool) - 95 - 593 + 116 + 691 - 163 - 590 + 191 + 692 - toolButton_12 + toolButton_11 toggled(bool) - cea_source_12 + cea_source_11 setEnabled(bool) - 82 - 711 + 116 + 719 - 140 - 719 + 191 + 720 - toolButton_1 + toolButton_12 toggled(bool) - cea_source_1 + cea_source_12 setEnabled(bool) - 75 - 387 + 116 + 747 - 133 - 381 + 191 + 748 - use_cea + toolButton_13 toggled(bool) - groupBox_11 + cea_source_13 setEnabled(bool) - 332 - 60 + 116 + 775 - 552 - 106 + 191 + 776 - toolButton_2 + toolButton_14 toggled(bool) - cea_source_2 + cea_source_14 setEnabled(bool) - 98 - 409 + 116 + 803 - 148 - 416 + 191 + 804 - toolButton_10 + toolButton_15 toggled(bool) - cea_source_10 + cea_source_15 setEnabled(bool) - 77 - 654 + 116 + 831 - 161 - 659 + 191 + 832 - toolButton_15 + toolButton_16 toggled(bool) - cea_source_15 + cea_source_16 setEnabled(bool) - 76 - 805 + 116 + 859 - 153 - 803 + 191 + 860 - toolButton_14 + use_cea toggled(bool) - cea_source_14 + advanced_artists setEnabled(bool) - 98 - 769 + 138 + 75 - 132 - 770 + 129 + 137 - toolButton_13 + use_cwp toggled(bool) - cea_source_13 + advanced_works setEnabled(bool) - 63 - 741 + 139 + 76 - 138 - 741 + 129 + 326 - toolButton_4 + use_cea toggled(bool) - cea_source_4 + frame_23 setEnabled(bool) - 105 - 477 + 138 + 75 - 148 - 473 + 128 + 149 - toolButton_7 + use_cea toggled(bool) - cea_source_7 + label_93 setEnabled(bool) - 102 - 558 + 138 + 75 - 136 - 567 + 129 + 133 - toolButton_9 + use_cea toggled(bool) - cea_source_9 + label_94 setEnabled(bool) - 93 - 618 + 138 + 75 - 168 - 620 + 129 + 486 - toolButton_5 + use_cea toggled(bool) - cea_source_5 + label_95 setEnabled(bool) - 80 - 504 + 138 + 75 - 153 - 504 + 129 + 623 - toolButton_16 + use_cea toggled(bool) - cea_source_16 + label_96 setEnabled(bool) - 85 - 833 + 69 + 75 - 140 - 838 + 74 + 922 - cwp_partial + use_cwp toggled(bool) - cwp_partial_text + Style setEnabled(bool) - 91 - 793 + 47 + 63 - 755 - 792 + 39 + 122 - cea_arrangers + use_cwp toggled(bool) - groupBox_9 + label_99 setEnabled(bool) - 147 - 622 + 112 + 71 - 701 - 646 + 115 + 103 use_cwp toggled(bool) - Style + groupBox_18 setEnabled(bool) - 92 - 60 + 139 + 76 - 58 - 93 + 111 + 249 use_cwp toggled(bool) - advanced_works + label_100 setEnabled(bool) - 181 - 60 + 139 + 76 - 543 - 464 + 130 + 243 use_cwp toggled(bool) - groupBox_18 + Tags setEnabled(bool) - 181 - 60 + 139 + 76 - 135 - 228 + 129 + 416 - cea_arrangers + use_cwp toggled(bool) - groupBox_28 + label_101 setEnabled(bool) - 286 - 622 + 139 + 76 - 1040 - 646 + 119 + 393 use_cwp toggled(bool) - Tags + groupBox_12 setEnabled(bool) - 181 - 60 + 139 + 76 - 134 - 394 + 129 + 781 - use_cea + use_cwp toggled(bool) - advanced_artists + frame_29 setEnabled(bool) - 332 - 60 + 139 + 76 - 554 - 106 + 223 + 752 use_cwp toggled(bool) - groupBox_12 + groupBox_17 setEnabled(bool) - 181 - 60 + 139 + 76 - 173 - 754 + 129 + 1012 use_cwp toggled(bool) - groupBox_17 + label_103 setEnabled(bool) - 181 - 60 + 139 + 76 - 565 - 975 + 130 + 996 - cwp_use_muso_refdb + use_cea toggled(bool) - cwp_muso_genres - setVisible(bool) + label_111 + setEnabled(bool) - 55 - -282 + 138 + 75 - 92 - -53 + 130 + 121 - cwp_medley + use_cwp toggled(bool) - cwp_medley_text + label_113 setEnabled(bool) - 196 - 946 + 139 + 76 - 524 - 948 + 130 + 310 cwp_use_muso_refdb toggled(bool) - cwp_muso_classical + cwp_muso_genres setVisible(bool) - 89 - -280 + 78 + 83 - 97 - 164 + 104 + 347 cwp_use_muso_refdb toggled(bool) - cwp_muso_dates - setVisible(bool) + cwp_muso_genres + setChecked(bool) - 39 - -282 + 109 + 83 - 571 - 750 + 137 + 347 - cwp_arrangements + cwp_use_muso_refdb toggled(bool) - cwp_arrangements_text - setEnabled(bool) + cwp_muso_classical + setVisible(bool) - 217 - 852 + 150 + 83 - 1078 - 851 + 151 + 566 - cwp_muso_genres + cwp_use_muso_refdb toggled(bool) - cwp_genres_classical_main - setHidden(bool) + cwp_muso_classical + setChecked(bool) - 160 - -49 + 169 + 83 - 278 - -58 + 197 + 566 - cwp_muso_periods + cwp_use_muso_refdb toggled(bool) - cwp_period_map - setHidden(bool) + cwp_muso_dates + setVisible(bool) - 83 - 850 + 573 + 83 - 454 - 827 + 594 + 1166 @@ -10661,140 +12284,140 @@ p, li { white-space: pre-wrap; } setVisible(bool) - 540 - -280 + 573 + 83 - 127 - 850 + 181 + 1266 cwp_use_muso_refdb toggled(bool) - cwp_muso_genres + cwp_muso_dates setChecked(bool) - 156 - -281 + 573 + 83 - 184 - -46 + 594 + 1166 cwp_use_muso_refdb toggled(bool) - cwp_muso_classical + cwp_muso_periods setChecked(bool) - 70 - -281 + 573 + 83 - 153 - 171 + 181 + 1266 - cwp_use_muso_refdb + cwp_muso_genres toggled(bool) - cwp_muso_dates - setChecked(bool) + cwp_genres_classical_main + setHidden(bool) - 79 - -284 + 226 + 347 - 151 - 750 + 485 + 327 - cwp_muso_periods + cwp_muso_classical toggled(bool) - label_87 - setHidden(bool) + cwp_genres_arranger_as_composer + setVisible(bool) - 123 - 850 + 108 + 566 - 400 - 869 + 121 + 589 - cwp_override + cwp_muso_dates toggled(bool) - ce_genres_override - setEnabled(bool) + cwp_periods_arranger_as_composer + setVisible(bool) - 555 - 987 + 98 + 1166 - 722 - 987 + 108 + 1189 - cwp_use_muso_refdb + cwp_muso_periods toggled(bool) - cwp_muso_periods - setChecked(bool) + cwp_period_map + setHidden(bool) - 96 - -283 + 175 + 1266 - 103 - 850 + 542 + 1212 - cwp_muso_classical + cwp_muso_periods toggled(bool) - cwp_genres_arranger_as_composer - setVisible(bool) + label_87 + setHidden(bool) - 228 - 169 + 118 + 1266 - 101 - 197 + 545 + 1289 - cwp_muso_dates + cwp_genres_filter toggled(bool) - cwp_periods_arranger_as_composer + groupBox_36 setVisible(bool) - 59 - 743 + 41 + 249 - 63 - 760 + 38 + 274 diff --git a/plugins/classical_extras/suffixtree.py b/plugins/classical_extras/suffixtree.py index 35749b9f..3bac53bb 100644 --- a/plugins/classical_extras/suffixtree.py +++ b/plugins/classical_extras/suffixtree.py @@ -15,8 +15,6 @@ """ import sys -import re -import argparse END_OF_STRING = sys.maxsize @@ -257,6 +255,7 @@ def multi_lcs(strings_list): :return: a list of longest common strings (or lists) (more than one is possible if they are distinct and of the same length) """ + if not isinstance(strings_list, list): return {'response': [], 'error': 'Argument is not a list'} arg_type = type(strings_list[0]) diff --git a/plugins/classical_extras/ui_options_classical_extras.py b/plugins/classical_extras/ui_options_classical_extras.py index 9938604a..42d6462d 100644 --- a/plugins/classical_extras/ui_options_classical_extras.py +++ b/plugins/classical_extras/ui_options_classical_extras.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'C:\Users\Mark\Documents\Mark's documents\Music\Picard20\Classical Extras development\classical_extras\options_classical_extras.ui' +# Form implementation generated from reading ui file 'M:\Documents\Mark's documents\Music\Picard20\Classical Extras development\classical_extras\options_classical_extras.ui' # # Created by: PyQt5 UI code generator 5.11.2 # @@ -11,7 +11,7 @@ class Ui_ClassicalExtrasOptionsPage(object): def setupUi(self, ClassicalExtrasOptionsPage): ClassicalExtrasOptionsPage.setObjectName("ClassicalExtrasOptionsPage") - ClassicalExtrasOptionsPage.resize(1148, 918) + ClassicalExtrasOptionsPage.resize(1145, 918) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -46,37 +46,49 @@ def setupUi(self, ClassicalExtrasOptionsPage): sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setFocusPolicy(QtCore.Qt.WheelFocus) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea.setLineWidth(1) self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.scrollAreaWidgetContents = QtWidgets.QWidget() - self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 1087, 1044)) + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 1086, 1071)) self.scrollAreaWidgetContents.setAcceptDrops(False) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.verticalLayout_13 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents) self.verticalLayout_13.setObjectName("verticalLayout_13") self.frame_9 = QtWidgets.QFrame(self.scrollAreaWidgetContents) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.frame_9.setPalette(palette) - self.frame_9.setAutoFillBackground(True) - self.frame_9.setStyleSheet("") + self.frame_9.setAutoFillBackground(False) + self.frame_9.setStyleSheet("background-color: rgb(255, 255, 222);") self.frame_9.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_9.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_9.setObjectName("frame_9") @@ -84,6 +96,33 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.horizontalLayout_9.setObjectName("horizontalLayout_9") self.use_cea = QtWidgets.QCheckBox(self.frame_9) palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.use_cea.setPalette(palette) self.use_cea.setStyleSheet("") self.use_cea.setObjectName("use_cea") @@ -97,73 +136,71 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.line_4.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_4.setObjectName("line_4") self.verticalLayout_13.addWidget(self.line_4) - self.label_22 = QtWidgets.QLabel(self.scrollAreaWidgetContents) - self.label_22.setObjectName("label_22") - self.verticalLayout_13.addWidget(self.label_22) self.label_43 = QtWidgets.QLabel(self.scrollAreaWidgetContents) self.label_43.setObjectName("label_43") self.verticalLayout_13.addWidget(self.label_43) - self.groupBox_23 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.frame_18 = QtWidgets.QFrame(self.scrollAreaWidgetContents) + self.frame_18.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_18.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_18.setObjectName("frame_18") + self.verticalLayout_36 = QtWidgets.QVBoxLayout(self.frame_18) + self.verticalLayout_36.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_36.setSpacing(0) + self.verticalLayout_36.setObjectName("verticalLayout_36") + self.label_93 = QtWidgets.QLabel(self.frame_18) + self.label_93.setStyleSheet("background-color: rgb(176, 220, 192);") + self.label_93.setObjectName("label_93") + self.verticalLayout_36.addWidget(self.label_93) + self.frame_23 = QtWidgets.QFrame(self.frame_18) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_23.setPalette(palette) - self.groupBox_23.setAutoFillBackground(True) - self.groupBox_23.setStyleSheet("") - self.groupBox_23.setObjectName("groupBox_23") - self.formLayout_3 = QtWidgets.QFormLayout(self.groupBox_23) + self.frame_23.setPalette(palette) + self.frame_23.setCursor(QtGui.QCursor(QtCore.Qt.UpArrowCursor)) + self.frame_23.setAutoFillBackground(False) + self.frame_23.setStyleSheet("background-color: rgb(211, 248, 224);") + self.frame_23.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame_23.setObjectName("frame_23") + self.formLayout_3 = QtWidgets.QFormLayout(self.frame_23) self.formLayout_3.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout_3.setContentsMargins(9, 0, 9, -1) + self.formLayout_3.setVerticalSpacing(6) self.formLayout_3.setObjectName("formLayout_3") - self.groupBox_24 = QtWidgets.QGroupBox(self.groupBox_23) - self.groupBox_24.setObjectName("groupBox_24") - self.verticalLayout_24 = QtWidgets.QVBoxLayout(self.groupBox_24) - self.verticalLayout_24.setObjectName("verticalLayout_24") - self.cea_no_aliases = QtWidgets.QRadioButton(self.groupBox_24) - self.cea_no_aliases.setObjectName("cea_no_aliases") - self.verticalLayout_24.addWidget(self.cea_no_aliases) - self.cea_aliases = QtWidgets.QRadioButton(self.groupBox_24) - self.cea_aliases.setObjectName("cea_aliases") - self.verticalLayout_24.addWidget(self.cea_aliases) - self.cea_aliases_composer = QtWidgets.QRadioButton(self.groupBox_24) - self.cea_aliases_composer.setObjectName("cea_aliases_composer") - self.verticalLayout_24.addWidget(self.cea_aliases_composer) - self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.groupBox_24) - self.groupBox_25 = QtWidgets.QGroupBox(self.groupBox_23) - self.groupBox_25.setObjectName("groupBox_25") - self.horizontalLayout_11 = QtWidgets.QHBoxLayout(self.groupBox_25) - self.horizontalLayout_11.setObjectName("horizontalLayout_11") - self.cea_alias_overrides = QtWidgets.QRadioButton(self.groupBox_25) - self.cea_alias_overrides.setObjectName("cea_alias_overrides") - self.horizontalLayout_11.addWidget(self.cea_alias_overrides) - self.cea_credited_overrides = QtWidgets.QRadioButton(self.groupBox_25) - self.cea_credited_overrides.setObjectName("cea_credited_overrides") - self.horizontalLayout_11.addWidget(self.cea_credited_overrides) - self.cea_cyrillic = QtWidgets.QCheckBox(self.groupBox_25) - self.cea_cyrillic.setObjectName("cea_cyrillic") - self.horizontalLayout_11.addWidget(self.cea_cyrillic) - self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.groupBox_25) - self.groupBox_7 = QtWidgets.QGroupBox(self.groupBox_23) + self.label_22 = QtWidgets.QLabel(self.frame_23) + self.label_22.setObjectName("label_22") + self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.label_22) + self.groupBox_7 = QtWidgets.QGroupBox(self.frame_23) self.groupBox_7.setObjectName("groupBox_7") self.horizontalLayout_18 = QtWidgets.QHBoxLayout(self.groupBox_7) self.horizontalLayout_18.setObjectName("horizontalLayout_18") self.groupBox_26 = QtWidgets.QGroupBox(self.groupBox_7) - self.groupBox_26.setStyleSheet("border-color: rgb(116, 116, 116);") + self.groupBox_26.setAutoFillBackground(False) + self.groupBox_26.setStyleSheet("background-color: rgb(250, 250, 250);") self.groupBox_26.setObjectName("groupBox_26") self.verticalLayout_26 = QtWidgets.QVBoxLayout(self.groupBox_26) self.verticalLayout_26.setObjectName("verticalLayout_26") @@ -202,8 +239,11 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.verticalLayout_26.addWidget(self.label_88) self.horizontalLayout_18.addWidget(self.groupBox_26) self.groupBox_27 = QtWidgets.QGroupBox(self.groupBox_7) + self.groupBox_27.setStyleSheet("background-color: rgb(250, 250, 250);") self.groupBox_27.setObjectName("groupBox_27") self.verticalLayout_27 = QtWidgets.QVBoxLayout(self.groupBox_27) + self.verticalLayout_27.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.verticalLayout_27.setContentsMargins(9, -1, -1, -1) self.verticalLayout_27.setObjectName("verticalLayout_27") self.cea_performer_credited = QtWidgets.QCheckBox(self.groupBox_27) self.cea_performer_credited.setLayoutDirection(QtCore.Qt.RightToLeft) @@ -214,35 +254,96 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cea_composer_credited.setObjectName("cea_composer_credited") self.verticalLayout_27.addWidget(self.cea_composer_credited) self.horizontalLayout_18.addWidget(self.groupBox_27) - self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.groupBox_7) - self.verticalLayout_13.addWidget(self.groupBox_23) - self.groupBox_29 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.formLayout_3.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.groupBox_7) + self.groupBox_25 = QtWidgets.QGroupBox(self.frame_23) + self.groupBox_25.setStyleSheet("background-color: rgb(250, 250, 250);") + self.groupBox_25.setObjectName("groupBox_25") + self.horizontalLayout_11 = QtWidgets.QHBoxLayout(self.groupBox_25) + self.horizontalLayout_11.setObjectName("horizontalLayout_11") + self.cea_alias_overrides = QtWidgets.QRadioButton(self.groupBox_25) + self.cea_alias_overrides.setObjectName("cea_alias_overrides") + self.horizontalLayout_11.addWidget(self.cea_alias_overrides) + self.cea_credited_overrides = QtWidgets.QRadioButton(self.groupBox_25) + self.cea_credited_overrides.setObjectName("cea_credited_overrides") + self.horizontalLayout_11.addWidget(self.cea_credited_overrides) + self.cea_cyrillic = QtWidgets.QCheckBox(self.groupBox_25) + self.cea_cyrillic.setObjectName("cea_cyrillic") + self.horizontalLayout_11.addWidget(self.cea_cyrillic) + self.formLayout_3.setWidget(4, QtWidgets.QFormLayout.SpanningRole, self.groupBox_25) + self.groupBox_24 = QtWidgets.QGroupBox(self.frame_23) + self.groupBox_24.setStyleSheet("background-color: rgb(211, 248, 224);") + self.groupBox_24.setObjectName("groupBox_24") + self.verticalLayout_24 = QtWidgets.QVBoxLayout(self.groupBox_24) + self.verticalLayout_24.setObjectName("verticalLayout_24") + self.groupBox_23 = QtWidgets.QGroupBox(self.groupBox_24) + self.groupBox_23.setStyleSheet("background-color: rgb(250, 250, 250);") + self.groupBox_23.setTitle("") + self.groupBox_23.setObjectName("groupBox_23") + self.verticalLayout_40 = QtWidgets.QVBoxLayout(self.groupBox_23) + self.verticalLayout_40.setObjectName("verticalLayout_40") + self.cea_no_aliases = QtWidgets.QRadioButton(self.groupBox_23) + self.cea_no_aliases.setObjectName("cea_no_aliases") + self.verticalLayout_40.addWidget(self.cea_no_aliases) + self.cea_aliases = QtWidgets.QRadioButton(self.groupBox_23) + self.cea_aliases.setObjectName("cea_aliases") + self.verticalLayout_40.addWidget(self.cea_aliases) + self.cea_aliases_composer = QtWidgets.QRadioButton(self.groupBox_23) + self.cea_aliases_composer.setObjectName("cea_aliases_composer") + self.verticalLayout_40.addWidget(self.cea_aliases_composer) + self.verticalLayout_24.addWidget(self.groupBox_23) + self.formLayout_3.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.groupBox_24) + self.verticalLayout_36.addWidget(self.frame_23) + self.verticalLayout_13.addWidget(self.frame_18) + self.frame_19 = QtWidgets.QFrame(self.scrollAreaWidgetContents) + self.frame_19.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_19.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_19.setObjectName("frame_19") + self.verticalLayout_37 = QtWidgets.QVBoxLayout(self.frame_19) + self.verticalLayout_37.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_37.setSpacing(0) + self.verticalLayout_37.setObjectName("verticalLayout_37") + self.label_94 = QtWidgets.QLabel(self.frame_19) + self.label_94.setStyleSheet("background-color: rgb(176, 220, 192);") + self.label_94.setObjectName("label_94") + self.verticalLayout_37.addWidget(self.label_94) + self.groupBox_29 = QtWidgets.QGroupBox(self.frame_19) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_29.setPalette(palette) - self.groupBox_29.setAutoFillBackground(True) - self.groupBox_29.setStyleSheet("") + self.groupBox_29.setAutoFillBackground(False) + self.groupBox_29.setStyleSheet("background-color: rgb(211, 248, 224);") + self.groupBox_29.setTitle("") self.groupBox_29.setObjectName("groupBox_29") self.horizontalLayout_24 = QtWidgets.QHBoxLayout(self.groupBox_29) self.horizontalLayout_24.setObjectName("horizontalLayout_24") self.groupBox_34 = QtWidgets.QGroupBox(self.groupBox_29) + self.groupBox_34.setStyleSheet("background-color: rgb(250, 250, 250);") self.groupBox_34.setObjectName("groupBox_34") self.verticalLayout_31 = QtWidgets.QVBoxLayout(self.groupBox_34) self.verticalLayout_31.setObjectName("verticalLayout_31") @@ -253,19 +354,6 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cea_ra_performer.setObjectName("cea_ra_performer") self.verticalLayout_31.addWidget(self.cea_ra_performer) self.horizontalLayout_24.addWidget(self.groupBox_34) - self.line_6 = QtWidgets.QFrame(self.groupBox_29) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) - sizePolicy.setHorizontalStretch(1) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.line_6.sizePolicy().hasHeightForWidth()) - self.line_6.setSizePolicy(sizePolicy) - self.line_6.setAutoFillBackground(False) - self.line_6.setLineWidth(1) - self.line_6.setMidLineWidth(1) - self.line_6.setFrameShape(QtWidgets.QFrame.VLine) - self.line_6.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_6.setObjectName("line_6") - self.horizontalLayout_24.addWidget(self.line_6) self.line_7 = QtWidgets.QFrame(self.groupBox_29) self.line_7.setFrameShape(QtWidgets.QFrame.VLine) self.line_7.setFrameShadow(QtWidgets.QFrame.Sunken) @@ -280,6 +368,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox_30.sizePolicy().hasHeightForWidth()) self.groupBox_30.setSizePolicy(sizePolicy) + self.groupBox_30.setStyleSheet("background-color: rgb(250, 250, 250);") self.groupBox_30.setObjectName("groupBox_30") self.verticalLayout_29 = QtWidgets.QVBoxLayout(self.groupBox_30) self.verticalLayout_29.setObjectName("verticalLayout_29") @@ -299,38 +388,64 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cea_ra_merge_ta.setObjectName("cea_ra_merge_ta") self.verticalLayout_29.addWidget(self.cea_ra_merge_ta) self.horizontalLayout_24.addWidget(self.groupBox_30) - self.verticalLayout_13.addWidget(self.groupBox_29) - self.groupBox_5 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.verticalLayout_37.addWidget(self.groupBox_29) + self.verticalLayout_13.addWidget(self.frame_19) + self.frame_20 = QtWidgets.QFrame(self.scrollAreaWidgetContents) + self.frame_20.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_20.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_20.setObjectName("frame_20") + self.verticalLayout_38 = QtWidgets.QVBoxLayout(self.frame_20) + self.verticalLayout_38.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_38.setSpacing(0) + self.verticalLayout_38.setObjectName("verticalLayout_38") + self.label_95 = QtWidgets.QLabel(self.frame_20) + self.label_95.setStyleSheet("background-color: rgb(176, 220, 192);") + self.label_95.setObjectName("label_95") + self.verticalLayout_38.addWidget(self.label_95) + self.groupBox_5 = QtWidgets.QGroupBox(self.frame_20) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 255)) + brush = QtGui.QBrush(QtGui.QColor(211, 248, 224)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_5.setPalette(palette) - self.groupBox_5.setAutoFillBackground(True) - self.groupBox_5.setStyleSheet("") + self.groupBox_5.setAutoFillBackground(False) + self.groupBox_5.setStyleSheet("background-color: rgb(211, 248, 224);") + self.groupBox_5.setTitle("") self.groupBox_5.setObjectName("groupBox_5") self.gridLayout = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout.setVerticalSpacing(6) self.gridLayout.setObjectName("gridLayout") self.groupBox_9 = QtWidgets.QGroupBox(self.groupBox_5) + self.groupBox_9.setStyleSheet("background-color: rgba(250, 250, 250, 250);") self.groupBox_9.setObjectName("groupBox_9") self.verticalLayout_15 = QtWidgets.QVBoxLayout(self.groupBox_9) self.verticalLayout_15.setObjectName("verticalLayout_15") self.frame_5 = QtWidgets.QFrame(self.groupBox_9) + self.frame_5.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_5.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_5.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_5.setObjectName("frame_5") @@ -343,11 +458,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_44.setObjectName("label_44") self.horizontalLayout_8.addWidget(self.label_44) self.cea_chorusmaster = QtWidgets.QLineEdit(self.frame_5) - self.cea_chorusmaster.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_chorusmaster.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_chorusmaster.setObjectName("cea_chorusmaster") self.horizontalLayout_8.addWidget(self.cea_chorusmaster) self.verticalLayout_15.addWidget(self.frame_5) self.frame_3 = QtWidgets.QFrame(self.groupBox_9) + self.frame_3.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_3.setObjectName("frame_3") @@ -358,11 +474,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_46.setObjectName("label_46") self.horizontalLayout_6.addWidget(self.label_46) self.cea_concertmaster = QtWidgets.QLineEdit(self.frame_3) - self.cea_concertmaster.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_concertmaster.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_concertmaster.setObjectName("cea_concertmaster") self.horizontalLayout_6.addWidget(self.cea_concertmaster) self.verticalLayout_15.addWidget(self.frame_3) self.frame_12 = QtWidgets.QFrame(self.groupBox_9) + self.frame_12.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_12.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_12.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_12.setObjectName("frame_12") @@ -372,11 +489,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_34.setObjectName("label_34") self.horizontalLayout_23.addWidget(self.label_34) self.cea_lyricist = QtWidgets.QLineEdit(self.frame_12) - self.cea_lyricist.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_lyricist.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_lyricist.setObjectName("cea_lyricist") self.horizontalLayout_23.addWidget(self.cea_lyricist) self.verticalLayout_15.addWidget(self.frame_12) self.frame_7 = QtWidgets.QFrame(self.groupBox_9) + self.frame_7.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_7.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_7.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_7.setObjectName("frame_7") @@ -386,11 +504,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_26.setObjectName("label_26") self.horizontalLayout_19.addWidget(self.label_26) self.cea_librettist = QtWidgets.QLineEdit(self.frame_7) - self.cea_librettist.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_librettist.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_librettist.setObjectName("cea_librettist") self.horizontalLayout_19.addWidget(self.cea_librettist) self.verticalLayout_15.addWidget(self.frame_7) self.frame_10 = QtWidgets.QFrame(self.groupBox_9) + self.frame_10.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_10.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_10.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_10.setObjectName("frame_10") @@ -400,15 +519,17 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_30.setObjectName("label_30") self.horizontalLayout_21.addWidget(self.label_30) self.cea_translator = QtWidgets.QLineEdit(self.frame_10) - self.cea_translator.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_translator.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_translator.setObjectName("cea_translator") self.horizontalLayout_21.addWidget(self.cea_translator) self.verticalLayout_15.addWidget(self.frame_10) self.gridLayout.addWidget(self.groupBox_9, 0, 1, 1, 1) self.groupBox_8 = QtWidgets.QGroupBox(self.groupBox_5) + self.groupBox_8.setStyleSheet("background-color: rgba(250, 250, 250, 250);") self.groupBox_8.setTitle("") self.groupBox_8.setObjectName("groupBox_8") self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.groupBox_8) + self.verticalLayout_14.setSpacing(6) self.verticalLayout_14.setObjectName("verticalLayout_14") self.cea_arrangers = QtWidgets.QCheckBox(self.groupBox_8) self.cea_arrangers.setObjectName("cea_arrangers") @@ -427,10 +548,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.verticalLayout_14.addWidget(self.cea_no_solo) self.gridLayout.addWidget(self.groupBox_8, 0, 0, 1, 1) self.groupBox_28 = QtWidgets.QGroupBox(self.groupBox_5) + self.groupBox_28.setStyleSheet("background-color: rgb(250, 250, 250);") self.groupBox_28.setObjectName("groupBox_28") self.verticalLayout_21 = QtWidgets.QVBoxLayout(self.groupBox_28) self.verticalLayout_21.setObjectName("verticalLayout_21") self.frame_17 = QtWidgets.QFrame(self.groupBox_28) + self.frame_17.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_17.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_17.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_17.setObjectName("frame_17") @@ -440,11 +563,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_56.setObjectName("label_56") self.horizontalLayout_30.addWidget(self.label_56) self.cea_writer = QtWidgets.QLineEdit(self.frame_17) - self.cea_writer.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_writer.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_writer.setObjectName("cea_writer") self.horizontalLayout_30.addWidget(self.cea_writer) self.verticalLayout_21.addWidget(self.frame_17) self.frame_16 = QtWidgets.QFrame(self.groupBox_28) + self.frame_16.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_16.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_16.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_16.setObjectName("frame_16") @@ -454,11 +578,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_54.setObjectName("label_54") self.horizontalLayout_29.addWidget(self.label_54) self.cea_arranger = QtWidgets.QLineEdit(self.frame_16) - self.cea_arranger.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_arranger.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_arranger.setObjectName("cea_arranger") self.horizontalLayout_29.addWidget(self.cea_arranger) self.verticalLayout_21.addWidget(self.frame_16) self.frame_4 = QtWidgets.QFrame(self.groupBox_28) + self.frame_4.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_4.setObjectName("frame_4") @@ -469,11 +594,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_45.setObjectName("label_45") self.horizontalLayout_7.addWidget(self.label_45) self.cea_orchestrator = QtWidgets.QLineEdit(self.frame_4) - self.cea_orchestrator.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_orchestrator.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_orchestrator.setObjectName("cea_orchestrator") self.horizontalLayout_7.addWidget(self.cea_orchestrator) self.verticalLayout_21.addWidget(self.frame_4) self.frame_11 = QtWidgets.QFrame(self.groupBox_28) + self.frame_11.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_11.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_11.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_11.setObjectName("frame_11") @@ -483,11 +609,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_32.setObjectName("label_32") self.horizontalLayout_22.addWidget(self.label_32) self.cea_reconstructed = QtWidgets.QLineEdit(self.frame_11) - self.cea_reconstructed.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_reconstructed.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_reconstructed.setObjectName("cea_reconstructed") self.horizontalLayout_22.addWidget(self.cea_reconstructed) self.verticalLayout_21.addWidget(self.frame_11) self.frame_8 = QtWidgets.QFrame(self.groupBox_28) + self.frame_8.setStyleSheet("background-color: rgb(211, 248, 224);") self.frame_8.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_8.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_8.setObjectName("frame_8") @@ -497,41 +624,64 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_28.setObjectName("label_28") self.horizontalLayout_20.addWidget(self.label_28) self.cea_revised = QtWidgets.QLineEdit(self.frame_8) - self.cea_revised.setStyleSheet("background-color: rgb(229, 229, 255);") + self.cea_revised.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_revised.setObjectName("cea_revised") self.horizontalLayout_20.addWidget(self.cea_revised) self.verticalLayout_21.addWidget(self.frame_8) self.gridLayout.addWidget(self.groupBox_28, 0, 2, 1, 1) - self.verticalLayout_13.addWidget(self.groupBox_5) + self.verticalLayout_38.addWidget(self.groupBox_5) + self.verticalLayout_13.addWidget(self.frame_20) self.line_3 = QtWidgets.QFrame(self.scrollAreaWidgetContents) self.line_3.setLineWidth(1) self.line_3.setFrameShape(QtWidgets.QFrame.HLine) self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_3.setObjectName("line_3") self.verticalLayout_13.addWidget(self.line_3) - self.groupBox_31 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.frame_21 = QtWidgets.QFrame(self.scrollAreaWidgetContents) + self.frame_21.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_21.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_21.setObjectName("frame_21") + self.verticalLayout_39 = QtWidgets.QVBoxLayout(self.frame_21) + self.verticalLayout_39.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_39.setSpacing(0) + self.verticalLayout_39.setObjectName("verticalLayout_39") + self.label_96 = QtWidgets.QLabel(self.frame_21) + self.label_96.setStyleSheet("background-color: rgb(204, 168, 161);") + self.label_96.setObjectName("label_96") + self.verticalLayout_39.addWidget(self.label_96) + self.groupBox_31 = QtWidgets.QGroupBox(self.frame_21) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 110, 98)) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 110, 98)) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 110, 98)) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 110, 98)) + brush = QtGui.QBrush(QtGui.QColor(230, 215, 211)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_31.setPalette(palette) - self.groupBox_31.setAutoFillBackground(True) - self.groupBox_31.setStyleSheet("") + self.groupBox_31.setAutoFillBackground(False) + self.groupBox_31.setStyleSheet("background-color: rgb(230, 215, 211);") + self.groupBox_31.setTitle("") self.groupBox_31.setObjectName("groupBox_31") self.horizontalLayout_25 = QtWidgets.QHBoxLayout(self.groupBox_31) self.horizontalLayout_25.setObjectName("horizontalLayout_25") @@ -544,6 +694,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.verticalLayout_30 = QtWidgets.QVBoxLayout(self.groupBox_32) self.verticalLayout_30.setObjectName("verticalLayout_30") self.frame_13 = QtWidgets.QFrame(self.groupBox_32) + self.frame_13.setStyleSheet("background-color: rgb(204, 168, 161);") self.frame_13.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_13.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_13.setObjectName("frame_13") @@ -553,11 +704,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_50.setObjectName("label_50") self.horizontalLayout_26.addWidget(self.label_50) self.cea_lyrics_tag = QtWidgets.QLineEdit(self.frame_13) - self.cea_lyrics_tag.setStyleSheet("background-color: rgb(255, 233, 181);") + self.cea_lyrics_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_lyrics_tag.setObjectName("cea_lyrics_tag") self.horizontalLayout_26.addWidget(self.cea_lyrics_tag) self.verticalLayout_30.addWidget(self.frame_13) self.frame_14 = QtWidgets.QFrame(self.groupBox_32) + self.frame_14.setStyleSheet("background-color: rgb(204, 168, 161);") self.frame_14.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_14.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_14.setObjectName("frame_14") @@ -567,11 +719,12 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_51.setObjectName("label_51") self.horizontalLayout_27.addWidget(self.label_51) self.cea_album_lyrics = QtWidgets.QLineEdit(self.frame_14) - self.cea_album_lyrics.setStyleSheet("background-color: rgb(255, 233, 181);") + self.cea_album_lyrics.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_album_lyrics.setObjectName("cea_album_lyrics") self.horizontalLayout_27.addWidget(self.cea_album_lyrics) self.verticalLayout_30.addWidget(self.frame_14) self.frame_15 = QtWidgets.QFrame(self.groupBox_32) + self.frame_15.setStyleSheet("background-color: rgb(204, 168, 161);") self.frame_15.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_15.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_15.setObjectName("frame_15") @@ -581,1133 +734,218 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_52.setObjectName("label_52") self.horizontalLayout_28.addWidget(self.label_52) self.cea_track_lyrics = QtWidgets.QLineEdit(self.frame_15) - self.cea_track_lyrics.setStyleSheet("background-color: rgb(255, 233, 181);") + self.cea_track_lyrics.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_track_lyrics.setObjectName("cea_track_lyrics") self.horizontalLayout_28.addWidget(self.cea_track_lyrics) self.verticalLayout_30.addWidget(self.frame_15) self.horizontalLayout_25.addWidget(self.groupBox_32) - self.verticalLayout_13.addWidget(self.groupBox_31) + self.verticalLayout_39.addWidget(self.groupBox_31) + self.verticalLayout_13.addWidget(self.frame_21) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_13.addItem(spacerItem) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.verticalLayout_10.addWidget(self.scrollArea) self.tabWidget.addTab(self.Artists, "") - self.Tag_mapping = QtWidgets.QWidget() - self.Tag_mapping.setObjectName("Tag_mapping") - self.verticalLayout_28 = QtWidgets.QVBoxLayout(self.Tag_mapping) - self.verticalLayout_28.setObjectName("verticalLayout_28") - self.scrollArea_4 = QtWidgets.QScrollArea(self.Tag_mapping) + self.Works = QtWidgets.QWidget() + self.Works.setObjectName("Works") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.Works) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.scrollArea_3 = QtWidgets.QScrollArea(self.Works) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.scrollArea_4.sizePolicy().hasHeightForWidth()) - self.scrollArea_4.setSizePolicy(sizePolicy) - self.scrollArea_4.setWidgetResizable(True) - self.scrollArea_4.setObjectName("scrollArea_4") - self.scrollAreaWidgetContents_8 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_8.setGeometry(QtCore.QRect(0, 0, 1104, 854)) - self.scrollAreaWidgetContents_8.setObjectName("scrollAreaWidgetContents_8") - self.verticalLayout_33 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_8) - self.verticalLayout_33.setObjectName("verticalLayout_33") - self.label_18 = QtWidgets.QLabel(self.scrollAreaWidgetContents_8) - self.label_18.setStyleSheet("") - self.label_18.setObjectName("label_18") - self.verticalLayout_33.addWidget(self.label_18) - self.groupBox_11 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_8) + sizePolicy.setHeightForWidth(self.scrollArea_3.sizePolicy().hasHeightForWidth()) + self.scrollArea_3.setSizePolicy(sizePolicy) + self.scrollArea_3.setWidgetResizable(True) + self.scrollArea_3.setObjectName("scrollArea_3") + self.scrollAreaWidgetContents_3 = QtWidgets.QWidget() + self.scrollAreaWidgetContents_3.setGeometry(QtCore.QRect(0, 0, 1084, 1022)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scrollAreaWidgetContents_3.sizePolicy().hasHeightForWidth()) + self.scrollAreaWidgetContents_3.setSizePolicy(sizePolicy) + self.scrollAreaWidgetContents_3.setObjectName("scrollAreaWidgetContents_3") + self.verticalLayout_25 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_3) + self.verticalLayout_25.setObjectName("verticalLayout_25") + self.frame_2 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) + self.frame_2.setSizePolicy(sizePolicy) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_11.setPalette(palette) - self.groupBox_11.setAutoFillBackground(True) - self.groupBox_11.setStyleSheet("") - self.groupBox_11.setObjectName("groupBox_11") - self.verticalLayout_17 = QtWidgets.QVBoxLayout(self.groupBox_11) - self.verticalLayout_17.setObjectName("verticalLayout_17") - self.artist_tags = QtWidgets.QGroupBox(self.groupBox_11) + self.frame_2.setPalette(palette) + self.frame_2.setAutoFillBackground(False) + self.frame_2.setStyleSheet("background-color: rgb(255, 255, 222);") + self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_2.setObjectName("frame_2") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.frame_2) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.use_cwp = QtWidgets.QCheckBox(self.frame_2) + self.use_cwp.setObjectName("use_cwp") + self.horizontalLayout_3.addWidget(self.use_cwp) + self.cwp_collections = QtWidgets.QCheckBox(self.frame_2) + self.cwp_collections.setObjectName("cwp_collections") + self.horizontalLayout_3.addWidget(self.cwp_collections) + self.use_cache = QtWidgets.QCheckBox(self.frame_2) + self.use_cache.setObjectName("use_cache") + self.horizontalLayout_3.addWidget(self.use_cache) + self.verticalLayout_25.addWidget(self.frame_2) + self.frame_26 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) + self.frame_26.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_26.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_26.setObjectName("frame_26") + self.verticalLayout_44 = QtWidgets.QVBoxLayout(self.frame_26) + self.verticalLayout_44.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_44.setSpacing(0) + self.verticalLayout_44.setObjectName("verticalLayout_44") + self.label_99 = QtWidgets.QLabel(self.frame_26) + self.label_99.setStyleSheet("background-color: rgb(205, 230, 255);") + self.label_99.setObjectName("label_99") + self.verticalLayout_44.addWidget(self.label_99) + self.Style = QtWidgets.QGroupBox(self.frame_26) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.Style.sizePolicy().hasHeightForWidth()) + self.Style.setSizePolicy(sizePolicy) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) + brush = QtGui.QBrush(QtGui.QColor(225, 240, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.artist_tags.setPalette(palette) - self.artist_tags.setAutoFillBackground(True) - self.artist_tags.setStyleSheet("") - self.artist_tags.setObjectName("artist_tags") - self.verticalLayout_12 = QtWidgets.QVBoxLayout(self.artist_tags) - self.verticalLayout_12.setObjectName("verticalLayout_12") - self.label_3 = QtWidgets.QLabel(self.artist_tags) - self.label_3.setObjectName("label_3") - self.verticalLayout_12.addWidget(self.label_3) - self.cea_blank_tag = QtWidgets.QLineEdit(self.artist_tags) - self.cea_blank_tag.setStyleSheet("background-color: rgb(226, 237, 255);") - self.cea_blank_tag.setObjectName("cea_blank_tag") - self.verticalLayout_12.addWidget(self.cea_blank_tag) - self.cea_blank_tag_2 = QtWidgets.QLineEdit(self.artist_tags) - self.cea_blank_tag_2.setStyleSheet("background-color: rgb(226, 237, 255);") - self.cea_blank_tag_2.setObjectName("cea_blank_tag_2") - self.verticalLayout_12.addWidget(self.cea_blank_tag_2) - self.verticalLayout_17.addWidget(self.artist_tags) - self.label_19 = QtWidgets.QLabel(self.groupBox_11) - self.label_19.setObjectName("label_19") - self.verticalLayout_17.addWidget(self.label_19) - self.cea_keep = QtWidgets.QLineEdit(self.groupBox_11) - self.cea_keep.setStyleSheet("background-color: rgb(248, 219, 255);") - self.cea_keep.setObjectName("cea_keep") - self.verticalLayout_17.addWidget(self.cea_keep) - self.cea_clear_tags = QtWidgets.QCheckBox(self.groupBox_11) - self.cea_clear_tags.setObjectName("cea_clear_tags") - self.verticalLayout_17.addWidget(self.cea_clear_tags) - self.verticalLayout_33.addWidget(self.groupBox_11) - self.artist_tags_2 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_8) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.artist_tags_2.sizePolicy().hasHeightForWidth()) - self.artist_tags_2.setSizePolicy(sizePolicy) + self.Style.setPalette(palette) + self.Style.setAutoFillBackground(False) + self.Style.setStyleSheet("background-color: rgb(225, 240, 255);") + self.Style.setTitle("") + self.Style.setObjectName("Style") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.Style) + self.horizontalLayout.setObjectName("horizontalLayout") + self.groupBox_4 = QtWidgets.QGroupBox(self.Style) + self.groupBox_4.setObjectName("groupBox_4") + self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.groupBox_4) + self.verticalLayout_11.setObjectName("verticalLayout_11") + self.cwp_titles = QtWidgets.QRadioButton(self.groupBox_4) + self.cwp_titles.setObjectName("cwp_titles") + self.verticalLayout_11.addWidget(self.cwp_titles) + self.cwp_works = QtWidgets.QRadioButton(self.groupBox_4) + self.cwp_works.setObjectName("cwp_works") + self.verticalLayout_11.addWidget(self.cwp_works) + self.cwp_extended = QtWidgets.QRadioButton(self.groupBox_4) + self.cwp_extended.setObjectName("cwp_extended") + self.verticalLayout_11.addWidget(self.cwp_extended) + self.horizontalLayout.addWidget(self.groupBox_4) + self.groupBox_16 = QtWidgets.QGroupBox(self.Style) + self.groupBox_16.setEnabled(True) + self.groupBox_16.setObjectName("groupBox_16") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_16) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.cwp_hierarchical_works = QtWidgets.QRadioButton(self.groupBox_16) + self.cwp_hierarchical_works.setObjectName("cwp_hierarchical_works") + self.verticalLayout_6.addWidget(self.cwp_hierarchical_works) + self.cwp_level0_works = QtWidgets.QRadioButton(self.groupBox_16) + self.cwp_level0_works.setObjectName("cwp_level0_works") + self.verticalLayout_6.addWidget(self.cwp_level0_works) + self.horizontalLayout.addWidget(self.groupBox_16) + self.verticalLayout_44.addWidget(self.Style) + self.verticalLayout_25.addWidget(self.frame_26) + self.frame_27 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) + self.frame_27.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_27.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_27.setObjectName("frame_27") + self.verticalLayout_45 = QtWidgets.QVBoxLayout(self.frame_27) + self.verticalLayout_45.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_45.setSpacing(0) + self.verticalLayout_45.setObjectName("verticalLayout_45") + self.label_100 = QtWidgets.QLabel(self.frame_27) + self.label_100.setStyleSheet("background-color: rgb(255, 186, 189);") + self.label_100.setObjectName("label_100") + self.verticalLayout_45.addWidget(self.label_100) + self.groupBox_18 = QtWidgets.QGroupBox(self.frame_27) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.artist_tags_2.setPalette(palette) - self.artist_tags_2.setFocusPolicy(QtCore.Qt.NoFocus) - self.artist_tags_2.setAutoFillBackground(True) - self.artist_tags_2.setStyleSheet("font: 75 8pt \"MS Shell Dlg 2\";") - self.artist_tags_2.setObjectName("artist_tags_2") - self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.artist_tags_2) - self.verticalLayout_3.setObjectName("verticalLayout_3") - self.textBrowser = QtWidgets.QTextBrowser(self.artist_tags_2) - self.textBrowser.setStyleSheet("background-color: rgb(255, 232, 184);") - self.textBrowser.setObjectName("textBrowser") - self.verticalLayout_3.addWidget(self.textBrowser) - self.frame = QtWidgets.QFrame(self.artist_tags_2) - self.frame.setObjectName("frame") - self._14 = QtWidgets.QHBoxLayout(self.frame) - self._14.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) - self._14.setContentsMargins(1, 1, 1, 1) - self._14.setSpacing(6) - self._14.setObjectName("_14") - self.toolButton_1 = QtWidgets.QToolButton(self.frame) - self.toolButton_1.setStyleSheet("") - self.toolButton_1.setCheckable(True) - self.toolButton_1.setObjectName("toolButton_1") - self._14.addWidget(self.toolButton_1) - self.cea_source_1 = QtWidgets.QComboBox(self.frame) - self.cea_source_1.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.cea_source_1.sizePolicy().hasHeightForWidth()) - self.cea_source_1.setSizePolicy(sizePolicy) - self.cea_source_1.setMouseTracking(False) - self.cea_source_1.setFocusPolicy(QtCore.Qt.StrongFocus) - self.cea_source_1.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_1.setEditable(True) - self.cea_source_1.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - self.cea_source_1.setObjectName("cea_source_1") - self.cea_source_1.addItem("") - self.cea_source_1.setItemText(0, "") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self.cea_source_1.addItem("") - self._14.addWidget(self.cea_source_1) - self.label_21 = QtWidgets.QLabel(self.frame) - self.label_21.setObjectName("label_21") - self._14.addWidget(self.label_21) - self.cea_tag_1 = QtWidgets.QLineEdit(self.frame) - self.cea_tag_1.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_1.setObjectName("cea_tag_1") - self._14.addWidget(self.cea_tag_1) - self.cea_cond_1 = QtWidgets.QCheckBox(self.frame) - self.cea_cond_1.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_1.setObjectName("cea_cond_1") - self._14.addWidget(self.cea_cond_1) - self.verticalLayout_3.addWidget(self.frame) - self._15 = QtWidgets.QHBoxLayout() - self._15.setContentsMargins(0, 0, 0, 0) - self._15.setSpacing(6) - self._15.setObjectName("_15") - self.toolButton_2 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_2.setStyleSheet("") - self.toolButton_2.setCheckable(True) - self.toolButton_2.setObjectName("toolButton_2") - self._15.addWidget(self.toolButton_2) - self.cea_source_2 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_2.setEnabled(False) - self.cea_source_2.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_2.setEditable(True) - self.cea_source_2.setObjectName("cea_source_2") - self.cea_source_2.addItem("") - self.cea_source_2.setItemText(0, "") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self.cea_source_2.addItem("") - self._15.addWidget(self.cea_source_2) - self.label_23 = QtWidgets.QLabel(self.artist_tags_2) - self.label_23.setObjectName("label_23") - self._15.addWidget(self.label_23) - self.cea_tag_2 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_2.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_2.setObjectName("cea_tag_2") - self._15.addWidget(self.cea_tag_2) - self.cea_cond_2 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_2.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_2.setObjectName("cea_cond_2") - self._15.addWidget(self.cea_cond_2) - self.verticalLayout_3.addLayout(self._15) - self._16 = QtWidgets.QHBoxLayout() - self._16.setContentsMargins(0, 0, 0, 0) - self._16.setSpacing(6) - self._16.setObjectName("_16") - self.toolButton_3 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_3.setStyleSheet("") - self.toolButton_3.setCheckable(True) - self.toolButton_3.setObjectName("toolButton_3") - self._16.addWidget(self.toolButton_3) - self.cea_source_3 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_3.setEnabled(False) - self.cea_source_3.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_3.setEditable(True) - self.cea_source_3.setObjectName("cea_source_3") - self.cea_source_3.addItem("") - self.cea_source_3.setItemText(0, "") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self.cea_source_3.addItem("") - self._16.addWidget(self.cea_source_3) - self.label_25 = QtWidgets.QLabel(self.artist_tags_2) - self.label_25.setObjectName("label_25") - self._16.addWidget(self.label_25) - self.cea_tag_3 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_3.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_3.setObjectName("cea_tag_3") - self._16.addWidget(self.cea_tag_3) - self.cea_cond_3 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_3.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_3.setObjectName("cea_cond_3") - self._16.addWidget(self.cea_cond_3) - self.verticalLayout_3.addLayout(self._16) - self._17 = QtWidgets.QHBoxLayout() - self._17.setContentsMargins(0, 0, 0, 0) - self._17.setSpacing(6) - self._17.setObjectName("_17") - self.toolButton_4 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_4.setStyleSheet("") - self.toolButton_4.setCheckable(True) - self.toolButton_4.setObjectName("toolButton_4") - self._17.addWidget(self.toolButton_4) - self.cea_source_4 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_4.setEnabled(False) - self.cea_source_4.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_4.setEditable(True) - self.cea_source_4.setObjectName("cea_source_4") - self.cea_source_4.addItem("") - self.cea_source_4.setItemText(0, "") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self.cea_source_4.addItem("") - self._17.addWidget(self.cea_source_4) - self.label_27 = QtWidgets.QLabel(self.artist_tags_2) - self.label_27.setObjectName("label_27") - self._17.addWidget(self.label_27) - self.cea_tag_4 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_4.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_4.setObjectName("cea_tag_4") - self._17.addWidget(self.cea_tag_4) - self.cea_cond_4 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_4.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_4.setObjectName("cea_cond_4") - self._17.addWidget(self.cea_cond_4) - self.verticalLayout_3.addLayout(self._17) - self._18 = QtWidgets.QHBoxLayout() - self._18.setContentsMargins(0, 0, 0, 0) - self._18.setSpacing(6) - self._18.setObjectName("_18") - self.toolButton_5 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_5.setStyleSheet("") - self.toolButton_5.setCheckable(True) - self.toolButton_5.setObjectName("toolButton_5") - self._18.addWidget(self.toolButton_5) - self.cea_source_5 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_5.setEnabled(False) - self.cea_source_5.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_5.setEditable(True) - self.cea_source_5.setObjectName("cea_source_5") - self.cea_source_5.addItem("") - self.cea_source_5.setItemText(0, "") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self.cea_source_5.addItem("") - self._18.addWidget(self.cea_source_5) - self.label_29 = QtWidgets.QLabel(self.artist_tags_2) - self.label_29.setObjectName("label_29") - self._18.addWidget(self.label_29) - self.cea_tag_5 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_5.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_5.setObjectName("cea_tag_5") - self._18.addWidget(self.cea_tag_5) - self.cea_cond_5 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_5.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_5.setObjectName("cea_cond_5") - self._18.addWidget(self.cea_cond_5) - self.verticalLayout_3.addLayout(self._18) - self._19 = QtWidgets.QHBoxLayout() - self._19.setContentsMargins(0, 0, 0, 0) - self._19.setSpacing(6) - self._19.setObjectName("_19") - self.toolButton_6 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_6.setStyleSheet("") - self.toolButton_6.setCheckable(True) - self.toolButton_6.setObjectName("toolButton_6") - self._19.addWidget(self.toolButton_6) - self.cea_source_6 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_6.setEnabled(False) - self.cea_source_6.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_6.setEditable(True) - self.cea_source_6.setObjectName("cea_source_6") - self.cea_source_6.addItem("") - self.cea_source_6.setItemText(0, "") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self.cea_source_6.addItem("") - self._19.addWidget(self.cea_source_6) - self.label_31 = QtWidgets.QLabel(self.artist_tags_2) - self.label_31.setObjectName("label_31") - self._19.addWidget(self.label_31) - self.cea_tag_6 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_6.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_6.setObjectName("cea_tag_6") - self._19.addWidget(self.cea_tag_6) - self.cea_cond_6 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_6.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_6.setObjectName("cea_cond_6") - self._19.addWidget(self.cea_cond_6) - self.verticalLayout_3.addLayout(self._19) - self._20 = QtWidgets.QHBoxLayout() - self._20.setContentsMargins(0, 0, 0, 0) - self._20.setSpacing(6) - self._20.setObjectName("_20") - self.toolButton_7 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_7.setStyleSheet("") - self.toolButton_7.setCheckable(True) - self.toolButton_7.setObjectName("toolButton_7") - self._20.addWidget(self.toolButton_7) - self.cea_source_7 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_7.setEnabled(False) - self.cea_source_7.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_7.setEditable(True) - self.cea_source_7.setObjectName("cea_source_7") - self.cea_source_7.addItem("") - self.cea_source_7.setItemText(0, "") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self.cea_source_7.addItem("") - self._20.addWidget(self.cea_source_7) - self.label_33 = QtWidgets.QLabel(self.artist_tags_2) - self.label_33.setObjectName("label_33") - self._20.addWidget(self.label_33) - self.cea_tag_7 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_7.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_7.setObjectName("cea_tag_7") - self._20.addWidget(self.cea_tag_7) - self.cea_cond_7 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_7.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_7.setObjectName("cea_cond_7") - self._20.addWidget(self.cea_cond_7) - self.verticalLayout_3.addLayout(self._20) - self._21 = QtWidgets.QHBoxLayout() - self._21.setContentsMargins(0, 0, 0, 0) - self._21.setSpacing(6) - self._21.setObjectName("_21") - self.toolButton_8 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_8.setStyleSheet("") - self.toolButton_8.setCheckable(True) - self.toolButton_8.setObjectName("toolButton_8") - self._21.addWidget(self.toolButton_8) - self.cea_source_8 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_8.setEnabled(False) - self.cea_source_8.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_8.setEditable(True) - self.cea_source_8.setObjectName("cea_source_8") - self.cea_source_8.addItem("") - self.cea_source_8.setItemText(0, "") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self.cea_source_8.addItem("") - self._21.addWidget(self.cea_source_8) - self.label_35 = QtWidgets.QLabel(self.artist_tags_2) - self.label_35.setObjectName("label_35") - self._21.addWidget(self.label_35) - self.cea_tag_8 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_8.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_8.setObjectName("cea_tag_8") - self._21.addWidget(self.cea_tag_8) - self.cea_cond_8 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_8.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_8.setObjectName("cea_cond_8") - self._21.addWidget(self.cea_cond_8) - self.verticalLayout_3.addLayout(self._21) - self._30 = QtWidgets.QHBoxLayout() - self._30.setContentsMargins(0, 0, 0, 0) - self._30.setSpacing(6) - self._30.setObjectName("_30") - self.toolButton_9 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_9.setStyleSheet("") - self.toolButton_9.setCheckable(True) - self.toolButton_9.setObjectName("toolButton_9") - self._30.addWidget(self.toolButton_9) - self.cea_source_9 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_9.setEnabled(False) - self.cea_source_9.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_9.setEditable(True) - self.cea_source_9.setObjectName("cea_source_9") - self.cea_source_9.addItem("") - self.cea_source_9.setItemText(0, "") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self.cea_source_9.addItem("") - self._30.addWidget(self.cea_source_9) - self.label_53 = QtWidgets.QLabel(self.artist_tags_2) - self.label_53.setObjectName("label_53") - self._30.addWidget(self.label_53) - self.cea_tag_9 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_9.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_9.setObjectName("cea_tag_9") - self._30.addWidget(self.cea_tag_9) - self.cea_cond_9 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_9.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_9.setObjectName("cea_cond_9") - self._30.addWidget(self.cea_cond_9) - self.verticalLayout_3.addLayout(self._30) - self._31 = QtWidgets.QHBoxLayout() - self._31.setContentsMargins(0, 0, 0, 0) - self._31.setSpacing(6) - self._31.setObjectName("_31") - self.toolButton_10 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_10.setStyleSheet("") - self.toolButton_10.setCheckable(True) - self.toolButton_10.setObjectName("toolButton_10") - self._31.addWidget(self.toolButton_10) - self.cea_source_10 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_10.setEnabled(False) - self.cea_source_10.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_10.setEditable(True) - self.cea_source_10.setObjectName("cea_source_10") - self.cea_source_10.addItem("") - self.cea_source_10.setItemText(0, "") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self.cea_source_10.addItem("") - self._31.addWidget(self.cea_source_10) - self.label_55 = QtWidgets.QLabel(self.artist_tags_2) - self.label_55.setObjectName("label_55") - self._31.addWidget(self.label_55) - self.cea_tag_10 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_10.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_10.setObjectName("cea_tag_10") - self._31.addWidget(self.cea_tag_10) - self.cea_cond_10 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_10.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_10.setObjectName("cea_cond_10") - self._31.addWidget(self.cea_cond_10) - self.verticalLayout_3.addLayout(self._31) - self._32 = QtWidgets.QHBoxLayout() - self._32.setContentsMargins(0, 0, 0, 0) - self._32.setSpacing(6) - self._32.setObjectName("_32") - self.toolButton_11 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_11.setStyleSheet("") - self.toolButton_11.setCheckable(True) - self.toolButton_11.setObjectName("toolButton_11") - self._32.addWidget(self.toolButton_11) - self.cea_source_11 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_11.setEnabled(False) - self.cea_source_11.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_11.setEditable(True) - self.cea_source_11.setObjectName("cea_source_11") - self.cea_source_11.addItem("") - self.cea_source_11.setItemText(0, "") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self.cea_source_11.addItem("") - self._32.addWidget(self.cea_source_11) - self.label_57 = QtWidgets.QLabel(self.artist_tags_2) - self.label_57.setObjectName("label_57") - self._32.addWidget(self.label_57) - self.cea_tag_11 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_11.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_11.setObjectName("cea_tag_11") - self._32.addWidget(self.cea_tag_11) - self.cea_cond_11 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_11.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_11.setObjectName("cea_cond_11") - self._32.addWidget(self.cea_cond_11) - self.verticalLayout_3.addLayout(self._32) - self._33 = QtWidgets.QHBoxLayout() - self._33.setContentsMargins(0, 0, 0, 0) - self._33.setSpacing(6) - self._33.setObjectName("_33") - self.toolButton_12 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_12.setStyleSheet("") - self.toolButton_12.setCheckable(True) - self.toolButton_12.setObjectName("toolButton_12") - self._33.addWidget(self.toolButton_12) - self.cea_source_12 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_12.setEnabled(False) - self.cea_source_12.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_12.setEditable(True) - self.cea_source_12.setObjectName("cea_source_12") - self.cea_source_12.addItem("") - self.cea_source_12.setItemText(0, "") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self.cea_source_12.addItem("") - self._33.addWidget(self.cea_source_12) - self.label_59 = QtWidgets.QLabel(self.artist_tags_2) - self.label_59.setObjectName("label_59") - self._33.addWidget(self.label_59) - self.cea_tag_12 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_12.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_12.setObjectName("cea_tag_12") - self._33.addWidget(self.cea_tag_12) - self.cea_cond_12 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_12.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_12.setObjectName("cea_cond_12") - self._33.addWidget(self.cea_cond_12) - self.verticalLayout_3.addLayout(self._33) - self._34 = QtWidgets.QHBoxLayout() - self._34.setContentsMargins(0, 0, 0, 0) - self._34.setSpacing(6) - self._34.setObjectName("_34") - self.toolButton_13 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_13.setStyleSheet("") - self.toolButton_13.setCheckable(True) - self.toolButton_13.setObjectName("toolButton_13") - self._34.addWidget(self.toolButton_13) - self.cea_source_13 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_13.setEnabled(False) - self.cea_source_13.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_13.setEditable(True) - self.cea_source_13.setObjectName("cea_source_13") - self.cea_source_13.addItem("") - self.cea_source_13.setItemText(0, "") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self.cea_source_13.addItem("") - self._34.addWidget(self.cea_source_13) - self.label_61 = QtWidgets.QLabel(self.artist_tags_2) - self.label_61.setObjectName("label_61") - self._34.addWidget(self.label_61) - self.cea_tag_13 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_13.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_13.setObjectName("cea_tag_13") - self._34.addWidget(self.cea_tag_13) - self.cea_cond_13 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_13.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_13.setObjectName("cea_cond_13") - self._34.addWidget(self.cea_cond_13) - self.verticalLayout_3.addLayout(self._34) - self._35 = QtWidgets.QHBoxLayout() - self._35.setContentsMargins(0, 0, 0, 0) - self._35.setSpacing(6) - self._35.setObjectName("_35") - self.toolButton_14 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_14.setStyleSheet("") - self.toolButton_14.setCheckable(True) - self.toolButton_14.setObjectName("toolButton_14") - self._35.addWidget(self.toolButton_14) - self.cea_source_14 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_14.setEnabled(False) - self.cea_source_14.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_14.setEditable(True) - self.cea_source_14.setObjectName("cea_source_14") - self.cea_source_14.addItem("") - self.cea_source_14.setItemText(0, "") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self.cea_source_14.addItem("") - self._35.addWidget(self.cea_source_14) - self.label_63 = QtWidgets.QLabel(self.artist_tags_2) - self.label_63.setObjectName("label_63") - self._35.addWidget(self.label_63) - self.cea_tag_14 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_14.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_14.setObjectName("cea_tag_14") - self._35.addWidget(self.cea_tag_14) - self.cea_cond_14 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_14.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_14.setObjectName("cea_cond_14") - self._35.addWidget(self.cea_cond_14) - self.verticalLayout_3.addLayout(self._35) - self._36 = QtWidgets.QHBoxLayout() - self._36.setContentsMargins(0, 0, 0, 0) - self._36.setSpacing(6) - self._36.setObjectName("_36") - self.toolButton_15 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_15.setStyleSheet("") - self.toolButton_15.setCheckable(True) - self.toolButton_15.setObjectName("toolButton_15") - self._36.addWidget(self.toolButton_15) - self.cea_source_15 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_15.setEnabled(False) - self.cea_source_15.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_15.setEditable(True) - self.cea_source_15.setObjectName("cea_source_15") - self.cea_source_15.addItem("") - self.cea_source_15.setItemText(0, "") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self.cea_source_15.addItem("") - self._36.addWidget(self.cea_source_15) - self.label_65 = QtWidgets.QLabel(self.artist_tags_2) - self.label_65.setObjectName("label_65") - self._36.addWidget(self.label_65) - self.cea_tag_15 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_15.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_15.setObjectName("cea_tag_15") - self._36.addWidget(self.cea_tag_15) - self.cea_cond_15 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_15.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_15.setObjectName("cea_cond_15") - self._36.addWidget(self.cea_cond_15) - self.verticalLayout_3.addLayout(self._36) - self._37 = QtWidgets.QHBoxLayout() - self._37.setContentsMargins(0, 0, 0, 0) - self._37.setSpacing(6) - self._37.setObjectName("_37") - self.toolButton_16 = QtWidgets.QToolButton(self.artist_tags_2) - self.toolButton_16.setStyleSheet("") - self.toolButton_16.setCheckable(True) - self.toolButton_16.setObjectName("toolButton_16") - self._37.addWidget(self.toolButton_16) - self.cea_source_16 = QtWidgets.QComboBox(self.artist_tags_2) - self.cea_source_16.setEnabled(False) - self.cea_source_16.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_source_16.setEditable(True) - self.cea_source_16.setObjectName("cea_source_16") - self.cea_source_16.addItem("") - self.cea_source_16.setItemText(0, "") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self.cea_source_16.addItem("") - self._37.addWidget(self.cea_source_16) - self.label_67 = QtWidgets.QLabel(self.artist_tags_2) - self.label_67.setObjectName("label_67") - self._37.addWidget(self.label_67) - self.cea_tag_16 = QtWidgets.QLineEdit(self.artist_tags_2) - self.cea_tag_16.setStyleSheet("background-color: rgb(255, 221, 116);") - self.cea_tag_16.setObjectName("cea_tag_16") - self._37.addWidget(self.cea_tag_16) - self.cea_cond_16 = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_cond_16.setLayoutDirection(QtCore.Qt.RightToLeft) - self.cea_cond_16.setObjectName("cea_cond_16") - self._37.addWidget(self.cea_cond_16) - self.verticalLayout_3.addLayout(self._37) - self._38 = QtWidgets.QHBoxLayout() - self._38.setContentsMargins(0, 0, 0, 0) - self._38.setSpacing(6) - self._38.setObjectName("_38") - self.label_42 = QtWidgets.QLabel(self.artist_tags_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_42.sizePolicy().hasHeightForWidth()) - self.label_42.setSizePolicy(sizePolicy) - self.label_42.setLayoutDirection(QtCore.Qt.LeftToRight) - self.label_42.setLineWidth(1) - self.label_42.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) - self.label_42.setObjectName("label_42") - self._38.addWidget(self.label_42) - self.label_17 = QtWidgets.QLabel(self.artist_tags_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_17.sizePolicy().hasHeightForWidth()) - self.label_17.setSizePolicy(sizePolicy) - self.label_17.setLayoutDirection(QtCore.Qt.RightToLeft) - self.label_17.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_17.setObjectName("label_17") - self._38.addWidget(self.label_17) - self.verticalLayout_3.addLayout(self._38) - self.cea_tag_sort = QtWidgets.QCheckBox(self.artist_tags_2) - self.cea_tag_sort.setLayoutDirection(QtCore.Qt.LeftToRight) - self.cea_tag_sort.setObjectName("cea_tag_sort") - self.verticalLayout_3.addWidget(self.cea_tag_sort) - self.verticalLayout_33.addWidget(self.artist_tags_2) - self.scrollArea_4.setWidget(self.scrollAreaWidgetContents_8) - self.verticalLayout_28.addWidget(self.scrollArea_4) - self.tabWidget.addTab(self.Tag_mapping, "") - self.Works = QtWidgets.QWidget() - self.Works.setObjectName("Works") - self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.Works) - self.verticalLayout_4.setObjectName("verticalLayout_4") - self.scrollArea_3 = QtWidgets.QScrollArea(self.Works) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.scrollArea_3.sizePolicy().hasHeightForWidth()) - self.scrollArea_3.setSizePolicy(sizePolicy) - self.scrollArea_3.setWidgetResizable(True) - self.scrollArea_3.setObjectName("scrollArea_3") - self.scrollAreaWidgetContents_3 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_3.setGeometry(QtCore.QRect(0, 0, 1087, 1012)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.scrollAreaWidgetContents_3.sizePolicy().hasHeightForWidth()) - self.scrollAreaWidgetContents_3.setSizePolicy(sizePolicy) - self.scrollAreaWidgetContents_3.setObjectName("scrollAreaWidgetContents_3") - self.verticalLayout_25 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_3) - self.verticalLayout_25.setObjectName("verticalLayout_25") - self.frame_2 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) - self.frame_2.setSizePolicy(sizePolicy) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(236, 255, 171)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.frame_2.setPalette(palette) - self.frame_2.setAutoFillBackground(True) - self.frame_2.setStyleSheet("") - self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_2.setObjectName("frame_2") - self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.frame_2) - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.use_cwp = QtWidgets.QCheckBox(self.frame_2) - self.use_cwp.setObjectName("use_cwp") - self.horizontalLayout_3.addWidget(self.use_cwp) - self.cwp_collections = QtWidgets.QCheckBox(self.frame_2) - self.cwp_collections.setObjectName("cwp_collections") - self.horizontalLayout_3.addWidget(self.cwp_collections) - self.use_cache = QtWidgets.QCheckBox(self.frame_2) - self.use_cache.setObjectName("use_cache") - self.horizontalLayout_3.addWidget(self.use_cache) - self.verticalLayout_25.addWidget(self.frame_2) - self.Style = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.Style.sizePolicy().hasHeightForWidth()) - self.Style.setSizePolicy(sizePolicy) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(205, 230, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.Style.setPalette(palette) - self.Style.setAutoFillBackground(True) - self.Style.setStyleSheet("") - self.Style.setObjectName("Style") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.Style) - self.horizontalLayout.setObjectName("horizontalLayout") - self.groupBox_4 = QtWidgets.QGroupBox(self.Style) - self.groupBox_4.setObjectName("groupBox_4") - self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.groupBox_4) - self.verticalLayout_11.setObjectName("verticalLayout_11") - self.cwp_titles = QtWidgets.QRadioButton(self.groupBox_4) - self.cwp_titles.setObjectName("cwp_titles") - self.verticalLayout_11.addWidget(self.cwp_titles) - self.cwp_works = QtWidgets.QRadioButton(self.groupBox_4) - self.cwp_works.setObjectName("cwp_works") - self.verticalLayout_11.addWidget(self.cwp_works) - self.cwp_extended = QtWidgets.QRadioButton(self.groupBox_4) - self.cwp_extended.setObjectName("cwp_extended") - self.verticalLayout_11.addWidget(self.cwp_extended) - self.horizontalLayout.addWidget(self.groupBox_4) - self.groupBox_16 = QtWidgets.QGroupBox(self.Style) - self.groupBox_16.setEnabled(True) - self.groupBox_16.setObjectName("groupBox_16") - self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_16) - self.verticalLayout_6.setObjectName("verticalLayout_6") - self.cwp_hierarchical_works = QtWidgets.QRadioButton(self.groupBox_16) - self.cwp_hierarchical_works.setObjectName("cwp_hierarchical_works") - self.verticalLayout_6.addWidget(self.cwp_hierarchical_works) - self.cwp_level0_works = QtWidgets.QRadioButton(self.groupBox_16) - self.cwp_level0_works.setObjectName("cwp_level0_works") - self.verticalLayout_6.addWidget(self.cwp_level0_works) - self.horizontalLayout.addWidget(self.groupBox_16) - self.verticalLayout_25.addWidget(self.Style) - self.groupBox_18 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_3) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(255, 220, 222)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_18.setPalette(palette) - self.groupBox_18.setAutoFillBackground(True) - self.groupBox_18.setStyleSheet("") + self.groupBox_18.setAutoFillBackground(False) + self.groupBox_18.setStyleSheet("background-color: rgb(255, 220, 222);") + self.groupBox_18.setTitle("") self.groupBox_18.setObjectName("groupBox_18") self.horizontalLayout_16 = QtWidgets.QHBoxLayout(self.groupBox_18) self.horizontalLayout_16.setObjectName("horizontalLayout_16") @@ -1753,66 +991,98 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cwp_aliases_tags_user.setObjectName("cwp_aliases_tags_user") self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.cwp_aliases_tags_user) self.cwp_aliases_tag_text = QtWidgets.QLineEdit(self.groupBox_21) - self.cwp_aliases_tag_text.setStyleSheet("background-color: rgb(255, 206, 207);") + self.cwp_aliases_tag_text.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_aliases_tag_text.setObjectName("cwp_aliases_tag_text") self.formLayout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.cwp_aliases_tag_text) self.horizontalLayout_17.addWidget(self.groupBox_21) self.horizontalLayout_16.addWidget(self.groupBox_20) - self.verticalLayout_25.addWidget(self.groupBox_18) - self.Tags = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_3) + self.verticalLayout_45.addWidget(self.groupBox_18) + self.verticalLayout_25.addWidget(self.frame_27) + self.frame_28 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) + self.frame_28.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_28.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_28.setObjectName("frame_28") + self.verticalLayout_46 = QtWidgets.QVBoxLayout(self.frame_28) + self.verticalLayout_46.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_46.setSpacing(0) + self.verticalLayout_46.setObjectName("verticalLayout_46") + self.label_101 = QtWidgets.QLabel(self.frame_28) + self.label_101.setStyleSheet("background-color: rgb(255, 194, 158);") + self.label_101.setObjectName("label_101") + self.verticalLayout_46.addWidget(self.label_101) + self.Tags = QtWidgets.QGroupBox(self.frame_28) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.Tags.sizePolicy().hasHeightForWidth()) self.Tags.setSizePolicy(sizePolicy) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 194, 158)) + brush = QtGui.QBrush(QtGui.QColor(255, 221, 201)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.Tags.setPalette(palette) - self.Tags.setAutoFillBackground(True) - self.Tags.setStyleSheet("") + self.Tags.setAutoFillBackground(False) + self.Tags.setStyleSheet("background-color: rgb(255, 221, 201);") + self.Tags.setTitle("") self.Tags.setObjectName("Tags") self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.Tags) self.verticalLayout_9.setObjectName("verticalLayout_9") self.groupBox_2 = QtWidgets.QGroupBox(self.Tags) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_2.setPalette(palette) - self.groupBox_2.setAutoFillBackground(True) - self.groupBox_2.setStyleSheet("") + self.groupBox_2.setAutoFillBackground(False) + self.groupBox_2.setStyleSheet("background-color: rgb(255, 209, 182);") self.groupBox_2.setObjectName("groupBox_2") self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.groupBox_2) self.verticalLayout_8.setObjectName("verticalLayout_8") @@ -1833,7 +1103,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_11.setObjectName("label_11") self._8.addWidget(self.label_11) self.cwp_work_tag_multi = QtWidgets.QLineEdit(self.groupBox_2) - self.cwp_work_tag_multi.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_work_tag_multi.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_work_tag_multi.setObjectName("cwp_work_tag_multi") self._8.addWidget(self.cwp_work_tag_multi) self.cwp_multi_work_sep = QtWidgets.QComboBox(self.groupBox_2) @@ -1864,7 +1134,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_15.setObjectName("label_15") self._12.addWidget(self.label_15) self.cwp_work_tag_single = QtWidgets.QLineEdit(self.groupBox_2) - self.cwp_work_tag_single.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_work_tag_single.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_work_tag_single.setObjectName("cwp_work_tag_single") self._12.addWidget(self.cwp_work_tag_single) self.cwp_single_work_sep = QtWidgets.QComboBox(self.groupBox_2) @@ -1895,7 +1165,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_12.setObjectName("label_12") self._9.addWidget(self.label_12) self.cwp_top_tag = QtWidgets.QLineEdit(self.groupBox_2) - self.cwp_top_tag.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_top_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_top_tag.setObjectName("cwp_top_tag") self._9.addWidget(self.cwp_top_tag) self.label_10 = QtWidgets.QLabel(self.groupBox_2) @@ -1905,27 +1175,36 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.verticalLayout_9.addWidget(self.groupBox_2) self.groupBox = QtWidgets.QGroupBox(self.Tags) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 162, 116)) + brush = QtGui.QBrush(QtGui.QColor(255, 209, 182)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox.setPalette(palette) - self.groupBox.setAutoFillBackground(True) - self.groupBox.setStyleSheet("") + self.groupBox.setAutoFillBackground(False) + self.groupBox.setStyleSheet("background-color: rgb(255, 209, 182);") self.groupBox.setObjectName("groupBox") self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox) self.verticalLayout_7.setObjectName("verticalLayout_7") @@ -1942,7 +1221,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_13.setObjectName("label_13") self._10.addWidget(self.label_13) self.cwp_movt_no_tag = QtWidgets.QLineEdit(self.groupBox) - self.cwp_movt_no_tag.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_movt_no_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_movt_no_tag.setObjectName("cwp_movt_no_tag") self._10.addWidget(self.cwp_movt_no_tag) self.cwp_movt_no_sep = QtWidgets.QComboBox(self.groupBox) @@ -1957,22 +1236,22 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cwp_movt_no_sep.addItem("") self._10.addWidget(self.cwp_movt_no_sep) self.verticalLayout_7.addLayout(self._10) - self.frame1 = QtWidgets.QFrame(self.groupBox) - self.frame1.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame1.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame1.setObjectName("frame1") - self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.frame1) + self.frame = QtWidgets.QFrame(self.groupBox) + self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame.setObjectName("frame") + self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.frame) self.horizontalLayout_5.setObjectName("horizontalLayout_5") - self.label_49 = QtWidgets.QLabel(self.frame1) + self.label_49 = QtWidgets.QLabel(self.frame) self.label_49.setObjectName("label_49") self.horizontalLayout_5.addWidget(self.label_49) - self.label_47 = QtWidgets.QLabel(self.frame1) + self.label_47 = QtWidgets.QLabel(self.frame) self.label_47.setObjectName("label_47") self.horizontalLayout_5.addWidget(self.label_47) - self.label_48 = QtWidgets.QLabel(self.frame1) + self.label_48 = QtWidgets.QLabel(self.frame) self.label_48.setObjectName("label_48") self.horizontalLayout_5.addWidget(self.label_48) - self.verticalLayout_7.addWidget(self.frame1) + self.verticalLayout_7.addWidget(self.frame) self._11 = QtWidgets.QHBoxLayout() self._11.setContentsMargins(0, 0, 0, 0) self._11.setSpacing(6) @@ -1986,11 +1265,11 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_14.setObjectName("label_14") self._11.addWidget(self.label_14) self.cwp_movt_tag_exc = QtWidgets.QLineEdit(self.groupBox) - self.cwp_movt_tag_exc.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_movt_tag_exc.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_movt_tag_exc.setObjectName("cwp_movt_tag_exc") self._11.addWidget(self.cwp_movt_tag_exc) self.cwp_movt_tag_exc1 = QtWidgets.QLineEdit(self.groupBox) - self.cwp_movt_tag_exc1.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_movt_tag_exc1.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_movt_tag_exc1.setObjectName("cwp_movt_tag_exc1") self._11.addWidget(self.cwp_movt_tag_exc1) self.label_39 = QtWidgets.QLabel(self.groupBox) @@ -2010,11 +1289,11 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_9.setObjectName("label_9") self._6.addWidget(self.label_9) self.cwp_movt_tag_inc = QtWidgets.QLineEdit(self.groupBox) - self.cwp_movt_tag_inc.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_movt_tag_inc.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_movt_tag_inc.setObjectName("cwp_movt_tag_inc") self._6.addWidget(self.cwp_movt_tag_inc) self.cwp_movt_tag_inc1 = QtWidgets.QLineEdit(self.groupBox) - self.cwp_movt_tag_inc1.setStyleSheet("background-color: rgb(255, 221, 116);") + self.cwp_movt_tag_inc1.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_movt_tag_inc1.setObjectName("cwp_movt_tag_inc1") self._6.addWidget(self.cwp_movt_tag_inc1) self.label_37 = QtWidgets.QLabel(self.groupBox) @@ -2022,30 +1301,53 @@ def setupUi(self, ClassicalExtrasOptionsPage): self._6.addWidget(self.label_37) self.verticalLayout_7.addLayout(self._6) self.verticalLayout_9.addWidget(self.groupBox) - self.verticalLayout_25.addWidget(self.Tags) - self.groupBox_12 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_3) + self.verticalLayout_46.addWidget(self.Tags) + self.verticalLayout_25.addWidget(self.frame_28) + self.frame_29 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) + self.frame_29.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_29.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_29.setObjectName("frame_29") + self.verticalLayout_47 = QtWidgets.QVBoxLayout(self.frame_29) + self.verticalLayout_47.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_47.setSpacing(0) + self.verticalLayout_47.setObjectName("verticalLayout_47") + self.label_102 = QtWidgets.QLabel(self.frame_29) + self.label_102.setStyleSheet("background-color: rgb(195, 168, 179);") + self.label_102.setObjectName("label_102") + self.verticalLayout_47.addWidget(self.label_102) + self.groupBox_12 = QtWidgets.QGroupBox(self.frame_29) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(221, 209, 221)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_12.setPalette(palette) - self.groupBox_12.setAutoFillBackground(True) - self.groupBox_12.setStyleSheet("") + self.groupBox_12.setAutoFillBackground(False) + self.groupBox_12.setStyleSheet("background-color: rgb(221, 209, 221);") + self.groupBox_12.setTitle("") self.groupBox_12.setObjectName("groupBox_12") self.verticalLayout_19 = QtWidgets.QVBoxLayout(self.groupBox_12) self.verticalLayout_19.setObjectName("verticalLayout_19") @@ -2063,306 +1365,221 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.horizontalLayout_13.addWidget(self.cwp_partial) self.cwp_partial_text = QtWidgets.QLineEdit(self.groupBox_13) self.cwp_partial_text.setEnabled(False) - self.cwp_partial_text.setStyleSheet("background-color: rgb(225, 225, 169);") + self.cwp_partial_text.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_partial_text.setObjectName("cwp_partial_text") - self.horizontalLayout_13.addWidget(self.cwp_partial_text) - self.verticalLayout_19.addWidget(self.groupBox_13) - self.groupBox_14 = QtWidgets.QGroupBox(self.groupBox_12) - self.groupBox_14.setObjectName("groupBox_14") - self.horizontalLayout_15 = QtWidgets.QHBoxLayout(self.groupBox_14) - self.horizontalLayout_15.setObjectName("horizontalLayout_15") - self.cwp_arrangements = QtWidgets.QCheckBox(self.groupBox_14) - self.cwp_arrangements.setObjectName("cwp_arrangements") - self.horizontalLayout_15.addWidget(self.cwp_arrangements) - self.cwp_arrangements_text = QtWidgets.QLineEdit(self.groupBox_14) - self.cwp_arrangements_text.setEnabled(False) - self.cwp_arrangements_text.setStyleSheet("background-color: rgb(225, 225, 169);") - self.cwp_arrangements_text.setObjectName("cwp_arrangements_text") - self.horizontalLayout_15.addWidget(self.cwp_arrangements_text) - self.verticalLayout_19.addWidget(self.groupBox_14) - self.groupBox_15 = QtWidgets.QGroupBox(self.groupBox_12) - self.groupBox_15.setObjectName("groupBox_15") - self.horizontalLayout_12 = QtWidgets.QHBoxLayout(self.groupBox_15) - self.horizontalLayout_12.setObjectName("horizontalLayout_12") - self.cwp_medley = QtWidgets.QCheckBox(self.groupBox_15) - self.cwp_medley.setObjectName("cwp_medley") - self.horizontalLayout_12.addWidget(self.cwp_medley) - self.cwp_medley_text = QtWidgets.QLineEdit(self.groupBox_15) - self.cwp_medley_text.setEnabled(False) - self.cwp_medley_text.setStyleSheet("background-color: rgb(225, 225, 169);") - self.cwp_medley_text.setObjectName("cwp_medley_text") - self.horizontalLayout_12.addWidget(self.cwp_medley_text) - self.verticalLayout_19.addWidget(self.groupBox_15) - self.verticalLayout_25.addWidget(self.groupBox_12) - self.groupBox_17 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_3) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_17.setPalette(palette) - self.groupBox_17.setAutoFillBackground(True) - self.groupBox_17.setStyleSheet("") - self.groupBox_17.setObjectName("groupBox_17") - self.horizontalLayout_14 = QtWidgets.QHBoxLayout(self.groupBox_17) - self.horizontalLayout_14.setObjectName("horizontalLayout_14") - self.cwp_use_sk = QtWidgets.QCheckBox(self.groupBox_17) - self.cwp_use_sk.setObjectName("cwp_use_sk") - self.horizontalLayout_14.addWidget(self.cwp_use_sk) - self.cwp_write_sk = QtWidgets.QCheckBox(self.groupBox_17) - self.cwp_write_sk.setObjectName("cwp_write_sk") - self.horizontalLayout_14.addWidget(self.cwp_write_sk) - self.verticalLayout_25.addWidget(self.groupBox_17) - self.label_82 = QtWidgets.QLabel(self.scrollAreaWidgetContents_3) - self.label_82.setObjectName("label_82") - self.verticalLayout_25.addWidget(self.label_82) - self.scrollArea_3.setWidget(self.scrollAreaWidgetContents_3) - self.verticalLayout_4.addWidget(self.scrollArea_3) - self.tabWidget.addTab(self.Works, "") - self.Genres = QtWidgets.QWidget() - self.Genres.setObjectName("Genres") - self.gridLayout_2 = QtWidgets.QGridLayout(self.Genres) - self.gridLayout_2.setObjectName("gridLayout_2") - self.scrollArea_6 = QtWidgets.QScrollArea(self.Genres) - self.scrollArea_6.setWidgetResizable(True) - self.scrollArea_6.setObjectName("scrollArea_6") - self.scrollAreaWidgetContents_5 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_5.setGeometry(QtCore.QRect(0, 0, 1087, 1248)) - self.scrollAreaWidgetContents_5.setObjectName("scrollAreaWidgetContents_5") - self.gridLayout_13 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_5) - self.gridLayout_13.setObjectName("gridLayout_13") - self.groupBox_41 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_5) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(185, 135, 188)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_41.setPalette(palette) - self.groupBox_41.setAutoFillBackground(True) - self.groupBox_41.setStyleSheet("") - self.groupBox_41.setObjectName("groupBox_41") - self.verticalLayout_32 = QtWidgets.QVBoxLayout(self.groupBox_41) - self.verticalLayout_32.setObjectName("verticalLayout_32") - self.groupBox_48 = QtWidgets.QGroupBox(self.groupBox_41) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_48.setPalette(palette) - self.groupBox_48.setAutoFillBackground(True) - self.groupBox_48.setStyleSheet("") - self.groupBox_48.setObjectName("groupBox_48") - self.gridLayout_10 = QtWidgets.QGridLayout(self.groupBox_48) - self.gridLayout_10.setObjectName("gridLayout_10") - self.groupBox_42 = QtWidgets.QGroupBox(self.groupBox_48) - self.groupBox_42.setObjectName("groupBox_42") - self.gridLayout_7 = QtWidgets.QGridLayout(self.groupBox_42) - self.gridLayout_7.setObjectName("gridLayout_7") - self.cwp_workdate_source_composed = QtWidgets.QCheckBox(self.groupBox_42) - self.cwp_workdate_source_composed.setObjectName("cwp_workdate_source_composed") - self.gridLayout_7.addWidget(self.cwp_workdate_source_composed, 0, 0, 1, 1) - self.cwp_workdate_source_published = QtWidgets.QCheckBox(self.groupBox_42) - self.cwp_workdate_source_published.setObjectName("cwp_workdate_source_published") - self.gridLayout_7.addWidget(self.cwp_workdate_source_published, 0, 1, 1, 1) - self.cwp_workdate_source_premiered = QtWidgets.QCheckBox(self.groupBox_42) - self.cwp_workdate_source_premiered.setObjectName("cwp_workdate_source_premiered") - self.gridLayout_7.addWidget(self.cwp_workdate_source_premiered, 0, 2, 1, 1) - self.line_9 = QtWidgets.QFrame(self.groupBox_42) - self.line_9.setFrameShape(QtWidgets.QFrame.HLine) - self.line_9.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_9.setObjectName("line_9") - self.gridLayout_7.addWidget(self.line_9, 1, 0, 1, 3) - self.cwp_workdate_use_first = QtWidgets.QRadioButton(self.groupBox_42) - self.cwp_workdate_use_first.setObjectName("cwp_workdate_use_first") - self.gridLayout_7.addWidget(self.cwp_workdate_use_first, 2, 0, 1, 1) - self.cwp_workdate_use_all = QtWidgets.QRadioButton(self.groupBox_42) - self.cwp_workdate_use_all.setObjectName("cwp_workdate_use_all") - self.gridLayout_7.addWidget(self.cwp_workdate_use_all, 2, 1, 1, 1) - self.cwp_workdate_annotate = QtWidgets.QCheckBox(self.groupBox_42) - self.cwp_workdate_annotate.setObjectName("cwp_workdate_annotate") - self.gridLayout_7.addWidget(self.cwp_workdate_annotate, 2, 2, 1, 1) - self.gridLayout_10.addWidget(self.groupBox_42, 1, 0, 1, 3) - self.label_68 = QtWidgets.QLabel(self.groupBox_48) - self.label_68.setObjectName("label_68") - self.gridLayout_10.addWidget(self.label_68, 0, 0, 1, 1) - self.cwp_workdate_include = QtWidgets.QCheckBox(self.groupBox_48) - self.cwp_workdate_include.setObjectName("cwp_workdate_include") - self.gridLayout_10.addWidget(self.cwp_workdate_include, 2, 0, 1, 2) - self.cwp_workdate_tag = QtWidgets.QLineEdit(self.groupBox_48) - self.cwp_workdate_tag.setStyleSheet("background-color: rgb(183, 174, 225);") - self.cwp_workdate_tag.setObjectName("cwp_workdate_tag") - self.gridLayout_10.addWidget(self.cwp_workdate_tag, 0, 1, 1, 2) - self.verticalLayout_32.addWidget(self.groupBox_48) - self.line_5 = QtWidgets.QFrame(self.groupBox_41) - self.line_5.setMidLineWidth(0) - self.line_5.setFrameShape(QtWidgets.QFrame.HLine) - self.line_5.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_5.setObjectName("line_5") - self.verticalLayout_32.addWidget(self.line_5) - self.groupBox_49 = QtWidgets.QGroupBox(self.groupBox_41) + self.horizontalLayout_13.addWidget(self.cwp_partial_text) + self.verticalLayout_19.addWidget(self.groupBox_13) + self.groupBox_14 = QtWidgets.QGroupBox(self.groupBox_12) + self.groupBox_14.setObjectName("groupBox_14") + self.horizontalLayout_15 = QtWidgets.QHBoxLayout(self.groupBox_14) + self.horizontalLayout_15.setObjectName("horizontalLayout_15") + self.cwp_arrangements = QtWidgets.QCheckBox(self.groupBox_14) + self.cwp_arrangements.setObjectName("cwp_arrangements") + self.horizontalLayout_15.addWidget(self.cwp_arrangements) + self.cwp_arrangements_text = QtWidgets.QLineEdit(self.groupBox_14) + self.cwp_arrangements_text.setEnabled(False) + self.cwp_arrangements_text.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_arrangements_text.setObjectName("cwp_arrangements_text") + self.horizontalLayout_15.addWidget(self.cwp_arrangements_text) + self.verticalLayout_19.addWidget(self.groupBox_14) + self.groupBox_15 = QtWidgets.QGroupBox(self.groupBox_12) + self.groupBox_15.setObjectName("groupBox_15") + self.horizontalLayout_12 = QtWidgets.QHBoxLayout(self.groupBox_15) + self.horizontalLayout_12.setObjectName("horizontalLayout_12") + self.cwp_medley = QtWidgets.QCheckBox(self.groupBox_15) + self.cwp_medley.setObjectName("cwp_medley") + self.horizontalLayout_12.addWidget(self.cwp_medley) + self.cwp_medley_text = QtWidgets.QLineEdit(self.groupBox_15) + self.cwp_medley_text.setEnabled(False) + self.cwp_medley_text.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_medley_text.setObjectName("cwp_medley_text") + self.horizontalLayout_12.addWidget(self.cwp_medley_text) + self.verticalLayout_19.addWidget(self.groupBox_15) + self.verticalLayout_47.addWidget(self.groupBox_12) + self.verticalLayout_25.addWidget(self.frame_29) + self.frame_30 = QtWidgets.QFrame(self.scrollAreaWidgetContents_3) + self.frame_30.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_30.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_30.setObjectName("frame_30") + self.verticalLayout_48 = QtWidgets.QVBoxLayout(self.frame_30) + self.verticalLayout_48.setContentsMargins(0, 0, -1, 0) + self.verticalLayout_48.setSpacing(0) + self.verticalLayout_48.setObjectName("verticalLayout_48") + self.label_103 = QtWidgets.QLabel(self.frame_30) + self.label_103.setStyleSheet("background-color: rgb(182, 182, 62);") + self.label_103.setObjectName("label_103") + self.verticalLayout_48.addWidget(self.label_103) + self.groupBox_17 = QtWidgets.QGroupBox(self.frame_30) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(195, 168, 179)) + brush = QtGui.QBrush(QtGui.QColor(227, 227, 143)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_49.setPalette(palette) - self.groupBox_49.setAutoFillBackground(True) - self.groupBox_49.setStyleSheet("") - self.groupBox_49.setObjectName("groupBox_49") - self.gridLayout_12 = QtWidgets.QGridLayout(self.groupBox_49) - self.gridLayout_12.setObjectName("gridLayout_12") - self.label_69 = QtWidgets.QLabel(self.groupBox_49) - self.label_69.setObjectName("label_69") - self.gridLayout_12.addWidget(self.label_69, 0, 0, 1, 1) - self.cwp_muso_periods = QtWidgets.QCheckBox(self.groupBox_49) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.cwp_muso_periods.setFont(font) - self.cwp_muso_periods.setObjectName("cwp_muso_periods") - self.gridLayout_12.addWidget(self.cwp_muso_periods, 4, 0, 1, 2) - self.cwp_muso_dates = QtWidgets.QCheckBox(self.groupBox_49) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.cwp_muso_dates.setFont(font) - self.cwp_muso_dates.setObjectName("cwp_muso_dates") - self.gridLayout_12.addWidget(self.cwp_muso_dates, 1, 0, 1, 3) - self.cwp_period_tag = QtWidgets.QLineEdit(self.groupBox_49) - self.cwp_period_tag.setStyleSheet("background-color: rgb(183, 174, 225);") - self.cwp_period_tag.setObjectName("cwp_period_tag") - self.gridLayout_12.addWidget(self.cwp_period_tag, 0, 1, 1, 2) - self.label_70 = QtWidgets.QLabel(self.groupBox_49) - self.label_70.setObjectName("label_70") - self.gridLayout_12.addWidget(self.label_70, 3, 0, 1, 1) - self.cwp_period_map = QtWidgets.QPlainTextEdit(self.groupBox_49) - self.cwp_period_map.setStyleSheet("background-color: rgb(183, 174, 225);") - self.cwp_period_map.setObjectName("cwp_period_map") - self.gridLayout_12.addWidget(self.cwp_period_map, 3, 2, 2, 1) - self.label_87 = QtWidgets.QLabel(self.groupBox_49) - self.label_87.setObjectName("label_87") - self.gridLayout_12.addWidget(self.label_87, 5, 2, 1, 1) - self.cwp_periods_arranger_as_composer = QtWidgets.QCheckBox(self.groupBox_49) - self.cwp_periods_arranger_as_composer.setObjectName("cwp_periods_arranger_as_composer") - self.gridLayout_12.addWidget(self.cwp_periods_arranger_as_composer, 2, 0, 1, 1) - self.verticalLayout_32.addWidget(self.groupBox_49) - self.gridLayout_13.addWidget(self.groupBox_41, 3, 0, 1, 2) - self.cwp_use_muso_refdb = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_5) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.cwp_use_muso_refdb.setFont(font) - self.cwp_use_muso_refdb.setObjectName("cwp_use_muso_refdb") - self.gridLayout_13.addWidget(self.cwp_use_muso_refdb, 0, 0, 1, 1) - self.label_81 = QtWidgets.QLabel(self.scrollAreaWidgetContents_5) - self.label_81.setObjectName("label_81") - self.gridLayout_13.addWidget(self.label_81, 4, 0, 1, 1) - self.groupBox_35 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_5) + self.groupBox_17.setPalette(palette) + self.groupBox_17.setAutoFillBackground(False) + self.groupBox_17.setStyleSheet("background-color: rgb(227, 227, 143);") + self.groupBox_17.setTitle("") + self.groupBox_17.setObjectName("groupBox_17") + self.horizontalLayout_14 = QtWidgets.QHBoxLayout(self.groupBox_17) + self.horizontalLayout_14.setObjectName("horizontalLayout_14") + self.cwp_use_sk = QtWidgets.QCheckBox(self.groupBox_17) + self.cwp_use_sk.setObjectName("cwp_use_sk") + self.horizontalLayout_14.addWidget(self.cwp_use_sk) + self.cwp_write_sk = QtWidgets.QCheckBox(self.groupBox_17) + self.cwp_write_sk.setObjectName("cwp_write_sk") + self.horizontalLayout_14.addWidget(self.cwp_write_sk) + self.verticalLayout_48.addWidget(self.groupBox_17) + self.verticalLayout_25.addWidget(self.frame_30) + self.label_82 = QtWidgets.QLabel(self.scrollAreaWidgetContents_3) + self.label_82.setObjectName("label_82") + self.verticalLayout_25.addWidget(self.label_82) + self.scrollArea_3.setWidget(self.scrollAreaWidgetContents_3) + self.verticalLayout_4.addWidget(self.scrollArea_3) + self.tabWidget.addTab(self.Works, "") + self.Genres = QtWidgets.QWidget() + self.Genres.setObjectName("Genres") + self.gridLayout_2 = QtWidgets.QGridLayout(self.Genres) + self.gridLayout_2.setObjectName("gridLayout_2") + self.scrollArea_6 = QtWidgets.QScrollArea(self.Genres) + self.scrollArea_6.setWidgetResizable(True) + self.scrollArea_6.setObjectName("scrollArea_6") + self.scrollAreaWidgetContents_5 = QtWidgets.QWidget() + self.scrollAreaWidgetContents_5.setGeometry(QtCore.QRect(0, 0, 1084, 1310)) + self.scrollAreaWidgetContents_5.setObjectName("scrollAreaWidgetContents_5") + self.gridLayout_13 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_5) + self.gridLayout_13.setObjectName("gridLayout_13") + self.frame_34 = QtWidgets.QFrame(self.scrollAreaWidgetContents_5) + self.frame_34.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_34.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_34.setObjectName("frame_34") + self.verticalLayout_52 = QtWidgets.QVBoxLayout(self.frame_34) + self.verticalLayout_52.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_52.setSpacing(0) + self.verticalLayout_52.setObjectName("verticalLayout_52") + self.label_104 = QtWidgets.QLabel(self.frame_34) + self.label_104.setStyleSheet("background-color: rgb(138, 222, 187);") + self.label_104.setObjectName("label_104") + self.verticalLayout_52.addWidget(self.label_104) + self.groupBox_35 = QtWidgets.QGroupBox(self.frame_34) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(19, 186, 161)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_35.setPalette(palette) - self.groupBox_35.setAutoFillBackground(True) - self.groupBox_35.setStyleSheet("") + self.groupBox_35.setAutoFillBackground(False) + self.groupBox_35.setStyleSheet("background-color: rgb(207, 236, 225);") + self.groupBox_35.setTitle("") self.groupBox_35.setObjectName("groupBox_35") self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_35) self.gridLayout_5.setObjectName("gridLayout_5") - self.groupBox_38 = QtWidgets.QGroupBox(self.groupBox_35) + self.label_73 = QtWidgets.QLabel(self.groupBox_35) + self.label_73.setObjectName("label_73") + self.gridLayout_5.addWidget(self.label_73, 0, 0, 1, 1) + self.label_74 = QtWidgets.QLabel(self.groupBox_35) + self.label_74.setObjectName("label_74") + self.gridLayout_5.addWidget(self.label_74, 0, 2, 1, 1) + self.cwp_subgenre_tag = QtWidgets.QLineEdit(self.groupBox_35) + self.cwp_subgenre_tag.setStyleSheet("background-color: rgb(0, 236, 173);") + self.cwp_subgenre_tag.setObjectName("cwp_subgenre_tag") + self.gridLayout_5.addWidget(self.cwp_subgenre_tag, 0, 3, 1, 1) + self.cwp_genre_tag = QtWidgets.QLineEdit(self.groupBox_35) + self.cwp_genre_tag.setStyleSheet("background-color: rgb(0, 236, 173);") + self.cwp_genre_tag.setObjectName("cwp_genre_tag") + self.gridLayout_5.addWidget(self.cwp_genre_tag, 0, 1, 1, 1) + self.verticalLayout_52.addWidget(self.groupBox_35) + self.gridLayout_13.addWidget(self.frame_34, 2, 0, 1, 1) + self.frame_33 = QtWidgets.QFrame(self.scrollAreaWidgetContents_5) + self.frame_33.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_33.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_33.setObjectName("frame_33") + self.verticalLayout_51 = QtWidgets.QVBoxLayout(self.frame_33) + self.verticalLayout_51.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_51.setSpacing(0) + self.verticalLayout_51.setObjectName("verticalLayout_51") + self.label_107 = QtWidgets.QLabel(self.frame_33) + self.label_107.setStyleSheet("background-color: rgb(138, 222, 187);") + self.label_107.setObjectName("label_107") + self.verticalLayout_51.addWidget(self.label_107) + self.groupBox_38 = QtWidgets.QGroupBox(self.frame_33) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_38.setPalette(palette) - self.groupBox_38.setAutoFillBackground(True) - self.groupBox_38.setStyleSheet("") + self.groupBox_38.setAutoFillBackground(False) + self.groupBox_38.setStyleSheet("background-color: rgb(207, 236, 225);") + self.groupBox_38.setTitle("") self.groupBox_38.setObjectName("groupBox_38") self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_38) self.gridLayout_3.setObjectName("gridLayout_3") @@ -2386,61 +1603,247 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_64.setObjectName("label_64") self.gridLayout_3.addWidget(self.label_64, 3, 0, 1, 1) self.cwp_genres_flag_text = QtWidgets.QLineEdit(self.groupBox_38) - self.cwp_genres_flag_text.setStyleSheet("background-color: rgb(156, 250, 211);") + self.cwp_genres_flag_text.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_genres_flag_text.setObjectName("cwp_genres_flag_text") self.gridLayout_3.addWidget(self.cwp_genres_flag_text, 3, 1, 1, 3) self.label_71 = QtWidgets.QLabel(self.groupBox_38) self.label_71.setObjectName("label_71") self.gridLayout_3.addWidget(self.label_71, 3, 4, 1, 1) self.cwp_genres_flag_tag = QtWidgets.QLineEdit(self.groupBox_38) - self.cwp_genres_flag_tag.setStyleSheet("background-color: rgb(156, 250, 211);") + self.cwp_genres_flag_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_genres_flag_tag.setObjectName("cwp_genres_flag_tag") self.gridLayout_3.addWidget(self.cwp_genres_flag_tag, 3, 5, 1, 1) self.cwp_genres_arranger_as_composer = QtWidgets.QCheckBox(self.groupBox_38) self.cwp_genres_arranger_as_composer.setObjectName("cwp_genres_arranger_as_composer") self.gridLayout_3.addWidget(self.cwp_genres_arranger_as_composer, 2, 0, 1, 1) - self.gridLayout_5.addWidget(self.groupBox_38, 3, 0, 1, 4) - self.label_73 = QtWidgets.QLabel(self.groupBox_35) - self.label_73.setObjectName("label_73") - self.gridLayout_5.addWidget(self.label_73, 0, 0, 1, 1) - self.cwp_genre_tag = QtWidgets.QLineEdit(self.groupBox_35) - self.cwp_genre_tag.setStyleSheet("background-color: rgb(0, 236, 173);") - self.cwp_genre_tag.setObjectName("cwp_genre_tag") - self.gridLayout_5.addWidget(self.cwp_genre_tag, 0, 1, 1, 1) - self.cwp_subgenre_tag = QtWidgets.QLineEdit(self.groupBox_35) - self.cwp_subgenre_tag.setStyleSheet("background-color: rgb(0, 236, 173);") - self.cwp_subgenre_tag.setObjectName("cwp_subgenre_tag") - self.gridLayout_5.addWidget(self.cwp_subgenre_tag, 0, 3, 1, 1) - self.label_74 = QtWidgets.QLabel(self.groupBox_35) - self.label_74.setObjectName("label_74") - self.gridLayout_5.addWidget(self.label_74, 0, 2, 1, 1) - self.groupBox_36 = QtWidgets.QGroupBox(self.groupBox_35) + self.verticalLayout_51.addWidget(self.groupBox_38) + self.gridLayout_13.addWidget(self.frame_33, 5, 0, 1, 1) + self.label_81 = QtWidgets.QLabel(self.scrollAreaWidgetContents_5) + self.label_81.setObjectName("label_81") + self.gridLayout_13.addWidget(self.label_81, 8, 0, 1, 1) + self.cwp_use_muso_refdb = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_5) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.cwp_use_muso_refdb.setFont(font) + self.cwp_use_muso_refdb.setObjectName("cwp_use_muso_refdb") + self.gridLayout_13.addWidget(self.cwp_use_muso_refdb, 1, 0, 1, 1) + self.frame_35 = QtWidgets.QFrame(self.scrollAreaWidgetContents_5) + self.frame_35.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_35.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_35.setObjectName("frame_35") + self.verticalLayout_53 = QtWidgets.QVBoxLayout(self.frame_35) + self.verticalLayout_53.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_53.setSpacing(0) + self.verticalLayout_53.setObjectName("verticalLayout_53") + self.label_108 = QtWidgets.QLabel(self.frame_35) + self.label_108.setStyleSheet("background-color: rgb(225, 168, 171);") + self.label_108.setObjectName("label_108") + self.verticalLayout_53.addWidget(self.label_108) + self.groupBox_39 = QtWidgets.QGroupBox(self.frame_35) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 224, 226)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + self.groupBox_39.setPalette(palette) + self.groupBox_39.setAutoFillBackground(False) + self.groupBox_39.setStyleSheet("background-color: rgb(245, 224, 226);") + self.groupBox_39.setTitle("") + self.groupBox_39.setObjectName("groupBox_39") + self.verticalLayout_34 = QtWidgets.QVBoxLayout(self.groupBox_39) + self.verticalLayout_34.setObjectName("verticalLayout_34") + self.groupBox_45 = QtWidgets.QGroupBox(self.groupBox_39) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + self.groupBox_45.setPalette(palette) + self.groupBox_45.setAutoFillBackground(False) + self.groupBox_45.setStyleSheet("background-color: rgb(245, 210, 213);") + self.groupBox_45.setObjectName("groupBox_45") + self.gridLayout_11 = QtWidgets.QGridLayout(self.groupBox_45) + self.gridLayout_11.setObjectName("gridLayout_11") + self.label_66 = QtWidgets.QLabel(self.groupBox_45) + self.label_66.setObjectName("label_66") + self.gridLayout_11.addWidget(self.label_66, 0, 0, 1, 1) + self.cwp_instruments_tag = QtWidgets.QLineEdit(self.groupBox_45) + self.cwp_instruments_tag.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_instruments_tag.setObjectName("cwp_instruments_tag") + self.gridLayout_11.addWidget(self.cwp_instruments_tag, 0, 1, 1, 1) + self.groupBox_40 = QtWidgets.QGroupBox(self.groupBox_45) + self.groupBox_40.setObjectName("groupBox_40") + self.horizontalLayout_33 = QtWidgets.QHBoxLayout(self.groupBox_40) + self.horizontalLayout_33.setObjectName("horizontalLayout_33") + self.cwp_instruments_MB_names = QtWidgets.QCheckBox(self.groupBox_40) + self.cwp_instruments_MB_names.setObjectName("cwp_instruments_MB_names") + self.horizontalLayout_33.addWidget(self.cwp_instruments_MB_names) + self.cwp_instruments_credited_names = QtWidgets.QCheckBox(self.groupBox_40) + self.cwp_instruments_credited_names.setObjectName("cwp_instruments_credited_names") + self.horizontalLayout_33.addWidget(self.cwp_instruments_credited_names) + self.gridLayout_11.addWidget(self.groupBox_40, 1, 0, 1, 2) + self.verticalLayout_34.addWidget(self.groupBox_45) + self.groupBox_47 = QtWidgets.QGroupBox(self.groupBox_39) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(245, 210, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + self.groupBox_47.setPalette(palette) + self.groupBox_47.setAutoFillBackground(False) + self.groupBox_47.setStyleSheet("background-color: rgb(245, 210, 213);") + self.groupBox_47.setObjectName("groupBox_47") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_47) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label_72 = QtWidgets.QLabel(self.groupBox_47) + self.label_72.setObjectName("label_72") + self.gridLayout_4.addWidget(self.label_72, 0, 0, 1, 1) + self.cwp_key_tag = QtWidgets.QLineEdit(self.groupBox_47) + self.cwp_key_tag.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_key_tag.setObjectName("cwp_key_tag") + self.gridLayout_4.addWidget(self.cwp_key_tag, 0, 2, 1, 1) + self.groupBox_52 = QtWidgets.QGroupBox(self.groupBox_47) + self.groupBox_52.setObjectName("groupBox_52") + self.horizontalLayout_35 = QtWidgets.QHBoxLayout(self.groupBox_52) + self.horizontalLayout_35.setObjectName("horizontalLayout_35") + self.cwp_key_never_include = QtWidgets.QRadioButton(self.groupBox_52) + self.cwp_key_never_include.setObjectName("cwp_key_never_include") + self.horizontalLayout_35.addWidget(self.cwp_key_never_include) + self.cwp_key_contingent_include = QtWidgets.QRadioButton(self.groupBox_52) + self.cwp_key_contingent_include.setObjectName("cwp_key_contingent_include") + self.horizontalLayout_35.addWidget(self.cwp_key_contingent_include) + self.cwp_key_include = QtWidgets.QRadioButton(self.groupBox_52) + self.cwp_key_include.setObjectName("cwp_key_include") + self.horizontalLayout_35.addWidget(self.cwp_key_include) + self.gridLayout_4.addWidget(self.groupBox_52, 1, 0, 1, 2) + self.verticalLayout_34.addWidget(self.groupBox_47) + self.verticalLayout_53.addWidget(self.groupBox_39) + self.gridLayout_13.addWidget(self.frame_35, 6, 0, 1, 1) + self.frame_32 = QtWidgets.QFrame(self.scrollAreaWidgetContents_5) + self.frame_32.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_32.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_32.setObjectName("frame_32") + self.verticalLayout_50 = QtWidgets.QVBoxLayout(self.frame_32) + self.verticalLayout_50.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_50.setSpacing(0) + self.verticalLayout_50.setObjectName("verticalLayout_50") + self.label_106 = QtWidgets.QLabel(self.frame_32) + self.label_106.setStyleSheet("background-color: rgb(138, 222, 187);") + self.label_106.setObjectName("label_106") + self.verticalLayout_50.addWidget(self.label_106) + self.cwp_genres_filter = QtWidgets.QCheckBox(self.frame_32) + self.cwp_genres_filter.setLayoutDirection(QtCore.Qt.LeftToRight) + self.cwp_genres_filter.setStyleSheet("background-color: rgb(138, 222, 187);") + self.cwp_genres_filter.setObjectName("cwp_genres_filter") + self.verticalLayout_50.addWidget(self.cwp_genres_filter) + self.groupBox_36 = QtWidgets.QGroupBox(self.frame_32) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_36.setPalette(palette) - self.groupBox_36.setAutoFillBackground(True) - self.groupBox_36.setStyleSheet("") + self.groupBox_36.setAutoFillBackground(False) + self.groupBox_36.setStyleSheet("background-color: rgb(207, 236, 225);") + self.groupBox_36.setTitle("") self.groupBox_36.setObjectName("groupBox_36") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_36) self.gridLayout_6.setObjectName("gridLayout_6") self.groupBox_43 = QtWidgets.QGroupBox(self.groupBox_36) + self.groupBox_43.setStyleSheet("background-color: rgb(190, 236, 219);") self.groupBox_43.setObjectName("groupBox_43") self.gridLayout_9 = QtWidgets.QGridLayout(self.groupBox_43) self.gridLayout_9.setObjectName("gridLayout_9") @@ -2474,6 +1877,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.gridLayout_9.addWidget(self.cwp_muso_genres, 1, 0, 1, 1) self.gridLayout_6.addWidget(self.groupBox_43, 2, 0, 1, 2) self.groupBox_44 = QtWidgets.QGroupBox(self.groupBox_36) + self.groupBox_44.setStyleSheet("background-color: rgb(190, 236, 219);") self.groupBox_44.setObjectName("groupBox_44") self.horizontalLayout_36 = QtWidgets.QHBoxLayout(self.groupBox_44) self.horizontalLayout_36.setObjectName("horizontalLayout_36") @@ -2511,30 +1915,53 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_78 = QtWidgets.QLabel(self.groupBox_36) self.label_78.setObjectName("label_78") self.gridLayout_6.addWidget(self.label_78, 1, 0, 1, 1) - self.gridLayout_5.addWidget(self.groupBox_36, 2, 0, 1, 4) - self.groupBox_37 = QtWidgets.QGroupBox(self.groupBox_35) + self.verticalLayout_50.addWidget(self.groupBox_36) + self.gridLayout_13.addWidget(self.frame_32, 4, 0, 1, 1) + self.frame_31 = QtWidgets.QFrame(self.scrollAreaWidgetContents_5) + self.frame_31.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_31.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_31.setObjectName("frame_31") + self.verticalLayout_49 = QtWidgets.QVBoxLayout(self.frame_31) + self.verticalLayout_49.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_49.setSpacing(0) + self.verticalLayout_49.setObjectName("verticalLayout_49") + self.label_105 = QtWidgets.QLabel(self.frame_31) + self.label_105.setStyleSheet("background-color: rgb(138, 222, 187);") + self.label_105.setObjectName("label_105") + self.verticalLayout_49.addWidget(self.label_105) + self.groupBox_37 = QtWidgets.QGroupBox(self.frame_31) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(138, 222, 187)) + brush = QtGui.QBrush(QtGui.QColor(207, 236, 225)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_37.setPalette(palette) - self.groupBox_37.setAutoFillBackground(True) - self.groupBox_37.setStyleSheet("") + self.groupBox_37.setAutoFillBackground(False) + self.groupBox_37.setStyleSheet("background-color: rgb(207, 236, 225);") + self.groupBox_37.setTitle("") self.groupBox_37.setObjectName("groupBox_37") self.horizontalLayout_32 = QtWidgets.QHBoxLayout(self.groupBox_37) self.horizontalLayout_32.setObjectName("horizontalLayout_32") @@ -2550,131 +1977,1242 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cwp_genres_infer = QtWidgets.QCheckBox(self.groupBox_37) self.cwp_genres_infer.setObjectName("cwp_genres_infer") self.horizontalLayout_32.addWidget(self.cwp_genres_infer) - self.gridLayout_5.addWidget(self.groupBox_37, 1, 0, 1, 4) - self.gridLayout_13.addWidget(self.groupBox_35, 1, 0, 1, 2) - self.groupBox_39 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_5) + self.verticalLayout_49.addWidget(self.groupBox_37) + self.gridLayout_13.addWidget(self.frame_31, 3, 0, 1, 1) + self.frame_36 = QtWidgets.QFrame(self.scrollAreaWidgetContents_5) + self.frame_36.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_36.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_36.setObjectName("frame_36") + self.verticalLayout_54 = QtWidgets.QVBoxLayout(self.frame_36) + self.verticalLayout_54.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_54.setSpacing(0) + self.verticalLayout_54.setObjectName("verticalLayout_54") + self.label_109 = QtWidgets.QLabel(self.frame_36) + self.label_109.setStyleSheet("background-color: rgb(195, 183, 213);") + self.label_109.setObjectName("label_109") + self.verticalLayout_54.addWidget(self.label_109) + self.groupBox_41 = QtWidgets.QGroupBox(self.frame_36) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(199, 93, 95)) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(199, 93, 95)) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(199, 93, 95)) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(199, 93, 95)) + brush = QtGui.QBrush(QtGui.QColor(229, 224, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_39.setPalette(palette) - self.groupBox_39.setAutoFillBackground(True) - self.groupBox_39.setStyleSheet("") - self.groupBox_39.setObjectName("groupBox_39") - self.verticalLayout_34 = QtWidgets.QVBoxLayout(self.groupBox_39) - self.verticalLayout_34.setObjectName("verticalLayout_34") - self.groupBox_45 = QtWidgets.QGroupBox(self.groupBox_39) + self.groupBox_41.setPalette(palette) + self.groupBox_41.setAutoFillBackground(False) + self.groupBox_41.setStyleSheet("background-color: rgb(229, 224, 236);") + self.groupBox_41.setTitle("") + self.groupBox_41.setObjectName("groupBox_41") + self.verticalLayout_32 = QtWidgets.QVBoxLayout(self.groupBox_41) + self.verticalLayout_32.setObjectName("verticalLayout_32") + self.groupBox_48 = QtWidgets.QGroupBox(self.groupBox_41) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_45.setPalette(palette) - self.groupBox_45.setAutoFillBackground(True) - self.groupBox_45.setStyleSheet("") - self.groupBox_45.setObjectName("groupBox_45") - self.gridLayout_11 = QtWidgets.QGridLayout(self.groupBox_45) - self.gridLayout_11.setObjectName("gridLayout_11") - self.label_66 = QtWidgets.QLabel(self.groupBox_45) - self.label_66.setObjectName("label_66") - self.gridLayout_11.addWidget(self.label_66, 0, 0, 1, 1) - self.cwp_instruments_tag = QtWidgets.QLineEdit(self.groupBox_45) - self.cwp_instruments_tag.setStyleSheet("background-color: rgb(248, 116, 120);") - self.cwp_instruments_tag.setObjectName("cwp_instruments_tag") - self.gridLayout_11.addWidget(self.cwp_instruments_tag, 0, 1, 1, 1) - self.groupBox_40 = QtWidgets.QGroupBox(self.groupBox_45) - self.groupBox_40.setObjectName("groupBox_40") - self.horizontalLayout_33 = QtWidgets.QHBoxLayout(self.groupBox_40) - self.horizontalLayout_33.setObjectName("horizontalLayout_33") - self.cwp_instruments_MB_names = QtWidgets.QCheckBox(self.groupBox_40) - self.cwp_instruments_MB_names.setObjectName("cwp_instruments_MB_names") - self.horizontalLayout_33.addWidget(self.cwp_instruments_MB_names) - self.cwp_instruments_credited_names = QtWidgets.QCheckBox(self.groupBox_40) - self.cwp_instruments_credited_names.setObjectName("cwp_instruments_credited_names") - self.horizontalLayout_33.addWidget(self.cwp_instruments_credited_names) - self.gridLayout_11.addWidget(self.groupBox_40, 1, 0, 1, 2) - self.verticalLayout_34.addWidget(self.groupBox_45) - self.groupBox_47 = QtWidgets.QGroupBox(self.groupBox_39) + self.groupBox_48.setPalette(palette) + self.groupBox_48.setAutoFillBackground(False) + self.groupBox_48.setStyleSheet("background-color: rgb(222, 212, 236);") + self.groupBox_48.setObjectName("groupBox_48") + self.gridLayout_10 = QtWidgets.QGridLayout(self.groupBox_48) + self.gridLayout_10.setObjectName("gridLayout_10") + self.groupBox_42 = QtWidgets.QGroupBox(self.groupBox_48) + self.groupBox_42.setObjectName("groupBox_42") + self.gridLayout_7 = QtWidgets.QGridLayout(self.groupBox_42) + self.gridLayout_7.setObjectName("gridLayout_7") + self.cwp_workdate_source_composed = QtWidgets.QCheckBox(self.groupBox_42) + self.cwp_workdate_source_composed.setObjectName("cwp_workdate_source_composed") + self.gridLayout_7.addWidget(self.cwp_workdate_source_composed, 0, 0, 1, 1) + self.cwp_workdate_source_published = QtWidgets.QCheckBox(self.groupBox_42) + self.cwp_workdate_source_published.setObjectName("cwp_workdate_source_published") + self.gridLayout_7.addWidget(self.cwp_workdate_source_published, 0, 1, 1, 1) + self.cwp_workdate_source_premiered = QtWidgets.QCheckBox(self.groupBox_42) + self.cwp_workdate_source_premiered.setObjectName("cwp_workdate_source_premiered") + self.gridLayout_7.addWidget(self.cwp_workdate_source_premiered, 0, 2, 1, 1) + self.line_9 = QtWidgets.QFrame(self.groupBox_42) + self.line_9.setFrameShape(QtWidgets.QFrame.HLine) + self.line_9.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_9.setObjectName("line_9") + self.gridLayout_7.addWidget(self.line_9, 1, 0, 1, 3) + self.cwp_workdate_use_first = QtWidgets.QRadioButton(self.groupBox_42) + self.cwp_workdate_use_first.setObjectName("cwp_workdate_use_first") + self.gridLayout_7.addWidget(self.cwp_workdate_use_first, 2, 0, 1, 1) + self.cwp_workdate_use_all = QtWidgets.QRadioButton(self.groupBox_42) + self.cwp_workdate_use_all.setObjectName("cwp_workdate_use_all") + self.gridLayout_7.addWidget(self.cwp_workdate_use_all, 2, 1, 1, 1) + self.cwp_workdate_annotate = QtWidgets.QCheckBox(self.groupBox_42) + self.cwp_workdate_annotate.setObjectName("cwp_workdate_annotate") + self.gridLayout_7.addWidget(self.cwp_workdate_annotate, 2, 2, 1, 1) + self.gridLayout_10.addWidget(self.groupBox_42, 1, 0, 1, 3) + self.label_68 = QtWidgets.QLabel(self.groupBox_48) + self.label_68.setObjectName("label_68") + self.gridLayout_10.addWidget(self.label_68, 0, 0, 1, 1) + self.cwp_workdate_include = QtWidgets.QCheckBox(self.groupBox_48) + self.cwp_workdate_include.setObjectName("cwp_workdate_include") + self.gridLayout_10.addWidget(self.cwp_workdate_include, 2, 0, 1, 2) + self.cwp_workdate_tag = QtWidgets.QLineEdit(self.groupBox_48) + self.cwp_workdate_tag.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_workdate_tag.setObjectName("cwp_workdate_tag") + self.gridLayout_10.addWidget(self.cwp_workdate_tag, 0, 1, 1, 2) + self.verticalLayout_32.addWidget(self.groupBox_48) + self.line_5 = QtWidgets.QFrame(self.groupBox_41) + self.line_5.setMidLineWidth(0) + self.line_5.setFrameShape(QtWidgets.QFrame.HLine) + self.line_5.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_5.setObjectName("line_5") + self.verticalLayout_32.addWidget(self.line_5) + self.groupBox_49 = QtWidgets.QGroupBox(self.groupBox_41) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(222, 212, 236)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + self.groupBox_49.setPalette(palette) + self.groupBox_49.setAutoFillBackground(False) + self.groupBox_49.setStyleSheet("background-color: rgb(222, 212, 236);") + self.groupBox_49.setObjectName("groupBox_49") + self.gridLayout_12 = QtWidgets.QGridLayout(self.groupBox_49) + self.gridLayout_12.setObjectName("gridLayout_12") + self.label_69 = QtWidgets.QLabel(self.groupBox_49) + self.label_69.setObjectName("label_69") + self.gridLayout_12.addWidget(self.label_69, 0, 0, 1, 1) + self.cwp_muso_periods = QtWidgets.QCheckBox(self.groupBox_49) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.cwp_muso_periods.setFont(font) + self.cwp_muso_periods.setObjectName("cwp_muso_periods") + self.gridLayout_12.addWidget(self.cwp_muso_periods, 4, 0, 1, 2) + self.cwp_muso_dates = QtWidgets.QCheckBox(self.groupBox_49) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.cwp_muso_dates.setFont(font) + self.cwp_muso_dates.setObjectName("cwp_muso_dates") + self.gridLayout_12.addWidget(self.cwp_muso_dates, 1, 0, 1, 3) + self.cwp_period_tag = QtWidgets.QLineEdit(self.groupBox_49) + self.cwp_period_tag.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_period_tag.setObjectName("cwp_period_tag") + self.gridLayout_12.addWidget(self.cwp_period_tag, 0, 1, 1, 2) + self.label_70 = QtWidgets.QLabel(self.groupBox_49) + self.label_70.setObjectName("label_70") + self.gridLayout_12.addWidget(self.label_70, 3, 0, 1, 1) + self.cwp_period_map = QtWidgets.QPlainTextEdit(self.groupBox_49) + self.cwp_period_map.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cwp_period_map.setObjectName("cwp_period_map") + self.gridLayout_12.addWidget(self.cwp_period_map, 3, 2, 2, 1) + self.label_87 = QtWidgets.QLabel(self.groupBox_49) + self.label_87.setObjectName("label_87") + self.gridLayout_12.addWidget(self.label_87, 5, 2, 1, 1) + self.cwp_periods_arranger_as_composer = QtWidgets.QCheckBox(self.groupBox_49) + self.cwp_periods_arranger_as_composer.setObjectName("cwp_periods_arranger_as_composer") + self.gridLayout_12.addWidget(self.cwp_periods_arranger_as_composer, 2, 0, 1, 1) + self.verticalLayout_32.addWidget(self.groupBox_49) + self.verticalLayout_54.addWidget(self.groupBox_41) + self.gridLayout_13.addWidget(self.frame_36, 7, 0, 1, 1) + self.label_119 = QtWidgets.QLabel(self.scrollAreaWidgetContents_5) + self.label_119.setObjectName("label_119") + self.gridLayout_13.addWidget(self.label_119, 0, 0, 1, 1) + self.scrollArea_6.setWidget(self.scrollAreaWidgetContents_5) + self.gridLayout_2.addWidget(self.scrollArea_6, 1, 0, 1, 1) + self.tabWidget.addTab(self.Genres, "") + self.Tag_mapping = QtWidgets.QWidget() + self.Tag_mapping.setObjectName("Tag_mapping") + self.verticalLayout_28 = QtWidgets.QVBoxLayout(self.Tag_mapping) + self.verticalLayout_28.setObjectName("verticalLayout_28") + self.scrollArea_4 = QtWidgets.QScrollArea(self.Tag_mapping) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scrollArea_4.sizePolicy().hasHeightForWidth()) + self.scrollArea_4.setSizePolicy(sizePolicy) + self.scrollArea_4.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.scrollArea_4.setWidgetResizable(True) + self.scrollArea_4.setObjectName("scrollArea_4") + self.scrollAreaWidgetContents_8 = QtWidgets.QWidget() + self.scrollAreaWidgetContents_8.setGeometry(QtCore.QRect(0, 0, 1084, 885)) + self.scrollAreaWidgetContents_8.setObjectName("scrollAreaWidgetContents_8") + self.verticalLayout_33 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_8) + self.verticalLayout_33.setObjectName("verticalLayout_33") + self.label_18 = QtWidgets.QLabel(self.scrollAreaWidgetContents_8) + self.label_18.setStyleSheet("") + self.label_18.setObjectName("label_18") + self.verticalLayout_33.addWidget(self.label_18) + self.frame_22 = QtWidgets.QFrame(self.scrollAreaWidgetContents_8) + self.frame_22.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_22.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_22.setObjectName("frame_22") + self.verticalLayout_41 = QtWidgets.QVBoxLayout(self.frame_22) + self.verticalLayout_41.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_41.setSpacing(0) + self.verticalLayout_41.setObjectName("verticalLayout_41") + self.label_97 = QtWidgets.QLabel(self.frame_22) + self.label_97.setStyleSheet("background-color: rgb(209, 171, 222);\n" +"") + self.label_97.setObjectName("label_97") + self.verticalLayout_41.addWidget(self.label_97) + self.groupBox_11 = QtWidgets.QGroupBox(self.frame_22) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + self.groupBox_11.setPalette(palette) + self.groupBox_11.setAutoFillBackground(False) + self.groupBox_11.setStyleSheet("background-color: rgb(242, 221, 245);") + self.groupBox_11.setTitle("") + self.groupBox_11.setObjectName("groupBox_11") + self.verticalLayout_17 = QtWidgets.QVBoxLayout(self.groupBox_11) + self.verticalLayout_17.setObjectName("verticalLayout_17") + self.artist_tags = QtWidgets.QGroupBox(self.groupBox_11) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(242, 221, 245)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + self.artist_tags.setPalette(palette) + self.artist_tags.setAutoFillBackground(False) + self.artist_tags.setStyleSheet("") + self.artist_tags.setObjectName("artist_tags") + self.verticalLayout_12 = QtWidgets.QVBoxLayout(self.artist_tags) + self.verticalLayout_12.setObjectName("verticalLayout_12") + self.label_3 = QtWidgets.QLabel(self.artist_tags) + self.label_3.setObjectName("label_3") + self.verticalLayout_12.addWidget(self.label_3) + self.cea_blank_tag = QtWidgets.QLineEdit(self.artist_tags) + self.cea_blank_tag.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_blank_tag.setObjectName("cea_blank_tag") + self.verticalLayout_12.addWidget(self.cea_blank_tag) + self.cea_blank_tag_2 = QtWidgets.QLineEdit(self.artist_tags) + self.cea_blank_tag_2.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_blank_tag_2.setObjectName("cea_blank_tag_2") + self.verticalLayout_12.addWidget(self.cea_blank_tag_2) + self.verticalLayout_17.addWidget(self.artist_tags) + self.label_19 = QtWidgets.QLabel(self.groupBox_11) + self.label_19.setObjectName("label_19") + self.verticalLayout_17.addWidget(self.label_19) + self.cea_keep = QtWidgets.QLineEdit(self.groupBox_11) + self.cea_keep.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_keep.setObjectName("cea_keep") + self.verticalLayout_17.addWidget(self.cea_keep) + self.cea_clear_tags = QtWidgets.QCheckBox(self.groupBox_11) + self.cea_clear_tags.setObjectName("cea_clear_tags") + self.verticalLayout_17.addWidget(self.cea_clear_tags) + self.verticalLayout_41.addWidget(self.groupBox_11) + self.verticalLayout_33.addWidget(self.frame_22) + self.frame_24 = QtWidgets.QFrame(self.scrollAreaWidgetContents_8) + self.frame_24.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_24.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_24.setObjectName("frame_24") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.frame_24) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.label_98 = QtWidgets.QLabel(self.frame_24) + self.label_98.setStyleSheet("background-color: rgb(229, 174, 142);") + self.label_98.setObjectName("label_98") + self.verticalLayout_3.addWidget(self.label_98) + self.artist_tags_2 = QtWidgets.QGroupBox(self.frame_24) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.artist_tags_2.sizePolicy().hasHeightForWidth()) + self.artist_tags_2.setSizePolicy(sizePolicy) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 161, 163)) + brush = QtGui.QBrush(QtGui.QColor(255, 229, 214)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) - self.groupBox_47.setPalette(palette) - self.groupBox_47.setAutoFillBackground(True) - self.groupBox_47.setStyleSheet("") - self.groupBox_47.setObjectName("groupBox_47") - self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_47) - self.gridLayout_4.setObjectName("gridLayout_4") - self.label_72 = QtWidgets.QLabel(self.groupBox_47) - self.label_72.setObjectName("label_72") - self.gridLayout_4.addWidget(self.label_72, 0, 0, 1, 1) - self.cwp_key_tag = QtWidgets.QLineEdit(self.groupBox_47) - self.cwp_key_tag.setStyleSheet("background-color: rgb(248, 116, 120);") - self.cwp_key_tag.setObjectName("cwp_key_tag") - self.gridLayout_4.addWidget(self.cwp_key_tag, 0, 2, 1, 1) - self.groupBox_52 = QtWidgets.QGroupBox(self.groupBox_47) - self.groupBox_52.setObjectName("groupBox_52") - self.horizontalLayout_35 = QtWidgets.QHBoxLayout(self.groupBox_52) - self.horizontalLayout_35.setObjectName("horizontalLayout_35") - self.cwp_key_never_include = QtWidgets.QRadioButton(self.groupBox_52) - self.cwp_key_never_include.setObjectName("cwp_key_never_include") - self.horizontalLayout_35.addWidget(self.cwp_key_never_include) - self.cwp_key_contingent_include = QtWidgets.QRadioButton(self.groupBox_52) - self.cwp_key_contingent_include.setObjectName("cwp_key_contingent_include") - self.horizontalLayout_35.addWidget(self.cwp_key_contingent_include) - self.cwp_key_include = QtWidgets.QRadioButton(self.groupBox_52) - self.cwp_key_include.setObjectName("cwp_key_include") - self.horizontalLayout_35.addWidget(self.cwp_key_include) - self.gridLayout_4.addWidget(self.groupBox_52, 1, 0, 1, 2) - self.verticalLayout_34.addWidget(self.groupBox_47) - self.gridLayout_13.addWidget(self.groupBox_39, 2, 0, 1, 2) - self.scrollArea_6.setWidget(self.scrollAreaWidgetContents_5) - self.gridLayout_2.addWidget(self.scrollArea_6, 1, 0, 1, 1) - self.tabWidget.addTab(self.Genres, "") + self.artist_tags_2.setPalette(palette) + self.artist_tags_2.setFocusPolicy(QtCore.Qt.NoFocus) + self.artist_tags_2.setAutoFillBackground(False) + self.artist_tags_2.setStyleSheet("font: 75 8pt \"MS Shell Dlg 2\";\n" +"background-color: rgb(255, 229, 214);") + self.artist_tags_2.setTitle("") + self.artist_tags_2.setObjectName("artist_tags_2") + self.gridLayout_14 = QtWidgets.QGridLayout(self.artist_tags_2) + self.gridLayout_14.setObjectName("gridLayout_14") + self.textBrowser = QtWidgets.QTextBrowser(self.artist_tags_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.textBrowser.sizePolicy().hasHeightForWidth()) + self.textBrowser.setSizePolicy(sizePolicy) + self.textBrowser.setMaximumSize(QtCore.QSize(16777215, 100)) + self.textBrowser.setStyleSheet("background-color: rgb(249, 215, 187);") + self.textBrowser.setObjectName("textBrowser") + self.gridLayout_14.addWidget(self.textBrowser, 0, 0, 1, 1) + self.frame_25 = QtWidgets.QFrame(self.artist_tags_2) + self.frame_25.setObjectName("frame_25") + self._14 = QtWidgets.QHBoxLayout(self.frame_25) + self._14.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self._14.setContentsMargins(1, 1, 1, 1) + self._14.setSpacing(6) + self._14.setObjectName("_14") + self.toolButton_1 = QtWidgets.QToolButton(self.frame_25) + self.toolButton_1.setAutoFillBackground(False) + self.toolButton_1.setStyleSheet("") + self.toolButton_1.setCheckable(True) + self.toolButton_1.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.toolButton_1.setAutoRaise(False) + self.toolButton_1.setObjectName("toolButton_1") + self._14.addWidget(self.toolButton_1) + self.cea_source_1 = QtWidgets.QComboBox(self.frame_25) + self.cea_source_1.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cea_source_1.sizePolicy().hasHeightForWidth()) + self.cea_source_1.setSizePolicy(sizePolicy) + self.cea_source_1.setMouseTracking(False) + self.cea_source_1.setFocusPolicy(QtCore.Qt.StrongFocus) + self.cea_source_1.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_1.setEditable(True) + self.cea_source_1.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + self.cea_source_1.setObjectName("cea_source_1") + self.cea_source_1.addItem("") + self.cea_source_1.setItemText(0, "") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self.cea_source_1.addItem("") + self._14.addWidget(self.cea_source_1) + self.label_21 = QtWidgets.QLabel(self.frame_25) + self.label_21.setObjectName("label_21") + self._14.addWidget(self.label_21) + self.cea_tag_1 = QtWidgets.QLineEdit(self.frame_25) + self.cea_tag_1.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_1.setObjectName("cea_tag_1") + self._14.addWidget(self.cea_tag_1) + self.cea_cond_1 = QtWidgets.QCheckBox(self.frame_25) + self.cea_cond_1.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_1.setObjectName("cea_cond_1") + self._14.addWidget(self.cea_cond_1) + self.gridLayout_14.addWidget(self.frame_25, 1, 0, 1, 1) + self._15 = QtWidgets.QHBoxLayout() + self._15.setContentsMargins(0, 0, 0, 0) + self._15.setSpacing(6) + self._15.setObjectName("_15") + self.toolButton_2 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_2.setStyleSheet("") + self.toolButton_2.setCheckable(True) + self.toolButton_2.setObjectName("toolButton_2") + self._15.addWidget(self.toolButton_2) + self.cea_source_2 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_2.setEnabled(False) + self.cea_source_2.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_2.setEditable(True) + self.cea_source_2.setObjectName("cea_source_2") + self.cea_source_2.addItem("") + self.cea_source_2.setItemText(0, "") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self.cea_source_2.addItem("") + self._15.addWidget(self.cea_source_2) + self.label_23 = QtWidgets.QLabel(self.artist_tags_2) + self.label_23.setObjectName("label_23") + self._15.addWidget(self.label_23) + self.cea_tag_2 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_2.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_2.setObjectName("cea_tag_2") + self._15.addWidget(self.cea_tag_2) + self.cea_cond_2 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_2.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_2.setObjectName("cea_cond_2") + self._15.addWidget(self.cea_cond_2) + self.gridLayout_14.addLayout(self._15, 2, 0, 1, 1) + self._16 = QtWidgets.QHBoxLayout() + self._16.setContentsMargins(0, 0, 0, 0) + self._16.setSpacing(6) + self._16.setObjectName("_16") + self.toolButton_3 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_3.setStyleSheet("") + self.toolButton_3.setCheckable(True) + self.toolButton_3.setObjectName("toolButton_3") + self._16.addWidget(self.toolButton_3) + self.cea_source_3 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_3.setEnabled(False) + self.cea_source_3.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_3.setEditable(True) + self.cea_source_3.setObjectName("cea_source_3") + self.cea_source_3.addItem("") + self.cea_source_3.setItemText(0, "") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self.cea_source_3.addItem("") + self._16.addWidget(self.cea_source_3) + self.label_25 = QtWidgets.QLabel(self.artist_tags_2) + self.label_25.setObjectName("label_25") + self._16.addWidget(self.label_25) + self.cea_tag_3 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_3.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_3.setObjectName("cea_tag_3") + self._16.addWidget(self.cea_tag_3) + self.cea_cond_3 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_3.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_3.setObjectName("cea_cond_3") + self._16.addWidget(self.cea_cond_3) + self.gridLayout_14.addLayout(self._16, 3, 0, 1, 1) + self._17 = QtWidgets.QHBoxLayout() + self._17.setContentsMargins(0, 0, 0, 0) + self._17.setSpacing(6) + self._17.setObjectName("_17") + self.toolButton_4 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_4.setStyleSheet("") + self.toolButton_4.setCheckable(True) + self.toolButton_4.setObjectName("toolButton_4") + self._17.addWidget(self.toolButton_4) + self.cea_source_4 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_4.setEnabled(False) + self.cea_source_4.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_4.setEditable(True) + self.cea_source_4.setObjectName("cea_source_4") + self.cea_source_4.addItem("") + self.cea_source_4.setItemText(0, "") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self.cea_source_4.addItem("") + self._17.addWidget(self.cea_source_4) + self.label_27 = QtWidgets.QLabel(self.artist_tags_2) + self.label_27.setObjectName("label_27") + self._17.addWidget(self.label_27) + self.cea_tag_4 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_4.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_4.setObjectName("cea_tag_4") + self._17.addWidget(self.cea_tag_4) + self.cea_cond_4 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_4.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_4.setObjectName("cea_cond_4") + self._17.addWidget(self.cea_cond_4) + self.gridLayout_14.addLayout(self._17, 4, 0, 1, 1) + self._18 = QtWidgets.QHBoxLayout() + self._18.setContentsMargins(0, 0, 0, 0) + self._18.setSpacing(6) + self._18.setObjectName("_18") + self.toolButton_5 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_5.setStyleSheet("") + self.toolButton_5.setCheckable(True) + self.toolButton_5.setObjectName("toolButton_5") + self._18.addWidget(self.toolButton_5) + self.cea_source_5 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_5.setEnabled(False) + self.cea_source_5.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_5.setEditable(True) + self.cea_source_5.setObjectName("cea_source_5") + self.cea_source_5.addItem("") + self.cea_source_5.setItemText(0, "") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self.cea_source_5.addItem("") + self._18.addWidget(self.cea_source_5) + self.label_29 = QtWidgets.QLabel(self.artist_tags_2) + self.label_29.setObjectName("label_29") + self._18.addWidget(self.label_29) + self.cea_tag_5 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_5.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_5.setObjectName("cea_tag_5") + self._18.addWidget(self.cea_tag_5) + self.cea_cond_5 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_5.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_5.setObjectName("cea_cond_5") + self._18.addWidget(self.cea_cond_5) + self.gridLayout_14.addLayout(self._18, 5, 0, 1, 1) + self._19 = QtWidgets.QHBoxLayout() + self._19.setContentsMargins(0, 0, 0, 0) + self._19.setSpacing(6) + self._19.setObjectName("_19") + self.toolButton_6 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_6.setStyleSheet("") + self.toolButton_6.setCheckable(True) + self.toolButton_6.setObjectName("toolButton_6") + self._19.addWidget(self.toolButton_6) + self.cea_source_6 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_6.setEnabled(False) + self.cea_source_6.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_6.setEditable(True) + self.cea_source_6.setObjectName("cea_source_6") + self.cea_source_6.addItem("") + self.cea_source_6.setItemText(0, "") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self.cea_source_6.addItem("") + self._19.addWidget(self.cea_source_6) + self.label_31 = QtWidgets.QLabel(self.artist_tags_2) + self.label_31.setObjectName("label_31") + self._19.addWidget(self.label_31) + self.cea_tag_6 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_6.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_6.setObjectName("cea_tag_6") + self._19.addWidget(self.cea_tag_6) + self.cea_cond_6 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_6.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_6.setObjectName("cea_cond_6") + self._19.addWidget(self.cea_cond_6) + self.gridLayout_14.addLayout(self._19, 6, 0, 1, 1) + self._20 = QtWidgets.QHBoxLayout() + self._20.setContentsMargins(0, 0, 0, 0) + self._20.setSpacing(6) + self._20.setObjectName("_20") + self.toolButton_7 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_7.setStyleSheet("") + self.toolButton_7.setCheckable(True) + self.toolButton_7.setObjectName("toolButton_7") + self._20.addWidget(self.toolButton_7) + self.cea_source_7 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_7.setEnabled(False) + self.cea_source_7.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_7.setEditable(True) + self.cea_source_7.setObjectName("cea_source_7") + self.cea_source_7.addItem("") + self.cea_source_7.setItemText(0, "") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self.cea_source_7.addItem("") + self._20.addWidget(self.cea_source_7) + self.label_33 = QtWidgets.QLabel(self.artist_tags_2) + self.label_33.setObjectName("label_33") + self._20.addWidget(self.label_33) + self.cea_tag_7 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_7.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_7.setObjectName("cea_tag_7") + self._20.addWidget(self.cea_tag_7) + self.cea_cond_7 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_7.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_7.setObjectName("cea_cond_7") + self._20.addWidget(self.cea_cond_7) + self.gridLayout_14.addLayout(self._20, 7, 0, 1, 1) + self._21 = QtWidgets.QHBoxLayout() + self._21.setContentsMargins(0, 0, 0, 0) + self._21.setSpacing(6) + self._21.setObjectName("_21") + self.toolButton_8 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_8.setStyleSheet("") + self.toolButton_8.setCheckable(True) + self.toolButton_8.setObjectName("toolButton_8") + self._21.addWidget(self.toolButton_8) + self.cea_source_8 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_8.setEnabled(False) + self.cea_source_8.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_8.setEditable(True) + self.cea_source_8.setObjectName("cea_source_8") + self.cea_source_8.addItem("") + self.cea_source_8.setItemText(0, "") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self.cea_source_8.addItem("") + self._21.addWidget(self.cea_source_8) + self.label_35 = QtWidgets.QLabel(self.artist_tags_2) + self.label_35.setObjectName("label_35") + self._21.addWidget(self.label_35) + self.cea_tag_8 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_8.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_8.setObjectName("cea_tag_8") + self._21.addWidget(self.cea_tag_8) + self.cea_cond_8 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_8.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_8.setObjectName("cea_cond_8") + self._21.addWidget(self.cea_cond_8) + self.gridLayout_14.addLayout(self._21, 8, 0, 1, 1) + self._30 = QtWidgets.QHBoxLayout() + self._30.setContentsMargins(0, 0, 0, 0) + self._30.setSpacing(6) + self._30.setObjectName("_30") + self.toolButton_9 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_9.setStyleSheet("") + self.toolButton_9.setCheckable(True) + self.toolButton_9.setObjectName("toolButton_9") + self._30.addWidget(self.toolButton_9) + self.cea_source_9 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_9.setEnabled(False) + self.cea_source_9.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_9.setEditable(True) + self.cea_source_9.setObjectName("cea_source_9") + self.cea_source_9.addItem("") + self.cea_source_9.setItemText(0, "") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self.cea_source_9.addItem("") + self._30.addWidget(self.cea_source_9) + self.label_53 = QtWidgets.QLabel(self.artist_tags_2) + self.label_53.setObjectName("label_53") + self._30.addWidget(self.label_53) + self.cea_tag_9 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_9.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_9.setObjectName("cea_tag_9") + self._30.addWidget(self.cea_tag_9) + self.cea_cond_9 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_9.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_9.setObjectName("cea_cond_9") + self._30.addWidget(self.cea_cond_9) + self.gridLayout_14.addLayout(self._30, 9, 0, 1, 1) + self._31 = QtWidgets.QHBoxLayout() + self._31.setContentsMargins(0, 0, 0, 0) + self._31.setSpacing(6) + self._31.setObjectName("_31") + self.toolButton_10 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_10.setStyleSheet("") + self.toolButton_10.setCheckable(True) + self.toolButton_10.setObjectName("toolButton_10") + self._31.addWidget(self.toolButton_10) + self.cea_source_10 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_10.setEnabled(False) + self.cea_source_10.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_10.setEditable(True) + self.cea_source_10.setObjectName("cea_source_10") + self.cea_source_10.addItem("") + self.cea_source_10.setItemText(0, "") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self.cea_source_10.addItem("") + self._31.addWidget(self.cea_source_10) + self.label_55 = QtWidgets.QLabel(self.artist_tags_2) + self.label_55.setObjectName("label_55") + self._31.addWidget(self.label_55) + self.cea_tag_10 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_10.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_10.setObjectName("cea_tag_10") + self._31.addWidget(self.cea_tag_10) + self.cea_cond_10 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_10.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_10.setObjectName("cea_cond_10") + self._31.addWidget(self.cea_cond_10) + self.gridLayout_14.addLayout(self._31, 10, 0, 1, 1) + self._32 = QtWidgets.QHBoxLayout() + self._32.setContentsMargins(0, 0, 0, 0) + self._32.setSpacing(6) + self._32.setObjectName("_32") + self.toolButton_11 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_11.setStyleSheet("") + self.toolButton_11.setCheckable(True) + self.toolButton_11.setObjectName("toolButton_11") + self._32.addWidget(self.toolButton_11) + self.cea_source_11 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_11.setEnabled(False) + self.cea_source_11.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_11.setEditable(True) + self.cea_source_11.setObjectName("cea_source_11") + self.cea_source_11.addItem("") + self.cea_source_11.setItemText(0, "") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self.cea_source_11.addItem("") + self._32.addWidget(self.cea_source_11) + self.label_57 = QtWidgets.QLabel(self.artist_tags_2) + self.label_57.setObjectName("label_57") + self._32.addWidget(self.label_57) + self.cea_tag_11 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_11.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_11.setObjectName("cea_tag_11") + self._32.addWidget(self.cea_tag_11) + self.cea_cond_11 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_11.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_11.setObjectName("cea_cond_11") + self._32.addWidget(self.cea_cond_11) + self.gridLayout_14.addLayout(self._32, 11, 0, 1, 1) + self._33 = QtWidgets.QHBoxLayout() + self._33.setContentsMargins(0, 0, 0, 0) + self._33.setSpacing(6) + self._33.setObjectName("_33") + self.toolButton_12 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_12.setStyleSheet("") + self.toolButton_12.setCheckable(True) + self.toolButton_12.setObjectName("toolButton_12") + self._33.addWidget(self.toolButton_12) + self.cea_source_12 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_12.setEnabled(False) + self.cea_source_12.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_12.setEditable(True) + self.cea_source_12.setObjectName("cea_source_12") + self.cea_source_12.addItem("") + self.cea_source_12.setItemText(0, "") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self.cea_source_12.addItem("") + self._33.addWidget(self.cea_source_12) + self.label_59 = QtWidgets.QLabel(self.artist_tags_2) + self.label_59.setObjectName("label_59") + self._33.addWidget(self.label_59) + self.cea_tag_12 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_12.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_12.setObjectName("cea_tag_12") + self._33.addWidget(self.cea_tag_12) + self.cea_cond_12 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_12.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_12.setObjectName("cea_cond_12") + self._33.addWidget(self.cea_cond_12) + self.gridLayout_14.addLayout(self._33, 12, 0, 1, 1) + self._34 = QtWidgets.QHBoxLayout() + self._34.setContentsMargins(0, 0, 0, 0) + self._34.setSpacing(6) + self._34.setObjectName("_34") + self.toolButton_13 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_13.setStyleSheet("") + self.toolButton_13.setCheckable(True) + self.toolButton_13.setObjectName("toolButton_13") + self._34.addWidget(self.toolButton_13) + self.cea_source_13 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_13.setEnabled(False) + self.cea_source_13.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_13.setEditable(True) + self.cea_source_13.setObjectName("cea_source_13") + self.cea_source_13.addItem("") + self.cea_source_13.setItemText(0, "") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self.cea_source_13.addItem("") + self._34.addWidget(self.cea_source_13) + self.label_61 = QtWidgets.QLabel(self.artist_tags_2) + self.label_61.setObjectName("label_61") + self._34.addWidget(self.label_61) + self.cea_tag_13 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_13.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_13.setObjectName("cea_tag_13") + self._34.addWidget(self.cea_tag_13) + self.cea_cond_13 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_13.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_13.setObjectName("cea_cond_13") + self._34.addWidget(self.cea_cond_13) + self.gridLayout_14.addLayout(self._34, 13, 0, 1, 1) + self._35 = QtWidgets.QHBoxLayout() + self._35.setContentsMargins(0, 0, 0, 0) + self._35.setSpacing(6) + self._35.setObjectName("_35") + self.toolButton_14 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_14.setStyleSheet("") + self.toolButton_14.setCheckable(True) + self.toolButton_14.setObjectName("toolButton_14") + self._35.addWidget(self.toolButton_14) + self.cea_source_14 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_14.setEnabled(False) + self.cea_source_14.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_14.setEditable(True) + self.cea_source_14.setObjectName("cea_source_14") + self.cea_source_14.addItem("") + self.cea_source_14.setItemText(0, "") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self.cea_source_14.addItem("") + self._35.addWidget(self.cea_source_14) + self.label_63 = QtWidgets.QLabel(self.artist_tags_2) + self.label_63.setObjectName("label_63") + self._35.addWidget(self.label_63) + self.cea_tag_14 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_14.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_14.setObjectName("cea_tag_14") + self._35.addWidget(self.cea_tag_14) + self.cea_cond_14 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_14.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_14.setObjectName("cea_cond_14") + self._35.addWidget(self.cea_cond_14) + self.gridLayout_14.addLayout(self._35, 14, 0, 1, 1) + self._36 = QtWidgets.QHBoxLayout() + self._36.setContentsMargins(0, 0, 0, 0) + self._36.setSpacing(6) + self._36.setObjectName("_36") + self.toolButton_15 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_15.setStyleSheet("") + self.toolButton_15.setCheckable(True) + self.toolButton_15.setObjectName("toolButton_15") + self._36.addWidget(self.toolButton_15) + self.cea_source_15 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_15.setEnabled(False) + self.cea_source_15.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_15.setEditable(True) + self.cea_source_15.setObjectName("cea_source_15") + self.cea_source_15.addItem("") + self.cea_source_15.setItemText(0, "") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self.cea_source_15.addItem("") + self._36.addWidget(self.cea_source_15) + self.label_65 = QtWidgets.QLabel(self.artist_tags_2) + self.label_65.setObjectName("label_65") + self._36.addWidget(self.label_65) + self.cea_tag_15 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_15.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_15.setObjectName("cea_tag_15") + self._36.addWidget(self.cea_tag_15) + self.cea_cond_15 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_15.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_15.setObjectName("cea_cond_15") + self._36.addWidget(self.cea_cond_15) + self.gridLayout_14.addLayout(self._36, 15, 0, 1, 1) + self._37 = QtWidgets.QHBoxLayout() + self._37.setContentsMargins(0, 0, 0, 0) + self._37.setSpacing(6) + self._37.setObjectName("_37") + self.toolButton_16 = QtWidgets.QToolButton(self.artist_tags_2) + self.toolButton_16.setStyleSheet("") + self.toolButton_16.setCheckable(True) + self.toolButton_16.setObjectName("toolButton_16") + self._37.addWidget(self.toolButton_16) + self.cea_source_16 = QtWidgets.QComboBox(self.artist_tags_2) + self.cea_source_16.setEnabled(False) + self.cea_source_16.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_source_16.setEditable(True) + self.cea_source_16.setObjectName("cea_source_16") + self.cea_source_16.addItem("") + self.cea_source_16.setItemText(0, "") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self.cea_source_16.addItem("") + self._37.addWidget(self.cea_source_16) + self.label_67 = QtWidgets.QLabel(self.artist_tags_2) + self.label_67.setObjectName("label_67") + self._37.addWidget(self.label_67) + self.cea_tag_16 = QtWidgets.QLineEdit(self.artist_tags_2) + self.cea_tag_16.setStyleSheet("background-color: rgb(250, 250, 250);") + self.cea_tag_16.setObjectName("cea_tag_16") + self._37.addWidget(self.cea_tag_16) + self.cea_cond_16 = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_cond_16.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_cond_16.setObjectName("cea_cond_16") + self._37.addWidget(self.cea_cond_16) + self.gridLayout_14.addLayout(self._37, 16, 0, 1, 1) + self._38 = QtWidgets.QHBoxLayout() + self._38.setContentsMargins(0, 0, 0, 0) + self._38.setSpacing(6) + self._38.setObjectName("_38") + self.label_42 = QtWidgets.QLabel(self.artist_tags_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_42.sizePolicy().hasHeightForWidth()) + self.label_42.setSizePolicy(sizePolicy) + self.label_42.setLayoutDirection(QtCore.Qt.LeftToRight) + self.label_42.setLineWidth(1) + self.label_42.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_42.setObjectName("label_42") + self._38.addWidget(self.label_42) + self.label_17 = QtWidgets.QLabel(self.artist_tags_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_17.sizePolicy().hasHeightForWidth()) + self.label_17.setSizePolicy(sizePolicy) + self.label_17.setLayoutDirection(QtCore.Qt.RightToLeft) + self.label_17.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_17.setObjectName("label_17") + self._38.addWidget(self.label_17) + self.gridLayout_14.addLayout(self._38, 17, 0, 1, 1) + self.cea_tag_sort = QtWidgets.QCheckBox(self.artist_tags_2) + self.cea_tag_sort.setLayoutDirection(QtCore.Qt.LeftToRight) + self.cea_tag_sort.setObjectName("cea_tag_sort") + self.gridLayout_14.addWidget(self.cea_tag_sort, 18, 0, 1, 1) + self.verticalLayout_3.addWidget(self.artist_tags_2) + self.verticalLayout_33.addWidget(self.frame_24) + self.scrollArea_4.setWidget(self.scrollAreaWidgetContents_8) + self.verticalLayout_28.addWidget(self.scrollArea_4) + self.tabWidget.addTab(self.Tag_mapping, "") self.Advanced = QtWidgets.QWidget() self.Advanced.setObjectName("Advanced") self.horizontalLayout_31 = QtWidgets.QHBoxLayout(self.Advanced) @@ -2688,67 +3226,116 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.scrollArea_2.setWidgetResizable(True) self.scrollArea_2.setObjectName("scrollArea_2") self.scrollAreaWidgetContents_2 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 1087, 1205)) + self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, -33, 1084, 1408)) self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2") self.verticalLayout_18 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2) self.verticalLayout_18.setObjectName("verticalLayout_18") - self.groupBox_3 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_2) + self.frame_37 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.frame_37.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_37.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_37.setObjectName("frame_37") + self.verticalLayout_55 = QtWidgets.QVBoxLayout(self.frame_37) + self.verticalLayout_55.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_55.setSpacing(0) + self.verticalLayout_55.setObjectName("verticalLayout_55") + self.label_110 = QtWidgets.QLabel(self.frame_37) + self.label_110.setStyleSheet("background-color: rgb(208, 208, 156);") + self.label_110.setObjectName("label_110") + self.verticalLayout_55.addWidget(self.label_110) + self.groupBox_3 = QtWidgets.QGroupBox(self.frame_37) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_3.setPalette(palette) - self.groupBox_3.setAutoFillBackground(True) - self.groupBox_3.setStyleSheet("") + self.groupBox_3.setAutoFillBackground(False) + self.groupBox_3.setStyleSheet("background-color: rgb(229, 229, 197);") + self.groupBox_3.setTitle("") self.groupBox_3.setObjectName("groupBox_3") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_3) self.verticalLayout_5.setObjectName("verticalLayout_5") self.ce_no_run = QtWidgets.QCheckBox(self.groupBox_3) self.ce_no_run.setObjectName("ce_no_run") self.verticalLayout_5.addWidget(self.ce_no_run) - self.verticalLayout_18.addWidget(self.groupBox_3) - self.advanced_artists = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_2) + self.verticalLayout_55.addWidget(self.groupBox_3) + self.verticalLayout_18.addWidget(self.frame_37) + self.frame_38 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.frame_38.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_38.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_38.setObjectName("frame_38") + self.verticalLayout_56 = QtWidgets.QVBoxLayout(self.frame_38) + self.verticalLayout_56.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_56.setSpacing(0) + self.verticalLayout_56.setObjectName("verticalLayout_56") + self.label_111 = QtWidgets.QLabel(self.frame_38) + self.label_111.setStyleSheet("background-color: rgb(208, 208, 156);") + self.label_111.setObjectName("label_111") + self.verticalLayout_56.addWidget(self.label_111) + self.advanced_artists = QtWidgets.QGroupBox(self.frame_38) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.advanced_artists.setPalette(palette) - self.advanced_artists.setAutoFillBackground(True) - self.advanced_artists.setStyleSheet("") + self.advanced_artists.setAutoFillBackground(False) + self.advanced_artists.setStyleSheet("background-color: rgb(229, 229, 197);") + self.advanced_artists.setTitle("") self.advanced_artists.setObjectName("advanced_artists") self.verticalLayout_20 = QtWidgets.QVBoxLayout(self.advanced_artists) self.verticalLayout_20.setObjectName("verticalLayout_20") + self.label_112 = QtWidgets.QLabel(self.advanced_artists) + self.label_112.setObjectName("label_112") + self.verticalLayout_20.addWidget(self.label_112) self.run_options_2 = QtWidgets.QGroupBox(self.advanced_artists) + self.run_options_2.setTitle("") self.run_options_2.setObjectName("run_options_2") self._13 = QtWidgets.QVBoxLayout(self.run_options_2) self._13.setContentsMargins(9, 9, 9, 9) @@ -2758,53 +3345,76 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cea_orchestras_2.setObjectName("cea_orchestras_2") self._13.addWidget(self.cea_orchestras_2) self.cea_orchestras = QtWidgets.QLineEdit(self.run_options_2) - self.cea_orchestras.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cea_orchestras.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_orchestras.setObjectName("cea_orchestras") self._13.addWidget(self.cea_orchestras) self.cea_choirs_2 = QtWidgets.QLabel(self.run_options_2) self.cea_choirs_2.setObjectName("cea_choirs_2") self._13.addWidget(self.cea_choirs_2) self.cea_choirs = QtWidgets.QLineEdit(self.run_options_2) - self.cea_choirs.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cea_choirs.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_choirs.setObjectName("cea_choirs") self._13.addWidget(self.cea_choirs) self.cea_groups_2 = QtWidgets.QLabel(self.run_options_2) self.cea_groups_2.setObjectName("cea_groups_2") self._13.addWidget(self.cea_groups_2) self.cea_groups = QtWidgets.QLineEdit(self.run_options_2) - self.cea_groups.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cea_groups.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_groups.setObjectName("cea_groups") self._13.addWidget(self.cea_groups) self.verticalLayout_20.addWidget(self.run_options_2) - self.verticalLayout_18.addWidget(self.advanced_artists) - self.advanced_works = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_2) + self.verticalLayout_56.addWidget(self.advanced_artists) + self.verticalLayout_18.addWidget(self.frame_38) + self.frame_39 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.frame_39.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_39.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_39.setObjectName("frame_39") + self.verticalLayout_57 = QtWidgets.QVBoxLayout(self.frame_39) + self.verticalLayout_57.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_57.setSpacing(0) + self.verticalLayout_57.setObjectName("verticalLayout_57") + self.label_113 = QtWidgets.QLabel(self.frame_39) + self.label_113.setStyleSheet("background-color: rgb(208, 208, 156);") + self.label_113.setObjectName("label_113") + self.verticalLayout_57.addWidget(self.label_113) + self.advanced_works = QtWidgets.QGroupBox(self.frame_39) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.advanced_works.sizePolicy().hasHeightForWidth()) self.advanced_works.setSizePolicy(sizePolicy) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.advanced_works.setPalette(palette) - self.advanced_works.setAutoFillBackground(True) - self.advanced_works.setStyleSheet("") + self.advanced_works.setAutoFillBackground(False) + self.advanced_works.setStyleSheet("background-color: rgb(229, 229, 197);") + self.advanced_works.setTitle("") self.advanced_works.setObjectName("advanced_works") self.verticalLayout = QtWidgets.QVBoxLayout(self.advanced_works) self.verticalLayout.setObjectName("verticalLayout") @@ -2821,38 +3431,70 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_4.setObjectName("label_4") self._2.addWidget(self.label_4) self.cwp_retries = QtWidgets.QSpinBox(self.advanced_works) - self.cwp_retries.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_retries.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_retries.setSuffix("") self.cwp_retries.setMinimum(0) self.cwp_retries.setMaximum(20) self.cwp_retries.setObjectName("cwp_retries") self._2.addWidget(self.cwp_retries) self.verticalLayout.addLayout(self._2) + self._23 = QtWidgets.QHBoxLayout() + self._23.setContentsMargins(0, 0, 0, 0) + self._23.setSpacing(6) + self._23.setObjectName("_23") + self.label_120 = QtWidgets.QLabel(self.advanced_works) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_120.sizePolicy().hasHeightForWidth()) + self.label_120.setSizePolicy(sizePolicy) + self.label_120.setObjectName("label_120") + self._23.addWidget(self.label_120) + self.cwp_allow_empty_parts = QtWidgets.QCheckBox(self.advanced_works) + self.cwp_allow_empty_parts.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cwp_allow_empty_parts.setText("") + self.cwp_allow_empty_parts.setObjectName("cwp_allow_empty_parts") + self._23.addWidget(self.cwp_allow_empty_parts) + self.verticalLayout.addLayout(self._23) self.groupBox_51 = QtWidgets.QGroupBox(self.advanced_works) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(241, 241, 167)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_51.setPalette(palette) - self.groupBox_51.setAutoFillBackground(True) + self.groupBox_51.setAutoFillBackground(False) + self.groupBox_51.setStyleSheet("background-color: rgb(241, 241, 167);") + self.groupBox_51.setTitle("") self.groupBox_51.setObjectName("groupBox_51") self.verticalLayout_35 = QtWidgets.QVBoxLayout(self.groupBox_51) self.verticalLayout_35.setObjectName("verticalLayout_35") + self.label_114 = QtWidgets.QLabel(self.groupBox_51) + self.label_114.setObjectName("label_114") + self.verticalLayout_35.addWidget(self.label_114) self._22 = QtWidgets.QHBoxLayout() self._22.setContentsMargins(0, 0, 0, 0) self._22.setSpacing(6) @@ -2866,7 +3508,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_90.setObjectName("label_90") self._22.addWidget(self.label_90) self.cwp_common_chars = QtWidgets.QSpinBox(self.groupBox_51) - self.cwp_common_chars.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_common_chars.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_common_chars.setSuffix("") self.cwp_common_chars.setMinimum(0) self.cwp_common_chars.setMaximum(99) @@ -2879,29 +3521,43 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.verticalLayout.addWidget(self.groupBox_51) self.run_options_3a = QtWidgets.QGroupBox(self.advanced_works) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(198, 198, 148)) + brush = QtGui.QBrush(QtGui.QColor(202, 202, 140)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.run_options_3a.setPalette(palette) - self.run_options_3a.setAutoFillBackground(True) + self.run_options_3a.setAutoFillBackground(False) + self.run_options_3a.setStyleSheet("background-color: rgb(202, 202, 140);") + self.run_options_3a.setTitle("") self.run_options_3a.setObjectName("run_options_3a") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.run_options_3a) self.verticalLayout_2.setObjectName("verticalLayout_2") + self.label_115 = QtWidgets.QLabel(self.run_options_3a) + self.label_115.setObjectName("label_115") + self.verticalLayout_2.addWidget(self.label_115) self._3 = QtWidgets.QHBoxLayout() self._3.setContentsMargins(0, 0, 0, 0) self._3.setSpacing(6) @@ -2915,9 +3571,9 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_6.setObjectName("label_6") self._3.addWidget(self.label_6) self.cwp_proximity = QtWidgets.QSpinBox(self.run_options_3a) - self.cwp_proximity.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_proximity.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_proximity.setSuffix("") - self.cwp_proximity.setMaximum(20) + self.cwp_proximity.setMaximum(99) self.cwp_proximity.setObjectName("cwp_proximity") self._3.addWidget(self.cwp_proximity) self.verticalLayout_2.addLayout(self._3) @@ -2934,9 +3590,9 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_7.setObjectName("label_7") self._4.addWidget(self.label_7) self.cwp_end_proximity = QtWidgets.QSpinBox(self.run_options_3a) - self.cwp_end_proximity.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_end_proximity.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_end_proximity.setSuffix("") - self.cwp_end_proximity.setMaximum(20) + self.cwp_end_proximity.setMaximum(99) self.cwp_end_proximity.setObjectName("cwp_end_proximity") self._4.addWidget(self.cwp_end_proximity) self.verticalLayout_2.addLayout(self._4) @@ -2971,7 +3627,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_8.setObjectName("label_8") self._7.addWidget(self.label_8) self.cwp_substring_match = QtWidgets.QSpinBox(self.run_options_3a) - self.cwp_substring_match.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_substring_match.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_substring_match.setMaximum(100) self.cwp_substring_match.setObjectName("cwp_substring_match") self._7.addWidget(self.cwp_substring_match) @@ -2988,14 +3644,14 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_91.setObjectName("label_91") self.verticalLayout_2.addWidget(self.label_91) self.cwp_prepositions = QtWidgets.QLineEdit(self.run_options_3a) - self.cwp_prepositions.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_prepositions.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_prepositions.setObjectName("cwp_prepositions") self.verticalLayout_2.addWidget(self.cwp_prepositions) self.cwp_removewords_2 = QtWidgets.QLabel(self.run_options_3a) self.cwp_removewords_2.setObjectName("cwp_removewords_2") self.verticalLayout_2.addWidget(self.cwp_removewords_2) self.cwp_removewords = QtWidgets.QLineEdit(self.run_options_3a) - self.cwp_removewords.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_removewords.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_removewords.setObjectName("cwp_removewords") self.verticalLayout_2.addWidget(self.cwp_removewords) self.line = QtWidgets.QFrame(self.run_options_3a) @@ -3006,42 +3662,66 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cwp_synonyms_2 = QtWidgets.QLabel(self.run_options_3a) self.cwp_synonyms_2.setObjectName("cwp_synonyms_2") self.verticalLayout_2.addWidget(self.cwp_synonyms_2) - self.cwp_synonyms = QtWidgets.QLineEdit(self.run_options_3a) - self.cwp_synonyms.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_synonyms = QtWidgets.QTextEdit(self.run_options_3a) + self.cwp_synonyms.setMaximumSize(QtCore.QSize(16777215, 120)) + self.cwp_synonyms.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_synonyms.setObjectName("cwp_synonyms") self.verticalLayout_2.addWidget(self.cwp_synonyms) self.label_16 = QtWidgets.QLabel(self.run_options_3a) self.label_16.setObjectName("label_16") self.verticalLayout_2.addWidget(self.label_16) self.cwp_replacements = QtWidgets.QLineEdit(self.run_options_3a) - self.cwp_replacements.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_replacements.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_replacements.setObjectName("cwp_replacements") self.verticalLayout_2.addWidget(self.cwp_replacements) self.verticalLayout.addWidget(self.run_options_3a) - self.verticalLayout_18.addWidget(self.advanced_works) - self.groupBox_46 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_2) + self.verticalLayout_57.addWidget(self.advanced_works) + self.verticalLayout_18.addWidget(self.frame_39) + self.frame_40 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.frame_40.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_40.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_40.setObjectName("frame_40") + self.verticalLayout_58 = QtWidgets.QVBoxLayout(self.frame_40) + self.verticalLayout_58.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_58.setSpacing(0) + self.verticalLayout_58.setObjectName("verticalLayout_58") + self.label_116 = QtWidgets.QLabel(self.frame_40) + self.label_116.setStyleSheet("background-color: rgb(208, 208, 156);") + self.label_116.setObjectName("label_116") + self.verticalLayout_58.addWidget(self.label_116) + self.groupBox_46 = QtWidgets.QGroupBox(self.frame_40) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_46.setPalette(palette) - self.groupBox_46.setAutoFillBackground(True) - self.groupBox_46.setStyleSheet("") + self.groupBox_46.setAutoFillBackground(False) + self.groupBox_46.setStyleSheet("background-color: rgb(229, 229, 197);") + self.groupBox_46.setTitle("") self.groupBox_46.setObjectName("groupBox_46") self.gridLayout_8 = QtWidgets.QGridLayout(self.groupBox_46) self.gridLayout_8.setObjectName("gridLayout_8") @@ -3049,44 +3729,67 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_85.setObjectName("label_85") self.gridLayout_8.addWidget(self.label_85, 1, 0, 1, 1) self.cwp_muso_refdb = QtWidgets.QLineEdit(self.groupBox_46) - self.cwp_muso_refdb.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_muso_refdb.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_muso_refdb.setObjectName("cwp_muso_refdb") self.gridLayout_8.addWidget(self.cwp_muso_refdb, 1, 3, 1, 1) self.label_84 = QtWidgets.QLabel(self.groupBox_46) self.label_84.setObjectName("label_84") self.gridLayout_8.addWidget(self.label_84, 1, 2, 1, 1) self.cwp_muso_path = QtWidgets.QLineEdit(self.groupBox_46) - self.cwp_muso_path.setStyleSheet("background-color: rgb(203, 182, 175);") + self.cwp_muso_path.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_muso_path.setObjectName("cwp_muso_path") self.gridLayout_8.addWidget(self.cwp_muso_path, 1, 1, 1, 1) self.label_86 = QtWidgets.QLabel(self.groupBox_46) self.label_86.setObjectName("label_86") self.gridLayout_8.addWidget(self.label_86, 0, 0, 1, 2) - self.verticalLayout_18.addWidget(self.groupBox_46) - self.groupBox_6 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents_2) + self.verticalLayout_58.addWidget(self.groupBox_46) + self.verticalLayout_18.addWidget(self.frame_40) + self.frame_41 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.frame_41.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_41.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_41.setObjectName("frame_41") + self.verticalLayout_59 = QtWidgets.QVBoxLayout(self.frame_41) + self.verticalLayout_59.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_59.setSpacing(0) + self.verticalLayout_59.setObjectName("verticalLayout_59") + self.label_117 = QtWidgets.QLabel(self.frame_41) + self.label_117.setStyleSheet("background-color: rgb(208, 208, 156);") + self.label_117.setObjectName("label_117") + self.verticalLayout_59.addWidget(self.label_117) + self.groupBox_6 = QtWidgets.QGroupBox(self.frame_41) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 127)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_6.setPalette(palette) self.groupBox_6.setLayoutDirection(QtCore.Qt.LeftToRight) - self.groupBox_6.setAutoFillBackground(True) - self.groupBox_6.setStyleSheet("") + self.groupBox_6.setAutoFillBackground(False) + self.groupBox_6.setStyleSheet("background-color: rgb(229, 229, 197);") + self.groupBox_6.setTitle("") self.groupBox_6.setObjectName("groupBox_6") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_6) self.horizontalLayout_2.setObjectName("horizontalLayout_2") @@ -3100,6 +3803,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.log_debug.setObjectName("log_debug") self.horizontalLayout_2.addWidget(self.log_debug) self.groupBox_50 = QtWidgets.QGroupBox(self.groupBox_6) + self.groupBox_50.setStyleSheet("background-color: rgb(229, 229, 159);") self.groupBox_50.setObjectName("groupBox_50") self.horizontalLayout_34 = QtWidgets.QHBoxLayout(self.groupBox_50) self.horizontalLayout_34.setObjectName("horizontalLayout_34") @@ -3110,30 +3814,52 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.log_info.setObjectName("log_info") self.horizontalLayout_34.addWidget(self.log_info) self.horizontalLayout_2.addWidget(self.groupBox_50) - self.verticalLayout_18.addWidget(self.groupBox_6) - self.frame_6 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.verticalLayout_59.addWidget(self.groupBox_6) + self.verticalLayout_18.addWidget(self.frame_41) + self.frame_42 = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + self.frame_42.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_42.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_42.setObjectName("frame_42") + self.verticalLayout_60 = QtWidgets.QVBoxLayout(self.frame_42) + self.verticalLayout_60.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_60.setSpacing(0) + self.verticalLayout_60.setObjectName("verticalLayout_60") + self.label_118 = QtWidgets.QLabel(self.frame_42) + self.label_118.setStyleSheet("background-color: rgb(208, 208, 156);") + self.label_118.setObjectName("label_118") + self.verticalLayout_60.addWidget(self.label_118) + self.frame_6 = QtWidgets.QFrame(self.frame_42) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 0)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 197)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.frame_6.setPalette(palette) - self.frame_6.setAutoFillBackground(True) - self.frame_6.setStyleSheet("") + self.frame_6.setAutoFillBackground(False) + self.frame_6.setStyleSheet("background-color: rgb(229, 229, 197);") self.frame_6.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_6.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_6.setObjectName("frame_6") @@ -3141,27 +3867,36 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.verticalLayout_16.setObjectName("verticalLayout_16") self.groupBox_33 = QtWidgets.QGroupBox(self.frame_6) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_33.setPalette(palette) - self.groupBox_33.setAutoFillBackground(True) - self.groupBox_33.setStyleSheet("") + self.groupBox_33.setAutoFillBackground(False) + self.groupBox_33.setStyleSheet("background-color: rgb(229, 229, 159);") self.groupBox_33.setObjectName("groupBox_33") self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox_33) self.horizontalLayout_4.setObjectName("horizontalLayout_4") @@ -3169,6 +3904,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_41.setObjectName("label_41") self.horizontalLayout_4.addWidget(self.label_41) self.ce_version_tag = QtWidgets.QLineEdit(self.groupBox_33) + self.ce_version_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.ce_version_tag.setObjectName("ce_version_tag") self.horizontalLayout_4.addWidget(self.ce_version_tag) self.label_36 = QtWidgets.QLabel(self.groupBox_33) @@ -3177,6 +3913,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.horizontalLayout_4.addWidget(self.label_36) self.cea_options_tag = QtWidgets.QLineEdit(self.groupBox_33) self.cea_options_tag.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cea_options_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.cea_options_tag.setObjectName("cea_options_tag") self.horizontalLayout_4.addWidget(self.cea_options_tag) self.label_38 = QtWidgets.QLabel(self.groupBox_33) @@ -3185,53 +3922,59 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.horizontalLayout_4.addWidget(self.label_38) self.cwp_options_tag = QtWidgets.QLineEdit(self.groupBox_33) self.cwp_options_tag.setLayoutDirection(QtCore.Qt.RightToLeft) + self.cwp_options_tag.setStyleSheet("background-color: rgb(250, 250, 250);") self.cwp_options_tag.setObjectName("cwp_options_tag") self.horizontalLayout_4.addWidget(self.cwp_options_tag) self.verticalLayout_16.addWidget(self.groupBox_33) self.groupBox_10 = QtWidgets.QGroupBox(self.frame_6) palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) - brush = QtGui.QBrush(QtGui.QColor(170, 170, 164)) + brush = QtGui.QBrush(QtGui.QColor(229, 229, 159)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) self.groupBox_10.setPalette(palette) - self.groupBox_10.setAutoFillBackground(True) + self.groupBox_10.setAutoFillBackground(False) + self.groupBox_10.setStyleSheet("background-color: rgb(229, 229, 159);") self.groupBox_10.setObjectName("groupBox_10") self.horizontalLayout_10 = QtWidgets.QHBoxLayout(self.groupBox_10) self.horizontalLayout_10.setObjectName("horizontalLayout_10") self.cea_override = QtWidgets.QCheckBox(self.groupBox_10) self.cea_override.setObjectName("cea_override") self.horizontalLayout_10.addWidget(self.cea_override) - self.ce_tagmap_override = QtWidgets.QCheckBox(self.groupBox_10) - self.ce_tagmap_override.setEnabled(False) - self.ce_tagmap_override.setObjectName("ce_tagmap_override") - self.horizontalLayout_10.addWidget(self.ce_tagmap_override) - self.line_10 = QtWidgets.QFrame(self.groupBox_10) - self.line_10.setFrameShape(QtWidgets.QFrame.VLine) - self.line_10.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_10.setObjectName("line_10") - self.horizontalLayout_10.addWidget(self.line_10) self.cwp_override = QtWidgets.QCheckBox(self.groupBox_10) self.cwp_override.setObjectName("cwp_override") self.horizontalLayout_10.addWidget(self.cwp_override) self.ce_genres_override = QtWidgets.QCheckBox(self.groupBox_10) - self.ce_genres_override.setEnabled(False) + self.ce_genres_override.setEnabled(True) self.ce_genres_override.setObjectName("ce_genres_override") self.horizontalLayout_10.addWidget(self.ce_genres_override) + self.ce_tagmap_override = QtWidgets.QCheckBox(self.groupBox_10) + self.ce_tagmap_override.setEnabled(True) + self.ce_tagmap_override.setObjectName("ce_tagmap_override") + self.horizontalLayout_10.addWidget(self.ce_tagmap_override) self.line_11 = QtWidgets.QFrame(self.groupBox_10) self.line_11.setFrameShape(QtWidgets.QFrame.VLine) self.line_11.setFrameShadow(QtWidgets.QFrame.Sunken) @@ -3242,7 +3985,11 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.ce_options_overwrite.setObjectName("ce_options_overwrite") self.horizontalLayout_10.addWidget(self.ce_options_overwrite) self.verticalLayout_16.addWidget(self.groupBox_10) - self.verticalLayout_18.addWidget(self.frame_6) + self.label_121 = QtWidgets.QLabel(self.frame_6) + self.label_121.setObjectName("label_121") + self.verticalLayout_16.addWidget(self.label_121) + self.verticalLayout_60.addWidget(self.frame_6) + self.verticalLayout_18.addWidget(self.frame_42) self.label_83 = QtWidgets.QLabel(self.scrollAreaWidgetContents_2) self.label_83.setObjectName("label_83") self.verticalLayout_18.addWidget(self.label_83) @@ -3283,7 +4030,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.label_58.setText("") self.label_58.setObjectName("label_58") self.textBrowser_3 = QtWidgets.QTextBrowser(self.scrollAreaWidgetContents_4) - self.textBrowser_3.setGeometry(QtCore.QRect(9, 28, 641, 71)) + self.textBrowser_3.setGeometry(QtCore.QRect(9, 28, 641, 81)) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -3295,6 +4042,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.tabWidget.addTab(self.Help, "") self.vboxlayout.addWidget(self.tabWidget) self.label_4.setBuddy(self.cwp_retries) + self.label_120.setBuddy(self.cwp_retries) self.label_90.setBuddy(self.cwp_retries) self.label_6.setBuddy(self.cwp_retries) self.label_7.setBuddy(self.cwp_retries) @@ -3303,17 +4051,7 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.retranslateUi(ClassicalExtrasOptionsPage) self.tabWidget.setCurrentIndex(5) - self.cwp_titles.clicked['bool'].connect(self.groupBox_16.setDisabled) - self.cea_no_aliases.toggled['bool'].connect(self.cea_credited_overrides.toggle) - self.cea_aliases_composer.clicked['bool'].connect(self.cea_alias_overrides.setEnabled) - self.cea_aliases.clicked['bool'].connect(self.cea_alias_overrides.setEnabled) - self.cwp_works.clicked['bool'].connect(self.groupBox_16.setEnabled) - self.cwp_aliases.clicked['bool'].connect(self.groupBox_20.setEnabled) - self.cwp_extended.clicked['bool'].connect(self.groupBox_16.setEnabled) self.cea_ra_use.toggled['bool'].connect(self.groupBox_30.setEnabled) - self.cwp_no_aliases.clicked['bool'].connect(self.groupBox_20.setDisabled) - self.cea_no_aliases.clicked['bool'].connect(self.cea_alias_overrides.setDisabled) - self.use_cea.toggled['bool'].connect(self.groupBox_23.setEnabled) self.use_cea.toggled['bool'].connect(self.groupBox_29.setEnabled) self.cea_ra_replace_ta.toggled['bool'].connect(self.cea_ra_noblank_ta.setEnabled) self.use_cea.toggled['bool'].connect(self.groupBox_31.setEnabled) @@ -3321,51 +4059,58 @@ def setupUi(self, ClassicalExtrasOptionsPage): self.cea_split_lyrics.toggled['bool'].connect(self.groupBox_32.setEnabled) self.use_cwp.toggled['bool'].connect(self.use_cache.setEnabled) self.use_cea.toggled['bool'].connect(self.groupBox_5.setEnabled) - self.cea_override.toggled['bool'].connect(self.ce_tagmap_override.setEnabled) - self.toolButton_3.toggled['bool'].connect(self.cea_source_3.setEnabled) - self.toolButton_11.toggled['bool'].connect(self.cea_source_11.setEnabled) - self.toolButton_6.toggled['bool'].connect(self.cea_source_6.setEnabled) self.use_cea.toggled['bool'].connect(self.artist_tags.setEnabled) - self.toolButton_8.toggled['bool'].connect(self.cea_source_8.setEnabled) - self.toolButton_12.toggled['bool'].connect(self.cea_source_12.setEnabled) + self.cea_arrangers.toggled['bool'].connect(self.groupBox_9.setEnabled) + self.cea_arrangers.toggled['bool'].connect(self.groupBox_28.setEnabled) self.toolButton_1.toggled['bool'].connect(self.cea_source_1.setEnabled) - self.use_cea.toggled['bool'].connect(self.groupBox_11.setEnabled) self.toolButton_2.toggled['bool'].connect(self.cea_source_2.setEnabled) - self.toolButton_10.toggled['bool'].connect(self.cea_source_10.setEnabled) - self.toolButton_15.toggled['bool'].connect(self.cea_source_15.setEnabled) - self.toolButton_14.toggled['bool'].connect(self.cea_source_14.setEnabled) - self.toolButton_13.toggled['bool'].connect(self.cea_source_13.setEnabled) + self.toolButton_3.toggled['bool'].connect(self.cea_source_3.setEnabled) self.toolButton_4.toggled['bool'].connect(self.cea_source_4.setEnabled) + self.toolButton_5.toggled['bool'].connect(self.cea_source_5.setEnabled) + self.toolButton_6.toggled['bool'].connect(self.cea_source_6.setEnabled) self.toolButton_7.toggled['bool'].connect(self.cea_source_7.setEnabled) + self.toolButton_8.toggled['bool'].connect(self.cea_source_8.setEnabled) self.toolButton_9.toggled['bool'].connect(self.cea_source_9.setEnabled) - self.toolButton_5.toggled['bool'].connect(self.cea_source_5.setEnabled) + self.toolButton_10.toggled['bool'].connect(self.cea_source_10.setEnabled) + self.toolButton_11.toggled['bool'].connect(self.cea_source_11.setEnabled) + self.toolButton_12.toggled['bool'].connect(self.cea_source_12.setEnabled) + self.toolButton_13.toggled['bool'].connect(self.cea_source_13.setEnabled) + self.toolButton_14.toggled['bool'].connect(self.cea_source_14.setEnabled) + self.toolButton_15.toggled['bool'].connect(self.cea_source_15.setEnabled) self.toolButton_16.toggled['bool'].connect(self.cea_source_16.setEnabled) - self.cwp_partial.toggled['bool'].connect(self.cwp_partial_text.setEnabled) - self.cea_arrangers.toggled['bool'].connect(self.groupBox_9.setEnabled) - self.use_cwp.toggled['bool'].connect(self.Style.setEnabled) + self.use_cea.toggled['bool'].connect(self.advanced_artists.setEnabled) self.use_cwp.toggled['bool'].connect(self.advanced_works.setEnabled) + self.use_cea.toggled['bool'].connect(self.frame_23.setEnabled) + self.use_cea.toggled['bool'].connect(self.label_93.setEnabled) + self.use_cea.toggled['bool'].connect(self.label_94.setEnabled) + self.use_cea.toggled['bool'].connect(self.label_95.setEnabled) + self.use_cea.toggled['bool'].connect(self.label_96.setEnabled) + self.use_cwp.toggled['bool'].connect(self.Style.setEnabled) + self.use_cwp.toggled['bool'].connect(self.label_99.setEnabled) self.use_cwp.toggled['bool'].connect(self.groupBox_18.setEnabled) - self.cea_arrangers.toggled['bool'].connect(self.groupBox_28.setEnabled) + self.use_cwp.toggled['bool'].connect(self.label_100.setEnabled) self.use_cwp.toggled['bool'].connect(self.Tags.setEnabled) - self.use_cea.toggled['bool'].connect(self.advanced_artists.setEnabled) + self.use_cwp.toggled['bool'].connect(self.label_101.setEnabled) self.use_cwp.toggled['bool'].connect(self.groupBox_12.setEnabled) + self.use_cwp.toggled['bool'].connect(self.frame_29.setEnabled) self.use_cwp.toggled['bool'].connect(self.groupBox_17.setEnabled) + self.use_cwp.toggled['bool'].connect(self.label_103.setEnabled) + self.use_cea.toggled['bool'].connect(self.label_111.setEnabled) + self.use_cwp.toggled['bool'].connect(self.label_113.setEnabled) self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_genres.setVisible) - self.cwp_medley.toggled['bool'].connect(self.cwp_medley_text.setEnabled) + self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_genres.setChecked) self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_classical.setVisible) + self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_classical.setChecked) self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_dates.setVisible) - self.cwp_arrangements.toggled['bool'].connect(self.cwp_arrangements_text.setEnabled) - self.cwp_muso_genres.toggled['bool'].connect(self.cwp_genres_classical_main.setHidden) - self.cwp_muso_periods.toggled['bool'].connect(self.cwp_period_map.setHidden) self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_periods.setVisible) - self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_genres.setChecked) - self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_classical.setChecked) self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_dates.setChecked) - self.cwp_muso_periods.toggled['bool'].connect(self.label_87.setHidden) - self.cwp_override.toggled['bool'].connect(self.ce_genres_override.setEnabled) self.cwp_use_muso_refdb.toggled['bool'].connect(self.cwp_muso_periods.setChecked) + self.cwp_muso_genres.toggled['bool'].connect(self.cwp_genres_classical_main.setHidden) self.cwp_muso_classical.toggled['bool'].connect(self.cwp_genres_arranger_as_composer.setVisible) self.cwp_muso_dates.toggled['bool'].connect(self.cwp_periods_arranger_as_composer.setVisible) + self.cwp_muso_periods.toggled['bool'].connect(self.cwp_period_map.setHidden) + self.cwp_muso_periods.toggled['bool'].connect(self.label_87.setHidden) + self.cwp_genres_filter.toggled['bool'].connect(self.groupBox_36.setVisible) QtCore.QMetaObject.connectSlotsByName(ClassicalExtrasOptionsPage) def retranslateUi(self, ClassicalExtrasOptionsPage): @@ -3373,25 +4118,11 @@ def retranslateUi(self, ClassicalExtrasOptionsPage): self.Artists.setToolTip(_translate("ClassicalExtrasOptionsPage", "


")) self.use_cea.setToolTip(_translate("ClassicalExtrasOptionsPage", "

should be selected otherwise this section will not run

")) self.use_cea.setText(_translate("ClassicalExtrasOptionsPage", "Create extra artist metadata (MUST BE TICKED FOR THIS SECTION TO RUN)")) - self.label_79.setText(_translate("ClassicalExtrasOptionsPage", "(Note that the \"infer work-types option has moved to the \"genres\" tab)")) - self.label_22.setText(_translate("ClassicalExtrasOptionsPage", "

The next section does not change the contents of "artist" or "album artist" tags - it only affects writer (composer etc.) and peformer tags, by using as-credited/alias names from the artist data for the release.

")) + self.label_79.setText(_translate("ClassicalExtrasOptionsPage", "(Note that the \"infer work-types\" option has moved to the \"genres\" tab)")) self.label_43.setText(_translate("ClassicalExtrasOptionsPage", "

The naming style for \'artist\' tags is set in the main Picard Options->Metadata section

")) - self.groupBox_23.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Work-artists" are types such as composer, writer, arranger and lyricist who belong to the MusicBrainz Work-Artist relationship

"Performers" are types such as performer and conductor who belong to the MusicBrainz Recording-Artist relationship

")) - self.groupBox_23.setTitle(_translate("ClassicalExtrasOptionsPage", "Work-artist/performer naming options")) - self.groupBox_24.setTitle(_translate("ClassicalExtrasOptionsPage", "MusicBrainz standard names and Aliases")) - self.cea_no_aliases.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Do not use aliases (but may be replaced by as-credited name)

")) - self.cea_no_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Use MB standard names")) - self.cea_aliases.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Alias will only be available for use if the work-artist/performer is also a release artist, recording artist or track artist.

")) - self.cea_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Use alias for all work-artists/performers")) - self.cea_aliases_composer.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Only use alias (if available) for work-artists (writers, composers, arrangers, lyricists etc.)

")) - self.cea_aliases_composer.setText(_translate("ClassicalExtrasOptionsPage", "Use alias for work-artists only")) - self.groupBox_25.setTitle(_translate("ClassicalExtrasOptionsPage", "Sub-options")) - self.cea_alias_overrides.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Alias (if it exists) will over-ride as-credited

")) - self.cea_alias_overrides.setText(_translate("ClassicalExtrasOptionsPage", "Alias over-rides credited-as")) - self.cea_credited_overrides.setToolTip(_translate("ClassicalExtrasOptionsPage", "

As-credited (if it exists) will over-ride alias

")) - self.cea_credited_overrides.setText(_translate("ClassicalExtrasOptionsPage", "Credited-as over-rides MB/Alias")) - self.cea_cyrillic.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Will be based on sort names. For cyrillic script names, patronyms will be removed.

")) - self.cea_cyrillic.setText(_translate("ClassicalExtrasOptionsPage", "Fix non-Latin text in names (where possible and if not fixed by other naming options)")) + self.label_93.setText(_translate("ClassicalExtrasOptionsPage", "

Work-artist / performer naming options

")) + self.frame_23.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Work-artists" are types such as composer, writer, arranger and lyricist who belong to the MusicBrainz Work-Artist relationship

"Performers" are types such as performer and conductor who belong to the MusicBrainz Recording-Artist relationship

")) + self.label_22.setText(_translate("ClassicalExtrasOptionsPage", "

This section does not change the contents of "artist" or "album artist" tags - it only affects writer (composer etc.) and peformer tags, by using as-credited/alias names from the artist data for the release.

")) self.groupBox_7.setToolTip(_translate("ClassicalExtrasOptionsPage", "


")) self.groupBox_7.setTitle(_translate("ClassicalExtrasOptionsPage", "Credited-as options:-")) self.groupBox_26.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Select the source for \'as-credited\' names - whether these are applied depends on the sub-options choices.

")) @@ -3410,9 +4141,23 @@ def retranslateUi(self, ClassicalExtrasOptionsPage): self.groupBox_27.setTitle(_translate("ClassicalExtrasOptionsPage", "Places to use them ...")) self.cea_performer_credited.setText(_translate("ClassicalExtrasOptionsPage", "Use for performing artists")) self.cea_composer_credited.setText(_translate("ClassicalExtrasOptionsPage", "Use for work-artists")) + self.groupBox_25.setTitle(_translate("ClassicalExtrasOptionsPage", "Sub-options")) + self.cea_alias_overrides.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Alias (if it exists) will over-ride as-credited

")) + self.cea_alias_overrides.setText(_translate("ClassicalExtrasOptionsPage", "Alias over-rides credited-as")) + self.cea_credited_overrides.setToolTip(_translate("ClassicalExtrasOptionsPage", "

As-credited (if it exists) will over-ride alias

")) + self.cea_credited_overrides.setText(_translate("ClassicalExtrasOptionsPage", "Credited-as over-rides MB/Alias")) + self.cea_cyrillic.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Will be based on sort names. For cyrillic script names, patronyms will be removed.

")) + self.cea_cyrillic.setText(_translate("ClassicalExtrasOptionsPage", "Fix non-Latin text in names (where possible and if not fixed by other naming options)")) + self.groupBox_24.setTitle(_translate("ClassicalExtrasOptionsPage", "MusicBrainz standard names and Aliases")) + self.cea_no_aliases.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Do not use aliases (but may be replaced by as-credited name)

")) + self.cea_no_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Use MB standard names")) + self.cea_aliases.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Alias will only be available for use if the work-artist/performer is also a release artist, recording artist or track artist.

")) + self.cea_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Use alias for all work-artists/performers")) + self.cea_aliases_composer.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Only use alias (if available) for work-artists (writers, composers, arrangers, lyricists etc.)

")) + self.cea_aliases_composer.setText(_translate("ClassicalExtrasOptionsPage", "Use alias for work-artists only")) + self.label_94.setText(_translate("ClassicalExtrasOptionsPage", "

Recording artist options

")) self.groupBox_29.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Select recording artist options (see "What\'s this")

")) self.groupBox_29.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

In MusicBrainz, the recording artist may be different from the track artist. For classical music, the MusicBrainz guidelines state that the track artist should be the composer; however the recording artist(s) is/are usually the principal performer(s).

Classical Extras puts the recording artists into \'hidden variables\' (as a minimum) using the chosen naming convention.

There is also option to allow you to replace the track artist by the recording artist (or to merge them). The chosen action will be applied to the \'artist\', \'artists\', \'artistsort\' and \'artists_sort\' tags. Note that \'artist\' is a single-valued string whereas \'artists\' is a list and may be multi-valued. Lists are properly merged, but because the \'artist\' string may have different join-phrases etc, a merged tag may have the recording artist(s) in brackets after the track artist(s).

")) - self.groupBox_29.setTitle(_translate("ClassicalExtrasOptionsPage", "Recording artist options")) self.groupBox_34.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

In classical music (in MusicBrainz), recording artists will usually be performers whereas track artists are composers. By default, the naming convention for performers (set in the previous section) will be used (although only the as-credited name set for the recording artist will be applied). Alternatively, the naming convention for track artists can be used - which is determined by the main Picard metadat options.

")) self.groupBox_34.setTitle(_translate("ClassicalExtrasOptionsPage", "Naming convention as for ...")) self.cea_ra_trackartist.setText(_translate("ClassicalExtrasOptionsPage", "...track artist (set in Picard options)")) @@ -3422,7 +4167,7 @@ def retranslateUi(self, ClassicalExtrasOptionsPage): self.cea_ra_replace_ta.setText(_translate("ClassicalExtrasOptionsPage", "Replace track artist by recording artist")) self.cea_ra_noblank_ta.setText(_translate("ClassicalExtrasOptionsPage", "Only replace if rec. artist exists")) self.cea_ra_merge_ta.setText(_translate("ClassicalExtrasOptionsPage", "Merge track artist and recording artist")) - self.groupBox_5.setTitle(_translate("ClassicalExtrasOptionsPage", "Other artist options")) + self.label_95.setText(_translate("ClassicalExtrasOptionsPage", "

Other artist options

")) self.groupBox_9.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter text to appear in annotations. Do not use any quotation marks.

")) self.groupBox_9.setTitle(_translate("ClassicalExtrasOptionsPage", "Annotations - performers and lyricists")) self.label_44.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Annotation to include with "chorus master" in conductor tag.

")) @@ -3458,8 +4203,8 @@ def retranslateUi(self, ClassicalExtrasOptionsPage): self.label_32.setText(_translate("ClassicalExtrasOptionsPage", "Reconstructed by")) self.label_28.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Annotation for "revised by", to include in arranger tag tag

")) self.label_28.setText(_translate("ClassicalExtrasOptionsPage", "Revised by")) + self.label_96.setText(_translate("ClassicalExtrasOptionsPage", "

Lyrics

")) self.groupBox_31.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Please note that this section operates on the underlying input file tags, not the Picard-generated tags (MusicBrainz does not have lyrics)

Sometimes "lyrics" tags can contain album notes (repeated for every track in an album) as well as track notes and lyrics. This section will filter out the common text and place it in a different tag from the text which is unique to each track.

")) - self.groupBox_31.setTitle(_translate("ClassicalExtrasOptionsPage", "Lyrics")) self.cea_split_lyrics.setToolTip(_translate("ClassicalExtrasOptionsPage", "

enables this section

")) self.cea_split_lyrics.setText(_translate("ClassicalExtrasOptionsPage", "Split lyrics tag into track and album levels")) self.groupBox_32.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter a valid tag name (no spaces, punctuation or special charcaters other than underline; use lower case)

")) @@ -3470,23 +4215,202 @@ def retranslateUi(self, ClassicalExtrasOptionsPage): self.label_52.setToolTip(_translate("ClassicalExtrasOptionsPage", "

The name of the tag where notes/lyrics unique to a track should be placed

")) self.label_52.setText(_translate("ClassicalExtrasOptionsPage", "Tag for track notes / lyrics")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.Artists), _translate("ClassicalExtrasOptionsPage", "Artists")) - self.label_18.setText(_translate("ClassicalExtrasOptionsPage", "

N.B. Artist options (1st tab) need to be enabled for this section to run

")) + self.use_cwp.setToolTip(_translate("ClassicalExtrasOptionsPage", "

"Include all work levels" should be selected otherwise this section will not run.

")) + self.use_cwp.setText(_translate("ClassicalExtrasOptionsPage", "Include all work levels (MUST BE TICKED FOR THIS SECTION TO RUN)*")) + self.cwp_collections.setToolTip(_translate("ClassicalExtrasOptionsPage", "

This will include parent works where the relationship has the attribute \'part of collection\'.
PLEASE BE CONSISTENT and do not use different options on albums with the same works, or the results may be unexpected.

")) + self.cwp_collections.setText(_translate("ClassicalExtrasOptionsPage", "Include collection relationships (but not \"series\")")) + self.use_cache.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Select to use cached works. Deselect to refesh from MusicBrainz.

")) + self.use_cache.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use cache" prevents excessive look-ups of the MB database. Every look-up of a parent work needs to be performed separately (hopefully the MB database might make this easier some day). Network usage constraints by MB means that each look-up takes a minimum of 1 second. Once a release has been looked-up, the works are retained in cache, significantly reducing the time required if, say, the options are changed and the data refreshed. However, if the user edits the works in the MB database then the cache will need to be turned off temporarily for the refresh to find the new/changed works. Also some types of work (e.g. arrangements) will require a full look-up if options have been changed.

")) + self.use_cache.setText(_translate("ClassicalExtrasOptionsPage", "Use cache (if available)*")) + self.label_99.setText(_translate("ClassicalExtrasOptionsPage", "

Tagging style

")) + self.Style.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "\n" +"\n" +"

"Tagging style". This section determines how the hierarchy of works will be sourced.

\n" +"

Works source: There are 3 options for determing the principal source of the works metadata

\n" +"

"Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.

\n" +"


\n" +"

"Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).

\n" +"


\n" +"

"Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles where it is significantly different. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations.

\n" +"

Source of canonical work text. Where either of the second two options above are chosen, there is a further choice to be made:

\n" +"

"Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. E.g. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.

\n" +"

"Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.

\n" +"

Strategy for setting style: It is suggested that you start with "extended/enhanced" style and the "Consistent with lowest level work description" as the source (this is the default). If this does not give acceptable results, try switching to "Full MusicBrainz work hierarchy". If the "enhanced" details in curly brackets (from the track title) give odd results then switch the style to "canonical works" only. Any remaining oddities are then probably in the MusicBrainz data, which may require editing.

")) + self.groupBox_4.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

There are 3 options for determing the principal source of the works metadata

- "Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.

- "Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will be in the language of the release.

- "Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles where it is significantly different. The supplementary data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager).

")) + self.groupBox_4.setTitle(_translate("ClassicalExtrasOptionsPage", "Works source")) + self.cwp_titles.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.

")) + self.cwp_titles.setText(_translate("ClassicalExtrasOptionsPage", "Use only metadata from title text")) + self.cwp_works.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).

")) + self.cwp_works.setText(_translate("ClassicalExtrasOptionsPage", "Use only metadata from canonical works")) + self.cwp_extended.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles **where it is significantly different**. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations

")) + self.cwp_extended.setText(_translate("ClassicalExtrasOptionsPage", "Use canonical work metadata enhanced with title text")) + self.groupBox_16.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Where either of the second two options above are chosen, there is a further choice to be made:

- "Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.

- "Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.

")) + self.groupBox_16.setTitle(_translate("ClassicalExtrasOptionsPage", "Source of canonical work text (if applicable)")) + self.cwp_hierarchical_works.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. E.g. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.

")) + self.cwp_hierarchical_works.setText(_translate("ClassicalExtrasOptionsPage", "Full MusicBrainz work hierarchy (may be more verbose)")) + self.cwp_level0_works.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.

")) + self.cwp_level0_works.setText(_translate("ClassicalExtrasOptionsPage", "Consistent with lowest level work description (may be less verbose, but not always complete)")) + self.label_100.setText(_translate("ClassicalExtrasOptionsPage", "

Aliases (NB Use a consistent approach throughout the library otherwise duplicate works may occur - see the Readme)*

")) + self.groupBox_18.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Replace work names by aliases" will use primary aliases for the chosen locale instead of standard MusicBrainz work names. To choose the locale, use the drop-down under "translate artist names" in the main Picard Options-->Metadata page. Note that this option is not saved as a file tag since, if different choices are made for different releases, different work names may be stored and therefore cannot be grouped together in your player/library manager.

The sub-options allow either the replacement of all work names, where a primary alias exists, just the replacement of work names which are in non-Latin script, or only replace those which are flagged with user "Folksonomy" tags. The tag text needs to be included in the text box, in which case flagged works will be \'aliased\' as well as non-Latin script works, if the second sub-option is chosen. Note that the tags may either be anyone\'s tags ("Look in all tags") or the user\'s own tags. If selecting "Look in user\'s own tags only" you must be logged in to your MusicBrainz user account (in the Picard Options->General page), otherwise repeated dialogue boxes may be generated and you may need to force restart Picard.

")) + self.groupBox_19.setTitle(_translate("ClassicalExtrasOptionsPage", "Replace MB work names?")) + self.cwp_aliases.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Use primary aliases for the chosen locale instead of standard MusicBrainz work names.

")) + self.cwp_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Replace work names by aliases. Select method -->")) + self.cwp_no_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Do not replace work names")) + self.groupBox_20.setTitle(_translate("ClassicalExtrasOptionsPage", "What to replace?")) + self.cwp_aliases_all.setText(_translate("ClassicalExtrasOptionsPage", "All work names")) + self.cwp_aliases_greek.setText(_translate("ClassicalExtrasOptionsPage", "Non-latin work names")) + self.cwp_aliases_tagged.setText(_translate("ClassicalExtrasOptionsPage", "Only tagged works")) + self.groupBox_21.setTitle(_translate("ClassicalExtrasOptionsPage", "Tags (\"Folksonomy\") identifying works to be replaced by aliases")) + self.cwp_aliases_tags_all.setText(_translate("ClassicalExtrasOptionsPage", "Look in all tags")) + self.cwp_aliases_tags_user.setText(_translate("ClassicalExtrasOptionsPage", "Look in user\'s own tags only (MUST BE LOGGED IN!)")) + self.cwp_aliases_tag_text.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple tags by commas

")) + self.label_101.setText(_translate("ClassicalExtrasOptionsPage", "

Tags to create - Use commas to separate multiple tags or leave blank to omit

")) + self.Tags.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple tags by commas.

")) + self.Tags.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Tags to create" sets the names of the tags that will be created from the sources described above. All these tags will be blanked before filling as specified. Tags specified against more than one source will have later sources appended in the sequence specified, separated by separators as specified.

")) + self.groupBox_2.setTitle(_translate("ClassicalExtrasOptionsPage", "Work tags")) + self.label_40.setText(_translate("ClassicalExtrasOptionsPage", "Separator")) + self.label_11.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Some software (notably Muso) can display a 2-level work hierarchy as well as the work-movement hierarchy. This tag can be use to store the 2-level work name (a double colon :: is used to separate the levels within the tag).

")) + self.label_11.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Work - for software with 2-level capability (e.g. Muso)

")) + self.cwp_multi_work_sep.setItemText(1, _translate("ClassicalExtrasOptionsPage", "; ")) + self.cwp_multi_work_sep.setItemText(2, _translate("ClassicalExtrasOptionsPage", ": ")) + self.cwp_multi_work_sep.setItemText(3, _translate("ClassicalExtrasOptionsPage", ". ")) + self.cwp_multi_work_sep.setItemText(4, _translate("ClassicalExtrasOptionsPage", ", ")) + self.cwp_multi_work_sep.setItemText(5, _translate("ClassicalExtrasOptionsPage", "- ")) + self.label.setText(_translate("ClassicalExtrasOptionsPage", "(In this format, intermediate works will be displayed after a double colon :: )")) + self.label_15.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Software which can display a movement and work (but no higher levels) could use any tags specified here. Note that if there are multiple work levels, the intermediate levels will not be tagged. Users wanting all the information should use the tags from the previous option (but it may cause some breaks in the display if levels change) - alternatively the missing work levels can be included in a movement tag (see below).

")) + self.label_15.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Work - for software with 1-level capability (e.g. iTunes)

")) + self.cwp_single_work_sep.setItemText(1, _translate("ClassicalExtrasOptionsPage", "; ")) + self.cwp_single_work_sep.setItemText(2, _translate("ClassicalExtrasOptionsPage", ": ")) + self.cwp_single_work_sep.setItemText(3, _translate("ClassicalExtrasOptionsPage", ". ")) + self.cwp_single_work_sep.setItemText(4, _translate("ClassicalExtrasOptionsPage", ", ")) + self.cwp_single_work_sep.setItemText(5, _translate("ClassicalExtrasOptionsPage", "- ")) + self.label_2.setText(_translate("ClassicalExtrasOptionsPage", "(Intermediate works will not be displayed:- Either 1. use the 2-level format if you wish to display them, but note that this will ceate new work, or 2. include them in the movement [see below])")) + self.label_12.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This is the top-level work held in MB. This can be useful for cataloguing and searching (if the library software is capable). Note that this will always be the "canonical" MB name, not one derived from titles or the lowest level work name and that no annotations (e.g. key or work year) will be added. However, if "replace work names by aliases" has been selected and is applicable, the relevant alias will be used.

")) + self.label_12.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for top-level (canonical) work (for capable library managers)

")) + self.label_10.setText(_translate("ClassicalExtrasOptionsPage", " N/A ")) + self.groupBox.setTitle(_translate("ClassicalExtrasOptionsPage", "Movement/Part tags")) + self.label_13.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This is not necessarily the embedded movt/part number, but is the sequence number of the movement within its parent work on the current release.

")) + self.label_13.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for(computed) movement number

")) + self.cwp_movt_no_sep.setItemText(1, _translate("ClassicalExtrasOptionsPage", "; ")) + self.cwp_movt_no_sep.setItemText(2, _translate("ClassicalExtrasOptionsPage", ": ")) + self.cwp_movt_no_sep.setItemText(3, _translate("ClassicalExtrasOptionsPage", ". ")) + self.cwp_movt_no_sep.setItemText(4, _translate("ClassicalExtrasOptionsPage", ", ")) + self.cwp_movt_no_sep.setItemText(5, _translate("ClassicalExtrasOptionsPage", "- ")) + self.label_49.setText(_translate("ClassicalExtrasOptionsPage", "Use different movement tags if required...")) + self.label_47.setText(_translate("ClassicalExtrasOptionsPage", "for use with multi-level work tags")) + self.label_48.setText(_translate("ClassicalExtrasOptionsPage", "for use with1-level work tags (intermediate works will prefix movement)")) + self.label_14.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

As below, but without the movement part/number prefix (if applicable)

")) + self.label_14.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Movement - excluding embedded movt/part numbers

")) + self.label_39.setText(_translate("ClassicalExtrasOptionsPage", " N/A ")) + self.label_9.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This tag(s) will contain the full lowest-level part name extracted from the lowest-level work name, according to the chosen tagging style.

")) + self.label_9.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Movement - including embedded movt/part numbers

")) + self.label_37.setText(_translate("ClassicalExtrasOptionsPage", " N/A ")) + self.label_102.setText(_translate("ClassicalExtrasOptionsPage", "

Partial recordings, arrangements and medleys

")) + self.groupBox_12.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter text - do not use any quotation marks

")) + self.label_20.setText(_translate("ClassicalExtrasOptionsPage", "N.B. If these options are selected or deselected, quit and restart Picard before proceeding")) + self.groupBox_13.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

If this option is selected, partial recordings will be treated as a sub-part of the whole recording and will have the related text included in its name. Note that this text is at the end of the canonical name, but the latter will probably be stripped from the sub-part as it duplicates the recording work name; any title text will be appended to the whole. Note that, if "Consistent with lowest level work description" is chosen in section 2, the text may be treated as a "prefix" similar to those in the "Advanced" tab. If this eliminates other similar prefixes and has unwanted effects, then either change the desired text slightly (e.g. surround with brackets) or use the "Full MusicBrainz work hierarchy" option in section 2.

")) + self.groupBox_13.setTitle(_translate("ClassicalExtrasOptionsPage", "Partial recordings")) + self.cwp_partial.setText(_translate("ClassicalExtrasOptionsPage", "Show partial recordings as separate sub-part, labelled with ->")) + self.groupBox_14.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

If this option is selected, works which are arrangements of other works will have the latter treated in the same manner as "parent" works, except that the arrangement work name will be prefixed by the text provided.

")) + self.groupBox_14.setTitle(_translate("ClassicalExtrasOptionsPage", "Arrangements")) + self.cwp_arrangements.setText(_translate("ClassicalExtrasOptionsPage", "Show arrangements as parts of original works, labelled with ->")) + self.groupBox_15.setTitle(_translate("ClassicalExtrasOptionsPage", "Medleys")) + self.cwp_medley.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Medleys

These can occur in two ways in MusicBrainz: (a) the recording is described as a "medley of" a number of works and (b) the track is described as (more than one) "medley including a recording of" a work. In the first case, the specified text will be included in brackets after the work name, whereas in the second case, the track will be treated as a recording of multiple works and the specified text will appear in the parent work name.


")) + self.cwp_medley.setText(_translate("ClassicalExtrasOptionsPage", "Include medley list, labelled with ->")) + self.label_103.setText(_translate("ClassicalExtrasOptionsPage", "

SongKong-compatible tag usage

")) + self.groupBox_17.setToolTip(_translate("ClassicalExtrasOptionsPage", "

See "What\'s this"

")) + self.groupBox_17.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use work tags on file (no look up on MB) if Use Cache selected": This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, "Use cache" also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. **In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.**


"Write SongKong-compatible work tags" does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data).


Note that Picard and SongKong use the tag musicbrainz_workid to mean different things. If Picard has overwritten the SongKong tag (not a problem if this plugin is used) then a warning will be given and the works will be looked up on MusicBrainz. Also note that once a release is loaded, subsequent refreshes will use the cache (if option is ticked) in preference to the file tags.

")) + self.cwp_use_sk.setText(_translate("ClassicalExtrasOptionsPage", "Use work tags on file (no look up on MB) if Use Cache selected* (NOT RECOMMENDED - SEE README)")) + self.cwp_write_sk.setText(_translate("ClassicalExtrasOptionsPage", "Write SongKong-compatible work tags*")) + self.label_82.setText(_translate("ClassicalExtrasOptionsPage", "* ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.Works), _translate("ClassicalExtrasOptionsPage", "Works and parts")) + self.label_104.setText(_translate("ClassicalExtrasOptionsPage", "

Genre tags

")) + self.label_73.setText(_translate("ClassicalExtrasOptionsPage", "Name of genre tag")) + self.label_74.setText(_translate("ClassicalExtrasOptionsPage", "Name of sub-genre tag")) + self.label_107.setText(_translate("ClassicalExtrasOptionsPage", "

"Classical" genre

")) + self.cwp_genres_classical_exclude.setText(_translate("ClassicalExtrasOptionsPage", "Exclude the text \"classical\" from main genre tag even if listed above")) + self.cwp_genres_classical_selective.setText(_translate("ClassicalExtrasOptionsPage", "Make track \"classical\" only if there is a classical-specific genre (or do nothing if there is no filter)")) + self.cwp_muso_classical.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso composer list to determine if classical*")) + self.cwp_genres_classical_all.setText(_translate("ClassicalExtrasOptionsPage", "Make all tracks \"classical\"")) + self.label_64.setText(_translate("ClassicalExtrasOptionsPage", "Write a flag with text =")) + self.label_71.setText(_translate("ClassicalExtrasOptionsPage", " in the following tag if the track is classical")) + self.cwp_genres_arranger_as_composer.setText(_translate("ClassicalExtrasOptionsPage", "(Treat arrangers as for composers)")) + self.label_81.setText(_translate("ClassicalExtrasOptionsPage", "* ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS")) + self.cwp_use_muso_refdb.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso reference database (default path is set on \"advanced\" tab)*")) + self.label_108.setText(_translate("ClassicalExtrasOptionsPage", "

Instruments and keys

")) + self.groupBox_45.setTitle(_translate("ClassicalExtrasOptionsPage", "Instruments")) + self.label_66.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for instruments (will hold all instruments for a track)")) + self.groupBox_40.setTitle(_translate("ClassicalExtrasOptionsPage", "Name sources to use for instruments (select at least one, otherwise no instruments will be included in tag)")) + self.cwp_instruments_MB_names.setText(_translate("ClassicalExtrasOptionsPage", "MusicBrainz standard names")) + self.cwp_instruments_credited_names.setText(_translate("ClassicalExtrasOptionsPage", "\"Credited-as\" names")) + self.groupBox_47.setTitle(_translate("ClassicalExtrasOptionsPage", "Keys")) + self.label_72.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for key(s)")) + self.groupBox_52.setTitle(_translate("ClassicalExtrasOptionsPage", "Include key(s) in work name?")) + self.cwp_key_never_include.setText(_translate("ClassicalExtrasOptionsPage", "Never")) + self.cwp_key_contingent_include.setText(_translate("ClassicalExtrasOptionsPage", "Only if key not already mentioned in work name")) + self.cwp_key_include.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Include key(s) in work names" gives the option to include the key signature for a work in brackets after the name of the work in the metadata. Keys will be added in the appropriate levels: e.g. Dvořák\'s New World Symphony will get (E minor) at the work level, but only movements with different keys will be annotated viz. "II. Largo (D-flat major, C-Sharp minor)"

")) + self.cwp_key_include.setText(_translate("ClassicalExtrasOptionsPage", "Always")) + self.label_106.setText(_translate("ClassicalExtrasOptionsPage", "

Allowed genres (filter)

")) + self.cwp_genres_filter.setText(_translate("ClassicalExtrasOptionsPage", "Only apply genres to tags if they match pre-defined names:")) + self.groupBox_36.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Explanation of genre-matching:


Only genres matching those in the boxes will be placed in the genre or sub-genre tags.


If there is a matching genre found in the "classical main genres" or "classical sub-genres" box, then the track will be treated as being classical.

")) + self.groupBox_43.setTitle(_translate("ClassicalExtrasOptionsPage", "Classical genres (i.e. specific to classical music) - List separated by commas")) + self.label_60.setText(_translate("ClassicalExtrasOptionsPage", "Main genres:")) + self.label_75.setText(_translate("ClassicalExtrasOptionsPage", "Sub-genres:")) + self.cwp_muso_genres.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Select this to use the "classical genres" in Muso options as the "Main classical genres" here.

")) + self.cwp_muso_genres.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso classical genres*")) + self.groupBox_44.setTitle(_translate("ClassicalExtrasOptionsPage", "General genres (may be associated with classical music, but not necessarily, e.g. \"instrumental\") - List separated by commas")) + self.label_62.setText(_translate("ClassicalExtrasOptionsPage", "Main genres")) + self.label_76.setText(_translate("ClassicalExtrasOptionsPage", "Sub-genres")) + self.label_80.setText(_translate("ClassicalExtrasOptionsPage", "Genre name to use if none of the above main genres apply (leave blank if not required)")) + self.label_77.setText(_translate("ClassicalExtrasOptionsPage", "List genres, separated by commas. Only those genres listed will be included in tags.")) + self.label_78.setText(_translate("ClassicalExtrasOptionsPage", "See \"what\'s this\" for more details.")) + self.label_105.setText(_translate("ClassicalExtrasOptionsPage", "

Source of genres - Note: if "existing file tag" is selected, information from the tag "genre" and the genre tag name specified above (if different) will be used

")) + self.cwp_genres_use_file.setToolTip(_translate("ClassicalExtrasOptionsPage", "

NB: This will use the contents of the file tag with the name given above (usually \'genre\').

")) + self.cwp_genres_use_file.setText(_translate("ClassicalExtrasOptionsPage", "Existing file tag (see note above)")) + self.cwp_genres_use_folks.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This will use the folksonomy tags for works as a possible source of genres (if they match one of the lists below).


To use the folksonomy tags for releases/tracks, select the main Picard option in Options->Metadata->"Use folksonomy tags as genre". Again (unlike vanilla Picard) they will only be used by this plugin if they match one of the lists below.

")) + self.cwp_genres_use_folks.setText(_translate("ClassicalExtrasOptionsPage", "Folksonomy work tags")) + self.cwp_genres_use_worktype.setText(_translate("ClassicalExtrasOptionsPage", "Work-type")) + self.cwp_genres_infer.setText(_translate("ClassicalExtrasOptionsPage", "Infer from artist metadata")) + self.label_109.setText(_translate("ClassicalExtrasOptionsPage", "

Periods and dates

")) + self.groupBox_48.setTitle(_translate("ClassicalExtrasOptionsPage", "Work dates")) + self.groupBox_42.setTitle(_translate("ClassicalExtrasOptionsPage", "Source of work dates for above tag")) + self.cwp_workdate_source_composed.setText(_translate("ClassicalExtrasOptionsPage", "Composed date (or parent composed date)")) + self.cwp_workdate_source_published.setText(_translate("ClassicalExtrasOptionsPage", "Published date")) + self.cwp_workdate_source_premiered.setText(_translate("ClassicalExtrasOptionsPage", "Premiered date")) + self.cwp_workdate_use_first.setText(_translate("ClassicalExtrasOptionsPage", "Use first available of above (in listed order)")) + self.cwp_workdate_use_all.setText(_translate("ClassicalExtrasOptionsPage", "Include all sources")) + self.cwp_workdate_annotate.setText(_translate("ClassicalExtrasOptionsPage", "Annotate dates using source name")) + self.label_68.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for work date")) + self.cwp_workdate_include.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Includeworkdate in work names" gives the option to include the \'work year\' for a work in brackets after the name of the work in the metadata. Dates (years) will be added in the appropriate levels: e.g. Smetana\'s \'Má vlast\' will get (1874-1879) at the work level, but the movements with different dates will be annotated viz. "Vyšehrad, JB 1:112/1 (1874)". If the dates are the same, there should be no repitetion at the movement level. (Work dates will be used in preference order, i.e. composed - published - premiered, with only the first available date being shown).

")) + self.cwp_workdate_include.setText(_translate("ClassicalExtrasOptionsPage", "Include workdate in work name (in preference order listed above, with no annotation)")) + self.groupBox_49.setTitle(_translate("ClassicalExtrasOptionsPage", "Periods")) + self.label_69.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for period")) + self.cwp_muso_periods.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso map*")) + self.cwp_muso_dates.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso composer dates (if no work date) to determine period*")) + self.label_70.setText(_translate("ClassicalExtrasOptionsPage", "Period map:")) + self.label_87.setText(_translate("ClassicalExtrasOptionsPage", " (Period name, Start year, End year; Period name2, ... etc.) - periods may overlap [Do not use commas or semi-colons within period name]")) + self.cwp_periods_arranger_as_composer.setText(_translate("ClassicalExtrasOptionsPage", "(Treat arrangers as for composers)")) + self.label_119.setText(_translate("ClassicalExtrasOptionsPage", "

N.B. At least one of the first two tabs (Artists: "Create extra artist metadata", or Works and parts: "Include all work levels") must be enabled for this section to run.
(Functionality will be reduced unless both the first two tabs are enabled.)

")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.Genres), _translate("ClassicalExtrasOptionsPage", "Genres etc.")) + self.label_18.setText(_translate("ClassicalExtrasOptionsPage", "

N.B. At least one of the first two tabs (Artists: "Create extra artist metadata", or Works and parts: "Include all work levels") must be enabled for this section to run.

")) + self.label_97.setText(_translate("ClassicalExtrasOptionsPage", "

Initial tag processing

")) self.groupBox_11.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "


")) - self.groupBox_11.setTitle(_translate("ClassicalExtrasOptionsPage", "Initial tag processing")) self.artist_tags.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options->Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive.

")) self.artist_tags.setTitle(_translate("ClassicalExtrasOptionsPage", "Remove Picard-generated tags before applying subsequent actions? (NB existing LOCAL FILE tags will remain unless cleared using standard Picard options - to remove these, overwrite them in the next section)")) self.label_3.setText(_translate("ClassicalExtrasOptionsPage", "

Picard-generated tags to blank (comma-separated, case-sensitive):

")) self.cea_blank_tag.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter file tag names, separated by commas

")) self.cea_blank_tag_2.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter file tag names, separated by commas

")) self.label_19.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "


")) - self.label_19.setText(_translate("ClassicalExtrasOptionsPage", "

List existing file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if "Clear existing tags" is selected on main options):-

")) + self.label_19.setText(_translate("ClassicalExtrasOptionsPage", "

List existing file tags which will be appended to rather than over-written by tag mapping (this will keep tags even if "Clear existing tags" is selected on main options)
NB To allow appending to happen, do not also include these tags in "Preserve tags" on the main options.

")) self.cea_keep.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter file tag names, separated by commas

")) self.cea_keep.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This refers to the tags which already exist on files which have been matched to MusicBrainz in the right-hand panel, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if \'Clear existing tags\' is specified in the main Options->Tags screen) or keep them (if \'Preserve these tags...\' is specified after the \'Clear existing tags\' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below). List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if "Clear existing tags" is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular genre-related tags (see the Genres etc. options tab for more).

Note that if "Split lyrics tag" is specified (see the Artists tab), then the tag named there will be included in the \'Keep file tags\' list and does not need to be added in this section.

")) self.cea_clear_tags.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Note that the main Picard option "Clear existing tags" should be unchecked for this option to operate in preference to that Picard option. The difference is that this option will not intefere with cover art, whereas the main Picard option will remove previous cover art.

")) self.cea_clear_tags.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

If selected: the bottom pane of Picard will only show tags which have been generated from the MusicBrainz lookups plus any existing file tags which are listed above or in the main options "Preserve tags...".

This does not mean that the file tags will be removed when saving the file. For that to happen, "Clear existing tags" needs to be selected in the main options.

")) self.cea_clear_tags.setText(_translate("ClassicalExtrasOptionsPage", "Do not show any file tags that are NOT listed above AND NOT listed in the main Picard \"Preserve tags...\" option (Options->Tags), even if \"Clear existing tags\" is not selected.")) + self.label_98.setText(_translate("ClassicalExtrasOptionsPage", "

Tag map details

")) self.artist_tags_2.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter tags, separated by commas.

")) - self.artist_tags_2.setTitle(_translate("ClassicalExtrasOptionsPage", "Tag map details")) self.textBrowser.setHtml(_translate("ClassicalExtrasOptionsPage", "\n" "\n" -"

"Tagging style". This section determines how the hierarchy of works will be sourced.

\n" -"

Works source: There are 3 options for determing the principal source of the works metadata

\n" -"

"Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.

\n" -"


\n" -"

"Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).

\n" -"


\n" -"

"Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles where it is significantly different. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations.

\n" -"

Source of canonical work text. Where either of the second two options above are chosen, there is a further choice to be made:

\n" -"

"Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. E.g. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.

\n" -"

"Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.

\n" -"

Strategy for setting style: It is suggested that you start with "extended/enhanced" style and the "Consistent with lowest level work description" as the source (this is the default). If this does not give acceptable results, try switching to "Full MusicBrainz work hierarchy". If the "enhanced" details in curly brackets (from the track title) give odd results then switch the style to "canonical works" only. Any remaining oddities are then probably in the MusicBrainz data, which may require editing.

")) - self.Style.setTitle(_translate("ClassicalExtrasOptionsPage", "Tagging style")) - self.groupBox_4.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

There are 3 options for determing the principal source of the works metadata

- "Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.

- "Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will be in the language of the release.

- "Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles where it is significantly different. The supplementary data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager).

")) - self.groupBox_4.setTitle(_translate("ClassicalExtrasOptionsPage", "Works source")) - self.cwp_titles.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.

")) - self.cwp_titles.setText(_translate("ClassicalExtrasOptionsPage", "Use only metadata from title text")) - self.cwp_works.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).

")) - self.cwp_works.setText(_translate("ClassicalExtrasOptionsPage", "Use only metadata from canonical works")) - self.cwp_extended.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles **where it is significantly different**. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations

")) - self.cwp_extended.setText(_translate("ClassicalExtrasOptionsPage", "Use canonical work metadata enhanced with title text")) - self.groupBox_16.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Where either of the second two options above are chosen, there is a further choice to be made:

- "Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.

- "Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.

")) - self.groupBox_16.setTitle(_translate("ClassicalExtrasOptionsPage", "Source of canonical work text (if applicable)")) - self.cwp_hierarchical_works.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. E.g. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.

")) - self.cwp_hierarchical_works.setText(_translate("ClassicalExtrasOptionsPage", "Full MusicBrainz work hierarchy (may be more verbose)")) - self.cwp_level0_works.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.

")) - self.cwp_level0_works.setText(_translate("ClassicalExtrasOptionsPage", "Consistent with lowest level work description (may be less verbose, but not always complete)")) - self.groupBox_18.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Replace work names by aliases" will use primary aliases for the chosen locale instead of standard MusicBrainz work names. To choose the locale, use the drop-down under "translate artist names" in the main Picard Options-->Metadata page. Note that this option is not saved as a file tag since, if different choices are made for different releases, different work names may be stored and therefore cannot be grouped together in your player/library manager.

The sub-options allow either the replacement of all work names, where a primary alias exists, just the replacement of work names which are in non-Latin script, or only replace those which are flagged with user "Folksonomy" tags. The tag text needs to be included in the text box, in which case flagged works will be \'aliased\' as well as non-Latin script works, if the second sub-option is chosen. Note that the tags may either be anyone\'s tags ("Look in all tags") or the user\'s own tags. If selecting "Look in user\'s own tags only" you must be logged in to your MusicBrainz user account (in the Picard Options->General page), otherwise repeated dialogue boxes may be generated and you may need to force restart Picard.

")) - self.groupBox_18.setTitle(_translate("ClassicalExtrasOptionsPage", "Aliases (NB Use a consistent approach throughout the library otherwise duplicate works may occur - see the Readme)*")) - self.groupBox_19.setTitle(_translate("ClassicalExtrasOptionsPage", "Replace MB work names?")) - self.cwp_aliases.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Use primary aliases for the chosen locale instead of standard MusicBrainz work names.

")) - self.cwp_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Replace work names by aliases. Select method -->")) - self.cwp_no_aliases.setText(_translate("ClassicalExtrasOptionsPage", "Do not replace work names")) - self.groupBox_20.setTitle(_translate("ClassicalExtrasOptionsPage", "What to replace?")) - self.cwp_aliases_all.setText(_translate("ClassicalExtrasOptionsPage", "All work names")) - self.cwp_aliases_greek.setText(_translate("ClassicalExtrasOptionsPage", "Non-latin work names")) - self.cwp_aliases_tagged.setText(_translate("ClassicalExtrasOptionsPage", "Only tagged works")) - self.groupBox_21.setTitle(_translate("ClassicalExtrasOptionsPage", "Tags (\"Folksonomy\") identifying works to be replaced by aliases")) - self.cwp_aliases_tags_all.setText(_translate("ClassicalExtrasOptionsPage", "Look in all tags")) - self.cwp_aliases_tags_user.setText(_translate("ClassicalExtrasOptionsPage", "Look in user\'s own tags only (MUST BE LOGGED IN!)")) - self.cwp_aliases_tag_text.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple tags by commas

")) - self.Tags.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple tags by commas.

")) - self.Tags.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Tags to create" sets the names of the tags that will be created from the sources described above. All these tags will be blanked before filling as specified. Tags specified against more than one source will have later sources appended in the sequence specified, separated by separators as specified.

")) - self.Tags.setTitle(_translate("ClassicalExtrasOptionsPage", "Tags to create - Use commas to separate multiple tags or leave blank to omit")) - self.groupBox_2.setTitle(_translate("ClassicalExtrasOptionsPage", "Work tags")) - self.label_40.setText(_translate("ClassicalExtrasOptionsPage", "Separator")) - self.label_11.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Some software (notably Muso) can display a 2-level work hierarchy as well as the work-movement hierarchy. This tag can be use to store the 2-level work name (a double colon :: is used to separate the levels within the tag).

")) - self.label_11.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Work - for software with 2-level capability (e.g. Muso)

")) - self.cwp_multi_work_sep.setItemText(1, _translate("ClassicalExtrasOptionsPage", "; ")) - self.cwp_multi_work_sep.setItemText(2, _translate("ClassicalExtrasOptionsPage", ": ")) - self.cwp_multi_work_sep.setItemText(3, _translate("ClassicalExtrasOptionsPage", ". ")) - self.cwp_multi_work_sep.setItemText(4, _translate("ClassicalExtrasOptionsPage", ", ")) - self.cwp_multi_work_sep.setItemText(5, _translate("ClassicalExtrasOptionsPage", "- ")) - self.label.setText(_translate("ClassicalExtrasOptionsPage", "(In this format, intermediate works will be displayed after a double colon :: )")) - self.label_15.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Software which can display a movement and work (but no higher levels) could use any tags specified here. Note that if there are multiple work levels, the intermediate levels will not be tagged. Users wanting all the information should use the tags from the previous option (but it may cause some breaks in the display if levels change) - alternatively the missing work levels can be included in a movement tag (see below).

")) - self.label_15.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Work - for software with 1-level capability (e.g. iTunes)

")) - self.cwp_single_work_sep.setItemText(1, _translate("ClassicalExtrasOptionsPage", "; ")) - self.cwp_single_work_sep.setItemText(2, _translate("ClassicalExtrasOptionsPage", ": ")) - self.cwp_single_work_sep.setItemText(3, _translate("ClassicalExtrasOptionsPage", ". ")) - self.cwp_single_work_sep.setItemText(4, _translate("ClassicalExtrasOptionsPage", ", ")) - self.cwp_single_work_sep.setItemText(5, _translate("ClassicalExtrasOptionsPage", "- ")) - self.label_2.setText(_translate("ClassicalExtrasOptionsPage", "(Intermediate works will not be displayed:- Either 1. use the 2-level format if you wish to display them, but note that this will ceate new work, or 2. include them in the movement [see below])")) - self.label_12.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This is the top-level work held in MB. This can be useful for cataloguing and searching (if the library software is capable). Note that this will always be the "canonical" MB name, not one derived from titles or the lowest level work name and that no annotations (e.g. key or work year) will be added. However, if "replace work names by aliases" has been selected and is applicable, the relevant alias will be used.

")) - self.label_12.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for top-level (canonical) work (for capable library managers)

")) - self.label_10.setText(_translate("ClassicalExtrasOptionsPage", " N/A ")) - self.groupBox.setTitle(_translate("ClassicalExtrasOptionsPage", "Movement/Part tags")) - self.label_13.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This is not necessarily the embedded movt/part number, but is the sequence number of the movement within its parent work on the current release.

")) - self.label_13.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for(computed) movement number

")) - self.cwp_movt_no_sep.setItemText(1, _translate("ClassicalExtrasOptionsPage", "; ")) - self.cwp_movt_no_sep.setItemText(2, _translate("ClassicalExtrasOptionsPage", ": ")) - self.cwp_movt_no_sep.setItemText(3, _translate("ClassicalExtrasOptionsPage", ". ")) - self.cwp_movt_no_sep.setItemText(4, _translate("ClassicalExtrasOptionsPage", ", ")) - self.cwp_movt_no_sep.setItemText(5, _translate("ClassicalExtrasOptionsPage", "- ")) - self.label_49.setText(_translate("ClassicalExtrasOptionsPage", "Use different movement tags if required...")) - self.label_47.setText(_translate("ClassicalExtrasOptionsPage", "for use with multi-level work tags")) - self.label_48.setText(_translate("ClassicalExtrasOptionsPage", "for use with1-level work tags (intermediate works will prefix movement)")) - self.label_14.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

As below, but without the movement part/number prefix (if applicable)

")) - self.label_14.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Movement - excluding embedded movt/part numbers

")) - self.label_39.setText(_translate("ClassicalExtrasOptionsPage", " N/A ")) - self.label_9.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This tag(s) will contain the full lowest-level part name extracted from the lowest-level work name, according to the chosen tagging style.

")) - self.label_9.setText(_translate("ClassicalExtrasOptionsPage", "

Tags for Movement - including embedded movt/part numbers

")) - self.label_37.setText(_translate("ClassicalExtrasOptionsPage", " N/A ")) - self.groupBox_12.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Enter text - do not use any quotation marks

")) - self.groupBox_12.setTitle(_translate("ClassicalExtrasOptionsPage", "Partial recordings, arrangements and medleys")) - self.label_20.setText(_translate("ClassicalExtrasOptionsPage", "N.B. If these options are selected or deselected, quit and restart Picard before proceeding")) - self.groupBox_13.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

If this option is selected, partial recordings will be treated as a sub-part of the whole recording and will have the related text included in its name. Note that this text is at the end of the canonical name, but the latter will probably be stripped from the sub-part as it duplicates the recording work name; any title text will be appended to the whole. Note that, if "Consistent with lowest level work description" is chosen in section 2, the text may be treated as a "prefix" similar to those in the "Advanced" tab. If this eliminates other similar prefixes and has unwanted effects, then either change the desired text slightly (e.g. surround with brackets) or use the "Full MusicBrainz work hierarchy" option in section 2.

")) - self.groupBox_13.setTitle(_translate("ClassicalExtrasOptionsPage", "Partial recordings")) - self.cwp_partial.setText(_translate("ClassicalExtrasOptionsPage", "Show partial recordings as separate sub-part, labelled with ->")) - self.groupBox_14.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

If this option is selected, works which are arrangements of other works will have the latter treated in the same manner as "parent" works, except that the arrangement work name will be prefixed by the text provided.

")) - self.groupBox_14.setTitle(_translate("ClassicalExtrasOptionsPage", "Arrangements")) - self.cwp_arrangements.setText(_translate("ClassicalExtrasOptionsPage", "Show arrangements as parts of original works, labelled with ->")) - self.groupBox_15.setTitle(_translate("ClassicalExtrasOptionsPage", "Medleys")) - self.cwp_medley.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Medleys

These can occur in two ways in MusicBrainz: (a) the recording is described as a "medley of" a number of works and (b) the track is described as (more than one) "medley including a recording of" a work. In the first case, the specified text will be included in brackets after the work name, whereas in the second case, the track will be treated as a recording of multiple works and the specified text will appear in the parent work name.


")) - self.cwp_medley.setText(_translate("ClassicalExtrasOptionsPage", "Include medley list, labelled with ->")) - self.groupBox_17.setToolTip(_translate("ClassicalExtrasOptionsPage", "

See "What\'s this"

")) - self.groupBox_17.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Use work tags on file (no look up on MB) if Use Cache selected": This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, "Use cache" also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. **In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.**


"Write SongKong-compatible work tags" does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data).


Note that Picard and SongKong use the tag musicbrainz_workid to mean different things. If Picard has overwritten the SongKong tag (not a problem if this plugin is used) then a warning will be given and the works will be looked up on MusicBrainz. Also note that once a release is loaded, subsequent refreshes will use the cache (if option is ticked) in preference to the file tags.

")) - self.groupBox_17.setTitle(_translate("ClassicalExtrasOptionsPage", "SongKong-compatible tag usage")) - self.cwp_use_sk.setText(_translate("ClassicalExtrasOptionsPage", "Use work tags on file (no look up on MB) if Use Cache selected* (NOT RECOMMENDED - SEE README)")) - self.cwp_write_sk.setText(_translate("ClassicalExtrasOptionsPage", "Write SongKong-compatible work tags*")) - self.label_82.setText(_translate("ClassicalExtrasOptionsPage", "* ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.Works), _translate("ClassicalExtrasOptionsPage", "Works and parts")) - self.groupBox_41.setTitle(_translate("ClassicalExtrasOptionsPage", "Periods and dates")) - self.groupBox_48.setTitle(_translate("ClassicalExtrasOptionsPage", "Work dates")) - self.groupBox_42.setTitle(_translate("ClassicalExtrasOptionsPage", "Source of work dates for above tag")) - self.cwp_workdate_source_composed.setText(_translate("ClassicalExtrasOptionsPage", "Composed date (or parent composed date)")) - self.cwp_workdate_source_published.setText(_translate("ClassicalExtrasOptionsPage", "Published date")) - self.cwp_workdate_source_premiered.setText(_translate("ClassicalExtrasOptionsPage", "Premiered date")) - self.cwp_workdate_use_first.setText(_translate("ClassicalExtrasOptionsPage", "Use first available of above (in listed order)")) - self.cwp_workdate_use_all.setText(_translate("ClassicalExtrasOptionsPage", "Include all sources")) - self.cwp_workdate_annotate.setText(_translate("ClassicalExtrasOptionsPage", "Annotate dates using source name")) - self.label_68.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for work date")) - self.cwp_workdate_include.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Includeworkdate in work names" gives the option to include the \'work year\' for a work in brackets after the name of the work in the metadata. Dates (years) will be added in the appropriate levels: e.g. Smetana\'s \'Má vlast\' will get (1874-1879) at the work level, but the movements with different dates will be annotated viz. "Vyšehrad, JB 1:112/1 (1874)". If the dates are the same, there should be no repitetion at the movement level. (Work dates will be used in preference order, i.e. composed - published - premiered, with only the first available date being shown).

")) - self.cwp_workdate_include.setText(_translate("ClassicalExtrasOptionsPage", "Include workdate in work name (in preference order listed above, with no annotation)")) - self.groupBox_49.setTitle(_translate("ClassicalExtrasOptionsPage", "Periods")) - self.label_69.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for period")) - self.cwp_muso_periods.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso map*")) - self.cwp_muso_dates.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso composer dates (if no work date) to determine period*")) - self.label_70.setText(_translate("ClassicalExtrasOptionsPage", "Period map:")) - self.label_87.setText(_translate("ClassicalExtrasOptionsPage", " (Period name, Start year, End year; Period name2, ... etc.) - periods may overlap [Do not use commas or semi-colons within period name]")) - self.cwp_periods_arranger_as_composer.setText(_translate("ClassicalExtrasOptionsPage", "(Treat arrangers as for composers)")) - self.cwp_use_muso_refdb.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso reference database (default path is set on \"advanced\" tab)*")) - self.label_81.setText(_translate("ClassicalExtrasOptionsPage", "* ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS")) - self.groupBox_35.setTitle(_translate("ClassicalExtrasOptionsPage", "Genres")) - self.groupBox_38.setTitle(_translate("ClassicalExtrasOptionsPage", "\"Classical\" genre ")) - self.cwp_genres_classical_exclude.setText(_translate("ClassicalExtrasOptionsPage", "Exclude \"classical\" from main genre tag even if listed above")) - self.cwp_genres_classical_selective.setText(_translate("ClassicalExtrasOptionsPage", "Make track \"classical\" only if there is a classical-specific genre")) - self.cwp_muso_classical.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso composer list to determine if classical*")) - self.cwp_genres_classical_all.setText(_translate("ClassicalExtrasOptionsPage", "Make all tracks \"classical\"")) - self.label_64.setText(_translate("ClassicalExtrasOptionsPage", "Write a flag with text =")) - self.label_71.setText(_translate("ClassicalExtrasOptionsPage", " in the following tag if the track is classical")) - self.cwp_genres_arranger_as_composer.setText(_translate("ClassicalExtrasOptionsPage", "(Treat arrangers as for composers)")) - self.label_73.setText(_translate("ClassicalExtrasOptionsPage", "Name of genre tag")) - self.label_74.setText(_translate("ClassicalExtrasOptionsPage", "Name of sub-genre tag")) - self.groupBox_36.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Detailed explanation of genre-matching:

If none of the boxes are filled, then all genres found will be included.

If one box is filled (e.g. "classical main genres") and there is a matching genre found, then remaining genres will only be included if they match genres in that box or the related sub-genre box. So in this case if "classical sub-genres" is filled and "general sub-genres" is not, then only matching genres will be included.

If both "classical" and "general" main genre boxes are filled, then only genres matching those boxes or "classical sub-genres" will be included.

")) - self.groupBox_36.setTitle(_translate("ClassicalExtrasOptionsPage", "Allowed genres")) - self.groupBox_43.setTitle(_translate("ClassicalExtrasOptionsPage", "Classical genres (i.e. specific to classical music) - List separated by commas")) - self.label_60.setText(_translate("ClassicalExtrasOptionsPage", "Main genres:")) - self.label_75.setText(_translate("ClassicalExtrasOptionsPage", "Sub-genres:")) - self.cwp_muso_genres.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Select this to use the "classical genres" in Muso options as the "Main classical genres" here.

")) - self.cwp_muso_genres.setText(_translate("ClassicalExtrasOptionsPage", "Use Muso classical genres*")) - self.groupBox_44.setTitle(_translate("ClassicalExtrasOptionsPage", "General genres (may be associated with classical music, but not necessarily, e.g. \"instrumental\") - List separated by commas")) - self.label_62.setText(_translate("ClassicalExtrasOptionsPage", "Main genres")) - self.label_76.setText(_translate("ClassicalExtrasOptionsPage", "Sub-genres")) - self.label_80.setText(_translate("ClassicalExtrasOptionsPage", "Genre name to use if none of the above main genres apply (leave blank if not required)")) - self.label_77.setText(_translate("ClassicalExtrasOptionsPage", "List genres, separated by commas. Only those genres listed will be included in tags. If no genres are listed in a box, then any genres found will be included.")) - self.label_78.setText(_translate("ClassicalExtrasOptionsPage", "See \"what\'s this\" for more details.")) - self.groupBox_37.setTitle(_translate("ClassicalExtrasOptionsPage", "Source of genres - Note: if \"existing file tag\" is selected, information from the tag \"genre\" and the genre tag name specified above (if different) will be used")) - self.cwp_genres_use_file.setToolTip(_translate("ClassicalExtrasOptionsPage", "

NB: This will use the contents of the file tag with the name given above (usually \'genre\').

")) - self.cwp_genres_use_file.setText(_translate("ClassicalExtrasOptionsPage", "Existing file tag (see note above)")) - self.cwp_genres_use_folks.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This will use the folksonomy tags for works as a possible source of genres (if they match one of the lists below).


To use the folksonomy tags for releases/tracks, select the main Picard option in Options->Metadata->"Use folksonomy tags as genre". Again (unlike vanilla Picard) they will only be used by this plugin if they match one of the lists below.

")) - self.cwp_genres_use_folks.setText(_translate("ClassicalExtrasOptionsPage", "Folksonomy work tags")) - self.cwp_genres_use_worktype.setText(_translate("ClassicalExtrasOptionsPage", "Work-type")) - self.cwp_genres_infer.setText(_translate("ClassicalExtrasOptionsPage", "Infer from artist metadata")) - self.groupBox_39.setTitle(_translate("ClassicalExtrasOptionsPage", "Instruments and keys")) - self.groupBox_45.setTitle(_translate("ClassicalExtrasOptionsPage", "Instruments")) - self.label_66.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for instruments (will hold all instruments for a track)")) - self.groupBox_40.setTitle(_translate("ClassicalExtrasOptionsPage", "Name sources to use for instruments (select at least one, otherwise no instruments will be included in tag)")) - self.cwp_instruments_MB_names.setText(_translate("ClassicalExtrasOptionsPage", "MusicBrainz standard names")) - self.cwp_instruments_credited_names.setText(_translate("ClassicalExtrasOptionsPage", "\"Credited-as\" names")) - self.groupBox_47.setTitle(_translate("ClassicalExtrasOptionsPage", "Keys")) - self.label_72.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for key(s)")) - self.groupBox_52.setTitle(_translate("ClassicalExtrasOptionsPage", "Include key(s) in work name?")) - self.cwp_key_never_include.setText(_translate("ClassicalExtrasOptionsPage", "Never")) - self.cwp_key_contingent_include.setText(_translate("ClassicalExtrasOptionsPage", "Only if key not already mentioned in work name")) - self.cwp_key_include.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

"Include key(s) in work names" gives the option to include the key signature for a work in brackets after the name of the work in the metadata. Keys will be added in the appropriate levels: e.g. Dvořák\'s New World Symphony will get (E minor) at the work level, but only movements with different keys will be annotated viz. "II. Largo (D-flat major, C-Sharp minor)"

")) - self.cwp_key_include.setText(_translate("ClassicalExtrasOptionsPage", "Always")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.Genres), _translate("ClassicalExtrasOptionsPage", "Genres etc.")) - self.groupBox_3.setTitle(_translate("ClassicalExtrasOptionsPage", "General")) + self.label_110.setText(_translate("ClassicalExtrasOptionsPage", "

General

")) self.ce_no_run.setText(_translate("ClassicalExtrasOptionsPage", "Do not run Classical Extras for tracks where no pre-existing file is detected (warning tag will be written)")) + self.label_111.setText(_translate("ClassicalExtrasOptionsPage", "

Artists (only effective if \"Artists\" section enabled)

")) self.advanced_artists.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple names by commas. Do not use any quotation marks.

")) self.advanced_artists.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Permits the listing of strings by which ensembles of different types may be identified. This is used by the plugin to place performer details in the relevant hidden variables and thus make them available for use in the "Tag mapping" tab as sources for any required tags.

If it is important that only whole words are to be matched, be sure to include a space after the string.

")) - self.advanced_artists.setTitle(_translate("ClassicalExtrasOptionsPage", "Artists")) - self.run_options_2.setTitle(_translate("ClassicalExtrasOptionsPage", "Ensemble strings (separate names by commas)")) + self.label_112.setText(_translate("ClassicalExtrasOptionsPage", "

Ensemble strings (separate names by commas)

")) self.cea_orchestras_2.setText(_translate("ClassicalExtrasOptionsPage", "Orchestras")) self.cea_choirs_2.setText(_translate("ClassicalExtrasOptionsPage", "Choirs")) self.cea_groups_2.setText(_translate("ClassicalExtrasOptionsPage", "Groups (i.e. other ensembles such as quartets etc.)")) - self.advanced_works.setTitle(_translate("ClassicalExtrasOptionsPage", "Work levels")) + self.label_113.setText(_translate("ClassicalExtrasOptionsPage", "

Works and parts (only effective if \"Works and parts\" section enabled)

")) self.label_4.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

Sometimes MB lookups fail. Unfortunately Picard (currently) has no automatic "retry" function. The plugin will attempt to retry for the specified number of attempts. If it still fails, the hidden variable _cwp_error will be set with a message; if error logging is checked in section 4, an error message will be written to the log and the contents of _cwp_error will be written out to a special tag called "An_error_has_occurred" which should appear prominently in the bottom pane of Picard. The problem may be resolved by refreshing, otherwise there may be a problem with the MB database availability. It is unlikely to be a software problem with the plugin.

")) self.label_4.setText(_translate("ClassicalExtrasOptionsPage", "

Max number of re-tries to access works (in case of server errors)*

")) - self.groupBox_51.setTitle(_translate("ClassicalExtrasOptionsPage", "Removal of common text between parent and child works")) + self.label_120.setText(_translate("ClassicalExtrasOptionsPage", "

Allow blank part names for arrangements and part recordings if arrangement/partial label is provided

")) + self.label_114.setText(_translate("ClassicalExtrasOptionsPage", "

Removal of common text between parent and child works

")) self.label_90.setText(_translate("ClassicalExtrasOptionsPage", "

Minimum number of similar words required before eliminating. Use zero for no elimination.
(Punctuation and accents etc. will be ignored in word comparison)

")) self.label_89.setText(_translate("ClassicalExtrasOptionsPage", "NB Parent name text at the start of a work which is followed by punctuation in the work name will always be stripped regardless of this setting.")) self.run_options_3a.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This subsection contains various parameters affecting the processing of strings in titles. Because titles are free-form, not all circumstances can be anticipated. Detailed documentation of these is beyond the scope of this Readme as the effects can be quite complex and subtle and may require an understanding of the plugin code (which is of course open-source) to acsertain them. If pure canonical works are used ("Use only metadata from canonical works" and, if necessary, "Full MusicBrainz work hierarchy" on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below:

"Proximity of new words". When using extended metadata - i.e. "metadata enhanced with title text", the plugin will attempt to remove similar words between the canonical work name (in MusicBrainz) and the title before extending the canonical name. After removing such words, a rather "bitty" result may occur. To avoid this, any new words with the specified proximity will have the words between them (or up to the end) included even if they repeat words in the work name.

"Prefixes". When using "metadata from titles" or extended metadata, the structure of the works in MusicBrainz is used to infer the structure in the title text, so strings that are repeated between tracks which are part of the same MusicBrainz work will be treated as "higher level". This can lead to anomolies if, for instance, the titles are "Work name: Part 1", "Work name: Part 2", "Part" will be treated as part of the parent work name. Specifying such words in "Prefixes" will prevent this.

"Synonyms". These words will be considered equivalent when comparing work name and title text. Thus if one word appears in the work name, that and its synonym will be removed from the title in extending the metadata (subject to the proximity setting above).

"Replacements". These words/phrases will be replaced in the title text in extended metadata, regardless of the text in the work name.

")) - self.run_options_3a.setTitle(_translate("ClassicalExtrasOptionsPage", "How title metadata should be included in extended metadata (use cautiously - read documentation)")) + self.label_115.setText(_translate("ClassicalExtrasOptionsPage", "

How title metadata should be included in extended metadata (use cautiously - read documentation)
(Only applies if "Use canonical work metadata enhanced with title text" selected on "Works and parts" tab)

")) self.label_6.setText(_translate("ClassicalExtrasOptionsPage", "

Proximity of new words (to each other) to trigger in-fill with existing words (default = 2)

")) self.label_7.setText(_translate("ClassicalExtrasOptionsPage", "

Proximity of new words (to start or end) to trigger in-fill with existing words (default =1)

")) self.label_5.setText(_translate("ClassicalExtrasOptionsPage", "

Treat hyphenated words as two words for comparison purposes

")) self.label_8.setText(_translate("ClassicalExtrasOptionsPage", "

Proportion of a string to be matched to a (usually larger) string for it to be considered essentially similar (default = 66%)

")) self.cwp_substring_match.setSuffix(_translate("ClassicalExtrasOptionsPage", "%")) - self.label_92.setText(_translate("ClassicalExtrasOptionsPage", "

Prepositions and prefixes
DO NOT USE ANY COMMAS OR QUOTE MARKS (apostophes in words are acceptable)

")) - self.label_91.setText(_translate("ClassicalExtrasOptionsPage", "Prepositions: these are words that will not be regarded as providing additional information (not treated as \'new\' words) unless they precede a new word. Use lower case only, comma separated")) + self.label_92.setText(_translate("ClassicalExtrasOptionsPage", "

Prepositions/conjunctions and prefixes
DO NOT USE ANY COMMAS OR QUOTE MARKS (apostophes in words are acceptable)

")) + self.label_91.setText(_translate("ClassicalExtrasOptionsPage", "

Prepositions & conjunctions: these are words that will not be regarded as providing additional information (not treated as \'new\' words) unless they precede a new word.
Use lower case only, comma separated

")) self.cwp_removewords_2.setText(_translate("ClassicalExtrasOptionsPage", "

Prefixes to be ignored in comparison (case insensitive, comma separated)
To prevent a prefix from being ignored when extending metadata with title info, precede it with a space.
To ensure only whole words are removed, follow with a space.

")) self.cwp_removewords.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple names by commas. Do not use any quotation marks.

")) - self.cwp_synonyms_2.setText(_translate("ClassicalExtrasOptionsPage", "

Synonyms and replacements - must be written as tuples separated by forward slashes - e.g (a,b) / (c,d)

DO NOT USE COMMAS, BRACKETS or FORWARD SLASHES IN ANY PHRASE.
N.B. The matching of \'a\' is case-sensitive

As SYNONYMS \'a\' and \'b\' will be treated as similar when comparing works/parts and titles. The text in tags will be unaltered.
Both a and b must be single words with no punctuation. No word may appear in more than one synonym (case sensitive):-

")) - self.cwp_synonyms.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Separate multiple names by forward slash. Entries must be 2-tuples, e.g. (Replace_this, with_this). Do not use any quotation marks or spaces.

")) - self.label_16.setText(_translate("ClassicalExtrasOptionsPage", "

For REMOVALS/REPLACEMENTS - These will result in the \"extended\" text in tags being changed
Put the word or phrase in the first part of the tuple and leave the second blank - e.g. (I don\'t want to see this phrase, )
or put the replacement word(s) in the second part:-

")) + self.cwp_synonyms_2.setText(_translate("ClassicalExtrasOptionsPage", "

Synonyms and replacements - must be written as tuples separated by forward slashes - e.g (a,b) / (c,d,e) - a tuple may have two or more synonyms.

N.B. The matching is case-insensitive. Roman numerals will be treated as synonyms of arabic numerals in any event, so no need to enter these.

The last member of the tuple should be the canonical form (i.e. the one to which others are converted for comparison or replacement) and must be a normal string (not a regex). See readme for full details.
Unless entering a regular expression, use backslash \\ to escape any regex metacharacters, namely \\ ^ $ . | ? * + ( ) [ ] {
Also escape commas , and forward slashes /. Do not enclose strings in quote marks.

Enter SYNONYM tuples below - each item in a tuple will be treated as similar when comparing works/parts and titles. The text in tags will be unaltered.

")) + self.label_16.setText(_translate("ClassicalExtrasOptionsPage", "

Enter REMOVALS/REPLACEMENTS below - these will result in the "extended" text in tags being changed
Put the word(s), phrase(s), or regular exprerssion(s) in the first part(s) of the tuple. The replacement text (or nothing - to remove) goes in the last member of the tuple.

N.B. Replacement text will operate BEFORE synonyms are considered.

")) self.cwp_replacements.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Entries must be 2-tuples, e.g. (Replace this, with this). Separate multiple tuples by forward slash. Do not use any quotation marks. Spaces are acceptable. The first item of a tuple may be a regular expression - enclose it with double exclamation marks - e.g.(!!regex here!!, replacement text here).

")) - self.groupBox_46.setTitle(_translate("ClassicalExtrasOptionsPage", "Genres etc. (only required if Muso-specific options are used for genres/periods)")) + self.label_116.setText(_translate("ClassicalExtrasOptionsPage", "

Genres etc. (only required if Muso-specific options are used for genres/periods)

")) self.label_85.setText(_translate("ClassicalExtrasOptionsPage", "Path to Muso reference database:")) self.label_84.setText(_translate("ClassicalExtrasOptionsPage", "Name of Muso reference database")) self.label_86.setText(_translate("ClassicalExtrasOptionsPage", "

RESTART PICARD AFTER CHANGING THESE

")) + self.label_117.setText(_translate("ClassicalExtrasOptionsPage", "

Logging options*

")) self.groupBox_6.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

These options are in addition to the options chosen in Picard\'s "Help->View error/debug log" settings. They only affect messages written by this plugin. To enable debug messages to be shown, the flag needs to be set here and "Debug mode" needs to be turned on in the log. It is strongly advised to keep the "debug" and "info" flags unchecked unless debugging is required as they slow up processing significantly and may even cause Picard to crash on large releases. The "error" and "warning" flags should be left checked, unless it is required to suppress messages written out to tags (the default is to write messages to the tags 001_errors and 002_warnings).

")) - self.groupBox_6.setTitle(_translate("ClassicalExtrasOptionsPage", "Logging options*")) self.log_error.setText(_translate("ClassicalExtrasOptionsPage", "Error")) self.log_warning.setText(_translate("ClassicalExtrasOptionsPage", "Warning")) self.log_debug.setText(_translate("ClassicalExtrasOptionsPage", "Debug")) self.groupBox_50.setTitle(_translate("ClassicalExtrasOptionsPage", "Custom logging")) self.log_basic.setText(_translate("ClassicalExtrasOptionsPage", "Basic")) self.log_info.setText(_translate("ClassicalExtrasOptionsPage", "Full")) + self.label_118.setText(_translate("ClassicalExtrasOptionsPage", "

Classical Extras Special Tags

")) self.groupBox_33.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

This can be used so that the user has a record of the version of Classical Extras which generated the tags and which options were selected to achieve the resulting tags. Note that the tags will be blanked first so this will only show the last options used on a particular file. The same tag can be used for both sets of options, resulting in a multi-valued tag.

")) self.groupBox_33.setTitle(_translate("ClassicalExtrasOptionsPage", "Save plugin details and options in a tag?*")) self.label_41.setText(_translate("ClassicalExtrasOptionsPage", "Tag name for plugin version")) @@ -4148,466 +4896,26 @@ def retranslateUi(self, ClassicalExtrasOptionsPage): self.groupBox_10.setWhatsThis(_translate("ClassicalExtrasOptionsPage", "

If options have previously been saved (see above), selecting these will cause the saved options to be used in preference to the displayed options. The displayed options will not be affected and will be used if no saved options are present. The default is for no over-ride. If artist options over-ride is chosen, then tag map detail options may be included or not in the override.


The last checkbox, "Overwrite options in Options Pages", is for VERY CAREFUL USE ONLY. It will cause any options read from the saved tags (if the relevant box has been ticked) to over-write the options on the plugin Options Page UI. The intended use of this is if for some reason the user\'s preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, to prevent inadvertant use. Far better is to make a backup copy of the picard.ini file.

")) self.groupBox_10.setTitle(_translate("ClassicalExtrasOptionsPage", "Over-ride plugin options displayed in Options Pages with options from local file tags (previously saved using method in box above)?*")) self.cea_override.setText(_translate("ClassicalExtrasOptionsPage", "Artist options")) - self.ce_tagmap_override.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Will not over-ride displayed options unless artist options over-ride is also selected

")) - self.ce_tagmap_override.setText(_translate("ClassicalExtrasOptionsPage", "Tag map detail options")) self.cwp_override.setText(_translate("ClassicalExtrasOptionsPage", "Work options")) self.ce_genres_override.setText(_translate("ClassicalExtrasOptionsPage", "Genres etc. options")) + self.ce_tagmap_override.setToolTip(_translate("ClassicalExtrasOptionsPage", "

Will not over-ride displayed options unless artist options over-ride is also selected

")) + self.ce_tagmap_override.setText(_translate("ClassicalExtrasOptionsPage", "Tag mapping options")) self.ce_options_overwrite.setText(_translate("ClassicalExtrasOptionsPage", "Overwrite options in Options Pages (READ WARNINGS in Readme)")) + self.label_121.setText(_translate("ClassicalExtrasOptionsPage", "Note that the above saved options include the related \"advanced\" options on this tab as well as the options on each of the main tabs.")) self.label_83.setText(_translate("ClassicalExtrasOptionsPage", "* ASTERISKED OPTIONS ARE NOT SAVED IN FILE TAGS")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.Advanced), _translate("ClassicalExtrasOptionsPage", "Advanced")) self.textBrowser_2.setHtml(_translate("ClassicalExtrasOptionsPage", "\n" "\n" -"

General Information

\n" -"

This is the documentation for version 2.0 of "classical_extras". There may be beta versions later than this - check my github site for newer releases (make sure that the "metabrainz/2.0" branch is selected). For further help, please review the forum thread or post any new questions there. It only works with Picard version 2.0, NOT earlier versions. If you are using Picard 1.4.x, please choose the "1.0" branch on github and use the latest release there - also use the earlier forum thread.

\n" -"

This version has only been tested with FLAC and mp3 files. It does work with m4a files, but Picard does not write all m4a tags (see further notes for iTunes users at the end of the "works and parts tab" section). "Classical Extras" populates hidden variables in Picard with information from the MusicBrainz database about the recording, artists and work(s), and of any containing works, passing up through mutiple work-part levels until the top is reached. The "Options" page (Options->Options->Plugins->Classical Extras) allows the user to determine how these hidden variables are written to file tags, as well as a variety of other options.

\n" -"

This plugin is particularly designed to assist with tagging of classical music so that player or library manager software which can display multiple work levels, different artist types and custom tags can have access to these details.
It has two main components "Extra Artists" and "Work Parts" which can be used independently or together. "Work Parts" will take at least as many seconds to process as there are works to look up (owing to MB throttling) so users who only want the extra artist information and the tag-mapping feature, but not the work details, may turn it off (e.g. perhaps for \'popular\' music).

\n" -"

Hidden metadata variables produced by this plugin are (mostly) prefixed with "_cwp_" or "_cea_" depending on which component of the plugin created them. Full details of these variables are given in a later section. Tags are output depending on the choices specified by the user in the Options Page. Defaults are provided for these tags which can be added to / modified / deleted according to user requirements. If the Options Page does not provide sufficient flexibility, users familiar with scripting can write Tagger Scripts to access the hidden variables directly.

\n" -"

Updates

\n" -"

Version 2.0: Major overhaul of version 0.9.4 to achieve Picard 2.0 and Python 3.7 compatibility. All webservice calls re-written for JSON rather than XmlNode responses. Periods are written to tag in date order. Addition of sub-options for inclusion of key signature information in work names. If the MB database has circular work references (i.e a parent is a descendant of itself) then these will be trapped, ignored and reported. Numerous small refinements, especially of text comparison algorithms (e.g. option to control removal of prepositions - see advanced tab). Bug fixes.

\n" -"

For a list of previous version changes, see the end of this document.

\n" -"

Installation

\n" -"

Install the zip file in your plugins folder in the usual fashion

\n" -"

Usage

\n" -"

After installation, go to the Options Page and modify choices as required. There are 4 tabs - "Artists", "Tag mapping", Works and parts" and "Advanced". The sections below describe each of these. If the options provided do not allow sufficient flexibility for a user\'s need (hopefully unlikely!) then Tagger Scripts may be used to process the hidden variables or other tags. Alternatively, it may be possible to achieve the required result by running and saving twice (or more!) with different options each time. This is not recommended for more than a one-off - a script would be better.

\n" -"

Important:

\n" -"
  1. The plugin will not work fully unless "Use release relationships" and "Use track relationships" are enabled in Picard->Options->Metadata. The plugin will enable these options by default when starting Picard. However, it may be that the MusicBrainz database has conflicting data between track and release relationships, in which case you may wish to temporarily turn off one of these options, but it is better to fix the incorrect data using "Edit relationships" in MusicBrainz.
  2. \n" -"
  3. It is recommended only to use the plugin on one or a few release(s) at a time, particularly for initial tagging if the "Works and parts" function is being used. The plugin is not designed to do "bulk tagging" of untagged files - it may be better to use a tool such as SongKong for that and then use the plugin to enhance the results as required. However, once you have tagged files (either in Picard or another tool) such that they all have MusicBrainz IDs, you should be able to re-tag multiple releases by dragging the containing folder into Picard; this is useful to pick up changed MusicBrainz data or if you change the Classical Extras version or options (but bear in mind that the "Works and parts" function will still take at least 1 second per track.
  4. \n" -"
  5. Check for error messages before saving a release. The plugin will write out special "error message" tags which should appear prominently in the bottom Picard pane. In particular, look for "000_major_warning" and "001_errors". If you get "000_major_warning" with the message "WARNING: Classical Extras not run for this track as no file present - deselect the option on the advanced tab to run. If there is a file, then try \'Refresh\'." then do what it says (this will only occur if you have opted to not run Classical Extras for tracks where no pre-existing file is detected)!
  6. \n" -"

    Also, watch out for "002_important_warning" - "No file with matching trackid - IF THERE SHOULD BE ONE, TRY \'REFRESH\' - (unable to process any saved options, lyrics or \'keep\' tags)"; this will always occur if you load a file without MusicBrainz ids - just refresh it to pick up any existing file tags such as lyrics, if required. This will occur if you have manually matched files rather than used Picard\'s "lookup" or "scan" functions. It may also be due to Picard processing issues - more likely if the files are on a network server; if you are getting it a lot then it may be better to move the files onto local storage to do the updates.

    \n" -"
  7. If you are just changing option settings then you can usually "use cache" (see "work and parts" tab section 1) to avoid the 1-second per work delay. However, if the works data in MusicBrainz has been changed then obviously you will need to do a full look-up, so disable cache. If the work structure has been fundamentally changed (i.e. a different hierarchy of existing works) - either within the MusicBrainz database or by selecting/deselecting the "include collection relations", partial" or "arrangements" options - then you will need to quit and restart Picard to correctly pick up the new structure.
  8. \n" -"
  9. Keep a backup of your picard.ini file (AppData->Roaming->MusicBrainz in Windows) in case you erase your settings or Picard crashes and loses them for you.
\n" -"

Artists tab

\n" -"

There are five coloured sections as shown in the screen image below:

\n" -"

\n" -"
  1. "Create extra artist metadata" should be selected otherwise this section (and the tag mapping section) will not run. This is the default.
  2. \n" -"

    (Note that the option "Infer work types..." in version 0.9.1 and prior has moved to the Genres tab and changed somewhat.)

    \n" -"
  3. "Work-artist/performer naming options". This section deals primarily with the application of aliases and credited_as names to replace the MusicBrainz standard names. The first box allows you to choose whether to replace MusicBrainz standard names by aliases - either for all work-artists/performers or only work-artists (writers, composers, arrangers, lyricists etc.). The second box sets the usage of "as-credited" names: the first part of this lists all the places where as-credited names can occur (really!) and the second part allows you to apply these to performing artists and/or work-artists.
  4. \n" -"

    Please note that, in the current version of this plugin, only aliases and credited_as names which are in the "release XML node" are available (i.e. roughly those relating to the metadata shown in the release overview page in MusicBrainz). So, for example, if a recording is an arrangement of another work and that other work (but not the arrangement) has a composer linked to it, then the composer\'s alias will not be available (nor is the composer shown on the MB release overview page). In some cases (if appropriate) this can be remedied by adding the relevant link to the lowest-level work.

    \n" -"

    Note regarding aliases and credited-as names:
    In a MB release, an artist can appear in one of seven contexts. Each of these is accessible in releaseXmlNode and the track and recording contexts are also accessible in trackXmlNode.
    (They are applied in sequence - e.g. track artist credit will over-ride release artist credit)
    The seven contexts are:
    Recording: credited-as and alias (this is applied first as it is the most general - i.e. it may apply to more than one release)
    Release-group: credited-as and alias
    Release: credited-as and alias
    Release relationship: credited-as only (see note)
    Recording relationship (direct): credited-as only (see note)
    Recording relationship (via work): credited-as only (see note)
    Track: credited-as and alias
    Note: Aliases
    may be retrieved for "relationship" artists, but the retrieval is not reliable (MusicBrainz webservice issue)

    \n" -"

    N.B. if more than one release is loaded in Picard, any available alias names loaded so far will be available and used. However, as-credited names will only be used from the current release. If you do not want these names to be available then you may need to restart Picard after changing the option settings (otherwise they will still be cached).

    \n" -"

    In addition to the above, the main Picard options have an effect on how \'track artists\' (or any tags derived from them through tag-mapping) are displayed. In Options->Metadata, if "Translate artist names..." is selected then the alias will be used for the track artist (or failing that, a name based on the sort-name), rather than the \'as-credited\' name. If "Use standardized artist names" is selected then neither the alis nor the \'as-credited\' name will be used. In order to facilitate consistency, Classical Extras will save these Picard options along with its own options in specific tags (see "Advanced options" section 5).

    \n" -"

    The bottom box then (a) allows a choice as to whether aliases will over-ride as-credited names or vice versa and (b) whether if there are still some names in non-Latin script, whether these should be replaced (this will always remove middle [patronymic] names from Cyrillic-script names [but does not deal fully with other non-Latin scripts]; it is based on the sort names wherever possible).

    \n" -"

    Note that none of this processing affects the contents of the "artist or "album_artist" tags. These tags may be either work-artists or performing artists. Their contents are determined by the standard Picard options "translate artist names" and "use standardized artist names" in Options-->Metadata. If "translate name" is selected, the name will be the alias or (if no alias) the \'unsorted\' sort-name; otherwise the name will be the MusicBrainz name if "use standardized artist names" is selected or the as-credited name (if available) if it is not selected.

    \n" -"
  5. "Recording artist options". In MusicBrainz, the recording artist may be different from the track artist. For classical music, the MusicBrainz guidelines state that the track artist should be the composer; however the recording artist(s) is/are usually the principal performer(s).
    Because, in classical music (in MusicBrainz), recording artists will usually be performers whereas track artists are composers, by default, the naming convention for performers (set in the previous section) will be used (although only the as-credited name set for the recording artist will be applied). Alternatively, the naming convention for track artists can be used - which is determined by the main Picard metadata options.
  6. \n" -"

    Classical Extras puts the recording artists into \'hidden variables\' (as a minimum) using the chosen naming convention. There is also option to allow you to replace the track artist by the recording artist (or to merge them). The chosen action will be applied to the \'artist\', \'artists\', \'artistsort\' and \'artists_sort\' tags. Note that \'artist\' is a single-valued string whereas \'artists\' is a list and may be multi-valued. Lists are properly merged, but because the \'artist\' string may have different join-phrases etc, a merged tag may have the recording artist(s) in brackets after the track artist(s). Obviously, for classical music, if you use "merge" then the artist tag will have both the composer and the recording artists: this may be desirable for simple players (with no composer recognition) but otherwise may look odd.

    \n" -"

    Note that, if the original track artist is required in tag mapping (i.e. as it was before replacement/merge with recording artist), it is available through the hidden variable _cea_MB_artists.

    \n" -"

    Note also that, if @loujin\'s browser script has been used to fill the recording artist data, this will be the same as the performing artists in the Recording-Artist relationship - i.e. it may be a lengthy list rather than the principal artist for the track.

    \n" -"
  7. "Other artist options":
  8. \n" -"

    "Modify host tags and include annotations" (Previously called "Include arrangers from all work levels"). This will gather together, for example, any arranger-type information from the recording, work or parent works and place it in the "arranger" tag (\'host\' tag), with the annotation (see below) in brackets. All arranger types will also be put in a hidden variable, e.g. _cwp_orchestrators. The table below shows the artist types, host tag and hidden variable for each artist type.

    \n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"\n" -"
    \n" -"

    Artist type

    \n" -"

    Host tag

    \n" -"

    Hidden variable

    \n" -"

    writer

    \n" -"

    composer

    \n" -"

    writers

    \n" -"

    lyricist

    \n" -"

    lyricist

    \n" -"

    lyricists

    \n" -"

    revised by

    \n" -"

    arranger

    \n" -"

    revisors

    \n" -"

    translator

    \n" -"

    lyricist

    \n" -"

    translators

    \n" -"

    arranger

    \n" -"

    arranger

    \n" -"

    arrangers

    \n" -"

    reconstructed by

    \n" -"

    arranger

    \n" -"

    reconstructors

    \n" -"

    orchestrator

    \n" -"

    arranger

    \n" -"

    orchestrators

    \n" -"

    instrument arranger

    \n" -"

    arranger

    \n" -"

    arrangers (with instrument type in brackets)

    \n" -"

    vocal arranger

    \n" -"

    arranger

    \n" -"

    arrangers (with voice type in brackets)

    \n" -"

    chorus master

    \n" -"

    conductor

    \n" -"

    chorusmasters

    \n" -"

    concertmaster

    \n" -"

    performer (with annotation as a sub-key)

    \n" -"

    leaders

    \n" -"

    If you want to be more selective in what is included in host tags, then disable this option and use the tag mapping section to get the data from the hidden variables. If you want to add arrangers as composers, do so in the tag mapping section also.

    \n" -"

    (Note that Picard does not natively pick up all arrangers, but that the plugin will do so, provided the "Works and parts" section is run.)

    \n" -"

    "Name album as \'Composer Last Name(s): Album Name\'" will add the composer(s) last name(s) before the album name, if they are listed as album artists. If there is more than one composer, they will be listed in the descending order of the length of their music on the release. MusicBrainz style is to exclude the composer name unless it is actually part of the album name, but it can be useful to add it for library organisation. The default is checked.

    \n" -"

    "Do not write \'lyricist\' tag if no vocal performers". Hopefully self-evident. This applies to both the Picard \'lyricist\' tag and the related internal plugin hidden variables \'_cwp_lyricists\' etc.

    \n" -"

    Note that the plugin will search for lyricists at all work levels (bottom up), but will stop after finding the first one (unless that was just a translator).

    \n" -"

    "Do not include attributes in an instrument type" (previously just referred to the attribute \'solo\'). MusicBrainz permits the use of "solo", "guest" and "additional" as instrument attributes although, for classical music, its use should be fairly rare - usually only if explicitly stated as a "solo" on the the sleevenotes. Classical Extras provides the option to exclude these attributes (the default), but you may wish to enable them for certain releases or non-Classical / cross-over releases.

    \n" -"

    "Annotations": The chosen text will be used to annotate the artist type within the host tag (see table above for host tags), but only if "Modify host tags" is selected.

    \n" -"

    Please note that the use of the word "master" is the MusicBrainz term and is not intended to be gender-specific. Users can specify whatever text they please.

    \n" -"
  9. "Lyrics". Please note that this section operates on the underlying input file tags, not the Picard-generated tags (MusicBrainz does not have lyrics) Sometimes "lyrics" tags can contain album notes (repeated for every track in an album) as well as track notes and lyrics. This section will filter out the common text for a release and place it in a different tag from the text which is unique to each track.
\n" -"

"Split lyrics tag": enables this section.

\n" -"

"Incoming lyrics tag": The name of the lyrics file tag in the input file (normally just \'lyrics\').

\n" -"

"Tag for album notes": The name of the tag where common text should be placed.

\n" -"

"Tag for track notes": The name of the tag where notes/lyrics unique to a track should be placed.

\n" -"

Note that if the \'output\' tags are not specified, then internal \'hidden\' variables will still be available for use in the tag-mapping section (called album_notes and track_notes).

\n" -"

Tag mapping tab

\n" -"

There are two coloured sections as shown in the screen image below:

\n" -"

\n" -"

Note that the "Create extra artist metadata" option needs to be selected on the Artist tab for these sections to run.

\n" -"
  1. "Initial tag processing": This takes place before any of the detailed tag mapping in the second section.
  2. \n" -"

    "Remove Picard-generated tags before applying subsequent actions?". Any tags specified in the next two rows will be blanked before applying the tag sources described in the following section. NB this applies only to Picard-generated tags, not to other tags which might pre-exist on the file: to blank those, use the main Options->Tags page. Comma-separate the tag names within the rows and note that these names are case-sensitive.

    \n" -"

    "List existing file tags which will be appended ...": This refers to the tags which already exist on files which have been matched to MusicBrainz, not the tags generated by Picard from the MusicBrainz database. Normally, Picard cannot process these tags - either it will overwrite them (if it creates a similarly named tag), clear them (if \'Clear existing tags\' is specified in the main Options->Tags screen) or keep them (if \'Preserve these tags...\' is specified after the \'Clear existing tags\' option). Classical Extras allows a further option - for the tags to be appended to in the tag mapping section (see below) or otherwise used. List file tags which will be appended to rather than over-written by tag mapping (NB this will keep tags even if "Clear existing tags" is selected on main options). In addition, certain existing tags may be used by Classical Extras - in particular "is_classical" (which is set by SongKong to \'1\' if the track is deemed to be classical, based on an extensive database) is used to add \'classical\' to the variable "_cea_worktype", if "Infer work types" is selected in the first section of the Artists tab. If you include "is_classical" in this list then any files which have "is_classical" = 1 will be treated as being classical, regardless of genre.

    \n" -"

    Note that if "Split lyrics tag" is specified (see the Artists tab), then the tag named there will be included in the "...existing file tags..." list and does not need to be added in this section.

    \n" -"

    "Clear any previous file tags...": This operates in an almost similar way to the main Picard option (Options->Tags->"Clear existing tags"). All existing file tags will be cleared unless they are in the main Picard "Preserve tags..." option or the "...existing file tags..." list. The main differences from the basic Picard option are that (a) artwork is always preseved - i.e. this largely addresses PICARD-257 and (b) the tags that are not kept are not shown as deleted in the bottom pane of Picard; however, a warning tag is written.

    \n" -"
  3. "Tag map details". This section permits the contents of any hidden variable or tag to be written to one or more tags.
\n" -"
  • Sources: Some of the most useful sources are available from the drop-down list. Otherwise they can simply be typed in the box. Click on the "source from" button to enable entry. Some useful names are:
  • \n" -"
    • soloists : List of performers (with instruments in brackets), who are NOT ensembles or conductors, separated by semi-colons. Note they may not strictly be "soloists" in that they may be part of an ensemble.
    • \n" -"
    • soloist_names : Names of the above (i.e. no instruments).
    • \n" -"
    • vocalists / instrumentalists / other_soloists : Soloists who are vocalists, instrumentalists or not specified respectively.
    • \n" -"
    • vocalist_names / instrumentalist_names : Names of vocalists / instrumentalists (i.e. no instrument / voice).
    • \n" -"
    • ensembles : List of performers who are ensembles (with type / instruments - e.g. "orchestra" - in brackets), separated by semi-colons.
    • \n" -"
    • ensemble_names : Names of the above (i.e. no instruments).
    • \n" -"
    • album_soloists : Sub-list of soloist_names who are also album artists.
    • \n" -"
    • album_conductors : List of conductors who are also album artists.
    • \n" -"
    • album_ensembles: Sub-list of ensemble_names who are also album artists.
    • \n" -"
    • album_composers : List of composers who are also album artists.
    • \n" -"
    • album_composer_lastnames : Last names of composers of ANY track on the album who are also album artists. This is the source used to prefix the album name (when that option is selected).
    • \n" -"
    • support_performers : Sub-list of soloist_names who are NOT album artists.
    • \n" -"
    • composers : Note that, if "Fix cyrillic names" in the last section is checked, this is based on sort name, to avoid non-latin language problems (if translation is not already made via locale choices).
    • \n" -"
    • conductors : Note that, if "Fix cyrillic names" in the last section is checked, this is based on sort name, to avoid non-latin language problems (if translation is not already made via locale choices).
    • \n" -"
    • arrangers : Includes all arrangers and instrument arrangers (except orchestrators) - if option above selected - standard Picard tag omits some.
    • \n" -"
    • orchestrators : Arrangers who are orchestrators.
    • \n" -"
    • leaders : AKA concertmasters.
    • \n" -"
    • chorusmasters : as distinct from conductors (chorus masters may rehearse the choir but not conduct the performance).
    \n" -"

    Note that the Classical Extras sources for all artist types are spelled in the plural (to differentiate from the native Picard tags). Most of the names are for artist data and are sourced from hidden variables (prefixed with "_cea_" or "_cwp_"). In specifying the source, the prefix is not necessary - e.g. "arrangers" will pick up all data in _cea_arrangers and _cwp_arrangers (covering those with recording and work relationships respectively). Using the prefix will only get the specific variable.

    \n" -"

    In addition, the drop-down contains some typical combinations of multiple sources (see note on multiple sources below).

    \n" -"

    Any Picard tag names can also be typed in as sources. Any hidden variables may also be used. Any source names which are prefixed by a backslash will be treated as string constants; blanks may also be used.

    \n" -"

    It is possible to specify multiple sources. If these are separated by a comma, then each will be appended to the mapped tag(s) (if not already filled or if not "conditional"). So, for example, a source of "album_soloists, album_conductors, album_ensembles" mapped to a tag of "artist" with "conditional" ticked will fill artist (if blanked) by album_soloists, if any, otherwise album_conductors etc. Sources separated by a + will be concatenated before being used to fill the mapped tags. The concatenated result will only be applied if the contents of each of the sources to be concatenated is non-blank (note that this constraint only applies to concatenation of multiple sources). No spaces will be added on concatenation, so these have to be added explicitly by concatenating "\\ ". So, for example "ensemble_names + \\ (conducted by + conductors +\\), ensemble_names", with "Conditional" selected, will yield something like "BBC Symphony Orchestra (conducted by Walter Weller)" or just "BBC Symphony Orchestra" if there is no conductor. Do not use any commas in text strings.

    \n" -"

    Another example: to add the leader\'s name in brackets to the tag with the performing orchestra, put "\\ (leader +leaders+\\)" in the source box and the tag containing the orchestra in the tag box. If there is no leader, the text will not be appended.

    \n" -"

    The tag mapping section is not restricted to artist metadata. For example, in the default drop-down list are "work_type" which only has content if the "Infer work types" box in the first section of the Artists tab is checked, and "release" which contains the album name before prefixing with composer last names if that option was chosen.

    \n" -"
  • Tags: Enter the (comma-separated) "destination" tag names into which the sources should be written (case sensitive). Note that this will result in the source data being APPENDED in the tag - it will not overwrite the existing contents. Check "Conditional?" if the tag is only to be updated if it is previously blank (all non-empty sources in the current line will be applied). The lines will be applied in the order shown. Users should be able to achieve most requirements via a combination of blanking tags, using the right source order and "conditional" flags. For example, to overwrite a tag sourced from "composer" with "conductor", specify "conductor" first, then "composer" as conditional. Note that, for example, to demote the MB-supplied artist to only appear if no other listed choices are present, blank the artist tag and then add it as a conditional source at the end of the list.
  • \n" -"
  • "Also populate sort tags": If a sort tag is associated with the source tag then the sort names will be placed in a sort tag corresponding to the destination tag. Note that the only explicit sort tags written by Picard are for artist, albumartist and composer. Piacrd also writes hidden variables \'_artists_sort\' and \'albumartists_sort\' (note the plurals - these are the sort tags for multi-valued alternatives \'artists\' and \'_albumartists\'). To be consistent with this approach, the plugin writes hidden variables for other tags - e.g. \'_arranger_sort\'. The plugin also writes hidden sort variables for the various hidden artist variables - e.g. \'_cwp_librettists\' has a matching sort variable \'_cwp_librettists_sort\'. Therefore most artist-type sources will have a sort tag/variable associated with them and these will be placed in a destination sort tag if this option is selected - in other words, selecting this option will cause most destination tags to have associated sort tags. Furthermore, any hidden sort variables associated with tags which are not listed explicitly in the tag mapping section will also be written out as tags (i.e. even if the related tags are not included as destination tags). Note, however, that composite sources (e.g. " ensemble_names + \\; + conductors") do not have sort tags associated with them.
\n" -"

If this option is not selected, no additional sort tags will be written, but the hidden variables will still be available, so if a sort tag is required explicitly, just map the sort tag directly - e.g. map \'conductors_sort\' to \'conductor_sort\'.

\n" -"

More complex operations can be built using tagger scripts. If required, these can be set to run conditionally by setting a tag or hidden variable in this section and then testing it in the script.

\n" -"

Work and parts tab

\n" -"

There six coloured sections as shown in the screen print below:

\n" -"

\n" -"
  1. "Include all work levels" should be selected otherwise this section will not run. This is the default.
  2. \n" -"

    "Include collection relationships" (selected by default) will include parent works where the relationship has the attribute \'part of collection\'. See Discussion for the background to this. Note that only "work" entity types will be included, not "series" entities. If this option is changed, it will not take effect on releases already loaded in Picard - you will need to quit and restart. PLEASE BE CONSISTENT and do not use different options on albums with the same works, or the results may be unexpected.

    \n" -"

    "Use cache (if available)" prevents excessive look-ups of the MB database. Every look-up of a parent work needs to be performed separately (hopefully the MB database might make this easier some day). Network usage constraints by MB means that each look-up takes a minimum of 1 second. Once a release has been looked-up, the works are retained in cache, significantly reducing the time required if, say, the options are changed and the data refreshed. However, if the user edits the works in the MB database then the cache will need to be turned off temporarily for the refresh to find the new/changed works. Also some types of work (e.g. arrangements) will require a full look-up if options have been changed. Do not leave this option turned off as it will make the plugin slower and may cause problems. This option will always be set on when Picard is started, regardless of how it was left when it was last closed.

    \n" -"
  3. "Tagging style". This section determines how the hierarchy of works will be sourced.
  4. \n" -"
    • Works source: There are 3 options for determing the principal source of the works metadata
    • \n" -"
      • "Use only metadata from title text". The plugin will attempt to extract the hierarchy of works from the track title by looking for repetitions and patterns. If the title does not contain all the work names in the hierarchy then obviously this will limit what can be provided.
      • \n" -"
      • "Use only metadata from canonical works". The hierarchy in the MB database will be used. Assuming the work is correctly entered in MB, this should provide all the data. However the text may differ from the track titles and will be the same for all recordings. It may also be in the language of the composer whereas the titles will probably be in the language of the release. (This language issue can also be addressed by using aliases - see below).
      • \n" -"
      • "Use canonical work metadata enhanced with title text". This supplements the canonical data with text from the titles where it is significantly different. The supplementary title data will be in curly brackets. This is clearly the most complete metadata style of the three but may lead to long descriptions. It is particularly useful for providing translations - see image below for an example (using the Muso library manager).
      \n" -"

      \n" -"
    • Source of canonical work text. Where either of the second two options above are chosen, there is a further choice to be made:
    \n" -"
    • "Full MusicBrainz work hierarchy". The names of each level of work are used to populate the relevant tags. E.g. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast, JB 1:112", not "Má vlast". So, while accurate, this option might be more verbose.
    • \n" -"
    • "Consistent with lowest level work description". The names of the level 0 work are used to populate the relevant tags. I.e. if "Má vlast: I. Vyšehrad, JB 1:112/1" (level 0) is part of "Má vlast, JB 1:112" (level 1) then the parent work will be tagged as "Má vlast", not "Má vlast, JB 1:112". This frequently looks better, but not always, particularly if the level 0 work name does not contain all the parent work detail. If the full structure is not implicit in the level 0 name then a warning will be logged and written to the "warning" tag.
    \n" -"

    Strategy for setting style: It is suggested that you start with "extended/enhanced" style and the "Consistent with lowest level work description" as the source (this is the default). If this does not give acceptable results, try switching to "Full MusicBrainz work hierarchy". If the "enhanced" details in curly brackets (from the track title) give odd results then switch the style to "canonical works" only. Any remaining oddities are then probably in the MusicBrainz data, which may require editing.

    \n" -"
  5. "Aliases"
  6. \n" -"

    "Replace work names by aliases" will use primary aliases for the chosen locale instead of standard MusicBrainz work names. To choose the locale, use the drop-down under "translate artist names" in the main Picard Options-->Metadata page. Note that this option is not saved as a file tag since, if different choices are made for different releases, different work names may be stored and therefore cannot be grouped together in your player/library manager. The sub-options then allow either the replacement of all work names, where a primary alias exists, just the replacement of work names which are in non-Latin script, or only replace those which are flagged with user "Folksonomy" tags. The tag text needs to be included in the text box, in which case flagged works will be \'aliased\' as well as non-Latin script works, if the second sub-option is chosen. Note that the tags may either be anyone\'s tags ("Look in all tags") or the user\'s own tags. If selecting "Look in user\'s own tags only" you must be logged in to your MusicBrainz user account (in the Picard Options->General page), otherwise repeated dialogue boxes may be generated and you may need to force restart Picard.

    \n" -"
  7. "Tags to create" sets the names of the tags that will be created from the sources described above. All these tags will be blanked before filling as specified. Tags specified against more than one source will have later sources appended in the sequence specified, separated by separators as specified.
  8. \n" -"
    • Work tags:
    • \n" -"
      • "Tags for Work - for software with 2-level capability". Some software (notably Muso) can display a 2-level work hierarchy as well as the work-movement hierarchy. This tag can be use to store the 2-level work name (a double colon :: is used to separate the levels within the tag).
      • \n" -"
      • "Tags for Work - for software with 1-level capability". Software which can display a movement and work (but no higher levels) could use any tags specified here. Note that if there are multiple work levels, the intermediate levels will not be tagged. Users wanting all the information should use the tags from the previous option (but it may cause some breaks in the display if levels change) - alternatively the missing work levels can be included in a movement tag (see below).
      • \n" -"
      • "Tags for top-level (canonical) work". This is the top-level work held in MB. This can be useful for cataloguing and searching (if the library software is capable).
      \n" -"
    • Movement/Part tags: (a) "Tags for(computed) movement number". This is not necessarily the embedded movt/part number, but is the sequence number of the movement within its parent work on the current release.
      (b) "Tags for Movement - excluding embedded movt/part numbers". As below, but without the movement part/number prefix (if applicable)
      (c) "Tags for Movement - including embedded movt/part numbers". This tag(s) will contain the full lowest-level part name extracted from the lowest-level work name, according to the chosen tagging style.
      For options (b) and (c), the tags can either be filled "for use with multi-level work tags" or "for use with 1-level work tags (intermediate works will prefix movement)" - or different tags for each column. The latter option will include any intermediate work levels which are missing from a single-level work tag. Use different tag names for these, from the multi-level version, otherwise both versions will be appended, creating a multi-valued tag (a warning will be given).
      Note that if a tag is included in (a) and either of (b) or (c), the movement number will be prepended at the beginning of the tag, followed by the selected separator.
    \n" -"

    Strategy for setting tags: It is suggested that initially you just use the multi-level work tag and related movement tags, even if your software only has a single-level work capability. This may result in work names being repeated in work headings, but may look better than the alternative of having work names repeated in movement names. This is the default.

    \n" -"

    If this does not look good you can then compare it with the alternative approach and change as required for specific releases. If your software does not have any "work" capability, then you can still get the full work details by, for example, specifying "title" as both a work and a movement tag.

    \n" -"
  9. "Partial recordings, arrangements and medleys" gives various options where recordings are not just simply of a named complete work.
  10. \n" -"
    • Partial recordings: If this option is selected, partial recordings will be treated as a sub-part of the whole recording and will have the related text (in the adjacent box) included in its name. Note that this text is at the end of the canonical name, but the latter will probably be stripped from the sub-part as it duplicates the recording work name; any title text will be appended to the whole. Note that, if "Consistent with lowest level work description" is chosen in section 2, the text may be treated as a "prefix" similar to those in the "Advanced" tab. If this eliminates other similar prefixes and has unwanted effects, then either change the desired text slightly (e.g. surround with brackets) or use the "Full MusicBrainz work hierarchy" option in section 2. Note that similar text between the partial work and its \'parent\' will be removed which will frequently result in no text other than the specified \'partial text\', unless extended metadata is used resulting in appended text in {}.
    • \n" -"
    • Arrangements: If this option is selected, works which are arrangements of other works will have the latter treated in the same manner as "parent" works, except that the arrangement work name will be prefixed by the text provided. Note that similar text between the arranged work and its parent will be removed unless this results in no text, in which case a stricter comparison (as for the derivation of \'part\' names from works) will be used.
    \n" -"

    Important note: If the Partial or Arrangement options are changed (i.e. selected/deselected) then quit and restart Picard as the work structure is fundamentally different. If the related text (only) is changed then the release can simply be refreshed.

    \n" -"
    • Medleys These can occur in two ways in MusicBrainz: (a) the recording is described as a "medley of" a number of works and (b) the track is described as (more than one) "medley including a recording of" a work. See Homecoming for examples of both (tracks 8, 9 and 11). In the first case, the specified text will be included in brackets after the work name, whereas in the second case, the track will be treated as a recording of multiple works and the specified text will appear in the parent work name.
    \n" -"
  11. "SongKong-compatible tag usage".
\n" -"

"Use work tags on file (no look up on MB) if Use Cache selected": This will enable the existing work tags on the file to be used in preference to looking up on MusicBrainz, if those tags are SongKong-compatible (which should be the case if SongKong has been used or if the SongKong tags have been previously written by this plugin). If present, this can speed up processing considerably, but obviously any new data on MusicBrainz will be missed. For the option to operate, "Use cache" also needs to be selected. Although faster, some of the subtleties of a full look-up will be missed - for example, parent works which are arrangements will not be highlighted as such, some arrangers or composers of original works may be omitted and some medley information may be missed. In general, therefore, the use of this option will result in poorer metadata than allowing the full database look-up to run. It is not recommended unless speed is more important than quality.

\n" -"

"Write SongKong-compatible work tags" does what it says. These can then be used by the previous option, if the release is subsequently reloaded into Picard, to speed things up (assuming the reload was not to pick up new work data).

\n" -"

The default for both these options is unchecked.

\n" -"

Note that Picard and SongKong use the tag musicbrainz_workid to mean different things. If Picard has overwritten the SongKong tag (not a problem if this plugin is used) then a warning will be given and the works will be looked up on MusicBrainz. Also note that once a release is loaded, subsequent refreshes will use the cache (if option is ticked) in preference to the file tags.

\n" -"

Note for iTunes users: iTunes and Picard do not work well together. iTunes can display work and movement for m4a(mp4) files, but Picard does not write the movement tag. To work round this, write the movement to the "subtitle" tag assuming that is not otherwise used, and use a simple Mp3tag action to convert it to MOVEMENTNAME before importing to iTunes. If you are writing to a FLAC file which will subsequently be converted to m4a then different tag names may be required; e.g. using dBpoweramp, write the movement to "movement name". In both cases use "work" for the work. To store the top_work, use "grouping" if writing directly to m4a, but "style" if writing to FLAC followed by dBpoweramp conversion. You can put multiple tags into the boxes described above so that your options are multi-purpose. N.B. if work tags are specified and the work has at least one level (i.e. at least work: movement), then the tag "show work movement" will be set to 1. This is used by iTunes to trigger the hierarchical display and should work both directly with m4a files and indirectly via files which are subsequently converted.

\n" -"

Genres etc. tab

\n" -"

This section is dependent on both the artists and workparts sections. If either of those sections are not run then this section will not operate correctly. At the very top of the tab is a checkbox "Use Muso reference database...". For Muso users, selecting this enables you to use reference data for genres, composers and periods which have been entered in Muso\'s "Options->Classical Music" section. Regardless as to whether this is selected, there are then three main coloured sections, each with a number of subsections. The details in each section differ depending on whether the "Muso" option is selected. The screen print below shows the options assuming it is not selected (differences occurring when "Muso" is selected are discussed later):

\n" -"

\n" -"
  1. "Genres". Two separate tags may be used to store genre information, a main genre tage (usually just "genre") and a sub-genre tag. These need to be specified at the top of the section. If either is left blank then the related processing will not run.
  2. \n" -"
    • Source of genres Any or all of four sources may be selected. In each case, any values found are treated as "candidate genres" - they will only be applied to the specified genre and sub-genre tags in accordance with the criteria in the "allowed genres" section (see below).
    • \n" -"

      (a) "Existing file tag". The contents of the existing file tag (as specified above - main genre tag only) will be included as candidate genres.

      \n" -"

      (b) "Folksonomy work tags". This will use the folksonomy tags for works (including parent works) as a possible source of genres. To use the folksonomy tags for releases/tracks, select the main Picard option in Options->Metadata->"Use folksonomy tags as genre". Again (unlike vanilla Picard) these are candidate genres, subject to matching allowed genres.

      \n" -"

      (c) "Work-type". The work-type attribute of works or parent works will be used.

      \n" -"

      (d) "Infer from artist metadata". This option was on the artist tab in version 0.9.1 and prior. Owing to the additional genre processing now available, the operation of this option is slightly restricted compared to the earlier versions. It attempts to create candidate genres based on information in the artist-related tags. Values provided are: Orchestral, Concerto, Choral, Opera, Duet, ,Trio, Quartet, Chamber music, Aria (\'classical values\') and Vocal, Song, Instrumental (\'generic values\'). If the track is a recorded work and the track artist is the composer (i.e. MusicBrainz \'classical style\'), the candidate genre values will also include "Classical". The \'classical values\' will only be included as candidate genres if the track is deemed to be \'classical\' by some part of the genre processing section.

      \n" -"
    • Allowed genres Four boxes are provided for lists of genres which are "allowed" to appear in the specified tags. Each list should be comma-separated (and no commas in any genre name). Candidate genres matching those in a "main genre" box will be added to the specified main genre tag. Similarly for sub-genres. If a candidate genre matches a \'classical genre\' (in one of the top two boxes), then the track will be deemed to be "Classical" (see next part for more details).
    \n" -"

    If none of the boxes are filled, then all genres found will be included.

    \n" -"

    If one box is filled (e.g. "classical main genres") and there is a matching genre found, then remaining genres will only be included if they match genres in that box or the related sub-genre box. So in this case if "classical sub-genres" is filled and "general sub-genres" is not, then only matching genres will be included.

    \n" -"

    If both "classical" and "general" main genre boxes are filled, then only genres matching those boxes or "classical sub-genres" will be included.

    \n" -"

    You may also enter a genre name to be used if no matching main genre is found (otherwise the tag will be blank).

    \n" -"
    • "Classical" genre Normally (i.e. the default) a work will only be deemed to be \'classical\' if it is inferred from the MusicBrainz style (see "source of genres") or if a candidate genre matches a "Classical" genre or sub-genre list. However, you may select that all tracks are \'classical\' regardless. There is also an option to exclude the word "Classical" from any genre tag, but still treat the work as classical. If a work is deemed to be classical, a tag may be written with a specified value as set out in the last two boxes of this section. For example, to be consistent with SonKong/Jaikoz, you could set "is_classical" to "1".
    \n" -"
  3. "Instruments and keys".
  4. \n" -"
    • Instruments Specify the tag name you wish instrument names to appear in. Instruments will be sourced from performer relationships. Instrument names may either be the standard MusicBrainz names or the "credited as" names in the performer relationship, or both. Vocal types are treated similarly to instruments. (Note that, in v0.9.1 and prior, instruments were written to the same tag as inferred genres. If you wish to continue this, then you may use the same tag name here as for the genre tag.)
    • \n" -"
    • Keys Specify the tag name in which you wish the key signatures of works to appear. Keys will be obtained from all work levels (assuming these have been looked up): for example, Dvořák\'s Largo From the New World will be shown as D♭ major, C# minor (the main keys of the movement) and E minor (the home key of the overall work).
    \n" -"

    "Include key(s) in work names" gives the option to include the key signature for a work in brackets after the name of the work in the metadata. Keys will be added in the appropriate levels: e.g. Dvořák\'s New World Symphony will get (E minor) at the work level, but only movements with different keys will be annotated viz. "II. Largo (D-flat major, C-Sharp minor)". The default sub-option is to only add these details if the key signature is missing from the work title (other sub-options are to never or to always include the information).

    \n" -"
  5. "Periods and dates".
\n" -"
  • Work dates Specify the tag name to hold work dates. Work dates will be given as a "year" value only, e.g. "1808" or a range: "1808-1810". The sources of these dates is specified in the next part. Only work dates for the lowest-level work will be used - i.e. if the movement has a composed date(s), this will be used, otherwise the the dates from the parent work will be used (if available).
\n" -"

"Source of work dates". Select which sources to use - from composed, published and premiered, then decide whether to use them in preferential order (e.g. if "composed date" exists, then the others will not be used) or to show them all.

\n" -"

"Include workdate in work name ..." operates analogously to "Include key(s) in work names" described above. (Work dates will be used in preference order, i.e. composed - published - premiered, with only the first available date being shown).

\n" -"
  • Periods This section will use work dates, where available, to determine the "classical period" to which it belongs, by means of a "period map" (Muso users can also use composer dates - see below).
\n" -"

Specify the tag name to hold the period data. The period map should then be entered in the format "Period name, Start_year, End_year; Period name2, Start_year, End_year;" etc. Periods may overlap. Do not use commas or semi-colons within period names. Start and end years must be integers.

\n" -"

Genres etc. tab - Muso-specific processing

\n" -"

Users of Muso have additional capabilities, illustrated in the following screen, which appear when the option "Use Muso reference database ..." is selected at the top of the tab.

\n" -"

\n" -"

For these options to work, the path/name of the Muso reference database needs to be specified on the advanced tab. The default path is "C:\\Users\\Public\\Music\\muso\\database" and the default filename is "Reference.xml". The additional options are as follows.

\n" -"
  1. "Use Muso classical genres". If this is selected, the box for classical main genres is eliminated and the genre list from Muso\'s "Tools->Options->Classical Music->Classical Music Genres" is used instead.
  2. \n" -"
  3. "Use Muso composer list to determine if classical". If the composer name is in Muso\'s list "Tools->Options->Classical Music->Composer Roster", then the work will be deemed to be classical. If this option is selected, a further option appears to "Treat arrangers as for composers" - if selected then arrangers will also be looked up in the roster.
  4. \n" -"
  5. "Use Muso composer dates (if no work date) to determine period". The birth date + 20 -> death dates of Muso\'s composer roster will be used to assign periods if no work date is available. If this option is selected, a further option appears to "Treat arrangers as for composers" - if selected then arrangers\' working lives will also be used to determine periods.
  6. \n" -"

    (This might be replaced / supplemented by MusicBrainz in the future, but would involve another 1-second lookup per composer).

    \n" -"
  7. "Use Muso map". Replace the period map with the one in Muso at "Tools->Options->Classical Music->Classical Music Periods"
\n" -"

Note that non-Muso users may also use this functionality, if they wish, by manually creating a reference xml file with the relevant tags, e.g.:

\n" -"

<ClassicalGenre>

\n" -"

<Name>Cantata</Name>

\n" -"

</ClassicalGenre>

\n" -"

<Composer>

\n" -"

<Name>Max REGER</Name>

\n" -"

<Birth>1873</Birth>

\n" -"

<Death>1916</Death>

\n" -"

</Composer>

\n" -"

<ClassicalPeriod>

\n" -"

<Name>Early Romantic</Name>

\n" -"

<Start_x0020_Date>1800</Start_x0020_Date>

\n" -"

<End_x0020_Date>1850</End_x0020_Date>

\n" -"

</ClassicalPeriod>

\n" -"

Advanced tab

\n" -"

Hopefully, this tab should not be much used. In any case, it should not need to be changed frequently. There are seven sections as shown in the sceeen print below:

\n" -"

\n" -"
  1. "General". There is only one checkbox - "Do not run Classical Extras for tracks where no pre-existing file is detected (warning tag will be written)". This option will disable Classical Extras processing if no file is present; this means (for example) that single discs from box sets can be loaded without incurring the additional processing overhead (work look-ups etc.) for all the other discs. Also if a compilation album is loaded, where the tracks are on multiple releases, the plugin will only process the release tracks which match. If a file is present but it does not yet have a MusicBrainz trackid tag, then it will initally be treated in the same way as a non-existent file; however, after the initial loading it will (if matched by Picard) be given a MB trackid and "refreshing" the release will result in any such tracks being processed by Classical Extras, while the unmatched tracks are left untouched.
  2. \n" -"
  3. "Artists". This has only one subsection - "Ensemble strings" - which permits the listing of strings by which ensembles of different types may be identified. This is used by the plugin to place performer details in the relevant hidden variables and thus make them available for use in the "Tag mapping" tab as sources for any required tags. If it is important that only whole words are to be matched, be sure to include a space after the string.
  4. \n" -"
  5. "Work levels". This section has parameters applicable to the "works and parts" functions.
  6. \n" -"
    • Max number of re-tries to access works (in case of server errors). Sometimes MB lookups fail. Unfortunately Picard (currently) has no automatic "retry" function. The plugin will attempt to retry for the specified number of attempts. If it still fails, the hidden variable _cwp_error will be set with a message; if error logging is checked in section 4, an error message will be written to the log and the contents of _cwp_error will be written out to a special tag called "001_errors" which should appear prominently in the bottom pane of Picard. The problem may be resolved by refreshing, otherwise there may be a problem with the MB database availability. It is unlikely to be a software problem with the plugin.
    • \n" -"
    • Removal of common text between parent and child works. This section controls the naming of parts, by stripping parent text from the work name. If the work begins with the parent name followed by punctuation then the common text will always be stripped to give the part name (even if there are punctuation or some other minor differences).
    • \n" -"

      However, common text which is not followed by punctuation or which is not at the start may also be stripped: to prevent this, set "Minimum number of similar words required before eliminating (other than at start)" to zero. Otherwise common text longer than the specified number of words (default = 2) will be stripped.

      \n" -"
    • How title metadata should be included in extended metadata. This subsection contains various parameters affecting the processing of strings in titles. (Some of it also affects the elimination of common text between parent and child works referred to above). Because titles are free-form, not all circumstances can be anticipated. If pure canonical works are used ("Use only metadata from canonical works" and, if necessary, "Full MusicBrainz work hierarchy" on the Works and parts tab, section 2) then this processing should be irrelevant, but no text from titles will be included. Some explanations are given below:
    \n" -"
    • "Proximity of new words". When using extended metadata - i.e. "metadata enhanced with title text", the plugin will attempt to remove similar words in the canonical work name (in MusicBrainz) and the title before extending the canonical name. After removing such words, a rather "bitty" result may occur. To avoid this, any new words with the specified proximity will have the words between them (or up to the start/end) included even if they repeat words in the work name.
    • \n" -"
    • "Treat hyphenated words as two words for comparison purposes" (default = True). In comparing words, hyphenated words will be considered as separte words unless this option is deselected.
    • \n" -"
    • "Proportion of a string to be matched ... for it to be considered essentially similar..." (default = 66%). If the title and work descriptions are largely the same then the title text will not be used to extend the work text, even if there are some new words.
    • \n" -"
    • "Prepositions". Words listed here will not generally be treated as "new" (i.e. if they are in the title text but not in the work text, they will not be included in the "extended" text) unless they precede a new word which is not itself a preposition. Note that, although the term "preposition" is used here, because that is the obvious usage, any word can be listed. A group of more than one such words will be treated as new if they are not in the work text and they precede a new word which is not listed as a preposition.
    • \n" -"
    • "Prefixes". When using "metadata from titles" or extended metadata, the structure of the works in MusicBrainz is used to infer the structure in the title text, so strings that are repeated between tracks which are part of the same MusicBrainz work will be treated as "higher level". This can lead to anomalies if, for instance, the titles are "Work name: Part 1", "Work name: Part 2", "Part" is repeated and so will be treated as part of the parent work name. Specifying such words in "Prefixes" will prevent this.
    • \n" -"
    • "Synonyms". These words will be considered equivalent when comparing work name and title text. Thus if one word appears in the work name, that and its synonym will be removed from the title in extending the metadata (subject to the proximity setting above). Note that only single word synonyms are allowed, with no duplicates or punctuation. Each entry should be a pair (2-tuple) in the form (key word, equivalent word) - no quote marks are necessary. Each pair should be separated by a forward slash - /.
    • \n" -"
    • Replacements". These words/phrases will be replaced in the title text in extended metadata, regardless of the text in the work name. Each entry should be a pair (2-tuple) in the form (original text, replacement text) - no quote marks are necessary. Each pair should be separated by a forward slash - /. If required the original text (to be replaced) can be a regular expression, in which case it must be surrounded by double exclamation marks, thus: (!!regex here!!, replacement text here)
    \n" -"
  7. "Genres etc. ...". This is only required if Muso-specific options are used for genres/periods. Specify the path and file name for the reference database (the default is the Muso default for a shared database). Note that the database is only loaded when Picard starts so you will need to restart Picard is these options are changed.
  8. \n" -"
  9. "Logging options". These options are in addition to the options chosen in Picard\'s "Help->View error/debug log" settings. They only affect messages written by this plugin. To enable debug messages to be shown in the Picard log, the flag needs to be set here and "Debug mode" needs to be turned on in the log. It is strongly advised to keep the "debug" flag unchecked unless debugging is required as it slows up processing and may even cause Picard to hang if there is a large number of files (better to use the \'info\' option - see below). The "error" and "warning" flags should be left checked, unless it is required to suppress messages (the messages are also written to the tags 001_errors and 002_warnings).
  10. \n" -"

    As well as the main Picard log, a custom logging function is provided. This may be either "basic" or "full". If "basic" is selected, a file "session.log" will be written (over-written each session) to a "Classical Extras" directory inside the same directory as the plugins folder. (Note - the easy way to find this directory is to select options-->plugins in Picard and the "Open plugins folder", then go up one level). The session log gives a processing summary for each release and includes errors, warnings and debug messages if those options have been selected.

    \n" -"

    If "full" is selected, all errors, warnings and debugs will be written, along with additional debugging messages, to a custom log file for each release processed. These files are stored in the "Classical Extras" directory inside the same directory as the plugins folder. The log file for a release is named using the release MBID. Debugging from these files requires an understanding of the source code.

    \n" -"

    Selecting "full" will slow Picard, but should not normally result in hanging or crashing.

    \n" -"
  11. "Save plugin details and options in a tag?" can be used so that the user has a record of the version of Classical Extras which generated the tags and which options were selected to achieve the resulting tags. Note that the tags will be blanked first so this will only show the last options used on a particular file. The same tag can be used for both sets of options, resulting in a multi-valued tag. All the options in the Classical Extras UI are saved except those which are asterisked.
  12. \n" -"

    The tag contents are in dict format. The options in these tags can then be used to over-ride the displayed options subsequently (see below).

    \n" -"

    N.B. The "Tag name for artist/misc. options" also saves the Picard options for \'translate_artist_names\' and \'standardize_artists\' as these interact with the Classical Extras options.

    \n" -"
  13. "Over-ride plugin options displayed in UI with options from local file tags". If options have previously been saved (see above), selecting these will cause the saved options to be used in preference to the displayed options. The displayed options will not be affected and will be used if no saved options are present. The default is for no over-ride. If "Artists options" over-ride is selected then a sub-option to over-ride (or not) the "Tag details options" is available; this refers to just the detailed tag map in the second box in the tag-mapping tab.
\n" -"

Note that very occasionally (if the tag containing the options has been corrupted) use of this option may cause an error. In such a case you will need to deselect the "over-ride" option and set the required options manually; then save the resulting tags and the corrupted tag should be over-written

\n" -"

The last checkbox, "Overwrite options in Options Pages", is for VERY CAREFUL USE ONLY. It will cause any options read from the saved tags (if the relevant box has been ticked) to over-write the options on the plugin Options Page UI. The intended use of this is if for some reason the user\'s preferred options have been erased/reverted to default - by using this option, the previously-used choices from a reliable filed album can be used to populate the Options Page. The box will automatically be unticked after loading/refreshing one album, and will always be turned off when starting Picard, to prevent inadvertant use. Far better is to make a backup copy of the picard.ini file.

\n" -"

Information on hidden variables

\n" -"

This section is for users who want to write their own scripts, or add additional tags (in the tag mapping section) based on hidden variables. The definition and source of each hidden variable is listed. Apologies if there are errors and omissions in this section - to double check the actual hidden variables fgr any track, use the plugin "View script variables".

\n" -"

Works and parts

\n" -"
  • _cwp_work_n, where n is an integer >=0 : The MB work name at level n. For n=0, the tag is the same as the current standard Picard tag "work"
  • \n" -"
  • _cwp_work_top : The top work name (i.e. for maximal n). Thus, if max n = N, _cwp_work_top will be equivalent to _cwp_work_N. Note, however, that this will always be the "canonical" MB name, not one derived from titles or the lowest level work name and that no annotations (e.g. key or work year) will be added (whereas they will be added to _cwp_work_N). Nevertheless, if "replace work names by aliases" has been selected and is applicable, the relevant alias will be used.
  • \n" -"
  • _cwp_workid_n : The matching work id for each work name. For n=0, the tag is the same as the standard Picard tag "MusicBrainz Work Id"
  • \n" -"
  • _cwp_workid_top : The matching work id for the top work name.
  • \n" -"
  • _cwp_part_n : A "stripped" version of _cwp_work_n, where higher-level work text has been removed wherever possible, to avoid duplication on display. Thus in theory, _cwp_work_0 will be the same as "_cwp_work_top: _cwp_part_(N-1): ...: _cwp_part_0" (punctuation excepted), but may differ in more complex situations where there is not an exact hierarchy of text as the work levels are traversed. (See below for the "_X0" series which attempts to address any such inconsistencies)
  • \n" -"
  • _cwp_part_levels : The number of work levels attached to THIS TRACK. Should be equal to N = max(n) referred to above.
  • \n" -"
  • _cwp_work_part_levels : The maximum number of levels for ANY TRACK in the album which has the same top work as this track.
  • \n" -"
  • _cwp_single_work_album : A flag = 1 if there is only one top work in this album, else = 0.
  • \n" -"
  • _cwp_work : the level selected by the plugin to be the source of the single-level work name if "Use only metadata from canonical works" is selected (usually the top level, but one lower in the case of a single work album).
  • \n" -"
  • _cwp_groupheading : the level selected by the plugin to be the source of the multi-level work name if "Use only metadata from canonical works" is selected.
  • \n" -"
  • _cwp_part : The movement name derived from the MB work names (generally = _cwp_part_0) and used as the source for the movement name used for "Tags for Movement - including embedded movt/part numbers".
  • \n" -"
  • _cwp_inter_work : Intermediate works between _cwp_part and _cwp_work (if any).
\n" -"

If there is more than one work any level, then _cwp_work_n and _cwp_workid_n will have multiple entries. Another common situation is that a "bottom level" work is spread across more than one track. Rather than artificially split the work into sub-parts, this is often shown in MusicBrainz as a track being a "partial recording of" a work. The plugin deals with this by creating a notional lowest-level with the suffix " (part)" appended to the work it is a partial recording of. In order that this notional part can be separately identified from the full work, the musicbrainz_recordingid is used as the identifier rather than the workid. If there is more than one "parent" work of a lower level work, multi-valued tags are generated.

\n" -"
  • _cwp_X0_part_0 : A "stripped" version of _cwp_work_0 (see above), where elements of _cwp_work_0 which repeat within level 1 have been stripped.
  • \n" -"
  • _cwp_X0_work_n : The elements of _cwp_work_0 which repeat within level n
\n" -"

As well as variables derived from MB\'s work structure, some variables are produced which are derived from the track title. Typically titles may be in the format "Work: Movement", but not always. Sometimes the title is prefixed by the name of the composer; in this case the variable

\n" -"
  • _cwp_title is provided which excludes the composer name and subsequent processing is carried out using this rather than the full title.
\n" -"

The plugin uses a number of methods attempt to extract the works and movement from the title. The resulting variables are:

\n" -"
  • _cwp_title_work_n, and
  • \n" -"
  • _cwp_title_part_n which mirror those for the ones based on MB works described above.
  • \n" -"
  • _cwp_title_part_levels which similarly mirrors _cwp_part_levels
  • \n" -"
  • _cwp_title_work_levels which similarly mirrors _cwp_work_part_levels
  • \n" -"
  • _cwp_title_work is the level selected by the plugin to be the source of the single-level work name if "Use only metadata from title text" is selected (usually the top level, but one lower in the case of a single work album).
  • \n" -"
  • _cwp_title_groupheading is similarly the level selected by the plugin to be the source of the multi-level work name if "Use only metadata from title text" is selected.
  • \n" -"
  • _cwp_extended_part : = _cwp_part with additional movement information from the title - given in {}.
  • \n" -"
  • _cwp_extended_groupheading : = _cwp_groupheading with additional work information from the title - given in {}.
  • \n" -"
  • _cwp_extended_work : = _cwp_work with additional work information from the title - given in {}.
  • \n" -"
  • _cwp_extended_inter_work : = _cwp_inter_work with additional work information from the title - given in {}. The "extended" variables can be useful where the "canonical" work names in MB are in the original language and the titles are in English (say). Various heuristics are used to try and add (and only add) meaningful additional information, but oddities may occur which require manual editing.
\n" -"

Artist tags which derive from work-artist relationships are also set in this section:

\n" -"
  • _cwp_composers
  • \n" -"
  • _cwp_writers
  • \n" -"
  • _cwp_arrangers : This is for arrangers of the work and also "instrument arrangers" and "vocal arrangers" with appropriate annotation for instrument and voice types. (Picard does not currently write the latter to the Arranger tag if they are part of the work-artists relationship, despite style guidance saying to use specific instrument types instead of generic arranger.)
  • \n" -"
  • _cwp_arranger_names : Just the names of the above (no annotations)
  • \n" -"
  • _cwp_orchestrators
  • \n" -"
  • _cwp_reconstructors - \'reconstructed by\' relationships
  • \n" -"
  • _cwp_revisors - \'revised by\' relationships
  • \n" -"
  • _cwp_lyricists
  • \n" -"
  • _cwp_librettists
  • \n" -"
  • _cwp_translators
\n" -"

Finally, the tags _cwp_error and_cwp_warning are provided to supply warnings and error messages to the user.

\n" -"

Artists

\n" -"

All the additional hidden variables for artists written by Classical Extras are prefixed by _cea_. Note that these are generally in the plural, whereas the standard tags are singular. If the user blanks a tag then the original value is stored in the singular with the _cea_ prefix. Thus _cea_arranger would be the contents of the Picard tag "arranger" before blanking, whereas _cea_arrangers is hidden variable created by Classical Extras.

\n" -"
  • _cea_recording_artist : The artist credited with the recording (not necessarily the track artist). Note that this is the only "_cea_" tag which is singular, because it is in the same format as the \'artist\' tag, whereas...
  • \n" -"
  • _cea_recording_artists : The list/multiple value version of the above. (This follows the approach in Picard for \'artist\' and \'artists\', being the track artists.)
  • \n" -"
  • _cea_MB_artists: The original track artists per MusicBrainz before any replacement by / merging with recording artists.
  • \n" -"
  • _cea_soloists : List of performers (with instruments in brackets), who are NOT ensembles or conductors, separated by semi-colons. Note they may not strictly be "soloists" in that they may be part of an ensemble.
  • \n" -"
  • _cea_recording_artistsort : Sort names of _cea_recording_artist
  • \n" -"
  • _cea_recording_artists_sort : Sort names of _cea_recording_artists
  • \n" -"
  • _cea_soloist_names : Names of the above (i.e. no instruments).
  • \n" -"
  • _cea_soloists_sort : Sort_names of the above.
  • \n" -"
  • _cea_vocalists : Soloists who are vocalists (with voice in brackets).
  • \n" -"
  • _cea_vocalist_names : Names of the above (no voice).
  • \n" -"
  • _cea_instrumentalists : Soloists who have instruments but are not vocalists.
  • \n" -"
  • _cea_instrumentalist_names : Names of the above (no instrument).
  • \n" -"
  • _cea_other_soloists : Soloists who do not have specified instrument/voice.
  • \n" -"
  • _cea_ensembles : List of performers which are ensembles (with type / instruments - e.g. "orchestra" - in brackets), separated by semi-colons.
  • \n" -"
  • _cea_ensemble_names : Names of the above (i.e. no instruments).
  • \n" -"
  • _cea_ensembles_sort : Sort_names of the above.
  • \n" -"
  • _cea_album_soloists : Sub-list of soloist_names who are also album artists
  • \n" -"
  • _cea_album_soloists_sort : Sort_names of the above.
  • \n" -"
  • _cea_album_conductors : List of conductors whao are also album artists
  • \n" -"
  • _cea_album_conductors_sort : Sort_names of the above.
  • \n" -"
  • _cea_album_ensembles: Sub-list of ensemble_names who are also album artists
  • \n" -"
  • _cea_album_ensembles_sort : Sort_names of the above.
  • \n" -"
  • _cea_album_composers : List of composers who are also album artists
  • \n" -"
  • _cea_album_composers_sort : Sort_names of the above.
  • \n" -"
  • _cea_album_track_composer_lastnames : Last names of the above. (N.B. This only includes the composers of the current track - compare with _cea_album_composer_lastnames below).
  • \n" -"
  • _cea_album_composer_lastnames : Last names of composers of ANY track on the album who are also album artists. This can be used to prefix the album name if required. (cf _cea_album_track_composer_lastnames)
  • \n" -"
  • _cea_support_performers : Sub-list of soloist_names who are NOT album artists
  • \n" -"
  • _cea_support_performers_sort : Sort_names of the above.
  • \n" -"
  • _cea_composers : Alternative composer name, based on sort name, to avoid non-latin language problems.
  • \n" -"
  • _cea_conductors : Alternative conductor name, based on sort name, to avoid non-latin language problems.
  • \n" -"
  • _cea_performers : An alternative to performer, based on the sort name (see note re non-Latin script below).
  • \n" -"
  • _cea_arrangers : All arrangers for the recording with instrument/voice type in brackets, if provided. If the work and parts functionality has also been selected, the arrangers of works, which Picard also currently omits will be put in _cwp_arrangers.
  • \n" -"
  • _cea_orchestrators : Arrangers (per Picard) included in the MB database as type "orchestrator".
  • \n" -"
  • _cea_chorusmasters : A person who (per Picard) is a conductor, but is "chorus master" in the MB database (i.e. not necessarily conducting the performance).
  • \n" -"
  • _cea_leaders : The leader of the orchestra ("concertmaster" in MusicBrainz) - not created by Picard as standard.
  • \n" -"
  • _cea_work_type : Although not strictly an artist field, this is derived from artist and performer metadata. This is the variable populated if "Infer work types" is selected on the Artists tab.
\n" -"

Genres etc.

\n" -"

Most of the genres, keys and date information requires the works and parts section to have been run, and so the variables are prefixed _cwp_, but instruments and genres which are derived from artist information and are prefixed _cea_.

\n" -"
  • _cea_instruments : Names of all instruments on the track (MusicBrainz names)
  • \n" -"
  • _cea_instuments_credited : As above, but MB names replaced by as-credited names, if any
  • \n" -"
  • _cea_instruments_all : MB and as-credited names
  • \n" -"
  • _cea_work_type : The genre(s) inferred from artist information
  • \n" -"
  • _cea_work_type_if_classical : As above, but only of relevance if the work is classical
  • \n" -"
  • _cwp_candidate_genres : List of all tags, work types etc. found (depending on specified sources) before filtering for "allowed" genres.
  • \n" -"
  • _cwp_keys : keys associated with this track (from all work levels).
  • \n" -"
  • _cwp_composed_dates : Date composed (integer) or range (integer-integer).
  • \n" -"
  • _cwp_published_dates : Date published (integer) or range (integer-integer).
  • \n" -"
  • _cwp_premiered_dates : Date premiered (integer) or range (integer-integer).
  • \n" -"
  • _cwp_untagged_genres : Genres in _cwp_candidate_genres which have been filtered out as they are not in any "allowed" list.
  • \n" -"
  • _cwp_unrostered_composers : For Muso users: composers who are not in Muso\'s classical composers roster.
\n" -"

Software-specific notes

\n" -"

Note that _cwp_part_levels > 0 will indicate that the track recording is part of a work and so could be used to set other software-specific flags (e.g. for iTunes "show work movement") to indicate a multi-level "work: movement".

\n" -"

SongKong

\n" -"

SongKong users may wish to map the _cwp variables to tags produced by SongKong if consistency is desired, in which case the mappings are principally:

\n" -"
  • _cwp_work_0 => musicbrainz_work_composition
  • \n" -"
  • _cwp_workid_0 => musicbrainz_work_composition_id
  • \n" -"
  • _cwp_work_n => musicbrainz_work_part_leveln, for n = 1..6
  • \n" -"
  • _cwp_workid_n => musicbrainz_work_part_leveln_id, for n = 1..6
  • \n" -"
  • _cwp_work_top => musicbrainz_work
\n" -"

These mappings are carried out automatically if the relevant options on the "Work and parts" tab are selected.
In addition, _cwp_title_work and _cwp_title_part_0 are intended to be equivalent to SongKong\'s work and part tags. (N.B. Full consistency between SongKong and Picard may also require the modification of Artist and related tags via a script, or the preservation of the related file tags)

\n" -"

Muso

\n" -"

The tag "groupheading" should be set as the "Tags for Work - for software with 2-level capability". Muso will use this directly and extract the levels from it (split by the double colon). Muso permits a variety of import options which should be capable of combination with the tagging options in this plugin to achieve most desired effects. To avoid the use of import options in Muso, set the output tags from the plugin to be the native ones used by Muso (NB "title" may include or exclude groupheading - Muso should recognise it and extract it).

\n" -"

To make use of Muso\'s in-built classical music processing to set explicit tags in Picard, enable the "Use Muso reference database ..." option on the "Genres etc." tab.

\n" -"

Players with no "work" capability

\n" -"

Leave the "title" tag unchanged or make it a combination of work and movement.

\n" -"

Possible Enhancements

\n" -"

Planned enhancements (among others) are

\n" -"
  1. Include information regarding dates (e.g. date composed, recording date).
  2. \n" -"
  3. Improved genre capability, possibly with specific tags for different aspects of genre, e.g. periods.
\n" -"

Technical Matters

\n" -"

Issues were encountered with the Picard API in that there is not a documented way to let Picard know that it is still doing asynchronous tasks in the background and has not finished processing metadata. Many thanks to @dns_server for assistance in dealing with this and to @sophist for the albumartist_website code which I have borrowed from. I have tried to add some more comments to help any others trying the same techniques.

\n" -"

Also, the documentation of the XML lookup is virtually non-existent. The response is an XmlNode object (not a dict, although it is represented as one). Each node has a name with {attribs, text, children} values. The structure is more clearly understood if the web-based lookup is used (which is well documented at https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2 ) as this gives an XML response. I wrote a function (parse_data) to parse XmlNode objects, or lists thereof, for a (parameterised) hierarchy of nodes (and optional attribs value tests) in order to extract required data from the response. This may be of use to other plugin authors. The situation has been made slightly better in Picard 2.0 as the objects returned from register_track_metadata_processor are standard JSON. See https://musicbrainz.org/doc/Development/JSON_Web_Service. My parse_data function works with JSON as well as XmlNode formats, but the arguments you need to provide to it are different as the formats are structured differently. Picard 2.0 allows tagger.webservice calls to specify XML or default to JSON. This plugin has now migrated over to JSON-only.

\n" -"

To get the whole picture, in XML, for a release, use (for example) https://musicbrainz.org/ws/2/release/f3bb4fdd-5db0-43a8-be73-7a1747f6c2ef?inc=release-groups+media+recordings+artist-credits+artists+aliases+labels+isrcs+collections+artist-rels+release-rels+url-rels+recording-rels+work-rels+recording-level-rels+work-level-rels. (Add &fmt=JSON at the end to get the JSON response). This simulates the response given by Picard with the "Use release relationships" and "Use track relationships" options selected. Note that the Picard album_metadata_processor returns releaseXmlNode which is everything from the Release node of the XML downwards, whereas track_metadata_processor returns trackXmlNode which is everything from a Track node downwards (release -> medium-list -> medium -> track-list is above track). Replace all hyphens in XML with underscores when parsing the Python object (JSON uses hyphens).

\n" -"

A large variety of releases were used to test this plugin, but there may still be bugs, so further testing is welcome. The following release was particularly complex and useful for testing: https://musicbrainz.org/release/ec519fde-94ee-4812-9717-659d91be11d4. Also this release was a bit tricky - a large box set with some works appearing as originals and in arrangements: https://musicbrainz.org/release/5288f266-bab8-45bd-83e4-555730f02fa0.

\n" -"

Very technical matters

\n" -"

I\'ve done a bit of research and observed the following behaviour in Picard when using the register_track_metadata_processor() API:

\n" -"
  1. If a new album (i.e. not yet tagged by Picard and with no MBIDs) is loaded, clustered and looked-up/scanned, resulting in the matched files being shown in the right-hand pane, then:
  2. \n" -"
    • track.metadata gives the lookup result from MB - i.e. no file information, just the track info.
    • \n" -"
    • album.tagger.files[xxxx].metadata (where xxxx is the path/filename of the track) gives the file information (dirname etc.) and the tags on the original file.
    • \n" -"
    • album.tagger.files[xxxx].orig_metadata gives the same as album.tagger.files[xxxx].metadata
    \n" -"
  3. However, if the album is then "refreshed", this does not just carry out a repeat operation, instead:
\n" -"
  • track.metadata gives the same as before
  • \n" -"
  • album.tagger.files[xxxx].metadata gives the metadata as in track.metadata and also includes all the file information as before.
  • \n" -"
  • album.tagger.files[xxxx].orig_metadata gives the same as before (i.e. the original tags).
\n" -"

So, the \'post-refreshment\' metadata is actually usable - by matching the musicbrainz_trackid of track.metadata and album.tagger.files[xxxx].metadata, you can get the file details of the track. Not elegant, but it works. But why is it necessary to "refresh" to get what seems like a logical data set? Basically, the metadata process is not complete when the plugin is called, so not all metadata is available to it.

\n" -"

I don\'t know why get_files_from_objects(objs) doesn\'t work in the register_track_metadata_processor() API when objs is a list of tracks, but does provide a list of file objects when objs is a list of albums. Also it does work in the register_file_action(xxx)/register_track_action(xxx) API, but I assume that is because itemsview.py can identify that you have clicked on an item that is a track and a file (this is after the metadata processing has run).

\n" -"

In order to get round these problems, I have adopted a \'hack\' which looks up the files for an album and finds which file matches the required track on tracknumber and discnumber. This seems to work, but there may be circumstances when it does not. As a consequence, refreshment should only be required if get_files_from_objects(objs) doesn\'t get all the album files.

\n" -"

List of previous updates

\n" -"

Version 1.0: Final version with Picard 1.4.2 compatibility

\n" -"

Version 0.9.4: Bug fixes.

\n" -"

Version 0.9.3: Custom logging if enabled on "Advanced" tab - writes one log file per album in addition to standard Picard log. Also a session log with basic data is written. Performance improvements (especially for lyrics processing) and various bug fixes.

\n" -"

Version 0.9.2: A new tab in the options page has been created - "Genres etc." This includes special processing for genres, instruments, keys, work dates and periods. The "Infer work types" option on the "Artists" tab has been moved to this tab (but will be reset to the default of "False" and will need to be reset as required, because it operates slightly differently now that there is more control over the setting of genres). A separate section has ben added to this readme giving more details on the options in this tab.

\n" -"

Version 0.9.1: Bug fixes.

\n" -"

Version 0.9: Additional option to clear previous file tags which are not wanted, without interfering with cover art. Additional option to replace instrument names with as-credited names. Also instrument names are now saved to hidden variables tags (instruments, instruments_credited and instruments_all) which can be mapped to file tags as required. Sub-option added to the \'override artist options\' option on the "advanced" tab - to allow tag map details to be included or not in this over-ride. Minor bug fixes and UI improvements. This is the next \'official\' version after 0.7.

\n" -"

Version 0.8.9: Provide option (in advanced tab) to disable Classical Extras processing if no file is present; this enables (for example) single discs from box sets to be loaded without incurring the additional processing overhead for all the other discs. The settings of the main Picard options "translate_artist_names" and "standardize_artists" is now saved along with the Classical Extras options so that they can be used to over-ride the displayed options. This is because they interact with the Classical Extras options in certain cases. Also:- graceful recovery from authentication failure; improved UI - more scalable; minor bug fixes.

\n" -"

Version 0.8.8: Fixes to allow for (1) disabling of \'use_cache\' across releases which may share works and (2) works which might appear in their own right and also have arrangements. Also, re-set certain important options to defaults on starting Picard, namely: \'use_cache\' set to True, \'log_debug\', \'log_info\' and \'options_overwrite\' set to False; the user will need to deliberately re-set these on starting Picard if required - this is to prevent inadvertently leaving these flags in an abnormal state.

\n" -"

Version 0.8.7: Revised treatment of "conditional" tag mapping. Previously, if multiple sources were specified for a tag mapping and the "conditional" flag set, only the first non-empty source was used. Now all sources will be mapped to a tag if it was empty before executing the current tag mapping line. This is considered to be more intuitive and leads to less complex mapping lines. However, it some cases it may be necessary to split a line from a previous version if the previous behaviour was specifically desired. Improved algorithms for extending metadata with title info. Bug fixes.

\n" -"

Version 0.8.6: More consistent approach to sort tags and hidden variables. Bug fixes.

\n" -"

Version 0.8.5: Improved handling of instruments. Bug fixes.

\n" -"

Version 0.8.4: Improved UI, bug fixes and code improvements.

\n" -"

Version 0.8.3: Ability to use aliases of work names instead of the MusicBrainz standard names. Various options to use aliases and as-credited names for performers and writers (i.e. relationship artists). Option to use recording artists as well as (or instead of) track artists. All relationship artists are now tagged for all work levels (with some special processing for lyricists). New UI layout to separate tag mapping from artist options. Option to split lyrics into common (release) and unique (track) components. Various code improvements as well as bug fixes.

\n" -"

Version 0.8.2: Improved algorithms and a few minor bug fixes.

\n" -"

Version 0.8.1: Bug fixes and minor enhancements - including revison and extension of "synonyms" section on the "advanced" tab.

\n" -"

Version 0.8: Handle multiple recordings and/or multiple parents of a work. Handle multiple albumartist composers for one track. Option to use "credited as" name for artists (inc. performers and composers) who are "release artist" or "track artist". Option to exclude "solo" from instrument types. Option to over-ride plugin options with those used when the album was last saved. Option to keep (and append to) specified existing file tags - furthermore if "is_classical" is present, the work-type variable will include "Classical". Option to use (and write) SongKong-compatible work tags (saves processing time if SongKong is used to pre-process large numbers of files). Include the work (and its parents) of which a work is an arrangement (as a "pseudo-parent"). Include medleys in movement/part description (as [Medley of:...] or other descriptor specified in options). Allow for multiple parents of recordings and works (and multiple parents of those) - multiples are given as multiple tag instances, where permitted, otherwise separated by semi-colons. Option as to whether to include parent works where the relationship attribute is "part of collection". Plus minor enhancements and bug fixes too numerous to mention! (Note that some old versions of the plugin may say v0.8 when they are only v0.7)

\n" -"

Version 0.7: Bug fixes. Pull request issued for this version.

\n" -"

Version 0.6.6: Bug fixes and algorithm improvements. Allow multiple sources for a tag (each will be appended) and use + as separator for concantenating sources, not a comma, to distinguish from the use of comma to separate multiples. Provide additional hidden variables ("sources") for vocalists and instrumentalists. Option to include intermediate work levels in a movement tag (for systems which cannot display multiple work levels).

\n" -"

Version 0.6.5: Include ability to use multiple (concatenated) sources in tag mapping (see notes under "tag mapping"). All artist "sources" using hidden variables (_cea_...) are now consistently in the plural, to distinguish from standard tags. Note that if "album" is used as a source in tag mapping, it will now be the revised name, where composer prefixing is used. To use the original name, use "release" as the source. Also various bug fixes, notably to ensure that all arrangers get picked up for use in tag mapping.

\n" -"

Version 0.6.4: Write out version number to user-specified tag. Provide default comment tags for writing ui options. Provide better m4a and iTunes compatibility (see notes). Added functionality for chorus master, orchestrator and concert master (leader). Re-arranged artist tab in ui. Various bug fixes.

\n" -"

Version 0.6.3: Bug fixes. Modified ui default options.

\n" -"

Version 0.6.2: Bug fixes. More flexible handling of artists (can blank and then add back later). Modified ui default options.

\n" -"

Version 0.6.1: Amended regex to permit non-Latin characters in work text.

")) +"

General description

\n" +"

Classical Extras provides tagging enhancements for Picard and, in particular, utilises MusicBrainz’s hierarchy of works to provide work/movement tags. All options are set through a user interface in Picard options->plugins. This interface provides separate sections to enhance artist/performer tags, works and parts, genres and also allows for a generalised "tag mapping" (simple scripting). While it is designed to cater for the complexities of classical music tagging, it may also be useful for other music which has more than just basic song/artist/album data.

The options screen provides five tabs for users to control the tags produced:

1. Artists: Options as to whether artist tags will contain standard MB names, aliases or as-credited names. Ability to include and annotate names for specialist roles (chorus master, arranger, lyricist etc.). Ability to read lyrics tags on the file which has been loaded and assign them to track and album levels if required. (Note: Picard will not normally process incoming file tags).

2. Works and parts: The plugin will build a hierarchy of works and parts (e.g. Work -> Part -> Movement or Opera -> Act -> Number) based on the works in MusicBrainz\'s database. These can then be displayed in tags in a variety of ways according to user preferences. Furthermore partial recordings, medleys, arrangements and collections of works are all handled according to user choices. There is a processing overhead for this at present because MusicBrainz limits look-ups to one per second.

3. Genres etc.: Options are available to customise the source and display of information relating to genres, instruments, keys, work dates and periods. Additional capabilities are provided for users of Muso (or others who provide the relevant XML files) to use pre-existing databases of classical genres, classical composers and classical periods.

4. Tag mapping: in some ways, this is a simple substitute for some of Picard\'s scripting capability. The main advantage is that the plugin will remember what tag mapping you use for each release (or even track).

5. Advanced: Various options to control the detailed processing of the above.

All user options can be saved on a per-album (or even per-track) basis so that tweaks can be used to deal with inconsistencies in the MusicBrainz data (e.g. include English titles from the track listing where the MusicBrainz works are in the composer\'s language and/or script). Also existing file tags can be processed (not possible in native Picard).

See the readme file
on GitHub here for full details.

")) self.textBrowser_3.setHtml(_translate("ClassicalExtrasOptionsPage", "\n" "\n" -"

Please see my website for a more readable version and also a lot of other help.

\n" -"

The text below may be slightly out of date and lacks images.

")) +"

Please see the readme file or my website for full details of this plugin and how to use it.

\n" +"

This help page now has only general information.

\n" +"

There are extensive tooltips and "What\'s This" popups (right click for them)

")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.Help), _translate("ClassicalExtrasOptionsPage", "Help"))