def new_search(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') parent = ClassAlias(Folder) artist_query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(query)).config(distinct = True, offset = artist_offset, limit = artist_count) album_query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(query)).config(distinct = True, offset = album_offset, limit = album_count) song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count] return request.formatter({ 'searchResult2': { 'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ], 'album': [ f.as_subsonic_child(request.user) for f in album_query ], 'song': [ t.as_subsonic_child(request.user) for t in song_query ] }})
def search_id3(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') artist_query = store.find(Artist, Artist.name.contains_string(query))[artist_offset : artist_offset + artist_count] album_query = store.find(Album, Album.name.contains_string(query))[album_offset : album_offset + album_count] song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count] return request.formatter({ 'searchResult3': { 'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ], 'album': [ a.as_subsonic_album(request.user) for a in album_query ], 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] }})
def search_id3(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') artist_query = session.query(Artist).filter(Artist.name.contains(query)).slice(artist_offset, artist_offset + artist_count) album_query = session.query(Album).filter(Album.name.contains(query)).slice(album_offset, album_offset + album_count) song_query = session.query(Track).filter(Track.title.contains(query)).slice(song_offset, song_offset + song_count) return request.formatter({ 'searchResult2': { 'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ], 'album': [ a.as_subsonic_album(request.user) for a in album_query ], 'song': [ t.as_subsonic_child(request.user) for t in song_query ] }})
def cover_art(): status, res = get_entity(request, Folder) if not status: return res if not res.has_cover_art or not os.path.isfile(os.path.join(res.path, 'cover.jpg')): return request.error_formatter(70, 'Cover art not found') size = request.values.get('size') if size: try: size = int(size) except: return request.error_formatter(0, 'Invalid size value') else: return send_file(os.path.join(res.path, 'cover.jpg')) im = Image.open(os.path.join(res.path, 'cover.jpg')) if size > im.size[0] and size > im.size[1]: return send_file(os.path.join(res.path, 'cover.jpg')) size_path = os.path.join(config.get('webapp', 'cache_dir'), str(size)) path = os.path.join(size_path, str(res.id)) if os.path.exists(path): return send_file(path) if not os.path.exists(size_path): os.makedirs(size_path) im.thumbnail([size, size], Image.ANTIALIAS) im.save(path, 'JPEG') return send_file(path)
def new_search(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') artist_query = session.query(Folder).filter(~ Folder.tracks.any(), Folder.path.contains(query)).slice(artist_offset, artist_offset + artist_count) album_query = session.query(Folder).filter(Folder.tracks.any(), Folder.path.contains(query)).slice(album_offset, album_offset + album_count) song_query = sesion.query(Track).filter(Track.title.contains(query)).slice(song_offset, song_offset + song_count) return request.formatter({ 'searchResult2': { 'artist': [ { 'id': a.id, 'name': a.name } for a in artist_query ], 'album': [ f.as_subsonic_child(request.user) for f in album_query ], 'song': [ t.as_subsonic_child(request.user) for t in song_query ] }})
def cover_art(): status, res = get_entity(request, Folder) if not status: return res if not res.has_cover_art or not os.path.isfile( os.path.join(res.path, 'cover.jpg')): return request.error_formatter(70, 'Cover art not found') size = request.values.get('size') if size: try: size = int(size) except: return request.error_formatter(0, 'Invalid size value') else: return send_file(os.path.join(res.path, 'cover.jpg')) im = Image.open(os.path.join(res.path, 'cover.jpg')) if size > im.size[0] and size > im.size[1]: return send_file(os.path.join(res.path, 'cover.jpg')) size_path = os.path.join(app.config['WEBAPP']['cache_dir'], str(size)) path = os.path.abspath(os.path.join(size_path, str(res.id))) if os.path.exists(path): return send_file(path, mimetype='image/jpeg') if not os.path.exists(size_path): os.makedirs(size_path) im.thumbnail([size, size], Image.ANTIALIAS) im.save(path, 'JPEG') return send_file(path, mimetype='image/jpeg')
def rate(): id, rating = map(request.args.get, [ 'id', 'rating' ]) if not id or not rating: return request.error_formatter(10, 'Missing parameter') try: uid = uuid.UUID(id) rating = int(rating) except: return request.error_formatter(0, 'Invalid parameter') if not rating in xrange(6): return request.error_formatter(0, 'rating must be between 0 and 5 (inclusive)') if rating == 0: RatingTrack.query.filter(RatingTrack.user_id == request.user.id).filter(RatingTrack.rated_id == uid).delete() RatingFolder.query.filter(RatingFolder.user_id == request.user.id).filter(RatingFolder.rated_id == uid).delete() else: rated = Track.query.get(uid) rating_ent = RatingTrack if not rated: rated = Folder.query.get(uid) rating_ent = RatingFolder if not rated: return request.error_formatter(70, 'Unknown id') rating_info = rating_ent.query.get((request.user.id, uid)) if rating_info: rating_info.rating = rating else: session.add(rating_ent(user = request.user, rated = rated, rating = rating)) session.commit() return request.formatter({})
def new_search(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') artist_query = Folder.query.filter(~ Folder.tracks.any(), Folder.name.contains(query)).slice(artist_offset, artist_offset + artist_count) album_query = Folder.query.filter(Folder.tracks.any(), Folder.name.contains(query)).slice(album_offset, album_offset + album_count) song_query = Track.query.filter(Track.title.contains(query)).slice(song_offset, song_offset + song_count) return request.formatter({ 'searchResult2': { 'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ], 'album': [ f.as_subsonic_child(request.user) for f in album_query ], 'song': [ t.as_subsonic_child(request.user) for t in song_query ] }})
def cover_art(): # Use the track id to get the file path. # TODO: support for multiple query types is a matter of a few case statements status, res = get_entity(request, Track) if not status: return res res = res.folder if not res.has_cover_art or not os.path.isfile(os.path.join(res.path, 'cover.jpg')): return request.error_formatter(70, 'Cover art not found') size = request.args.get('size') if size: try: size = int(size) except: return request.error_formatter(0, 'Invalid size value') else: return send_file(os.path.join(res.path, 'cover.jpg')) im = Image.open(os.path.join(res.path, 'cover.jpg')) if size > im.size[0] and size > im.size[1]: return send_file(os.path.join(res.path, 'cover.jpg')) size_path = os.path.join(config.get('base', 'cache_dir'), str(size)) path = os.path.join(size_path, str(res.id)) if os.path.exists(path): return send_file(path) if not os.path.exists(size_path): os.makedirs(size_path) im.thumbnail([size, size], Image.ANTIALIAS) im.save(path, 'JPEG') return send_file(path, mimetype='image/jpeg;base64')
def search_id3(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') artist_query = Artist.query.filter(Artist.name.contains(query)).slice(artist_offset, artist_offset + artist_count) album_query = Album.query.filter(Album.name.contains(query)).slice(album_offset, album_offset + album_count) song_query = Track.query.filter(Track.title.contains(query)).slice(song_offset, song_offset + song_count) return request.formatter({ 'searchResult2': { 'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ], 'album': [ a.as_subsonic_album(request.user) for a in album_query ], 'song': [ t.as_subsonic_child(request.user) for t in song_query ] }})
def update_playlist(): status, res = get_entity(request, Playlist, 'playlistId') if not status: return res if res.user_id != request.user.id and not request.user.admin: return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours") playlist = res name, comment, public = map(request.values.get, [ 'name', 'comment', 'public' ]) to_add, to_remove = map(request.values.getlist, [ 'songIdToAdd', 'songIndexToRemove' ]) try: to_add = map(uuid.UUID, to_add) to_remove = map(int, to_remove) except: return request.error_formatter(0, 'Invalid parameter') if name: playlist.name = name if comment: playlist.comment = comment if public: playlist.public = public in (True, 'True', 'true', 1, '1') for sid in to_add: track = store.get(Track, sid) if not track: return request.error_formatter(70, 'Unknown song') playlist.add(track) playlist.remove_at_indexes(to_remove) store.commit() return request.formatter({})
def album_list(): ltype, size, offset = map(request.args.get, ['type', 'size', 'offset']) try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = session.query(Folder).filter(Folder.tracks.any()) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({'albumList': {}}) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query.offset(x).limit(1).one()) return request.formatter({ 'albumList': { 'album': [a.as_subsonic_child(request.user) for a in albums] } }) elif ltype == 'newest': query = query.order_by(desc(Folder.created)) elif ltype == 'highest': query = query.join(RatingFolder).group_by(Folder.id).order_by( desc(func.avg(RatingFolder.rating))) elif ltype == 'frequent': query = query.join(Track, Folder.tracks).group_by(Folder.id).order_by( desc(func.avg(Track.play_count))) elif ltype == 'recent': query = query.join(Track, Folder.tracks).group_by(Folder.id).order_by( desc(func.max(Track.last_play))) elif ltype == 'starred': query = query.join(StarredFolder).join(User).filter( User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Folder.name) elif ltype == 'alphabeticalByArtist': # this is a mess because who knows how your file structure is set up # with the database changes it's more difficult to get the parent of a dir parent = aliased(Folder) query = query.join(parent, Folder.parent).order_by( parent.name).order_by(Folder.name) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList': { 'album': [ f.as_subsonic_child(request.user) for f in query.limit(size).offset(offset) ] } })
def cover_art(): folder = None track = None res_id = None status, cover_art = get_entity(request, CoverArt) if not status: return cover_art if cover_art.is_embedded: original_dir = os.path.join(config.get('base', 'cache_dir'), 'original') if not os.path.exists(original_dir): os.makedirs(original_dir) cover_art_file = os.path.join(original_dir, str(cover_art.id)) # Have we already extracted the cover art to the temporary # directory? If so, we don't bother to do it again. Otherwise # we have to pull the image out of the track tag data. # Future: Not handling multiple cover art images in one # track yet... results are unpredictable. if not os.path.isfile(cover_art_file): # Extract cover art from tag data and save to file. cover_tag = CoverTag(cover_art.path) if not cover_tag.data: return request.error_formatter(70, 'Cover art not found') else: f = file(cover_art_file, 'wb') f.write(cover_tag.data) f.close() else: cover_art_file = cover_art.path if not os.path.isfile(cover_art_file): return request.error_formatter(70, 'Cover art not found') size = request.args.get('size') if size: try: size = int(size) except: return request.error_formatter(0, 'Invalid size value') else: return send_file(cover_art_file) im = Image.open(cover_art_file) if size > im.size[0] and size > im.size[1]: return send_file(cover_art_file) size_path = os.path.join(config.get('base', 'cache_dir'), str(size)) path = os.path.join(size_path, str(cover_art.id)) if os.path.exists(path): return send_file(path) if not os.path.exists(size_path): os.makedirs(size_path) im.thumbnail([size, size], Image.ANTIALIAS) im.save(path, 'JPEG') return send_file(path)
def album_list(): ltype, size, offset = map(request.args.get, ['type', 'size', 'offset']) try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = store.find(Folder, Track.folder_id == Folder.id) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({'albumList': {}}) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query[x]) return request.formatter({ 'albumList': { 'album': [a.as_subsonic_child(request.user) for a in albums] } }) elif ltype == 'newest': query = query.order_by(Desc(Folder.created)).config(distinct=True) elif ltype == 'highest': query = query.find(RatingFolder.rated_id == Folder.id).group_by( Folder.id).order_by(Desc(Avg(RatingFolder.rating))) elif ltype == 'frequent': query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count))) elif ltype == 'recent': query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play))) elif ltype == 'starred': query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Folder.name).config(distinct=True) elif ltype == 'alphabeticalByArtist': parent = ClassAlias(Folder) query = query.find(Folder.parent_id == parent.id).order_by( parent.name, Folder.name).config(distinct=True) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList': { 'album': [ f.as_subsonic_child(request.user) for f in query[offset:offset + size] ] } })
def album_list_id3(): ltype, size, offset = map(request.args.get, ['type', 'size', 'offset']) try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = store.find(Album) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({'albumList2': {}}) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query[x]) return request.formatter({ 'albumList2': { 'album': [a.as_subsonic_album(request.user) for a in albums] } }) elif ltype == 'newest': query = query.find(Track.album_id == Album.id).group_by( Album.id).order_by(Desc(Min(Track.created))) elif ltype == 'frequent': query = query.find(Track.album_id == Album.id).group_by( Album.id).order_by(Desc(Avg(Track.play_count))) elif ltype == 'recent': query = query.find(Track.album_id == Album.id).group_by( Album.id).order_by(Desc(Max(Track.last_play))) elif ltype == 'starred': query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Album.name) elif ltype == 'alphabeticalByArtist': query = query.find(Artist.id == Album.artist_id).order_by( Artist.name, Album.name) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList2': { 'album': [ f.as_subsonic_album(request.user) for f in query[offset:offset + size] ] } })
def download(typed): eid = request.args.get('id') cid = clean_id(eid) if not is_track_id(eid): return request.error_formatter(10, 'Invalid id') tr = typed.gettrack(cid) if tr is None or tr.path is None: return request.error_formatter(70, 'Track not found'), 404 else: return send_file(tr.path, conditional=True)
def song(typed): eid = request.args.get('id') cid = clean_id(eid) if not is_track_id(eid): return request.error_formatter(10, 'Missing or invalid Song id') tr = typed.gettrackfull(cid) if not tr: return request.error_formatter(70, 'Song not found'), 404 return request.formatter({'song': format_track(tr)})
def lyrics(): artist, title = map(request.args.get, ['artist', 'title']) if not artist: return request.error_formatter(10, 'Missing artist parameter') if not title: return request.error_formatter(10, 'Missing title parameter') query = session.query(Track).join(Album, Artist).filter( func.lower(Track.title) == title.lower() and func.lower(Artist.name) == artist.lower()) for track in query: lyrics_path = os.path.splitext(track.path)[0] + '.txt' if os.path.exists(lyrics_path): app.logger.debug('Found lyrics file: ' + lyrics_path) try: lyrics = read_file_as_unicode(lyrics_path) except UnicodeError: # Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or # return no lyrics. Log it anyway. app.logger.warn('Unsupported encoding for lyrics file ' + lyrics_path) continue return request.formatter({ 'lyrics': { 'artist': track.album.artist.name, 'title': track.title, '_value_': lyrics } }) try: r = requests.get( "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect", params={ 'artist': artist, 'song': title }) root = ElementTree.fromstring(r.content) ns = {'cl': 'http://api.chartlyrics.com/'} return request.formatter({ 'lyrics': { 'artist': root.find('cl:LyricArtist', namespaces=ns).text, 'title': root.find('cl:LyricSong', namespaces=ns).text, '_value_': root.find('cl:Lyric', namespaces=ns).text } }) except requests.exceptions.RequestException, e: app.logger.warn('Error while requesting the ChartLyrics API: ' + str(e))
def album_list_id3(): ltype, size, offset = map(request.args.get, ['type', 'size', 'offset']) try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = session.query(Album) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({'albumList2': {}}) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query.offset(x).limit(1).one()) return request.formatter({ 'albumList2': { 'album': [a.as_subsonic_album(request.user) for a in albums] } }) elif ltype == 'newest': query = query.join(Track, Album.tracks).group_by(Album.id).order_by( desc(func.min(Track.created))) elif ltype == 'frequent': query = query.join(Track, Album.tracks).group_by(Album.id).order_by( desc(func.avg(Track.play_count))) elif ltype == 'recent': query = query.join(Track, Album.tracks).group_by(Album.id).order_by( desc(func.max(Track.last_play))) elif ltype == 'starred': query = query.join(StarredAlbum).join(User).filter( User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Album.name) elif ltype == 'alphabeticalByArtist': query = query.join(Artist).order_by(Artist.name).order_by(Album.name) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList2': { 'album': [ f.as_subsonic_album(request.user) for f in query.limit(size).offset(offset) ] } })
def user_changepass(): username, password = map(request.args.get, ['username', 'password']) if not username or not password: return request.error_formatter(10, 'Missing parameter') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') status = UserManager.change_password2(store, username, password) if status != UserManager.SUCCESS: return request.error_formatter(0, UserManager.error_str(status)) return request.formatter({})
def user_info(): username = request.values.get('username') if username is None: return request.error_formatter(10, 'Missing username') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') user = store.find(User, User.name == username).one() if user is None: return request.error_formatter(0, 'Unknown user') return request.formatter({ 'user': user.as_subsonic_user() })
def user_info(): username = request.args.get('username') if username is None: return request.error_formatter(10, 'Missing username') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') user = store.find(User, User.name == username).one() if user is None: return request.error_formatter(0, 'Unknown user') return request.formatter({'user': user.as_subsonic_user()})
def user_info(): username = request.args.get('username') if username is None: return request.error_formatter(10, 'Missing username') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') user = User.query.filter(User.name == username).first() if user is None: return request.error_formatter(0, 'Unknown user') return request.formatter({ 'user': user.as_subsonic_user() })
def user_changepass(): username, password = map(request.args.get, [ 'username', 'password' ]) if not username or not password: return request.error_formatter(10, 'Missing parameter') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') status = UserManager.change_password2(username, password) if status != UserManager.SUCCESS: return request.error_formatter(0, UserManager.error_str(status)) return request.formatter({})
def _album_list(typed): ok, atype = check_parameter(request, 'type', allow_none=False, choices=[ 'random', 'newest', 'highest', 'frequent', 'recent', 'starred', 'alphabeticalByName', 'alphabeticalByArtist', 'byYear', 'genre' ]) if not ok: return False, atype ok, size = check_parameter(request, 'size', fct=lambda val: int(val) if val else 10) if not ok: return False, size ok, offset = check_parameter(request, 'offset', fct=lambda val: int(val) if val else 0) if not ok: return False, offset fromYear = request.args.get('fromYear') toYear = request.args.get('toYear') genre = request.args.get('genre') if atype == "byYear" and (not fromYear or not toYear): return False, request.error_formatter( 10, 'Missing parameter fromYear or toYear') elif atype == "genre" and not genre: return False, request.error_formatter(10, 'Missing parameter genre') if atype == "random": count = typed.countalbums() def gen(): for _ in range(size): x = random.choice(range(count)) yield typed.getalbum( ffilter=lambda query: query.offset(x).limit(1)) return True, gen() else: fltr = get_filter_by_type(atype, fromYear=fromYear, toYear=toYear, genre=genre) srt = get_sort_by_type(atype) return True, typed.listalbums(fltr, skip=offset, limit=size, order_by=srt)
def user_del(): if not request.user.admin: return request.error_formatter(50, 'Admin restricted') username = request.values.get('username') user = store.find(User, User.name == username).one() if not user: return request.error_formatter(70, 'Unknown user') status = UserManager.delete(store, user.id) if status != UserManager.SUCCESS: return request.error_formatter(0, UserManager.error_str(status)) return request.formatter({})
def user_del(): if not request.user.admin: return request.error_formatter(50, 'Admin restricted') username = request.args.get('username') user = User.query.filter(User.name == username).first() if not user: return request.error_formatter(70, 'Unknown user') status = UserManager.delete(user.id) if status != UserManager.SUCCESS: return request.error_formatter(0, UserManager.error_str(status)) return request.formatter({})
def album_list(): ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ]) if not ltype: return request.error_formatter(10, 'Missing type') try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = store.find(Folder, Track.folder_id == Folder.id) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({ 'albumList': {} }) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query[x]) return request.formatter({ 'albumList': { 'album': [ a.as_subsonic_child(request.user) for a in albums ] } }) elif ltype == 'newest': query = query.order_by(Desc(Folder.created)).config(distinct = True) elif ltype == 'highest': query = query.find(RatingFolder.rated_id == Folder.id).group_by(Folder.id).order_by(Desc(Avg(RatingFolder.rating))) elif ltype == 'frequent': query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count))) elif ltype == 'recent': query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play))) elif ltype == 'starred': query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Folder.name).config(distinct = True) elif ltype == 'alphabeticalByArtist': parent = ClassAlias(Folder) query = query.find(Folder.parent_id == parent.id).order_by(parent.name, Folder.name).config(distinct = True) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList': { 'album': [ f.as_subsonic_child(request.user) for f in query[offset:offset+size] ] } })
def check_parameter(request, key, allow_none=True, choices=None, fct=None): val = request.args.get(key) if val is None and not allow_none: return False, request.error_formatter(10, 'Missing parameter %s' % key) if choices is not None and val not in choices: return False, request.error_formatter( 0, 'Invalid value %s for parameter %s' % (val, key)) if fct is not None: try: return True, fct(val) except: return False, request.error_formatter(0, 'Invalid parameter %s' % key) return True, val
def album_list(): ltype, size, offset = map(request.args.get, [ 'type', 'size', 'offset' ]) try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = session.query(Folder).filter(Folder.tracks.any()) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({ 'albumList': {} }) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query.offset(x).limit(1).one()) return request.formatter({ 'albumList': { 'album': [ a.as_subsonic_child(request.user) for a in albums ] } }) elif ltype == 'newest': query = query.order_by(desc(Folder.created)) elif ltype == 'highest': query = query.join(RatingFolder).group_by(Folder.id).order_by(desc(func.avg(RatingFolder.rating))) elif ltype == 'frequent': query = query.join(Track, Folder.tracks).group_by(Folder.id).order_by(desc(func.avg(Track.play_count))) elif ltype == 'recent': query = query.join(Track, Folder.tracks).group_by(Folder.id).order_by(desc(func.max(Track.last_play))) elif ltype == 'starred': query = query.join(StarredFolder).join(User).filter(User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Folder.name) elif ltype == 'alphabeticalByArtist': # this is a mess because who knows how your file structure is set up # with the database changes it's more difficult to get the parent of a dir parent = aliased(Folder) query = query.join(parent, Folder.parent).order_by(parent.name).order_by(Folder.name) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList': { 'album': [ f.as_subsonic_child(request.user) for f in query.limit(size).offset(offset) ] } })
def user_del(): if not request.user.admin: return request.error_formatter(50, 'Admin restricted') username = request.args.get('username') user = store.find(User, User.name == username).one() if not user: return request.error_formatter(70, 'Unknown user') status = UserManager.delete(store, user.id) if status != UserManager.SUCCESS: return request.error_formatter(0, UserManager.error_str(status)) return request.formatter({})
def user_add(): if not request.user.admin: return request.error_formatter(50, 'Admin restricted') username, password, email, admin = map(request.args.get, [ 'username', 'password', 'email', 'adminRole' ]) if not username or not password or not email: return request.error_formatter(10, 'Missing parameter') admin = True if admin in (True, 'True', 'true', 1, '1') else False status = UserManager.add(username, password, email, admin) if status == UserManager.NAME_EXISTS: return request.error_formatter(0, 'There is already a user with that username') return request.formatter({})
def try_star(ent, starred_ent, eid): try: uid = uuid.UUID(eid) except: return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__) if starred_ent.query.get((request.user.id, uid)): return 2, request.error_formatter(0, '%s already starred' % ent.__name__) e = ent.query.get(uid) if e: session.add(starred_ent(user = request.user, starred = e)) else: return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__) return 0, None
def list_playlists(): query = store.find(Playlist, Or(Playlist.user_id == request.user.id, Playlist.public == True)).order_by(Playlist.name) username = request.values.get('username') if username: if not request.user.admin: return request.error_formatter(50, 'Restricted to admins') user = store.find(User, User.name == username).one() if not user: return request.error_formatter(70, 'No such user') query = store.find(Playlist, Playlist.user_id == User.id, User.name == username).order_by(Playlist.name) return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } })
def new_search(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') parent = ClassAlias(Folder) artist_query = store.find( parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(query)).config(distinct=True, offset=artist_offset, limit=artist_count) album_query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(query)).config( distinct=True, offset=album_offset, limit=album_count) song_query = store.find( Track, Track.title.contains_string(query))[song_offset:song_offset + song_count] return request.formatter({ 'searchResult2': { 'artist': [{ 'id': str(a.id), 'name': a.name } for a in artist_query], 'album': [f.as_subsonic_child(request.user) for f in album_query], 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] } })
def authorize(): if not request.path.startswith('/rest/'): return error = request.error_formatter(40, 'Unauthorized'), 401 if request.authorization: status, user = UserManager.try_auth(store, request.authorization.username, request.authorization.password) if status == UserManager.SUCCESS: request.username = request.authorization.username request.user = user return (username, password) = map(request.values.get, ['u', 'p']) if not username or not password: return error password = decode_password(password) status, user = UserManager.try_auth(store, username, password) if status != UserManager.SUCCESS: return error request.username = username request.user = user
def music_directory(typed): eid = request.args.get('id') cid = clean_id(eid) if is_artist_id(eid): dirlist = typed.listalbumsbyartists( lambda query: query.filter(Artist.id == cid)) children = dirlist[0].albums format_directory_id = format_artist_id format_child = format_album elif is_album_id(eid): dirlist = typed.listtracksbyalbums( lambda query: query.filter(Album.id == cid)) children = dirlist[0].tracks format_directory_id = format_album_id format_child = format_track else: return request.error_formatter(10, 'Missing or invalid id') return request.formatter({ 'directory': { 'id': format_directory_id(dirlist[0].id), 'name': dirlist[0].name, 'child': [format_child(child, child=True) for child in children] } })
def user_add(): if not request.user.admin: return request.error_formatter(50, 'Admin restricted') username, password, email, admin = map( request.args.get, ['username', 'password', 'email', 'adminRole']) if not username or not password or not email: return request.error_formatter(10, 'Missing parameter') admin = True if admin in (True, 'True', 'true', 1, '1') else False status = UserManager.add(store, username, password, email, admin) if status == UserManager.NAME_EXISTS: return request.error_formatter( 0, 'There is already a user with that username') return request.formatter({})
def album_list_id3(): ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ]) if not ltype: return request.error_formatter(10, 'Missing type') try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = store.find(Album) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({ 'albumList2': {} }) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query[x]) return request.formatter({ 'albumList2': { 'album': [ a.as_subsonic_album(request.user) for a in albums ] } }) elif ltype == 'newest': query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Min(Track.created))) elif ltype == 'frequent': query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Avg(Track.play_count))) elif ltype == 'recent': query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Max(Track.last_play))) elif ltype == 'starred': query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Album.name) elif ltype == 'alphabeticalByArtist': query = query.find(Artist.id == Album.artist_id).order_by(Artist.name, Album.name) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList2': { 'album': [ f.as_subsonic_album(request.user) for f in query[offset:offset+size] ] } })
def add_chat_message(): msg = request.args.get("message") if not msg: return request.error_formatter(10, "Missing message") session.add(ChatMessage(user=request.user, message=msg)) session.commit() return request.formatter({})
def user_changepass(): username, password = map(request.values.get, [ 'username', 'password' ]) if not username or not password: return request.error_formatter(10, 'Missing parameter') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') password = decode_password(password) status = UserManager.change_password2(store, username, password) if status != UserManager.SUCCESS: code = 0 if status == UserManager.NO_SUCH_USER: code = 70 return request.error_formatter(code, UserManager.error_str(status)) return request.formatter({})
def try_unstar(ent, eid): try: uid = uuid.UUID(eid) except: return request.error_formatter(0, 'Invalid id') ent.query.filter(ent.user_id == request.user.id).filter(ent.starred_id == uid).delete() return None
def add_chat_message(): msg = request.args.get('message') if not msg: return request.error_formatter(10, 'Missing message') session.add(ChatMessage(user = request.user, message = msg)) session.commit() return request.formatter({})
def user_changepass(): username, password = map(request.values.get, ['username', 'password']) if not username or not password: return request.error_formatter(10, 'Missing parameter') if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') password = decode_password(password) status = UserManager.change_password2(store, username, password) if status != UserManager.SUCCESS: code = 0 if status == UserManager.NO_SUCH_USER: code = 70 return request.error_formatter(code, UserManager.error_str(status)) return request.formatter({})
def users_info(): if not request.user.admin: return request.error_formatter(50, 'Admin restricted') return request.formatter( {'users': { 'user': [u.as_subsonic_user() for u in store.find(User)] }})
def try_unstar(ent, eid): try: uid = uuid.UUID(eid) except: return request.error_formatter(0, 'Invalid id') store.find(ent, ent.user_id == request.user.id, ent.starred_id == uid).remove() return None
def album_list_id3(): ltype, size, offset = map(request.args.get, [ 'type', 'size', 'offset' ]) try: size = int(size) if size else 10 offset = int(offset) if offset else 0 except: return request.error_formatter(0, 'Invalid parameter format') query = session.query(Album) if ltype == 'random': albums = [] count = query.count() if not count: return request.formatter({ 'albumList2': {} }) for _ in xrange(size): x = random.choice(xrange(count)) albums.append(query.offset(x).limit(1).one()) return request.formatter({ 'albumList2': { 'album': [ a.as_subsonic_album(request.user) for a in albums ] } }) elif ltype == 'newest': query = query.join(Track, Album.tracks).group_by(Album.id).order_by(desc(func.min(Track.created))) elif ltype == 'frequent': query = query.join(Track, Album.tracks).group_by(Album.id).order_by(desc(func.avg(Track.play_count))) elif ltype == 'recent': query = query.join(Track, Album.tracks).group_by(Album.id).order_by(desc(func.max(Track.last_play))) elif ltype == 'starred': query = query.join(StarredAlbum).join(User).filter(User.name == request.username) elif ltype == 'alphabeticalByName': query = query.order_by(Album.name) elif ltype == 'alphabeticalByArtist': query = query.join(Artist).order_by(Artist.name).order_by(Album.name) else: return request.error_formatter(0, 'Unknown search type') return request.formatter({ 'albumList2': { 'album': [ f.as_subsonic_album(request.user) for f in query.limit(size).offset(offset) ] } })
def try_unstar(ent, eid): try: uid = uuid.UUID(eid) except: return request.error_formatter(0, 'Invalid id') session.query(ent).filter(ent.user_id == request.user.id).filter( ent.starred_id == uid).delete() return None
def try_star(ent, starred_ent, eid): try: uid = uuid.UUID(eid) except: return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__) if store.get(starred_ent, (request.user.id, uid)): return 2, request.error_formatter(0, '%s already starred' % ent.__name__) e = store.get(ent, uid) if e: starred = starred_ent() starred.user_id = request.user.id starred.starred_id = uid store.add(starred) else: return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__) return 0, None
def update_playlist(): status, res = get_entity(request, Playlist, 'playlistId') if not status: return res if res.user_id != request.user.id and not request.user.admin: return request.error_formatter( 50, "You're not allowed to delete a playlist that isn't yours") playlist = res name, comment, public = map(request.args.get, ['name', 'comment', 'public']) to_add, to_remove = map(request.args.getlist, ['songIdToAdd', 'songIndexToRemove']) try: to_add = set(map(uuid.UUID, to_add)) to_remove = sorted(set(map(int, to_remove))) except: return request.error_formatter(0, 'Invalid parameter') if name: playlist.name = name if comment: playlist.comment = comment if public: playlist.public = public in (True, 'True', 'true', 1, '1') for sid in to_add: track = Track.query.get(sid) if not track: return request.error_formatter(70, 'Unknown song') if track not in playlist.tracks: playlist.tracks.append(track) offset = 0 for idx in to_remove: idx = idx - offset if idx < 0 or idx >= len(playlist.tracks): return request.error_formatter(0, 'Index out of range') playlist.tracks.pop(idx) offset += 1 session.commit() return request.formatter({})
def album(typed): eid = request.args.get('id') cid = clean_id(eid) if not is_album_id(eid): return request.error_formatter(10, 'Missing or invalid Album id') al = typed.listtracksbyalbums(lambda query: query.filter(Album.id == cid)) if len(al) == 0: return request.error_formatter(70, 'Album not found'), 404 return request.formatter({ 'album': { 'id': format_album_id(cid), 'name': al[0].name, 'albumCount': al[0].track_count(), 'song': [format_track(track) for track in al[0].tracks] } })
def cover_art(typed): eid = request.args.get('id') cid = clean_id(eid) if not is_track_id(eid) and not is_album_id(eid): return request.error_formatter(10, 'Invalid id') if is_track_id(eid): tr = typed.gettrackinfo(cid) if tr is None or tr.album_id is None: return request.error_formatter(70, 'Cover art not found'), 404 else: cid = tr.album_id cover = typed.getcoverbyalbumid(cid) if cover is None or cover.mbid == '0': return request.error_formatter(70, 'Cover art not found'), 404 else: return send_from_directory(Thumb.getdir(), os.path.basename(cover.path), conditional=True)
def search2and3(typed): q = request.args.get('query') if not q: return request.error_formatter(10, 'Missing query parameter') ok, artistCount = check_parameter(request, 'artistCount', fct=lambda val: int(val) if val else 20) if not ok: return False, artistCount ok, artistOffset = check_parameter(request, 'artistOffset', fct=lambda val: int(val) if val else 0) if not ok: return False, artistOffset ok, albumCount = check_parameter(request, 'albumCount', fct=lambda val: int(val) if val else 20) if not ok: return False, albumCount ok, albumOffset = check_parameter(request, 'albumOffset', fct=lambda val: int(val) if val else 0) if not ok: return False, albumOffset ok, songCount = check_parameter(request, 'songCount', fct=lambda val: int(val) if val else 20) if not ok: return False, songCount ok, songOffset = check_parameter(request, 'songOffset', fct=lambda val: int(val) if val else 0) if not ok: return False, songOffset artists = typed.listartists( lambda query: query.filter(Artist.name.contains(q)), skip=artistOffset, limit=artistCount) albums = typed.listalbums( lambda query: query.filter(Album.name.contains(q)), skip=albumOffset, limit=albumCount) tracks = typed.listtracks( lambda query: query.filter(TrackInfo.name.contains(q)), skip=songOffset, limit=songCount) return request.formatter({ 'searchResult2': { 'artist': [format_artist(artist) for artist in artists], 'album': [format_album(album) for album in albums], 'track': [format_track(track) for track in tracks] } })
def create_playlist(): # Only(?) method where the android client uses form data rather than GET params playlist_id, name = map( lambda x: request.args.get(x) or request.form.get(x), ['playlistId', 'name']) # songId actually doesn't seem to be required songs = request.args.getlist('songId') or request.form.getlist('songId') try: playlist_id = uuid.UUID(playlist_id) if playlist_id else None songs = set(map(uuid.UUID, songs)) except: return request.error_formatter(0, 'Invalid parameter') if playlist_id: playlist = store.get(Playlist, playlist_id) if not playlist: return request.error_formatter(70, 'Unknwon playlist') if playlist.user_id != request.user.id and not request.user.admin: return request.error_formatter( 50, "You're not allowed to modify a playlist that isn't yours") playlist.tracks.clear() if name: playlist.name = name elif name: playlist = Playlist() playlist.user_id = request.user.id playlist.name = name store.add(playlist) else: return request.error_formatter(10, 'Missing playlist id or name') for sid in songs: track = store.get(Track, sid) if not track: return request.error_formatter(70, 'Unknown song') playlist.tracks.add(track) store.commit() return request.formatter({})
def search_id3(): query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) try: artist_count = int(artist_count) if artist_count else 20 artist_offset = int(artist_offset) if artist_offset else 0 album_count = int(album_count) if album_count else 20 album_offset = int(album_offset) if album_offset else 0 song_count = int(song_count) if song_count else 20 song_offset = int(song_offset) if song_offset else 0 except: return request.error_formatter(0, 'Invalid parameter') if not query: return request.error_formatter(10, 'Missing query parameter') artist_query = store.find( Artist, Artist.name.contains_string(query))[artist_offset:artist_offset + artist_count] album_query = store.find( Album, Album.name.contains_string(query))[album_offset:album_offset + album_count] song_query = store.find( Track, Track.title.contains_string(query))[song_offset:song_offset + song_count] return request.formatter({ 'searchResult3': { 'artist': [a.as_subsonic_artist(request.user) for a in artist_query], 'album': [a.as_subsonic_album(request.user) for a in album_query], 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] } })
def add_chat_message(): msg = request.args.get('message') if not msg: return request.error_formatter(10, 'Missing message') chat = ChatMessage() chat.user_id = request.user.id chat.message = msg store.add(chat) store.commit() return request.formatter({})
def list_playlists(): query = Playlist.query.filter(or_(Playlist.user_id == request.user.id, Playlist.public == True)).order_by(func.lower(Playlist.name)) username = request.args.get('username') if username: if not request.user.admin: return request.error_formatter(50, 'Restricted to admins') query = Playlist.query.join(User).filter(User.name == username).order_by(func.lower(Playlist.name)) return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } })
def delete_playlist(): status, res = get_entity(request, Playlist) if not status: return res if res.user_id != request.user.id and not request.user.admin: return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours") store.remove(res) store.commit() return request.formatter({})
def show_playlist(): status, res = get_entity(request, Playlist) if not status: return res if res.user_id != request.user.id and not request.user.admin: return request.error_formatter('50', 'Private playlist') info = res.as_subsonic_playlist(request.user) info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ] return request.formatter({ 'playlist': info })