Hello community, here is the log from the commit of package sacad for openSUSE:Factory checked in at 2020-06-14 18:33:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/sacad (Old) and /work/SRC/openSUSE:Factory/.sacad.new.3606 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "sacad" Sun Jun 14 18:33:53 2020 rev:2 rq:814322 version:2.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/sacad/sacad.changes 2020-03-26 23:33:08.258772083 +0100 +++ /work/SRC/openSUSE:Factory/.sacad.new.3606/sacad.changes 2020-06-14 18:35:47.190633210 +0200 @@ -1,0 +2,6 @@ +Sat Jun 13 11:19:24 UTC 2020 - Martin Hauke <[email protected]> + +- Update to version 2.3.0 + * Add Deezer cover source + +------------------------------------------------------------------- Old: ---- sacad-2.2.3.tar.gz New: ---- sacad-2.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ sacad.spec ++++++ --- /var/tmp/diff_new_pack.yRQOhI/_old 2020-06-14 18:35:47.854635324 +0200 +++ /var/tmp/diff_new_pack.yRQOhI/_new 2020-06-14 18:35:47.858635337 +0200 @@ -1,7 +1,7 @@ # -# spec file for package python-sacad +# spec file for package sacad # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,11 +12,12 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ +# Name: sacad -Version: 2.2.3 +Version: 2.3.0 Release: 0 Summary: Search and download music album covers License: MPL-2.0 @@ -24,9 +25,11 @@ URL: https://github.com/desbma/sacad Source: https://github.com/desbma/sacad/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz BuildRequires: fdupes +BuildRequires: fdupes BuildRequires: python-rpm-macros BuildRequires: python3-setuptools -BuildRequires: fdupes +Requires: python3-Pillow >= 2.7.0 +Requires: python3-Unidecode >= 1.1.1 Requires: python3-aiohttp >= 3.6 Requires: python3-appdirs >= 1.4.0 Requires: python3-bitarray >= 0.8.3 @@ -34,9 +37,7 @@ Requires: python3-fake-useragent >= 0.1.11 Requires: python3-lxml >= 4.0.0 Requires: python3-mutagen >= 1.31 -Requires: python3-Pillow >= 2.7.0 Requires: python3-tqdm >= 4.28.1 -Requires: python3-Unidecode >= 1.1.1 Requires: python3-web_cache >= 1.1.0 BuildArch: noarch ++++++ sacad-2.2.3.tar.gz -> sacad-2.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/README.md new/sacad-2.3.0/README.md --- old/sacad-2.2.3/README.md 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/README.md 2020-06-08 01:13:37.000000000 +0200 @@ -24,11 +24,12 @@ * Support JPEG and PNG formats * Customizable output: save image along with the audio files / in a different directory named by artist/album / embed cover in audio files... * Currently support the following cover sources: - * Last.fm - * Google Images - * ~~CoverLib~~ (site is down) * Amazon CD (.com, .ca, .cn, .fr, .de, .co.jp and .co.uk variants) * Amazon digital music + * ~~CoverLib~~ (site is down) + * Deezer + * Google Images + * Last.fm * Smart sorting algorithm to select THE best cover for a given query, using several factors: source reliability, image format, image size, image similarity with reference cover, etc. * Automatically crunch images with optipng or jpegoptim (can save 30% of filesize without any loss of quality, great for portable players) * Cache search results locally for faster future search diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/sacad/__init__.py new/sacad-2.3.0/sacad/__init__.py --- old/sacad-2.2.3/sacad/__init__.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/sacad/__init__.py 2020-06-08 01:13:37.000000000 +0200 @@ -2,7 +2,7 @@ """ Smart Automatic Cover Art Downloader : search and download music album covers. """ -__version__ = "2.2.3" +__version__ = "2.3.0" __author__ = "desbma" __license__ = "MPL 2.0" @@ -17,11 +17,13 @@ from sacad.cover import CoverSourceResult, HAS_JPEGOPTIM, HAS_OPTIPNG, SUPPORTED_IMG_FORMATS -async def search_and_download(album, artist, format, size, out_filepath, *, size_tolerance_prct, amazon_tlds, no_lq_sources): +async def search_and_download(album, artist, format, size, out_filepath, *, size_tolerance_prct, amazon_tlds, + no_lq_sources, preserve_format=False): """ Search and download a cover, return True if success, False instead. """ # register sources source_args = (size, size_tolerance_prct) cover_sources = [sources.LastFmCoverSource(*source_args), + sources.DeezerCoverSource(*source_args), sources.AmazonCdCoverSource(*source_args), sources.AmazonDigitalCoverSource(*source_args)] for tld in amazon_tlds: @@ -58,7 +60,7 @@ done = False for result in results: try: - await result.get(format, size, size_tolerance_prct, out_filepath) + await result.get(format, size, size_tolerance_prct, out_filepath, preserve_format=preserve_format) except Exception as e: logging.getLogger("Main").warning("Download of %s failed: %s %s" % (result, e.__class__.__qualname__, @@ -101,6 +103,11 @@ help="""Disable cover sources that may return unreliable results (ie. Google Images). It will speed up search and improve reliability, but may fail to find results for some difficult searches.""") + arg_parser.add_argument("-p", + "--preserve-format", + action="store_true", + default=False, + help="Preserve source image format if possible. Target format will still be prefered when sorting results.") def cl_main(): @@ -164,7 +171,8 @@ args.out_filepath, size_tolerance_prct=args.size_tolerance_prct, amazon_tlds=args.amazon_tlds, - no_lq_sources=args.no_lq_sources) + no_lq_sources=args.no_lq_sources, + preserve_format=args.preserve_format) if hasattr(asyncio, "run"): # Python >=3.7.0 asyncio.run(coroutine) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/sacad/cover.py new/sacad-2.3.0/sacad/cover.py --- old/sacad-2.2.3/sacad/cover.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/sacad/cover.py 2020-06-08 01:13:37.000000000 +0200 @@ -37,6 +37,8 @@ SUPPORTED_IMG_FORMATS = {"jpg": CoverImageFormat.JPEG, "jpeg": CoverImageFormat.JPEG, "png": CoverImageFormat.PNG} +FORMAT_EXTENSIONS = {CoverImageFormat.JPEG: "jpg", + CoverImageFormat.PNG: "png"} def is_square(x): @@ -111,7 +113,7 @@ s += " [x%u]" % (len(self.urls)) return s - async def get(self, target_format, target_size, size_tolerance_prct, out_filepath): + async def get(self, target_format, target_size, size_tolerance_prct, out_filepath, *, preserve_format=False): """ Download cover and process it. """ if self.source_quality.value <= CoverSourceQuality.LOW.value: logging.getLogger("Cover").warning("Cover is from a potentially unreliable source and may be unrelated to the search") @@ -143,7 +145,7 @@ (abs(max(self.size) - target_size) > target_size * size_tolerance_prct / 100)) need_join = len(images_data) > 1 - if need_join or need_format_change or need_size_change: + if (need_format_change and (not preserve_format)) or need_join or need_size_change: # post process image_data = self.postProcess(images_data, target_format if need_format_change else None, @@ -152,7 +154,14 @@ # crunch image again image_data = await __class__.crunch(image_data, target_format) + format_changed = need_format_change + else: + format_changed = False + # write it + if need_format_change and (not format_changed): + assert(preserve_format) + out_filepath = "%s.%s" % (os.path.splitext(out_filepath)[0], FORMAT_EXTENSIONS[self.format]) with open(out_filepath, "wb") as file: file.write(image_data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/sacad/recurse.py new/sacad-2.3.0/sacad/recurse.py --- old/sacad-2.2.3/sacad/recurse.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/sacad/recurse.py 2020-06-08 01:13:37.000000000 +0200 @@ -12,6 +12,7 @@ import logging import operator import os +import resource import string import tempfile @@ -75,7 +76,7 @@ (self.metadata == other.metadata)) -def analyze_lib(lib_dir, cover_pattern, *, ignore_existing=False, full_scan=False): +def analyze_lib(lib_dir, cover_pattern, *, ignore_existing=False, full_scan=False, all_formats=False): """ Recursively analyze library, and return a list of work. """ work = [] stats = collections.OrderedDict(((k, 0) for k in("files", "albums", "missing covers", "errors"))) @@ -89,7 +90,8 @@ rel_filepaths, cover_pattern, ignore_existing=ignore_existing, - full_scan=full_scan) + full_scan=full_scan, + all_formats=all_formats) progress.set_postfix(stats, refresh=False) progress.update(1) work.extend(new_work) @@ -192,7 +194,7 @@ def analyze_dir(stats, parent_dir, rel_filepaths, cover_pattern, *, - ignore_existing=False, full_scan=False): + ignore_existing=False, full_scan=False, all_formats=False): """ Analyze a directory (non recursively) and return a list of Work objects. """ r = [] @@ -222,7 +224,10 @@ # add work item if needed if cover_pattern != EMBEDDED_ALBUM_ART_SYMBOL: cover_filepath = pattern_to_filepath(cover_pattern, parent_dir, metadata) - missing = (not os.path.isfile(cover_filepath)) or ignore_existing + if all_formats: + missing = ignore_existing or (not any(os.path.isfile("%s.%s" % (os.path.splitext(cover_filepath)[0], ext)) for ext in sacad.SUPPORTED_IMG_FORMATS)) + else: + missing = ignore_existing or (not os.path.isfile(cover_filepath)) else: cover_filepath = EMBEDDED_ALBUM_ART_SYMBOL missing = (not metadata.has_embedded_cover) or ignore_existing @@ -328,7 +333,7 @@ # so work in smaller chunks to avoid hitting fd limit # this also updates the progress faster (instead of working on all searches, work on finishing the chunk before # getting to the next one) - work_chunk_length = 16 + work_chunk_length = 12 for work_chunk in ichunk(work, work_chunk_length): futures = {} for i, cur_work in enumerate(work_chunk, i): @@ -345,7 +350,8 @@ cover_filepath, size_tolerance_prct=args.size_tolerance_prct, amazon_tlds=args.amazon_tlds, - no_lq_sources=args.no_lq_sources) + no_lq_sources=args.no_lq_sources, + preserve_format=args.preserve_format) future = asyncio.ensure_future(coroutine) futures[future] = cur_work @@ -421,11 +427,23 @@ logging_handler.setFormatter(logging_formatter) logging.getLogger().addHandler(logging_handler) + # bump nofile ulimit + try: + soft_lim, hard_lim = resource.getrlimit(resource.RLIMIT_NOFILE) + if ((soft_lim != resource.RLIM_INFINITY) and + ((soft_lim < hard_lim) or (hard_lim == resource.RLIM_INFINITY))): + resource.setrlimit(resource.RLIMIT_NOFILE, (hard_lim, hard_lim)) + logging.getLogger().debug("Max open files count set from %u to %u" % (soft_lim, hard_lim)) + except (AttributeError, OSError): + # not supported on system + pass + # do the job work = analyze_lib(args.lib_dir, args.cover_pattern, ignore_existing=args.ignore_existing, - full_scan=args.full_scan) + full_scan=args.full_scan, + all_formats=args.preserve_format) get_covers(work, args) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/sacad/sources/__init__.py new/sacad-2.3.0/sacad/sources/__init__.py --- old/sacad-2.2.3/sacad/sources/__init__.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/sacad/sources/__init__.py 2020-06-08 01:13:37.000000000 +0200 @@ -1,4 +1,5 @@ from sacad.sources.amazoncd import AmazonCdCoverSource, AmazonCdCoverSourceResult from sacad.sources.amazondigital import AmazonDigitalCoverSource, AmazonDigitalCoverSourceResult -from sacad.sources.lastfm import LastFmCoverSource, LastFmCoverSourceResult +from sacad.sources.deezer import DeezerCoverSource, DeezerCoverSourceResult from sacad.sources.google_images import GoogleImagesWebScrapeCoverSource, GoogleImagesCoverSourceResult +from sacad.sources.lastfm import LastFmCoverSource, LastFmCoverSourceResult diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/sacad/sources/amazonbase.py new/sacad-2.3.0/sacad/sources/amazonbase.py --- old/sacad-2.2.3/sacad/sources/amazonbase.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/sacad/sources/amazonbase.py 2020-06-08 01:13:37.000000000 +0200 @@ -9,8 +9,8 @@ def __init__(self, *args, base_domain, **kwargs): super().__init__(*args, allow_cookies=True, - min_delay_between_accesses=2 / 3, - jitter_range_ms=(0, 600), + min_delay_between_accesses=2, + jitter_range_ms=(0, 3000), rate_limited_domains=(base_domain,), **kwargs) self.current_ua = self.ua.firefox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/sacad/sources/deezer.py new/sacad-2.3.0/sacad/sources/deezer.py --- old/sacad-2.2.3/sacad/sources/deezer.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sacad-2.3.0/sacad/sources/deezer.py 2020-06-08 01:13:37.000000000 +0200 @@ -0,0 +1,76 @@ +import collections +import json +import operator + +from sacad.cover import CoverImageFormat, CoverImageMetadata, CoverSourceQuality, CoverSourceResult +from sacad.sources.base import CoverSource + + +class DeezerCoverSourceResult(CoverSourceResult): + + def __init__(self, *args, **kwargs): + super().__init__(*args, source_quality=CoverSourceQuality.NORMAL, **kwargs) + + +class DeezerCoverSource(CoverSource): + + """ + Cover source using the official Deezer API. + + https://developers.deezer.com/api/ + """ + + BASE_URL = "https://api.deezer.com/search" + COVER_SIZES = {"cover_small": (56, 56), + "cover": (120, 120), + "cover_medium": (250, 250), + "cover_big": (500, 500), + "cover_xl": (1000, 1000)} + + def __init__(self, *args, **kwargs): + super().__init__(*args, min_delay_between_accesses=0.1, **kwargs) + + def getSearchUrl(self, album, artist): + """ See CoverSource.getSearchUrl. """ + # build request url + search_params = collections.OrderedDict() + search_params["artist"] = artist + search_params["album"] = album + url_params = collections.OrderedDict() + #url_params["strict"] = "on" + url_params["order"] = "RANKING" + url_params["q"] = " ".join("%s:\"%s\"" % (k, v) for k, v in search_params.items()) + return __class__.assembleUrl(__class__.BASE_URL, url_params) + + def processQueryString(self, s): + """ See CoverSource.processQueryString. """ + # API search is fuzzy, not need to alter query + return s + + async def parseResults(self, api_data): + """ See CoverSource.parseResults. """ + results = [] + + # get unique albums + json_data = json.loads(api_data) + albums = [] + for e in json_data["data"]: + album = e["album"] + album_id = album["id"] + if album_id in map(operator.itemgetter("id"), albums): + continue + albums.append(album) + + for rank, album in enumerate(albums, 1): + for key, size in __class__.COVER_SIZES.items(): + img_url = album[key] + thumbnail_url = album["cover_small"] + results.append(DeezerCoverSourceResult(img_url, + size, + CoverImageFormat.JPEG, + thumbnail_url=thumbnail_url, + source=self, + rank=rank, + check_metadata=CoverImageMetadata.NONE)) + + return results diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/tests/__init__.py new/sacad-2.3.0/tests/__init__.py --- old/sacad-2.2.3/tests/__init__.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/tests/__init__.py 2020-06-08 01:13:37.000000000 +0200 @@ -148,7 +148,10 @@ """ Check all sources return valid results with different parameters. """ for size in range(300, 1200 + 1, 300): source_args = (size, 0) - sources = [] + sources = [sacad.sources.LastFmCoverSource(*source_args), + sacad.sources.DeezerCoverSource(*source_args), + #sacad.sources.GoogleImagesWebScrapeCoverSource(*source_args), + sacad.sources.AmazonDigitalCoverSource(*source_args)] sources.extend(sacad.sources.AmazonCdCoverSource(*source_args, tld=tld) for tld in sacad.sources.AmazonCdCoverSource.TLDS) for artist, album in zip(("Michael Jackson", "Björk"), ("Thriller", "Vespertine")): for source in sources: @@ -158,6 +161,7 @@ coroutine = sacad.CoverSourceResult.preProcessForComparison(results, size, 0) results = sched_and_run(coroutine, delay=0.5) if not (((size > 500) and isinstance(source, sacad.sources.LastFmCoverSource)) or + ((size > 1000) and isinstance(source, sacad.sources.DeezerCoverSource)) or (isinstance(source, sacad.sources.AmazonCdCoverSource) and (urllib.parse.urlsplit(source.base_url).netloc.rsplit(".", 1)[-1] in ("cn", "jp")))): self.assertGreaterEqual(len(results), 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/tests/recurse_test.py new/sacad-2.3.0/tests/recurse_test.py --- old/sacad-2.2.3/tests/recurse_test.py 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/tests/recurse_test.py 2020-06-08 01:13:37.000000000 +0200 @@ -13,7 +13,7 @@ import requests import sacad.recurse as recurse -from sacad.recurse import Metadata, Work +from sacad.recurse import Metadata def download(url, filepath): @@ -42,6 +42,10 @@ @classmethod def setUpClass(cls): + # + # Album 1: 1 valid ogg track + # + cls.temp_dir = tempfile.TemporaryDirectory() cls.album1_dir = os.path.join(cls.temp_dir.name, "album1") os.mkdir(cls.album1_dir) @@ -53,6 +57,10 @@ mf["album"] = "ALBUM1" mf.save() + # + # Album 2: 1 valid track + 1 invalid file + 1 png file + # + cls.album2_dir = os.path.join(cls.temp_dir.name, "album2") os.mkdir(cls.album2_dir) cls.album2_filepath1 = os.path.join(cls.album2_dir, "1.dat") @@ -65,28 +73,50 @@ mf["album"] = "ALBUM2" mf.save() + open(os.path.join(cls.album2_dir, "1.png"), "wb").close() + + # + # Album 3: 1 valid mp3 track + # + cls.album3_dir = os.path.join(cls.temp_dir.name, "album3") os.mkdir(cls.album3_dir) url = r"https://www.dropbox.com/s/mtac0y8azs5hqxo/Shuffle%2520for%2520K.M.mp3?dl=1" cls.album3_filepath = os.path.join(cls.album3_dir, "1 track.mp3") download(url, cls.album3_filepath) + # + # Album 4: 1 valid m4a track + # + cls.album4_dir = os.path.join(cls.temp_dir.name, "album4") os.mkdir(cls.album4_dir) url = "https://auphonic.com/media/audio-examples/01.auphonic-demo-unprocessed.m4a" cls.album4_filepath = os.path.join(cls.album4_dir, "1 track.m4a") download(url, cls.album4_filepath) + # + # Album 5: 2 valid ogg tracks with different metadata + # + cls.album5_dir = os.path.join(cls.temp_dir.name, "album5") os.mkdir(cls.album5_dir) cls.album5_filepath1 = shutil.copy(cls.album1_filepath, cls.album5_dir) cls.album5_filepath2 = shutil.copy(cls.album2_filepath2, os.path.join(cls.album5_dir, "bzz.ogg")) + # + # 'not album' dir: 1 invalid file + # + cls.not_album_dir = os.path.join(cls.temp_dir.name, "not an album") os.mkdir(cls.not_album_dir) shutil.copyfile(cls.album2_filepath1, os.path.join(cls.not_album_dir, "a.dat")) + # + # 'invalid album' dir: 1 ogg track without metadata + 1 invalid file + # + cls.invalid_album_dir = os.path.join(cls.temp_dir.name, "invalid album") os.mkdir(cls.invalid_album_dir) cls.invalid_album_filepath1 = shutil.copyfile(cls.album1_filepath, @@ -104,11 +134,13 @@ cls.temp_dir.cleanup() def test_analyze_lib(self): - with open(os.devnull, "wt") as dn, contextlib.redirect_stdout(dn): - for full_scan in (False, True): - with self.subTest(full_scan=full_scan): - work = recurse.analyze_lib(__class__.temp_dir.name, "a.jpg", - full_scan=full_scan) + for full_scan in (False, True): + for all_formats in (False, True): + with self.subTest(full_scan=full_scan, all_formats=all_formats): + work = recurse.analyze_lib(__class__.temp_dir.name, + "a.jpg", + full_scan=full_scan, + all_formats=all_formats) work.sort(key=lambda x: (x.cover_filepath, x.metadata)) self.assertEqual(len(work), 5 + int(full_scan)) self.assertEqual(work[0].cover_filepath, @@ -137,30 +169,38 @@ self.assertEqual(work[5].metadata, Metadata("ARTIST2", "ALBUM2", False)) - work = recurse.analyze_lib(__class__.temp_dir.name, "1.dat", - full_scan=full_scan) + work = recurse.analyze_lib(__class__.temp_dir.name, + "1.dat", + full_scan=full_scan, + all_formats=all_formats) work.sort(key=lambda x: (x.cover_filepath, x.metadata)) - self.assertEqual(len(work), 4 + int(full_scan)) - self.assertEqual(work[0].cover_filepath, - os.path.join(__class__.album1_dir, "1.dat")) - self.assertEqual(work[0].metadata, - Metadata("ARTIST1", "ALBUM1", False)) - self.assertEqual(work[1].cover_filepath, + self.assertEqual(len(work), 4 + int(full_scan) - int(all_formats)) + idx = 0 + if not all_formats: + self.assertEqual(work[idx].cover_filepath, + os.path.join(__class__.album1_dir, "1.dat")) + self.assertEqual(work[idx].metadata, + Metadata("ARTIST1", "ALBUM1", False)) + idx += 1 + self.assertEqual(work[idx].cover_filepath, os.path.join(__class__.album3_dir, "1.dat")) - self.assertEqual(work[1].metadata, + self.assertEqual(work[idx].metadata, Metadata("jpfmband", "Paris S.F", True)) - self.assertEqual(work[2].cover_filepath, + idx += 1 + self.assertEqual(work[idx].cover_filepath, os.path.join(__class__.album4_dir, "1.dat")) - self.assertEqual(work[2].metadata, + self.assertEqual(work[idx].metadata, Metadata("Auphonic", "Auphonic Demonstration", True)) - self.assertEqual(work[3].cover_filepath, + idx += 1 + self.assertEqual(work[idx].cover_filepath, os.path.join(__class__.album5_dir, "1.dat")) - self.assertEqual(work[3].metadata, + self.assertEqual(work[idx].metadata, Metadata("ARTIST1", "ALBUM1", False)) + idx += 1 if full_scan: - self.assertEqual(work[4].cover_filepath, + self.assertEqual(work[idx].cover_filepath, os.path.join(__class__.album5_dir, "1.dat")) - self.assertEqual(work[4].metadata, + self.assertEqual(work[idx].metadata, Metadata("ARTIST2", "ALBUM2", False)) def test_get_file_metadata(self): @@ -209,48 +249,55 @@ self.assertEqual(len(r), 0) def test_analyze_dir(self): - with open(os.devnull, "wt") as dn, contextlib.redirect_stdout(dn): - stats = collections.defaultdict(int) - r = recurse.analyze_dir(stats, - __class__.album1_dir, - os.listdir(__class__.album1_dir), - "1.jpg") - self.assertIn("files", stats) - self.assertEqual(stats["files"], 1) - self.assertIn("albums", stats) - self.assertEqual(stats["albums"], 1) - self.assertIn("missing covers", stats) - self.assertEqual(stats["missing covers"], 1) - self.assertNotIn("errors", stats) - self.assertEqual(len(r), 1) - self.assertEqual(r[0].cover_filepath, os.path.join(__class__.album1_dir, "1.jpg")) - self.assertEqual(r[0].audio_filepaths, (__class__.album1_filepath,)) - self.assertEqual(r[0].metadata, Metadata("ARTIST1", "ALBUM1", False)) + stats = collections.defaultdict(int) + r = recurse.analyze_dir(stats, + __class__.album1_dir, + os.listdir(__class__.album1_dir), + "1.jpg") + self.assertIn("files", stats) + self.assertEqual(stats["files"], 1) + self.assertIn("albums", stats) + self.assertEqual(stats["albums"], 1) + self.assertIn("missing covers", stats) + self.assertEqual(stats["missing covers"], 1) + self.assertNotIn("errors", stats) + self.assertEqual(len(r), 1) + self.assertEqual(r[0].cover_filepath, os.path.join(__class__.album1_dir, "1.jpg")) + self.assertEqual(r[0].audio_filepaths, (__class__.album1_filepath,)) + self.assertEqual(r[0].metadata, Metadata("ARTIST1", "ALBUM1", False)) + + for all_formats in (False, True): + for format_ext in ("jpg", "jpeg"): + with self.subTest(all_formats=all_formats, format_ext=format_ext): + stats.clear() + r = recurse.analyze_dir(stats, + __class__.album2_dir, + os.listdir(__class__.album2_dir), + "1.%s" % (format_ext), + all_formats=all_formats) + self.assertIn("files", stats) + self.assertEqual(stats["files"], 3) + self.assertIn("albums", stats) + self.assertEqual(stats["albums"], 1) + if all_formats: + self.assertNotIn("missing covers", stats) + self.assertEqual(len(r), 0) + else: + self.assertIn("missing covers", stats) + self.assertEqual(stats["missing covers"], 1) + self.assertEqual(len(r), 1) + self.assertEqual(r[0].cover_filepath, os.path.join(__class__.album2_dir, "1.%s" % (format_ext))) + self.assertEqual(r[0].audio_filepaths, (__class__.album2_filepath2,)) + self.assertEqual(r[0].metadata, Metadata("ARTIST2", "ALBUM2", False)) + self.assertNotIn("errors", stats) stats.clear() r = recurse.analyze_dir(stats, __class__.album2_dir, os.listdir(__class__.album2_dir), - "1.jpg") - self.assertIn("files", stats) - self.assertEqual(stats["files"], 2) - self.assertIn("albums", stats) - self.assertEqual(stats["albums"], 1) - self.assertIn("missing covers", stats) - self.assertEqual(stats["missing covers"], 1) - self.assertNotIn("errors", stats) - self.assertEqual(len(r), 1) - self.assertEqual(r[0].cover_filepath, os.path.join(__class__.album2_dir, "1.jpg")) - self.assertEqual(r[0].audio_filepaths, (__class__.album2_filepath2,)) - self.assertEqual(r[0].metadata, Metadata("ARTIST2", "ALBUM2", False)) - stats.clear() - - r = recurse.analyze_dir(stats, - __class__.album2_dir, - os.listdir(__class__.album2_dir), "1.dat") self.assertIn("files", stats) - self.assertEqual(stats["files"], 2) + self.assertEqual(stats["files"], 3) self.assertIn("albums", stats) self.assertEqual(stats["albums"], 1) self.assertNotIn("missing covers", stats) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sacad-2.2.3/win/Makefile new/sacad-2.3.0/win/Makefile --- old/sacad-2.2.3/win/Makefile 2019-11-30 20:51:20.000000000 +0100 +++ new/sacad-2.3.0/win/Makefile 2020-06-08 01:13:37.000000000 +0200 @@ -1,11 +1,11 @@ WINEARCH ?= win32 # versions -PYTHON_VERSION := 3.6.4 -PYTHON_VERSION_DL := 3.6.4 +PYTHON_VERSION := 3.6.8 +PYTHON_VERSION_DL := 3.6.8 PYTHON_VERSION_MAJOR := $(word 1,$(subst ., ,${PYTHON_VERSION})).$(word 2,$(subst ., ,${PYTHON_VERSION})) PYTHON_VERSION_SHORT := $(subst .,,${PYTHON_VERSION_MAJOR}) -CXFREEZE_VERSION := 5.1.1 +CXFREEZE_VERSION := 6.1 LXML_VERSION := 4.2.0 BITARRAY_VERSION := 0.8.3 @@ -160,11 +160,11 @@ ${CXFREEZE_WHEEL-win32}: mkdir -p $(dir $@) - ${CURL} https://pypi.python.org/packages/3b/04/d68567b8f3265d3936e37c1ee8b3378dc189ec66b4d43650affbefcc69c5/$(notdir $@) > $@ + ${CURL} https://files.pythonhosted.org/packages/ea/2b/076f152fdaff10a08ca4012713532b6d2606c1995ee85812d271135f90e2/$(notdir $@) > $@ ${CXFREEZE_WHEEL-win64}: mkdir -p $(dir $@) - ${CURL} https://pypi.python.org/packages/49/2b/07fc4cfff671ea348b310400e4f13a596e02079afc6bd1c33150b2cf30de/$(notdir $@) > $@ + ${CURL} https://files.pythonhosted.org/packages/50/dc/ad7111fc626a24504e468382c431698e4dcd60c944b9e9adf2187d073865/$(notdir $@) > $@ ${LXML_WHEEL-win32}: mkdir -p $(dir $@)
