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 , 2012
-# Maurits Meulenbelt , 2012
-# Maurits Meulenbelt , 2013-2022
+# Maurits Meulenbelt, 2012
+# Maurits Meulenbelt, 2013-2022
# Nikolai Prokoschenko , 2011
# Nikolai Prokoschenko , 2011
# Niko Strijbol , 2018
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: MusicBrainz\n"
"PO-Revision-Date: 2012-05-24 18:52+0000\n"
-"Last-Translator: Maurits Meulenbelt , 2013-2022\n"
+"Last-Translator: Maurits Meulenbelt, 2013-2022\n"
"Language-Team: Dutch (http://www.transifex.com/musicbrainz/musicbrainz/language/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -186,7 +186,7 @@ msgstr "Een ballet is muziek die is gecomponeerd om samen met choreografie te wo
#: DB:release_packaging/description:19
msgctxt "release_packaging"
msgid "A box usually containing multiple discs as part of a boxed set."
-msgstr ""
+msgstr "Een doos met meestal meerdere cd’s als onderdeel van een boxset."
#: DB:work_type/description:3
msgctxt "work_type"
diff --git a/po/countries/nl.po b/po/countries/nl.po
index 8fb9651f6..ce709c6a9 100644
--- a/po/countries/nl.po
+++ b/po/countries/nl.po
@@ -1,13 +1,13 @@
#
# Translators:
-# Maurits Meulenbelt , 2012
-# Maurits Meulenbelt , 2014-2015
+# Maurits Meulenbelt, 2012
+# Maurits Meulenbelt, 2014-2015
# Nikolai Prokoschenko , 2011
msgid ""
msgstr ""
"Project-Id-Version: MusicBrainz\n"
"PO-Revision-Date: 2012-05-24 19:20+0000\n"
-"Last-Translator: Maurits Meulenbelt , 2014-2015\n"
+"Last-Translator: Maurits Meulenbelt, 2014-2015\n"
"Language-Team: Dutch (http://www.transifex.com/musicbrainz/musicbrainz/language/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
diff --git a/po/es.po b/po/es.po
index 1a68dff47..3b5979c68 100644
--- a/po/es.po
+++ b/po/es.po
@@ -84,7 +84,7 @@ msgstr "Archivos desagrupados"
msgid "Added %(count)i release to collection \"%(name)s\""
msgid_plural "Added %(count)i releases to collection \"%(name)s\""
msgstr[0] "Añadida %(count)i publicación a la colección \"%(name)s\""
-msgstr[1] ""
+msgstr[1] "Añadidas %(count)i publicaciones a la colección \"%(name)s\""
msgstr[2] "Añadidas %(count)i publicaciones a la colección \"%(name)s\""
#: picard/collection.py:86
@@ -92,7 +92,7 @@ msgstr[2] "Añadidas %(count)i publicaciones a la colección \"%(name)s\""
msgid "Removed %(count)i release from collection \"%(name)s\""
msgid_plural "Removed %(count)i releases from collection \"%(name)s\""
msgstr[0] "Eliminada %(count)i publicación de la colección \"%(name)s\""
-msgstr[1] ""
+msgstr[1] "Eliminadas %(count)i publicaciones de la colección \"%(name)s\""
msgstr[2] "Eliminadas %(count)i publicaciones de la colección \"%(name)s\""
#: picard/collection.py:97
@@ -3675,7 +3675,7 @@ msgstr "Actualizar la lista"
msgid "%s (%i release)"
msgid_plural "%s (%i releases)"
msgstr[0] "%s (%i publicación)"
-msgstr[1] ""
+msgstr[1] "%s (%i publicaciones)"
msgstr[2] "%s (%i publicaciones)"
#: picard/ui/colors.py:37
@@ -3904,7 +3904,7 @@ msgstr "&Información"
msgid "%i file in this track"
msgid_plural "%i files in this track"
msgstr[0] "%i fichero en esta pista"
-msgstr[1] ""
+msgstr[1] "%i ficheros en esta pista"
msgstr[2] "%i ficheros en esta pista"
#: picard/ui/infodialog.py:409
@@ -4215,7 +4215,7 @@ msgid ""
msgid_plural ""
"There are %d unsaved files. Closing Picard will lose all unsaved changes."
msgstr[0] "Hay %d archivo no guardado. Si cierra Picard perderá todos los cambios sin guardar."
-msgstr[1] ""
+msgstr[1] "Hay %d archivos no guardados. Si cierras Picard perderás todos los cambios sin guardar."
msgstr[2] "Hay %d archivos no guardados. Si cierras Picard perderás todos los cambios sin guardar."
#: picard/ui/mainwindow.py:347
@@ -4740,7 +4740,7 @@ msgstr ""
msgid "(different across %d item)"
msgid_plural "(different across %d items)"
msgstr[0] "(varía en %d elemento)"
-msgstr[1] ""
+msgstr[1] "(varía en %d elementos)"
msgstr[2] "(varía en %d elementos)"
#: picard/ui/metadatabox.py:120
@@ -4748,7 +4748,7 @@ msgstr[2] "(varía en %d elementos)"
msgid "(missing from %d item)"
msgid_plural "(missing from %d items)"
msgstr[0] "(falta en %d elemento)"
-msgstr[1] ""
+msgstr[1] "(falta en %d elementos)"
msgstr[2] "(falta en %d elementos)"
#: picard/ui/metadatabox.py:218
@@ -4807,7 +4807,7 @@ msgstr "Remover de la lista 'Etiquetas Preservadas'"
msgid "Use Original Value"
msgid_plural "Use Original Values"
msgstr[0] ""
-msgstr[1] ""
+msgstr[1] "Usar valor original"
msgstr[2] "Usar valor original"
#: picard/ui/metadatabox.py:439
diff --git a/po/nl.po b/po/nl.po
index 8452e02ad..eb5983d2b 100644
--- a/po/nl.po
+++ b/po/nl.po
@@ -6,8 +6,8 @@
# reneweesp , 2017-2019
# Dogmatica , 2006
# Jan van Thiel , 2006
-# Maurits Meulenbelt , 2012
-# Maurits Meulenbelt , 2012-2022
+# Maurits Meulenbelt, 2012
+# Maurits Meulenbelt, 2012-2022
# Philipp Wolfer , 2019-2021
msgid ""
msgstr ""
@@ -15,7 +15,7 @@ msgstr ""
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-05-01 15:38+0200\n"
"PO-Revision-Date: 2012-05-29 16:17+0000\n"
-"Last-Translator: Maurits Meulenbelt , 2012-2022\n"
+"Last-Translator: Maurits Meulenbelt, 2012-2022\n"
"Language-Team: Dutch (http://www.transifex.com/musicbrainz/musicbrainz/language/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
diff --git a/po/zh_TW.po b/po/zh_TW.po
index 3979fde7d..f8b92b455 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -7,7 +7,7 @@
# Philipp Wolfer , 2019-2022
# S B , 2022
# Shen-Ta Hsieh(BestSteve) , 2016
-# Shen-Ta Hsieh(BestSteve) , 2016-2020
+# Shen-Ta Hsieh(BestSteve) , 2016-2020,2022
# riotism, 2017-2018,2020
msgid ""
msgstr ""
@@ -15,7 +15,7 @@ msgstr ""
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-05-01 15:38+0200\n"
"PO-Revision-Date: 2012-05-29 16:17+0000\n"
-"Last-Translator: Philipp Wolfer , 2019-2022\n"
+"Last-Translator: Shen-Ta Hsieh(BestSteve) , 2016-2020,2022\n"
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/musicbrainz/musicbrainz/language/zh_TW/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -6933,7 +6933,7 @@ msgstr "指揮"
#: picard/util/tags.py:53
msgid "Copyright"
-msgstr "著作權"
+msgstr "版權"
#: picard/util/tags.py:55
msgid "Video Director"
diff --git a/resources/img-src/picard-logo-haiku.iom b/resources/img-src/picard-logo-haiku.iom
new file mode 100644
index 000000000..df27930e5
Binary files /dev/null and b/resources/img-src/picard-logo-haiku.iom differ
diff --git a/test/data/eac-datatrack.log b/test/data/eac-datatrack.log
new file mode 100644
index 000000000..8e19e657f
Binary files /dev/null and b/test/data/eac-datatrack.log differ
diff --git a/test/test_browser.py b/test/test_browser.py
index ad6808a94..990c7cbda 100644
--- a/test/test_browser.py
+++ b/test/test_browser.py
@@ -196,6 +196,7 @@ class BrowserLookupTest(PicardTestCase):
@patch.object(webbrowser2, 'open')
def test_search_entity(self, mock_open):
+ self.set_config_values({'query_limit': 25})
result = self.lookup.search_entity('foo', 'search:123')
self.assertTrue(result)
url = mock_open.call_args[0][0]
@@ -209,6 +210,7 @@ class BrowserLookupTest(PicardTestCase):
@patch.object(webbrowser2, 'open')
def test_search_entity_advanced(self, mock_open):
+ self.set_config_values({'query_limit': 25})
result = self.lookup.search_entity('foo', 'search:123', adv=True)
self.assertTrue(result)
url = mock_open.call_args[0][0]
@@ -232,6 +234,7 @@ class BrowserLookupTest(PicardTestCase):
@patch.object(webbrowser2, 'open')
def test_search_entity_mbid_lookup_force_browser(self, mock_open):
+ self.set_config_values({'query_limit': 25})
with patch.object(self.lookup, 'mbid_lookup') as mock_lookup:
entity = 'artist'
mbid = '4836aa50-a9ae-490a-983b-cfc8efca92de'
diff --git a/test/test_disc_eaclog.py b/test/test_disc_eaclog.py
index 3320ccd35..88eba0e5e 100644
--- a/test/test_disc_eaclog.py
+++ b/test/test_disc_eaclog.py
@@ -31,7 +31,10 @@ from picard.disc.eaclog import (
filter_toc_entries,
toc_from_file,
)
-from picard.disc.utils import NotSupportedTOCError
+from picard.disc.utils import (
+ NotSupportedTOCError,
+ TocEntry,
+)
test_log = (
@@ -46,25 +49,9 @@ test_log = (
)
test_entries = [
- {
- 'num': '1',
- 'start_time': '0:00.00',
- 'length_time': '5:32.14',
- 'start_sector': '0',
- 'end_sector': '24913'
- }, {
- 'num': '2',
- 'start_time': '5:32.14',
- 'length_time': '4:07.22',
- 'start_sector': '24914',
- 'end_sector': '43460'
- }, {
- 'num': '3',
- 'start_time': '9:39.36',
- 'length_time': '3:50.29',
- 'start_sector': '43461',
- 'end_sector': '60739'
- }
+ TocEntry(1, 0, 24913),
+ TocEntry(2, 24914, 43460),
+ TocEntry(3, 43461, 60739),
]
@@ -93,6 +80,11 @@ class TestTocFromFile(PicardTestCase):
def test_toc_from_file_xld(self):
self._test_toc_from_file('xld.log')
+ def test_toc_from_file_with_datatrack(self):
+ test_log = get_test_data_path('eac-datatrack.log')
+ toc = toc_from_file(test_log)
+ self.assertEqual((1, 8, 178288, 150, 20575, 42320, 62106, 78432, 94973, 109750, 130111), toc)
+
def test_toc_from_empty_file(self):
test_log = get_test_data_path('eac-empty.log')
with self.assertRaises(NotSupportedTOCError):
diff --git a/test/test_disc_utils.py b/test/test_disc_utils.py
index 23a71f88e..5641ea88a 100644
--- a/test/test_disc_utils.py
+++ b/test/test_disc_utils.py
@@ -18,35 +18,19 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
from test.picardtestcase import PicardTestCase
from picard.disc.utils import (
NotSupportedTOCError,
+ TocEntry,
calculate_mb_toc_numbers,
)
test_entries = [
- {
- 'num': '1',
- 'start_time': '0:00.00',
- 'length_time': '5:32.14',
- 'start_sector': '0',
- 'end_sector': '24913'
- }, {
- 'num': '2',
- 'start_time': '5:32.14',
- 'length_time': '4:07.22',
- 'start_sector': '24914',
- 'end_sector': '43460'
- }, {
- 'num': '3',
- 'start_time': '9:39.36',
- 'length_time': '3:50.29',
- 'start_sector': '43461',
- 'end_sector': '60739'
- }
+ TocEntry(1, 0, 24913),
+ TocEntry(2, 24914, 43460),
+ TocEntry(3, 43461, 60739),
]
@@ -56,10 +40,14 @@ class TestCalculateMbTocNumbers(PicardTestCase):
self.assertEqual((1, 3, 60890, 150, 25064, 43611), calculate_mb_toc_numbers(test_entries))
def test_calculate_mb_toc_numbers_invalid_track_numbers(self):
- entries = [{'num': '1'}, {'num': '3'}, {'num': '4'}]
- with self.assertRaises(NotSupportedTOCError):
+ entries = [TocEntry(1, 0, 100), TocEntry(3, 101, 200), TocEntry(4, 201, 300)]
+ with self.assertRaisesRegex(NotSupportedTOCError, r"^Non-standard track number sequence: \(1, 3, 4\)$"):
calculate_mb_toc_numbers(entries)
def test_calculate_mb_toc_numbers_empty_entries(self):
- with self.assertRaises(NotSupportedTOCError):
+ with self.assertRaisesRegex(NotSupportedTOCError, r"^Empty track list$"):
calculate_mb_toc_numbers([])
+
+ def test_calculate_mb_toc_numbers_ignore_datatrack(self):
+ entries = [*test_entries, TocEntry(4, 72140, 80000)]
+ self.assertEqual((1, 3, 60890, 150, 25064, 43611), calculate_mb_toc_numbers(entries))
diff --git a/test/test_ui_options_plugins.py b/test/test_ui_options_plugins.py
new file mode 100644
index 000000000..42d5e3a2a
--- /dev/null
+++ b/test/test_ui_options_plugins.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#
+# Picard, the next-generation MusicBrainz tagger
+#
+# Copyright (C) 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
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from test.picardtestcase import PicardTestCase
+
+from picard.ui.options.plugins import PluginsOptionsPage
+
+
+class PluginsOptionsPageTest(PicardTestCase):
+
+ def test_link_authors(self):
+ self.assertEqual(
+ 'Wile E. Coyote, Road <Runner>',
+ PluginsOptionsPage.link_authors('Wile E. Coyote , Road '),
+ )
diff --git a/ui/options_advanced.ui b/ui/options_advanced.ui
index ff7bc50c7..638a1eb0e 100644
--- a/ui/options_advanced.ui
+++ b/ui/options_advanced.ui
@@ -7,7 +7,7 @@
0
0
570
- 435
+ 455
@@ -20,13 +20,79 @@
2
- -
-
+
-
+
+
+
+ 0
+ 0
+
+
- Include sub-folders when adding files from folder
+ Ignore file paths matching the following regular expression:
+
+
+ true
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Maximum number of entities to return per MusicBrainz query
+
+
+
+ -
+
+
+ 50
+
+
+ 1
+
+
-
+
+ 25
+
+
+ -
+
+ 50
+
+
+ -
+
+ 75
+
+
+ -
+
+ 100
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
-
@@ -78,26 +144,10 @@
- -
-
-
-
- 0
- 0
-
-
+
-
+
- Ignore file paths matching the following regular expression:
-
-
- true
-
-
-
- -
-
-
-
+ Include sub-folders when adding files from folder
@@ -108,9 +158,6 @@
- -
-
-
@@ -188,6 +235,7 @@
ignore_hidden_files
recursively_add_files
ignore_track_duration_difference_under
+ query_limit
completeness_ignore_videos
completeness_ignore_pregap
completeness_ignore_data