def load_instant(): """ This endpoint takes in a list of recording_mbids and optional desc/name arguments and then loads the recording_mbid's metadata and creates a JSPF file from this data and sends it to the front end so a playlist can be instantly played. .. note:: We recommend that you do not send more than 50 recording_mbids in one request -- our server infrastructure will likely give you a gateway error (502) if you do. :param recording_mbids: A comma separated list of recording_mbids :type recording_mbids: ``str`` :param desc: A description for this instant playlist (optional). :type desc: ``str`` :param name: A name for this instant playlist (optional). :type name: ``str`` :statuscode 200: playlist generated :statuscode 400: invalid recording_mbid arguments """ recordings = request.args.get("recording_mbids", default=None) if recordings is None: raise BadRequest("recording_mbids argument must be present and contain a comma separated list of recording_mbids") recording_mbids = [] for mbid in recordings.split(","): mbid_clean = mbid.strip() if not is_valid_uuid(mbid_clean): raise BadRequest(f"Recording mbid {mbid} is not valid.") recording_mbids.append(mbid_clean) desc = request.args.get("desc", default="") if not desc: desc = "Instant playlist" name = request.args.get("name", default="") if not name: name = "Instant playlist" now = datetime.now() playlist = WritablePlaylist(description=desc, name=name, creator="listenbrainz", creator_id=1, created=now) for i, mbid in enumerate(recording_mbids): rec = WritablePlaylistRecording(position=i, mbid=mbid, added_by_id=1, created=now) playlist.recordings.append(rec) fetch_playlist_recording_metadata(playlist) return render_template( "player/player-page.html", props=ujson.dumps({"playlist": serialize_jspf(playlist)}) )
def load_release(release_mbid): """ This endpoint takes a release mbid, loads the tracks for this release and makes a playlist from it and sends it to the front end via JSPF. :statuscode 200: playlist generated :statuscode 400: invalid recording_mbid arguments """ release_mbid = release_mbid.strip() if not is_valid_uuid(release_mbid): raise BadRequest(f"Recording mbid {release_mbid} is not valid.") playlist = None if mb_engine: release = get_release_by_mbid(release_mbid, includes=["media", "artists"]) if not release: raise NotFound("This release was not found in our database. It may not have replicated to this server yet.") name = "Release %s by %s" % (release["name"], release["artist-credit-phrase"]) desc = 'Release <a href="https://musicbrainz.org/release/%s">%s</a> by %s' % (release["mbid"], release["name"], release["artist-credit-phrase"]) now = datetime.now() playlist = WritablePlaylist(description=desc, name=name, creator="listenbrainz", creator_id=1, created=now) for medium in release["medium-list"]: for recording in medium["track-list"]: rec = WritablePlaylistRecording(title=recording["name"], artist_credit=release["artist-credit-phrase"], artist_mbids=[a["artist"]["mbid"] for a in recording["artist-credit"]], release_name=release["name"], release_mbid=release["mbid"], position=recording["position"], mbid=recording["recording_id"], added_by_id=1, created=now) playlist.recordings.append(rec) return render_template( "player/player-page.html", props=ujson.dumps({"playlist": serialize_jspf(playlist) if playlist is not None else {}}) )
def create(playlist: model_playlist.WritablePlaylist) -> model_playlist.Playlist: """Create a playlist Arguments: playlist: A playlist to add Raises: InvalidUser: if ``playlist.creator_id`` isn't a valid listenbrainz user id, or if ``playlist.created_for_id`` is set and isn't a valid listenbrainz user id Returns: a Playlist, representing the playlist that was inserted, with the id, mbid, and created date added. """ # TODO: These two gets should be done in a single query creator = db_user.get(playlist.creator_id) if creator is None: raise Exception("TODO: Custom exception") # TODO: In a way this is less than ideal -- the caller must take the string name and find the ID, # and then the name is fetched for verification again. Should we accept created_for here and do # lookup only here and not he in the API call validation? if playlist.created_for_id: created_for = db_user.get(playlist.created_for_id) if created_for is None: raise Exception("TODO: Custom exception") query = sqlalchemy.text(""" INSERT INTO playlist.playlist (creator_id , name , description , public , copied_from_id , created_for_id , algorithm_metadata) VALUES (:creator_id , :name , :description , :public , :copied_from_id , :created_for_id , :algorithm_metadata) RETURNING id, mbid, created """) fields = playlist.dict(include={'creator_id', 'name', 'description', 'public', 'copied_from_id', 'created_for_id', 'algorithm_metadata'}) with ts.engine.connect() as connection: result = connection.execute(query, fields) row = dict(result.fetchone()) playlist.id = row['id'] playlist.mbid = row['mbid'] playlist.created = row['created'] playlist.creator = creator['musicbrainz_id'] playlist.recordings = insert_recordings(connection, playlist.id, playlist.recordings, 0) if playlist.collaborator_ids: add_playlist_collaborators(connection, playlist.id, playlist.collaborator_ids) collaborator_ids = get_collaborators_for_playlists(connection, [playlist.id]) collaborator_ids = collaborator_ids.get(playlist.id, []) playlist.collaborators = get_collaborators_names_from_ids(collaborator_ids) return model_playlist.Playlist.parse_obj(playlist.dict())
def create_playlist(): """ Create a playlist. The playlist must be in JSPF format with MusicBrainz extensions, which is defined here: https://musicbrainz.org/doc/jspf . To create an empty playlist, you can send an empty playlist with only the title field filled out. If you would like to create a playlist populated with recordings, each of the track items in the playlist must have an identifier element that contains the MusicBrainz recording that includes the recording MBID. When creating a playlist, only the playlist title and the track identifier elements will be used -- all other elements in the posted JSPF wil be ignored. If a created_for field is found and the user is not an approved playlist bot, then a 403 forbidden will be raised. :reqheader Authorization: Token <user token> :statuscode 200: playlist accepted. :statuscode 400: invalid JSON sent, see error message for details. :statuscode 401: invalid authorization. See error message for details. :statuscode 403: forbidden. The submitting user is not allowed to create playlists for other users. :resheader Content-Type: *application/json* """ user = validate_auth_header() data = request.json validate_create_playlist_required_items(data) validate_playlist(data) public = data["playlist"]["extension"][PLAYLIST_EXTENSION_URI]["public"] collaborators = data.get("playlist", {}).\ get("extension", {}).get(PLAYLIST_EXTENSION_URI, {}).\ get("collaborators", []) # Uniquify collaborators list collaborators = list(set(collaborators)) # Don't allow creator to also be a collaborator if user["musicbrainz_id"] in collaborators: collaborators.remove(user["musicbrainz_id"]) username_lookup = collaborators created_for = data["playlist"].get("created_for", None) if created_for: username_lookup.append(created_for) users = {} if username_lookup: users = db_user.get_many_users_by_mb_id(username_lookup) collaborator_ids = [] for collaborator in collaborators: if collaborator.lower() not in users: log_raise_400("Collaborator {} doesn't exist".format(collaborator)) collaborator_ids.append(users[collaborator.lower()]["id"]) # filter description description = data["playlist"].get("annotation", None) if description is not None: description = _filter_description_html(description) playlist = WritablePlaylist(name=data['playlist']['title'], creator_id=user["id"], description=description, collaborator_ids=collaborator_ids, collaborators=collaborators, public=public) if data["playlist"].get("created_for", None): if user["musicbrainz_id"] not in current_app.config["APPROVED_PLAYLIST_BOTS"]: raise APIForbidden("Playlist contains a created_for field, but submitting user is not an approved playlist bot.") created_for_user = users.get(data["playlist"]["created_for"].lower()) if not created_for_user: log_raise_400("created_for user does not exist.") playlist.created_for_id = created_for_user["id"] if "track" in data["playlist"]: for track in data["playlist"]["track"]: try: playlist.recordings.append(WritablePlaylistRecording(mbid=UUID(track['identifier'][len(PLAYLIST_TRACK_URI_PREFIX):]), added_by_id=user["id"])) except ValueError: log_raise_400("Invalid recording MBID found in submitted recordings") try: playlist = db_playlist.create(playlist) except Exception as e: current_app.logger.error("Error while creating new playlist: {}".format(e)) raise APIInternalServerError("Failed to create the playlist. Please try again.") return jsonify({'status': 'ok', 'playlist_mbid': playlist.mbid})