Merge branch 'metabrainz:master' into single-instance-gsoc

This commit is contained in:
Kamil
2022-06-26 16:17:30 +02:00
committed by GitHub
35 changed files with 353 additions and 202 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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/

View File

@@ -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

View File

@@ -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)

10
NEWS.md
View File

@@ -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

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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."""

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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"]),

View File

@@ -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()

View File

@@ -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 + "<hr width='90%'/>")
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("<b>{0}:</b> {1}".format(label, value))
self.ui.details.setText("<p>{0}</p>".format("<br/>\n".join(text)))
@staticmethod
def link_authors(authors):
formatted_authors = []
re_author = re.compile(r"(?P<author>.*?)\s*<(?P<email>.*?@.*?)>")
for author in authors.split(','):
author = author.strip()
match = re_author.fullmatch(author)
if match:
author_str = '<a href="mailto:{email}">{author}</a>'.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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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"))

View File

@@ -5,7 +5,7 @@
#
# Translators:
# Lukáš Lalinský <lalinsky@gmail.com>, 2020
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 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 <mfmeulenbelt@gmail.com>, 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"

View File

@@ -2,8 +2,8 @@
# Translators:
# reneweesp <bmom43@hotmail.com>, 2017
# Maurits Meulenbelt <email address hidden>, 2012
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 2012
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 2013-2022
# Maurits Meulenbelt, 2012
# Maurits Meulenbelt, 2013-2022
# Nikolai Prokoschenko <email address hidden>, 2011
# Nikolai Prokoschenko <nikolai@prokoschenko.de>, 2011
# Niko Strijbol <strijbol.niko@gmail.com>, 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 <mfmeulenbelt@gmail.com>, 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 cds als onderdeel van een boxset."
#: DB:work_type/description:3
msgctxt "work_type"

View File

@@ -1,13 +1,13 @@
#
# Translators:
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 2012
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 2014-2015
# Maurits Meulenbelt, 2012
# Maurits Meulenbelt, 2014-2015
# Nikolai Prokoschenko <nikolai@prokoschenko.de>, 2011
msgid ""
msgstr ""
"Project-Id-Version: MusicBrainz\n"
"PO-Revision-Date: 2012-05-24 19:20+0000\n"
"Last-Translator: Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 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"

View File

@@ -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

View File

@@ -6,8 +6,8 @@
# reneweesp <bmom43@hotmail.com>, 2017-2019
# Dogmatica <jeroendoggen@hotmail.com>, 2006
# Jan van Thiel <zout@gewis.nl>, 2006
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 2012
# Maurits Meulenbelt <mfmeulenbelt@gmail.com>, 2012-2022
# Maurits Meulenbelt, 2012
# Maurits Meulenbelt, 2012-2022
# Philipp Wolfer <ph.wolfer@gmail.com>, 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 <mfmeulenbelt@gmail.com>, 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"

View File

@@ -7,7 +7,7 @@
# Philipp Wolfer <ph.wolfer@gmail.com>, 2019-2022
# S B <koalaleaves@protonmail.com>, 2022
# Shen-Ta Hsieh(BestSteve) <ibmibmibm.tw@gmail.com>, 2016
# Shen-Ta Hsieh(BestSteve) <ibmibmibm.tw@gmail.com>, 2016-2020
# Shen-Ta Hsieh(BestSteve) <ibmibmibm.tw@gmail.com>, 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 <ph.wolfer@gmail.com>, 2019-2022\n"
"Last-Translator: Shen-Ta Hsieh(BestSteve) <ibmibmibm.tw@gmail.com>, 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"

Binary file not shown.

BIN
test/data/eac-datatrack.log Normal file

Binary file not shown.

View File

@@ -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'

View File

@@ -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):

View File

@@ -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))

View File

@@ -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(
'<a href="mailto:coyote@acme.com">Wile E. Coyote</a>, Road &lt;Runner&gt;',
PluginsOptionsPage.link_authors('Wile E. Coyote <coyote@acme.com>, Road <Runner>'),
)

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>570</width>
<height>435</height>
<height>455</height>
</rect>
</property>
<layout class="QVBoxLayout">
@@ -20,13 +20,79 @@
<property name="spacing">
<number>2</number>
</property>
<item row="5" column="0">
<widget class="QCheckBox" name="recursively_add_files">
<item row="1" column="0">
<widget class="QLabel" name="label_ignore_regex">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Include sub-folders when adding files from folder</string>
<string>Ignore file paths matching the following regular expression:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_query_limit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Maximum number of entities to return per MusicBrainz query</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="query_limit">
<property name="currentText">
<string notr="true">50</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string notr="true">25</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">50</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">75</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">100</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="regex_error">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="ignore_regex"/>
</item>
<item row="6" column="0" rowspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
@@ -78,26 +144,10 @@
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_ignore_regex">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item row="5" column="0">
<widget class="QCheckBox" name="recursively_add_files">
<property name="text">
<string>Ignore file paths matching the following regular expression:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="regex_error">
<property name="text">
<string/>
<string>Include sub-folders when adding files from folder</string>
</property>
</widget>
</item>
@@ -108,9 +158,6 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="ignore_regex"/>
</item>
</layout>
</widget>
</item>
@@ -188,6 +235,7 @@
<tabstop>ignore_hidden_files</tabstop>
<tabstop>recursively_add_files</tabstop>
<tabstop>ignore_track_duration_difference_under</tabstop>
<tabstop>query_limit</tabstop>
<tabstop>completeness_ignore_videos</tabstop>
<tabstop>completeness_ignore_pregap</tabstop>
<tabstop>completeness_ignore_data</tabstop>