示例#1
0
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")))
    with tqdm.tqdm(desc="Analyzing library",
                   unit="dir",
                   postfix=stats) as progress, \
            tqdm_logging.redirect_logging(progress):
        for rootpath, rel_dirpaths, rel_filepaths in os.walk(lib_dir):
            new_work = analyze_dir(stats,
                                   rootpath,
                                   rel_filepaths,
                                   cover_pattern,
                                   ignore_existing=ignore_existing,
                                   full_scan=full_scan,
                                   all_formats=all_formats)
            progress.set_postfix(stats, refresh=False)
            progress.update(1)
            work.extend(new_work)
    return work
示例#2
0
文件: recurse.py 项目: desbma/sacad
def analyze_lib(lib_dir, cover_filename, *, ignore_existing=False):
  """ Recursively analyze library, and return a dict of path -> (artist, album). """
  work = {}
  stats = collections.OrderedDict(((k, 0) for k in("files", "albums", "missing covers", "errors")))
  with tqdm.tqdm(desc="Analyzing library",
                 unit="dir",
                 postfix=stats) as progress, \
          tqdm_logging.redirect_logging(progress):
    for rootpath, rel_dirpaths, rel_filepaths in os.walk(lib_dir):
      metadata = analyze_dir(stats,
                             rootpath,
                             rel_filepaths,
                             cover_filename,
                             ignore_existing=ignore_existing)
      progress.set_postfix(stats, refresh=False)
      progress.update(1)
      if all(metadata[:-1]):
        work[rootpath] = metadata[:-1]
  return work
示例#3
0
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)
示例#4
0
文件: recurse.py 项目: desbma/sacad
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)