Hello community, here is the log from the commit of package picard for openSUSE:Factory checked in at 2019-11-12 11:56:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/picard (Old) and /work/SRC/openSUSE:Factory/.picard.new.2990 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "picard" Tue Nov 12 11:56:44 2019 rev:37 rq:747515 version:2.2.3 Changes: -------- --- /work/SRC/openSUSE:Factory/picard/picard.changes 2019-10-14 12:43:48.974337149 +0200 +++ /work/SRC/openSUSE:Factory/.picard.new.2990/picard.changes 2019-11-12 11:58:23.387533039 +0100 @@ -1,0 +2,23 @@ +Mon Nov 11 21:10:07 UTC 2019 - Jaime Marquínez Ferrándiz <jaime.marquinez.ferran...@fastmail.net> + +- Update to 2.2.3: + * Bug + * [PICARD-1633] – macOS: Automatic locale detection broken + * [PICARD-1634] – macOS: File browser sets wrong horizontal scroll position + * [PICARD-1635] – Terminated randomly when deleting files when saving + * [PICARD-1636] – Default locale detection fails if locale categories have different locales + * [PICARD-1637] – Crash when saving after removing some underlying files + * [PICARD-1640] – Picard with –config-file parameter copies over legacy configuration + * [PICARD-1642] – Picard crashes on launch (AttributeError: ‘NoneType’ object has no attribute ‘setPopupMode’) + * [PICARD-1643] – Performer with instruments containing non-ASCII characters are not written to Vorbis and APE tags + * [PICARD-1644] – Crash when initializing translations on Python 3.8 + * [PICARD-1647] – macOS: Plugin enable/disable button does not always update the icon + * [PICARD-1648] – Crashes when using search dialogs + * [PICARD-1651] – File and release counts in status bar not updated when files get removed + * [PICARD-1654] – macOS: Logout button / username stays visible after logout + * [PICARD-1655] – macOS: Login dialog can be hidden behind options + * Improvement + * [PICARD-1630] – Ensure FLAC metadata is visible/editable in Windows Explorer + * [PICARD-1632] – Tooltips for genre filter help hide too quickly + +------------------------------------------------------------------- Old: ---- picard-2.2.2.tar.gz New: ---- picard-2.2.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ picard.spec ++++++ --- /var/tmp/diff_new_pack.lqj6nh/_old 2019-11-12 11:58:23.791533469 +0100 +++ /var/tmp/diff_new_pack.lqj6nh/_new 2019-11-12 11:58:23.795533473 +0100 @@ -17,7 +17,7 @@ Name: picard -Version: 2.2.2 +Version: 2.2.3 Release: 0 Summary: The Next Generation MusicBrainz Tagger License: GPL-2.0-or-later ++++++ picard-2.2.2.tar.gz -> picard-2.2.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/.circleci/config.yml new/picard-release-2.2.3/.circleci/config.yml --- old/picard-release-2.2.2/.circleci/config.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/picard-release-2.2.3/.circleci/config.yml 2019-11-06 13:33:23.000000000 +0100 @@ -0,0 +1,55 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` + - image: circleci/python:3.6.1 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: | + python3 -m venv venv + . venv/bin/activate + pip install -r requirements.txt + + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum "requirements.txt" }} + + # run tests! + # this example uses Django's built-in test-runner + # other common Python testing frameworks include pytest and nose + # https://pytest.org + # https://nose.readthedocs.io + - run: + name: run tests + command: | + . venv/bin/activate + python setup.py test + + - store_artifacts: + path: test-reports + destination: test-reports diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/.travis.yml new/picard-release-2.2.3/.travis.yml --- old/picard-release-2.2.2/.travis.yml 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/.travis.yml 2019-11-06 13:33:23.000000000 +0100 @@ -30,8 +30,7 @@ - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.11.3 mutagen discid" - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.10 mutagen discid" - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.9 mutagen discid" - - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.8 mutagen discid" - - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.8 mutagen==1.37 discid" + - INSTALL_DEPS="$PIP_INSTALL pyqt5==5.8 mutagen==1.37 discid sip==4.19.8" matrix: include: - os: osx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/NEWS.md new/picard-release-2.2.3/NEWS.md --- old/picard-release-2.2.2/NEWS.md 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/NEWS.md 2019-11-06 13:33:23.000000000 +0100 @@ -1,3 +1,26 @@ +# Version 2.2.3 - 2019-11-06 + +## Bug-fix +- [PICARD-1633](https://tickets.metabrainz.org/browse/PICARD-1633) - macOS: Automatic locale detection broken +- [PICARD-1634](https://tickets.metabrainz.org/browse/PICARD-1634) - macOS: File browser sets wrong horizontal scroll position +- [PICARD-1635](https://tickets.metabrainz.org/browse/PICARD-1635) - Terminated randomly when deleting files when saving +- [PICARD-1636](https://tickets.metabrainz.org/browse/PICARD-1636) - Default locale detection fails if locale categories have different locales +- [PICARD-1637](https://tickets.metabrainz.org/browse/PICARD-1637) - Crash when saving after removing some underlying files +- [PICARD-1640](https://tickets.metabrainz.org/browse/PICARD-1640) - Picard with --config-file parameter copies over legacy configuration +- [PICARD-1642](https://tickets.metabrainz.org/browse/PICARD-1642) - Picard crashes on launch (AttributeError: 'NoneType' object has no attribute 'setPopupMode') +- [PICARD-1643](https://tickets.metabrainz.org/browse/PICARD-1643) - Performer with instruments containing non-ASCII characters are not written to Vorbis and APE tags +- [PICARD-1644](https://tickets.metabrainz.org/browse/PICARD-1644) - Crash when initializing translations on Python 3.8 +- [PICARD-1647](https://tickets.metabrainz.org/browse/PICARD-1647) - macOS: Plugin enable/disable button does not always update the icon +- [PICARD-1648](https://tickets.metabrainz.org/browse/PICARD-1648) - Crashes when using search dialogs +- [PICARD-1651](https://tickets.metabrainz.org/browse/PICARD-1651) - File and release counts in status bar not updated when files get removed +- [PICARD-1654](https://tickets.metabrainz.org/browse/PICARD-1654) - macOS: Logout button / username stays visible after logout +- [PICARD-1655](https://tickets.metabrainz.org/browse/PICARD-1655) - macOS: Login dialog can be hidden behind options + +## Improvement +- [PICARD-1630](https://tickets.metabrainz.org/browse/PICARD-1630) - Ensure FLAC metadata is visible/editable in Windows Explorer +- [PICARD-1632](https://tickets.metabrainz.org/browse/PICARD-1632) - Tooltips for genre filter help hide too quickly + + # Version 2.2.2 - 2019-10-08 ## Bug-fix diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/__init__.py new/picard-release-2.2.3/picard/__init__.py --- old/picard-release-2.2.2/picard/__init__.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/__init__.py 2019-11-06 13:33:23.000000000 +0100 @@ -24,7 +24,7 @@ PICARD_APP_NAME = "Picard" PICARD_APP_ID = "org.musicbrainz.Picard" PICARD_DESKTOP_NAME = PICARD_APP_ID + ".desktop" -PICARD_VERSION = (2, 2, 2, 'final', 0) +PICARD_VERSION = (2, 2, 3, 'final', 0) # optional build version diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/config.py new/picard-release-2.2.3/picard/config.py --- old/picard-release-2.2.2/picard/config.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/config.py 2019-11-06 13:33:23.000000000 +0100 @@ -113,14 +113,6 @@ """Common initializer method for :meth:`from_app` and :meth:`from_file`.""" - # If there are no settings, copy existing settings from old format - # (registry on windows systems) - if not self.allKeys(): - oldFormat = QtCore.QSettings(PICARD_ORG_NAME, PICARD_APP_NAME) - for k in oldFormat.allKeys(): - self.setValue(k, oldFormat.value(k)) - self.sync() - self.application = ConfigSection(self, "application") self.setting = ConfigSection(self, "setting") self.persist = ConfigSection(self, "persist") @@ -145,6 +137,15 @@ QtCore.QSettings.__init__(this, QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, PICARD_ORG_NAME, PICARD_APP_NAME, parent) + + # If there are no settings, copy existing settings from old format + # (registry on windows systems) + if not this.allKeys(): + oldFormat = QtCore.QSettings(PICARD_ORG_NAME, PICARD_APP_NAME) + for k in oldFormat.allKeys(): + this.setValue(k, oldFormat.value(k)) + this.sync() + this.__initialize() return this diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/file.py new/picard-release-2.2.3/picard/file.py --- old/picard-release-2.2.2/picard/file.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/file.py 2019-11-06 13:33:23.000000000 +0100 @@ -258,11 +258,7 @@ 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: + except self.PreserveTimesUtimeError as why: log.warning(why) else: save() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/formats/apev2.py new/picard-release-2.2.3/picard/formats/apev2.py --- old/picard-release-2.2.2/picard/formats/apev2.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/formats/apev2.py 2019-11-06 13:33:23.000000000 +0100 @@ -250,8 +250,11 @@ @classmethod def supports_tag(cls, name): - return (bool(name) and is_valid_key(name) - and name not in UNSUPPORTED_TAGS) + return (bool(name) and name not in UNSUPPORTED_TAGS + and (is_valid_key(name) + or name.startswith('comment:') + or name.startswith('lyrics:') + or name.startswith('performer:'))) class MusepackFile(APEv2File): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/formats/vorbis.py new/picard-release-2.2.3/picard/formats/vorbis.py --- old/picard-release-2.2.2/picard/formats/vorbis.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/formats/vorbis.py 2019-11-06 13:33:23.000000000 +0100 @@ -69,6 +69,32 @@ return INVALID_CHARS.search(key) is None +def flac_sort_pics_after_tags(metadata_blocks): + """ + Reorder the metadata_blocks so that all picture blocks are located after + the first Vorbis comment block. + + Windows fails to read FLAC tags if the picture blocks are located before + the Vorbis comments. Reordering the blocks fixes this. + """ + # First remember all picture blocks that are located before the tag block. + tagindex = 0 + picblocks = [] + for block in metadata_blocks: + if block.code == mutagen.flac.VCFLACDict.code: + tagindex = metadata_blocks.index(block) + break + elif block.code == mutagen.flac.Picture.code: + picblocks.append(block) + else: + return # No tags found, nothing to sort + + # Now move those picture block after the tag block, maintaining their order. + for pic in picblocks: + metadata_blocks.remove(pic) + metadata_blocks.insert(tagindex, pic) + + class VCommentFile(File): """Generic VComment-based file.""" @@ -244,7 +270,7 @@ picture.width = image.width picture.height = image.height picture.type = image_type_as_id3_num(image.maintype) - if self._File == mutagen.flac.FLAC: + if is_flac: file.add_picture(picture) else: tags.setdefault("METADATA_BLOCK_PICTURE", []).append( @@ -254,6 +280,9 @@ self._remove_deleted_tags(metadata, file.tags) + if is_flac: + flac_sort_pics_after_tags(file.metadata_blocks) + kwargs = {} if is_flac and config.setting["remove_id3_from_flac"]: kwargs["deleteid3"] = True @@ -305,7 +334,10 @@ def supports_tag(cls, name): unsupported_tags = ['r128_album_gain', 'r128_track_gain'] return (bool(name) and name not in unsupported_tags - and is_valid_key(name)) + and (is_valid_key(name) + or name.startswith('comment:') + or name.startswith('lyrics:') + or name.startswith('performer:'))) class FLACFile(VCommentFile): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/i18n.py new/picard-release-2.2.3/picard/i18n.py --- old/picard-release-2.2.2/picard/i18n.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/i18n.py 2019-11-06 13:33:23.000000000 +0100 @@ -63,12 +63,14 @@ import Foundation defaults = Foundation.NSUserDefaults.standardUserDefaults() current_locale = defaults.objectForKey_('AppleLanguages')[0] + current_locale = current_locale.replace('-', '_') locale.setlocale(locale.LC_ALL, current_locale) except Exception as e: logger(e) else: try: - current_locale = locale.setlocale(locale.LC_ALL, '') + locale.setlocale(locale.LC_ALL, '') + current_locale = '.'.join(locale.getlocale(locale.LC_MESSAGES)) except Exception as e: logger(e) os.environ['LANGUAGE'] = os.environ['LANG'] = current_locale @@ -77,7 +79,7 @@ try: logger("Loading gettext translation, localedir=%r", localedir) trans = gettext.translation("picard", localedir) - trans.install(True) + trans.install() _ngettext = trans.ngettext logger("Loading gettext translation (picard-countries), localedir=%r", localedir) trans_countries = gettext.translation("picard-countries", localedir) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/tagger.py new/picard-release-2.2.3/picard/tagger.py --- old/picard-release-2.2.2/picard/tagger.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/tagger.py 2019-11-06 13:33:23.000000000 +0100 @@ -285,9 +285,13 @@ webbrowser2.open(authorization_url) if not parent: parent = self.window - authorization_code, ok = QtWidgets.QInputDialog.getText(parent, - _("MusicBrainz Account"), _("Authorization code:")) - if ok: + dialog = QtWidgets.QInputDialog(parent) + dialog.setWindowModality(QtCore.Qt.WindowModal) + dialog.setWindowTitle(_("MusicBrainz Account")) + dialog.setLabelText(_("Authorization code:")) + status = dialog.exec_() + if status == QtWidgets.QDialog.Accepted: + authorization_code = dialog.textValue() self.webservice.oauth_manager.exchange_authorization_code( authorization_code, scopes, partial(self.on_mb_authorization_finished, callback)) @@ -677,6 +681,7 @@ self._acoustid.stop_analyze(file) del self.files[file.filename] file.remove(from_parent) + self.tagger_stats_changed.emit() def remove_album(self, album): """Remove the specified album.""" @@ -692,6 +697,7 @@ self.nats = None self.album_removed.emit(album) run_album_post_removal_processors(album) + self.tagger_stats_changed.emit() def remove_nat(self, track): """Remove the specified non-album track.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/filebrowser.py new/picard-release-2.2.3/picard/ui/filebrowser.py --- old/picard-release-2.2.2/picard/ui/filebrowser.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/filebrowser.py 2019-11-06 13:33:23.000000000 +0100 @@ -108,7 +108,8 @@ level = -1 super().scrollTo(index, scrolltype) parent = self.currentIndex().parent() - while parent.isValid(): + root = self.rootIndex() + while parent.isValid() and parent != root: parent = parent.parent() level += 1 pos_x = max(self.indentation() * level, 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/itemviews.py new/picard-release-2.2.3/picard/ui/itemviews.py --- old/picard-release-2.2.2/picard/ui/itemviews.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/itemviews.py 2019-11-06 13:33:23.000000000 +0100 @@ -17,6 +17,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from collections import defaultdict from functools import partial from heapq import ( heappop, @@ -141,18 +142,18 @@ TreeItem.text_color = self.palette().text().color() TreeItem.text_color_secondary = self.palette() \ .brush(QtGui.QPalette.Disabled, QtGui.QPalette.Text).color() - TrackItem.track_colors = { + TrackItem.track_colors = defaultdict(lambda: TreeItem.text_color, { File.NORMAL: interface_colors.get_qcolor('entity_saved'), File.CHANGED: TreeItem.text_color, File.PENDING: interface_colors.get_qcolor('entity_pending'), File.ERROR: interface_colors.get_qcolor('entity_error'), - } - FileItem.file_colors = { + }) + FileItem.file_colors = defaultdict(lambda: TreeItem.text_color, { File.NORMAL: TreeItem.text_color, File.CHANGED: TreeItem.text_color, File.PENDING: interface_colors.get_qcolor('entity_pending'), File.ERROR: interface_colors.get_qcolor('entity_error'), - } + }) def save_state(self): config.persist["splitter_state"] = self.saveState() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/mainwindow.py new/picard-release-2.2.3/picard/ui/mainwindow.py --- old/picard-release-2.2.2/picard/ui/mainwindow.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/mainwindow.py 2019-11-06 13:33:23.000000000 +0100 @@ -589,7 +589,8 @@ def _update_cd_lookup_button(self): if len(self.cd_lookup_menu.actions()) > 1: button = self.toolbar.widgetForAction(self.cd_lookup_action) - button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + if button: + button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.cd_lookup_action.setMenu(self.cd_lookup_menu) else: self.cd_lookup_action.setMenu(None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/options/general.py new/picard-release-2.2.3/picard/ui/options/general.py --- old/picard-release-2.2.2/picard/ui/options/general.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/options/general.py 2019-11-06 13:33:23.000000000 +0100 @@ -103,6 +103,9 @@ self.ui.logged_in.hide() self.ui.login.show() self.ui.logout.hide() + # Workaround for Qt not repainting the view on macOS after the changes. + # See https://tickets.metabrainz.org/browse/PICARD-1654 + self.ui.vboxlayout.parentWidget().repaint() def login(self): self.tagger.mb_login(self.on_login_finished, self) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/options/genres.py new/picard-release-2.2.3/picard/ui/options/genres.py --- old/picard-release-2.2.2/picard/ui/options/genres.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/options/genres.py 2019-11-06 13:33:23.000000000 +0100 @@ -94,11 +94,9 @@ self.ui.setupUi(self) self.ui.genres_filter.setToolTip(_(TOOLTIP_GENRES_FILTER)) - self.ui.genres_filter.setToolTipDuration(5000) self.ui.genres_filter.textChanged.connect(self.update_test_genres_filter) self.ui.test_genres_filter.setToolTip(_(TOOLTIP_TEST_GENRES_FILTER)) - self.ui.test_genres_filter.setToolTipDuration(5000) self.ui.test_genres_filter.textChanged.connect(self.update_test_genres_filter) #FIXME: colors aren't great from accessibility POV diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/options/plugins.py new/picard-release-2.2.3/picard/ui/options/plugins.py --- old/picard-release-2.2.2/picard/ui/options/plugins.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/options/plugins.py 2019-11-06 13:33:23.000000000 +0100 @@ -114,6 +114,9 @@ @staticmethod def set_icon(button, stdicon): button.setIcon(button.style().standardIcon(getattr(QtWidgets.QStyle, stdicon))) + # Workaround for Qt sometimes not updating the icon. + # See https://tickets.metabrainz.org/browse/PICARD-1647 + button.repaint() def show_install(self, button, mode): if mode == 'hide': diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/searchdialog/__init__.py new/picard-release-2.2.3/picard/ui/searchdialog/__init__.py --- old/picard-release-2.2.2/picard/ui/searchdialog/__init__.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/searchdialog/__init__.py 2019-11-06 13:33:23.000000000 +0100 @@ -3,6 +3,7 @@ # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2016 Rahul Raturi # Copyright (C) 2018 Laurent Monin +# Copyright (C) 2019 Philipp Wolfer # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -47,9 +48,8 @@ class ResultTable(QtWidgets.QTableWidget): - def __init__(self, parent, column_titles): - super().__init__(0, len(column_titles), parent) - self.setHorizontalHeaderLabels(column_titles) + def __init__(self, parent): + super().__init__(parent) self.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) self.setSelectionBehavior( @@ -70,6 +70,13 @@ self.verticalScrollBar().valueChanged.connect(emit_scrolled) self.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + def prepare(self, headers): + self.clear() + self.setColumnCount(len(headers)) + self.setHorizontalHeaderLabels(headers) + self.setRowCount(0) + self.setSortingEnabled(False) + class SearchBox(QtWidgets.QWidget): @@ -193,7 +200,6 @@ def __init__(self, parent, accept_button_title, show_search=True, search_type=None): super().__init__(parent) self.search_results = [] - self.table = None self.show_search = show_search self.search_type = search_type self.search_box = None @@ -203,6 +209,7 @@ # matching label as values self.columns = None self.sorting_enabled = True + self.create_table() self.finished.connect(self.save_state) @property @@ -283,20 +290,23 @@ def add_widget_to_center_layout(self, widget): """Update center widget with new child. If child widget exists, schedule it for deletion.""" - wid = self.center_layout.takeAt(0) - if wid: - if wid.widget().objectName() == "results_table": - self.table = None - wid.widget().deleteLater() + widget_item = self.center_layout.takeAt(0) + if widget_item: + current_widget = widget_item.widget() + current_widget.hide() + self.center_layout.removeWidget(current_widget) + if current_widget != self.table: + current_widget.deleteLater() self.center_layout.addWidget(widget) + widget.show() def show_progress(self): - self.progress_widget = QtWidgets.QWidget(self) - self.progress_widget.setObjectName("progress_widget") - layout = QtWidgets.QVBoxLayout(self.progress_widget) - text_label = QtWidgets.QLabel(_('<strong>Loading...</strong>'), self.progress_widget) + progress_widget = QtWidgets.QWidget(self) + progress_widget.setObjectName("progress_widget") + layout = QtWidgets.QVBoxLayout(progress_widget) + text_label = QtWidgets.QLabel(_('<strong>Loading...</strong>'), progress_widget) text_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom) - gif_label = QtWidgets.QLabel(self.progress_widget) + gif_label = QtWidgets.QLabel(progress_widget) movie = QtGui.QMovie(":/images/loader.gif") gif_label.setMovie(movie) movie.start() @@ -304,8 +314,8 @@ layout.addWidget(text_label) layout.addWidget(gif_label) layout.setContentsMargins(1, 1, 1, 1) - self.progress_widget.setLayout(layout) - self.add_widget_to_center_layout(self.progress_widget) + progress_widget.setLayout(layout) + self.add_widget_to_center_layout(progress_widget) def show_error(self, error, show_retry_button=False): """Display the error string. @@ -314,42 +324,45 @@ error -- Error string show_retry_button -- Whether to display retry button or not """ - self.error_widget = QtWidgets.QWidget(self) - self.error_widget.setObjectName("error_widget") - layout = QtWidgets.QVBoxLayout(self.error_widget) - error_label = QtWidgets.QLabel(error, self.error_widget) + error_widget = QtWidgets.QWidget(self) + error_widget.setObjectName("error_widget") + layout = QtWidgets.QVBoxLayout(error_widget) + error_label = QtWidgets.QLabel(error, error_widget) error_label.setWordWrap(True) error_label.setAlignment(QtCore.Qt.AlignCenter) error_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) layout.addWidget(error_label) if show_retry_button: - retry_widget = QtWidgets.QWidget(self.error_widget) + retry_widget = QtWidgets.QWidget(error_widget) retry_layout = QtWidgets.QHBoxLayout(retry_widget) - retry_button = QtWidgets.QPushButton(_("Retry"), self.error_widget) + retry_button = QtWidgets.QPushButton(_("Retry"), error_widget) retry_button.clicked.connect(self.retry) retry_button.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)) retry_layout.addWidget(retry_button) retry_layout.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) retry_widget.setLayout(retry_layout) layout.addWidget(retry_widget) - self.error_widget.setLayout(layout) - self.add_widget_to_center_layout(self.error_widget) + error_widget.setLayout(layout) + self.add_widget_to_center_layout(error_widget) - def prepare_table(self): - self.table = ResultTable(self, self.table_headers) + def create_table(self): + self.table = ResultTable(self) self.table.verticalHeader().setDefaultSectionSize(100) self.table.setSortingEnabled(False) - self.table.setObjectName("results_table") self.table.cellDoubleClicked.connect(self.accept) - self.restore_table_header_state() - self.add_widget_to_center_layout(self.table) + self.table.hide() def enable_accept_button(): self.accept_button.setEnabled(True) self.table.itemSelectionChanged.connect( enable_accept_button) + def prepare_table(self): + self.table.prepare(self.table_headers) + self.restore_table_header_state() + def show_table(self, sort_column=None, sort_order=QtCore.Qt.DescendingOrder): + self.add_widget_to_center_layout(self.table) self.table.horizontalHeader().setSortIndicatorShown(self.sorting_enabled) self.table.setSortingEnabled(self.sorting_enabled) if self.sorting_enabled and sort_column: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/picard/ui/searchdialog/album.py new/picard-release-2.2.3/picard/ui/searchdialog/album.py --- old/picard-release-2.2.2/picard/ui/searchdialog/album.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/picard/ui/searchdialog/album.py 2019-11-06 13:33:23.000000000 +0100 @@ -253,9 +253,6 @@ If server replies without error, try to get small thumbnail of front coverart of the release. """ - if not self.table: - return - cover_cell.fetch_task = None if error: @@ -292,9 +289,6 @@ Args: row -- Album's row in results table """ - if not self.table: - return - cover_cell.fetch_task = None if error: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/requirements-macos.txt new/picard-release-2.2.3/requirements-macos.txt --- old/picard-release-2.2.2/requirements-macos.txt 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/requirements-macos.txt 2019-11-06 13:33:23.000000000 +0100 @@ -1,3 +1,5 @@ discid==1.2.0 mutagen==1.42.0 +pyobjc-core==5.2 +pyobjc-framework-Cocoa==5.2 PyQt5==5.13.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/test/formats/common.py new/picard-release-2.2.3/test/formats/common.py --- old/picard-release-2.2.2/test/formats/common.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/test/formats/common.py 2019-11-06 13:33:23.000000000 +0100 @@ -354,6 +354,18 @@ self.assertNotIn('performer:piano', new_metadata) @skipUnlessTestfile + def test_save_performer(self): + if not self.format.supports_tag('performer:'): + return + instrument = "accordéon clavier « boutons »" + artist = "桑山哲也" + tag = "performer:" + instrument + metadata = Metadata({ tag: artist }) + loaded_metadata = save_and_load_metadata(self.filename, metadata) + self.assertIn(tag, loaded_metadata) + self.assertEqual(artist, loaded_metadata[tag]) + + @skipUnlessTestfile def test_ratings(self): if not self.supports_ratings: raise unittest.SkipTest("Ratings not supported") @@ -409,7 +421,16 @@ self.assertNotIn('artist', loaded_metadata) self.assertEqual(new_metadata['title'], loaded_metadata['title']) - def test_lyrcis_with_description(self): - metadata = Metadata({'lyrics:foo': 'bar'}) + @skipUnlessTestfile + def test_lyrics_with_description(self): + metadata = Metadata({'lyrics:foó': 'bar'}) + loaded_metadata = save_and_load_metadata(self.filename, metadata) + self.assertEqual(metadata['lyrics:foó'], loaded_metadata['lyrics']) + + @skipUnlessTestfile + def test_comments_with_description(self): + if not self.format.supports_tag('comment:foó'): + return + metadata = Metadata({'comment:foó': 'bar'}) loaded_metadata = save_and_load_metadata(self.filename, metadata) - self.assertEqual(metadata['lyrics:foo'], loaded_metadata['lyrics']) + self.assertEqual(metadata['comment:foó'], loaded_metadata['comment:foó']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/test/formats/test_apev2.py new/picard-release-2.2.3/test/formats/test_apev2.py --- old/picard-release-2.2.2/test/formats/test_apev2.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/test/formats/test_apev2.py 2019-11-06 13:33:23.000000000 +0100 @@ -71,6 +71,12 @@ loaded_metadata = load_metadata(self.filename) self.assertEqual(0, len(loaded_metadata.images)) + def test_supports_extended_tags(self): + performer_tag = "performer:accordéon clavier « boutons »" + self.assertTrue(self.format.supports_tag(performer_tag)) + self.assertTrue(self.format.supports_tag('lyrics:foó')) + self.assertTrue(self.format.supports_tag('comment:foó')) + class MonkeysAudioTest(CommonApeTests.ApeTestCase): testfile = 'test.ape' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/test/formats/test_id3.py new/picard-release-2.2.3/test/formats/test_id3.py --- old/picard-release-2.2.2/test/formats/test_id3.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/test/formats/test_id3.py 2019-11-06 13:33:23.000000000 +0100 @@ -229,7 +229,7 @@ self.assertEqual(1, len(raw_metadata['TXXX:Replaygain_Album_Peak'].text)) self.assertNotIn('TXXX:REPLAYGAIN_ALBUM_PEAK', raw_metadata) - def test_lyrcis_with_description(self): + def test_lyrics_with_description(self): metadata = Metadata({'lyrics:foo': 'bar'}) loaded_metadata = save_and_load_metadata(self.filename, metadata) self.assertEqual(metadata['lyrics:foo'], loaded_metadata['lyrics:foo']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/test/formats/test_vorbis.py new/picard-release-2.2.3/test/formats/test_vorbis.py --- old/picard-release-2.2.2/test/formats/test_vorbis.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/test/formats/test_vorbis.py 2019-11-06 13:33:23.000000000 +0100 @@ -4,6 +4,12 @@ import shutil from tempfile import mkstemp +from mutagen.flac import ( + Padding, + Picture, + VCFLACDict, +) + from test.picardtestcase import PicardTestCase from picard import ( @@ -12,6 +18,7 @@ ) from picard.coverart.image import CoverArtImage from picard.formats import vorbis +from picard.metadata import Metadata from .common import ( TAGS, @@ -19,12 +26,14 @@ load_metadata, load_raw, save_and_load_metadata, + save_metadata, save_raw, skipUnlessTestfile, ) from .coverart import ( CommonCoverArtTests, file_save_image, + load_coverart_file, ) @@ -117,6 +126,12 @@ loaded_metadata = load_metadata(self.filename) self.assertEqual(0, len(loaded_metadata.images)) + def test_supports_extended_tags(self): + performer_tag = "performer:accordéon clavier « boutons »" + self.assertTrue(self.format.supports_tag(performer_tag)) + self.assertTrue(self.format.supports_tag('lyrics:foó')) + self.assertTrue(self.format.supports_tag('comment:foó')) + class FLACTest(CommonVorbisTests.VorbisTestCase): testfile = 'test.flac' @@ -137,6 +152,29 @@ new_metadata = save_and_load_metadata(self.filename, original_metadata) self.assertEqual(new_metadata['~waveformatextensible_channel_mask'], '0x3') + @skipUnlessTestfile + def test_sort_pics_after_tags(self): + # First save file with pic block before tags + pic = Picture() + pic.data = load_coverart_file('mb.png') + f = load_raw(self.filename) + f.metadata_blocks.insert(1, pic) + f.save() + + # Save the file with Picard + metadata = Metadata() + save_metadata(self.filename, metadata) + + # Load raw file and verify picture block position + f = load_raw(self.filename) + tagindex = f.metadata_blocks.index(f.tags) + haspics = False + for b in f.metadata_blocks: + if b.code == Picture.code: + haspics = True + self.assertGreater(f.metadata_blocks.index(b), tagindex) + self.assertTrue(haspics, "Picture block expected, none found") + class OggVorbisTest(CommonVorbisTests.VorbisTestCase): testfile = 'test.ogg' @@ -208,6 +246,33 @@ for key in INVALID_KEYS: self.assertFalse(vorbis.is_valid_key(key), '%r is invalid' % key) + def test_flac_sort_pics_after_tags(self): + pic1 = Picture() + pic2 = Picture() + pic3 = Picture() + tags = VCFLACDict() + pad = Padding() + + blocks = [] + vorbis.flac_sort_pics_after_tags(blocks) + self.assertEqual([], blocks) + + blocks = [tags] + vorbis.flac_sort_pics_after_tags(blocks) + self.assertEqual([tags], blocks) + + blocks = [tags, pad, pic1] + vorbis.flac_sort_pics_after_tags(blocks) + self.assertEqual([tags, pad, pic1], blocks) + + blocks = [pic1, pic2, tags, pad, pic3] + vorbis.flac_sort_pics_after_tags(blocks) + self.assertEqual([tags, pic1, pic2, pad, pic3], blocks) + + blocks = [pic1, pic2, pad, pic3] + vorbis.flac_sort_pics_after_tags(blocks) + self.assertEqual([pic1, pic2, pad, pic3], blocks) + class FlacCoverArtTest(CommonCoverArtTests.CoverArtTestCase): testfile = 'test.flac' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/picard-release-2.2.2/test/test_taggenrefilter.py new/picard-release-2.2.3/test/test_taggenrefilter.py --- old/picard-release-2.2.2/test/test_taggenrefilter.py 2019-10-08 14:22:20.000000000 +0200 +++ new/picard-release-2.2.3/test/test_taggenrefilter.py 2019-11-06 13:33:23.000000000 +0100 @@ -73,7 +73,6 @@ -/r[io]ck$/ -/disco+/ +/discoooo/ - +/*/ """ tag_filter = TagGenreFilter(filters) @@ -92,6 +91,31 @@ self.assertTrue(tag_filter.skip('xdiscooox')) self.assertFalse(tag_filter.skip('xdiscoooox')) + def test_regex_filter_keep_all(self): + filters = """ + -/^j.zz/ + -/r[io]ck$/ + -/disco+/ + +/discoooo/ + +/.*/ + """ + tag_filter = TagGenreFilter(filters) + + self.assertFalse(tag_filter.skip('jazz')) + self.assertFalse(tag_filter.skip('jizz')) + self.assertFalse(tag_filter.skip('jazz blues')) + self.assertFalse(tag_filter.skip('blues jazz')) + + self.assertFalse(tag_filter.skip('rock')) + self.assertFalse(tag_filter.skip('blues rock')) + self.assertFalse(tag_filter.skip('blues rick')) + self.assertFalse(tag_filter.skip('rock blues')) + + self.assertFalse(tag_filter.skip('disco')) + self.assertFalse(tag_filter.skip('xdiscox')) + self.assertFalse(tag_filter.skip('xdiscooox')) + self.assertFalse(tag_filter.skip('xdiscoooox')) + def test_uppercased_filter(self): filters = """ -JAZZ*