Example #1
0
    def track(self, slug):
        track = library_dao.get_track_by_slug(slug)

        if track is None:
            raise cherrypy.NotFound()

        remotes.update_track(track)

        remotes_album = remotes_artist = None

        if track.album is not None:
            remotes.update_album(track.album)
            remotes_album = remotes.get_album(track.album)

        if track.artist is not None:
            remotes.update_artist(track.artist)
            remotes_artist = remotes.get_artist(track.artist)

        remotes_track = remotes.get_track(track)

        if track.artist is not None:
            artist_listened_tuples = self.get_artist_listened_tuples(track.artist.name)

            if track.album is not None:
                album_listened_tuples = self.get_album_listened_tuples(track.artist.name, track.album.name)
            else:
                album_listened_tuples = None
        else:
            artist_listened_tuples = None
            album_listened_tuples = None

        return {
            'track': track,
            'remotes_artist': remotes_artist,
            'remotes_album': remotes_album,
            'remotes_track': remotes_track,
            'album_listened_tuples': album_listened_tuples,
            'artist_listened_tuples': artist_listened_tuples,
        }
Example #2
0
    def get_cover(self, type, slug, size="default"):
        if type not in ["album", "artist"]:
            raise ValueError("Invalid type %s supplied" % type)

        entity = None

        if type == "album":
            entity = library_dao.get_album_by_slug(slug)

            if entity is None:
                raise ValueError("Entity not found")

            remotes.update_album(entity)

            if entity.cover_path is None or not os.path.exists(entity.cover_path):
                try:
                    cherrypy.engine.bgtask.put_unique(self.fetch_album_cover, 15, entity.id)
                except NonUniqueQueueError:
                    pass

        elif type == "artist":
            entity = library_dao.get_artist_by_slug(slug)

            if entity is None:
                raise ValueError("Entity not found")

            remotes.update_artist(entity)

            if entity.cover_path is None or not os.path.exists(entity.cover_path):
                try:
                    cherrypy.engine.bgtask.put_unique(self.fetch_artist_cover, 15, entity.id)
                except NonUniqueQueueError:
                    pass

        if entity is None:
            raise ValueError("Entity not found")

        if entity.cover_path is not None:
            if entity.cover is None:
                cover_ext = os.path.splitext(entity.cover_path)[1].decode("utf8")
                temp_cover = self._mktemp(cover_ext).encode("utf8")
                temp_cover_large = self._mktemp(cover_ext).encode("utf8")

                cover = image_service.resize(
                    entity.cover_path, temp_cover, Covers.DEFAULT_WIDTH, Covers.DEFAULT_HEIGHT, Covers.DEFAULT_GRAVITY
                )

                large_offset = self._get_image_offset(Covers.LARGE_WIDTH, Covers.LARGE_HEIGHT, Covers.LARGE_GRAVITY)

                cover_large = image_service.resize(
                    entity.cover_path,
                    temp_cover_large,
                    Covers.LARGE_WIDTH,
                    Covers.LARGE_HEIGHT,
                    Covers.LARGE_GRAVITY,
                    large_offset,
                )

                if cover and cover_large:
                    import mmh3

                    with open(temp_cover, "rb") as file:
                        entity.cover = file.read()
                        entity.cover_hash = base64.b64encode(mmh3.hash_bytes(entity.cover))

                    with open(temp_cover_large, "rb") as file:
                        entity.cover_large = file.read()

                    os.remove(temp_cover)
                    os.remove(temp_cover_large)

                    get_database().commit()

            return self.guess_mime(entity), entity.cover_large if size == "large" else entity.cover

        return None, None
Example #3
0
    def artist(self, slug):
        artist = library_dao.get_artist_by_slug(slug)

        if artist is None:
            raise cherrypy.NotFound()

        album_group_order = {
            'default': 0,
            'ep': 1,
            'split': 2,
            'live': 3,
            'va': 4
        }

        album_groups = {}

        remotes.update_artist(artist)

        for album in artist.albums:
            if re.search(r'\blive\b', album.name.lower()):
                if 'live' not in album_groups:
                    album_groups['live'] = {
                        'title': 'Live',
                        'albums': []
                    }

                album_groups['live']['albums'].append(album)
            elif album.is_split:
                if 'split' not in album_groups:
                    album_groups['split'] = {
                        'title': 'Splits',
                        'albums': []
                    }

                album_groups['split']['albums'].append(album)
            elif album.is_va:
                if 'va' not in album_groups:
                    album_groups['va'] = {
                        'title': 'Various Artists',
                        'albums': []
                    }

                album_groups['va']['albums'].append(album)
            elif album.is_ep:
                if 'ep' not in album_groups:
                    album_groups['ep'] = {
                        'title': 'Singles & EPs',
                        'albums': []
                    }

                album_groups['ep']['albums'].append(album)
            else:
                if 'default' not in album_groups:
                    album_groups['default'] = {
                        'title': 'Albums',
                        'albums': []
                    }

                album_groups['default']['albums'].append(album)

            remotes.update_album(album)

        album_groups = OrderedDict(sorted(album_groups.items(),
                                   key=lambda album_group: album_group_order[album_group[0]]))

        remotes_artist = remotes.get_artist(artist)

        same_artists = set()

        for artist_result in search.query_artist(artist.name, exact_metaphone=True):
            if artist != artist_result:
                same_artists.add(artist_result)

        dir_tracks = self._dir_tracks(artist.no_album_tracks)

        remotes_user = remotes.get_user(cherrypy.request.user)

        artist_listened_tuples = self.get_artist_listened_tuples(artist.name)

        return {
            'remotes_user': remotes_user,
            'dir_tracks': dir_tracks,
            'artist': artist,
            'album_groups': album_groups,
            'remotes_artist': remotes_artist,
            'same_artists': same_artists,
            'artist_listened_tuples': artist_listened_tuples
        }
Example #4
0
    def albums(self, view=None, sort=None, filter=None, filter_value=None, page=None):
        if view is None:
            view = "covers"

        if sort is None:
            sort = "created"

        if filter is None:
            filter = "none"

        if filter_value is None:
            filter_value = ""

        if page is None:
            page = 1

        page = int(page)

        page_size = 24

        offset = page_size * (page - 1)

        query = (get_database()
                 .query(Album)
                 .join(Track, Album.id == Track.album_id)
                 .filter(Track.scanned)
                 .group_by(Album.id))

        albums = []

        if filter == "yours":
            remotes_user = remotes.get_user(cherrypy.request.user)

            album_ids = []

            if remotes_user is not None and remotes_user['lastfm'] is not None:
                for album in remotes_user['lastfm']['top_albums_overall']:
                    album_results = search.query_album(album['name'], exact=True)

                    if len(album_results) > 0:
                        album_ids.append(album_results[0].id)

            query = query.filter(Album.id.in_(album_ids))
        elif filter == "1year":
            now = datetime.datetime.utcnow()
            query = query.filter(Album.created > now - datetime.timedelta(days=365))
        elif filter == "va":
            query = (query.join(Artist, Artist.id == Track.artist_id)
                          .having(func.count(distinct(Artist.id)) > 1))
        elif filter == "invalid":
            query = query.filter("invalid is not null")
        elif filter == "tag":
            album_ids = []

            if filter_value != "":
                remotes.update_tag(filter_value)
                remotes_tag = remotes.get_tag(filter_value)

                if remotes_tag is not None and remotes_tag['lastfm'] is not None:
                    for album in remotes_tag['lastfm']['albums']:
                        album_results = search.query_album(album['name'], exact=True)

                        if len(album_results) > 0:
                            album_ids.append(album_results[0].id)

            query = query.filter(Album.id.in_(album_ids))

        # count before adding order_by() for performance reasons..
        total = query.count()
        pages = math.ceil(total / page_size)

        if sort == "created":
            query = query.order_by(Album.created.desc())
        elif sort == "updated":
            query = query.order_by(Album.updated.desc())
        elif sort == "seen":
            query = (query.outerjoin(UserAndAlbum, and_(Album.id == UserAndAlbum.album_id,
                                     UserAndAlbum.user_id == cherrypy.request.user.id))
                     .order_by(UserAndAlbum.seen.desc())
                     .order_by(Album.updated.desc()))
        elif sort == "date":
            query = (query
                     .order_by(Album.date.desc())
                     .order_by(Album.updated.desc()))
        elif sort == "random":
            query = query.order_by(func.rand())
            page = None

        albums = query.limit(page_size).offset(offset).all()

        for album in albums:
            remotes.update_album(album)

            for artist in album.artists:
                remotes.update_artist(artist)

        return {
            'albums': albums,
            'page': page,
            'page_size': page_size,
            'total': total,
            'sort': sort,
            'filter': filter,
            'filter_value': filter_value,
            'pages': pages,
            "view": view
        }
Example #5
0
    def artists(self, sort=None, filter=None, filter_value=None, page=None):
        if sort is None:
            sort = "created"

        if filter is None:
            filter = "none"

        if filter_value is None:
            filter_value = ""

        if page is None:
            page = 1

        page = int(page)

        page_size = 24

        offset = page_size * (page - 1)

        query = (get_database()
                 .query(Artist)
                 .join(Track, Artist.id == Track.artist_id)
                 .filter(Track.scanned)
                 .group_by(Artist.id))

        if sort == "created":
            query = query.order_by(Artist.created.desc())
        elif sort == "updated":
            query = query.order_by(Artist.updated.desc())
        elif sort == "random":
            query = query.order_by(func.rand())
            page = None

        if filter == "yours":
            remotes_user = remotes.get_user(cherrypy.request.user)

            artist_ids = []

            if remotes_user is not None and remotes_user['lastfm'] is not None:
                for artist in remotes_user['lastfm']['top_artists_overall']:
                    artist_results = search.query_artist(artist['name'], exact=True)

                    if len(artist_results) > 0:
                        artist_ids.append(artist_results[0].id)

            query = query.filter(Artist.id.in_(artist_ids))
        elif filter == "invalid":
            query = query.filter("invalid is not null")
        elif filter == "tag":
            artist_ids = []

            if filter_value != "":
                remotes.update_tag(filter_value)
                remotes_tag = remotes.get_tag(filter_value)

                if remotes_tag is not None and remotes_tag['lastfm'] is not None:
                    for artist in remotes_tag['lastfm']['artists']:
                        artist_results = search.query_artist(artist['name'], exact=True)

                        if len(artist_results) > 0:
                            artist_ids.append(artist_results[0].id)

            query = query.filter(Artist.id.in_(artist_ids))

        total = query.count()
        pages = math.ceil(total / page_size)

        artists = query.limit(page_size).offset(offset).all()

        for artist in artists:
            remotes.update_artist(artist)

        return {
            'artists': artists,
            'page': page,
            'page_size': page_size,
            'total': total,
            'sort': sort,
            'filter': filter,
            'filter_value': filter_value,
            'pages': pages
        }
Example #6
0
    def album(self, artist_slug, album_slug):
        try:
            album = (get_database().query(Album)
                     # _dir_tracks() uses paths
                     .options(joinedload(Album.tracks, Track.paths))
                     .filter_by(slug=album_slug).one())
        except NoResultFound:
            raise cherrypy.NotFound()

        # always update seen, True means now
        album.seen = True

        remotes.update_album(album)

        remotes_artists = []

        for artist in album.artists:
            remotes.update_artist(artist)
            remotes_artist = remotes.get_artist(artist)

            if remotes_artist is not None:
                remotes_artists.append(remotes_artist)

        disc_nos = {}

        for track in album.tracks:
            remotes.update_track(track)

            disc = '' if track.disc is None else track.disc

            if disc not in disc_nos:
                disc_nos[disc] = []

            if track.number is not None:
                # extract max track no, '01' will become 1, '01/10' will become 10
                disc_nos[disc].append(int(re.search('\d+', track.number).group()))
            else:
                disc_nos[disc].append(None)

        album_disc_nos = []

        for disc, numbers in disc_nos.items():
            max_number = max([number for number in numbers if number is not None], default=None)

            if max_number is not None:
                album_disc_nos.append((disc, len(numbers), max_number))

        album_disc_nos = sorted(album_disc_nos, key=lambda album_disc_no: album_disc_no[0])

        remotes_album = remotes.get_album(album)

        dir_tracks = self._dir_tracks(album.tracks)

        if len(album.artists) > 0:
            artist_name = album.artists[0].name
        else:
            artist_name = None

        album_listened_tuples = self.get_album_listened_tuples(artist_name, album.name)
        artist_listened_tuples = self.get_artist_listened_tuples(artist_name)

        return {
            'album_disc_nos': album_disc_nos,
            'album': album,
            'dir_tracks': dir_tracks,
            'remotes_artists': remotes_artists,
            'remotes_album': remotes_album,
            'album_listened_tuples': album_listened_tuples,
            'artist_listened_tuples': artist_listened_tuples,
        }
Example #7
0
    def add(self, archive_password=None, audio_file=None, session=None, artist_name_fallback=None):
        cache_key = LibraryUpload.CACHE_KEY % (cherrypy.request.user.id, session)

        all_tracks = None

        if not cache.has(cache_key):
            raise cherrypy.HTTPError(status=409)
        else:
            all_tracks = cache.get(cache_key)

        if audio_file is not None and len(audio_file) == 0:
            audio_file = None

        if archive_password is not None and len(archive_password) == 0:
            archive_password = None

        content_disposition = cherrypy.request.headers.get('content-disposition')

        filename = content_disposition[content_disposition.index('filename=') + 9:]

        if filename.startswith('"') and filename.endswith('"'):
            filename = filename[1:-1]

        filename = unquote(filename)

        ext = os.path.splitext(filename)[1].lower()[1:]
        basename = os.path.splitext(filename)[0]

        cache_path = os.path.join(cherrypy.config['opmuse'].get('cache.path'), 'upload')

        if not os.path.exists(cache_path):
            os.mkdir(cache_path)

        tempdir = tempfile.mkdtemp(dir=cache_path)

        path = os.path.join(tempdir, filename)

        paths = []

        rarfile.PATH_SEP = '/'

        messages = []

        with open(path, 'wb') as fileobj:
            fileobj.write(cherrypy.request.rfile.read())

        # this file is a regular file that belongs to an audio_file
        if audio_file is not None:
            track = None
            tries = 0

            # try and sleep until we get the track.. this will almost always
            # be needed because of the async upload.
            while track is None and tries < 10:
                track = library_dao.get_track_by_filename(audio_file.encode('utf8'))
                tries += 1

                if track is None:
                    time.sleep(3)

            if track is None:
                messages.append(('warning', ("<strong>%s</strong>: Skipping <strong>%s</strong>, timeout trying to " +
                                "find its track.") % (audio_file, filename)))
            else:
                track_structure = TrackStructureParser(track)
                track_path = track_structure.get_path(absolute=True)
                relative_track_path = track_structure.get_path(absolute=False).decode('utf8', 'replace')

                new_path = os.path.join(track_path, filename.encode('utf8'))

                if os.path.exists(new_path):
                    messages.append(('warning', ("<strong>%s</strong>: Skipping <strong>%s</strong>, already exists " +
                                    "in <strong>%s</strong>.") % (audio_file, filename, relative_track_path)))
                else:
                    shutil.move(path.encode('utf8'), new_path)
                    messages.append(('info', ("<strong>%s</strong>: Uploaded <strong>%s</strong> to " +
                                    "<strong>%s</strong>.") % (audio_file, filename, relative_track_path)))

        elif ext == "zip":
            # set artist name fallback to zip's name so if it's missing artist tags
            # it's easily distinguishable and editable so it can be fixed after upload.
            artist_name_fallback = basename

            try:
                zip = ZipFile(path)

                if archive_password is not None:
                    zip.setpassword(archive_password.encode())

                zip.extractall(tempdir)

                os.remove(path)

                for name in zip.namelist():
                    namepath = os.path.join(tempdir, name)

                    # ignore hidden files, e.g. OSX archive weirdness and such
                    if name.startswith(".") or os.path.split(name)[0] == "__MACOSX":
                        shutil.rmtree(namepath)
                        continue

                    paths.append(namepath.encode('utf8'))

            except Exception as error:
                messages.append(('danger', "<strong>%s</strong>: %s" % (os.path.basename(path), error)))

        elif ext == "rar":
            # look at corresponding ext == zip comment...
            artist_name_fallback = basename

            try:
                rar = RarFile(path)

                if archive_password is None and rar.needs_password():
                    messages.append(('danger', "<strong>%s</strong>: Needs password but none provided." %
                                    os.path.basename(path)))
                else:
                    if archive_password is not None:
                        rar.setpassword(archive_password)

                    rar.extractall(tempdir)

                    os.remove(path)

                    for name in rar.namelist():
                        namepath = os.path.join(tempdir, name)

                        if name.startswith("."):
                            shutil.rmtree(namepath)
                            continue

                        paths.append(namepath.encode('utf8'))

            except Exception as error:
                messages.append(('danger', "<strong>%s</strong>: %s" % (os.path.basename(path), error)))

        # this is a plain audio file
        else:
            paths.append(path.encode('utf8'))

        for path in paths:
            # update modified time to now, we don't want the time from the zip
            # archive or whatever
            os.utime(path, None)

        if len(paths) > 0:
            tracks, add_files_messages = library_dao.add_files(paths, move=True, remove_dirs=False,
                                                               artist_name_fallback=artist_name_fallback,
                                                               user=cherrypy.request.user)
            messages += add_files_messages
        else:
            tracks = []

        shutil.rmtree(tempdir)

        for track in tracks:
            all_tracks.append(track.id)

            if track.album is not None:
                remotes.update_album(track.album)

            if track.artist is not None:
                remotes.update_artist(track.artist)

            remotes.update_track(track)

        hierarchy = Library._produce_track_hierarchy(library_dao.get_tracks_by_ids(all_tracks))

        return {'hierarchy': hierarchy, 'messages': messages}
Example #8
0
    def default(self, query=None, type=None):
        artists = []
        albums = []
        tracks = []
        track_ids = []

        hierarchy = None

        album_track_ids = set()

        recent_searches = []

        if query is not None:
            albums = None
            tracks = None

            # only search for artists
            if type == 'artist':
                artists = search.query_artist(query, exact=True)
                albums = []
                tracks = []
            else:
                artists = search.query_artist(query)

            if albums is None:
                albums = search.query_album(query)

            if tracks is None:
                tracks = search.query_track(query)

            for artist in artists:
                remotes.update_artist(artist)

            for album in albums:
                remotes.update_album(album)

            for track in tracks:
                track_ids.append(track.id)
                remotes.update_track(track)

            entities = artists + albums + tracks

            if len(entities) == 1:
                for artist in artists:
                    raise HTTPRedirect('/%s' % artist.slug)
                for album in albums:
                    raise HTTPRedirect('/%s/%s' % (album.artists[0].slug, album.slug))
                for track in tracks:
                    raise HTTPRedirect('/library/track/%s' % track.slug)

            if cache.has(Search.CACHE_RECENT_KEY):
                recent_searches = cache.get(Search.CACHE_RECENT_KEY)
            else:
                cache.set(Search.CACHE_RECENT_KEY, recent_searches)

            if type is None and len(entities) > 0:
                if len(recent_searches) == 0 or query != recent_searches[0][0]:
                    recent_searches.insert(0, (query, datetime.datetime.now(), cherrypy.request.user.login))

                    if len(recent_searches) > Search.MAX_RECENT:
                        recent_searches.pop()

            entities = sorted(entities, key=lambda entity: entity._SEARCH_SCORE, reverse=True)

            hierarchy = Library._produce_track_hierarchy(entities)

            for key, result_artist in hierarchy['artists'].items():
                for key, result_album in result_artist['albums'].items():
                    for track_id in library_dao.get_track_ids_by_album_id(result_album['entity'].id):
                        album_track_ids.add(track_id)

        return {
            'query': query,
            'hierarchy': hierarchy,
            'tracks': tracks,
            'albums': albums,
            'artists': artists,
            'track_ids': track_ids,
            'album_track_ids': list(album_track_ids),
            'recent_searches': recent_searches
        }