add api docs for streaming routes

+ add trackhash schema
This commit is contained in:
mungai-njoroge
2024-03-04 02:22:25 +03:00
committed by Mungai Njoroge
parent 7d064a8562
commit fb635ff35f
7 changed files with 87 additions and 45 deletions
+2 -1
View File
@@ -29,6 +29,7 @@ from app.api import (
getall, getall,
) )
# TODO: Move this description to a separate file
open_api_description = f""" open_api_description = f"""
The REST API exposed by your Swing Music server The REST API exposed by your Swing Music server
@@ -65,7 +66,7 @@ def create_api():
with app.app_context(): with app.app_context():
app.register_api(album.api) app.register_api(album.api)
app.register_api(artist.api) app.register_api(artist.api)
app.register_blueprint(send_file.api) app.register_api(send_file.api)
app.register_blueprint(search.api) app.register_blueprint(search.api)
app.register_blueprint(folder.api) app.register_blueprint(folder.api)
app.register_blueprint(playlist.api) app.register_blueprint(playlist.api)
+15 -14
View File
@@ -4,31 +4,32 @@ Contains all the album routes.
import random import random
from pydantic import Field
from flask_openapi3 import Tag from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint from flask_openapi3 import APIBlueprint
from pydantic import BaseModel, Field
from app.api.apischemas import AlbumHashSchema, AlbumLimitSchema, ArtistHashSchema from app.api.apischemas import AlbumHashSchema, AlbumLimitSchema, ArtistHashSchema
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as adb
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as lastfmdb
from app.lib.albumslib import sort_by_track_no
from app.models import FavType, Track
from app.serializers.album import serialize_for_card
from app.serializers.track import serialize_track
from app.settings import Defaults from app.settings import Defaults
from app.models import FavType, Track
from app.store.albums import AlbumStore from app.store.albums import AlbumStore
from app.store.tracks import TrackStore from app.store.tracks import TrackStore
from app.utils.hashing import create_hash from app.utils.hashing import create_hash
from app.lib.albumslib import sort_by_track_no
from app.serializers.album import serialize_for_card
from app.serializers.track import serialize_track
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as adb
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as lastfmdb
get_albums_by_albumartist = adb.get_albums_by_albumartist get_albums_by_albumartist = adb.get_albums_by_albumartist
check_is_fav = favdb.check_is_favorite check_is_fav = favdb.check_is_favorite
bp_tag = Tag(name="Album", description="Single album") bp_tag = Tag(name="Album", description="Single album")
api = APIBlueprint("album", __name__, url_prefix="", abp_tags=[bp_tag]) api = APIBlueprint("album", __name__, url_prefix="/album", abp_tags=[bp_tag])
@api.post("/album") # NOTE: Don't use "/" as it will cause redirects (failure)
@api.post("")
def get_album_tracks_and_info(body: AlbumHashSchema): def get_album_tracks_and_info(body: AlbumHashSchema):
""" """
Get album and tracks Get album and tracks
@@ -79,7 +80,7 @@ def get_album_tracks_and_info(body: AlbumHashSchema):
} }
@api.get("/album/<albumhash>/tracks") @api.get("/<albumhash>/tracks")
def get_album_tracks(path: AlbumHashSchema): def get_album_tracks(path: AlbumHashSchema):
""" """
Get album tracks Get album tracks
@@ -106,7 +107,7 @@ class GetMoreFromArtistsBody(AlbumLimitSchema):
) )
@api.post("/album/from-artist") @api.post("/from-artist")
def get_more_from_artist(body: GetMoreFromArtistsBody): def get_more_from_artist(body: GetMoreFromArtistsBody):
""" """
Get more from artist Get more from artist
@@ -152,7 +153,7 @@ class GetAlbumVersionsBody(ArtistHashSchema):
) )
@api.post("/album/other-versions") @api.post("/other-versions")
def get_album_versions(body: GetAlbumVersionsBody): def get_album_versions(body: GetAlbumVersionsBody):
""" """
Get other versions Get other versions
@@ -183,7 +184,7 @@ class GetSimilarAlbumsQuery(ArtistHashSchema, AlbumLimitSchema):
pass pass
@api.get("/album/similar") @api.get("/similar")
def get_similar_albums(query: GetSimilarAlbumsQuery): def get_similar_albums(query: GetSimilarAlbumsQuery):
""" """
Get similar albums Get similar albums
+17 -5
View File
@@ -7,7 +7,6 @@ from pydantic import BaseModel, Field
from app.settings import Defaults from app.settings import Defaults
class AlbumHashSchema(BaseModel): class AlbumHashSchema(BaseModel):
""" """
Extending this class will give you a model with the `albumhash` field Extending this class will give you a model with the `albumhash` field
@@ -34,6 +33,19 @@ class ArtistHashSchema(BaseModel):
) )
class TrackHashSchema(BaseModel):
"""
Extending this class will give you a model with the `trackhash` field
"""
trackhash: str = Field(
description="The track hash",
example=Defaults.API_TRACKHASH,
min_length=Defaults.HASH_LENGTH,
max_length=Defaults.HASH_LENGTH,
)
class GenericLimitSchema(BaseModel): class GenericLimitSchema(BaseModel):
""" """
Extending this class will give you a model with the `limit` field Extending this class will give you a model with the `limit` field
@@ -42,7 +54,7 @@ class GenericLimitSchema(BaseModel):
limit: int = Field( limit: int = Field(
description="The number of items to return", description="The number of items to return",
example=Defaults.API_CARD_LIMIT, example=Defaults.API_CARD_LIMIT,
default=Defaults.API_CARD_LIMIT default=Defaults.API_CARD_LIMIT,
) )
@@ -55,7 +67,7 @@ class TrackLimitSchema(BaseModel):
limit: int = Field( limit: int = Field(
description="The number of tracks to return", description="The number of tracks to return",
example=Defaults.API_CARD_LIMIT, example=Defaults.API_CARD_LIMIT,
default=Defaults.API_CARD_LIMIT default=Defaults.API_CARD_LIMIT,
) )
@@ -67,7 +79,7 @@ class AlbumLimitSchema(BaseModel):
limit: int = Field( limit: int = Field(
description="The number of albums to return", description="The number of albums to return",
example=Defaults.API_CARD_LIMIT, example=Defaults.API_CARD_LIMIT,
default=Defaults.API_CARD_LIMIT default=Defaults.API_CARD_LIMIT,
) )
@@ -79,5 +91,5 @@ class ArtistLimitSchema(BaseModel):
limit: int = Field( limit: int = Field(
description="The number of artists to return", description="The number of artists to return",
example=Defaults.API_CARD_LIMIT, example=Defaults.API_CARD_LIMIT,
default=Defaults.API_CARD_LIMIT default=Defaults.API_CARD_LIMIT,
) )
+6 -7
View File
@@ -6,9 +6,8 @@ import math
import random import random
from datetime import datetime from datetime import datetime
from flask import request
from flask_openapi3 import APIBlueprint, Tag from flask_openapi3 import APIBlueprint, Tag
from pydantic import BaseModel, Field from pydantic import Field
from app.api.apischemas import AlbumLimitSchema, ArtistHashSchema, ArtistLimitSchema, TrackLimitSchema from app.api.apischemas import AlbumLimitSchema, ArtistHashSchema, ArtistLimitSchema, TrackLimitSchema
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
@@ -21,10 +20,10 @@ from app.store.artists import ArtistStore
from app.store.tracks import TrackStore from app.store.tracks import TrackStore
bp_tag = Tag(name="Artist", description="Single artist") bp_tag = Tag(name="Artist", description="Single artist")
api = APIBlueprint("artist", __name__, url_prefix="/", abp_tags=[bp_tag]) api = APIBlueprint("artist", __name__, url_prefix="/artist", abp_tags=[bp_tag])
@api.get("/artist/<string:artisthash>") @api.get("/<string:artisthash>")
def get_artist(path: ArtistHashSchema, query: TrackLimitSchema): def get_artist(path: ArtistHashSchema, query: TrackLimitSchema):
""" """
Get artist data. Get artist data.
@@ -88,7 +87,7 @@ class GetArtistAlbumsQuery(AlbumLimitSchema):
) )
@api.get("/artist/<artisthash>/albums") @api.get("/<artisthash>/albums")
def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery): def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
return_all = query.all return_all = query.all
artisthash = path.artisthash artisthash = path.artisthash
@@ -175,7 +174,7 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
} }
@api.get("/artist/<artisthash>/tracks") @api.get("/<artisthash>/tracks")
def get_all_artist_tracks(path: ArtistHashSchema): def get_all_artist_tracks(path: ArtistHashSchema):
""" """
Get all artist tracks Get all artist tracks
@@ -187,7 +186,7 @@ def get_all_artist_tracks(path: ArtistHashSchema):
return {"tracks": serialize_tracks(tracks)} return {"tracks": serialize_tracks(tracks)}
@api.get("/artist/<artisthash>/similar") @api.get("/<artisthash>/similar")
def get_similar_artists(path: ArtistHashSchema, query: ArtistLimitSchema): def get_similar_artists(path: ArtistHashSchema, query: ArtistLimitSchema):
""" """
Returns similar artists. Returns similar artists.
+43 -15
View File
@@ -1,30 +1,43 @@
""" """
Contains all the track routes. Contains all the track routes.
""" """
import os import os
from flask import Blueprint, send_file, request from flask import Blueprint, send_file, request
from flask_openapi3 import APIBlueprint, Tag
from pydantic import BaseModel, Field
from app.api.apischemas import TrackHashSchema
from app.lib.trackslib import get_silence_paddings from app.lib.trackslib import get_silence_paddings
from app.store.tracks import TrackStore from app.store.tracks import TrackStore
api = Blueprint("track", __name__, url_prefix="/") bp_tag = Tag(name="File", description="Single artist")
api = APIBlueprint("track", __name__, url_prefix="/file", abp_tags=[bp_tag])
@api.route("/file/<trackhash>") class SendTrackFileQuery(BaseModel):
def send_track_file(trackhash: str): filepath: str = Field(
description="The filepath to play (if available)", default=None
)
@api.get("/<trackhash>")
def send_track_file(path: TrackHashSchema, query: SendTrackFileQuery):
""" """
Returns an audio file that matches the passed id to the client. Get file
Falls back to track hash if id is not found.
Returns a playable audio file that corresponds to the given filepath. Falls back to track hash if filepath is not found.
""" """
trackhash = path.trackhash
filepath = query.filepath
msg = {"msg": "File Not Found"} msg = {"msg": "File Not Found"}
def get_mime(filename: str) -> str: def get_mime(filename: str) -> str:
ext = filename.rsplit(".", maxsplit=1)[-1] ext = filename.rsplit(".", maxsplit=1)[-1]
return f"audio/{ext}" return f"audio/{ext}"
filepath = request.args.get("filepath") # If filepath is provide, try to send that
if filepath is not None: if filepath is not None:
try: try:
track = TrackStore.get_tracks_by_filepaths([filepath])[0] track = TrackStore.get_tracks_by_filepaths([filepath])[0]
@@ -37,9 +50,7 @@ def send_track_file(trackhash: str):
audio_type = get_mime(filepath) audio_type = get_mime(filepath)
return send_file(filepath, mimetype=audio_type) return send_file(filepath, mimetype=audio_type)
if trackhash is None: # Else, find file by trackhash
return msg, 404
tracks = TrackStore.get_tracks_by_trackhashes([trackhash]) tracks = TrackStore.get_tracks_by_trackhashes([trackhash])
for track in tracks: for track in tracks:
@@ -56,11 +67,28 @@ def send_track_file(trackhash: str):
return msg, 404 return msg, 404
@api.route("/file/silence", methods=["POST"]) class GetAudioSilenceBody(BaseModel):
def get_audio_silence(): ending_file: str = Field(
data = request.get_json() description="The ending file's path",
ending_file = data.get("end", None) # ending file's filepath example="/home/cwilvx/Music/Made in Kenya/Sol generation/Bensoul - Salama.mp3",
starting_file = data.get("start", None) # starting file's filepath )
starting_file: str = Field(
description="The beginning file's path",
example="/home/cwilvx/Music/Tidal/Albums/Bensoul - Qwarantunes/Bensoul - Peddi.m4a",
)
@api.post("/silence")
def get_audio_silence(body: GetAudioSilenceBody):
"""
Get silence paddings
Returns the duration of silence at the end of the current ending track and the duration of silence at the beginning of the next track.
NOTE: Durations are in milliseconds.
"""
ending_file = body.ending_file # ending file's filepath
starting_file = body.starting_file # starting file's filepath
if ending_file is None or starting_file is None: if ending_file is None or starting_file is None:
return {"msg": "No filepath provided"}, 400 return {"msg": "No filepath provided"}, 400
+3 -3
View File
@@ -60,7 +60,7 @@ def get_silence_paddings(ending_file: str, starting_file: str):
""" """
Returns the ending silence of a track and the starting silence of the next. Returns the ending silence of a track and the starting silence of the next.
""" """
silence = {"start": 0, "end": 0} silence = {"starting_file": 0, "ending_file": 0}
ending_thread = None ending_thread = None
starting_thread = None starting_thread = None
@@ -77,9 +77,9 @@ def get_silence_paddings(ending_file: str, starting_file: str):
starting_thread.start() starting_thread.start()
if ending_thread: if ending_thread:
silence["end"] = ending_thread.join() silence["ending_file"] = ending_thread.join()
if starting_thread: if starting_thread:
silence["start"] = starting_thread.join() silence["starting_file"] = starting_thread.join()
return silence return silence
+1
View File
@@ -100,6 +100,7 @@ class Defaults:
HASH_LENGTH = 10 HASH_LENGTH = 10
API_ALBUMHASH = "c5bcec6cb3" API_ALBUMHASH = "c5bcec6cb3"
API_ARTISTHASH = "fc6f0acac5" API_ARTISTHASH = "fc6f0acac5"
API_TRACKHASH = "0853280a12"
API_ALBUMNAME = "Rumours" API_ALBUMNAME = "Rumours"
API_CARD_LIMIT = 6 API_CARD_LIMIT = 6