#!/usr/bin/env python3 # -*- coding: utf-8 -*- # This is the Smart Title Case plugin for MusicBrainz Picard. # Copyright (C) 2017 Sophist. # # Updated for use with Picard v2.0 by Bob Swift (rdswift). # # It is based on the Title Case plugin by Javier Kohen # Copyright 2007 Javier Kohen # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. PLUGIN_NAME = "Smart Title Case" PLUGIN_AUTHOR = "Sophist based on an earlier plugin by Javier Kohen" PLUGIN_DESCRIPTION = """ Capitalize First Character In Every Word Of Album/Track Title/Artist.
Leaves words containing embedded uppercase as-is i.e. USA or DoA.
For Artist/AlbumArtist, title cases only artists not join phrases
e.g. The Beatles feat. The Who. """ PLUGIN_VERSION = "0.4.2" PLUGIN_API_VERSIONS = ["2.0"] PLUGIN_LICENSE = "GPL-2.0-or-later" PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html" import re, unicodedata from picard import log from picard.metadata import ( register_track_metadata_processor, register_album_metadata_processor, ) title_tags = ['title', 'album'] artist_tags = [ ('artist', 'artists'), ('artistsort', '~artists_sort'), ('albumartist', '~albumartists'), ('albumartistsort', '~albumartists_sort'), ] title_re = re.compile(r'\w[^-,/\s\u2010\u2011]*') def match_word(match): word = match.group(0) if word == word.lower(): word = word[0].upper() + word[1:] return word def string_title_match(match_word, string): return title_re.sub(match_word, string) def string_cleanup(string): if not string: return "" return unicodedata.normalize("NFKC", string) def string_title_case(string): """Title-case a string using a less destructive method than str.title. >>> string_title_case('make title case') 'Make Title Case' >>> string_title_case('Already Title Case') 'Already Title Case' >>> string_title_case('mIxEd cAsE') 'mIxEd cAsE' >>> string_title_case('a') 'A' >>> string_title_case("apostrophe's apostrophe's") "Apostrophe's Apostrophe's" >>> string_title_case('(bracketed text)') '(Bracketed Text)' >>> string_title_case("'single quotes'") "'Single Quotes'" >>> string_title_case('"double quotes"') '"Double Quotes"' >>> string_title_case('a,b') 'A,B' >>> string_title_case('a-b') 'A-B' >>> string_title_case('a/b') 'A/B' >>> string_title_case('flügel') 'Flügel' >>> string_title_case('HARVEST STORY by 杉山清貴') 'HARVEST STORY By 杉山清貴' """ return string_title_match(match_word, string_cleanup(string)) def artist_title_case(text, artists, artists_upper): """ Use the array of artists and the joined string to identify artists to make title case and the join strings to leave as-is. >>> artist_title_case('the beatles feat. the who', ['the beatles', 'the who'], ['The Beatles', 'The Who']) 'The Beatles feat. The Who' >>> artist_title_case('kesha feat. 3OH!3', ['kesha', '3OH!3'], ['Kesha', '3OH!3']) 'Kesha feat. 3OH!3' """ find = "^(" + r")(\s+\S+?\s+)(".join((map(re.escape, map(string_cleanup,artists)))) + ")(.*$)" replace = "".join([r"%s\g<%d>" % (a, x*2 + 2) for x, a in enumerate(artists_upper)]) result = re.sub(find, replace, string_cleanup(text)) return result def title_case(tagger, metadata, *args): for name in title_tags: if name in metadata: values = metadata.getall(name) new_values = [string_title_case(v) for v in values] if values != new_values: log.debug("SmartTitleCase: %s: %r replaced with %r", name, values, new_values) metadata[name] = new_values for artist_string, artists_list in artist_tags: if artist_string in metadata and artists_list in metadata: artist = metadata.getall(artist_string) artists = metadata.getall(artists_list) new_artists = list(map(string_title_case, artists)) new_artist = [artist_title_case(x, artists, new_artists) for x in artist] if artists != new_artists and artist != new_artist: log.debug("SmartTitleCase: %s: %s replaced with %s", artist_string, artist, new_artist) log.debug("SmartTitleCase: %s: %r replaced with %r", artists_list, artists, new_artists) metadata[artist_string] = new_artist metadata[artists_list] = new_artists elif artists != new_artists or artist != new_artist: if artists != new_artists: log.warning("SmartTitleCase: %s changed, %s wasn't", artists_list, artist_string) log.warning("SmartTitleCase: %s: %r changed to %r", artists_list, artists, new_artists) log.warning("SmartTitleCase: %s: %r unchanged", artist_string, artist) else: log.warning("SmartTitleCase: %s changed, %s wasn't", artist_string, artists_list) log.warning("SmartTitleCase: %s: %r changed to %r", artist_string, artist, new_artist) log.warning("SmartTitleCase: %s: %r unchanged", artists_list, artists) register_track_metadata_processor(title_case) register_album_metadata_processor(title_case)