From 4b62e3fb0544cc4fe41d0bf7f34dc468ee736c9c Mon Sep 17 00:00:00 2001 From: twodoorcoupe Date: Mon, 15 Jul 2024 11:52:18 +0200 Subject: [PATCH 1/4] Make image processing multithreaded --- picard/album.py | 1 + picard/coverart/__init__.py | 13 +++--- picard/coverart/image.py | 10 +++++ picard/coverart/processing/__init__.py | 57 ++++++++++++++++++-------- picard/util/imagelist.py | 5 +++ 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/picard/album.py b/picard/album.py index c4ccb1628e..223d06525f 100644 --- a/picard/album.py +++ b/picard/album.py @@ -460,6 +460,7 @@ def _load_track(node, mm, artists, extra_metadata): self._tracks_loaded = True def _finalize_loading_album(self): + self._new_metadata.images.wait_for_image_processing() with self.suspend_metadata_images_update: for track in self._new_tracks: track.orig_metadata.copy(track.metadata) diff --git a/picard/coverart/__init__.py b/picard/coverart/__init__.py index 88f3eea1f0..b1a44c6640 100644 --- a/picard/coverart/__init__.py +++ b/picard/coverart/__init__.py @@ -49,7 +49,10 @@ ) from picard.extension_points.metadata import register_album_metadata_processor from picard.i18n import N_ -from picard.util import imageinfo +from picard.util import ( + imageinfo, + thread, +) class CoverArt: @@ -76,12 +79,11 @@ def retrieve(self): def _set_metadata(self, coverartimage, data, image_info): try: if coverartimage.can_be_processed: - run_image_processors(coverartimage, data, image_info) + thread.run_task(partial(run_image_processors, coverartimage, data, image_info)) else: coverartimage.set_tags_data(data) if coverartimage.can_be_saved_to_metadata: - log.debug("Storing to metadata: %r [%s]", - coverartimage, coverartimage.imageinfo_as_string()) + log.debug("Storing to metadata: %r", coverartimage) self.metadata.images.append(coverartimage) for track in self.album._new_tracks: track.metadata.images.append(coverartimage) @@ -91,8 +93,7 @@ def _set_metadata(self, coverartimage, data, image_info): if not self.front_image_found: self.front_image_found = coverartimage.is_front_image() else: - log.debug("Not storing to metadata: %r [%s]", - coverartimage, coverartimage.imageinfo_as_string()) + log.debug("Not storing to metadata: %r", coverartimage) except CoverArtImageIOError as e: self.album.error_append(e) self.album._finalize_loading(error=True) diff --git a/picard/coverart/image.py b/picard/coverart/image.py index c9a1b88127..90ca3063c3 100644 --- a/picard/coverart/image.py +++ b/picard/coverart/image.py @@ -324,6 +324,16 @@ def set_tags_data(self, data): def set_external_file_data(self, data): self.external_file_coverart = CoverArtImage(data=data, url=self.url) + @property + def has_finished_processing(self): + if not self.datahash: + return False + config = get_config() + if (config.setting['save_images_to_files'] and not + (self.external_file_coverart and self.external_file_coverart.datahash)): + return False + return True + @property def maintype(self): """Returns one type only, even for images having more than one type set. diff --git a/picard/coverart/processing/__init__.py b/picard/coverart/processing/__init__.py index c663dcc249..c465ab3504 100644 --- a/picard/coverart/processing/__init__.py +++ b/picard/coverart/processing/__init__.py @@ -18,6 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from functools import partial import time from picard import log @@ -36,6 +37,7 @@ ProcessingTarget, get_cover_art_processors, ) +from picard.util import thread from picard.util.imageinfo import IdentificationError @@ -53,6 +55,24 @@ def run_image_metadata_filters(metadata): return True +def _run_processors_queue(image, target, queue, coverartimage, start_time): + for processor in queue: + processor.run(image, target) + data = image.get_result() + if target == ProcessingTarget.TAGS: + coverartimage.set_tags_data(data) + image_target = "embedded" + else: + coverartimage.set_external_file_data(data) + image_target = "external" + log.debug( + "Image processing for %s cover art image %s finished in %d ms", + image_target, + coverartimage, + 1000 * (time.time() - start_time) + ) + + def run_image_processors(coverartimage, data, image_info): config = get_config() tags_data = data @@ -63,23 +83,28 @@ def run_image_processors(coverartimage, data, image_info): both_queue, tags_queue, file_queue = get_cover_art_processors() for processor in both_queue: processor.run(image, ProcessingTarget.BOTH) - if config.setting['save_images_to_tags']: - tags_image = image.copy() - for processor in tags_queue: - processor.run(tags_image, ProcessingTarget.TAGS) - tags_data = tags_image.get_result() - coverartimage.set_tags_data(tags_data) if config.setting['save_images_to_files']: - file_image = image.copy() - for processor in file_queue: - processor.run(file_image, ProcessingTarget.FILE) - file_data = file_image.get_result() - coverartimage.set_external_file_data(file_data) - log.debug( - "Image processing for %s finished in %d ms", - coverartimage, - 1000 * (time.time() - start_time) - ) + run_queue = partial( + _run_processors_queue, + image.copy(), + ProcessingTarget.FILE, + file_queue, + coverartimage, + start_time + ) + thread.run_task(run_queue) + if config.setting['save_images_to_tags']: + run_queue = partial( + _run_processors_queue, + image.copy(), + ProcessingTarget.TAGS, + tags_queue, + coverartimage, + start_time + ) + thread.run_task(run_queue) + else: + coverartimage.set_tags_data(tags_data) except IdentificationError as e: raise CoverArtProcessingError(e) except CoverArtProcessingError as e: diff --git a/picard/util/imagelist.py b/picard/util/imagelist.py index 2a8ec73177..32e210b4ce 100644 --- a/picard/util/imagelist.py +++ b/picard/util/imagelist.py @@ -24,6 +24,7 @@ from collections.abc import MutableSequence +import time from picard.config import get_config @@ -112,3 +113,7 @@ def get_types_dict(self): continue types_dict[image_types] = image return types_dict + + def wait_for_image_processing(self): + while not all(image.has_finished_processing for image in self._images): + time.sleep(0.1) From 65d01b45c0e84bf10e50a72f18848044e11f33de Mon Sep 17 00:00:00 2001 From: twodoorcoupe Date: Tue, 16 Jul 2024 10:41:37 +0200 Subject: [PATCH 2/4] Fix error handling or multithreaded image processing --- picard/coverart/__init__.py | 7 +-- picard/coverart/image.py | 8 +-- picard/coverart/processing/__init__.py | 68 +++++++++++--------------- test/test_coverart_processing.py | 23 +++++++-- 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/picard/coverart/__init__.py b/picard/coverart/__init__.py index b1a44c6640..27db9a0154 100644 --- a/picard/coverart/__init__.py +++ b/picard/coverart/__init__.py @@ -44,9 +44,6 @@ CoverArtProvider, cover_art_providers, ) -from picard.extension_points.cover_art_processors import ( - CoverArtProcessingError, -) from picard.extension_points.metadata import register_album_metadata_processor from picard.i18n import N_ from picard.util import ( @@ -79,7 +76,7 @@ def retrieve(self): def _set_metadata(self, coverartimage, data, image_info): try: if coverartimage.can_be_processed: - thread.run_task(partial(run_image_processors, coverartimage, data, image_info)) + thread.run_task(partial(run_image_processors, coverartimage, data, image_info, self.album)) else: coverartimage.set_tags_data(data) if coverartimage.can_be_saved_to_metadata: @@ -98,7 +95,7 @@ def _set_metadata(self, coverartimage, data, image_info): self.album.error_append(e) self.album._finalize_loading(error=True) raise e - except (CoverArtImageIdentificationError, CoverArtProcessingError) as e: + except CoverArtImageIdentificationError as e: self.album.error_append(e) def _coverart_downloaded(self, coverartimage, data, http, error): diff --git a/picard/coverart/image.py b/picard/coverart/image.py index 90ca3063c3..d2b677dc74 100644 --- a/picard/coverart/image.py +++ b/picard/coverart/image.py @@ -328,10 +328,10 @@ def set_external_file_data(self, data): def has_finished_processing(self): if not self.datahash: return False - config = get_config() - if (config.setting['save_images_to_files'] and not - (self.external_file_coverart and self.external_file_coverart.datahash)): - return False + if not (self.external_file_coverart and self.external_file_coverart.datahash): + config = get_config() + if config.setting['save_images_to_files']: + return False return True @property diff --git a/picard/coverart/processing/__init__.py b/picard/coverart/processing/__init__.py index c465ab3504..535d3d789d 100644 --- a/picard/coverart/processing/__init__.py +++ b/picard/coverart/processing/__init__.py @@ -55,60 +55,48 @@ def run_image_metadata_filters(metadata): return True -def _run_processors_queue(image, target, queue, coverartimage, start_time): - for processor in queue: - processor.run(image, target) - data = image.get_result() - if target == ProcessingTarget.TAGS: - coverartimage.set_tags_data(data) - image_target = "embedded" - else: - coverartimage.set_external_file_data(data) - image_target = "external" - log.debug( - "Image processing for %s cover art image %s finished in %d ms", - image_target, - coverartimage, - 1000 * (time.time() - start_time) - ) +def _run_processors_queue(album, coverartimage, initial_data, start_time, image, target, queue): + data = initial_data + try: + for processor in queue: + processor.run(image, target) + data = image.get_result() + except CoverArtProcessingError as e: + album.error_append(e) + finally: + if target == ProcessingTarget.TAGS: + coverartimage.set_tags_data(data) + else: + coverartimage.set_external_file_data(data) + log.debug( + "Image processing for %s cover art image %s finished in %d ms", + target.name, + coverartimage, + 1000 * (time.time() - start_time) + ) -def run_image_processors(coverartimage, data, image_info): +def run_image_processors(coverartimage, data, image_info, album): config = get_config() - tags_data = data - file_data = data try: start_time = time.time() image = ProcessingImage(data, image_info) both_queue, tags_queue, file_queue = get_cover_art_processors() for processor in both_queue: processor.run(image, ProcessingTarget.BOTH) + run_queue_common = partial(_run_processors_queue, album, coverartimage, data, start_time) if config.setting['save_images_to_files']: - run_queue = partial( - _run_processors_queue, - image.copy(), - ProcessingTarget.FILE, - file_queue, - coverartimage, - start_time - ) + run_queue = partial(run_queue_common, image.copy(), ProcessingTarget.FILE, file_queue) thread.run_task(run_queue) if config.setting['save_images_to_tags']: - run_queue = partial( - _run_processors_queue, - image.copy(), - ProcessingTarget.TAGS, - tags_queue, - coverartimage, - start_time - ) + run_queue = partial(run_queue_common, image.copy(), ProcessingTarget.TAGS, tags_queue) thread.run_task(run_queue) else: - coverartimage.set_tags_data(tags_data) + coverartimage.set_tags_data(data) except IdentificationError as e: - raise CoverArtProcessingError(e) + album.error_append(CoverArtProcessingError(e)) except CoverArtProcessingError as e: - coverartimage.set_tags_data(tags_data) + coverartimage.set_tags_data(data) if config.setting['save_images_to_files']: - coverartimage.set_external_file_data(file_data) - raise e + coverartimage.set_external_file_data(data) + album.error_append(e) diff --git a/test/test_coverart_processing.py b/test/test_coverart_processing.py index 36c0c5d786..945eb3c34a 100644 --- a/test/test_coverart_processing.py +++ b/test/test_coverart_processing.py @@ -152,7 +152,8 @@ def setUp(self): def _check_image_processors(self, size, expected_tags_size, expected_file_size=None): coverartimage = CoverArtImage() image, info = create_fake_image(size[0], size[1], 'jpg') - run_image_processors(coverartimage, image, info) + album = Album(None) + run_image_processors(coverartimage, image, info, album) tags_size = (coverartimage.width, coverartimage.height) if config.setting['save_images_to_tags']: self.assertEqual(tags_size, expected_tags_size) @@ -300,8 +301,20 @@ def test_format_conversion(self): self._check_convert_image('jpeg', format) self.set_config_values(self.settings) - def test_identification_error(self): - image, info = create_fake_image(0, 0, 'jpg') + def _check_processing_error(self, image, info): + self.set_config_values(self.settings) coverartimage = CoverArtImage() - with self.assertRaises(CoverArtProcessingError): - run_image_processors(coverartimage, image, info) + album = Album(None) + run_image_processors(coverartimage, image, info, album) + self.assertNotEqual(album.errors, []) + for error in album.errors: + self.assertIsInstance(error, CoverArtProcessingError) + + def test_identification_error(self): + image, info = create_fake_image(0, 0, "jpg") + self._check_processing_error(image, info) + + def test_encoding_error(self): + image, info = create_fake_image(500, 500, "jpg") + info.extension = ".test" + self._check_processing_error(image, info) From aa7da88e3c85755ae2d9c1509c002fd57fa0d3f9 Mon Sep 17 00:00:00 2001 From: twodoorcoupe Date: Thu, 18 Jul 2024 12:26:52 +0200 Subject: [PATCH 3/4] Make album not poll until each image has finished processing --- picard/album.py | 1 - picard/coverart/__init__.py | 58 ++++----- picard/coverart/image.py | 10 -- picard/coverart/processing/__init__.py | 121 ++++++++++++------ .../extension_points/cover_art_processors.py | 10 +- picard/util/imagelist.py | 5 - test/test_coverart_processing.py | 10 +- 7 files changed, 114 insertions(+), 101 deletions(-) diff --git a/picard/album.py b/picard/album.py index 223d06525f..c4ccb1628e 100644 --- a/picard/album.py +++ b/picard/album.py @@ -460,7 +460,6 @@ def _load_track(node, mm, artists, extra_metadata): self._tracks_loaded = True def _finalize_loading_album(self): - self._new_metadata.images.wait_for_image_processing() with self.suspend_metadata_images_update: for track in self._new_tracks: track.orig_metadata.copy(track.metadata) diff --git a/picard/coverart/__init__.py b/picard/coverart/__init__.py index 27db9a0154..b6a10d0770 100644 --- a/picard/coverart/__init__.py +++ b/picard/coverart/__init__.py @@ -32,13 +32,10 @@ from picard import log from picard.config import get_config -from picard.coverart.image import ( - CoverArtImageIdentificationError, - CoverArtImageIOError, -) +from picard.coverart.image import CoverArtImageIOError from picard.coverart.processing import ( + CoverArtImageProcessing, run_image_filters, - run_image_processors, ) from picard.coverart.providers import ( CoverArtProvider, @@ -46,10 +43,7 @@ ) from picard.extension_points.metadata import register_album_metadata_processor from picard.i18n import N_ -from picard.util import ( - imageinfo, - thread, -) +from picard.util import imageinfo class CoverArt: @@ -60,6 +54,7 @@ def __init__(self, album, metadata, release): self.metadata = metadata self.release = release # not used in this class, but used by providers self.front_image_found = False + self.image_processing = CoverArtImageProcessing(album) def __repr__(self): return "%s for %r" % (self.__class__.__name__, self.album) @@ -74,29 +69,19 @@ def retrieve(self): log.debug("Cover art disabled by user options.") def _set_metadata(self, coverartimage, data, image_info): - try: - if coverartimage.can_be_processed: - thread.run_task(partial(run_image_processors, coverartimage, data, image_info, self.album)) - else: - coverartimage.set_tags_data(data) - if coverartimage.can_be_saved_to_metadata: - log.debug("Storing to metadata: %r", coverartimage) - self.metadata.images.append(coverartimage) - for track in self.album._new_tracks: - track.metadata.images.append(coverartimage) - # If the image already was a front image, - # there might still be some other non-CAA front - # images in the queue - ignore them. - if not self.front_image_found: - self.front_image_found = coverartimage.is_front_image() - else: - log.debug("Not storing to metadata: %r", coverartimage) - except CoverArtImageIOError as e: - self.album.error_append(e) - self.album._finalize_loading(error=True) - raise e - except CoverArtImageIdentificationError as e: - self.album.error_append(e) + self.image_processing.run_image_processors(coverartimage, data, image_info) + if coverartimage.can_be_saved_to_metadata: + log.debug("Storing to metadata: %r", coverartimage) + self.metadata.images.append(coverartimage) + for track in self.album._new_tracks: + track.metadata.images.append(coverartimage) + # If the image already was a front image, + # there might still be some other non-CAA front + # images in the queue - ignore them. + if not self.front_image_found: + self.front_image_found = coverartimage.is_front_image() + else: + log.debug("Not storing to metadata: %r", coverartimage) def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" @@ -123,9 +108,7 @@ def _coverart_downloaded(self, coverartimage, data, http, error): filters_result = run_image_filters(data, image_info, self.album, coverartimage) if filters_result: self._set_metadata(coverartimage, data, image_info) - except (CoverArtImageIOError, imageinfo.IdentificationError): - # It doesn't make sense to store/download more images if we can't - # save them in the temporary folder, abort. + except imageinfo.IdentificationError: return self.next_in_queue() @@ -137,6 +120,9 @@ def next_in_queue(self): if self.album.id not in self.album.tagger.albums: # album removed return + if self.album.loaded: + # album has finished loading due to errors + return config = get_config() if (self.front_image_found @@ -144,6 +130,7 @@ def next_in_queue(self): and not config.setting['save_images_to_files'] and config.setting['embed_only_one_front_image']): # no need to continue + self.image_processing.threadpool.waitForDone() self.album._finalize_loading(None) return @@ -168,6 +155,7 @@ def next_in_queue(self): return except StopIteration: # nothing more to do + self.image_processing.threadpool.waitForDone() self.album._finalize_loading(None) return diff --git a/picard/coverart/image.py b/picard/coverart/image.py index d2b677dc74..c9a1b88127 100644 --- a/picard/coverart/image.py +++ b/picard/coverart/image.py @@ -324,16 +324,6 @@ def set_tags_data(self, data): def set_external_file_data(self, data): self.external_file_coverart = CoverArtImage(data=data, url=self.url) - @property - def has_finished_processing(self): - if not self.datahash: - return False - if not (self.external_file_coverart and self.external_file_coverart.datahash): - config = get_config() - if config.setting['save_images_to_files']: - return False - return True - @property def maintype(self): """Returns one type only, even for images having more than one type set. diff --git a/picard/coverart/processing/__init__.py b/picard/coverart/processing/__init__.py index 535d3d789d..acd1b88e74 100644 --- a/picard/coverart/processing/__init__.py +++ b/picard/coverart/processing/__init__.py @@ -21,8 +21,14 @@ from functools import partial import time +from PyQt6.QtCore import QThreadPool + from picard import log from picard.config import get_config +from picard.coverart.image import ( + CoverArtImageIdentificationError, + CoverArtImageIOError, +) from picard.coverart.processing import ( # noqa: F401 # pylint: disable=unused-import filters, processors, @@ -55,48 +61,79 @@ def run_image_metadata_filters(metadata): return True -def _run_processors_queue(album, coverartimage, initial_data, start_time, image, target, queue): - data = initial_data - try: - for processor in queue: - processor.run(image, target) - data = image.get_result() - except CoverArtProcessingError as e: - album.error_append(e) - finally: - if target == ProcessingTarget.TAGS: - coverartimage.set_tags_data(data) - else: - coverartimage.set_external_file_data(data) - log.debug( - "Image processing for %s cover art image %s finished in %d ms", - target.name, - coverartimage, - 1000 * (time.time() - start_time) - ) +def handle_processing_exceptions(func): + def wrapper(self, *args, **kwargs): + try: + func(self, *args, **kwargs) + except CoverArtImageIOError as e: + self.album.error_append(e) + self.threadpool.clear() + if self.album.loaded: + self.album._finalize_loading(error=True) + except (CoverArtImageIdentificationError, CoverArtProcessingError) as e: + self.album.error_append(e) + return wrapper + + +class CoverArtImageProcessing: + + def __init__(self, album): + self.album = album + self.queues = get_cover_art_processors() + self.threadpool = QThreadPool() + self.threadpool.setMaxThreadCount(1) + + @handle_processing_exceptions + def _run_processors_queue(self, coverartimage, initial_data, start_time, image, target): + data = initial_data + try: + queue = self.queues[target] + for processor in queue: + processor.run(image, target) + data = image.get_result() + except CoverArtProcessingError as e: + raise e + finally: + if target == ProcessingTarget.TAGS: + coverartimage.set_tags_data(data) + else: + coverartimage.set_external_file_data(data) + log.debug( + "Image processing for %s cover art image %s finished in %d ms", + target.name, + coverartimage, + 1000 * (time.time() - start_time) + ) + @handle_processing_exceptions + def _run_image_processors(self, coverartimage, initial_data, image_info): + config = get_config() + try: + start_time = time.time() + image = ProcessingImage(initial_data, image_info) + for processor in self.queues[ProcessingTarget.BOTH]: + processor.run(image, ProcessingTarget.BOTH) + run_queue_common = partial(self._run_processors_queue, coverartimage, initial_data, start_time) + if config.setting['save_images_to_files']: + run_queue = partial(run_queue_common, image.copy(), ProcessingTarget.FILE) + thread.run_task(run_queue, thread_pool=self.threadpool) + if config.setting['save_images_to_tags']: + run_queue = partial(run_queue_common, image.copy(), ProcessingTarget.TAGS) + thread.run_task(run_queue, thread_pool=self.threadpool) + else: + coverartimage.set_tags_data(initial_data) + except IdentificationError as e: + raise CoverArtProcessingError(e) + except CoverArtProcessingError as e: + coverartimage.set_tags_data(initial_data) + if config.setting['save_images_to_files']: + coverartimage.set_external_file_data(initial_data) + raise e -def run_image_processors(coverartimage, data, image_info, album): - config = get_config() - try: - start_time = time.time() - image = ProcessingImage(data, image_info) - both_queue, tags_queue, file_queue = get_cover_art_processors() - for processor in both_queue: - processor.run(image, ProcessingTarget.BOTH) - run_queue_common = partial(_run_processors_queue, album, coverartimage, data, start_time) - if config.setting['save_images_to_files']: - run_queue = partial(run_queue_common, image.copy(), ProcessingTarget.FILE, file_queue) - thread.run_task(run_queue) - if config.setting['save_images_to_tags']: - run_queue = partial(run_queue_common, image.copy(), ProcessingTarget.TAGS, tags_queue) - thread.run_task(run_queue) + def run_image_processors(self, coverartimage, initial_data, image_info): + if coverartimage.can_be_processed: + run_processors = partial(self._run_image_processors, coverartimage, initial_data, image_info) + thread.run_task(run_processors, thread_pool=self.threadpool) else: - coverartimage.set_tags_data(data) - except IdentificationError as e: - album.error_append(CoverArtProcessingError(e)) - except CoverArtProcessingError as e: - coverartimage.set_tags_data(data) - if config.setting['save_images_to_files']: - coverartimage.set_external_file_data(data) - album.error_append(e) + set_data = partial(handle_processing_exceptions, coverartimage.set_tags_data, initial_data) + thread.run_task(set_data, thread_pool=self.threadpool) diff --git a/picard/extension_points/cover_art_processors.py b/picard/extension_points/cover_art_processors.py index 8989a0f024..d929300b28 100644 --- a/picard/extension_points/cover_art_processors.py +++ b/picard/extension_points/cover_art_processors.py @@ -92,16 +92,16 @@ def run(self, image, target): def get_cover_art_processors(): - queue_both, queue_tags, queue_file = [], [], [] + queues = dict.fromkeys(list(ProcessingTarget), []) for processor in ext_point_cover_art_processors: if processor.same_processing(): - queue_both.append(processor) + queues[ProcessingTarget.BOTH].append(processor) else: if processor.save_to_tags(): - queue_tags.append(processor) + queues[ProcessingTarget.TAGS].append(processor) if processor.save_to_file(): - queue_file.append(processor) - return queue_both, queue_tags, queue_file + queues[ProcessingTarget.FILE].append(processor) + return queues def register_cover_art_processor(cover_art_processor): diff --git a/picard/util/imagelist.py b/picard/util/imagelist.py index 32e210b4ce..2a8ec73177 100644 --- a/picard/util/imagelist.py +++ b/picard/util/imagelist.py @@ -24,7 +24,6 @@ from collections.abc import MutableSequence -import time from picard.config import get_config @@ -113,7 +112,3 @@ def get_types_dict(self): continue types_dict[image_types] = image return types_dict - - def wait_for_image_processing(self): - while not all(image.has_finished_processing for image in self._images): - time.sleep(0.1) diff --git a/test/test_coverart_processing.py b/test/test_coverart_processing.py index 945eb3c34a..46358d1fa1 100644 --- a/test/test_coverart_processing.py +++ b/test/test_coverart_processing.py @@ -29,7 +29,7 @@ from picard.album import Album from picard.const.cover_processing import ResizeModes from picard.coverart.image import CoverArtImage -from picard.coverart.processing import run_image_processors +from picard.coverart.processing import CoverArtImageProcessing from picard.coverart.processing.filters import ( bigger_previous_image_filter, image_types_filter, @@ -153,7 +153,9 @@ def _check_image_processors(self, size, expected_tags_size, expected_file_size=N coverartimage = CoverArtImage() image, info = create_fake_image(size[0], size[1], 'jpg') album = Album(None) - run_image_processors(coverartimage, image, info, album) + image_processing = CoverArtImageProcessing(album) + image_processing.run_image_processors(coverartimage, image, info) + image_processing.threadpool.waitForDone() tags_size = (coverartimage.width, coverartimage.height) if config.setting['save_images_to_tags']: self.assertEqual(tags_size, expected_tags_size) @@ -305,7 +307,9 @@ def _check_processing_error(self, image, info): self.set_config_values(self.settings) coverartimage = CoverArtImage() album = Album(None) - run_image_processors(coverartimage, image, info, album) + image_processing = CoverArtImageProcessing(album) + image_processing.run_image_processors(coverartimage, image, info) + image_processing.threadpool.waitForDone() self.assertNotEqual(album.errors, []) for error in album.errors: self.assertIsInstance(error, CoverArtProcessingError) From 89702c7d8fcb0d8c7acd615fd0447c45ae67d1a1 Mon Sep 17 00:00:00 2001 From: twodoorcoupe Date: Mon, 22 Jul 2024 08:52:00 +0200 Subject: [PATCH 4/4] Wrap the method to wait for processing completion --- picard/coverart/__init__.py | 4 ++-- picard/coverart/processing/__init__.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/picard/coverart/__init__.py b/picard/coverart/__init__.py index b6a10d0770..1141389047 100644 --- a/picard/coverart/__init__.py +++ b/picard/coverart/__init__.py @@ -130,7 +130,7 @@ def next_in_queue(self): and not config.setting['save_images_to_files'] and config.setting['embed_only_one_front_image']): # no need to continue - self.image_processing.threadpool.waitForDone() + self.image_processing.wait_for_processing() self.album._finalize_loading(None) return @@ -155,7 +155,7 @@ def next_in_queue(self): return except StopIteration: # nothing more to do - self.image_processing.threadpool.waitForDone() + self.image_processing.wait_for_processing() self.album._finalize_loading(None) return diff --git a/picard/coverart/processing/__init__.py b/picard/coverart/processing/__init__.py index acd1b88e74..05a0132ef3 100644 --- a/picard/coverart/processing/__init__.py +++ b/picard/coverart/processing/__init__.py @@ -137,3 +137,6 @@ def run_image_processors(self, coverartimage, initial_data, image_info): else: set_data = partial(handle_processing_exceptions, coverartimage.set_tags_data, initial_data) thread.run_task(set_data, thread_pool=self.threadpool) + + def wait_for_processing(self): + self.threadpool.waitForDone()