Example #1
0
def syncImage(img, current, session):
    """Add or updated the Image."""
    def _img_str(i):
        return "%s - %s" % (i.type, i.description)

    for db_img in current.images:
        img_info = (img.type, img.md5, img.size)
        db_img_info = (db_img.type, db_img.md5, db_img.size)

        if db_img_info == img_info:
            img = None
            break
        elif (db_img.type == img.type
              and db_img.description == img.description):

            if img.md5 != db_img.md5:
                # Update image
                current.images.remove(db_img)
                current.images.append(img)
                session.add(current)
                pout(Fg.green("Updating image") + ": " + _img_str(img))
            img = None
            break

    if img:
        # Add image
        current.images.append(img)
        session.add(current)
        pout(Fg.green("Adding image") + ": " + _img_str(img))
Example #2
0
def syncImage(img, current, session):
    """Add or updated the Image."""
    def _img_str(i):
        return "%s - %s" % (i.type, i.description)

    for db_img in current.images:
        img_info = (img.type, img.md5, img.size)
        db_img_info = (db_img.type, db_img.md5, db_img.size)

        if db_img_info == img_info:
            img = None
            break
        elif (db_img.type == img.type and
                db_img.description == img.description):

            if img.md5 != db_img.md5:
                # Update image
                current.images.remove(db_img)
                current.images.append(img)
                session.add(current)
                pout(Fg.green("Updating image") + ": " + _img_str(img))
            img = None
            break

    if img:
        # Add image
        current.images.append(img)
        session.add(current)
        pout(Fg.green("Adding image") + ": " + _img_str(img))
Example #3
0
 def _syncLib(lib):
     args._library = lib
     args.paths = []
     for p in lib.paths:
         args.paths.append(str(p) if isinstance(p, Path) else p)
     pout("{}yncing library '{}': paths={}".format(
         "Force s" if args.force else "S", lib.name, lib.paths),
          log=log)
     return eyed3_main(args, None)
Example #4
0
def test_plog(capfd):
    msg = "Xymox - Phoenix"

    log = logging.getLogger("test_plog")
    with patch.object(log, "info") as mock:
        pout(msg, log=log)
    out, _ = capfd.readouterr()
    assert out == msg + "\n"
    mock.assert_called_once_with(msg)
Example #5
0
def deleteOrphans(session):
    num_orphaned_artists = 0
    num_orphaned_albums = 0
    num_orphaned_tracks = 0
    found_ids = set()

    # Tracks
    for track in session.query(Track).all():
        if not os.path.exists(track.path):
            pout(Fg.red("Removing track") + ": " + track.path)
            session.delete(track)
            num_orphaned_tracks += 1
            log.warn("Deleting track: %s" % str(track))

    session.flush()

    # Artists
    found_ids.clear()
    for artist in session.query(Artist).all():
        if (artist.id == VARIOUS_ARTISTS_ID or artist.id in found_ids):
            continue

        any_track = session.query(Track).filter(Track.artist_id == artist.id) \
                                        .first()
        any_album = session.query(Album).filter(Album.artist_id == artist.id) \
                                        .first()
        if not any_track and not any_album:
            log.warn("Deleting artist: %s" % str(artist))
            session.delete(artist)
            num_orphaned_artists += 1
        else:
            found_ids.add(artist.id)

    session.flush()

    # Albums
    found_ids.clear()
    for album in session.query(Album).all():
        if album.id in found_ids:
            continue

        any_track = session.query(Track).filter(Track.album_id == album.id) \
                                        .first()
        if not any_track:
            log.warn("Deleting album: %s" % str(album))
            session.delete(album)
            num_orphaned_albums += 1
        else:
            found_ids.add(album.id)

    return (num_orphaned_tracks, num_orphaned_artists, num_orphaned_albums)
Example #6
0
def deleteOrphans(session):
    num_orphaned_artists = 0
    num_orphaned_albums = 0
    num_orphaned_tracks = 0
    found_ids = set()

    # Tracks
    for track in session.query(Track).all():
        if not os.path.exists(track.path):
            pout(Fg.red("Removing track") + ": " + track.path)
            session.delete(track)
            num_orphaned_tracks += 1
            log.warn("Deleting track: %s" % str(track))
    session.flush()

    # Albums
    found_ids.clear()
    for album in session.query(Album).all():
        if album.id in found_ids:
            continue

        any_track = session.query(Track).filter(Track.album_id == album.id).first()
        if not any_track:
            log.warn("Deleting album: %s" % str(album))
            session.delete(album)
            num_orphaned_albums += 1
        else:
            found_ids.add(album.id)
    session.flush()

    # Artists
    found_ids.clear()
    for artist in session.query(Artist).all():
        if (artist.id == VARIOUS_ARTISTS_ID or
                artist.id in found_ids):
            continue

        any_track = session.query(Track).filter(Track.artist_id == artist.id) \
                                        .first()
        any_album = session.query(Album).filter(Album.artist_id == artist.id) \
                                        .first()
        if not any_track and (not any_album or not any_album.tracks):
            log.warn("Deleting artist: %s" % str(artist))
            session.delete(artist)
            num_orphaned_artists += 1
        else:
            found_ids.add(artist.id)
    session.flush()

    return (num_orphaned_tracks, num_orphaned_artists, num_orphaned_albums)
Example #7
0
    def handleDirectory(self, d, _):
        pout(Fg.blue("Syncing directory") + ": " + str(d))
        audio_files = list(self._file_cache)
        self._file_cache = []

        image_files = self._dir_images
        self._dir_images = []

        if not audio_files:
            return

        d_datetime = datetime.fromtimestamp(getctime(d))

        album_type = self._albumTypeHint(audio_files) or LP_TYPE

        album = None
        session = self._db_session
        for audio_file in audio_files:
            try:
                album = self._syncAudioFile(audio_file, album_type, d_datetime,
                                            session)
            except Exception as ex:
                # TODO: log and skip????
                raise

        if album:
            # Directory images.
            for img_file in image_files:
                img_type = art.matchArtFile(img_file)
                if img_type is None:
                    log.warn("Skipping unrecognized image file: %s" % img_file)
                    continue

                new_img = Image.fromFile(img_file, img_type)
                if new_img:
                    new_img.description = os.path.basename(img_file)
                    syncImage(
                        new_img, album if img_type in IMAGE_TYPES["album"] else
                        album.artist, session)
                else:
                    log.warn("Invalid image file: " + img_file)

        session.commit()
        if self.args.monitor:
            self._watchDir(d)
Example #8
0
    def _getArtist(self, session, name, resolved_artist):
        artist_rows = session.query(Artist).filter_by(
            name=name, lib_id=self._lib.id).all()
        if artist_rows:
            if len(artist_rows) > 1 and resolved_artist:
                # Use previously resolved artist for this directory.
                artist = resolved_artist
            elif len(artist_rows) > 1:
                # Resolve artist
                try:
                    heading = "Multiple artists names '%s'" % \
                              artist_rows[0].name
                    artist = console.selectArtist(Fg.blue(heading),
                                                  choices=artist_rows,
                                                  allow_create=True)
                except PromptExit:
                    log.warn("Duplicate artist requires user "
                             "intervention to resolve.")
                    artist = None
                else:
                    if artist not in artist_rows:
                        session.add(artist)
                        session.flush()
                        pout(Fg.blue("Updating artist") + ": " + name)
                    resolved_artist = artist
            else:
                # Artist match
                artist = artist_rows[0]
        else:
            # New artist
            artist = Artist(name=name, lib_id=self._lib.id)
            session.add(artist)
            session.flush()
            pout(Fg.green("Adding artist") + ": " + name)

        return artist, resolved_artist
Example #9
0
{{ async }} def main(args):
    pout("\m/")
Example #10
0
    def _run(self, args=None):
        args = args or self.args
        args.plugin = self.plugin

        # FIXME
        # TODO: add CommandException to get rid of return 1 etc at this level

        libs = {lib.name: lib for lib in args.config.music_libs}
        if not libs and not args.paths:
            perr("\nMissing at least one path/library in which to sync!\n")
            self.parser.print_usage()
            return 1

        sync_libs = []
        if args.paths:
            file_paths = []
            for arg in args.paths:
                if arg in libs:
                    # Library name
                    sync_libs.append(libs[arg])
                else:
                    # Path
                    file_paths.append(arg)
            if file_paths:
                sync_libs.append(MusicLibrary(MAIN_LIB_NAME, paths=file_paths))
        else:
            sync_libs = list(libs.values())

        args.db_engine, args.db_session = self.db_engine, self.db_session

        def _syncLib(lib):
            args._library = lib
            args.paths = []
            for p in lib.paths:
                args.paths.append(str(p) if isinstance(p, Path) else p)
            pout("{}yncing library '{}': paths={}".format(
                "Force s" if args.force else "S", lib.name, lib.paths),
                 log=log)
            return eyed3_main(args, None)

        try:
            for lib in sync_libs:
                if not lib.sync and not args.force:
                    pout("[{}] - sync=False".format(lib.name), log=log)
                    continue
                result = _syncLib(lib)
                if result != 0:
                    return result
        except IOError as err:
            perr(str(err))
            return 1

        if args.monitor:
            monitor = self.plugin.monitor_proc

            # Commit now, since we won't be returning
            self.db_session.commit()

            monitor.start()
            while True:
                if monitor.sync_queue.empty():
                    time.sleep(SYNC_INTERVAL / 2)
                    continue

                sync_libs = {}
                for i in range(monitor.sync_queue.qsize()):
                    lib, path = monitor.sync_queue.get_nowait()
                    if lib not in sync_libs:
                        sync_libs[lib] = set()
                    sync_libs[lib].add(path)

                for lib, paths in list(sync_libs.items()):
                    result = _syncLib(MusicLibrary(lib, paths=paths))
                    if result != 0:
                        return result
                    self.db_session.commit()
            monitor.join()
Example #11
0
    def handleDone(self):
        t = time.time() - self.start_time
        session = self._db_session

        session.query(Meta).one().last_sync = datetime.utcnow()
        self._lib.last_sync = datetime.utcnow()

        num_orphaned_artists = 0
        num_orphaned_albums = 0
        if not self.args.no_purge:
            log.debug(
                "Purging orphans (tracks, artists, albums) from database")
            (self._num_deleted, num_orphaned_artists,
             num_orphaned_albums) = deleteOrphans(session)

        if self._num_loaded or self._num_deleted:
            pout("")
            pout("== Library '{}' sync'd [ {:f}s time ({:f} files/s) ] ==".
                 format(self._lib.name, t, self._num_loaded / t))
            pout("%d files sync'd" % self._num_loaded)
            pout("%d tracks added" % self._num_added)
            pout("%d tracks modified" % self._num_modified)
            if not self.args.no_purge:
                pout("%d orphaned tracks deleted" % self._num_deleted)
                pout("%d orphaned artists deleted" % num_orphaned_artists)
                pout("%d orphaned albums deleted" % num_orphaned_albums)
            pout("")
Example #12
0
    def _syncAudioFile(self, audio_file, album_type, d_datetime, session):
        path = audio_file.path
        info = audio_file.info
        tag = audio_file.tag

        album = None
        is_various = (album_type == VARIOUS_TYPE)

        if not info or not tag:
            log.warn("File missing %s, skipping: %s" %
                     ("audio" if not info else "tag/metadata", path))
            return
        elif None in (tag.title, tag.artist):
            log.warn("File missing required artist and/or title "
                     "metadata, skipping: %s" % path)
            return

        # Used when a duplicate artist is resolved for the entire directory.
        resolved_artist = None
        resolved_album_artist = None

        try:
            track = session.query(Track)\
                           .filter_by(path=path, lib_id=self._lib.id).one()
        except NoResultFound:
            track = None
        else:
            if datetime.fromtimestamp(getctime(path)) == track.ctime:
                # Track is in DB and the file is not modified.
                # stash the album though, we'll look for artwork
                # updates later
                album = track.album
                return

        # Either adding the track (track == None)
        # or modifying (track != None)

        artist, resolved_artist = self._getArtist(session, tag.artist,
                                                  resolved_artist)
        if tag.album_type != SINGLE_TYPE:
            if tag.album_artist and tag.artist != tag.album_artist:
                album_artist, resolved_album_artist = \
                        self._getArtist(session, tag.album_artist,
                                        resolved_album_artist)
            else:
                album_artist = artist

            if artist is None:
                # see PromptExit
                return

            album_artist_id = album_artist.id if not is_various \
                                              else VARIOUS_ARTISTS_ID
            album_rows = session.query(Album)\
                                .filter_by(title=tag.album,
                                           lib_id=self._lib.id,
                                           artist_id=album_artist_id).all()
            rel_date = tag.release_date
            rec_date = tag.recording_date
            or_date = tag.original_release_date

            if album_rows:
                if len(album_rows) > 1:
                    # This artist has more than one album with the same
                    # title.
                    raise NotImplementedError("FIXME")
                album = album_rows[0]

                album.type = album_type
                album.release_date = rel_date
                album.original_release_date = or_date
                album.recording_date = rec_date
                pout(Fg.yellow("Updating album") + ": " + album.title)
            elif tag.album:
                album = Album(title=tag.album,
                              lib_id=self._lib.id,
                              artist_id=album_artist_id,
                              type=album_type,
                              release_date=rel_date,
                              original_release_date=or_date,
                              recording_date=rec_date,
                              date_added=d_datetime)
                session.add(album)
                pout(Fg.green("Adding album") + ": " + album.title)

            session.flush()

        if not track:
            track = Track(audio_file=audio_file, lib_id=self._lib.id)
            self._num_added += 1
            pout(Fg.green("Adding track") + ": " + path)
        else:
            track.update(audio_file)
            self._num_modified += 1
            pout(Fg.yellow("Updating track") + ": " + path)

        genre = tag.genre
        genre_tag = None
        if genre:
            try:
                genre_tag = session.query(Tag)\
                                   .filter_by(name=genre.name,
                                              lib_id=self._lib.id).one()
            except NoResultFound:
                genre_tag = Tag(name=genre.name, lib_id=self._lib.id)
                session.add(genre_tag)
                session.flush()

        track.artist_id = artist.id
        track.album_id = album.id if album else None
        if genre_tag:
            track.tags.append(genre_tag)
        session.add(track)

        if album:
            # Tag images
            for img in tag.images:
                for img_type in art.TO_ID3_ART_TYPES:
                    if img.picture_type in \
                            art.TO_ID3_ART_TYPES[img_type]:
                        break
                    img_type = None

                if img_type is None:
                    log.warn("Skipping unsupported image type: %s" %
                             img.picture_type)
                    continue

                new_img = Image.fromTagFrame(img, img_type)
                if new_img:
                    syncImage(
                        new_img, album if img_type in IMAGE_TYPES["album"] else
                        album.artist, session)
                else:
                    log.warn("Invalid image in tag")

        return album
Example #13
0
def test_pout(capfd):
    msg = "There's a war outside!"
    pout(msg)
    out, _ = capfd.readouterr()
    assert out == msg + "\n"