mirror of
https://github.com/fergalmoran/picard.git
synced 2026-01-06 16:44:06 +00:00
Merge pull request #1798 from phw/PICARD-2171-optional-guess-title
PICARD-2171: Optional guess title and tracknumber
This commit is contained in:
@@ -79,7 +79,7 @@ from picard.util import (
|
||||
is_absolute_path,
|
||||
samefile,
|
||||
thread,
|
||||
tracknum_from_filename,
|
||||
tracknum_and_title_from_filename,
|
||||
)
|
||||
from picard.util.filenaming import (
|
||||
make_short_filename,
|
||||
@@ -198,6 +198,7 @@ class File(QtCore.QObject, Item):
|
||||
def _loading_finished(self, callback, result=None, error=None):
|
||||
if self.state != File.PENDING or self.tagger.stopping:
|
||||
return
|
||||
config = get_config()
|
||||
if error is not None:
|
||||
self.state = self.ERROR
|
||||
self.error_append(str(error))
|
||||
@@ -228,9 +229,11 @@ class File(QtCore.QObject, Item):
|
||||
else:
|
||||
self.clear_errors()
|
||||
self.state = self.NORMAL
|
||||
self._copy_loaded_metadata(result)
|
||||
postprocessors = []
|
||||
if config.setting["guess_tracknumber_and_title"]:
|
||||
postprocessors.append(self._guess_tracknumber_and_title)
|
||||
self._copy_loaded_metadata(result, postprocessors)
|
||||
# use cached fingerprint from file metadata
|
||||
config = get_config()
|
||||
if not config.setting["ignore_existing_acoustid_fingerprints"]:
|
||||
fingerprints = self.metadata.getall('acoustid_fingerprint')
|
||||
if fingerprints:
|
||||
@@ -239,24 +242,22 @@ class File(QtCore.QObject, Item):
|
||||
self.update()
|
||||
callback(self)
|
||||
|
||||
def _copy_loaded_metadata(self, metadata):
|
||||
filename, _ = os.path.splitext(self.base_filename)
|
||||
def _copy_loaded_metadata(self, metadata, postprocessors=None):
|
||||
metadata['~length'] = format_time(metadata.length)
|
||||
if 'tracknumber' not in metadata:
|
||||
tracknumber = tracknum_from_filename(self.base_filename)
|
||||
if tracknumber is not None:
|
||||
tracknumber = str(tracknumber)
|
||||
metadata['tracknumber'] = tracknumber
|
||||
if 'title' not in metadata:
|
||||
stripped_filename = filename.lstrip('0')
|
||||
tnlen = len(tracknumber)
|
||||
if stripped_filename[:tnlen] == tracknumber:
|
||||
metadata['title'] = stripped_filename[tnlen:].lstrip()
|
||||
if 'title' not in metadata:
|
||||
metadata['title'] = filename
|
||||
if postprocessors:
|
||||
for processor in postprocessors:
|
||||
processor(metadata)
|
||||
self.orig_metadata = metadata
|
||||
self.metadata.copy(metadata)
|
||||
|
||||
def _guess_tracknumber_and_title(self, metadata):
|
||||
if 'tracknumber' not in metadata or 'title' not in metadata:
|
||||
tracknumber, title = tracknum_and_title_from_filename(self.base_filename)
|
||||
if 'tracknumber' not in metadata:
|
||||
metadata['tracknumber'] = tracknumber
|
||||
if 'title' not in metadata:
|
||||
metadata['title'] = title
|
||||
|
||||
def copy_metadata(self, metadata, preserve_deleted=True):
|
||||
acoustid = self.metadata["acoustid_id"]
|
||||
saved_metadata = {}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Picard, the next-generation MusicBrainz tagger
|
||||
#
|
||||
# Copyright (C) 2006-2008, 2011 Lukáš Lalinský
|
||||
# Copyright (C) 2008-2009, 2018-2019 Philipp Wolfer
|
||||
# Copyright (C) 2008-2009, 2018-2019, 2021 Philipp Wolfer
|
||||
# Copyright (C) 2011 Johannes Weißl
|
||||
# Copyright (C) 2011-2013 Michael Wiencek
|
||||
# Copyright (C) 2013, 2018 Laurent Monin
|
||||
@@ -75,6 +75,7 @@ class MetadataOptionsPage(OptionsPage):
|
||||
BoolOption("setting", "convert_punctuation", True),
|
||||
BoolOption("setting", "standardize_artists", False),
|
||||
BoolOption("setting", "standardize_instruments", True),
|
||||
BoolOption("setting", "guess_tracknumber_and_title", True),
|
||||
]
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -103,6 +104,7 @@ class MetadataOptionsPage(OptionsPage):
|
||||
self.ui.nat_name.setText(config.setting["nat_name"])
|
||||
self.ui.standardize_artists.setChecked(config.setting["standardize_artists"])
|
||||
self.ui.standardize_instruments.setChecked(config.setting["standardize_instruments"])
|
||||
self.ui.guess_tracknumber_and_title.setChecked(config.setting["guess_tracknumber_and_title"])
|
||||
|
||||
def save(self):
|
||||
config = get_config()
|
||||
@@ -119,6 +121,7 @@ class MetadataOptionsPage(OptionsPage):
|
||||
self.tagger.nats.update()
|
||||
config.setting["standardize_artists"] = self.ui.standardize_artists.isChecked()
|
||||
config.setting["standardize_instruments"] = self.ui.standardize_instruments.isChecked()
|
||||
config.setting["guess_tracknumber_and_title"] = self.ui.guess_tracknumber_and_title.isChecked()
|
||||
|
||||
def set_va_name_default(self):
|
||||
self.ui.va_name.setText(self.options[0].default)
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
# Automatically generated - don't edit.
|
||||
# Use `python setup.py build_ui` to update it.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MetadataOptionsPage(object):
|
||||
def setupUi(self, MetadataOptionsPage):
|
||||
MetadataOptionsPage.setObjectName("MetadataOptionsPage")
|
||||
@@ -43,6 +45,9 @@ class Ui_MetadataOptionsPage(object):
|
||||
self.track_ars = QtWidgets.QCheckBox(self.metadata_groupbox)
|
||||
self.track_ars.setObjectName("track_ars")
|
||||
self.verticalLayout_3.addWidget(self.track_ars)
|
||||
self.guess_tracknumber_and_title = QtWidgets.QCheckBox(self.metadata_groupbox)
|
||||
self.guess_tracknumber_and_title.setObjectName("guess_tracknumber_and_title")
|
||||
self.verticalLayout_3.addWidget(self.guess_tracknumber_and_title)
|
||||
self.verticalLayout.addWidget(self.metadata_groupbox)
|
||||
self.custom_fields_groupbox = QtWidgets.QGroupBox(MetadataOptionsPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
|
||||
@@ -100,9 +105,9 @@ class Ui_MetadataOptionsPage(object):
|
||||
self.convert_punctuation.setText(_("Convert Unicode punctuation characters to ASCII"))
|
||||
self.release_ars.setText(_("Use release relationships"))
|
||||
self.track_ars.setText(_("Use track relationships"))
|
||||
self.guess_tracknumber_and_title.setText(_("Guess track number and title from filename if empty"))
|
||||
self.custom_fields_groupbox.setTitle(_("Custom Fields"))
|
||||
self.label_6.setText(_("Various artists:"))
|
||||
self.label_7.setText(_("Non-album tracks:"))
|
||||
self.nat_name_default.setText(_("Default"))
|
||||
self.va_name_default.setText(_("Default"))
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Copyright (C) 2004 Robert Kaye
|
||||
# Copyright (C) 2006-2009, 2011-2012, 2014 Lukáš Lalinský
|
||||
# Copyright (C) 2008-2011, 2014, 2018-2020 Philipp Wolfer
|
||||
# Copyright (C) 2008-2011, 2014, 2018-2021 Philipp Wolfer
|
||||
# Copyright (C) 2009 Carlin Mangar
|
||||
# Copyright (C) 2009 david
|
||||
# Copyright (C) 2010 fatih
|
||||
@@ -392,7 +392,7 @@ def tracknum_from_filename(base_filename):
|
||||
"""Guess and extract track number from filename
|
||||
Returns `None` if none found, the number as integer else
|
||||
"""
|
||||
filename, _ = os.path.splitext(base_filename)
|
||||
filename, _ext = os.path.splitext(base_filename)
|
||||
for r in _tracknum_regexps:
|
||||
match = re.search(r, filename, re.I)
|
||||
if match:
|
||||
@@ -409,6 +409,26 @@ def tracknum_from_filename(base_filename):
|
||||
return None
|
||||
|
||||
|
||||
def tracknum_and_title_from_filename(base_filename):
|
||||
"""Guess tracknumber and title from filename.
|
||||
Uses `tracknum_from_filename` to guess the tracknumber. The filename is used
|
||||
as the title. If the tracknumber is at the beginning of the title it gets stripped.
|
||||
|
||||
Returns a tuple `(tracknumber, title)`.
|
||||
"""
|
||||
filename, _ext = os.path.splitext(base_filename)
|
||||
title = filename
|
||||
tracknumber = tracknum_from_filename(base_filename)
|
||||
if tracknumber is not None:
|
||||
tracknumber = str(tracknumber)
|
||||
stripped_filename = filename.lstrip('0')
|
||||
tnlen = len(tracknumber)
|
||||
if stripped_filename[:tnlen] == tracknumber:
|
||||
title = stripped_filename[tnlen:].lstrip()
|
||||
|
||||
return (tracknumber, title)
|
||||
|
||||
|
||||
def is_hidden(filepath):
|
||||
"""Test whether a file or directory is hidden.
|
||||
A file is considered hidden if it starts with a dot
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Copyright (C) 2006-2007 Lukáš Lalinský
|
||||
# Copyright (C) 2010 fatih
|
||||
# Copyright (C) 2010-2011, 2014, 2018-2020 Philipp Wolfer
|
||||
# Copyright (C) 2010-2011, 2014, 2018-2021 Philipp Wolfer
|
||||
# Copyright (C) 2012, 2014, 2018 Wieland Hoffmann
|
||||
# Copyright (C) 2013 Ionuț Ciocîrlan
|
||||
# Copyright (C) 2013-2014, 2018-2020 Laurent Monin
|
||||
@@ -46,6 +46,8 @@ from picard.util import (
|
||||
iter_unique,
|
||||
limited_join,
|
||||
sort_by_similarity,
|
||||
tracknum_and_title_from_filename,
|
||||
tracknum_from_filename,
|
||||
uniqify,
|
||||
)
|
||||
|
||||
@@ -429,3 +431,43 @@ class IterUniqueTest(PicardTestCase):
|
||||
result = iter_unique(items)
|
||||
self.assertTrue(isinstance(result, Iterator))
|
||||
self.assertEqual([1, 2, 3, 4], list(result))
|
||||
|
||||
|
||||
class TracknumFromFilenameTest(PicardTestCase):
|
||||
|
||||
def test_returns_expected_tracknumber(self):
|
||||
tests = (
|
||||
(None, 'Foo.mp3'),
|
||||
(1, 'Foo 0001.mp3'),
|
||||
(99, '99 Foo.mp3'),
|
||||
(42, '42. Foo.mp3'),
|
||||
(None, '20000 Feet.mp3'),
|
||||
(242, 'track no 242.mp3'),
|
||||
(242, 'track-242.mp3'),
|
||||
(242, 'track nr 242.mp3'),
|
||||
(242, 'track_242.mp3'),
|
||||
# (None, '30,000 Pounds of Bananas.mp3'),
|
||||
# (None, 'Dalas 1 PM.mp3'),
|
||||
# (None, "Don't Stop the 80's.mp3"),
|
||||
# (None, '99 Luftballons.mp3'),
|
||||
# (None, 'Symphony no. 5 in D minor.mp3'),
|
||||
)
|
||||
for expected, filename in tests:
|
||||
tracknumber = tracknum_from_filename(filename)
|
||||
self.assertEqual(expected, tracknumber)
|
||||
|
||||
|
||||
class TracknumAndTitleFromFilenameTest(PicardTestCase):
|
||||
|
||||
def test_returns_expected_tracknumber(self):
|
||||
tests = (
|
||||
((None, 'Foo'), 'Foo.mp3'),
|
||||
(('1', 'Track 0001'), 'Track 0001.mp3'),
|
||||
(('99', 'Foo'), '99 Foo.mp3'),
|
||||
(('42', 'Foo'), '0000042 Foo.mp3'),
|
||||
((None, '20000 Feet'), '20000 Feet.mp3'),
|
||||
# ((None, '20,000 Feet'), '20,000 Feet.mp3'),
|
||||
)
|
||||
for expected, filename in tests:
|
||||
result = tracknum_and_title_from_filename(filename)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@@ -77,6 +77,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="guess_tracknumber_and_title">
|
||||
<property name="text">
|
||||
<string>Guess track number and title from filename if empty</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Reference in New Issue
Block a user