def main(localedir=None, autoupdate=True): # Some libs (ie. Phonon) require those to be set QtWidgets.QApplication.setApplicationName(PICARD_APP_NAME) QtWidgets.QApplication.setOrganizationName(PICARD_ORG_NAME) signal.signal(signal.SIGINT, signal.SIG_DFL) picard_args, unparsed_args = process_picard_args() if picard_args.version: return version() if picard_args.long_version: return longversion() tagger = Tagger(picard_args, unparsed_args, localedir, autoupdate) # Initialize Qt default translations translator = QtCore.QTranslator() locale = QtCore.QLocale() translation_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath) log.debug("Looking for Qt locale %s in %s", locale.name(), translation_path) if translator.load(locale, "qtbase_", directory=translation_path): tagger.installTranslator(translator) else: log.warning('Error loading Qt locale %s', locale.name()) tagger.startTimer(1000) sys.exit(tagger.run())
def on_remote_image_fetched(self, url, data, reply, error, fallback_data=None): if error: log.error("Failed loading remote image from %s: %s", url, reply.errorString()) if fallback_data: self._load_fallback_data(url, fallback_data) return data = bytes(data) mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) # Some sites return a mime type with encoding like "image/jpeg; charset=UTF-8" mime = mime.split(';')[0] url_query = QtCore.QUrlQuery(url.query()) # If mime indicates only binary data we can try to guess the real mime type if mime in ('application/octet-stream', 'binary/data'): mime = imageinfo.identify(data)[2] if mime in ('image/jpeg', 'image/png'): self.load_remote_image(url, mime, data) elif url_query.hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl(url_query.queryItemValue("imgurl", QtCore.QUrl.FullyDecoded)) self.fetch_remote_image(url) elif url_query.hasQueryItem("mediaurl"): # Bing uses mediaurl url = QtCore.QUrl(url_query.queryItemValue("mediaurl", QtCore.QUrl.FullyDecoded)) self.fetch_remote_image(url) else: log.warning("Can't load remote image with MIME-Type %s", mime) if fallback_data: self._load_fallback_data(url, fallback_data)
def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" self.album._requests -= 1 if error: self.album.error_append('Coverart error: %s' % (http.errorString())) elif len(data) < 1000: log.warning("Not enough data, skipping %s" % coverartimage) else: self._message(N_( "Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s" ), { 'type': coverartimage.types_as_string(), 'albumid': self.album.id, 'host': coverartimage.host }, echo=None) try: self._set_metadata(coverartimage, data) except CoverArtImageIOError: # It doesn't make sense to store/download more images if we can't # save them in the temporary folder, abort. return self.next_in_queue()
def on_remote_image_fetched(self, url, data, reply, error, fallback_data=None): data = bytes(data) mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) url_query = QtCore.QUrlQuery(url.query()) if mime in ('image/jpeg', 'image/png'): self.load_remote_image(url, mime, data) elif url_query.hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl( url_query.queryItemValue("imgurl", QtCore.QUrl.FullyDecoded)) self.fetch_remote_image(url) else: log.warning("Can't load remote image with MIME-Type %s", mime) if fallback_data: # Tests for image format obtained from file-magic try: mime = imageinfo.identify(fallback_data)[2] except imageinfo.IdentificationError as e: log.error("Unable to identify dropped data format: %s" % e) else: self.load_remote_image(url, mime, fallback_data) log.debug("Trying the dropped %s data", mime)
def create_action_toolbar(self): if getattr(self, 'toolbar', None): self.toolbar.clear() self.removeToolBar(self.toolbar) self.toolbar = toolbar = QtGui.QToolBar(_(u"Actions")) self.insertToolBar(self.search_toolbar, self.toolbar) self.toolbar_toggle_action = self.toolbar.toggleViewAction() self.update_toolbar_style() toolbar.setObjectName("main_toolbar") def add_toolbar_action(action): toolbar.addAction(action) widget = toolbar.widgetForAction(action) widget.setFocusPolicy(QtCore.Qt.TabFocus) widget.setAttribute(QtCore.Qt.WA_MacShowFocusRect) for action in config.setting['toolbar_layout']: if action not in ('cd_lookup_action', 'separator'): try: add_toolbar_action(getattr(self, action)) except AttributeError: log.warning('Warning: Unknown action name "%r" found in config. Ignored.', action) elif action == 'cd_lookup_action': add_toolbar_action(self.cd_lookup_action) drives = get_cdrom_drives() if len(drives) > 1: self.cd_lookup_menu = QtGui.QMenu() for drive in drives: self.cd_lookup_menu.addAction(drive) self.cd_lookup_menu.triggered.connect(self.tagger.lookup_cd) button = toolbar.widgetForAction(self.cd_lookup_action) button.setPopupMode(QtGui.QToolButton.MenuButtonPopup) button.setMenu(self.cd_lookup_menu) elif action == 'separator': toolbar.addSeparator()
def create_work_and_movement_from_title(work): """ Attempts to parse work.title in the form "<Work>: <Number>. <Movement>", where <Number> is in Roman numerals. Sets the `is_movement` and `part_number` properties on `work` and creates a `parent` work if not already present. """ title = work.title match = parse_work_name(title) if match: work.title = match.group('movement') work.is_movement = True try: number = number_to_int(match.group('movementnumber')) except ValueError as e: log.error(e) number = 0 if not work.part_number: work.part_number = number elif work.part_number != number: log.warning( 'Movement number mismatch for "%s": %s != %i' % (title, match.group('movementnumber'), work.part_number)) if not work.parent: work.parent = Work(match.group('work')) work.parent.is_work = True elif work.parent.title != match.group('work'): log.warning('Movement work name mismatch for "%s": "%s" != "%s"' % (title, match.group('work'), work.parent.title)) return work
def process_metadata(self, album, metadata, track, release): if not config.setting['happidev_apikey']: error = 'API key is missing, please provide a valid value' log.warning('{}: {}'.format(PLUGIN_NAME, error)) return artist = metadata['artist'] title = metadata['title'] if not (artist and title): log.debug( '{}: both artist and title are required to obtain lyrics'. format(PLUGIN_NAME)) return path = '/v1/music' queryargs = { 'q': '"{}" "{}"'.format(artist, title), 'lyrics': 'true', 'type': 'track', } album._requests += 1 log.debug('{}: GET {}?{}'.format(PLUGIN_NAME, quote(path), urlencode(queryargs))) self._request(album.tagger.webservice, path, partial(self.process_search_response, album, metadata), queryargs)
def extract_and_submit_acousticbrainz_features(self, objs): """Extract AcousticBrainz features and submit them.""" if not self.ab_extractor.available(): return for file in iter_files_from_objects(objs): # Skip unmatched files if not file.can_extract(): log.warning( "AcousticBrainz requires a MusicBrainz Recording ID, but file does not have it: %s" % file.filename) # And process matched ones else: file.set_pending() # Check if file was either already processed or sent to the AcousticBrainz server if file.acousticbrainz_features_file: results = (file.acousticbrainz_features_file, 0, "Writing results") ab_extractor_callback(self, file, results, False) elif file.acousticbrainz_is_duplicate: results = (None, 0, "Duplicate") ab_extractor_callback(self, file, results, False) else: file.acousticbrainz_error = False # Launch the acousticbrainz on a separate process log.debug("Extracting AcousticBrainz features from %s" % file.filename) ab_feature_extraction( self, file.metadata["musicbrainz_recordingid"], file.filename, partial(ab_extractor_callback, self, file))
def process_search_response(self, album, metadata, response, reply, error): if self._handle_error(album, error, response): log.warning('{}: lyrics NOT found for track "{}" by {}'.format( PLUGIN_NAME, metadata['title'], metadata['artist'])) return try: lyrics_url = response['result'][0]['api_lyrics'] log.debug('{}: lyrics found for track "{}" by {} at {}'.format( PLUGIN_NAME, metadata['title'], metadata['artist'], lyrics_url)) path = urlparse(lyrics_url).path except (TypeError, KeyError, ValueError): log.warn( '{}: failed parsing search response for "{}" by {}'.format( PLUGIN_NAME, metadata['title'], metadata['artist']), exc_info=True) album._requests -= 1 album._finalize_loading(None) return self._request(album.tagger.webservice, path, partial(self.process_lyrics_response, album, metadata), important=True)
def main(localedir=None, autoupdate=True): # Some libs (ie. Phonon) require those to be set QtWidgets.QApplication.setApplicationName(PICARD_APP_NAME) QtWidgets.QApplication.setOrganizationName(PICARD_ORG_NAME) signal.signal(signal.SIGINT, signal.SIG_DFL) picard_args, unparsed_args = process_picard_args() if picard_args.version: return version() if picard_args.long_version: return longversion() tagger = Tagger(picard_args, unparsed_args, localedir, autoupdate) # Initialize Qt default translations translator = QtCore.QTranslator() locale = QtCore.QLocale() translation_path = QtCore.QLibraryInfo.location( QtCore.QLibraryInfo.TranslationsPath) log.debug("Looking for Qt locale %s in %s", locale.name(), translation_path) if translator.load(locale, "qtbase_", directory=translation_path): tagger.installTranslator(translator) else: log.warning('Error loading Qt locale %s', locale.name()) tagger.startTimer(1000) sys.exit(tagger.run())
def load_plugindir(self, plugindir): plugindir = os.path.normpath(plugindir) if not os.path.isdir(plugindir): log.warning("Plugin directory %r doesn't exist", plugindir) return # first, handle eventual plugin updates for updatepath in [os.path.join(plugindir, file) for file in os.listdir(plugindir) if file.endswith(".update")]: path = os.path.splitext(updatepath)[0] name = is_zip(path) if not name: name = _plugin_name_from_path(path) if name: self.remove_plugin(name) os.rename(updatepath, path) log.debug("Updating plugin %r (%r))", name, path) else: log.error("Cannot get plugin name from %r", updatepath) # now load found plugins names = set() for path in [os.path.join(plugindir, file) for file in os.listdir(plugindir)]: name = is_zip(path) if not name: name = _plugin_name_from_path(path) if name: names.add(name) log.debug("Looking for plugins in directory %r, %d names found", plugindir, len(names)) for name in sorted(names): self.load_plugin(name, plugindir)
def load_remote_image(self, url, mime, data): try: coverartimage = CoverArtImage( url=url.toString(), data=data ) except CoverArtImageError as e: log.warning("Can't load image: %s" % unicode(e)) return pixmap = QtGui.QPixmap() pixmap.loadFromData(data) self.__set_data([mime, data], pixmap=pixmap) if isinstance(self.item, Album): album = self.item album.metadata.append_image(coverartimage) for track in album.tracks: track.metadata.append_image(coverartimage) for file in album.iterfiles(): file.metadata.append_image(coverartimage) elif isinstance(self.item, Track): track = self.item track.metadata.append_image(coverartimage) for file in track.iterfiles(): file.metadata.append_image(coverartimage) elif isinstance(self.item, File): file = self.item file.metadata.append_image(coverartimage)
def load_remote_image(self, url, mime, data): try: coverartimage = CoverArtImage( url=url.toString(), data=data ) except CoverArtImageError as e: log.warning("Can't load image: %s" % unicode(e)) return if isinstance(self.item, Album): album = self.item album.metadata.append_image(coverartimage) for track in album.tracks: track.metadata.append_image(coverartimage) for file in album.iterfiles(): file.metadata.append_image(coverartimage) file.update() elif isinstance(self.item, Track): track = self.item track.metadata.append_image(coverartimage) for file in track.iterfiles(): file.metadata.append_image(coverartimage) file.update() elif isinstance(self.item, File): file = self.item file.metadata.append_image(coverartimage) file.update() self.cover_art.set_metadata(self.item.metadata) self.show()
def load_plugindir(self, plugindir): plugindir = os.path.normpath(plugindir) if not os.path.isdir(plugindir): log.warning("Plugin directory %r doesn't exist", plugindir) return # first, handle eventual plugin updates for updatepath in [ os.path.join(plugindir, file) for file in os.listdir(plugindir) if file.endswith('.update') ]: path = os.path.splitext(updatepath)[0] name = is_zip(path) if not name: name = _plugin_name_from_path(path) if name: self.remove_plugin(name) os.rename(updatepath, path) log.debug('Updating plugin %r (%r))', name, path) else: log.error('Cannot get plugin name from %r', updatepath) # now load found plugins names = set() for path in [ os.path.join(plugindir, file) for file in os.listdir(plugindir) ]: name = is_zip(path) if not name: name = _plugin_name_from_path(path) if name: names.add(name) log.debug("Looking for plugins in directory %r, %d names found", plugindir, len(names)) for name in sorted(names): self.load_plugin(name, plugindir)
def normpath(path): try: path = os.path.realpath(path) except OSError as why: # realpath can fail if path does not exist or is not accessible log.warning('Failed getting realpath for "%s": %s', path, why) return os.path.normpath(path)
def create_action_toolbar(self): if self.toolbar: self.toolbar.clear() self.removeToolBar(self.toolbar) self.toolbar = toolbar = QtWidgets.QToolBar(_("Actions")) self.insertToolBar(self.search_toolbar, self.toolbar) self.update_toolbar_style() toolbar.setObjectName("main_toolbar") def add_toolbar_action(action): toolbar.addAction(action) widget = toolbar.widgetForAction(action) widget.setFocusPolicy(QtCore.Qt.TabFocus) widget.setAttribute(QtCore.Qt.WA_MacShowFocusRect) for action in config.setting['toolbar_layout']: if action == 'cd_lookup_action': add_toolbar_action(self.cd_lookup_action) if len(self.cd_lookup_menu.actions()) > 1: button = toolbar.widgetForAction(self.cd_lookup_action) button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) button.setMenu(self.cd_lookup_menu) elif action == 'separator': toolbar.addSeparator() else: try: add_toolbar_action(getattr(self, action)) except AttributeError: log.warning('Warning: Unknown action name "%r" found in config. Ignored.', action) self.show_toolbar()
def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" self.album._requests -= 1 if error: self.album.error_append(u'Coverart error: %s' % (unicode(http.errorString()))) elif len(data) < 1000: log.warning("Not enough data, skipping %s" % coverartimage) else: self._message( N_("Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s"), { 'type': coverartimage.types_as_string(), 'albumid': self.album.id, 'host': coverartimage.host }, echo=None ) try: self._set_metadata(coverartimage, data) except CoverArtImageIOError: # It doesn't make sense to store/download more images if we can't # save them in the temporary folder, abort. return self.next_in_queue()
def create_action_toolbar(self): if getattr(self, 'toolbar', None): self.toolbar.clear() self.removeToolBar(self.toolbar) self.toolbar = toolbar = QtWidgets.QToolBar(_("Actions")) self.insertToolBar(self.search_toolbar, self.toolbar) self.update_toolbar_style() toolbar.setObjectName("main_toolbar") def add_toolbar_action(action): toolbar.addAction(action) widget = toolbar.widgetForAction(action) widget.setFocusPolicy(QtCore.Qt.TabFocus) widget.setAttribute(QtCore.Qt.WA_MacShowFocusRect) for action in config.setting['toolbar_layout']: if action == 'cd_lookup_action': add_toolbar_action(self.cd_lookup_action) if len(self.cd_lookup_menu.actions()) > 1: button = toolbar.widgetForAction(self.cd_lookup_action) button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) button.setMenu(self.cd_lookup_menu) elif action == 'separator': toolbar.addSeparator() else: try: add_toolbar_action(getattr(self, action)) except AttributeError: log.warning( 'Warning: Unknown action name "%r" found in config. Ignored.', action) self.show_toolbar()
def on_remote_image_fetched(self, url, data, reply, error, fallback_data=None): if error: log.error("Failed loading remote image from %s: %s", url, reply.errorString()) if fallback_data: self._load_fallback_data(url, fallback_data) return data = bytes(data) mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) # Some sites return a mime type with encoding like "image/jpeg; charset=UTF-8" mime = mime.split(';')[0] url_query = QtCore.QUrlQuery(url.query()) log.debug('Fetched remote image with MIME-Type %s from %s', mime, url.toString()) # If mime indicates only binary data we can try to guess the real mime type if (mime in ('application/octet-stream', 'binary/data') or mime.startswith('image/') or imageinfo.supports_mime_type(mime)): try: self._try_load_remote_image(url, data) return except CoverArtImageError: pass if url_query.hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl(url_query.queryItemValue("imgurl", QtCore.QUrl.FullyDecoded)) log.debug('Possible Google images result, trying to fetch imgurl=%s', url.toString()) self.fetch_remote_image(url) elif url_query.hasQueryItem("mediaurl"): # Bing uses mediaurl url = QtCore.QUrl(url_query.queryItemValue("mediaurl", QtCore.QUrl.FullyDecoded)) log.debug('Possible Bing images result, trying to fetch imgurl=%s', url.toString()) self.fetch_remote_image(url) else: log.warning("Can't load remote image with MIME-Type %s", mime) if fallback_data: self._load_fallback_data(url, fallback_data)
def load_remote_image(self, url, mime, data): try: coverartimage = CoverArtImage(url=url.toString(), data=data) except CoverArtImageError as e: log.warning("Can't load image: %s" % unicode(e)) return pixmap = QtGui.QPixmap() pixmap.loadFromData(data) self.__set_data([mime, data], pixmap=pixmap) if isinstance(self.item, Album): album = self.item album.metadata.append_image(coverartimage) for track in album.tracks: track.metadata.append_image(coverartimage) for file in album.iterfiles(): file.metadata.append_image(coverartimage) elif isinstance(self.item, Track): track = self.item track.metadata.append_image(coverartimage) for file in track.iterfiles(): file.metadata.append_image(coverartimage) elif isinstance(self.item, File): file = self.item file.metadata.append_image(coverartimage) self.currentImage = len(self.metadata.images) - 1 self.__update_image_count()
def load_remote_image(self, url, mime, data): try: coverartimage = CoverArtImage( url=url.toString(), types=['front'], data=data ) except CoverArtImageError as e: log.warning("Can't load image: %s" % e) return if config.setting["load_image_behavior"] == 'replace': set_image = set_image_replace debug_info = "Replacing with dropped %r in %r" else: set_image = set_image_append debug_info = "Appending dropped %r to %r" update = True if isinstance(self.item, Album): album = self.item album.enable_update_metadata_images(False) set_image(album, coverartimage) for track in album.tracks: set_image(track, coverartimage) track.metadata_images_changed.emit() for file in album.iterfiles(): set_image(file, coverartimage) file.metadata_images_changed.emit() file.update() album.enable_update_metadata_images(True) album.update_metadata_images() album.update(False) elif isinstance(self.item, Track): track = self.item track.album.enable_update_metadata_images(False) set_image(track, coverartimage) track.metadata_images_changed.emit() for file in track.iterfiles(): set_image(file, coverartimage) file.metadata_images_changed.emit() file.update() track.album.enable_update_metadata_images(True) track.album.update_metadata_images() track.album.update(False) elif isinstance(self.item, File): file = self.item set_image(file, coverartimage) file.metadata_images_changed.emit() file.update() else: debug_info = "Dropping %r to %r is not handled" update = False log.debug(debug_info, coverartimage, self.item) if update: self.cover_art.set_metadata(self.item.metadata) self.show()
def is_dark_theme(self): dark_theme = False try: with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") as key: dark_theme = winreg.QueryValueEx(key, "AppsUseLightTheme")[0] == 0 except OSError: log.warning('Failed reading AppsUseLightTheme from registry') return dark_theme
def _macos_find_root_volume(): try: 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) return None
def load_mbid(self, type, mbid): self.bring_tagger_front() if type == 'album': self.load_album(mbid) elif type == 'nat': self.load_nat(mbid) else: log.warning('Unknown type to load: %s', type)
def load_remote_image(self, url, mime, data): try: coverartimage = CoverArtImage(url=url.toString(), types=['front'], data=data) except CoverArtImageError as e: log.warning("Can't load image: %s" % e) return if config.setting["load_image_behavior"] == 'replace': set_image = set_image_replace debug_info = "Replacing with dropped %r in %r" else: set_image = set_image_append debug_info = "Appending dropped %r to %r" if isinstance(self.item, Album): album = self.item album.enable_update_metadata_images(False) set_image(album, coverartimage) for track in album.tracks: track.enable_update_metadata_images(False) set_image(track, coverartimage) for file in album.iterfiles(): set_image(file, coverartimage) file.update(signal=False) for track in album.tracks: track.enable_update_metadata_images(True) album.enable_update_metadata_images(True) album.update(update_tracks=False) elif isinstance(self.item, FileListItem): parents = set() filelist = self.item filelist.enable_update_metadata_images(False) set_image(filelist, coverartimage) for file in filelist.iterfiles(): for parent in iter_file_parents(file): parent.enable_update_metadata_images(False) parents.add(parent) set_image(file, coverartimage) file.update(signal=False) for parent in parents: set_image(parent, coverartimage) parent.enable_update_metadata_images(True) if isinstance(parent, Album): parent.update(update_tracks=False) else: parent.update() filelist.enable_update_metadata_images(True) filelist.update() elif isinstance(self.item, File): file = self.item set_image(file, coverartimage) file.update() else: debug_info = "Dropping %r to %r is not handled" log.debug(debug_info, coverartimage, self.item)
def _load(self, filename): log.debug("Loading file %r", filename) config = get_config() self.__casemap = {} file = ASF(encode_filename(filename)) metadata = Metadata() for name, values in file.tags.items(): if name == 'WM/Picture': for image in values: try: (mime, data, image_type, description) = unpack_image(image.value) except ValueError as e: log.warning('Cannot unpack image from %r: %s', filename, e) continue try: coverartimage = TagCoverArtImage( file=filename, tag=name, types=types_from_id3(image_type), comment=description, support_types=True, data=data, id3_type=image_type, ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.images.append(coverartimage) continue elif name == 'WM/SharedUserRating': # Rating in WMA ranges from 0 to 99, normalize this to the range 0 to 5 values[0] = int( round( int(str(values[0])) / 99.0 * (config.setting['rating_steps'] - 1))) elif name == 'WM/PartOfSet': disc = str(values[0]).split("/") if len(disc) > 1: metadata["totaldiscs"] = disc[1] values[0] = disc[0] name_lower = name.lower() if name in self.__RTRANS: name = self.__RTRANS[name] elif name_lower in self.__RTRANS_CI: orig_name = name name = self.__RTRANS_CI[name_lower] self.__casemap[name] = orig_name else: continue values = [str(value) for value in values if value] if values: metadata[name] = values self._info(metadata, file) return metadata
def queue_images(self): # this method has to return CoverArtProvider.FINISHED or # CoverArtProvider.WAIT old = getattr(self, 'queue_downloads') #compat with old plugins if callable(old): log.warning('CoverArtProvider: queue_downloads() was replaced by queue_images()') return old() else: raise NotImplementedError
def handle_cached_toptags(self, tagtype, query): """ Copy toptags from module-global cache to local toptags list. """ toptags = CACHE.get(query, None) if toptags is not None: self.toptags[tagtype].extend(toptags) # noqa else: log.warning('cache error: %s, %s', tagtype, query)
def on_remote_image_fetched(self, data, reply, error): mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) if mime in ('image/jpeg', 'image/png'): self.load_remote_image(mime, data) elif reply.url().hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl(reply.url().queryItemValue("imgurl")) self.fetch_remote_image(url) else: log.warning("Can't load image with MIME-Type %s", mime)
def on_remote_image_fetched(self, url, data, reply, error): mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) if mime in ('image/jpeg', 'image/png'): self.load_remote_image(url, mime, data) elif reply.url().hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl(reply.url().queryItemValue("imgurl")) self.fetch_remote_image(url) else: log.warning("Can't load image with MIME-Type %s", mime)
def get_accent_color(self): accent_color = None try: with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\DWM") as key: accent_color_dword = winreg.QueryValueEx(key, "ColorizationColor")[0] accent_color_hex = '#{:06x}'.format(accent_color_dword & 0xffffff) accent_color = QtGui.QColor(accent_color_hex) except OSError: log.warning('Failed reading ColorizationColor from registry') return accent_color
def _display_artwork(self, images, col): """Draw artwork in corresponding cell if image type matches type in Type column. Arguments: images -- The images to be drawn. col -- Column in which images are to be drawn. Can be _new_cover_col or _existing_cover_col. """ row = 0 row_count = self.artwork_table.rowCount() for image in images: while row != row_count: image_type = self.artwork_table.item( row, self.artwork_table._type_col) if image_type and image_type.data( QtCore.Qt.UserRole) == image.types_as_string(): break row += 1 if row == row_count: continue data = None try: if image.thumbnail: try: data = image.thumbnail.data except CoverArtImageIOError as e: log.warning(unicode(e)) pass else: data = image.data except CoverArtImageIOError: log.error(traceback.format_exc()) continue item = QtWidgets.QTableWidgetItem() item.setData(QtCore.Qt.UserRole, image) pixmap = QtGui.QPixmap() if data is not None: pixmap.loadFromData(data) item.setToolTip( _("Double-click to open in external viewer\n" "Temporary file: %s\n" "Source: %s") % (image.tempfile_filename, image.source)) infos = [] if image.comment: infos.append(image.comment) infos.append(u"%s (%s)" % (bytes2human.decimal( image.datalength), bytes2human.binary(image.datalength))) if image.width and image.height: infos.append(u"%d x %d" % (image.width, image.height)) infos.append(image.mimetype) img_wgt = self.artwork_table.get_coverart_widget( pixmap, "\n".join(infos)) self.artwork_table.setCellWidget(row, col, img_wgt) self.artwork_table.setItem(row, col, item) row += 1
def _save_and_rename(self, old_filename, metadata): """Save the metadata.""" # Check that file has not been removed since thread was queued # Also don't save if we are stopping. if self.state == File.REMOVED: log.debug("File not saved because it was removed: %r", self.filename) return None if self.tagger.stopping: log.debug("File not saved because %s is stopping: %r", PICARD_APP_NAME, self.filename) return None new_filename = old_filename if not config.setting["dont_write_tags"]: save = partial(self._save, old_filename, metadata) if config.setting["preserve_timestamps"]: try: self._preserve_times(old_filename, save) except self.PreserveTimesStatError as why: log.warning(why) # we didn't save the file yet, bail out return None except self.FilePreserveTimesUtimeError as why: log.warning(why) else: save() # Rename files if config.setting["rename_files"] or config.setting["move_files"]: new_filename = self._rename(old_filename, metadata) # Move extra files (images, playlists, etc.) if config.setting["move_files"] and config.setting[ "move_additional_files"]: self._move_additional_files(old_filename, new_filename) # Delete empty directories if config.setting["delete_empty_dirs"]: dirname = os.path.dirname(old_filename) try: self._rmdir(dirname) head, tail = os.path.split(dirname) if not tail: head, tail = os.path.split(head) while head and tail: try: self._rmdir(head) except BaseException: break head, tail = os.path.split(head) except EnvironmentError: pass # Save cover art images if config.setting["save_images_to_files"]: self._save_images(os.path.dirname(new_filename), metadata) return new_filename
def _display_artwork(self, images, col): """Draw artwork in corresponding cell if image type matches type in Type column. Arguments: images -- The images to be drawn. col -- Column in which images are to be drawn. Can be _new_cover_col or _existing_cover_col. """ row = 0 row_count = self.artwork_table.rowCount() for image in images: while row != row_count: image_type = self.artwork_table.item(row, self.artwork_table._type_col) if image_type and image_type.data(QtCore.Qt.UserRole) == image.types_as_string(): break row += 1 if row == row_count: continue data = None try: if image.thumbnail: try: data = image.thumbnail.data except CoverArtImageIOError as e: log.warning(e) pass else: data = image.data except CoverArtImageIOError: log.error(traceback.format_exc()) continue item = QtWidgets.QTableWidgetItem() item.setData(QtCore.Qt.UserRole, image) pixmap = QtGui.QPixmap() if data is not None: pixmap.loadFromData(data) item.setToolTip( _("Double-click to open in external viewer\n" "Temporary file: %s\n" "Source: %s") % (image.tempfile_filename, image.source)) infos = [] if image.comment: infos.append(image.comment) infos.append("%s (%s)" % (bytes2human.decimal(image.datalength), bytes2human.binary(image.datalength))) if image.width and image.height: infos.append("%d x %d" % (image.width, image.height)) infos.append(image.mimetype) img_wgt = self.artwork_table.get_coverart_widget(pixmap, "\n".join(infos)) self.artwork_table.setCellWidget(row, col, img_wgt) self.artwork_table.setItem(row, col, item) row += 1
def load_plugin(self, name, plugindir): try: info = imp.find_module(name, [plugindir]) except ImportError: log.error("Failed loading plugin %r", name) return None plugin = None try: index = None for i, p in enumerate(self.plugins): if name == p.module_name: log.warning("Module %r conflict: unregistering previously" \ " loaded %r version %s from %r", p.module_name, p.name, p.version, p.file) _unregister_module_extensions(name) index = i break plugin_module = imp.load_module(_PLUGIN_MODULE_PREFIX + name, *info) plugin = PluginWrapper(plugin_module, plugindir, file=info[1]) versions = [ version_from_string(v) for v in list(plugin.api_versions) ] compatible_versions = list(set(versions) & self._api_versions) if compatible_versions: log.debug( "Loading plugin %r version %s, compatible with API: %s", plugin.name, plugin.version, ", ".join([ version_to_string(v, short=True) for v in sorted(compatible_versions) ])) plugin.compatible = True setattr(picard.plugins, name, plugin_module) if index: self.plugins[index] = plugin else: self.plugins.append(plugin) else: log.warning("Plugin '%s' from '%s' is not compatible" " with this version of Picard." % (plugin.name, plugin.file)) except VersionError as e: log.error("Plugin %r has an invalid API version string : %s", name, e) except: log.error("Plugin %r : %s", name, traceback.format_exc()) if info[0] is not None: info[0].close() return plugin
def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" self.album._requests -= 1 if error: self.album.error_append(u'Coverart error: %s' % (unicode(http.errorString()))) elif len(data) < 1000: log.warning("Not enough data, skipping %s" % coverartimage) else: self._message( N_("Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s"), { 'type': coverartimage.types_as_string(), 'albumid': self.album.id, 'host': coverartimage.host }, echo=None ) try: coverartimage.set_data(data) if coverartimage.can_be_saved_to_metadata: log.debug("Cover art image downloaded: %r [%s]" % ( coverartimage, coverartimage.imageinfo_as_string() ) ) self.metadata.append_image(coverartimage) for track in self.album._new_tracks: track.metadata.append_image(coverartimage) # If the image already was a front image, # there might still be some other non-CAA front # images in the queue - ignore them. if not self.front_image_found: self.front_image_found = coverartimage.is_front_image() else: log.debug("Thumbnail for cover art image downloaded: %r [%s]" % ( coverartimage, coverartimage.imageinfo_as_string() ) ) except CoverArtImageIOError as e: self.album.error_append(unicode(e)) self.album._finalize_loading(error=True) # It doesn't make sense to store/download more images if we can't # save them in the temporary folder, abort. return except CoverArtImageIdentificationError as e: self.album.error_append(unicode(e)) self.download_next_in_queue()
def barcode_process_metadata(self, barcode, response): # Check whether we have a concealed 404 and get the homepage if "<title>Contents - tango.info</title>" in response: log.debug("%s: No album with barcode %s on tango.info", PLUGIN_NAME, barcode) return table = table_regex.search(response) if not table: log.warning("%s: No table found on page for barcode %s on tango.info", PLUGIN_NAME, barcode) return albuminfo = {} trcontent = [match.groups()[0] for match in trs.finditer(table)] for tr in trcontent: trackinfo = [trmatch.groups()[0] for trmatch in tds.finditer(tr)] if not trackinfo: # check if list is empty, e.g. contains a <th> continue # Get genre if trackinfo[3] and not trackinfo[3] == "-": genre = unicode( re.split('<|>', trackinfo[3])[2].title(), 'utf8' ) else: genre = False # Get date if trackinfo[6] and not trackinfo[6] == "-": date = unicode(re.split('<|>', trackinfo[6])[2], 'utf8') else: date = False # Get singers if trackinfo[5] == "-" or not trackinfo[5]: vocal = False elif trackinfo[5]: # Catch and strip <a> tags vocal = unicode(re.sub("<[^>]*>", "", trackinfo[5]), 'utf8') else: vocal = False # expected format in HTML: <a href="/002390...">... tint = trackinfo[8].split("\"")[1][1:] albuminfo[tint] = { 'genre': genre, 'date': date, 'vocal': vocal } return albuminfo
def _save_and_rename(self, old_filename, metadata): """Save the metadata.""" # Check that file has not been removed since thread was queued # Also don't save if we are stopping. if self.state == File.REMOVED: log.debug("File not saved because it was removed: %r", self.filename) return None if self.tagger.stopping: log.debug("File not saved because %s is stopping: %r", PICARD_APP_NAME, self.filename) return None new_filename = old_filename if not config.setting["dont_write_tags"]: encoded_old_filename = encode_filename(old_filename) info = os.stat(encoded_old_filename) self._save(old_filename, metadata) if config.setting["preserve_timestamps"]: try: os.utime(encoded_old_filename, (info.st_atime, info.st_mtime)) except OSError: log.warning("Couldn't preserve timestamp for %r", old_filename) # Rename files if config.setting["rename_files"] or config.setting["move_files"]: new_filename = self._rename(old_filename, metadata) # Move extra files (images, playlists, etc.) if config.setting["move_files"] and config.setting[ "move_additional_files"]: self._move_additional_files(old_filename, new_filename) # Delete empty directories if config.setting["delete_empty_dirs"]: dirname = encode_filename(os.path.dirname(old_filename)) try: self._rmdir(dirname) head, tail = os.path.split(dirname) if not tail: head, tail = os.path.split(head) while head and tail: try: self._rmdir(head) except: break head, tail = os.path.split(head) except EnvironmentError: pass # Save cover art images if config.setting["save_images_to_files"]: self._save_images(os.path.dirname(new_filename), metadata) return new_filename
def _save_and_rename(self, old_filename, metadata): """Save the metadata.""" # Check that file has not been removed since thread was queued # Also don't save if we are stopping. if self.state == File.REMOVED: log.debug("File not saved because it was removed: %r", self.filename) return None if self.tagger.stopping: log.debug("File not saved because %s is stopping: %r", PICARD_APP_NAME, self.filename) return None new_filename = old_filename if not config.setting["dont_write_tags"]: save = partial(self._save, old_filename, metadata) if config.setting["preserve_timestamps"]: try: self._preserve_times(old_filename, save) except self.PreserveTimesStatError as why: log.warning(why) # we didn't save the file yet, bail out return None except self.FilePreserveTimesUtimeError as why: log.warning(why) else: save() # Rename files if config.setting["rename_files"] or config.setting["move_files"]: new_filename = self._rename(old_filename, metadata) # Move extra files (images, playlists, etc.) if config.setting["move_files"] and config.setting["move_additional_files"]: self._move_additional_files(old_filename, new_filename) # Delete empty directories if config.setting["delete_empty_dirs"]: dirname = os.path.dirname(old_filename) try: self._rmdir(dirname) head, tail = os.path.split(dirname) if not tail: head, tail = os.path.split(head) while head and tail: try: self._rmdir(head) except BaseException: break head, tail = os.path.split(head) except EnvironmentError: pass # Save cover art images if config.setting["save_images_to_files"]: self._save_images(os.path.dirname(new_filename), metadata) return new_filename
def check_io_encoding(): if _io_encoding == "ANSI_X3.4-1968": log.warning(""" System locale charset is ANSI_X3.4-1968 Your system's locale charset (i.e. the charset used to encode filenames) is set to ANSI_X3.4-1968. It is highly unlikely that this has been done intentionally. Most likely the locale is not set at all. An invalid setting will result in problems when creating data projects. To properly set the locale charset make sure the LC_* environment variables are set. Normally the distribution setup tools take care of this. Translation: Picard will have problems with non-english characters in filenames until you change your charset. """)
def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" self.album._requests -= 1 if error: self._coverart_http_error(http) elif len(data) < 1000: log.warning("Not enough data, skipping %s" % coverartimage) else: self._message( N_("Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s"), { 'type': ','.join(coverartimage.types), 'albumid': self.album.id, 'host': coverartimage.host } ) mime = mimetype.get_from_data(data, default="image/jpeg") try: self.metadata.make_and_add_image( mime, data, types=coverartimage.types, comment=coverartimage.comment, is_front=coverartimage.is_front ) for track in self.album._new_tracks: track.metadata.make_and_add_image( mime, data, types=coverartimage.types, comment=coverartimage.comment, is_front=coverartimage.is_front ) # If the image already was a front image, # there might still be some other non-CAA front # images in the queue - ignore them. if not self.front_image_found: self.front_image_found = coverartimage.is_front_image() except (IOError, OSError) as e: self.album.error_append(e.message) self.album._finalize_loading(error=True) # It doesn't make sense to store/download more images if we can't # save them in the temporary folder, abort. return self._download_next_in_queue()
def load_plugin(self, name, plugindir): try: info = imp.find_module(name, [plugindir]) except ImportError: log.error("Failed loading plugin %r", name) return None plugin = None try: index = None for i, p in enumerate(self.plugins): if name == p.module_name: log.warning("Module %r conflict: unregistering previously" \ " loaded %r version %s from %r", p.module_name, p.name, p.version, p.file) _unregister_module_extensions(name) index = i break plugin_module = imp.load_module(_PLUGIN_MODULE_PREFIX + name, *info) plugin = PluginWrapper(plugin_module, plugindir, file=info[1]) versions = [version_from_string(v) for v in list(plugin.api_versions)] compatible_versions = list(set(versions) & self._api_versions) if compatible_versions: log.debug("Loading plugin %r version %s, compatible with API: %s", plugin.name, plugin.version, ", ".join([version_to_string(v, short=True) for v in sorted(compatible_versions)])) plugin.compatible = True setattr(picard.plugins, name, plugin_module) if index is not None: self.plugins[index] = plugin else: self.plugins.append(plugin) else: log.warning("Plugin '%s' from '%s' is not compatible" " with this version of Picard." % (plugin.name, plugin.file)) except VersionError as e: log.error("Plugin %r has an invalid API version string : %s", name, e) except: log.error("Plugin %r : %s", name, traceback.format_exc()) if info[0] is not None: info[0].close() return plugin
def zip_import(path): if (not is_zip(path) or not os.path.isfile(path)): return (None, None, None) try: zip_importer = zipimport.zipimporter(path) plugin_name = _plugin_name_from_path(path) manifest_data = None if is_zipped_package(path): try: manifest_data = load_manifest(path) except Exception as why: log.warning("Failed to load manifest data from json: %s", why) return (zip_importer, plugin_name, manifest_data) except zipimport.ZipImportError: return (None, None, None)
def load_plugindir(self, plugindir): plugindir = os.path.normpath(plugindir) if not os.path.isdir(plugindir): log.warning("Plugin directory %r doesn't exist", plugindir) return names = set() for path in [os.path.join(plugindir, file) for file in os.listdir(plugindir)]: name = _plugin_name_from_path(path) if name: names.add(name) log.debug("Looking for plugins in directory %r, %d names found", plugindir, len(names)) for name in sorted(names): self.load_plugin(name, plugindir)
def check_io_encoding(): if _io_encoding == "ANSI_X3.4-1968": from picard import log log.warning(""" System locale charset is ANSI_X3.4-1968 Your system's locale charset (i.e. the charset used to encode filenames) is set to ANSI_X3.4-1968. It is highly unlikely that this has been done intentionally. Most likely the locale is not set at all. An invalid setting will result in problems when creating data projects. To properly set the locale charset make sure the LC_* environment variables are set. Normally the distribution setup tools take care of this. Translation: Picard will have problems with non-english characters in filenames until you change your charset. """)
def _save_and_rename(self, old_filename, metadata): """Save the metadata.""" # Check that file has not been removed since thread was queued # Also don't save if we are stopping. if self.state == File.REMOVED: log.debug("File not saved because it was removed: %r", self.filename) return None if self.tagger.stopping: log.debug("File not saved because %s is stopping: %r", PICARD_APP_NAME, self.filename) return None new_filename = old_filename if not config.setting["dont_write_tags"]: encoded_old_filename = encode_filename(old_filename) info = os.stat(encoded_old_filename) self._save(old_filename, metadata) if config.setting["preserve_timestamps"]: try: os.utime(encoded_old_filename, (info.st_atime, info.st_mtime)) except OSError: log.warning("Couldn't preserve timestamp for %r", old_filename) # Rename files if config.setting["rename_files"] or config.setting["move_files"]: new_filename = self._rename(old_filename, metadata) # Move extra files (images, playlists, etc.) if config.setting["move_files"] and config.setting["move_additional_files"]: self._move_additional_files(old_filename, new_filename) # Delete empty directories if config.setting["delete_empty_dirs"]: dirname = encode_filename(os.path.dirname(old_filename)) try: self._rmdir(dirname) head, tail = os.path.split(dirname) if not tail: head, tail = os.path.split(head) while head and tail: try: self._rmdir(head) except: break head, tail = os.path.split(head) except EnvironmentError: pass # Save cover art images if config.setting["save_images_to_files"]: self._save_images(os.path.dirname(new_filename), metadata) return new_filename
def install_plugin(self, path, dest): plugin_name = _plugin_name_from_path(path) if plugin_name: try: dest_exists = os.path.exists(dest) same_file = os_path_samefile(path, dest) if dest_exists else False if os.path.isfile(path) and not (dest_exists and same_file): shutil.copy(path, dest) elif os.path.isdir(path) and not same_file: if dest_exists: shutil.rmtree(dest) shutil.copytree(path, dest) plugin = self.load_plugin(plugin_name, USER_PLUGIN_DIR) if plugin is not None: self.plugin_installed.emit(plugin, False) except (OSError, IOError): log.warning("Unable to copy %s to plugin folder %s" % (path, USER_PLUGIN_DIR))
def _display_artwork_tab(self): tab = self.ui.artwork_tab images = self.obj.metadata.images if not images: self.tab_hide(tab) return self.ui.artwork_list.itemDoubleClicked.connect(self.show_item) for image in images: data = None try: if image.thumbnail: try: data = image.thumbnail.data except CoverArtImageIOError as e: log.warning(unicode(e)) pass else: data = image.data except CoverArtImageIOError: log.error(traceback.format_exc()) continue item = QtGui.QListWidgetItem() item.setData(QtCore.Qt.UserRole, image) if data is not None: pixmap = QtGui.QPixmap() pixmap.loadFromData(data) icon = QtGui.QIcon(pixmap) item.setIcon(icon) item.setToolTip( _("Double-click to open in external viewer\n" "Temporary file: %s\n" "Source: %s") % (image.tempfile_filename, image.source)) infos = [] infos.append(image.types_as_string()) if image.comment: infos.append(image.comment) infos.append(u"%s (%s)" % (bytes2human.decimal(image.datalength), bytes2human.binary(image.datalength))) if image.width and image.height: infos.append(u"%d x %d" % (image.width, image.height)) infos.append(image.mimetype) item.setText(u"\n".join(infos)) self.ui.artwork_list.addItem(item)
def on_remote_image_fetched(self, url, data, reply, error, fallback_data=None): mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) if mime in ('image/jpeg', 'image/png'): self.load_remote_image(url, mime, data) elif url.hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl(url.queryItemValue("imgurl")) self.fetch_remote_image(url) else: log.warning("Can't load remote image with MIME-Type %s", mime) if fallback_data: # Tests for image format obtained from file-magic try: mime = imageinfo.identify(fallback_data)[2] except imageinfo.IdentificationError as e: log.error("Unable to identify dropped data format: %s" % e) else: log.debug("Trying the dropped %s data", mime) self.load_remote_image(url, mime, fallback_data)
def load_remote_image(self, mime, data): pixmap = QtGui.QPixmap() if not pixmap.loadFromData(data): log.warning("Can't load image") return self.__set_data([mime, data], pixmap=pixmap) if isinstance(self.item, Album): album = self.item album.metadata.make_and_add_image(mime, data) for track in album.tracks: track.metadata.make_and_add_image(mime, data) for file in album.iterfiles(): file.metadata.make_and_add_image(mime, data) elif isinstance(self.item, Track): track = self.item track.metadata.make_and_add_image(mime, data) for file in track.iterfiles(): file.metadata.make_and_add_image(mime, data) elif isinstance(self.item, File): file = self.item file.metadata.make_and_add_image(mime, data)
def _save_and_rename(self, old_filename, metadata): """Save the metadata.""" new_filename = old_filename if not config.setting["dont_write_tags"]: encoded_old_filename = encode_filename(old_filename) info = os.stat(encoded_old_filename) self._save(old_filename, metadata) if config.setting["preserve_timestamps"]: try: os.utime(encoded_old_filename, (info.st_atime, info.st_mtime)) except OSError: log.warning("Couldn't preserve timestamp for %r", old_filename) # Rename files if config.setting["rename_files"] or config.setting["move_files"]: new_filename = self._rename(old_filename, metadata) # Move extra files (images, playlists, etc.) if config.setting["move_files"] and config.setting["move_additional_files"]: self._move_additional_files(old_filename, new_filename) # Delete empty directories if config.setting["delete_empty_dirs"]: dirname = encode_filename(os.path.dirname(old_filename)) try: self._rmdir(dirname) head, tail = os.path.split(dirname) if not tail: head, tail = os.path.split(head) while head and tail: try: self._rmdir(head) except: break head, tail = os.path.split(head) except EnvironmentError: pass # Save cover art images if config.setting["save_images_to_files"]: self._save_images(os.path.dirname(new_filename), metadata) return new_filename
def install_plugin(self, path, update=False, overwrite_confirm=None, plugin_name=None, plugin_data=None): """ path is either: 1) /some/dir/name.py 2) /some/dir/name (directory containing __init__.py) 3) /some/dir/name.zip (containing either 1 or 2) """ zip_plugin = False if not plugin_name: zip_plugin = is_zip(path) if not zip_plugin: plugin_name = _plugin_name_from_path(path) else: plugin_name = os.path.splitext(zip_plugin)[0] if plugin_name: try: if plugin_data and plugin_name: # zipped module from download zip_plugin = plugin_name + '.zip' dst = os.path.join(USER_PLUGIN_DIR, zip_plugin) if update: dst += '.update' if os.path.isfile(dst): os.remove(dst) ziptmp = tempfile.NamedTemporaryFile(delete=False, dir=USER_PLUGIN_DIR).name try: with open(ziptmp, "wb") as zipfile: zipfile.write(plugin_data) zipfile.flush() os.fsync(zipfile.fileno()) os.rename(ziptmp, dst) log.debug("Plugin saved to %r", dst) except BaseException: try: os.remove(ziptmp) except (IOError, OSError): pass raise elif os.path.isfile(path): dst = os.path.join(USER_PLUGIN_DIR, os.path.basename(path)) if update: dst += '.update' if os.path.isfile(dst): os.remove(dst) shutil.copy2(path, dst) elif os.path.isdir(path): dst = os.path.join(USER_PLUGIN_DIR, plugin_name) if update: dst += '.update' if os.path.isdir(dst): shutil.rmtree(dst) shutil.copytree(path, dst) if not update: try: installed_plugin = self.load_plugin(zip_plugin or plugin_name, USER_PLUGIN_DIR) except Exception as e: log.error('Unable to load plugin: %s.\nError occured: %s', plugin_name, e) installed_plugin = None if installed_plugin is not None: self.plugin_installed.emit(installed_plugin, False) else: self.plugin_updated.emit(plugin_name, False) except (OSError, IOError): log.warning("Unable to copy %s to plugin folder %s" % (path, USER_PLUGIN_DIR))
def create_actions(self): self.options_action = QtWidgets.QAction(icontheme.lookup('preferences-desktop'), _("&Options..."), self) self.options_action.setMenuRole(QtWidgets.QAction.PreferencesRole) self.options_action.triggered.connect(self.show_options) self.cut_action = QtWidgets.QAction(icontheme.lookup('edit-cut', icontheme.ICON_SIZE_MENU), _("&Cut"), self) self.cut_action.setShortcut(QtGui.QKeySequence.Cut) self.cut_action.setEnabled(False) self.cut_action.triggered.connect(self.cut) self.paste_action = QtWidgets.QAction(icontheme.lookup('edit-paste', icontheme.ICON_SIZE_MENU), _("&Paste"), self) self.paste_action.setShortcut(QtGui.QKeySequence.Paste) self.paste_action.setEnabled(False) self.paste_action.triggered.connect(self.paste) self.help_action = QtWidgets.QAction(_("&Help..."), self) self.help_action.setShortcut(QtGui.QKeySequence.HelpContents) self.help_action.triggered.connect(self.show_help) self.about_action = QtWidgets.QAction(_("&About..."), self) self.about_action.setMenuRole(QtWidgets.QAction.AboutRole) self.about_action.triggered.connect(self.show_about) self.donate_action = QtWidgets.QAction(_("&Donate..."), self) self.donate_action.triggered.connect(self.open_donation_page) self.report_bug_action = QtWidgets.QAction(_("&Report a Bug..."), self) self.report_bug_action.triggered.connect(self.open_bug_report) self.support_forum_action = QtWidgets.QAction(_("&Support Forum..."), self) self.support_forum_action.triggered.connect(self.open_support_forum) self.add_files_action = QtWidgets.QAction(icontheme.lookup('document-open'), _("&Add Files..."), self) self.add_files_action.setStatusTip(_("Add files to the tagger")) # TR: Keyboard shortcut for "Add Files..." self.add_files_action.setShortcut(QtGui.QKeySequence.Open) self.add_files_action.triggered.connect(self.add_files) self.add_directory_action = QtWidgets.QAction(icontheme.lookup('folder'), _("A&dd Folder..."), self) self.add_directory_action.setStatusTip(_("Add a folder to the tagger")) # TR: Keyboard shortcut for "Add Directory..." self.add_directory_action.setShortcut(QtGui.QKeySequence(_("Ctrl+D"))) self.add_directory_action.triggered.connect(self.add_directory) if self.show_close_window: self.close_window_action = QtWidgets.QAction(_("Close Window"), self) self.close_window_action.setShortcut(QtGui.QKeySequence(_("Ctrl+W"))) self.close_window_action.triggered.connect(self.close_active_window) self.save_action = QtWidgets.QAction(icontheme.lookup('document-save'), _("&Save"), self) self.save_action.setStatusTip(_("Save selected files")) # TR: Keyboard shortcut for "Save" self.save_action.setShortcut(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.save) self.submit_acoustid_action = QtWidgets.QAction(icontheme.lookup('acoustid-fingerprinter'), _("S&ubmit AcoustIDs"), self) self.submit_acoustid_action.setStatusTip(_("Submit acoustic fingerprints")) self.submit_acoustid_action.setEnabled(False) self.submit_acoustid_action.triggered.connect(self._on_submit_acoustid) self.exit_action = QtWidgets.QAction(_("E&xit"), self) self.exit_action.setMenuRole(QtWidgets.QAction.QuitRole) # TR: Keyboard shortcut for "Exit" self.exit_action.setShortcut(QtGui.QKeySequence(_("Ctrl+Q"))) self.exit_action.triggered.connect(self.close) self.remove_action = QtWidgets.QAction(icontheme.lookup('list-remove'), _("&Remove"), self) self.remove_action.setStatusTip(_("Remove selected files/albums")) self.remove_action.setEnabled(False) self.remove_action.triggered.connect(self.remove) self.browser_lookup_action = QtWidgets.QAction(icontheme.lookup('lookup-musicbrainz'), _("Lookup in &Browser"), self) self.browser_lookup_action.setStatusTip(_("Lookup selected item on MusicBrainz website")) self.browser_lookup_action.setEnabled(False) # TR: Keyboard shortcut for "Lookup in Browser" self.browser_lookup_action.setShortcut(QtGui.QKeySequence(_("Ctrl+Shift+L"))) self.browser_lookup_action.triggered.connect(self.browser_lookup) self.album_search_action = QtWidgets.QAction(icontheme.lookup('system-search'), _("Search for similar albums..."), self) self.album_search_action.setStatusTip(_("View similar releases and optionally choose a different release")) self.album_search_action.triggered.connect(self.show_more_albums) self.track_search_action = QtWidgets.QAction(icontheme.lookup('system-search'), _("Search for similar tracks..."), self) self.track_search_action.setStatusTip(_("View similar tracks and optionally choose a different release")) self.track_search_action.triggered.connect(self.show_more_tracks) self.show_file_browser_action = QtWidgets.QAction(_("File &Browser"), self) self.show_file_browser_action.setCheckable(True) if config.persist["view_file_browser"]: self.show_file_browser_action.setChecked(True) self.show_file_browser_action.setShortcut(QtGui.QKeySequence(_("Ctrl+B"))) self.show_file_browser_action.triggered.connect(self.show_file_browser) self.show_cover_art_action = QtWidgets.QAction(_("&Cover Art"), self) self.show_cover_art_action.setCheckable(True) if config.persist["view_cover_art"]: self.show_cover_art_action.setChecked(True) self.show_cover_art_action.triggered.connect(self.show_cover_art) self.show_toolbar_action = QtWidgets.QAction(_("&Actions"), self) self.show_toolbar_action.setCheckable(True) if config.persist["view_toolbar"]: self.show_toolbar_action.setChecked(True) self.show_toolbar_action.triggered.connect(self.show_toolbar) self.search_action = QtWidgets.QAction(icontheme.lookup('system-search'), _("Search"), self) self.search_action.setEnabled(False) self.search_action.triggered.connect(self.search) self.cd_lookup_action = QtWidgets.QAction(icontheme.lookup('media-optical'), _("Lookup &CD..."), self) self.cd_lookup_action.setStatusTip(_("Lookup the details of the CD in your drive")) # TR: Keyboard shortcut for "Lookup CD" self.cd_lookup_action.setShortcut(QtGui.QKeySequence(_("Ctrl+K"))) self.cd_lookup_action.triggered.connect(self.tagger.lookup_cd) self.cd_lookup_menu = QtWidgets.QMenu(_("Lookup &CD...")) self.cd_lookup_menu.triggered.connect(self.tagger.lookup_cd) self.cd_lookup_action.setEnabled(False) if discid is None: log.warning("CDROM: discid library not found - Lookup CD functionality disabled") else: drives = get_cdrom_drives() if not drives: log.warning("CDROM: No CD-ROM drives found - Lookup CD functionality disabled") else: shortcut_drive = config.setting["cd_lookup_device"].split(",")[0] if len(drives) > 1 else "" self.cd_lookup_action.setEnabled(True) for drive in drives: action = self.cd_lookup_menu.addAction(drive) action.setData(drive) if drive == shortcut_drive: # Clear existing shortcode on main action and assign it to sub-action self.cd_lookup_action.setShortcut(QtGui.QKeySequence()) action.setShortcut(QtGui.QKeySequence(_("Ctrl+K"))) self.analyze_action = QtWidgets.QAction(icontheme.lookup('picard-analyze'), _("&Scan"), self) self.analyze_action.setStatusTip(_("Use AcoustID audio fingerprint to identify the files by the actual music, even if they have no metadata")) self.analyze_action.setEnabled(False) self.analyze_action.setToolTip(_('Identify the file using its AcoustID audio fingerprint')) # TR: Keyboard shortcut for "Analyze" self.analyze_action.setShortcut(QtGui.QKeySequence(_("Ctrl+Y"))) self.analyze_action.triggered.connect(self.analyze) self.cluster_action = QtWidgets.QAction(icontheme.lookup('picard-cluster'), _("Cl&uster"), self) self.cluster_action.setStatusTip(_("Cluster files into album clusters")) self.cluster_action.setEnabled(False) # TR: Keyboard shortcut for "Cluster" self.cluster_action.setShortcut(QtGui.QKeySequence(_("Ctrl+U"))) self.cluster_action.triggered.connect(self.cluster) self.autotag_action = QtWidgets.QAction(icontheme.lookup('picard-auto-tag'), _("&Lookup"), self) tip = _("Lookup selected items in MusicBrainz") self.autotag_action.setToolTip(tip) self.autotag_action.setStatusTip(tip) self.autotag_action.setEnabled(False) # TR: Keyboard shortcut for "Lookup" self.autotag_action.setShortcut(QtGui.QKeySequence(_("Ctrl+L"))) self.autotag_action.triggered.connect(self.autotag) self.view_info_action = QtWidgets.QAction(icontheme.lookup('picard-edit-tags'), _("&Info..."), self) self.view_info_action.setEnabled(False) # TR: Keyboard shortcut for "Info" self.view_info_action.setShortcut(QtGui.QKeySequence(_("Ctrl+I"))) self.view_info_action.triggered.connect(self.view_info) self.refresh_action = QtWidgets.QAction(icontheme.lookup('view-refresh', icontheme.ICON_SIZE_MENU), _("&Refresh"), self) self.refresh_action.setShortcut(QtGui.QKeySequence(_("Ctrl+R"))) self.refresh_action.triggered.connect(self.refresh) self.enable_renaming_action = QtWidgets.QAction(_("&Rename Files"), self) self.enable_renaming_action.setCheckable(True) self.enable_renaming_action.setChecked(config.setting["rename_files"]) self.enable_renaming_action.triggered.connect(self.toggle_rename_files) self.enable_moving_action = QtWidgets.QAction(_("&Move Files"), self) self.enable_moving_action.setCheckable(True) self.enable_moving_action.setChecked(config.setting["move_files"]) self.enable_moving_action.triggered.connect(self.toggle_move_files) self.enable_tag_saving_action = QtWidgets.QAction(_("Save &Tags"), self) self.enable_tag_saving_action.setCheckable(True) self.enable_tag_saving_action.setChecked(not config.setting["dont_write_tags"]) self.enable_tag_saving_action.triggered.connect(self.toggle_tag_saving) self.tags_from_filenames_action = QtWidgets.QAction(_("Tags From &File Names..."), self) self.tags_from_filenames_action.triggered.connect(self.open_tags_from_filenames) self.tags_from_filenames_action.setEnabled(False) self.open_collection_in_browser_action = QtWidgets.QAction(_("&Open My Collections in Browser"), self) self.open_collection_in_browser_action.triggered.connect(self.open_collection_in_browser) self.open_collection_in_browser_action.setEnabled(config.setting["username"] != '') self.view_log_action = QtWidgets.QAction(_("View &Error/Debug Log"), self) self.view_log_action.triggered.connect(self.show_log) # TR: Keyboard shortcut for "View Error/Debug Log" self.view_log_action.setShortcut(QtGui.QKeySequence(_("Ctrl+E"))) self.view_history_action = QtWidgets.QAction(_("View Activity &History"), self) self.view_history_action.triggered.connect(self.show_history) # TR: Keyboard shortcut for "View Activity History" self.view_history_action.setShortcut(QtGui.QKeySequence(_("Ctrl+H"))) webservice_manager = self.tagger.webservice.manager webservice_manager.authenticationRequired.connect(self.show_password_dialog) webservice_manager.proxyAuthenticationRequired.connect(self.show_proxy_dialog) self.play_file_action = QtWidgets.QAction(icontheme.lookup('play-music'), _("Open in &Player"), self) self.play_file_action.setStatusTip(_("Play the file in your default media player")) self.play_file_action.setEnabled(False) self.play_file_action.triggered.connect(self.play_file) self.open_folder_action = QtWidgets.QAction(icontheme.lookup('folder', icontheme.ICON_SIZE_MENU), _("Open Containing &Folder"), self) self.open_folder_action.setStatusTip(_("Open the containing folder in your file explorer")) self.open_folder_action.setEnabled(False) self.open_folder_action.triggered.connect(self.open_folder) if self.tagger.autoupdate_enabled: self.check_update_action = QtWidgets.QAction(_("&Check for Update…"), self) self.check_update_action.setMenuRole(QtWidgets.QAction.ApplicationSpecificRole) self.check_update_action.triggered.connect(self.do_update_check)
def _load_plugin_from_directory(self, name, plugindir): module_file = None (zip_importer, module_name, manifest_data) = zip_import(os.path.join(plugindir, name + '.zip')) if zip_importer: name = module_name if not zip_importer.find_module(name): error = _("Failed loading zipped plugin %r") % name self.plugin_error(name, error) return None module_pathname = zip_importer.get_filename(name) else: try: info = imp.find_module(name, [plugindir]) module_file = info[0] module_pathname = info[1] except ImportError: error = _("Failed loading plugin %r") % name self.plugin_error(name, error) return None plugin = None try: existing_plugin, existing_plugin_index = self._get_plugin_index_by_name(name) if existing_plugin: log.warning("Module %r conflict: unregistering previously" " loaded %r version %s from %r", existing_plugin.module_name, existing_plugin.name, existing_plugin.version, existing_plugin.file) _unregister_module_extensions(name) full_module_name = _PLUGIN_MODULE_PREFIX + name if zip_importer: plugin_module = zip_importer.load_module(full_module_name) else: plugin_module = imp.load_module(full_module_name, *info) plugin = PluginWrapper(plugin_module, plugindir, file=module_pathname, manifest_data=manifest_data) compatible_versions = _compatible_api_versions(plugin.api_versions) if compatible_versions: log.debug("Loading plugin %r version %s, compatible with API: %s", plugin.name, plugin.version, ", ".join([version_to_string(v, short=True) for v in sorted(compatible_versions)])) plugin.compatible = True setattr(picard.plugins, name, plugin_module) if existing_plugin: self.plugins[existing_plugin_index] = plugin else: self.plugins.append(plugin) else: error = _("Plugin '%s' from '%s' is not compatible with this " "version of Picard.") % (plugin.name, plugin.file) self.plugin_error(plugin.name, error, log_func=log.warning) except VersionError as e: error = _("Plugin %r has an invalid API version string : %s") % (name, e) self.plugin_error(name, error) except BaseException: error = _("Plugin %r : %s") % (name, traceback.format_exc()) self.plugin_error(name, error) if module_file is not None: module_file.close() return plugin
def on_remote_image_fetched(self, data, reply, error): mime = str(reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader).toString()) if mime not in ('image/jpeg', 'image/png'): log.warning("Can't load image with MIME-Type %s", mime) return return self.load_remote_image(mime, data)
def convert_to_string(obj): from picard import log log.warning("string_() and convert_to_string() are deprecated, do not use") return __convert_to_string(obj)
from picard.album import Album, NatAlbum from picard.cluster import Cluster, ClusterList, UnmatchedFiles from picard.file import File from picard.track import Track, NonAlbumTrack from picard.util import encode_filename, icontheme from picard.plugin import ExtensionPoint from picard.ui.ratingwidget import RatingWidget from picard.ui.collectionmenu import CollectionMenu if sys.platform == 'darwin': try: from Foundation import NSURL NSURL_IMPORTED = True except ImportError: NSURL_IMPORTED = False log.warning("Unable to import NSURL, file drag'n'drop might not work correctly") class BaseAction(QtGui.QAction): NAME = "Unknown" MENU = [] def __init__(self): QtGui.QAction.__init__(self, self.NAME, None) self.triggered.connect(self.__callback) def __callback(self): objs = self.tagger.window.selected_objects self.callback(objs) def callback(self, objs):
continue mainkey, subkey = key.split(':', 1) if not subkey: continue instruments = standardise_performers_split(subkey) if len(instruments) == 1: continue log.debug("%s: Splitting Performer [%s] into separate performers", PLUGIN_NAME, subkey, ) for instrument in instruments: newkey = '%s:%s' % (mainkey, instrument) for value in values: metadata.add_unique(newkey, value) del metadata[key] try: from picard.plugin import PluginPriority register_track_metadata_processor(standardise_performers, priority=PluginPriority.HIGH) except ImportError: log.warning( "Running %r plugin on this Picard version may not work as you expect. " "Any other plugins that run before it will get the old performers " "rather than the standardized performers.", PLUGIN_NAME ) register_track_metadata_processor(standardise_performers)