Beispiel #1
0
    def _getArtist(self, session, name, resolved_artist):
        artist_rows = session.query(Artist).filter_by(name=name).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()
                        print(fg.yellow("Updating artist") + ": " + name)
                    resolved_artist = artist
            else:
                # Artist match
                artist = artist_rows[0]
        else:
            # New artist
            artist = Artist(name=name)
            session.add(artist)
            session.flush()
            print(fg.green("Adding artist") + ": " + name)

        return artist, resolved_artist
Beispiel #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)
                print(fg.green("Updating image") + ": " + _img_str(img))
            img = None
            break

    if img:
        # Add image
        current.images.append(img)
        session.add(current)
        print(fg.green("Adding image") + ": " + _img_str(img))
Beispiel #3
0
    def _displayArtistMusic(self, artist, albums, singles):
        if albums:
            print(u"%d albums by %s:" % (len(albums),
                                         Style.bright(fg.blue(artist.name))))
            for alb in albums:
                print(u"%s %s" % (str(alb.getBestDate()).center(17),
                                  alb.title))

        if singles:
            print(u"%d single tracks by %s" %
                  (len(singles), Style.bright(fg.blue(artist.name))))
            for s in singles:
                print(u"\t%s" % (s.title))
Beispiel #4
0
def promptArtist(text, name=None, default_name=None, default_city=None,
                 default_state=None, default_country=None, artist=None):
    if text:
        print(text)

    if name is None:
        name = prompt(fg.green("Artist name"), default=default_name)

    origin = {}
    for o in ("city", "state", "country"):
        origin["origin_%s" % o] = prompt("   %s" % fg.green(o.title()),
                                         default=locals()["default_%s" % o],
                                         required=False)

    if not artist:
        artist = Artist(name=name, **origin)
    else:
        artist.name = name
        for o in origin:
            setattr(artist, o, origin[o])
    return artist
Beispiel #5
0
def selectArtist(heading, choices=None, multiselect=False, allow_create=True):
    color = fg.green
    artist = None
    name = None

    if heading:
        print(heading)

    while artist is None:
        if choices:
            name = choices[0].name
            for menu_num, a in enumerate(choices):
                print("   %d) %s" % (menu_num + 1, a.origin()))
            menu_num += 1

            if not multiselect:
                if allow_create:
                    menu_num += 1
                    print("   %d) Enter a new artist" % menu_num)

                choice = prompt("Which artist", type_=int,
                                choices=range(1, menu_num + 1))
                choice -= 1
                if choice < len(choices):
                    artist = choices[choice]
                # Otherwise fall through to select artist below
            else:
                def _validate(_resp):
                    try:
                        _ints = [_i for _i in parseIntList(_resp)
                                    if _i in range(1, menu_num + 1)]
                        return bool(_ints)
                    except:
                        return False

                resp = prompt(color("Choose one or more artists"),
                              validate=_validate)
                artists = []
                for choice in [i - 1 for i in parseIntList(resp)]:
                    artists.append(choices[choice])
                # XXX: blech, returning a list here and a single value below
                return artists

        if artist is None:
            artist = promptArtist(None, name=name)
            if choices:
                if not Artist.checkUnique(choices + [artist]):
                    print(fg.red("Artist entered is not unique, try again..."))
                    artist = None

    assert(artist)
    return artist
Beispiel #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):
            print(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)
Beispiel #7
0
def _pErr(subject, msg):
    print(fg.red(subject) + ": %s" % str(msg))
Beispiel #8
0
    def _run(self):
        session = self.db_session

        artists = session.query(Artist)\
                         .filter(Artist.name == self.args.artist).all()
        if not artists:
            print(u"Artist not found: %s" % self.args.artist)
            return 1
        elif len(artists) > 1:
            artist = selectArtist(fg.blue("Select which '%s' to split...") %
                                  artists[0].name,
                                  choices=artists, allow_create=False)
        else:
            artist = artists[0]

        # Albums by artist
        albums = list(artist.albums) + artist.getAlbumsByType(VARIOUS_TYPE)
        # Singles by artist and compilations the artist appears on
        singles = artist.getTrackSingles()

        if len(albums) < 2 and len(singles) < 2:
            print("%d albums and %d singles found for '%s', nothing to do." %
                    (len(albums), len(singles), artist.name))
            return 0

        self._displayArtistMusic(artist, albums, singles)

        def _validN(_n):
            return _n > 1 and _n <= len(albums)
        n = prompt("\nEnter the number of distinct artists", type_=int,
                   validate=_validN)
        new_artists = []
        for i in range(1, n + 1):
            print(Style.bright(u"\n%s #%d") % (fg.blue(artist.name), i))

            # Reuse original artist for first
            a = artist if i == 1 else Artist(name=artist.name,
                                             date_added=artist.date_added)
            a.origin_city = prompt("   City", required=False)
            a.origin_state = prompt("   State", required=False)
            a.origin_country = prompt("   Country", required=False,
                                      type_=normalizeCountry)

            new_artists.append(a)

        if not Artist.checkUnique(new_artists):
            print(fg.red("Artists must be unique."))
            return 1

        for a in new_artists:
            session.add(a)

        # New Artist objects need IDs
        session.flush()

        print(Style.bright("\nAssign albums to the correct artist."))
        for i, a in enumerate(new_artists):
            print("Enter %s%d%s for %s from %s%s%s" %
                  (Style.BRIGHT, i + 1, Style.RESET_BRIGHT,
                  a.name,
                  Style.BRIGHT, a.origin(country_code="iso3c",
                                         title_case=False),
                  Style.RESET_BRIGHT))

        # prompt for correct artists
        def _promptForArtist(_text):
            a = prompt(_text, type_=int,
                       choices=range(1, len(new_artists) + 1))
            return new_artists[a - 1]

        print("")
        for alb in albums:
            # Get some of the path to help the decision
            path = commonDirectoryPrefix(*[t.path for t in alb.tracks])
            path = os.path.join(*path.split(os.sep)[-2:])

            a = _promptForArtist("%s (%s)" % (alb.title, path))
            if alb.type != "various":
                alb.artist_id = a.id
            for track in alb.tracks:
                if track.artist_id == artist.id:
                    track.artist_id = a.id

        print("")
        for track in singles:
            a = _promptForArtist(track.title)
            track.artist_id = a.id

        session.flush()
Beispiel #9
0
    def _run(self):
        session = self.db_session

        merge_list = []
        for artist_arg in self.args.artists:
            artists = session.query(Artist)\
                             .filter(Artist.name == artist_arg).all()
            if len(artists) == 1:
                merge_list.append(artists[0])
            elif len(artists) > 1:
                merge_list += selectArtist(
                        fg.blue("Select the artists to merge..."),
                        multiselect=True, choices=artists)

        if len(merge_list) > 1:
            # Reuse lowest id
            artist_ids = {a.id: a for a in merge_list}
            min_id = min(*artist_ids.keys())
            artist = artist_ids[min_id]

            mc = mostCommonItem
            new_artist = promptArtist(
                    "Merging %d artists into new artist..." % len(merge_list),
                    default_name=mc([a.name for a in merge_list]),
                    default_city=mc([a.origin_city for a in merge_list]),
                    default_state=mc([a.origin_state for a in merge_list]),
                    default_country=mc([a.origin_country for a in merge_list]),
                    artist=artist)
        else:
            print("Nothing to do, %s" %
                    ("artist not found" if not len(merge_list)
                                        else "only one artist found"))
            return 1

        assert(new_artist in merge_list)

        for artist in merge_list:
            if artist is new_artist:
                continue

            with session.no_autoflush:
                for alb in list(artist.albums):
                    # FIXME: use constant
                    if alb.type != "various":
                        alb.artist_id = new_artist.id
                        artist.albums.remove(alb)
                        with session.no_autoflush:
                            new_artist.albums.append(alb)

                    for track in alb.tracks:
                        if track.artist_id == artist.id:
                            # gotta check in case alb is type various
                            track.artist_id = new_artist.id

                for track in artist.getTrackSingles():
                    track.artist_id = new_artist.id

            # flush to get new artist ids in sync before delete, otherwise
            # cascade happens.
            session.flush()
            session.delete(artist)

            session.flush()
Beispiel #10
0
    def handleDirectory(self, 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))

        # This directory of files can be:
        # 1) an album by a single artist (tag.artist, or tag.albun_srtist and
        #    tag.album all equal)
        # 2) a comp (tag.album equal, tag.artist differ)
        # 3) not associated with a collection (tag.artist and tag.album differ)
        artists = set([f.tag.artist for f in audio_files if f.tag])
        album_artists = set([f.tag.album_artist for f in audio_files if f.tag])
        albums = set([f.tag.album for f in audio_files if f.tag])
        for s in artists, album_artists, albums:
            if None in s:
                s.remove(None)

        is_various = (len(artists) > 1 and len(album_artists) == 0 and
                      len(albums) == 1)

        def type_hint():
            hints = set()
            for tag in [f.tag for f in audio_files if f.tag]:
                hint_frame = tag.user_text_frames.get(TXXX_ALBUM_TYPE)
                if hint_frame:
                    hints.add(hint_frame.text)
            if len(hints) > 1:
                log.warn("Inconsistent type hints: %s" % str(hints))
                return None
            else:
                return hints.pop() if hints else None

        album_type = type_hint() or LP_TYPE
        if is_various and album_type not in (None, VARIOUS_TYPE):
            # is_various overrides
            log.warn("Using type various despite files saying %s" % album_type)
        album_type = VARIOUS_TYPE if is_various else album_type

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

        album = None
        session = self._db_session
        for audio_file in audio_files:
            path = audio_file.path
            info = audio_file.info
            tag = audio_file.tag

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

            try:
                track = session.query(Track).filter_by(path=path).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
                    continue

            # 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
                    continue

                album_artist_id = album_artist.id if not is_various \
                                                  else VARIOUS_ARTISTS_ID
                album_rows = session.query(Album)\
                                    .filter_by(title=tag.album,
                                               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
                    print(fg.yellow("Updating album") + ": " + album.title)
                elif tag.album:
                    album = Album(title=tag.album,
                                  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)
                    print(fg.green("Adding album") + ": " + album.title)

                session.flush()

            if not track:
                track = Track(audio_file=audio_file)
                self._num_added += 1
                print(fg.green("Adding track") + ": " + path)
            else:
                track.update(audio_file)
                self._num_modified += 1
                print(fg.yellow("Updating track") + ": " + path)

            genre = tag.genre
            genre_tag = None
            if genre:
                try:
                    genre_tag = session.query(Tag).filter_by(name=genre.name)\
                                       .one()
                except NoResultFound:
                    genre_tag = Tag(name=genre.name)
                    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")

        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)