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, }
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
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 }
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 }
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 }
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, }
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}
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 }