diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml index b2b678ac1..b388e8c91 100644 --- a/.github/workflows/codacy-analysis.yml +++ b/.github/workflows/codacy-analysis.yml @@ -24,11 +24,11 @@ jobs: steps: # Checkout the repository to the GitHub Actions runner - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@1.1.0 + uses: codacy/codacy-analysis-cli-action@v4 with: # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository # You can also omit the token and run the tools that support default configurations @@ -44,6 +44,6 @@ jobs: # Upload the SARIF file generated in the previous step - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: sarif_file: results.sarif diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 26600a411..acde4702b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,11 +21,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: python # If you wish to specify custom queries, you can do so here or in a config file. @@ -36,7 +36,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -50,4 +50,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 9a205f0c0..fd4fb0b69 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -36,12 +36,12 @@ jobs: PYTHON_SHA256SUM: 7888174c6fe441b00448c7ab3e9cbf0e6c3c7dea0750577baf09e1383fc44656 MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos-deployment-version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Fetch entire history, needed for setting the build number - run: git fetch --depth=1 origin +refs/tags/release-*:refs/tags/release-* - name: Cache libdiscid - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/libdiscid key: ${{ runner.os }}-libdiscid-${{ env.DISCID_VERSION }}-${{ env.MACOSX_DEPLOYMENT_TARGET }} @@ -104,7 +104,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} CODESIGN_MACOS_P12_PASSWORD: ${{ secrets.CODESIGN_MACOS_P12_PASSWORD }} - name: Archive production artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: macos-app-${{ matrix.macos-deployment-version }} path: artifacts/ @@ -120,12 +120,12 @@ jobs: - portable fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Fetch entire history, needed for setting the build number - run: git fetch --depth=1 origin +refs/tags/release-*:refs/tags/release-* - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: 3.8 - name: Setup Windows build environment @@ -216,7 +216,7 @@ jobs: if: env.CODESIGN == '1' run: Remove-Item .\codesign.pfx - name: Archive production artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 if: matrix.type != 'signed-app' || env.CODESIGN == '1' with: name: windows-${{ matrix.type }} @@ -229,31 +229,31 @@ jobs: - package-macos - package-windows steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 with: python-version: 3.9 - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: macos-app-10.12 path: artifacts/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: macos-app-10.14 path: artifacts/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: windows-signed-app path: artifacts/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: windows-store-app path: artifacts/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: windows-installer path: artifacts/ - - uses: actions/download-artifact@v1 + - uses: actions/download-artifact@v3 with: name: windows-portable path: artifacts/ diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 5a77a81e4..601680eb5 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -11,8 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 with: python-version: 3.8 - name: Install dependencies @@ -26,7 +26,7 @@ jobs: run: | python setup.py clean sdist - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: picard-sdist path: dist/* @@ -79,9 +79,9 @@ jobs: - os: macos-10.15 python-version: '3.10' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install gettext and openssl (macOS) @@ -103,7 +103,7 @@ jobs: run: | python setup.py clean bdist_wheel - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: picard-bdist-${{ runner.os }} path: dist/*.whl diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9052a7447..40502624d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,9 +12,6 @@ jobs: exclude: - os: macos-latest python-version: '3.6' - # pyobjc is not yet compatible with Python 3.10 - - os: macos-latest - python-version: '3.10' include: - os: macos-10.15 python-version: '3.6' @@ -22,9 +19,9 @@ jobs: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -38,6 +35,7 @@ jobs: isort --check-only --diff --recursive picard test - name: Test with pytest if: always() + timeout-minutes: 30 run: | pip install pytest pytest-randomly pytest-cov pytest --verbose --cov=picard --cov-report xml:coverage.xml test @@ -71,9 +69,9 @@ jobs: ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -84,6 +82,7 @@ jobs: env: DEPENDENCIES: ${{ matrix.dependencies }} - name: Test with pytest + timeout-minutes: 30 run: | pip install pytest pytest-randomly pytest-cov pytest --verbose test @@ -96,9 +95,9 @@ jobs: python-version: ['3.8'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install gettext (Linux) diff --git a/NEWS.md b/NEWS.md index 9a67d256a..aea7d94c2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,13 @@ +# Version 2.8.1 - 2022-06-07 + +## Bugfixes + +- [PICARD-2489](https://tickets.metabrainz.org/browse/PICARD-2489) - Preferred Releases UI not loading in Options menu on FreeBSD +- [PICARD-2491](https://tickets.metabrainz.org/browse/PICARD-2491) - Crash upon loading album information for releases with MBID redirects +- [PICARD-2493](https://tickets.metabrainz.org/browse/PICARD-2493) - If locales for picard-countries or picard-attributes are missing UI translation is completely skipped +- [PICARD-2494](https://tickets.metabrainz.org/browse/PICARD-2494) - Remove empty info dialog for "[standalone-recordings]" special album entry + + # Version 2.8 - 2022-05-24 ## Tasks diff --git a/picard/__init__.py b/picard/__init__.py index d6aed0bdf..bb16f00ec 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -42,7 +42,7 @@ PICARD_APP_NAME = "Picard" PICARD_DISPLAY_NAME = "MusicBrainz Picard" PICARD_APP_ID = "org.musicbrainz.Picard" PICARD_DESKTOP_NAME = PICARD_APP_ID + ".desktop" -PICARD_VERSION = Version(2, 8, 0, 'final', 0) +PICARD_VERSION = Version(2, 8, 1, 'final', 0) # optional build version diff --git a/picard/browser/filelookup.py b/picard/browser/filelookup.py index 327f5c2dd..7f09f6da7 100644 --- a/picard/browser/filelookup.py +++ b/picard/browser/filelookup.py @@ -35,10 +35,8 @@ import re from PyQt5 import QtCore from picard import log -from picard.const import ( - PICARD_URLS, - QUERY_LIMIT, -) +from picard.config import get_config +from picard.const import PICARD_URLS from picard.disc import Disc from picard.util import ( build_qurl, @@ -172,8 +170,9 @@ class FileLookup(object): def search_entity(self, type_, query, adv=False, mbid_matched_callback=None, force_browser=False): if not force_browser and self.mbid_lookup(query, type_, mbid_matched_callback=mbid_matched_callback): return True + config = get_config() params = { - 'limit': QUERY_LIMIT, + 'limit': config.setting['query_limit'], 'type': type_, 'query': query, } diff --git a/picard/cluster.py b/picard/cluster.py index c9fb17f51..37ff649df 100644 --- a/picard/cluster.py +++ b/picard/cluster.py @@ -45,7 +45,6 @@ import re from PyQt5 import QtCore from picard.config import get_config -from picard.const import QUERY_LIMIT from picard.file import File from picard.metadata import ( Metadata, @@ -282,11 +281,12 @@ class Cluster(FileList): N_("Looking up the metadata for cluster %(album)s..."), {'album': self.metadata['album']} ) + config = get_config() self.lookup_task = self.tagger.mb_api.find_releases(self._lookup_finished, artist=self.metadata['albumartist'], release=self.metadata['album'], tracks=str(len(self.files)), - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def clear_lookup_task(self): if self.lookup_task: diff --git a/picard/const/__init__.py b/picard/const/__init__.py index 210db1d25..0229dd84f 100644 --- a/picard/const/__init__.py +++ b/picard/const/__init__.py @@ -142,7 +142,7 @@ PLUGINS_API = { } # Default query limit -QUERY_LIMIT = 25 +QUERY_LIMIT = 50 # Maximum number of covers to draw in a stack in CoverArtThumbnail MAX_COVERS_TO_STACK = 4 diff --git a/picard/coverart/image.py b/picard/coverart/image.py index 858be1011..10e7246e8 100644 --- a/picard/coverart/image.py +++ b/picard/coverart/image.py @@ -43,6 +43,7 @@ from PyQt5.QtCore import ( from picard import log from picard.config import get_config from picard.const import DEFAULT_COVER_IMAGE_FILENAME +from picard.const.sys import IS_WIN from picard.coverart.utils import ( Id3ImageType, image_type_as_id3_num, @@ -55,6 +56,7 @@ from picard.util import ( imageinfo, is_absolute_path, periodictouch, + sanitize_filename, ) from picard.util.scripttofilename import script_to_filename @@ -331,7 +333,8 @@ class CoverArtImage: return config = get_config() if config.setting["image_type_as_filename"] and not self.is_front_image(): - filename = self.maintype + win_compat = IS_WIN or config.setting["windows_compatibility"] + filename = sanitize_filename(self.maintype, win_compat=win_compat) log.debug("Make cover filename from types: %r -> %r", self.types, filename) else: diff --git a/picard/disc/eaclog.py b/picard/disc/eaclog.py index 8395edf80..7facaf785 100644 --- a/picard/disc/eaclog.py +++ b/picard/disc/eaclog.py @@ -25,7 +25,10 @@ import re -from picard.disc.utils import calculate_mb_toc_numbers +from picard.disc.utils import ( + TocEntry, + calculate_mb_toc_numbers, +) RE_TOC_TABLE_HEADER = re.compile(r""" \s* @@ -50,9 +53,6 @@ RE_TOC_TABLE_LINE = re.compile(r""" \s*$""", re.VERBOSE) -PREGAP_LENGTH = 150 - - def filter_toc_entries(lines): """ Take iterator of lines, return iterator of toc entries @@ -71,7 +71,7 @@ def filter_toc_entries(lines): m = RE_TOC_TABLE_LINE.match(line) if not m: break - yield m.groupdict() + yield TocEntry(int(m['num']), int(m['start_sector']), int(m['end_sector'])) ENCODING_BOMS = { @@ -92,7 +92,7 @@ def _detect_encoding(path): def toc_from_file(path): - """Reads EAC / XLD log files, generates musicbrainz disc TOC listing for use as discid. + """Reads EAC / XLD log files, generates MusicBrainz disc TOC listing for use as discid. Warning: may work wrong for discs having data tracks. May generate wrong results on other non-standard cases.""" diff --git a/picard/disc/utils.py b/picard/disc/utils.py index 634f07942..88643deab 100644 --- a/picard/disc/utils.py +++ b/picard/disc/utils.py @@ -23,32 +23,48 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from collections import namedtuple + + PREGAP_LENGTH = 150 +DATA_TRACK_GAP = 11400 + + +TocEntry = namedtuple('TocEntry', 'number start_sector end_sector') class NotSupportedTOCError(Exception): pass -def calculate_mb_toc_numbers(toc_entries): +def calculate_mb_toc_numbers(toc): """ - Take iterator of toc entries, return a tuple of numbers for musicbrainz disc id + Take iterator of TOC entries, return a tuple of numbers for MusicBrainz disc id - Each toc entry is a dict with the following keys: - - num: track number + Each entry is a TocEntry namedtuple with the following fields: + - number: track number - start_sector: start sector of the track - end_sector: end sector of the track """ - eac = tuple(toc_entries) - num_tracks = len(eac) + toc = tuple(toc) + toc = _remove_data_track(toc) + num_tracks = len(toc) if not num_tracks: - raise NotSupportedTOCError("Empty track list: %s", eac) + raise NotSupportedTOCError("Empty track list") expected_tracknums = tuple(range(1, num_tracks+1)) - tracknums = tuple(int(e['num']) for e in eac) + tracknums = tuple(e.number for e in toc) if expected_tracknums != tracknums: - raise NotSupportedTOCError("Non-standard track number sequence: %s", tracknums) + raise NotSupportedTOCError(f"Non-standard track number sequence: {tracknums}") - leadout_offset = int(eac[-1]['end_sector']) + PREGAP_LENGTH + 1 - offsets = tuple((int(x['start_sector']) + PREGAP_LENGTH) for x in eac) + leadout_offset = toc[-1].end_sector + PREGAP_LENGTH + 1 + offsets = tuple(e.start_sector + PREGAP_LENGTH for e in toc) return (1, num_tracks, leadout_offset) + offsets + + +def _remove_data_track(toc): + if len(toc) > 1: + last_track_gap = toc[-1].start_sector - toc[-2].end_sector + if last_track_gap == DATA_TRACK_GAP + 1: + toc = toc[:-1] + return toc diff --git a/picard/disc/whipperlog.py b/picard/disc/whipperlog.py index e040b0d6b..2552a9146 100644 --- a/picard/disc/whipperlog.py +++ b/picard/disc/whipperlog.py @@ -18,10 +18,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - import yaml -from picard.disc.utils import calculate_mb_toc_numbers +from picard.disc.utils import ( + TocEntry, + calculate_mb_toc_numbers, +) def toc_from_file(path): @@ -32,11 +34,7 @@ def toc_from_file(path): with open(path, encoding='utf-8') as f: data = yaml.safe_load(f) toc_entries = ( - { - 'num': num, - 'start_sector': t['Start sector'], - 'end_sector': t['End sector'], - } + TocEntry(num, t['Start sector'], t['End sector']) for num, t in data['TOC'].items() ) return calculate_mb_toc_numbers(toc_entries) diff --git a/picard/file.py b/picard/file.py index 7de338054..7d143f3a7 100644 --- a/picard/file.py +++ b/picard/file.py @@ -4,7 +4,7 @@ # # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006-2009, 2011-2013, 2017 Lukáš Lalinský -# Copyright (C) 2007-2011, 2015, 2018-2021 Philipp Wolfer +# Copyright (C) 2007-2011, 2015, 2018-2022 Philipp Wolfer # Copyright (C) 2008 Gary van der Merwe # Copyright (C) 2008-2009 Nikolai Prokoschenko # Copyright (C) 2009 Carlin Mangar @@ -62,7 +62,6 @@ from picard import ( log, ) from picard.config import get_config -from picard.const import QUERY_LIMIT from picard.const.sys import ( IS_MACOS, IS_WIN, @@ -876,6 +875,7 @@ class File(QtCore.QObject, Item): self.clear_lookup_task() metadata = self.metadata self.set_pending() + config = get_config() self.lookup_task = self.tagger.mb_api.find_tracks( partial(self._lookup_finished, File.LOOKUP_METADATA), track=metadata['title'], @@ -885,7 +885,7 @@ class File(QtCore.QObject, Item): tracks=metadata['totaltracks'], qdur=str(metadata.length // 2000), isrc=metadata['isrc'], - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def clear_lookup_task(self): if self.lookup_task: diff --git a/picard/profile.py b/picard/profile.py index 038450f39..ce46376df 100644 --- a/picard/profile.py +++ b/picard/profile.py @@ -158,6 +158,11 @@ class UserProfileGroups(): N_("Ignore track duration difference under x seconds"), ["ignore_track_duration_difference_under", "label_track_duration_diff"] ), + SettingDesc( + "query_limit", + N_("Maximum number of entities to return per MusicBrainz query"), + ["query_limit", "label_query_limit"] + ), SettingDesc("completeness_ignore_videos", N_("Completeness check ignore: Video tracks"), ["completeness_ignore_videos"]), SettingDesc("completeness_ignore_pregap", N_("Completeness check ignore: Pregap tracks"), ["completeness_ignore_pregap"]), SettingDesc("completeness_ignore_data", N_("Completeness check ignore: Data tracks"), ["completeness_ignore_data"]), diff --git a/picard/ui/options/advanced.py b/picard/ui/options/advanced.py index 8e0c49e66..35cb862f3 100644 --- a/picard/ui/options/advanced.py +++ b/picard/ui/options/advanced.py @@ -4,7 +4,7 @@ # # Copyright (C) 2006-2007 Lukáš Lalinský # Copyright (C) 2013-2015, 2018, 2020-2021 Laurent Monin -# Copyright (C) 2014, 2019-2021 Philipp Wolfer +# Copyright (C) 2014, 2019-2022 Philipp Wolfer # Copyright (C) 2016-2017 Sambhav Kothari # # This program is free software; you can redistribute it and/or @@ -29,6 +29,7 @@ from picard.config import ( TextOption, get_config, ) +from picard.const import QUERY_LIMIT from picard.ui.options import ( OptionsPage, @@ -51,6 +52,7 @@ class AdvancedOptionsPage(OptionsPage): BoolOption("setting", "ignore_hidden_files", False), BoolOption("setting", "recursively_add_files", True), IntOption("setting", "ignore_track_duration_difference_under", 2), + IntOption("setting", "query_limit", QUERY_LIMIT), BoolOption("setting", "completeness_ignore_videos", False), BoolOption("setting", "completeness_ignore_pregap", False), BoolOption("setting", "completeness_ignore_data", False), @@ -70,6 +72,7 @@ class AdvancedOptionsPage(OptionsPage): self.ui.ignore_hidden_files.setChecked(config.setting["ignore_hidden_files"]) self.ui.recursively_add_files.setChecked(config.setting["recursively_add_files"]) self.ui.ignore_track_duration_difference_under.setValue(config.setting["ignore_track_duration_difference_under"]) + self.ui.query_limit.setCurrentText(str(config.setting["query_limit"])) self.ui.completeness_ignore_videos.setChecked(config.setting["completeness_ignore_videos"]) self.ui.completeness_ignore_pregap.setChecked(config.setting["completeness_ignore_pregap"]) self.ui.completeness_ignore_data.setChecked(config.setting["completeness_ignore_data"]) @@ -83,6 +86,7 @@ class AdvancedOptionsPage(OptionsPage): config.setting["ignore_hidden_files"] = self.ui.ignore_hidden_files.isChecked() config.setting["recursively_add_files"] = self.ui.recursively_add_files.isChecked() config.setting["ignore_track_duration_difference_under"] = self.ui.ignore_track_duration_difference_under.value() + config.setting["query_limit"] = self.ui.query_limit.currentText() config.setting["completeness_ignore_videos"] = self.ui.completeness_ignore_videos.isChecked() config.setting["completeness_ignore_pregap"] = self.ui.completeness_ignore_pregap.isChecked() config.setting["completeness_ignore_data"] = self.ui.completeness_ignore_data.isChecked() diff --git a/picard/ui/options/plugins.py b/picard/ui/options/plugins.py index 4f618839f..7efc9a117 100644 --- a/picard/ui/options/plugins.py +++ b/picard/ui/options/plugins.py @@ -31,8 +31,10 @@ from functools import partial +from html import escape from operator import attrgetter import os.path +import re from PyQt5 import ( QtCore, @@ -599,16 +601,33 @@ class PluginsOptionsPage(OptionsPage): if plugin.description: text.append(plugin.description + "
") infos = [ - (_("Name"), plugin.name), - (_("Authors"), plugin.author), + (_("Name"), escape(plugin.name)), + (_("Authors"), self.link_authors(plugin.author)), (_("License"), plugin.license), - (_("Files"), plugin.files_list), + (_("Files"), escape(plugin.files_list)), ] for label, value in infos: if value: text.append("{0}: {1}".format(label, value)) self.ui.details.setText("

{0}

".format("
\n".join(text))) + @staticmethod + def link_authors(authors): + formatted_authors = [] + re_author = re.compile(r"(?P.*?)\s*<(?P.*?@.*?)>") + for author in authors.split(','): + author = author.strip() + match = re_author.fullmatch(author) + if match: + author_str = '{author}'.format( + email=escape(match['email']), + author=escape(match['author']), + ) + formatted_authors.append(author_str) + else: + formatted_authors.append(escape(author)) + return ', '.join(formatted_authors) + def change_details(self): item = self.selected_item() if item: diff --git a/picard/ui/searchdialog/album.py b/picard/ui/searchdialog/album.py index a95d356d4..555b59893 100644 --- a/picard/ui/searchdialog/album.py +++ b/picard/ui/searchdialog/album.py @@ -31,11 +31,13 @@ from PyQt5 import ( from PyQt5.QtCore import pyqtSignal from picard import log -from picard.config import Option +from picard.config import ( + Option, + get_config, +) from picard.const import ( CAA_HOST, CAA_PORT, - QUERY_LIMIT, ) from picard.coverart.image import CaaThumbnailCoverArtImage from picard.mbjson import ( @@ -168,11 +170,12 @@ class AlbumSearchDialog(SearchDialog): self.retry_params = Retry(self.search, text) self.search_box_text(text) self.show_progress() + config = get_config() self.tagger.mb_api.find_releases(self.handle_reply, query=text, search=True, advanced_search=self.use_advanced_search, - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def show_similar_albums(self, cluster): """Perform search by using existing metadata information diff --git a/picard/ui/searchdialog/artist.py b/picard/ui/searchdialog/artist.py index 0570b0854..e8b6ab1d1 100644 --- a/picard/ui/searchdialog/artist.py +++ b/picard/ui/searchdialog/artist.py @@ -4,7 +4,7 @@ # # Copyright (C) 2016 Rahul Raturi # Copyright (C) 2018, 2020-2021 Laurent Monin -# Copyright (C) 2018-2021 Philipp Wolfer +# Copyright (C) 2018-2022 Philipp Wolfer # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,8 +23,10 @@ from PyQt5 import QtCore -from picard.config import Option -from picard.const import QUERY_LIMIT +from picard.config import ( + Option, + get_config, +) from picard.mbjson import artist_to_metadata from picard.metadata import Metadata @@ -64,11 +66,12 @@ class ArtistSearchDialog(SearchDialog): self.retry_params = Retry(self.search, text) self.search_box_text(text) self.show_progress() + config = get_config() self.tagger.mb_api.find_artists(self.handle_reply, query=text, search=True, advanced_search=self.use_advanced_search, - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def retry(self): self.retry_params.function(self.retry_params.query) diff --git a/picard/ui/searchdialog/track.py b/picard/ui/searchdialog/track.py index 3b87e3ce7..19e3b10fc 100644 --- a/picard/ui/searchdialog/track.py +++ b/picard/ui/searchdialog/track.py @@ -24,8 +24,10 @@ from PyQt5 import QtCore -from picard.config import Option -from picard.const import QUERY_LIMIT +from picard.config import ( + Option, + get_config, +) from picard.file import File from picard.mbjson import ( countries_from_node, @@ -76,11 +78,12 @@ class TrackSearchDialog(SearchDialog): self.retry_params = Retry(self.search, text) self.search_box_text(text) self.show_progress() + config = get_config() self.tagger.mb_api.find_tracks(self.handle_reply, query=text, search=True, advanced_search=self.use_advanced_search, - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def show_similar_tracks(self, file_): """Perform search using existing metadata information diff --git a/picard/ui/ui_options_advanced.py b/picard/ui/ui_options_advanced.py index 68237e4ea..d88779bca 100644 --- a/picard/ui/ui_options_advanced.py +++ b/picard/ui/ui_options_advanced.py @@ -10,7 +10,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_AdvancedOptionsPage(object): def setupUi(self, AdvancedOptionsPage): AdvancedOptionsPage.setObjectName("AdvancedOptionsPage") - AdvancedOptionsPage.resize(570, 435) + AdvancedOptionsPage.resize(570, 455) self.vboxlayout = QtWidgets.QVBoxLayout(AdvancedOptionsPage) self.vboxlayout.setObjectName("vboxlayout") self.groupBox = QtWidgets.QGroupBox(AdvancedOptionsPage) @@ -18,9 +18,45 @@ class Ui_AdvancedOptionsPage(object): self.gridlayout = QtWidgets.QGridLayout(self.groupBox) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName("gridlayout") - self.recursively_add_files = QtWidgets.QCheckBox(self.groupBox) - self.recursively_add_files.setObjectName("recursively_add_files") - self.gridlayout.addWidget(self.recursively_add_files, 5, 0, 1, 1) + self.label_ignore_regex = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_ignore_regex.sizePolicy().hasHeightForWidth()) + self.label_ignore_regex.setSizePolicy(sizePolicy) + self.label_ignore_regex.setWordWrap(True) + self.label_ignore_regex.setObjectName("label_ignore_regex") + self.gridlayout.addWidget(self.label_ignore_regex, 1, 0, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_query_limit = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_query_limit.sizePolicy().hasHeightForWidth()) + self.label_query_limit.setSizePolicy(sizePolicy) + self.label_query_limit.setObjectName("label_query_limit") + self.horizontalLayout_2.addWidget(self.label_query_limit) + self.query_limit = QtWidgets.QComboBox(self.groupBox) + self.query_limit.setCurrentText("50") + self.query_limit.setObjectName("query_limit") + self.query_limit.addItem("") + self.query_limit.setItemText(0, "25") + self.query_limit.addItem("") + self.query_limit.setItemText(1, "50") + self.query_limit.addItem("") + self.query_limit.setItemText(2, "75") + self.query_limit.addItem("") + self.query_limit.setItemText(3, "100") + self.horizontalLayout_2.addWidget(self.query_limit) + self.gridlayout.addLayout(self.horizontalLayout_2, 8, 0, 1, 1) + self.regex_error = QtWidgets.QLabel(self.groupBox) + self.regex_error.setText("") + self.regex_error.setObjectName("regex_error") + self.gridlayout.addWidget(self.regex_error, 3, 0, 1, 1) + self.ignore_regex = QtWidgets.QLineEdit(self.groupBox) + self.ignore_regex.setObjectName("ignore_regex") + self.gridlayout.addWidget(self.ignore_regex, 2, 0, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout.setObjectName("horizontalLayout") @@ -48,25 +84,12 @@ class Ui_AdvancedOptionsPage(object): self.ignore_track_duration_difference_under.setObjectName("ignore_track_duration_difference_under") self.horizontalLayout.addWidget(self.ignore_track_duration_difference_under) self.gridlayout.addLayout(self.horizontalLayout, 6, 0, 2, 1) - self.label_ignore_regex = QtWidgets.QLabel(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_ignore_regex.sizePolicy().hasHeightForWidth()) - self.label_ignore_regex.setSizePolicy(sizePolicy) - self.label_ignore_regex.setWordWrap(True) - self.label_ignore_regex.setObjectName("label_ignore_regex") - self.gridlayout.addWidget(self.label_ignore_regex, 1, 0, 1, 1) - self.regex_error = QtWidgets.QLabel(self.groupBox) - self.regex_error.setText("") - self.regex_error.setObjectName("regex_error") - self.gridlayout.addWidget(self.regex_error, 3, 0, 1, 1) + self.recursively_add_files = QtWidgets.QCheckBox(self.groupBox) + self.recursively_add_files.setObjectName("recursively_add_files") + self.gridlayout.addWidget(self.recursively_add_files, 5, 0, 1, 1) self.ignore_hidden_files = QtWidgets.QCheckBox(self.groupBox) self.ignore_hidden_files.setObjectName("ignore_hidden_files") self.gridlayout.addWidget(self.ignore_hidden_files, 4, 0, 1, 1) - self.ignore_regex = QtWidgets.QLineEdit(self.groupBox) - self.ignore_regex.setObjectName("ignore_regex") - self.gridlayout.addWidget(self.ignore_regex, 2, 0, 1, 1) self.vboxlayout.addWidget(self.groupBox) self.groupBox_completeness = QtWidgets.QGroupBox(AdvancedOptionsPage) self.groupBox_completeness.setObjectName("groupBox_completeness") @@ -101,11 +124,13 @@ class Ui_AdvancedOptionsPage(object): self.vboxlayout.addWidget(self.groupBox_2) self.retranslateUi(AdvancedOptionsPage) + self.query_limit.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(AdvancedOptionsPage) AdvancedOptionsPage.setTabOrder(self.ignore_regex, self.ignore_hidden_files) AdvancedOptionsPage.setTabOrder(self.ignore_hidden_files, self.recursively_add_files) AdvancedOptionsPage.setTabOrder(self.recursively_add_files, self.ignore_track_duration_difference_under) - AdvancedOptionsPage.setTabOrder(self.ignore_track_duration_difference_under, self.completeness_ignore_videos) + AdvancedOptionsPage.setTabOrder(self.ignore_track_duration_difference_under, self.query_limit) + AdvancedOptionsPage.setTabOrder(self.query_limit, self.completeness_ignore_videos) AdvancedOptionsPage.setTabOrder(self.completeness_ignore_videos, self.completeness_ignore_pregap) AdvancedOptionsPage.setTabOrder(self.completeness_ignore_pregap, self.completeness_ignore_data) AdvancedOptionsPage.setTabOrder(self.completeness_ignore_data, self.completeness_ignore_silence) @@ -113,9 +138,10 @@ class Ui_AdvancedOptionsPage(object): def retranslateUi(self, AdvancedOptionsPage): _translate = QtCore.QCoreApplication.translate self.groupBox.setTitle(_("Advanced options")) - self.recursively_add_files.setText(_("Include sub-folders when adding files from folder")) - self.label_track_duration_diff.setText(_("Ignore track duration difference under this number of seconds")) self.label_ignore_regex.setText(_("Ignore file paths matching the following regular expression:")) + self.label_query_limit.setText(_("Maximum number of entities to return per MusicBrainz query")) + self.label_track_duration_diff.setText(_("Ignore track duration difference under this number of seconds")) + self.recursively_add_files.setText(_("Include sub-folders when adding files from folder")) self.ignore_hidden_files.setText(_("Ignore hidden files")) self.groupBox_completeness.setTitle(_("Ignore the following tracks when determining whether a release is complete")) self.completeness_ignore_videos.setText(_("Video tracks")) diff --git a/po/appstream/nl.po b/po/appstream/nl.po index 7f5cfd4e2..6b97f5b2f 100644 --- a/po/appstream/nl.po +++ b/po/appstream/nl.po @@ -5,7 +5,7 @@ # # Translators: # Lukáš Lalinský , 2020 -# Maurits Meulenbelt , 2021 +# Maurits Meulenbelt, 2021 # #, fuzzy msgid "" @@ -14,7 +14,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-14 23:03+0100\n" "PO-Revision-Date: 2018-11-25 07:37+0000\n" -"Last-Translator: Maurits Meulenbelt , 2021\n" +"Last-Translator: Maurits Meulenbelt, 2021\n" "Language-Team: Dutch (https://www.transifex.com/musicbrainz/teams/13846/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" diff --git a/po/attributes/nl.po b/po/attributes/nl.po index 15f9b67c1..8cf18bb14 100644 --- a/po/attributes/nl.po +++ b/po/attributes/nl.po @@ -2,8 +2,8 @@ # Translators: # reneweesp , 2017 # Maurits Meulenbelt