242 changes: 121 additions & 121 deletions picard/formats/mp4.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion picard/formats/util.py
Expand Up @@ -59,7 +59,7 @@ def guess_format(filename, options=_formats):
results = []
# Since we are reading only 128 bytes and then immediately closing the file,
# use unbuffered mode.
with open(filename, "rb", 0) as fileobj:
with open(filename, 'rb', 0) as fileobj:
header = fileobj.read(128)
# Calls the score method of a particular format's associated filetype
# and assigns a positive score depending on how closely the fileobj's header matches
Expand Down
64 changes: 32 additions & 32 deletions picard/formats/vorbis.py
Expand Up @@ -122,11 +122,11 @@ class VCommentFile(File):
_File = None

__translate = {
"movement": "movementnumber",
"movementname": "movement",
"musicbrainz_releasetrackid": "musicbrainz_trackid",
"musicbrainz_trackid": "musicbrainz_recordingid",
"waveformatextensible_channel_mask": "~waveformatextensible_channel_mask",
'movement': 'movementnumber',
'movementname': 'movement',
'musicbrainz_releasetrackid': 'musicbrainz_trackid',
'musicbrainz_trackid': 'musicbrainz_recordingid',
'waveformatextensible_channel_mask': '~waveformatextensible_channel_mask',
}
__rtranslate = {v: k for k, v in __translate.items()}

Expand Down Expand Up @@ -170,18 +170,18 @@ def _load(self, filename):
value = str(round((float(value) * (config.setting['rating_steps'] - 1))))
except ValueError:
log.warning('Invalid rating value in %r: %s', filename, value)
elif name == "fingerprint" and value.startswith("MusicMagic Fingerprint"):
name = "musicip_fingerprint"
elif name == 'fingerprint' and value.startswith('MusicMagic Fingerprint'):
name = 'musicip_fingerprint'
value = value[22:]
elif name == "tracktotal":
if "totaltracks" in file.tags:
elif name == 'tracktotal':
if 'totaltracks' in file.tags:
continue
name = "totaltracks"
elif name == "disctotal":
if "totaldiscs" in file.tags:
name = 'totaltracks'
elif name == 'disctotal':
if 'totaldiscs' in file.tags:
continue
name = "totaldiscs"
elif name == "metadata_block_picture":
name = 'totaldiscs'
elif name == 'metadata_block_picture':
try:
image = mutagen.flac.Picture(base64.standard_b64decode(value))
coverartimage = TagCoverArtImage(
Expand All @@ -194,7 +194,7 @@ def _load(self, filename):
id3_type=image.type
)
except (CoverArtImageError, TypeError, ValueError, mutagen.flac.error) as e:
log.error('Cannot load image from %r: %s', filename, e)
log.error("Cannot load image from %r: %s", filename, e)
else:
metadata.images.append(coverartimage)
continue
Expand All @@ -214,22 +214,22 @@ def _load(self, filename):
id3_type=image.type
)
except CoverArtImageError as e:
log.error('Cannot load image from %r: %s', filename, e)
log.error("Cannot load image from %r: %s", filename, e)
else:
metadata.images.append(coverartimage)

# Read the unofficial COVERART tags, for backward compatibility only
if "metadata_block_picture" not in file.tags:
if 'metadata_block_picture' not in file.tags:
try:
for data in file["COVERART"]:
for data in file['COVERART']:
try:
coverartimage = TagCoverArtImage(
file=filename,
tag='COVERART',
data=base64.standard_b64decode(data)
)
except (CoverArtImageError, TypeError, ValueError) as e:
log.error('Cannot load image from %r: %s', filename, e)
log.error("Cannot load image from %r: %s", filename, e)
else:
metadata.images.append(coverartimage)
except KeyError:
Expand All @@ -245,9 +245,9 @@ def _save(self, filename, metadata):
file = self._File(encode_filename(filename))
if file.tags is None:
file.add_tags()
if config.setting["clear_existing_tags"]:
if config.setting['clear_existing_tags']:
preserve_tags = ['waveformatextensible_channel_mask']
if not is_flac and config.setting["preserve_images"]:
if not is_flac and config.setting['preserve_images']:
preserve_tags.append('metadata_block_picture')
preserve_tags.append('coverart')
preserved_values = {}
Expand All @@ -259,8 +259,8 @@ def _save(self, filename, metadata):
file.tags[name] = value
images_to_save = list(metadata.images.to_be_saved_to_tags())
if is_flac and (images_to_save
or (config.setting["clear_existing_tags"]
and not config.setting["preserve_images"])):
or (config.setting['clear_existing_tags']
and not config.setting['preserve_images'])):
file.clear_pictures()
tags = {}

Expand Down Expand Up @@ -293,10 +293,10 @@ def _save(self, filename, metadata):
name = self.__rtranslate[name]
tags.setdefault(name.upper(), []).append(value.rstrip('\0'))

if "totaltracks" in metadata:
tags.setdefault("TRACKTOTAL", []).append(metadata["totaltracks"])
if "totaldiscs" in metadata:
tags.setdefault("DISCTOTAL", []).append(metadata["totaldiscs"])
if 'totaltracks' in metadata:
tags.setdefault('TRACKTOTAL', []).append(metadata['totaltracks'])
if 'totaldiscs' in metadata:
tags.setdefault('DISCTOTAL', []).append(metadata['totaldiscs'])

for image in images_to_save:
picture = mutagen.flac.Picture()
Expand All @@ -312,12 +312,12 @@ def _save(self, filename, metadata):
+ len(picture.mime)
+ len(picture.desc.encode('UTF-8')))
if expected_block_size > FLAC_MAX_BLOCK_SIZE:
log.error('Failed saving image to %r: Image size of %d bytes exceeds maximum FLAC block size of %d bytes',
log.error("Failed saving image to %r: Image size of %d bytes exceeds maximum FLAC block size of %d bytes",
filename, expected_block_size, FLAC_MAX_BLOCK_SIZE)
continue
file.add_picture(picture)
else:
tags.setdefault("METADATA_BLOCK_PICTURE", []).append(
tags.setdefault('METADATA_BLOCK_PICTURE', []).append(
base64.b64encode(picture.write()).decode('ascii'))

file.tags.update(tags)
Expand All @@ -327,10 +327,10 @@ def _save(self, filename, metadata):
kwargs = {}
if is_flac:
flac_sort_pics_after_tags(file.metadata_blocks)
if config.setting["fix_missing_seekpoints_flac"]:
if config.setting['fix_missing_seekpoints_flac']:
flac_remove_empty_seektable(file)
if config.setting["remove_id3_from_flac"]:
kwargs["deleteid3"] = True
if config.setting['remove_id3_from_flac']:
kwargs['deleteid3'] = True
try:
file.save(**kwargs)
except TypeError:
Expand Down
6 changes: 4 additions & 2 deletions picard/i18n.py
Expand Up @@ -172,12 +172,14 @@ def setup_gettext(localedir, ui_language=None, logger=None):
QLocale.setDefault(QLocale(current_locale))

trans = _load_translation('picard', localedir, language=current_locale)
trans_countries = _load_translation('picard-countries', localedir, language=current_locale)
trans_attributes = _load_translation('picard-attributes', localedir, language=current_locale)
trans_constants = _load_translation('picard-constants', localedir, language=current_locale)
trans_countries = _load_translation('picard-countries', localedir, language=current_locale)

trans.install(['ngettext'])
builtins.__dict__['gettext_countries'] = trans_countries.gettext
builtins.__dict__['gettext_attributes'] = trans_attributes.gettext
builtins.__dict__['gettext_constants'] = trans_constants.gettext
builtins.__dict__['gettext_countries'] = trans_countries.gettext

if hasattr(trans_attributes, 'pgettext'):
builtins.__dict__['pgettext_attributes'] = trans_attributes.pgettext
Expand Down
2 changes: 1 addition & 1 deletion picard/log.py
Expand Up @@ -48,7 +48,7 @@
if IS_FROZEN:
picard_module_path = Path(FROZEN_TEMP_PATH).joinpath('picard').resolve()
else:
picard_module_path = Path(PathFinder().find_module('picard').get_filename()).resolve()
picard_module_path = Path(PathFinder().find_spec('picard').origin).resolve()

_MAX_TAIL_LEN = 10**6

Expand Down
58 changes: 29 additions & 29 deletions picard/metadata.py
Expand Up @@ -253,18 +253,18 @@ def compare_to_release_parts(self, release, weights):
parts = []

with self._lock.lock_for_read():
if "album" in self and "album" in weights:
if 'album' in self and 'album' in weights:
b = release['title']
parts.append((similarity2(self["album"], b), weights["album"]))
parts.append((similarity2(self['album'], b), weights['album']))

if "albumartist" in self and "albumartist" in weights:
a = self["albumartist"]
if 'albumartist' in self and 'albumartist' in weights:
a = self['albumartist']
b = artist_credit_from_node(release['artist-credit'])[0]
parts.append((similarity2(a, b), weights["albumartist"]))
parts.append((similarity2(a, b), weights['albumartist']))

if "totaltracks" in weights:
if 'totaltracks' in weights:
try:
a = int(self["totaltracks"])
a = int(self['totaltracks'])
if 'media' in release:
score = 0.0
for media in release['media']:
Expand All @@ -275,25 +275,25 @@ def compare_to_release_parts(self, release, weights):
else:
b = release['track-count']
score = trackcount_score(a, b)
parts.append((score, weights["totaltracks"]))
parts.append((score, weights['totaltracks']))
except (ValueError, KeyError):
pass

if "totalalbumtracks" in weights:
if 'totalalbumtracks' in weights:
try:
a = int(self["~totalalbumtracks"] or self["totaltracks"])
a = int(self['~totalalbumtracks'] or self['totaltracks'])
b = release['track-count']
score = trackcount_score(a, b)
parts.append((score, weights["totalalbumtracks"]))
parts.append((score, weights['totalalbumtracks']))
except (ValueError, KeyError):
pass

# Date Logic
date_match_factor = 0.0
if "date" in weights:
if "date" in release and release['date'] != '':
if 'date' in weights:
if 'date' in release and release['date'] != '':
release_date = release['date']
if "date" in self:
if 'date' in self:
metadata_date = self['date']
if release_date == metadata_date:
# release has a date and it matches what our metadata had exactly.
Expand Down Expand Up @@ -326,20 +326,20 @@ def compare_to_release_parts(self, release, weights):
parts.append((date_match_factor, weights['date']))

config = get_config()
if "releasecountry" in weights:
if 'releasecountry' in weights:
weights_from_preferred_countries(parts, release,
config.setting["preferred_release_countries"],
weights["releasecountry"])
config.setting['preferred_release_countries'],
weights['releasecountry'])

if "format" in weights:
if 'format' in weights:
weights_from_preferred_formats(parts, release,
config.setting["preferred_release_formats"],
weights["format"])
config.setting['preferred_release_formats'],
weights['format'])

if "releasetype" in weights:
if 'releasetype' in weights:
weights_from_release_type_scores(parts, release,
config.setting["release_type_scores"],
weights["releasetype"])
config.setting['release_type_scores'],
weights['releasetype'])

rg = QObject.tagger.get_release_group_by_id(release['release-group']['id'])
if release['id'] in rg.loaded_albums:
Expand Down Expand Up @@ -369,6 +369,12 @@ def compare_to_track(self, track, weights):
score = self.length_score(a, b)
parts.append((score, weights["length"]))

if 'isvideo' in weights:
metadata_is_video = self['~video'] == '1'
track_is_video = track.get('video', False)
score = 1 if metadata_is_video == track_is_video else 0
parts.append((score, weights['isvideo']))

if "releases" in track:
releases = track['releases']

Expand All @@ -377,12 +383,6 @@ def compare_to_track(self, track, weights):
sim = linear_combination_of_weights(parts) * search_score
return SimMatchTrack(similarity=sim, releasegroup=None, release=None, track=track)

if 'isvideo' in weights:
metadata_is_video = self['~video'] == '1'
track_is_video = track.get('video', False)
score = 1 if metadata_is_video == track_is_video else 0
parts.append((score, weights['isvideo']))

result = SimMatchTrack(similarity=-1, releasegroup=None, release=None, track=None)
for release in releases:
release_parts = self.compare_to_release_parts(release, weights)
Expand Down
82 changes: 41 additions & 41 deletions picard/oauth.py
Expand Up @@ -67,59 +67,59 @@ def port(self):

@property
def refresh_token(self):
return self.persist["oauth_refresh_token"]
return self.persist['oauth_refresh_token']

@refresh_token.setter
def refresh_token(self, value):
self.persist["oauth_refresh_token"] = value
self.persist['oauth_refresh_token'] = value

@refresh_token.deleter
def refresh_token(self):
self.persist.remove("oauth_refresh_token")
self.persist.remove('oauth_refresh_token')

@property
def refresh_token_scopes(self):
return self.persist["oauth_refresh_token_scopes"]
return self.persist['oauth_refresh_token_scopes']

@refresh_token_scopes.setter
def refresh_token_scopes(self, value):
self.persist["oauth_refresh_token_scopes"] = value
self.persist['oauth_refresh_token_scopes'] = value

@refresh_token_scopes.deleter
def refresh_token_scopes(self):
self.persist.remove("oauth_refresh_token_scopes")
self.persist.remove('oauth_refresh_token_scopes')

@property
def access_token(self):
return self.persist["oauth_access_token"]
return self.persist['oauth_access_token']

@access_token.setter
def access_token(self, value):
self.persist["oauth_access_token"] = value
self.persist['oauth_access_token'] = value

@access_token.deleter
def access_token(self):
self.persist.remove("oauth_access_token")
self.persist.remove('oauth_access_token')

@property
def access_token_expires(self):
return self.persist["oauth_access_token_expires"]
return self.persist['oauth_access_token_expires']

@access_token_expires.setter
def access_token_expires(self, value):
self.persist["oauth_access_token_expires"] = value
self.persist['oauth_access_token_expires'] = value

@access_token_expires.deleter
def access_token_expires(self):
self.persist.remove("oauth_access_token_expires")
self.persist.remove('oauth_access_token_expires')

@property
def username(self):
return self.persist["oauth_username"]
return self.persist['oauth_username']

@username.setter
def username(self, value):
self.persist["oauth_username"] = value
self.persist['oauth_username'] = value

def is_authorized(self):
return bool(self.refresh_token and self.refresh_token_scopes)
Expand Down Expand Up @@ -158,10 +158,10 @@ def url(self, path=None, params=None):

def get_authorization_url(self, scopes):
params = {
"response_type": "code",
"client_id": MUSICBRAINZ_OAUTH_CLIENT_ID,
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"scope": scopes,
'response_type': 'code',
'client_id': MUSICBRAINZ_OAUTH_CLIENT_ID,
'redirect_uri': "urn:ietf:wg:oauth:2.0:oob",
'scope': scopes,
}
return bytes(self.url(path="/oauth2/authorize", params=params).toEncoded()).decode()

Expand All @@ -182,10 +182,10 @@ def _query_data(params):
def refresh_access_token(self, callback):
log.debug("OAuth: refreshing access_token with a refresh_token %s", self.refresh_token)
params = {
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
"client_id": MUSICBRAINZ_OAUTH_CLIENT_ID,
"client_secret": MUSICBRAINZ_OAUTH_CLIENT_SECRET,
'grant_type': 'refresh_token',
'refresh_token': self.refresh_token,
'client_id': MUSICBRAINZ_OAUTH_CLIENT_ID,
'client_secret': MUSICBRAINZ_OAUTH_CLIENT_SECRET,
}
self.webservice.post_url(
url=self.url(path="/oauth2/token"),
Expand All @@ -194,7 +194,7 @@ def refresh_access_token(self, callback):
mblogin=True,
priority=True,
important=True,
request_mimetype="application/x-www-form-urlencoded",
request_mimetype='application/x-www-form-urlencoded',
)

def on_refresh_access_token_finished(self, callback, data, http, error):
Expand All @@ -204,24 +204,24 @@ def on_refresh_access_token_finished(self, callback, data, http, error):
log.error("OAuth: access_token refresh failed: %s", data)
if self._http_code(http) == 400:
response = load_json(data)
if response["error"] == "invalid_grant":
if response['error'] == 'invalid_grant':
self.forget_refresh_token()
else:
access_token = data["access_token"]
self.set_access_token(access_token, data["expires_in"])
access_token = data['access_token']
self.set_access_token(access_token, data['expires_in'])
except Exception as e:
log.error('OAuth: Unexpected error handling access token response: %r', e)
log.error("OAuth: Unexpected error handling access token response: %r", e)
finally:
callback(access_token=access_token)

def exchange_authorization_code(self, authorization_code, scopes, callback):
log.debug("OAuth: exchanging authorization_code %s for an access_token", authorization_code)
params = {
"grant_type": "authorization_code",
"code": authorization_code,
"client_id": MUSICBRAINZ_OAUTH_CLIENT_ID,
"client_secret": MUSICBRAINZ_OAUTH_CLIENT_SECRET,
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
'grant_type': 'authorization_code',
'code': authorization_code,
'client_id': MUSICBRAINZ_OAUTH_CLIENT_ID,
'client_secret': MUSICBRAINZ_OAUTH_CLIENT_SECRET,
'redirect_uri': "urn:ietf:wg:oauth:2.0:oob",
}
self.webservice.post_url(
url=self.url(path="/oauth2/token"),
Expand All @@ -230,7 +230,7 @@ def exchange_authorization_code(self, authorization_code, scopes, callback):
mblogin=True,
priority=True,
important=True,
request_mimetype="application/x-www-form-urlencoded",
request_mimetype='application/x-www-form-urlencoded',
)

def on_exchange_authorization_code_finished(self, scopes, callback, data, http, error):
Expand All @@ -241,12 +241,12 @@ def on_exchange_authorization_code_finished(self, scopes, callback, data, http,
log.error("OAuth: authorization_code exchange failed: %s", data)
error_msg = self._extract_error_description(http, data)
else:
self.set_refresh_token(data["refresh_token"], scopes)
self.set_access_token(data["access_token"], data["expires_in"])
self.set_refresh_token(data['refresh_token'], scopes)
self.set_access_token(data['access_token'], data['expires_in'])
successful = True
except Exception as e:
log.error('OAuth: Unexpected error handling authorization code response: %r', e)
error_msg = _('Unexpected authentication error')
log.error("OAuth: Unexpected error handling authorization code response: %r", e)
error_msg = _("Unexpected authentication error")
finally:
callback(successful=successful, error_msg=error_msg)

Expand All @@ -268,12 +268,12 @@ def on_fetch_username_finished(self, callback, data, http, error):
log.error("OAuth: username fetching failed: %s", data)
error_msg = self._extract_error_description(http, data)
else:
self.username = data["sub"]
self.username = data['sub']
log.debug("OAuth: got username %s", self.username)
successful = True
except Exception as e:
log.error('OAuth: Unexpected error handling username fetch response: %r', e)
error_msg = _('Unexpected authentication error')
log.error("OAuth: Unexpected error handling username fetch response: %r", e)
error_msg = _("Unexpected authentication error")
finally:
callback(successful=successful, error_msg=error_msg)

Expand All @@ -285,4 +285,4 @@ def _extract_error_description(self, http, data):
response = load_json(data)
return response['error_description']
except (JSONDecodeError, KeyError, TypeError):
return _('Unexpected request error (HTTP code %s)') % self._http_code(http)
return _("Unexpected request error (HTTP code %s)") % self._http_code(http)
4 changes: 2 additions & 2 deletions picard/plugin.py
Expand Up @@ -102,7 +102,7 @@ def unregister_module(self, name):

def __iter__(self):
config = get_config()
enabled_plugins = config.setting["enabled_plugins"] if config else []
enabled_plugins = config.setting['enabled_plugins'] if config else []
for name in self.__dict:
if name is None or name in enabled_plugins:
yield from self.__dict[name]
Expand Down Expand Up @@ -216,7 +216,7 @@ def __getattribute__(self, name):
try:
return super().__getattribute__(name)
except AttributeError:
log.debug('Attribute %r not found for plugin %r', name, self.module_name)
log.debug("Attribute %r not found for plugin %r", name, self.module_name)
return None

@property
Expand Down
322 changes: 186 additions & 136 deletions picard/pluginmanager.py

Large diffs are not rendered by default.

288 changes: 144 additions & 144 deletions picard/profile.py

Large diffs are not rendered by default.

50 changes: 25 additions & 25 deletions picard/releasegroup.py
Expand Up @@ -56,25 +56,25 @@ def __init__(self, rg_id):
self.refcount = 0

def load_versions(self, callback):
kwargs = {"release-group": self.id, "limit": 100}
kwargs = {'release-group': self.id, 'limit': 100}
self.tagger.mb_api.browse_releases(partial(self._request_finished, callback), **kwargs)

def _parse_versions(self, document):
"""Parse document and return a list of releases"""
del self.versions[:]
data = []

namekeys = ("tracks", "year", "country", "format", "label", "catnum")
namekeys = ('tracks', 'year', 'country', 'format', 'label', 'catnum')
headings = {
"tracks": N_('Tracks'),
"year": N_('Year'),
"country": N_('Country'),
"format": N_('Format'),
"label": N_('Label'),
"catnum": N_('Cat No'),
'tracks': N_("Tracks"),
'year': N_("Year"),
'country': N_("Country"),
'format': N_("Format"),
'label': N_("Label"),
'catnum': N_("Cat No"),
}
# additional keys displayed only for disambiguation
extrakeys = ("packaging", "barcode", "disambiguation")
extrakeys = ('packaging', 'barcode', 'disambiguation')

try:
releases = document['releases']
Expand All @@ -97,23 +97,23 @@ def _parse_versions(self, document):
tracks = "+".join(str(m['track-count']) for m in node['media'])
formats = []
for medium in node['media']:
if "format" in medium:
if 'format' in medium:
formats.append(medium['format'])
release = {
"id": node['id'],
"year": node['date'][:4] if "date" in node else "????",
"country": country_label,
"format": media_formats_from_node(node['media']),
"label": ", ".join(' '.join(x.split(' ')[:2]) for x in set(labels)),
"catnum": ", ".join(set(catnums)),
"tracks": tracks,
"barcode": node.get('barcode', '') or _('[no barcode]'),
"packaging": node.get('packaging', '') or '??',
"disambiguation": node.get('disambiguation', ''),
"_disambiguate_name": list(),
"totaltracks": sum(m['track-count'] for m in node['media']),
"countries": countries,
"formats": formats,
'id': node['id'],
'year': node['date'][:4] if 'date' in node else '????',
'country': country_label,
'format': media_formats_from_node(node['media']),
'label': ', '.join(' '.join(x.split(' ')[:2]) for x in set(labels)),
'catnum': ', '.join(set(catnums)),
'tracks': tracks,
'barcode': node.get('barcode', '') or _('[no barcode]'),
'packaging': node.get('packaging', '') or '??',
'disambiguation': node.get('disambiguation', ''),
'_disambiguate_name': list(),
'totaltracks': sum(m['track-count'] for m in node['media']),
'countries': countries,
'formats': formats,
}
data.append(release)

Expand All @@ -122,7 +122,7 @@ def _parse_versions(self, document):
# Group versions by same display name
for release in data:
name = " / ".join(release[k] for k in namekeys)
if name == release["tracks"]:
if name == release['tracks']:
name = "%s / %s" % (_('[no release info]'), name)
versions[name].append(release)

Expand Down
41,139 changes: 21,964 additions & 19,175 deletions picard/resources.py

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions picard/script/__init__.py
Expand Up @@ -4,7 +4,7 @@
#
# Copyright (C) 2006-2009, 2012 Lukáš Lalinský
# Copyright (C) 2007 Javier Kohen
# Copyright (C) 2008-2011, 2014-2015, 2018-2021 Philipp Wolfer
# Copyright (C) 2008-2011, 2014-2015, 2018-2021, 2023 Philipp Wolfer
# Copyright (C) 2009 Carlin Mangar
# Copyright (C) 2009 Nikolai Prokoschenko
# Copyright (C) 2011-2012 Michael Wiencek
Expand Down Expand Up @@ -104,9 +104,9 @@ def enabled_tagger_scripts_texts():
"""Returns an iterator over the enabled tagger scripts.
For each script, you'll get a tuple consisting of the script name and text"""
config = get_config()
if not config.setting["enable_tagger_scripts"]:
if not config.setting['enable_tagger_scripts']:
return []
return [(s_name, s_text) for _s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"] if s_enabled and s_text]
return [(s_name, s_text) for _s_pos, s_name, s_enabled, s_text in config.setting['list_of_scripts'] if s_enabled and s_text]


def get_file_naming_script(settings):
Expand All @@ -119,14 +119,14 @@ def get_file_naming_script(settings):
str: The text of the file naming script if available, otherwise None
"""
from picard.script import get_file_naming_script_presets
scripts = settings["file_renaming_scripts"]
selected_id = settings["selected_file_naming_script_id"]
scripts = settings['file_renaming_scripts']
selected_id = settings['selected_file_naming_script_id']
if selected_id:
if scripts and selected_id in scripts:
return scripts[selected_id]["script"]
return scripts[selected_id]['script']
for item in get_file_naming_script_presets():
if item["id"] == selected_id:
return str(item["script"])
if item['id'] == selected_id:
return str(item['script'])
log.error("Unable to retrieve the file naming script '%s'", selected_id)
return None

Expand All @@ -142,7 +142,10 @@ def get_file_naming_script_presets():
LICENSE = "GNU Public License version 2"

def preset_title(number, title):
return _("Preset %d: %s") % (number, _(title))
return _("Preset %(number)d: %(title)s") % {
'number': number,
'title': _(title),
}

yield FileNamingScript(
id=DEFAULT_NAMING_PRESET_ID,
Expand Down
150 changes: 75 additions & 75 deletions picard/script/functions.py

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions picard/script/parser.py
Expand Up @@ -104,12 +104,12 @@ def __init__(self, line, column, name=None):

def __str__(self):
if self.name is None:
return '{line:d}:{column:d}'.format(
return "{line:d}:{column:d}".format(
line=self.line,
column=self.column
)
else:
return '{line:d}:{column:d}:{name}'.format(
return "{line:d}:{column:d}:{name}".format(
line=self.line,
column=self.column,
name=self.name
Expand All @@ -124,7 +124,7 @@ def eval(self, state):

def normalize_tagname(name):
if name.startswith('_'):
return "~" + name[1:]
return '~' + name[1:]
return name


Expand All @@ -134,7 +134,7 @@ def __init__(self, name):
self.name = name

def __repr__(self):
return '<ScriptVariable %%%s%%>' % self.name
return "<ScriptVariable %%%s%%>" % self.name

def eval(self, state):
return state.context.get(normalize_tagname(self.name), "")
Expand Down Expand Up @@ -427,7 +427,7 @@ def insert(self, index, value):
return self._multi.insert(index, value)

def __repr__(self):
return '%s(%r, %r, %r)' % (self.__class__.__name__, self.parser, self._multi, self.separator)
return "%s(%r, %r, %r)" % (self.__class__.__name__, self.parser, self._multi, self.separator)

def __str__(self):
return self.separator.join(x for x in self if x)
26 changes: 13 additions & 13 deletions picard/script/serializer.py
Expand Up @@ -72,7 +72,7 @@ class MultilineLiteral(str):
def yaml_presenter(dumper, data):
if data:
data = data.rstrip() + '\n'
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')


yaml.add_representer(MultilineLiteral, MultilineLiteral.yaml_presenter)
Expand Down Expand Up @@ -191,15 +191,15 @@ def to_dict(self):
dict: Dictionary of the object's OUTPUT_FIELDS
"""
items = {key: getattr(self, key) for key in self.OUTPUT_FIELDS}
items["description"] = str(items["description"])
items["script"] = str(items["script"])
items['description'] = str(items['description'])
items['script'] = str(items['script'])
return items

def export_script(self, parent=None):
"""Export the script to a file.
"""
# return _export_script_dialog(script_item=self, parent=parent)
FILE_ERROR_EXPORT = N_('Error exporting file "%s". %s.')
FILE_ERROR_EXPORT = N_('Error exporting file "%(filename)s": %(error)s.')

default_script_directory = os.path.normpath(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation))
default_script_extension = "ptsp"
Expand All @@ -216,13 +216,13 @@ def export_script(self, parent=None):
(name, ext) = os.path.splitext(filename)
if ext and str(name).endswith('.' + ext):
filename = name
log.debug('Exporting script file: %s', filename)
log.debug("Exporting script file: %s", filename)
if file_type == self._file_types()['package']:
script_text = self.to_yaml()
else:
script_text = self.script + "\n"
try:
with open(filename, 'w', encoding='utf8') as o_file:
with open(filename, 'w', encoding='utf-8') as o_file:
o_file.write(script_text)
except OSError as error:
raise ScriptImportExportError(format=FILE_ERROR_EXPORT, filename=filename, error_msg=error.strerror)
Expand All @@ -240,8 +240,8 @@ def export_script(self, parent=None):
def import_script(cls, parent=None):
"""Import a script from a file.
"""
FILE_ERROR_IMPORT = N_('Error importing "%s". %s.')
FILE_ERROR_DECODE = N_('Error decoding "%s". %s.')
FILE_ERROR_IMPORT = N_('Error importing "%(filename)s": %(error)s')
FILE_ERROR_DECODE = N_('Error decoding "%(filename)s": %(error)s')

dialog_title = _("Import Script File")
dialog_file_types = cls._get_dialog_filetypes()
Expand All @@ -250,14 +250,14 @@ def import_script(cls, parent=None):
filename, file_type = QtWidgets.QFileDialog.getOpenFileName(parent, dialog_title, default_script_directory, dialog_file_types, options=options)
if not filename:
return None
log.debug('Importing script file: %s', filename)
log.debug("Importing script file: %s", filename)
try:
with open(filename, 'r', encoding='utf8') as i_file:
with open(filename, 'r', encoding='utf-8') as i_file:
file_content = i_file.read()
except OSError as error:
raise ScriptImportExportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=error.strerror)
if not file_content.strip():
raise ScriptImportExportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=N_('The file was empty'))
raise ScriptImportExportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=N_("The file was empty"))
if file_type == cls._file_types()['package']:
try:
return cls().create_from_yaml(file_content)
Expand Down Expand Up @@ -285,7 +285,7 @@ def create_from_dict(cls, script_dict, create_new_id=True):
if not isinstance(script_dict, Mapping):
raise ScriptImportError(N_("Argument is not a dictionary"))
if 'title' not in script_dict or 'script' not in script_dict:
raise ScriptImportError(N_('Invalid script package'))
raise ScriptImportError(N_("Invalid script package"))
new_object.update_from_dict(script_dict)
if create_new_id or not new_object['id']:
new_object._set_new_id()
Expand Down Expand Up @@ -329,7 +329,7 @@ def create_from_yaml(cls, yaml_string, create_new_id=True):
if not isinstance(yaml_dict, dict):
raise ScriptImportError(N_("File content not a dictionary"))
if 'title' not in yaml_dict or 'script' not in yaml_dict:
raise ScriptImportError(N_('Invalid script package'))
raise ScriptImportError(N_("Invalid script package"))
new_object.update_from_dict(yaml_dict)
if create_new_id or not new_object['id']:
new_object._set_new_id()
Expand Down
161 changes: 83 additions & 78 deletions picard/tagger.py

Large diffs are not rendered by default.

30 changes: 18 additions & 12 deletions picard/track.py
Expand Up @@ -117,6 +117,11 @@ def filter(self, counter):
if not self.skip(name):
yield (name, count)

def format_errors(self):
fmt = _("Error line %(lineno)d: %(error)s")
for lineno, error in self.errors.items():
yield fmt % {'lineno': lineno + 1, 'error': error}


class TrackArtist(DataObject):
def __init__(self, ta_id):
Expand Down Expand Up @@ -171,7 +176,7 @@ def update_file_metadata(self, file):
# Run the scripts for the file to allow usage of
# file specific metadata and variables
config = get_config()
if config.setting["clear_existing_tags"]:
if config.setting['clear_existing_tags']:
metadata = Metadata(self.orig_metadata)
metadata_proxy = MultiMetadataProxy(metadata, file.metadata)
self.run_scripts(metadata_proxy)
Expand Down Expand Up @@ -389,22 +394,23 @@ def load(self, priority=False, refresh=False):
config = get_config()
require_authentication = False
inc = {
"aliases",
"artist-credits",
"artists",
'aliases',
'artist-credits',
'artists',
}
if config.setting["track_ars"]:
if config.setting['track_ars']:
inc |= {
"artist-rels",
"recording-rels",
"url-rels",
"work-level-rels",
"work-rels",
'artist-rels',
'recording-rels',
'series-rels',
'url-rels',
'work-level-rels',
'work-rels',
}
require_authentication = self.set_genre_inc_params(inc, config) or require_authentication
if config.setting["enable_ratings"]:
if config.setting['enable_ratings']:
require_authentication = True
inc |= {"user-ratings"}
inc |= {'user-ratings'}
self.tagger.mb_api.get_track_by_id(
self.id,
self._recording_request_finished,
Expand Down
4 changes: 2 additions & 2 deletions picard/ui/__init__.py
Expand Up @@ -65,8 +65,8 @@ class PreserveGeometry:
defaultsize = None

def __init__(self):
Option.add_if_missing("persist", self.opt_name(), QtCore.QByteArray())
Option.add_if_missing("persist", self.splitters_name(), {})
Option.add_if_missing('persist', self.opt_name(), QtCore.QByteArray())
Option.add_if_missing('persist', self.splitters_name(), {})
if getattr(self, 'finished', None):
self.finished.connect(self.save_geometry)

Expand Down
8 changes: 4 additions & 4 deletions picard/ui/aboutdialog.py
Expand Up @@ -71,12 +71,12 @@ def _update_content(self):
])

# TR: Replace this with your name to have it appear in the "About" dialog.
args["translator_credits"] = _("translator-credits")
if args["translator_credits"] != "translator-credits":
args['translator_credits'] = _('translator-credits')
if args['translator_credits'] != 'translator-credits':
# TR: Replace LANG with language you are translating to.
args["translator_credits"] = _("<br/>Translated to LANG by %s") % args["translator_credits"].replace("\n", "<br/>")
args['translator_credits'] = _("<br/>Translated to LANG by %s") % args['translator_credits'].replace("\n", "<br/>")
else:
args["translator_credits"] = ""
args['translator_credits'] = ""
args['icons_credits'] = _(
'Icons made by Sambhav Kothari <sambhavs.email@gmail.com> '
'and <a href="http://www.flaticon.com/authors/madebyoliver">Madebyoliver</a>, '
Expand Down
357 changes: 357 additions & 0 deletions picard/ui/caa_types_selector.py
@@ -0,0 +1,357 @@
# -*- coding: utf-8 -*-
#
# Picard, the next-generation MusicBrainz tagger
#
# Copyright (C) 2007 Oliver Charles
# Copyright (C) 2007, 2010-2011 Lukáš Lalinský
# Copyright (C) 2007-2011, 2015, 2018-2023 Philipp Wolfer
# Copyright (C) 2011 Michael Wiencek
# Copyright (C) 2011-2012 Wieland Hoffmann
# Copyright (C) 2013-2015, 2018-2023 Laurent Monin
# Copyright (C) 2015-2016 Rahul Raturi
# Copyright (C) 2016-2017 Sambhav Kothari
# Copyright (C) 2017 Frederik “Freso” S. Olesen
# Copyright (C) 2018 Bob Swift
# Copyright (C) 2018 Vishal Choudhary
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.


from functools import partial

from PyQt5 import (
QtCore,
QtGui,
QtWidgets,
)

from picard.ui import PicardDialog
from picard.ui.util import (
StandardButton,
qlistwidget_items,
)


class ArrowButton(QtWidgets.QPushButton):
"""Standard arrow button for CAA image type selection dialog.
Keyword Arguments:
label {string} -- Label to display on the button
command {command} -- Command to execute when the button is clicked (default: {None})
parent {[type]} -- Parent of the QPushButton object being created (default: {None})
"""

def __init__(self, icon_name, command=None, parent=None):
icon = QtGui.QIcon(":/images/16x16/" + icon_name + '.png')
super().__init__(icon, "", parent=parent)
if command is not None:
self.clicked.connect(command)


class ArrowsColumn(QtWidgets.QWidget):
"""Standard arrow buttons column for CAA image type selection dialog.
Keyword Arguments:
selection_list {ListBox} -- ListBox of selected items associated with this arrow column
ignore_list {ListBox} -- ListBox of unselected items associated with this arrow column
callback {command} -- Command to execute after items are moved between lists (default: {None})
reverse {bool} -- Determines whether the arrow directions should be reversed (default: {False})
parent {[type]} -- Parent of the QWidget object being created (default: {None})
"""

def __init__(self, selection_list, ignore_list, callback=None, reverse=False, parent=None):
super().__init__(parent=parent)
self.selection_list = selection_list
self.ignore_list = ignore_list
self.callback = callback
spacer_item = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
arrows_layout = QtWidgets.QVBoxLayout()
arrows_layout.addItem(QtWidgets.QSpacerItem(spacer_item))
self.button_add = ArrowButton('go-next' if reverse else 'go-previous', self.move_from_ignore)
arrows_layout.addWidget(self.button_add)
self.button_add_all = ArrowButton('move-all-right' if reverse else 'move-all-left', self.move_all_from_ignore)
arrows_layout.addWidget(self.button_add_all)
self.button_remove = ArrowButton('go-previous' if reverse else 'go-next', self.move_to_ignore)
arrows_layout.addWidget(self.button_remove)
self.button_remove_all = ArrowButton('move-all-left' if reverse else 'move-all-right', self.move_all_to_ignore)
arrows_layout.addWidget(self.button_remove_all)
arrows_layout.addItem(QtWidgets.QSpacerItem(spacer_item))
self.setLayout(arrows_layout)

def move_from_ignore(self):
self.ignore_list.move_selected_items(self.selection_list, callback=self.callback)

def move_all_from_ignore(self):
self.ignore_list.move_all_items(self.selection_list, callback=self.callback)

def move_to_ignore(self):
self.selection_list.move_selected_items(self.ignore_list, callback=self.callback)

def move_all_to_ignore(self):
self.selection_list.move_all_items(self.ignore_list, callback=self.callback)


class ListBox(QtWidgets.QListWidget):
"""Standard list box for CAA image type selection dialog.
Keyword Arguments:
parent {[type]} -- Parent of the QListWidget object being created (default: {None})
"""

LISTBOX_WIDTH = 100
LISTBOX_HEIGHT = 250

def __init__(self, parent=None):
super().__init__(parent=parent)
self.setMinimumSize(QtCore.QSize(self.LISTBOX_WIDTH, self.LISTBOX_HEIGHT))
self.setSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Expanding)
self.setSortingEnabled(True)
self.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)

def move_item(self, item, target_list):
"""Move the specified item to another listbox."""
self.takeItem(self.row(item))
target_list.addItem(item)

def move_selected_items(self, target_list, callback=None):
"""Move the selected item to another listbox."""
for item in self.selectedItems():
self.move_item(item, target_list)
if callback:
callback()

def move_all_items(self, target_list, callback=None):
"""Move all items to another listbox."""
while self.count():
self.move_item(self.item(0), target_list)
if callback:
callback()

def all_items_data(self, role=QtCore.Qt.ItemDataRole.UserRole):
for item in qlistwidget_items(self):
yield item.data(role)


class CAATypesSelectorDialog(PicardDialog):
"""Display dialog box to select the CAA image types to include and exclude from download and use.
Keyword Arguments:
parent {[type]} -- Parent of the QDialog object being created (default: {None})
types_include {[string]} -- List of CAA image types to include (default: {None})
types_exclude {[string]} -- List of CAA image types to exclude (default: {None})
default_include {[string]} -- List of CAA image types to include by default (default: {None})
default_exclude {[string]} -- List of CAA image types to exclude by default (default: {None})
known_types {{string: string}} -- Dict. of all known CAA image types, unique name as key, translated title as value (default: {None})
"""

help_url = 'doc_cover_art_types'

def __init__(
self, parent=None, types_include=None, types_exclude=None,
default_include=None, default_exclude=None, known_types=None
):
super().__init__(parent)
if types_include is None:
types_include = []
if types_exclude is None:
types_exclude = []
self._default_include = default_include or []
self._default_exclude = default_exclude or []
self._known_types = known_types or {}

self.setWindowTitle(_("Cover art types"))
self.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetFixedSize)

# Create list boxes for dialog
self.list_include = ListBox()
self.list_exclude = ListBox()
self.list_ignore = ListBox()

# Populate list boxes from current settings
self.fill_lists(types_include, types_exclude)

# Set triggers when the lists receive the current focus
self.list_include.clicked.connect(partial(self.clear_focus, [self.list_ignore, self.list_exclude]))
self.list_exclude.clicked.connect(partial(self.clear_focus, [self.list_ignore, self.list_include]))
self.list_ignore.clicked.connect(partial(self.clear_focus, [self.list_include, self.list_exclude]))

# Add instructions to the dialog box
instructions = QtWidgets.QLabel()
instructions.setText(_("Please select the contents of the image type 'Include' and 'Exclude' lists."))
instructions.setWordWrap(True)
instructions.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(instructions)

self.arrows_include = ArrowsColumn(
self.list_include,
self.list_ignore,
callback=self.set_buttons_enabled_state,
)

self.arrows_exclude = ArrowsColumn(
self.list_exclude,
self.list_ignore,
callback=self.set_buttons_enabled_state,
reverse=True
)

lists_layout = QtWidgets.QHBoxLayout()

include_list_layout = QtWidgets.QVBoxLayout()
include_list_layout.addWidget(QtWidgets.QLabel(_("Include types list")))
include_list_layout.addWidget(self.list_include)
lists_layout.addLayout(include_list_layout)

lists_layout.addWidget(self.arrows_include)

ignore_list_layout = QtWidgets.QVBoxLayout()
ignore_list_layout.addWidget(QtWidgets.QLabel(""))
ignore_list_layout.addWidget(self.list_ignore)
lists_layout.addLayout(ignore_list_layout)

lists_layout.addWidget(self.arrows_exclude)

exclude_list_layout = QtWidgets.QVBoxLayout()
exclude_list_layout.addWidget(QtWidgets.QLabel(_("Exclude types list")))
exclude_list_layout.addWidget(self.list_exclude)
lists_layout.addLayout(exclude_list_layout)

self.layout.addLayout(lists_layout)

# Add usage explanation to the dialog box
instructions = QtWidgets.QLabel()
instructions.setText(_(
"CAA images with an image type found in the 'Include' list will be downloaded and used "
"UNLESS they also have an image type found in the 'Exclude' list. Images with types "
"found in the 'Exclude' list will NEVER be used. Image types not appearing in the 'Include' "
"or 'Exclude' lists will not be considered when determining whether or not to download and "
"use a CAA image.\n")
)
instructions.setWordWrap(True)
instructions.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(instructions)

self.buttonbox = QtWidgets.QDialogButtonBox(self)
self.buttonbox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonbox.addButton(
StandardButton(StandardButton.OK), QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole)
self.buttonbox.addButton(StandardButton(StandardButton.CANCEL),
QtWidgets.QDialogButtonBox.ButtonRole.RejectRole)
self.buttonbox.addButton(
StandardButton(StandardButton.HELP), QtWidgets.QDialogButtonBox.ButtonRole.HelpRole)

extrabuttons = [
(N_("I&nclude all"), self.move_all_to_include_list),
(N_("E&xclude all"), self.move_all_to_exclude_list),
(N_("C&lear all"), self.move_all_to_ignore_list),
(N_("Restore &Defaults"), self.reset_to_defaults),
]
for label, callback in extrabuttons:
button = QtWidgets.QPushButton(_(label))
self.buttonbox.addButton(button, QtWidgets.QDialogButtonBox.ButtonRole.ActionRole)
button.clicked.connect(callback)

self.layout.addWidget(self.buttonbox)

self.buttonbox.accepted.connect(self.accept)
self.buttonbox.rejected.connect(self.reject)
self.buttonbox.helpRequested.connect(self.show_help)

self.set_buttons_enabled_state()

def move_all_to_include_list(self):
self.list_ignore.move_all_items(self.list_include)
self.list_exclude.move_all_items(self.list_include)
self.set_buttons_enabled_state()

def move_all_to_exclude_list(self):
self.list_ignore.move_all_items(self.list_exclude)
self.list_include.move_all_items(self.list_exclude)
self.set_buttons_enabled_state()

def move_all_to_ignore_list(self):
self.list_include.move_all_items(self.list_ignore)
self.list_exclude.move_all_items(self.list_ignore)
self.set_buttons_enabled_state()

def fill_lists(self, includes, excludes):
"""Fill dialog listboxes.
First clears the contents of the three listboxes, and then populates the listboxes
from the dictionary of standard CAA types, using the provided 'includes' and
'excludes' lists to determine the appropriate list for each type.
Arguments:
includes -- list of standard image types to place in the "Include" listbox
excludes -- list of standard image types to place in the "Exclude" listbox
"""
self.list_include.clear()
self.list_exclude.clear()
self.list_ignore.clear()
for name, title in self._known_types.items():
item = QtWidgets.QListWidgetItem(title)
item.setData(QtCore.Qt.ItemDataRole.UserRole, name)
if name in includes:
self.list_include.addItem(item)
elif name in excludes:
self.list_exclude.addItem(item)
else:
self.list_ignore.addItem(item)

@property
def included(self):
return list(self.list_include.all_items_data()) or ['front']

@property
def excluded(self):
return list(self.list_exclude.all_items_data()) or ['none']

def clear_focus(self, lists):
for temp_list in lists:
temp_list.clearSelection()
self.set_buttons_enabled_state()

def reset_to_defaults(self):
self.fill_lists(self._default_include, self._default_exclude)
self.set_buttons_enabled_state()

def set_buttons_enabled_state(self):
has_items_include = self.list_include.count()
has_items_exclude = self.list_exclude.count()
has_items_ignore = self.list_ignore.count()

has_selected_include = bool(self.list_include.selectedItems())
has_selected_exclude = bool(self.list_exclude.selectedItems())
has_selected_ignore = bool(self.list_ignore.selectedItems())

# "Include" list buttons
self.arrows_include.button_add.setEnabled(has_items_ignore and has_selected_ignore)
self.arrows_include.button_add_all.setEnabled(has_items_ignore)
self.arrows_include.button_remove.setEnabled(has_items_include and has_selected_include)
self.arrows_include.button_remove_all.setEnabled(has_items_include)

# "Exclude" list buttons
self.arrows_exclude.button_add.setEnabled(has_items_ignore and has_selected_ignore)
self.arrows_exclude.button_add_all.setEnabled(has_items_ignore)
self.arrows_exclude.button_remove.setEnabled(has_items_exclude and has_selected_exclude)
self.arrows_exclude.button_remove_all.setEnabled(has_items_exclude)


def display_caa_types_selector(**kwargs):
dialog = CAATypesSelectorDialog(**kwargs)
result = dialog.exec_()
return (dialog.included, dialog.excluded, result == QtWidgets.QDialog.DialogCode.Accepted)
6 changes: 3 additions & 3 deletions picard/ui/cdlookup.py
Expand Up @@ -53,10 +53,10 @@

class CDLookupDialog(PicardDialog):

dialog_header_state = "cdlookupdialog_header_state"
dialog_header_state = 'cdlookupdialog_header_state'

options = [
Option("persist", dialog_header_state, QtCore.QByteArray())
Option('persist', dialog_header_state, QtCore.QByteArray())
]

def __init__(self, releases, disc, parent=None):
Expand Down Expand Up @@ -126,7 +126,7 @@ def lookup(self):
lookup = self.tagger.get_file_lookup()
lookup.discid_submission(submission_url)
else:
log.error('No submission URL for disc ID "%s"', self.disc.id)
log.error("No submission URL for disc ID %s", self.disc.id)
super().accept()

@restore_method
Expand Down
7 changes: 5 additions & 2 deletions picard/ui/collectionmenu.py
Expand Up @@ -6,7 +6,7 @@
# Copyright (C) 2014-2015, 2018, 2020-2022 Laurent Monin
# Copyright (C) 2016-2017 Sambhav Kothari
# Copyright (C) 2018 Vishal Choudhary
# Copyright (C) 2018, 2022 Philipp Wolfer
# Copyright (C) 2018, 2022-2023 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand Down Expand Up @@ -170,4 +170,7 @@ def updateText(self):

def label(self):
c = self.collection
return ngettext("%s (%i release)", "%s (%i releases)", c.size) % (c.name, c.size)
return ngettext("%(name)s (%(count)i release)", "%(name)s (%(count)i releases)", c.size) % {
'name': c.name,
'count': c.size,
}
37 changes: 20 additions & 17 deletions picard/ui/coverartbox.py
Expand Up @@ -96,7 +96,7 @@ def __init__(self, active=False, drops=False, pixmap_cache=None, *args, **kwargs

def screen_changed(self, screen):
pixel_ratio = screen.devicePixelRatio()
log.debug('screen changed, pixel ratio %s', pixel_ratio)
log.debug("screen changed, pixel ratio %s", pixel_ratio)
if pixel_ratio != self.pixel_ratio:
self.pixel_ratio = pixel_ratio
self._update_default_pixmaps()
Expand All @@ -107,8 +107,8 @@ def screen_changed(self, screen):

def _update_default_pixmaps(self):
w, h = self.scaled(THUMBNAIL_WIDTH, THUMBNAIL_WIDTH)
self.shadow = self._load_cached_default_pixmap(':/images/CoverArtShadow.png', w, h)
self.file_missing_pixmap = self._load_cached_default_pixmap(':/images/image-missing.png', w, h)
self.shadow = self._load_cached_default_pixmap(":/images/CoverArtShadow.png", w, h)
self.file_missing_pixmap = self._load_cached_default_pixmap(":/images/image-missing.png", w, h)

def _load_cached_default_pixmap(self, pixmap_path, w, h):
key = hash((pixmap_path, self.pixel_ratio))
Expand Down Expand Up @@ -204,8 +204,10 @@ def set_data(self, data, force=False, has_common_images=True):
if len(self.data) == 1:
pixmap = QtGui.QPixmap()
try:
pixmap.loadFromData(self.data[0].data)
pixmap = self.decorate_cover(pixmap)
if pixmap.loadFromData(self.data[0].data):
pixmap = self.decorate_cover(pixmap)
else:
pixmap = self.file_missing_pixmap
except CoverArtImageIOError:
pixmap = self.file_missing_pixmap
else:
Expand Down Expand Up @@ -280,7 +282,8 @@ def calculate_cover_coordinates(pixmap, cx, cy):
else:
thumb = QtGui.QPixmap()
try:
thumb.loadFromData(image.data)
if not thumb.loadFromData(image.data):
thumb = self.file_missing_pixmap
except CoverArtImageIOError:
thumb = self.file_missing_pixmap
thumb = self.decorate_cover(thumb)
Expand All @@ -291,7 +294,7 @@ def calculate_cover_coordinates(pixmap, cx, cy):
if not has_common_images:
# Draw a golden highlight around the first cover to indicate that
# images are not common to all selected items
color = QtGui.QColor("darkgoldenrod")
color = QtGui.QColor('darkgoldenrod')
border_length = 10
for k in range(border_length):
color.setAlpha(255 - k * 255 // border_length)
Expand Down Expand Up @@ -322,7 +325,7 @@ def set_metadata(self, metadata):
self.set_data(data, has_common_images=has_common_images)
release = None
if metadata:
release = metadata.get("musicbrainz_albumid", None)
release = metadata.get('musicbrainz_albumid', None)
if release:
self.setActive(True)
text = _("View release on MusicBrainz")
Expand Down Expand Up @@ -550,7 +553,7 @@ def _try_load_remote_image(self, url, data):
)

config = get_config()
if config.setting["load_image_behavior"] == 'replace':
if config.setting['load_image_behavior'] == 'replace':
set_image = set_image_replace
debug_info = "Replacing with dropped %r in %r"
else:
Expand Down Expand Up @@ -616,7 +619,7 @@ def choose_local_file(self):

def set_load_image_behavior(self, behavior):
config = get_config()
config.setting["load_image_behavior"] = behavior
config.setting['load_image_behavior'] = behavior

def keep_original_images(self):
self.item.keep_original_images()
Expand All @@ -626,19 +629,19 @@ def keep_original_images(self):
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self)
if self.show_details_button.isVisible():
name = _('Show more details...')
name = _("Show more details…")
show_more_details_action = QtWidgets.QAction(name, self.parent)
show_more_details_action.triggered.connect(self.show_cover_art_info)
menu.addAction(show_more_details_action)

if self.orig_cover_art.isVisible():
name = _('Keep original cover art')
name = _("Keep original cover art")
use_orig_value_action = QtWidgets.QAction(name, self.parent)
use_orig_value_action.triggered.connect(self.keep_original_images)
menu.addAction(use_orig_value_action)

if self.item and self.item.can_show_coverart:
name = _('Choose local file...')
name = _("Choose local file…")
choose_local_file_action = QtWidgets.QAction(name, self.parent)
choose_local_file_action.triggered.connect(self.choose_local_file)
menu.addAction(choose_local_file_action)
Expand All @@ -647,20 +650,20 @@ def contextMenuEvent(self, event):
menu.addSeparator()

load_image_behavior_group = QtWidgets.QActionGroup(self.parent)
action = QtWidgets.QAction(_('Replace front cover art'), self.parent)
action = QtWidgets.QAction(_("Replace front cover art"), self.parent)
action.setCheckable(True)
action.triggered.connect(partial(self.set_load_image_behavior, behavior='replace'))
load_image_behavior_group.addAction(action)
config = get_config()
if config.setting["load_image_behavior"] == 'replace':
if config.setting['load_image_behavior'] == 'replace':
action.setChecked(True)
menu.addAction(action)

action = QtWidgets.QAction(_('Append front cover art'), self.parent)
action = QtWidgets.QAction(_("Append front cover art"), self.parent)
action.setCheckable(True)
action.triggered.connect(partial(self.set_load_image_behavior, behavior='append'))
load_image_behavior_group.addAction(action)
if config.setting["load_image_behavior"] == 'append':
if config.setting['load_image_behavior'] == 'append':
action.setChecked(True)
menu.addAction(action)

Expand Down
6 changes: 3 additions & 3 deletions picard/ui/edittagdialog.py
Expand Up @@ -33,12 +33,12 @@
)

from picard.const import (
RELEASE_COUNTRIES,
RELEASE_FORMATS,
RELEASE_PRIMARY_GROUPS,
RELEASE_SECONDARY_GROUPS,
RELEASE_STATUS,
)
from picard.const.countries import RELEASE_COUNTRIES
from picard.util.tags import TAG_NAMES

from picard.ui import PicardDialog
Expand Down Expand Up @@ -66,9 +66,9 @@ def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
completer = None
if tag in {'date', 'originaldate', 'releasedate'}:
editor.setPlaceholderText(_('YYYY-MM-DD'))
editor.setPlaceholderText(_("YYYY-MM-DD"))
elif tag == 'originalyear':
editor.setPlaceholderText(_('YYYY'))
editor.setPlaceholderText(_("YYYY"))
elif tag == 'releasetype':
completer = QtWidgets.QCompleter(AUTOCOMPLETE_RELEASE_TYPES, editor)
elif tag == 'releasestatus':
Expand Down
32 changes: 16 additions & 16 deletions picard/ui/filebrowser.py
Expand Up @@ -50,19 +50,19 @@

def _macos_find_root_volume():
try:
for entry in os.scandir('/Volumes/'):
if entry.is_symlink() and os.path.realpath(entry.path) == '/':
for entry in os.scandir("/Volumes/"):
if entry.is_symlink() and os.path.realpath(entry.path) == "/":
return entry.path
except OSError:
log.warning('Could not detect macOS boot volume', exc_info=True)
log.warning("Could not detect macOS boot volume", exc_info=True)
return None


def _macos_extend_root_volume_path(path):
if not path.startswith('/Volumes/'):
if not path.startswith("/Volumes/"):
root_volume = _macos_find_root_volume()
if root_volume:
if path.startswith('/'):
if path.startswith("/"):
path = path[1:]
path = os.path.join(root_volume, path)
return path
Expand All @@ -77,8 +77,8 @@ def _macos_extend_root_volume_path(path):
class FileBrowser(QtWidgets.QTreeView):

options = [
TextOption("persist", "current_browser_path", _default_current_browser_path),
BoolOption("persist", "show_hidden_files", False),
TextOption('persist', 'current_browser_path', _default_current_browser_path),
BoolOption('persist', 'show_hidden_files', False),
]

def __init__(self, parent):
Expand All @@ -94,7 +94,7 @@ def __init__(self, parent):
self.toggle_hidden_action = QtWidgets.QAction(_("Show &hidden files"), self)
self.toggle_hidden_action.setCheckable(True)
config = get_config()
self.toggle_hidden_action.setChecked(config.persist["show_hidden_files"])
self.toggle_hidden_action.setChecked(config.persist['show_hidden_files'])
self.toggle_hidden_action.toggled.connect(self.show_hidden)
self.addAction(self.toggle_hidden_action)
self.set_as_starting_directory_action = QtWidgets.QAction(_("&Set as starting directory"), self)
Expand Down Expand Up @@ -143,7 +143,7 @@ def _set_model(self):
def _set_model_filter(self):
config = get_config()
model_filter = QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.Files | QtCore.QDir.Filter.Drives | QtCore.QDir.Filter.NoDotAndDotDot
if config.persist["show_hidden_files"]:
if config.persist['show_hidden_files']:
model_filter |= QtCore.QDir.Filter.Hidden
self.model().setFilter(model_filter)

Expand Down Expand Up @@ -188,26 +188,26 @@ def focusInEvent(self, event):

def show_hidden(self, state):
config = get_config()
config.persist["show_hidden_files"] = state
config.persist['show_hidden_files'] = state
self._set_model_filter()

def save_state(self):
indexes = self.selectedIndexes()
if indexes:
path = self.model().filePath(indexes[0])
config = get_config()
config.persist["current_browser_path"] = os.path.normpath(path)
config.persist['current_browser_path'] = os.path.normpath(path)

def restore_state(self):
pass

def _restore_state(self):
config = get_config()
if config.setting["starting_directory"]:
path = config.setting["starting_directory_path"]
if config.setting['starting_directory']:
path = config.setting['starting_directory_path']
scrolltype = QtWidgets.QAbstractItemView.ScrollHint.PositionAtTop
else:
path = config.persist["current_browser_path"]
path = config.persist['current_browser_path']
scrolltype = QtWidgets.QAbstractItemView.ScrollHint.PositionAtCenter
if path:
index = self.model().index(find_existing_path(path))
Expand Down Expand Up @@ -241,11 +241,11 @@ def move_files_here(self):
return
config = get_config()
path = self.model().filePath(indexes[0])
config.setting["move_files_to"] = self._get_destination_from_path(path)
config.setting['move_files_to'] = self._get_destination_from_path(path)

def set_as_starting_directory(self):
indexes = self.selectedIndexes()
if indexes:
config = get_config()
path = self.model().filePath(indexes[0])
config.setting["starting_directory_path"] = self._get_destination_from_path(path)
config.setting['starting_directory_path'] = self._get_destination_from_path(path)
56 changes: 31 additions & 25 deletions picard/ui/infodialog.py
Expand Up @@ -11,7 +11,7 @@
# Copyright (C) 2016-2017 Sambhav Kothari
# Copyright (C) 2017-2019 Antonio Larrosa
# Copyright (C) 2018 Vishal Choudhary
# Copyright (C) 2018-2022 Philipp Wolfer
# Copyright (C) 2018-2023 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand Down Expand Up @@ -146,7 +146,7 @@ def __init__(self, obj, parent=None):

# Add the ArtworkTable to the ui
self.ui.artwork_table = ArtworkTable(self.display_existing_artwork)
self.ui.artwork_table.setObjectName("artwork_table")
self.ui.artwork_table.setObjectName('artwork_table')
self.ui.artwork_tab.layout().addWidget(self.ui.artwork_table)
self.setTabOrder(self.ui.tabWidget, self.ui.artwork_table)
self.setTabOrder(self.ui.artwork_table, self.ui.buttonBox)
Expand All @@ -168,7 +168,7 @@ def _display_error_tab(self):

def _show_errors(self, errors):
if errors:
color = interface_colors.get_color("log_error")
color = interface_colors.get_color('log_error')
text = '<br />'.join(map(
lambda s: '<font color="%s">%s</font>' % (color, text_as_html(s)), errors))
self.ui.error.setText(text + '<hr />')
Expand Down Expand Up @@ -207,14 +207,20 @@ def _display_artwork(self, images, col):
pixmap.loadFromData(data)
item.setToolTip(
_("Double-click to open in external viewer\n"
"Temporary file: %s\n"
"Source: %s") % (image.tempfile_filename, image.source))
"Temporary file: %(tempfile)s\n"
"Source: %(sourcefile)s") % {
'tempfile': image.tempfile_filename,
'sourcefile': image.source,
})
except CoverArtImageIOError:
log.error(traceback.format_exc())
pixmap = missing_pixmap
item.setToolTip(
_("Missing temporary file: %s\n"
"Source: %s") % (image.tempfile_filename, image.source))
_("Missing temporary file: %(tempfile)s\n"
"Source: %(sourcefile)s") % {
'tempfile': image.tempfile_filename,
'sourcefile': image.source,
})
infos = []
if image.comment:
infos.append(image.comment)
Expand Down Expand Up @@ -277,38 +283,38 @@ def show_item(self, item):

def format_file_info(file_):
info = []
info.append((_('Filename:'), file_.filename))
info.append((_("Filename:"), file_.filename))
if '~format' in file_.orig_metadata:
info.append((_('Format:'), file_.orig_metadata['~format']))
info.append((_("Format:"), file_.orig_metadata['~format']))
try:
size = os.path.getsize(encode_filename(file_.filename))
sizestr = "%s (%s)" % (bytes2human.decimal(size), bytes2human.binary(size))
info.append((_('Size:'), sizestr))
info.append((_("Size:"), sizestr))
except BaseException:
pass
if file_.orig_metadata.length:
info.append((_('Length:'), format_time(file_.orig_metadata.length)))
info.append((_("Length:"), format_time(file_.orig_metadata.length)))
if '~bitrate' in file_.orig_metadata:
info.append((_('Bitrate:'), '%s kbps' % file_.orig_metadata['~bitrate']))
info.append((_("Bitrate:"), "%s kbps" % file_.orig_metadata['~bitrate']))
if '~sample_rate' in file_.orig_metadata:
info.append((_('Sample rate:'), '%s Hz' % file_.orig_metadata['~sample_rate']))
info.append((_("Sample rate:"), "%s Hz" % file_.orig_metadata['~sample_rate']))
if '~bits_per_sample' in file_.orig_metadata:
info.append((_('Bits per sample:'), str(file_.orig_metadata['~bits_per_sample'])))
info.append((_("Bits per sample:"), str(file_.orig_metadata['~bits_per_sample'])))
if '~channels' in file_.orig_metadata:
ch = file_.orig_metadata['~channels']
if ch == '1':
ch = _('Mono')
ch = _("Mono")
elif ch == '2':
ch = _('Stereo')
info.append((_('Channels:'), ch))
ch = _("Stereo")
info.append((_("Channels:"), ch))
return '<br/>'.join(map(lambda i: '<b>%s</b> %s' %
(escape(i[0]), escape(i[1])), info))


def format_tracklist(cluster):
info = []
info.append("<b>%s</b> %s" % (_('Album:'), escape(cluster.metadata["album"])))
info.append("<b>%s</b> %s" % (_('Artist:'), escape(cluster.metadata["albumartist"])))
info.append('<b>%s</b> %s' % (_("Album:"), escape(cluster.metadata['album'])))
info.append('<b>%s</b> %s' % (_("Artist:"), escape(cluster.metadata['albumartist'])))
info.append("")
TrackListItem = namedtuple('TrackListItem', 'number, title, artist, length')
tracklists = defaultdict(list)
Expand All @@ -318,9 +324,9 @@ def format_tracklist(cluster):
objlist = cluster.iterfiles(False)
for obj_ in objlist:
m = obj_.metadata
artist = m["artist"] or m["albumartist"] or cluster.metadata["albumartist"]
track = TrackListItem(m["tracknumber"], m["title"], artist,
m["~length"])
artist = m['artist'] or m['albumartist'] or cluster.metadata['albumartist']
track = TrackListItem(m['tracknumber'], m['title'], artist,
m['~length'])
tracklists[obj_.discnumber].append(track)

def sorttracknum(track):
Expand All @@ -338,9 +344,9 @@ def sorttracknum(track):
for discnumber in sorted(tracklists):
tracklist = tracklists[discnumber]
if ndiscs > 1:
info.append("<b>%s</b>" % (_('Disc %d') % discnumber))
lines = ["%s %s - %s (%s)" % item for item in sorted(tracklist, key=sorttracknum)]
info.append("<b>%s</b><br />%s<br />" % (_('Tracklist:'),
info.append('<b>%s</b>' % (_("Disc %d") % discnumber))
lines = ['%s %s - %s (%s)' % item for item in sorted(tracklist, key=sorttracknum)]
info.append('<b>%s</b><br />%s<br />' % (_("Tracklist:"),
'<br />'.join(escape(s).replace(' ', '&nbsp;') for s in lines)))
return '<br/>'.join(info)

Expand Down
2 changes: 1 addition & 1 deletion picard/ui/infostatus.py
Expand Up @@ -60,7 +60,7 @@ def _init_labels(self):
self._init_tooltips()

def _create_icons(self):
self.icon_eta = QtGui.QIcon(':/images/22x22/hourglass.png')
self.icon_eta = QtGui.QIcon(":/images/22x22/hourglass.png")
self.icon_cd = icontheme.lookup('media-optical')
self.icon_file = QtGui.QIcon(":/images/file.png")
self.icon_file_pending = QtGui.QIcon(":/images/file-pending.png")
Expand Down
6 changes: 3 additions & 3 deletions picard/ui/item.py
Expand Up @@ -86,15 +86,15 @@ def load(self, priority=False, refresh=False):
def tracknumber(self):
"""The track number as an int."""
try:
return int(self.metadata["tracknumber"])
return int(self.metadata['tracknumber'])
except BaseException:
return 0

@property
def discnumber(self):
"""The disc number as an int."""
try:
return int(self.metadata["discnumber"])
return int(self.metadata['discnumber'])
except BaseException:
return 0

Expand All @@ -105,7 +105,7 @@ def errors(self):
return self._errors

def error_append(self, msg):
log.error('%r: %s', self, msg)
log.error("%r: %s", self, msg)
self.errors.append(msg)

def clear_errors(self):
Expand Down
78 changes: 39 additions & 39 deletions picard/ui/itemviews.py
Expand Up @@ -158,24 +158,24 @@ class MainPanel(QtWidgets.QSplitter):
options = []

columns = [
(N_('Title'), 'title'),
(N_('Length'), '~length'),
(N_('Artist'), 'artist'),
(N_('Album Artist'), 'albumartist'),
(N_('Composer'), 'composer'),
(N_('Album'), 'album'),
(N_('Disc Subtitle'), 'discsubtitle'),
(N_('Track No.'), 'tracknumber'),
(N_('Disc No.'), 'discnumber'),
(N_('Catalog No.'), 'catalognumber'),
(N_('Barcode'), 'barcode'),
(N_('Media'), 'media'),
(N_('Genre'), 'genre'),
(N_('Fingerprint status'), '~fingerprint'),
(N_('Date'), 'date'),
(N_('Original Release Date'), 'originaldate'),
(N_('Release Date'), 'releasedate'),
(N_('Cover'), 'covercount'),
(N_("Title"), 'title'),
(N_("Length"), '~length'),
(N_("Artist"), 'artist'),
(N_("Album Artist"), 'albumartist'),
(N_("Composer"), 'composer'),
(N_("Album"), 'album'),
(N_("Disc Subtitle"), 'discsubtitle'),
(N_("Track No."), 'tracknumber'),
(N_("Disc No."), 'discnumber'),
(N_("Catalog No."), 'catalognumber'),
(N_("Barcode"), 'barcode'),
(N_("Media"), 'media'),
(N_("Genre"), 'genre'),
(N_("Fingerprint status"), '~fingerprint'),
(N_("Date"), 'date'),
(N_("Original Release Date"), 'originaldate'),
(N_("Release Date"), 'releasedate'),
(N_("Cover"), 'covercount'),
]

_column_indexes = {column[1]: i for i, column in enumerate(columns)}
Expand Down Expand Up @@ -396,12 +396,12 @@ def contextMenuEvent(self, event):
menu.addAction(action)

menu.addSeparator()
restore_action = QtWidgets.QAction(_('Restore default columns'), parent)
restore_action = QtWidgets.QAction(_("Restore default columns"), parent)
restore_action.setEnabled(not self.is_locked)
restore_action.triggered.connect(self.restore_defaults)
menu.addAction(restore_action)

lock_action = QtWidgets.QAction(_('Lock columns'), parent)
lock_action = QtWidgets.QAction(_("Lock columns"), parent)
lock_action.setCheckable(True)
lock_action.setChecked(self.is_locked)
lock_action.toggled.connect(self.lock)
Expand Down Expand Up @@ -543,9 +543,9 @@ def contextMenuEvent(self, event):
releases_menu = QtWidgets.QMenu(_("&Other versions"), menu)
menu.addSeparator()
menu.addMenu(releases_menu)
loading = releases_menu.addAction(_('Loading...'))
loading = releases_menu.addAction(_("Loading…"))
loading.setDisabled(True)
action_more = releases_menu.addAction(_('Show &more details...'))
action_more = releases_menu.addAction(_("Show &more details…"))
action_more.triggered.connect(self.window.album_other_versions_action.trigger)
bottom_separator = True

Expand All @@ -562,8 +562,8 @@ def _add_other_versions():
versions = obj.release_group.versions

album_tracks_count = obj.get_num_total_files() or len(obj.tracks)
preferred_countries = set(config.setting["preferred_release_countries"])
preferred_formats = set(config.setting["preferred_release_formats"])
preferred_countries = set(config.setting['preferred_release_countries'])
preferred_formats = set(config.setting['preferred_release_formats'])
ORDER_BEFORE, ORDER_AFTER = 0, 1

alternatives = []
Expand Down Expand Up @@ -610,7 +610,7 @@ def _add_other_versions():
else:
releases_menu.setEnabled(False)

if config.setting["enable_ratings"] and \
if config.setting['enable_ratings'] and \
len(self.window.selected_objects) == 1 and isinstance(obj, Track):
menu.addSeparator()
action = QtWidgets.QWidgetAction(menu)
Expand All @@ -626,7 +626,7 @@ def _add_other_versions():
menu.addSeparator()
menu.addMenu(CollectionMenu(selected_albums, _("Collections"), menu))

scripts = config.setting["list_of_scripts"]
scripts = config.setting['list_of_scripts']

if plugin_actions or scripts:
menu.addSeparator()
Expand Down Expand Up @@ -692,7 +692,7 @@ def supportedDropActions(self):

def mimeTypes(self):
"""List of MIME types accepted by this view."""
return ["text/uri-list", "application/picard.album-list"]
return ['text/uri-list', 'application/picard.album-list']

def dragEnterEvent(self, event):
super().dragEnterEvent(event)
Expand Down Expand Up @@ -733,7 +733,7 @@ def mimeData(self, items):
elif obj.iterfiles:
files.extend([url(f.filename) for f in obj.iterfiles()])
mimeData = QtCore.QMimeData()
mimeData.setData("application/picard.album-list", "\n".join(album_ids).encode())
mimeData.setData('application/picard.album-list', '\n'.join(album_ids).encode())
if files:
mimeData.setUrls(files)
return mimeData
Expand All @@ -753,14 +753,14 @@ def drop_urls(urls, target, move_to_multi_tracks=True):
tagger = QtCore.QObject.tagger
for url in urls:
log.debug("Dropped the URL: %r", url.toString(QtCore.QUrl.UrlFormattingOption.RemoveUserInfo))
if url.scheme() == "file" or not url.scheme():
filename = normpath(url.toLocalFile().rstrip("\0"))
if url.scheme() == 'file' or not url.scheme():
filename = normpath(url.toLocalFile().rstrip('\0'))
file = tagger.files.get(filename)
if file:
files.append(file)
else:
new_paths.append(filename)
elif url.scheme() in {"http", "https"}:
elif url.scheme() in {'http', 'https'}:
file_lookup = tagger.get_file_lookup()
file_lookup.mbid_lookup(url.path(), browser_fallback=False)
if files:
Expand Down Expand Up @@ -804,7 +804,7 @@ def dropMimeData(self, parent, index, data, action):
QtCore.QTimer.singleShot(0, partial(self.drop_urls, urls, target, self._move_to_multi_tracks))
handled = True
# application/picard.album-list
albums = data.data("application/picard.album-list")
albums = data.data('application/picard.album-list')
if albums:
album_ids = bytes(albums).decode().split("\n")
log.debug("Dropped albums = %r", album_ids)
Expand Down Expand Up @@ -843,8 +843,8 @@ def moveCursor(self, action, modifiers):

class FileTreeView(BaseTreeView):

header_state = Option("persist", "file_view_header_state", QtCore.QByteArray())
header_locked = BoolOption("persist", "file_view_header_locked", False)
header_state = Option('persist', 'file_view_header_state', QtCore.QByteArray())
header_locked = BoolOption('persist', 'file_view_header_locked', False)

def __init__(self, window, parent=None):
super().__init__(window, parent)
Expand All @@ -869,13 +869,13 @@ def remove_file_cluster(self, cluster):
self.set_clusters_text()

def set_clusters_text(self):
self.clusters.setText(MainPanel.TITLE_COLUMN, '%s (%d)' % (_("Clusters"), len(self.tagger.clusters)))
self.clusters.setText(MainPanel.TITLE_COLUMN, "%s (%d)" % (_("Clusters"), len(self.tagger.clusters)))


class AlbumTreeView(BaseTreeView):

header_state = Option("persist", "album_view_header_state", QtCore.QByteArray())
header_locked = BoolOption("persist", "album_view_header_locked", False)
header_state = Option('persist', 'album_view_header_state', QtCore.QByteArray())
header_locked = BoolOption('persist', 'album_view_header_locked', False)

def __init__(self, window, parent=None):
super().__init__(window, parent)
Expand Down Expand Up @@ -1194,10 +1194,10 @@ def decide_fingerprint_icon_info(file):
if getattr(file, 'acoustid_fingerprint', None):
if QtCore.QObject.tagger.acoustidmanager.is_submitted(file):
icon = FileItem.icon_fingerprint_gray
tooltip = _('Fingerprint has already been submitted')
tooltip = _("Fingerprint has already been submitted")
else:
icon = FileItem.icon_fingerprint
tooltip = _('Unsubmitted fingerprint')
tooltip = _("Unsubmitted fingerprint")
else:
icon = QtGui.QIcon()
tooltip = _('No fingerprint was calculated for this file, use "Scan" or "Generate AcoustID Fingerprints" to calculate the fingerprint.')
Expand Down
6 changes: 3 additions & 3 deletions picard/ui/logview.py
Expand Up @@ -162,7 +162,7 @@ def set_verbosity(self, level):
class LogView(LogViewCommon):

options = [
IntOption("setting", "log_verbosity", logging.WARNING),
IntOption('setting', 'log_verbosity', logging.WARNING),
]

def __init__(self, parent=None):
Expand Down Expand Up @@ -211,7 +211,7 @@ def __init__(self, parent=None):
self.clear_log_button.clicked.connect(self._clear_log_do)

# save as
self.save_log_as_button = QtWidgets.QPushButton(_("Save As..."))
self.save_log_as_button = QtWidgets.QPushButton(_("Save As…"))
self.hbox.addWidget(self.save_log_as_button)
self.save_log_as_button.clicked.connect(self._save_log_as_do)

Expand Down Expand Up @@ -271,7 +271,7 @@ def _save_log_as_do(self):
return

writer = QtGui.QTextDocumentWriter(path)
writer.setFormat(b"plaintext")
writer.setFormat(b'plaintext')
success = writer.write(self.doc)
if not success:
QtWidgets.QMessageBox.critical(
Expand Down