def test_getMasterOfPuppetsCover(self): """ Search and download cover for 'Master of Puppets' with different parameters. """ for format in sacad.cover.CoverImageFormat: for size in (300, 600, 1200): for size_tolerance in (0, 25, 50): with sacad.mkstemp_ctx.mkstemp( prefix="sacad_test_", suffix=".%s" % (format.name.lower())) as tmp_filepath: sacad.search_and_download("Master of Puppets", "Metallica", format, size, size_tolerance, (), False, tmp_filepath) out_format, out_width, out_height = __class__.getImgInfo( tmp_filepath) self.assertEqual(out_format, format) self.assertLessEqual( out_width, size * (100 + size_tolerance) / 100) self.assertGreaterEqual( out_width, size * (100 - size_tolerance) / 100) self.assertLessEqual( out_height, size * (100 + size_tolerance) / 100) self.assertGreaterEqual( out_height, size * (100 - size_tolerance) / 100)
def test_getMasterOfPuppetsCover(self): """ Search and download cover for 'Master of Puppets' with different parameters. """ async_loop = asyncio.get_event_loop() for format in sacad.cover.CoverImageFormat: for size in (300, 600, 1200): for size_tolerance in (0, 25, 50): with self.subTest(format=format, size=size, size_tolerance=size_tolerance): with sacad.mkstemp_ctx.mkstemp(prefix="sacad_test_", suffix=".%s" % (format.name.lower())) as tmp_filepath: coroutine = sacad.search_and_download("Master of Puppets", "Metallica", format, size, tmp_filepath, size_tolerance_prct=size_tolerance, amazon_tlds=(), no_lq_sources=False, async_loop=async_loop) sched_and_run(coroutine, async_loop, delay=0.5) out_format, out_width, out_height = __class__.getImgInfo(tmp_filepath) self.assertEqual(out_format, format) self.assertLessEqual(out_width, size * (100 + size_tolerance) / 100) self.assertGreaterEqual(out_width, size * (100 - size_tolerance) / 100) self.assertLessEqual(out_height, size * (100 + size_tolerance) / 100) self.assertGreaterEqual(out_height, size * (100 - size_tolerance) / 100)
def getCoverArt(self, artist, album): # search and download threadID = str(threading.get_ident()) print('Thread ID: ', threadID) coroutine = sacad.search_and_download(album, artist, SUPPORTED_IMG_FORMATS["jpg"], 400, "/tmp/cover.jpg", size_tolerance_prct=25, amazon_tlds=["co.uk"], no_lq_sources=False, async_loop=self.async_loop) future = asyncio.ensure_future(coroutine, loop=self.async_loop) self.async_loop.run_until_complete(future)
def test_getMasterOfPuppetsCover(self): """ Search and download cover for 'Master of Puppets' with different parameters. """ async_loop = asyncio.get_event_loop() for format in sacad.cover.CoverImageFormat: for size in (300, 600, 1200): for size_tolerance in (0, 25, 50): with self.subTest(format=format, size=size, size_tolerance=size_tolerance): with sacad.mkstemp_ctx.mkstemp( prefix="sacad_test_", suffix=".%s" % (format.name.lower())) as tmp_filepath: coroutine = sacad.search_and_download( "Master of Puppets", "Metallica", format, size, tmp_filepath, size_tolerance_prct=size_tolerance, amazon_tlds=(), no_lq_sources=False, async_loop=async_loop) sched_and_run(coroutine, async_loop, delay=0.5) out_format, out_width, out_height = __class__.getImgInfo( tmp_filepath) self.assertEqual(out_format, format) self.assertLessEqual( out_width, size * (100 + size_tolerance) / 100) self.assertGreaterEqual( out_width, size * (100 - size_tolerance) / 100) self.assertLessEqual( out_height, size * (100 + size_tolerance) / 100) self.assertGreaterEqual( out_height, size * (100 - size_tolerance) / 100)
def downloadAlbumArt(album: str, artist: str, is_single: bool = False): try: if is_single: fileName = f'{slugify(artist)} - {slugify(album)} - Single' else: fileName = f'{slugify(artist)} - {slugify(album)}' success = asyncio.get_event_loop().run_until_complete( sacad.search_and_download( album, # Album name artist, # Artist name "png", # File format, or None if you don't care 1024, # Preferred album size path.expanduser( f'~/Music/Albums/{fileName}.png'), # Output path size_tolerance_prct=25, is_single=is_single)) return success except Exception as e: print( f'ERROR! findAlbumArt.py - downloadAlbumArt. Passed in album: {album}, artist: {artist}. Error: {e}' ) return False
def fetch_media_art(self): if self.art_path is not None: return self.art_path if (self.meta["Artist"] is None) or (self.meta["Album"] is None): return None artdir = "artlib" path_concat = os.path.join("static", artdir) if not os.path.isdir(path_concat): os.makedirs(path_concat) filename = self.meta["Artist"] + "-" + self.meta["Album"] filename = unicode(unicodedata.normalize('NFKD', filename).encode('ascii', 'ignore')) filename = unicode(re.sub('[^\w\s-]', '', filename).strip().lower()) filename = unicode(re.sub('[-\s]+', '-', filename)) + ".jpg" path_concat = os.path.join(path_concat, filename) if os.path.isfile(path_concat): self.art_path = os.path.join(artdir, filename) return self.art_path if sacad.search_and_download( self.meta["Artist"], self.meta["Album"], sacad.cover.CoverImageFormat.JPEG, 600, 25, [], False, path_concat): self.art_path = os.path.join(artdir, filename) return self.art_path else: return None
def get_covers(work, args): """ Get missing covers. """ with contextlib.ExitStack() as cm: if args.filename == EMBEDDED_ALBUM_ART_SYMBOL: tmp_prefix = "%s_" % (os.path.splitext( os.path.basename(inspect.getfile(inspect.currentframe())))[0]) tmp_dir = cm.enter_context( tempfile.TemporaryDirectory(prefix=tmp_prefix)) # setup progress report stats = collections.OrderedDict( ((k, 0) for k in ("ok", "errors", "no result found"))) errors = [] not_found = [] with tqdm.tqdm(total=len(work), miniters=1, desc="Searching covers", unit=" covers", postfix=stats) as progress: def update_progress(future): path, cover_filepath, artist, album = futures[future] try: status = future.result() except Exception as exception: stats["errors"] += 1 errors.append((path, artist, album, exception)) else: if status: if args.filename == EMBEDDED_ALBUM_ART_SYMBOL: try: embed_album_art(cover_filepath, path) except Exception as exception: stats["errors"] += 1 errors.append((path, artist, album, exception)) else: stats["ok"] += 1 finally: os.remove(cover_filepath) else: stats["ok"] += 1 else: stats["no result found"] += 1 not_found.append((path, artist, album)) progress.set_postfix(stats) progress.update(1) # post work async_loop = asyncio.get_event_loop() i = 0 # default event loop on Windows has a 512 fd limit, see https://docs.python.org/3/library/asyncio-eventloops.html#windows # also on Linux default max open fd limit is 1024 (ulimit -n) # 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 for work_chunk in ichunk(work.items(), work_chunk_length): futures = {} for i, (path, (artist, album)) in enumerate(work_chunk, i): if args.filename == EMBEDDED_ALBUM_ART_SYMBOL: cover_filepath = os.path.join( tmp_dir, "%00u.%s" % (i, args.format.name.lower())) else: cover_filepath = os.path.join(path, args.filename) coroutine = sacad.search_and_download( album, artist, args.format, args.size, cover_filepath, size_tolerance_prct=args.size_tolerance_prct, amazon_tlds=args.amazon_tlds, no_lq_sources=args.no_lq_sources, async_loop=async_loop) future = asyncio.ensure_future(coroutine, loop=async_loop) futures[future] = (path, cover_filepath, artist, album) for future in futures: future.add_done_callback(update_progress) # wait for end of work root_future = asyncio.gather(*futures.keys(), loop=async_loop) async_loop.run_until_complete(root_future) # report accumulated errors for path, artist, album in not_found: print("Unable to find cover for '%s' by '%s' from '%s'" % (album, artist, path)) for path, artist, album, exception in errors: print( "Error occured while handling cover for '%s' by '%s' from '%s': %s %s" % (album, artist, path, exception.__class__.__qualname__, exception)) if errors: print("Please report this at https://github.com/desbma/sacad/issues")
def get_covers(work, args): """ Get missing covers. """ with contextlib.ExitStack() as cm: if args.cover_pattern == EMBEDDED_ALBUM_ART_SYMBOL: tmp_prefix = "%s_" % (os.path.splitext( os.path.basename(inspect.getfile(inspect.currentframe())))[0]) tmp_dir = cm.enter_context( tempfile.TemporaryDirectory(prefix=tmp_prefix)) # setup progress report stats = collections.OrderedDict( ((k, 0) for k in ("ok", "errors", "no result found"))) progress = cm.enter_context( tqdm.tqdm(total=len(work), miniters=1, desc="Searching covers", unit="cover", postfix=stats)) cm.enter_context(tqdm_logging.redirect_logging(progress)) def post_download(future): work = futures[future] try: status = future.result() except Exception as exception: stats["errors"] += 1 logging.getLogger("sacad_r").error( "Error occured while searching %s: " "%s %s" % (work, exception.__class__.__qualname__, exception)) else: if status: if work.cover_filepath == EMBEDDED_ALBUM_ART_SYMBOL: try: embed_album_art(work.tmp_cover_filepath, work.audio_filepaths) except Exception as exception: stats["errors"] += 1 logging.getLogger("sacad_r").error( "Error occured while embedding %s: " "%s %s" % (work, exception.__class__.__qualname__, exception)) else: stats["ok"] += 1 finally: os.remove(work.tmp_cover_filepath) else: stats["ok"] += 1 else: stats["no result found"] += 1 logging.getLogger("sacad_r").warning("Unable to find %s" % (work)) progress.set_postfix(stats, refresh=False) progress.update(1) # post work i = 0 # default event loop on Windows has a 512 fd limit, see https://docs.python.org/3/library/asyncio-eventloops.html#windows # also on Linux default max open fd limit is 1024 (ulimit -n) # 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 = 4 if sys.platform.startswith("win") else 12 for work_chunk in ichunk(work, work_chunk_length): futures = {} for i, cur_work in enumerate(work_chunk, i): if cur_work.cover_filepath == EMBEDDED_ALBUM_ART_SYMBOL: cover_filepath = os.path.join( tmp_dir, "%00u.%s" % (i, args.format.name.lower())) cur_work.tmp_cover_filepath = cover_filepath else: cover_filepath = cur_work.cover_filepath os.makedirs(os.path.dirname(cover_filepath), exist_ok=True) coroutine = sacad.search_and_download( cur_work.metadata.album, cur_work.metadata.artist, args.format, args.size, cover_filepath, size_tolerance_prct=args.size_tolerance_prct, amazon_tlds=args.amazon_tlds, no_lq_sources=args.no_lq_sources, preserve_format=args.preserve_format) future = asyncio.ensure_future(coroutine) futures[future] = cur_work for future in futures: future.add_done_callback(post_download) # wait for end of work root_future = asyncio.gather(*futures.keys()) asyncio.get_event_loop().run_until_complete(root_future)
def get_covers(work, args): """ Get missing covers. """ with contextlib.ExitStack() as cm: if args.filename == EMBEDDED_ALBUM_ART_SYMBOL: tmp_prefix = "%s_" % (os.path.splitext(os.path.basename(inspect.getfile(inspect.currentframe())))[0]) tmp_dir = cm.enter_context(tempfile.TemporaryDirectory(prefix=tmp_prefix)) # setup progress report stats = collections.OrderedDict(((k, 0) for k in("ok", "errors", "no result found"))) progress = cm.enter_context(tqdm.tqdm(total=len(work), miniters=1, desc="Searching covers", unit="cover", postfix=stats)) cm.enter_context(tqdm_logging.redirect_logging(progress)) def update_progress(future): path, cover_filepath, artist, album = futures[future] try: status = future.result() except Exception as exception: stats["errors"] += 1 logging.getLogger("sacad_r").error("Error occured while searching cover for " "'%s' by '%s' from '%s': %s %s" % (album, artist, path, exception.__class__.__qualname__, exception)) else: if status: if args.filename == EMBEDDED_ALBUM_ART_SYMBOL: try: embed_album_art(cover_filepath, path) except Exception as exception: stats["errors"] += 1 logging.getLogger("sacad_r").error("Error occured while embedding cover for " "'%s' by '%s' from '%s': %s %s" % (album, artist, path, exception.__class__.__qualname__, exception)) else: stats["ok"] += 1 finally: os.remove(cover_filepath) else: stats["ok"] += 1 else: stats["no result found"] += 1 logging.getLogger("sacad_r").warning("Unable to find cover for '%s' by '%s' from '%s'" % (album, artist, path)) progress.set_postfix(stats, refresh=False) progress.update(1) # post work async_loop = asyncio.get_event_loop() i = 0 # default event loop on Windows has a 512 fd limit, see https://docs.python.org/3/library/asyncio-eventloops.html#windows # also on Linux default max open fd limit is 1024 (ulimit -n) # 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 for work_chunk in ichunk(work.items(), work_chunk_length): futures = {} for i, (path, (artist, album)) in enumerate(work_chunk, i): if args.filename == EMBEDDED_ALBUM_ART_SYMBOL: cover_filepath = os.path.join(tmp_dir, "%00u.%s" % (i, args.format.name.lower())) else: cover_filepath = os.path.join(path, args.filename) coroutine = sacad.search_and_download(album, artist, args.format, args.size, cover_filepath, size_tolerance_prct=args.size_tolerance_prct, amazon_tlds=args.amazon_tlds, no_lq_sources=args.no_lq_sources, async_loop=async_loop) future = asyncio.ensure_future(coroutine, loop=async_loop) futures[future] = (path, cover_filepath, artist, album) for future in futures: future.add_done_callback(update_progress) # wait for end of work root_future = asyncio.gather(*futures.keys(), loop=async_loop) async_loop.run_until_complete(root_future)