diff --git a/app/api/__init__.py b/app/api/__init__.py index ebf4806c..b72c0ec0 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -29,6 +29,7 @@ from app.api import ( getall, ) +# TODO: Move this description to a separate file open_api_description = f""" The REST API exposed by your Swing Music server @@ -65,7 +66,7 @@ def create_api(): with app.app_context(): app.register_api(album.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(folder.api) app.register_blueprint(playlist.api) diff --git a/app/api/album.py b/app/api/album.py index c2f77dfb..75882b18 100644 --- a/app/api/album.py +++ b/app/api/album.py @@ -4,31 +4,32 @@ Contains all the album routes. import random +from pydantic import Field from flask_openapi3 import Tag from flask_openapi3 import APIBlueprint -from pydantic import BaseModel, Field 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.models import FavType, Track from app.store.albums import AlbumStore from app.store.tracks import TrackStore 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 check_is_fav = favdb.check_is_favorite 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): """ Get album and tracks @@ -79,7 +80,7 @@ def get_album_tracks_and_info(body: AlbumHashSchema): } -@api.get("/album//tracks") +@api.get("//tracks") def get_album_tracks(path: AlbumHashSchema): """ 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): """ 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): """ Get other versions @@ -183,7 +184,7 @@ class GetSimilarAlbumsQuery(ArtistHashSchema, AlbumLimitSchema): pass -@api.get("/album/similar") +@api.get("/similar") def get_similar_albums(query: GetSimilarAlbumsQuery): """ Get similar albums diff --git a/app/api/apischemas.py b/app/api/apischemas.py index a4031491..eb024a90 100644 --- a/app/api/apischemas.py +++ b/app/api/apischemas.py @@ -7,7 +7,6 @@ from pydantic import BaseModel, Field from app.settings import Defaults - class AlbumHashSchema(BaseModel): """ 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): """ Extending this class will give you a model with the `limit` field @@ -42,7 +54,7 @@ class GenericLimitSchema(BaseModel): limit: int = Field( description="The number of items to return", 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( description="The number of tracks to return", 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( description="The number of albums to return", 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( description="The number of artists to return", example=Defaults.API_CARD_LIMIT, - default=Defaults.API_CARD_LIMIT + default=Defaults.API_CARD_LIMIT, ) diff --git a/app/api/artist.py b/app/api/artist.py index e26a014c..a1309f1c 100644 --- a/app/api/artist.py +++ b/app/api/artist.py @@ -6,9 +6,8 @@ import math import random from datetime import datetime -from flask import request 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.db.sqlite.favorite import SQLiteFavoriteMethods as favdb @@ -21,10 +20,10 @@ from app.store.artists import ArtistStore from app.store.tracks import TrackStore 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/") +@api.get("/") def get_artist(path: ArtistHashSchema, query: TrackLimitSchema): """ Get artist data. @@ -88,7 +87,7 @@ class GetArtistAlbumsQuery(AlbumLimitSchema): ) -@api.get("/artist//albums") +@api.get("//albums") def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery): return_all = query.all artisthash = path.artisthash @@ -175,7 +174,7 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery): } -@api.get("/artist//tracks") +@api.get("//tracks") def get_all_artist_tracks(path: ArtistHashSchema): """ Get all artist tracks @@ -187,7 +186,7 @@ def get_all_artist_tracks(path: ArtistHashSchema): return {"tracks": serialize_tracks(tracks)} -@api.get("/artist//similar") +@api.get("//similar") def get_similar_artists(path: ArtistHashSchema, query: ArtistLimitSchema): """ Returns similar artists. diff --git a/app/api/send_file.py b/app/api/send_file.py index 94f3a4c5..e07f32a7 100644 --- a/app/api/send_file.py +++ b/app/api/send_file.py @@ -1,30 +1,43 @@ """ Contains all the track routes. """ + import os 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.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/") -def send_track_file(trackhash: str): +class SendTrackFileQuery(BaseModel): + filepath: str = Field( + description="The filepath to play (if available)", default=None + ) + + +@api.get("/") +def send_track_file(path: TrackHashSchema, query: SendTrackFileQuery): """ - Returns an audio file that matches the passed id to the client. - Falls back to track hash if id is not found. + Get file + + 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"} def get_mime(filename: str) -> str: ext = filename.rsplit(".", maxsplit=1)[-1] return f"audio/{ext}" - filepath = request.args.get("filepath") - + # If filepath is provide, try to send that if filepath is not None: try: track = TrackStore.get_tracks_by_filepaths([filepath])[0] @@ -37,9 +50,7 @@ def send_track_file(trackhash: str): audio_type = get_mime(filepath) return send_file(filepath, mimetype=audio_type) - if trackhash is None: - return msg, 404 - + # Else, find file by trackhash tracks = TrackStore.get_tracks_by_trackhashes([trackhash]) for track in tracks: @@ -56,11 +67,28 @@ def send_track_file(trackhash: str): return msg, 404 -@api.route("/file/silence", methods=["POST"]) -def get_audio_silence(): - data = request.get_json() - ending_file = data.get("end", None) # ending file's filepath - starting_file = data.get("start", None) # starting file's filepath +class GetAudioSilenceBody(BaseModel): + ending_file: str = Field( + description="The ending file's path", + example="/home/cwilvx/Music/Made in Kenya/Sol generation/Bensoul - Salama.mp3", + ) + 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: return {"msg": "No filepath provided"}, 400 diff --git a/app/lib/trackslib.py b/app/lib/trackslib.py index d12662f8..a685c0bd 100644 --- a/app/lib/trackslib.py +++ b/app/lib/trackslib.py @@ -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. """ - silence = {"start": 0, "end": 0} + silence = {"starting_file": 0, "ending_file": 0} ending_thread = None starting_thread = None @@ -77,9 +77,9 @@ def get_silence_paddings(ending_file: str, starting_file: str): starting_thread.start() if ending_thread: - silence["end"] = ending_thread.join() + silence["ending_file"] = ending_thread.join() if starting_thread: - silence["start"] = starting_thread.join() + silence["starting_file"] = starting_thread.join() return silence diff --git a/app/settings.py b/app/settings.py index 37203b46..e141576f 100644 --- a/app/settings.py +++ b/app/settings.py @@ -100,6 +100,7 @@ class Defaults: HASH_LENGTH = 10 API_ALBUMHASH = "c5bcec6cb3" API_ARTISTHASH = "fc6f0acac5" + API_TRACKHASH = "0853280a12" API_ALBUMNAME = "Rumours" API_CARD_LIMIT = 6