PICARD-2584: Moved AcoustID recording parsing into helper class

This commit is contained in:
Philipp Wolfer
2023-12-21 17:54:34 +01:00
parent a247b9c62a
commit 990a79c8f3
2 changed files with 89 additions and 39 deletions

View File

@@ -34,10 +34,7 @@ import json
from PyQt6 import QtCore
from picard import log
from picard.acoustid.json_helpers import (
max_source_count,
parse_recording,
)
from picard.acoustid.recordings import RecordingResolver
from picard.config import get_config
from picard.const import (
DEFAULT_FPCALC_THREADS,
@@ -49,6 +46,7 @@ from picard.util import (
find_executable,
win_prefix_longpath,
)
from picard.webservice.api_helpers import AcoustIdAPIHelper
def get_score(node):
@@ -76,7 +74,7 @@ AcoustIDTask = namedtuple('AcoustIDTask', ('file', 'next_func'))
class AcoustIDClient(QtCore.QObject):
def __init__(self, acoustid_api):
def __init__(self, acoustid_api: AcoustIdAPIHelper):
super().__init__()
self._queue = deque()
self._running = 0
@@ -108,42 +106,15 @@ class AcoustIDClient(QtCore.QObject):
mparms,
echo=None
)
task.next_func(doc, http, error)
else:
try:
recording_list = doc['recordings'] = []
status = document['status']
if status == 'ok':
results = document.get('results') or []
for result in results:
recordings = result.get('recordings') or []
max_sources = max_source_count(recordings)
result_score = get_score(result)
for recording in recordings:
parsed_recording = parse_recording(recording)
if parsed_recording is not None:
# Calculate a score based on result score and sources for this
# recording relative to other recordings in this result
score = min(recording.get('sources', 1) / max_sources, 1.0) * 100
parsed_recording['score'] = score * result_score
parsed_recording['acoustid'] = result['id']
recording_list.append(parsed_recording)
if results:
if not recording_list:
# Set AcoustID in tags if there was no matching recording
task.file.metadata['acoustid_id'] = results[0]['id']
task.file.update()
log.debug(
"AcoustID: Found no matching recordings for '%s',"
" setting acoustid_id tag to %r",
task.file.filename, results[0]['id']
)
else:
log.debug(
"AcoustID: Lookup successful for '%s' (recordings: %d)",
task.file.filename,
len(recording_list)
)
resolver = RecordingResolver(self._acoustid_api.webservice)
resolver.resolve(
document,
partial(self._on_recording_resolve_finish, task, document, http))
else:
mparms = {
'error': document['error']['message'],
@@ -157,11 +128,31 @@ class AcoustIDClient(QtCore.QObject):
mparms,
echo=None
)
task.next_func(doc, http, error)
except (AttributeError, KeyError, TypeError) as e:
log.error("AcoustID: Error reading response", exc_info=True)
error = e
task.next_func(doc, http, e)
task.next_func(doc, http, error)
def _on_recording_resolve_finish(self, task, document, http, result=None, error=None):
recording_list = document['recordings'] = result
if not recording_list:
results = document.get('results')
if results:
# Set AcoustID in tags if there was no matching recording
task.file.metadata['acoustid_id'] = results[0]['id']
task.file.update()
log.debug(
"AcoustID: Found no matching recordings for '%s',"
" setting acoustid_id tag to %r",
task.file.filename, results[0]['id']
)
else:
log.debug(
"AcoustID: Lookup successful for '%s' (recordings: %d)",
task.file.filename,
len(recording_list)
)
task.next_func(document, http, error)
def _lookup_fingerprint(self, task, result=None, error=None):
if task.file.state == File.REMOVED:

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
#
# Picard, the next-generation MusicBrainz tagger
#
# Copyright (C) 2023 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 picard.acoustid.json_helpers import (
max_source_count,
parse_recording,
)
from picard.webservice import WebService
from picard.webservice.api_helpers import MBAPIHelper
class RecordingResolver:
def __init__(self, ws: WebService) -> None:
self.mbapi = MBAPIHelper(ws)
def resolve(self, doc: dict, callback: callable) -> None:
recording_map = {}
results = doc.get('results') or []
for result in results:
recordings = result.get('recordings') or []
max_sources = max_source_count(recordings)
result_score = get_score(result)
for recording in recordings:
parsed_recording = parse_recording(recording)
if parsed_recording is not None:
# Calculate a score based on result score and sources for this
# recording relative to other recordings in this result
score = min(recording.get('sources', 1) / max_sources, 1.0) * 100
parsed_recording['score'] = score * result_score
parsed_recording['acoustid'] = result.get('id')
recording_map[parsed_recording['id']] = parsed_recording
# TODO: Load recording details for recordings without metadata
callback(recording_map.values())
def get_score(node):
try:
return float(node.get('score', 1.0))
except (TypeError, ValueError):
return 1.0