Merge pull request #1431 from phw/PICARD-34-acoustid-fingerprint-indicator
PICARD-34: AcoustID fingerprint indicator
@@ -62,18 +62,24 @@ class AcoustIDManager(QtCore.QObject):
|
||||
del self._fingerprints[file]
|
||||
self._check_unsubmitted()
|
||||
|
||||
def is_submitted(self, file):
|
||||
submission = self._fingerprints.get(file)
|
||||
if submission:
|
||||
return not submission.recordingid or submission.orig_recordingid == submission.recordingid
|
||||
return True
|
||||
|
||||
def _unsubmitted(self):
|
||||
for submission in self._fingerprints.values():
|
||||
for file, submission in self._fingerprints.items():
|
||||
if submission.recordingid and submission.orig_recordingid != submission.recordingid:
|
||||
yield submission
|
||||
yield (file, submission)
|
||||
|
||||
def _check_unsubmitted(self):
|
||||
enabled = next(self._unsubmitted(), None) is not None
|
||||
self.tagger.window.enable_submit(enabled)
|
||||
|
||||
def submit(self):
|
||||
fingerprints = list(self._unsubmitted())
|
||||
if not fingerprints:
|
||||
submissions = list(self._unsubmitted())
|
||||
if not submissions:
|
||||
self._check_unsubmitted()
|
||||
return
|
||||
log.debug("AcoustID: submitting ...")
|
||||
@@ -81,10 +87,11 @@ class AcoustIDManager(QtCore.QObject):
|
||||
N_('Submitting AcoustIDs ...'),
|
||||
echo=None
|
||||
)
|
||||
fingerprints = [fingerprint for _, fingerprint in submissions]
|
||||
self.tagger.acoustid_api.submit_acoustid_fingerprints(fingerprints,
|
||||
partial(self.__fingerprint_submission_finished, fingerprints))
|
||||
partial(self.__fingerprint_submission_finished, submissions))
|
||||
|
||||
def __fingerprint_submission_finished(self, fingerprints, document, http, error):
|
||||
def __fingerprint_submission_finished(self, submissions, document, http, error):
|
||||
if error:
|
||||
try:
|
||||
error = load_json(document)
|
||||
@@ -111,6 +118,7 @@ class AcoustIDManager(QtCore.QObject):
|
||||
echo=None,
|
||||
timeout=3000
|
||||
)
|
||||
for submission in fingerprints:
|
||||
for file, submission in submissions:
|
||||
submission.orig_recordingid = submission.recordingid
|
||||
file.update()
|
||||
self._check_unsubmitted()
|
||||
|
||||
@@ -531,6 +531,7 @@ class File(QtCore.QObject, Item):
|
||||
if not recording_id:
|
||||
recording_id = self.metadata['musicbrainz_recordingid']
|
||||
self.tagger.acoustidmanager.update(self, recording_id)
|
||||
self.update_item()
|
||||
|
||||
@classmethod
|
||||
def supports_tag(cls, name):
|
||||
|
||||
23823
picard/resources.py
@@ -137,6 +137,7 @@ class Track(DataObject, Item):
|
||||
|
||||
def add_file(self, file):
|
||||
if file not in self.linked_files:
|
||||
track_will_expand = self.num_linked_files == 1
|
||||
self.linked_files.append(file)
|
||||
self.num_linked_files += 1
|
||||
self.update_file_metadata(file)
|
||||
@@ -144,6 +145,9 @@ class Track(DataObject, Item):
|
||||
self.album._add_file(self, file)
|
||||
file.metadata_images_changed.connect(self.update_orig_metadata_images)
|
||||
run_file_post_addition_to_track_processors(self, file)
|
||||
if track_will_expand:
|
||||
# Files get expanded, ensure the existing item renders correctly
|
||||
self.linked_files[0].update_item()
|
||||
|
||||
def update_file_metadata(self, file):
|
||||
if file not in self.linked_files:
|
||||
|
||||
@@ -65,6 +65,9 @@ from picard.ui.scriptsmenu import ScriptsMenu
|
||||
from picard.ui.widgets.tristatesortheaderview import TristateSortHeaderView
|
||||
|
||||
|
||||
COLUMN_ICON_SIZE = 16
|
||||
|
||||
|
||||
class BaseAction(QtWidgets.QAction):
|
||||
NAME = "Unknown"
|
||||
MENU = []
|
||||
@@ -137,8 +140,12 @@ class MainPanel(QtWidgets.QSplitter):
|
||||
(N_('Barcode'), 'barcode'),
|
||||
(N_('Media'), 'media'),
|
||||
(N_('Genre'), 'genre'),
|
||||
(N_('Fingerprint status'), '~fingerprint'),
|
||||
]
|
||||
|
||||
TITLE_COLUMN = 0
|
||||
FINGERPRINT_COLUMN = 13
|
||||
|
||||
def __init__(self, window, parent=None):
|
||||
super().__init__(parent)
|
||||
self.window = window
|
||||
@@ -195,6 +202,8 @@ class MainPanel(QtWidgets.QSplitter):
|
||||
FileItem.icon_file_pending = QtGui.QIcon(":/images/file-pending.png")
|
||||
FileItem.icon_error = icontheme.lookup('dialog-error', icontheme.ICON_SIZE_MENU)
|
||||
FileItem.icon_saved = QtGui.QIcon(":/images/track-saved.png")
|
||||
FileItem.icon_fingerprint = icontheme.lookup('fingerprint', icontheme.ICON_SIZE_MENU)
|
||||
FileItem.icon_fingerprint_gray = icontheme.lookup('fingerprint-gray', icontheme.ICON_SIZE_MENU)
|
||||
FileItem.match_icons = [
|
||||
QtGui.QIcon(":/images/match-50.png"),
|
||||
QtGui.QIcon(":/images/match-60.png"),
|
||||
@@ -256,6 +265,35 @@ class MainPanel(QtWidgets.QSplitter):
|
||||
self.update_current_view()
|
||||
|
||||
|
||||
def paint_fingerprint_icon(painter, rect, icon):
|
||||
if not icon:
|
||||
return
|
||||
size = COLUMN_ICON_SIZE
|
||||
padding_h = (rect.width() - size) / 2
|
||||
padding_v = (rect.height() - size) / 2
|
||||
target_rect = QtCore.QRect(rect.x() + padding_h, rect.y() + padding_v, size, size)
|
||||
painter.drawPixmap(target_rect, icon.pixmap(size, size))
|
||||
|
||||
|
||||
class FingerprintColumnWidget(QtWidgets.QWidget):
|
||||
def __init__(self, file, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._file = file
|
||||
|
||||
def paintEvent(self, event=None):
|
||||
painter = QtGui.QPainter(self)
|
||||
paint_fingerprint_icon(painter, event.rect(), self.decide_icon())
|
||||
|
||||
def decide_icon(self):
|
||||
if getattr(self._file, 'acoustid_fingerprint', None):
|
||||
if self.tagger.acoustidmanager.is_submitted(self._file):
|
||||
return FileItem.icon_fingerprint_gray
|
||||
else:
|
||||
return FileItem.icon_fingerprint
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class ConfigurableColumnsHeader(TristateSortHeaderView):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -268,6 +306,7 @@ class ConfigurableColumnsHeader(TristateSortHeaderView):
|
||||
self.setStretchLastSection(True)
|
||||
self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.setSectionsClickable(False)
|
||||
self.sortIndicatorChanged.connect(self.on_sort_indicator_changed)
|
||||
|
||||
# enable sorting, but don't actually use it by default
|
||||
# XXX it would be nice to be able to go to the 'no sort' mode, but the
|
||||
@@ -283,6 +322,11 @@ class ConfigurableColumnsHeader(TristateSortHeaderView):
|
||||
if self.sectionSize(column) == 0:
|
||||
self.resizeSection(column, self.defaultSectionSize())
|
||||
self._visible_columns.add(column)
|
||||
if column == MainPanel.FINGERPRINT_COLUMN:
|
||||
self.setSectionResizeMode(column, QtWidgets.QHeaderView.Fixed)
|
||||
self.parent().resizeColumnToContents(column)
|
||||
else:
|
||||
self.setSectionResizeMode(column, QtWidgets.QHeaderView.Interactive)
|
||||
elif column in self._visible_columns:
|
||||
self._visible_columns.remove(column)
|
||||
|
||||
@@ -317,6 +361,19 @@ class ConfigurableColumnsHeader(TristateSortHeaderView):
|
||||
def restore_defaults(self):
|
||||
self.parent().restore_default_columns()
|
||||
|
||||
def paintSection(self, painter, rect, index):
|
||||
if index == MainPanel.FINGERPRINT_COLUMN:
|
||||
painter.save()
|
||||
super().paintSection(painter, rect, index)
|
||||
painter.restore()
|
||||
paint_fingerprint_icon(painter, rect, FileItem.icon_fingerprint_gray)
|
||||
else:
|
||||
super().paintSection(painter, rect, index)
|
||||
|
||||
def on_sort_indicator_changed(self, index, order):
|
||||
if index == MainPanel.FINGERPRINT_COLUMN:
|
||||
self.setSortIndicator(-1, QtCore.Qt.AscendingOrder)
|
||||
|
||||
|
||||
class BaseTreeView(QtWidgets.QTreeWidget):
|
||||
|
||||
@@ -326,7 +383,8 @@ class BaseTreeView(QtWidgets.QTreeWidget):
|
||||
self.window = window
|
||||
self.panel = parent
|
||||
|
||||
self.setHeaderLabels([_(h) for h, n in MainPanel.columns])
|
||||
self.setHeaderLabels([_(h) if n != '~fingerprint' else ''
|
||||
for h, n in MainPanel.columns])
|
||||
self.restore_state()
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
@@ -716,7 +774,7 @@ class FileTreeView(BaseTreeView):
|
||||
self.set_clusters_text()
|
||||
|
||||
def set_clusters_text(self):
|
||||
self.clusters.setText(0, '%s (%d)' % (_("Clusters"), len(self.tagger.clusters)))
|
||||
self.clusters.setText(MainPanel.TITLE_COLUMN, '%s (%d)' % (_("Clusters"), len(self.tagger.clusters)))
|
||||
|
||||
|
||||
class AlbumTreeView(BaseTreeView):
|
||||
@@ -736,7 +794,7 @@ class AlbumTreeView(BaseTreeView):
|
||||
self.insertTopLevelItem(0, item)
|
||||
else:
|
||||
item = AlbumItem(album, True, self)
|
||||
item.setIcon(0, AlbumItem.icon_cd)
|
||||
item.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd)
|
||||
for i, column in enumerate(MainPanel.columns):
|
||||
font = item.font(i)
|
||||
font.setBold(True)
|
||||
@@ -777,7 +835,7 @@ class ClusterItem(TreeItem):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.setIcon(0, ClusterItem.icon_dir)
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, ClusterItem.icon_dir)
|
||||
|
||||
def update(self):
|
||||
for i, column in enumerate(MainPanel.columns):
|
||||
@@ -843,22 +901,22 @@ class AlbumItem(TreeItem):
|
||||
for item in items: # Update after insertChildren so that setExpanded works
|
||||
item.update(update_album=False)
|
||||
if album.errors:
|
||||
self.setIcon(0, AlbumItem.icon_error)
|
||||
self.setToolTip(0, _("Error"))
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_error)
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, _("Error"))
|
||||
elif album.is_complete():
|
||||
if album.is_modified():
|
||||
self.setIcon(0, AlbumItem.icon_cd_saved_modified)
|
||||
self.setToolTip(0, _("Album modified and complete"))
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd_saved_modified)
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album modified and complete"))
|
||||
else:
|
||||
self.setIcon(0, AlbumItem.icon_cd_saved)
|
||||
self.setToolTip(0, _("Album unchanged and complete"))
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd_saved)
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album unchanged and complete"))
|
||||
else:
|
||||
if album.is_modified():
|
||||
self.setIcon(0, AlbumItem.icon_cd_modified)
|
||||
self.setToolTip(0, _("Album modified"))
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd_modified)
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album modified"))
|
||||
else:
|
||||
self.setIcon(0, AlbumItem.icon_cd)
|
||||
self.setToolTip(0, _("Album unchanged"))
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, AlbumItem.icon_cd)
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, _("Album unchanged"))
|
||||
for i, column in enumerate(MainPanel.columns):
|
||||
self.setText(i, album.column(column[1]))
|
||||
if selection_changed:
|
||||
@@ -885,17 +943,26 @@ class TrackItem(TreeItem):
|
||||
|
||||
def update(self, update_album=True, update_files=True):
|
||||
track = self.obj
|
||||
tree_widget = self.treeWidget()
|
||||
if track.num_linked_files == 1:
|
||||
file = track.linked_files[0]
|
||||
file.item = self
|
||||
color = TrackItem.track_colors[file.state]
|
||||
bgcolor = get_match_color(file.similarity, TreeItem.base_color)
|
||||
icon = FileItem.decide_file_icon(file)
|
||||
self.setToolTip(0, _(FileItem.decide_file_icon_info(file)))
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, _(FileItem.decide_file_icon_info(file)))
|
||||
self.takeChildren()
|
||||
self.setExpanded(False)
|
||||
self.setToolTip(MainPanel.FINGERPRINT_COLUMN, FileItem.decide_fingerprint_icon_info(file))
|
||||
if tree_widget:
|
||||
if not tree_widget.itemWidget(self, MainPanel.FINGERPRINT_COLUMN):
|
||||
tree_widget.setItemWidget(self, MainPanel.FINGERPRINT_COLUMN,
|
||||
FingerprintColumnWidget(file=file))
|
||||
else:
|
||||
self.setToolTip(0, "")
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, "")
|
||||
self.setToolTip(MainPanel.FINGERPRINT_COLUMN, "")
|
||||
if tree_widget:
|
||||
tree_widget.setItemWidget(self, MainPanel.FINGERPRINT_COLUMN, None)
|
||||
if track.ignored_for_completeness():
|
||||
color = TreeItem.text_color_secondary
|
||||
else:
|
||||
@@ -929,10 +996,10 @@ class TrackItem(TreeItem):
|
||||
self.addChildren(items)
|
||||
self.setExpanded(True)
|
||||
if track.error:
|
||||
self.setIcon(0, TrackItem.icon_error)
|
||||
self.setToolTip(0, track.error)
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, TrackItem.icon_error)
|
||||
self.setToolTip(MainPanel.TITLE_COLUMN, track.error)
|
||||
else:
|
||||
self.setIcon(0, icon)
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, icon)
|
||||
for i, column in enumerate(MainPanel.columns):
|
||||
self.setText(i, track.column(column[1]))
|
||||
self.setForeground(i, color)
|
||||
@@ -947,13 +1014,19 @@ class FileItem(TreeItem):
|
||||
|
||||
def update(self, update_track=True):
|
||||
file = self.obj
|
||||
self.setIcon(0, FileItem.decide_file_icon(file))
|
||||
self.setIcon(MainPanel.TITLE_COLUMN, FileItem.decide_file_icon(file))
|
||||
self.setToolTip(MainPanel.FINGERPRINT_COLUMN, self.decide_fingerprint_icon_info(file))
|
||||
color = FileItem.file_colors[file.state]
|
||||
bgcolor = get_match_color(file.similarity, TreeItem.base_color)
|
||||
for i, column in enumerate(MainPanel.columns):
|
||||
self.setText(i, file.column(column[1]))
|
||||
self.setForeground(i, color)
|
||||
self.setBackground(i, bgcolor)
|
||||
tree_widget = self.treeWidget()
|
||||
if tree_widget:
|
||||
if not tree_widget.itemWidget(self, MainPanel.FINGERPRINT_COLUMN):
|
||||
tree_widget.setItemWidget(self, MainPanel.FINGERPRINT_COLUMN,
|
||||
FingerprintColumnWidget(file=file))
|
||||
if self.isSelected():
|
||||
TreeItem.window.update_selection()
|
||||
|
||||
@@ -989,3 +1062,13 @@ class FileItem(TreeItem):
|
||||
return FileItem.match_icons_info[int(file.similarity * 5 + 0.5)]
|
||||
elif file.state == File.PENDING:
|
||||
return N_("Pending")
|
||||
|
||||
@staticmethod
|
||||
def decide_fingerprint_icon_info(file):
|
||||
if getattr(file, 'acoustid_fingerprint', None):
|
||||
if QtCore.QObject.tagger.acoustidmanager.is_submitted(file):
|
||||
return _('Fingerprint has already been submitted')
|
||||
else:
|
||||
return _('Unsubmitted fingerprint')
|
||||
else:
|
||||
return _('No fingerprint was calculated for this file, use "Scan" or "Generate AcoustID fingerprints" to calculate the fingerprint.')
|
||||
|
||||
@@ -501,7 +501,7 @@ class MainWindow(QtWidgets.QMainWindow, PreserveGeometry):
|
||||
self.analyze_action.setShortcut(QtGui.QKeySequence(_("Ctrl+Y")))
|
||||
self.analyze_action.triggered.connect(self.analyze)
|
||||
|
||||
self.generate_fingerprints_action = QtWidgets.QAction(icontheme.lookup('picard-fingerprint'), _("&Generate AcoustID fingerprints"), self)
|
||||
self.generate_fingerprints_action = QtWidgets.QAction(icontheme.lookup('fingerprint'), _("&Generate AcoustID fingerprints"), self)
|
||||
self.generate_fingerprints_action.setStatusTip(_("Generate the AcoustID audio fingerprints for the selected files without doing a lookup"))
|
||||
self.generate_fingerprints_action.setEnabled(False)
|
||||
self.generate_fingerprints_action.setToolTip(_('Generate the AcoustID audio fingerprints for the selected files'))
|
||||
|
||||
BIN
resources/images/16x16/fingerprint-gray.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
resources/images/16x16/fingerprint-gray@2x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
BIN
resources/images/16x16/fingerprint@2x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
87
resources/img-src/fingerprint-gray.svg
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 58.999 58.999"
|
||||
style="enable-background:new 0 0 58.999 58.999;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="fingerprint-gray.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
inkscape:export-filename="/home/phw/devel/musicbrainz/picard/resources/images/16x16/fingerprint-gray.png"
|
||||
inkscape:export-xdpi="26.030001"
|
||||
inkscape:export-ydpi="26.030001"><metadata
|
||||
id="metadata55"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs53" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview51"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.0000678"
|
||||
inkscape:cx="-25.99956"
|
||||
inkscape:cy="25.99956"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Capa_1" /><g
|
||||
id="g18"
|
||||
style="fill:#a0a0a0;fill-opacity:1"><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M29.083,26.038c-0.53,0.154-0.836,0.708-0.683,1.239c0.027,0.093,2.697,9.591,1.962,30.687 c-0.02,0.553,0.412,1.016,0.964,1.034c0.012,0.001,0.024,0.001,0.036,0.001c0.536,0,0.979-0.425,0.998-0.965 c0.747-21.414-1.925-30.919-2.038-31.313C30.169,26.19,29.616,25.888,29.083,26.038z"
|
||||
id="path2" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M54.228,16.479C49.992,6.469,40.232,0,29.362,0c-3.587,0-7.085,0.701-10.396,2.083 c-1.834,0.765-3.567,1.675-5.151,2.704c-0.463,0.302-0.595,0.921-0.294,1.384c0.302,0.463,0.922,0.595,1.384,0.294 c1.483-0.964,3.108-1.817,4.831-2.536C22.803,2.648,26.041,2,29.362,2c10.064,0,19.102,5.989,23.023,15.259 c1.63,3.854,2.511,10.95,2.891,14.838c0.051,0.517,0.485,0.902,0.994,0.902c0.032,0,0.065-0.002,0.099-0.005 c0.549-0.054,0.951-0.543,0.897-1.093C56.789,27.015,55.875,20.372,54.228,16.479z"
|
||||
id="path4" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M10.851,9.711c0.394-0.387,0.397-1.021,0.01-1.414c-0.387-0.393-1.02-0.397-1.414-0.01 C4.069,13.585-1.288,23.438,3.73,40.284c0.129,0.435,0.526,0.715,0.958,0.715c0.094,0,0.19-0.014,0.285-0.042 c0.529-0.157,0.831-0.714,0.673-1.243C1.899,27.132,3.698,16.758,10.851,9.711z"
|
||||
id="path6" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M30.13,6.014c-3.06-0.119-6.038,0.432-8.853,1.606C11.471,11.711,6.257,22.494,9.15,32.704 c0.289,1.021,0.585,2.107,0.744,3.189c0.358,2.449,0.377,6.956,0.377,14.105c0,0.553,0.447,1,1,1s1-0.447,1-1 c0-7.458-0.019-11.804-0.398-14.396c-0.176-1.202-0.491-2.36-0.799-3.444c-2.617-9.236,2.1-18.992,10.973-22.693 c2.548-1.063,5.259-1.546,8.012-1.454C37.17,8.267,45.23,14.535,47.661,21.7c2.104,6.199,2.122,16.76,1.861,26.271 c-0.015,0.552,0.421,1.012,0.973,1.027c0.542-0.01,1.012-0.419,1.027-0.973c0.265-9.689,0.238-20.471-1.967-26.969 C46.838,13.049,38.124,6.3,30.13,6.014z"
|
||||
id="path8" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M21.935,16.337c0.452-0.317,0.562-0.941,0.244-1.393c-0.316-0.452-0.941-0.563-1.393-0.244 c-5.15,3.616-7.388,10.563-5.565,17.295c0.021,0.073,2.051,7.462,2.051,19.004c0,0.553,0.447,1,1,1s1-0.447,1-1 c0-11.813-2.038-19.232-2.123-19.535C15.548,25.55,17.472,19.471,21.935,16.337z"
|
||||
id="path10" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M29.362,12c-1.087,0-2.169,0.117-3.218,0.349c-0.539,0.119-0.88,0.652-0.761,1.192 c0.119,0.539,0.646,0.873,1.192,0.761c0.907-0.2,1.845-0.302,2.786-0.302c5.233,0,9.933,3.114,11.972,7.935 c2.43,5.74,2.589,14.953,2.028,31.029c-0.02,0.552,0.412,1.016,0.964,1.034c0.012,0.001,0.024,0.001,0.036,0.001 c0.536,0,0.979-0.425,0.998-0.965c0.584-16.723,0.406-25.758-2.185-31.879C40.823,15.594,35.401,12,29.362,12z"
|
||||
id="path12" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M39.272,48.006c0.004-0.553-0.44-1.003-0.993-1.007c-0.002,0-0.005,0-0.007,0 c-0.549,0-0.996,0.443-1,0.993c-0.005,0.752,0.02,1.85,0.047,3.036c0.039,1.725,0.083,3.68,0.04,4.937 c-0.02,0.552,0.412,1.015,0.965,1.033c0.012,0.001,0.023,0.001,0.035,0.001c0.536,0,0.979-0.425,0.998-0.966 c0.046-1.313,0.001-3.299-0.039-5.051C39.291,49.82,39.267,48.743,39.272,48.006z"
|
||||
id="path14" /><path
|
||||
style="fill:#a0a0a0;fill-opacity:1"
|
||||
d="M29.362,19c-1.062,0-2.098,0.208-3.081,0.618c-3.892,1.624-5.783,6.015-4.4,10.214 c0.754,2.289,1.391,14.272,1.391,26.167c0,0.553,0.447,1,1,1s1-0.447,1-1c0-10.707-0.57-23.995-1.49-26.792 c-1.054-3.197,0.353-6.526,3.27-7.743C27.789,21.156,28.566,21,29.362,21c2.415,0,4.584,1.438,5.529,3.67 c1.867,4.419,2.422,12.419,2.558,18.353c0.013,0.544,0.458,0.977,1,0.977c0.007,0,0.016,0,0.023,0 c0.552-0.013,0.989-0.471,0.977-1.023c-0.141-6.106-0.726-14.375-2.72-19.093C35.475,20.917,32.583,19,29.362,19z"
|
||||
id="path16" /></g><g
|
||||
id="g20" /><g
|
||||
id="g22" /><g
|
||||
id="g24" /><g
|
||||
id="g26" /><g
|
||||
id="g28" /><g
|
||||
id="g30" /><g
|
||||
id="g32" /><g
|
||||
id="g34" /><g
|
||||
id="g36" /><g
|
||||
id="g38" /><g
|
||||
id="g40" /><g
|
||||
id="g42" /><g
|
||||
id="g44" /><g
|
||||
id="g46" /><g
|
||||
id="g48" /></svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -16,7 +16,7 @@
|
||||
viewBox="0 0 58.999 58.999"
|
||||
style="enable-background:new 0 0 58.999 58.999;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="picard-fingerprint.svg"
|
||||
sodipodi:docname="fingerprint.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
|
||||
id="metadata55"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -20,6 +20,10 @@
|
||||
<file>images/16x16/edit-cut@2x.png</file>
|
||||
<file>images/16x16/edit-paste.png</file>
|
||||
<file>images/16x16/edit-paste@2x.png</file>
|
||||
<file>images/16x16/fingerprint-gray.png</file>
|
||||
<file>images/16x16/fingerprint-gray@2x.png</file>
|
||||
<file>images/16x16/fingerprint.png</file>
|
||||
<file>images/16x16/fingerprint@2x.png</file>
|
||||
<file>images/16x16/folder.png</file>
|
||||
<file>images/16x16/folder@2x.png</file>
|
||||
<file>images/16x16/go-down.png</file>
|
||||
@@ -59,8 +63,6 @@
|
||||
<file>images/16x16/picard-cluster@2x.png</file>
|
||||
<file>images/16x16/picard-edit-tags.png</file>
|
||||
<file>images/16x16/picard-edit-tags@2x.png</file>
|
||||
<file>images/16x16/picard-fingerprint.png</file>
|
||||
<file>images/16x16/picard-fingerprint@2x.png</file>
|
||||
<file>images/16x16/play-music.png</file>
|
||||
<file>images/16x16/play-music@2x.png</file>
|
||||
<file>images/16x16/play.png</file>
|
||||
|
||||
53
test/test_acoustidmanager.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from test.picardtestcase import PicardTestCase
|
||||
|
||||
from picard.acoustid.manager import AcoustIDManager
|
||||
from picard.file import File
|
||||
|
||||
|
||||
class AcoustIDManagerTest(PicardTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.acoustidmanager = AcoustIDManager()
|
||||
self.tagger.window = MagicMock()
|
||||
self.tagger.window.enable_submit = MagicMock()
|
||||
|
||||
def test_add_invalid(self):
|
||||
file = File('foo.flac')
|
||||
self.acoustidmanager.add(file, '00000000-0000-0000-0000-000000000001')
|
||||
self.tagger.window.enable_submit.assert_not_called()
|
||||
|
||||
def test_add_and_update(self):
|
||||
file = File('foo.flac')
|
||||
file.acoustid_fingerprint = 'foo'
|
||||
file.acoustid_length = 120
|
||||
self.acoustidmanager.add(file, '00000000-0000-0000-0000-000000000001')
|
||||
self.tagger.window.enable_submit.assert_called_with(False)
|
||||
self.acoustidmanager.update(file, '00000000-0000-0000-0000-000000000002')
|
||||
self.tagger.window.enable_submit.assert_called_with(True)
|
||||
self.acoustidmanager.update(file, '00000000-0000-0000-0000-000000000001')
|
||||
self.tagger.window.enable_submit.assert_called_with(False)
|
||||
|
||||
def test_add_and_remove(self):
|
||||
file = File('foo.flac')
|
||||
file.acoustid_fingerprint = 'foo'
|
||||
file.acoustid_length = 120
|
||||
self.acoustidmanager.add(file, '00000000-0000-0000-0000-000000000001')
|
||||
self.tagger.window.enable_submit.assert_called_with(False)
|
||||
self.acoustidmanager.update(file, '00000000-0000-0000-0000-000000000002')
|
||||
self.tagger.window.enable_submit.assert_called_with(True)
|
||||
self.acoustidmanager.remove(file)
|
||||
self.tagger.window.enable_submit.assert_called_with(False)
|
||||
|
||||
def test_is_submitted(self):
|
||||
file = File('foo.flac')
|
||||
file.acoustid_fingerprint = 'foo'
|
||||
file.acoustid_length = 120
|
||||
self.assertTrue(self.acoustidmanager.is_submitted(file))
|
||||
self.acoustidmanager.add(file, '00000000-0000-0000-0000-000000000001')
|
||||
self.assertTrue(self.acoustidmanager.is_submitted(file))
|
||||
self.acoustidmanager.update(file, '00000000-0000-0000-0000-000000000002')
|
||||
self.assertFalse(self.acoustidmanager.is_submitted(file))
|
||||
self.acoustidmanager.update(file, '')
|
||||
self.assertTrue(self.acoustidmanager.is_submitted(file))
|
||||