Show soundtrack covers and edit alternative names (#49)
Covers: statistics cards show the soundtrack's cover on the left, and the
statistics popup shows it large. Until it arrives, and without a cover, the
soundtrack's initials stand in. Covers load only for cards on or near the
screen, three at a time, and are cached in covers/ next to the settings
(CoverCache), downloaded again only when cover_modified changes. The index and
file handling are shared with CharacterCache in a new FileCache.
Alternative names: the popup shows them and has an editor (add, remove, save)
that sends PUT /soundtracks/{uuid}/alternative-names. The statistics search and
the Search popup find soundtracks by them; Search shows the matching name after
the real one. Search now loads /soundtracks, falling back to the plain list.
The API types are regenerated from the server's spec, with a SoundtrackCover
theme style in every theme.
Closes #49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a735c515bf
commit
ef6ac8f1fd
37 files changed
+8029
-1763
No files matched your search
@@ -0,0 +1,22 @@
|
||||
# Generated by tools/generate_api_types.gd from server/openapi.json.
|
||||
# Do not edit; change the server and generate again.
|
||||
class_name AlternativeNames
|
||||
extends RefCounted
|
||||
## The [code]AlternativeNames[/code] schema of the MusicServer API.
|
||||
|
||||
## The new list of alternative names. Replaces the old list; send [] to remove them all.
|
||||
var names: Array[String]
|
||||
|
||||
|
||||
## Reads the fields from parsed JSON. Missing fields keep their defaults.
|
||||
static func from_dict(data: Dictionary) -> AlternativeNames:
|
||||
var result: AlternativeNames = AlternativeNames.new()
|
||||
result.names.assign(ApiJson.get_array(data, "names"))
|
||||
return result
|
||||
|
||||
|
||||
## Returns the fields under their JSON names, ready for [method JSON.stringify].
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"names": names,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bpdabeiw5wupm
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by tools/generate_api_types.gd from server/openapi.json.
|
||||
# Do not edit; change the server and generate again.
|
||||
class_name CoverSource
|
||||
extends RefCounted
|
||||
## The [code]CoverSource[/code] schema of the MusicServer API.
|
||||
|
||||
## The game's ID on SteamGridDB, or the anime's ID on AniList
|
||||
var id: String
|
||||
## Where the cover comes from
|
||||
var source: String
|
||||
|
||||
|
||||
## Reads the fields from parsed JSON. Missing fields keep their defaults.
|
||||
static func from_dict(data: Dictionary) -> CoverSource:
|
||||
var result: CoverSource = CoverSource.new()
|
||||
result.id = ApiJson.get_string(data, "id", "")
|
||||
result.source = ApiJson.get_string(data, "source", "")
|
||||
return result
|
||||
|
||||
|
||||
## Returns the fields under their JSON names, ready for [method JSON.stringify].
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"id": id,
|
||||
"source": source,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://06q8sppe2evn
|
||||
@@ -5,6 +5,10 @@ extends RefCounted
|
||||
## The [code]Soundtrack[/code] schema of the MusicServer API.
|
||||
|
||||
var added: String
|
||||
## Other names the soundtrack is known by
|
||||
var alternative_names: Array[String]
|
||||
## When the cover last changed, in Unix seconds. Missing when there is no cover.
|
||||
var cover_modified: int
|
||||
var last_played: String
|
||||
var name: String
|
||||
var times_played: int
|
||||
@@ -15,6 +19,8 @@ var uuid: String
|
||||
static func from_dict(data: Dictionary) -> Soundtrack:
|
||||
var result: Soundtrack = Soundtrack.new()
|
||||
result.added = ApiJson.get_string(data, "added", "")
|
||||
result.alternative_names.assign(ApiJson.get_array(data, "alternative_names"))
|
||||
result.cover_modified = ApiJson.get_int(data, "cover_modified", 0)
|
||||
result.last_played = ApiJson.get_string(data, "last_played", "")
|
||||
result.name = ApiJson.get_string(data, "name", "")
|
||||
result.times_played = ApiJson.get_int(data, "times_played", 0)
|
||||
@@ -26,6 +32,8 @@ static func from_dict(data: Dictionary) -> Soundtrack:
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"added": added,
|
||||
"alternative_names": alternative_names,
|
||||
"cover_modified": cover_modified,
|
||||
"last_played": last_played,
|
||||
"name": name,
|
||||
"times_played": times_played,
|
||||
|
||||
@@ -5,6 +5,10 @@ extends RefCounted
|
||||
## The [code]SoundtrackWithSongList[/code] schema of the MusicServer API.
|
||||
|
||||
var added: String
|
||||
## Other names the soundtrack is known by
|
||||
var alternative_names: Array[String]
|
||||
## When the cover last changed, in Unix seconds. Missing when there is no cover.
|
||||
var cover_modified: int
|
||||
var last_played: String
|
||||
var name: String
|
||||
var songs: Array[Song]
|
||||
@@ -16,6 +20,8 @@ var uuid: String
|
||||
static func from_dict(data: Dictionary) -> SoundtrackWithSongList:
|
||||
var result: SoundtrackWithSongList = SoundtrackWithSongList.new()
|
||||
result.added = ApiJson.get_string(data, "added", "")
|
||||
result.alternative_names.assign(ApiJson.get_array(data, "alternative_names"))
|
||||
result.cover_modified = ApiJson.get_int(data, "cover_modified", 0)
|
||||
result.last_played = ApiJson.get_string(data, "last_played", "")
|
||||
result.name = ApiJson.get_string(data, "name", "")
|
||||
for item: Variant in ApiJson.get_array(data, "songs"):
|
||||
@@ -30,6 +36,8 @@ static func from_dict(data: Dictionary) -> SoundtrackWithSongList:
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"added": added,
|
||||
"alternative_names": alternative_names,
|
||||
"cover_modified": cover_modified,
|
||||
"last_played": last_played,
|
||||
"name": name,
|
||||
"songs": songs.map(func(item: Song) -> Dictionary: return item.to_dict()),
|
||||
|
||||
@@ -4,6 +4,10 @@ class_name SoundtrackWithSongs
|
||||
extends RefCounted
|
||||
## The [code]SoundtrackWithSongs[/code] schema of the MusicServer API.
|
||||
|
||||
## Other names the soundtrack is known by
|
||||
var alternative_names: Array[String]
|
||||
## When the cover last changed, in Unix seconds. Missing when there is no cover.
|
||||
var cover_modified: int
|
||||
var songs: Array[SongInfoForStats]
|
||||
var soundtrack_id: int
|
||||
var soundtrack_last_played: String
|
||||
@@ -15,6 +19,8 @@ var soundtrack_uuid: String
|
||||
## Reads the fields from parsed JSON. Missing fields keep their defaults.
|
||||
static func from_dict(data: Dictionary) -> SoundtrackWithSongs:
|
||||
var result: SoundtrackWithSongs = SoundtrackWithSongs.new()
|
||||
result.alternative_names.assign(ApiJson.get_array(data, "alternative_names"))
|
||||
result.cover_modified = ApiJson.get_int(data, "cover_modified", 0)
|
||||
for item: Variant in ApiJson.get_array(data, "songs"):
|
||||
if item is Dictionary:
|
||||
result.songs.append(SongInfoForStats.from_dict(item))
|
||||
@@ -29,6 +35,8 @@ static func from_dict(data: Dictionary) -> SoundtrackWithSongs:
|
||||
## Returns the fields under their JSON names, ready for [method JSON.stringify].
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"alternative_names": alternative_names,
|
||||
"cover_modified": cover_modified,
|
||||
"songs": songs.map(func(item: SongInfoForStats) -> Dictionary: return item.to_dict()),
|
||||
"soundtrack_id": soundtrack_id,
|
||||
"soundtrack_last_played": soundtrack_last_played,
|
||||
|
||||
@@ -131,6 +131,31 @@ func fetch_soundtracks(on_done: Callable, on_failed: Callable = Callable()) -> v
|
||||
get_json_list("/soundtracks", Soundtrack, on_done, on_failed)
|
||||
|
||||
|
||||
## Downloads the cover picture of the soundtrack with [param uuid] as bytes (JPEG or
|
||||
## PNG). The server answers 404 while it has no cover for it.
|
||||
func fetch_soundtrack_cover(
|
||||
uuid: String, on_done: Callable, on_failed: Callable = Callable()) -> void:
|
||||
get_bytes("/soundtracks/%s/cover" % uuid.uri_encode(), on_done, on_failed)
|
||||
|
||||
|
||||
## Replaces the alternative names of the soundtrack with [param uuid] with
|
||||
## [param names]. The server trims them, drops duplicates and the soundtrack's own name,
|
||||
## and calls [param on_done] with the updated [Soundtrack].
|
||||
func set_alternative_names(uuid: String, names: Array[String], on_done: Callable,
|
||||
on_failed: Callable = Callable()) -> void:
|
||||
var request: AlternativeNames = AlternativeNames.new()
|
||||
request.names = names
|
||||
var path: String = "/soundtracks/%s/alternative-names" % uuid.uri_encode()
|
||||
var saved: Callable = func saved(data: Variant) -> void:
|
||||
if !(data is Dictionary):
|
||||
push_warning("Expected a JSON object from " + path)
|
||||
_call_if_valid(on_failed)
|
||||
return
|
||||
on_done.call(Soundtrack.from_dict(data))
|
||||
_request(path, HTTPClient.METHOD_PUT, _parse_json.bind(path, saved, on_failed), on_failed,
|
||||
true, JSON.stringify(request.to_dict()))
|
||||
|
||||
|
||||
## Fetches a soundtrack and its songs as a [SoundtrackWithSongList].
|
||||
func fetch_soundtrack(
|
||||
uuid: String, on_done: Callable, on_failed: Callable = Callable()) -> void:
|
||||
@@ -419,13 +444,14 @@ func _parse_json(
|
||||
|
||||
|
||||
# Sends the request with the token, and once more with a new token if the server
|
||||
# answers 401
|
||||
# answers 401. A [param json_body] is sent as the request's body.
|
||||
func _request(
|
||||
path: String,
|
||||
method: int,
|
||||
on_success: Callable,
|
||||
on_failed: Callable,
|
||||
may_retry: bool = true,
|
||||
json_body: String = "",
|
||||
) -> void:
|
||||
_with_token(func(token: String) -> void:
|
||||
_send(path, method, token, func(response_code: int, body: PackedByteArray) -> void:
|
||||
@@ -433,10 +459,10 @@ func _request(
|
||||
GameLog.debug("Token refused by the server, getting a new one")
|
||||
if _token == token:
|
||||
clear_token()
|
||||
_request(path, method, on_success, on_failed, false)
|
||||
_request(path, method, on_success, on_failed, false, json_body)
|
||||
return
|
||||
_handle_response(path, response_code, body, on_success, on_failed)
|
||||
)
|
||||
, json_body)
|
||||
, on_failed)
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,23 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AlternativeNames": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"names": {
|
||||
"description": "The new list of alternative names. Replaces the old list; send [] to remove them all.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 50,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"names"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CharacterDetails": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -36,6 +53,30 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CoverSource": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The game's ID on SteamGridDB, or the anime's ID on AniList",
|
||||
"maxLength": 50,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Where the cover comes from",
|
||||
"enum": [
|
||||
"steamgriddb",
|
||||
"anilist"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"source",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ProgressResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -175,6 +216,18 @@
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"alternative_names": {
|
||||
"description": "Other names the soundtrack is known by",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"cover_modified": {
|
||||
"description": "When the cover last changed, in Unix seconds. Missing when there is no cover.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"last_played": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
@@ -193,6 +246,7 @@
|
||||
"required": [
|
||||
"uuid",
|
||||
"name",
|
||||
"alternative_names",
|
||||
"times_played",
|
||||
"added"
|
||||
],
|
||||
@@ -205,6 +259,18 @@
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"alternative_names": {
|
||||
"description": "Other names the soundtrack is known by",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"cover_modified": {
|
||||
"description": "When the cover last changed, in Unix seconds. Missing when there is no cover.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"last_played": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
@@ -230,6 +296,7 @@
|
||||
"songs",
|
||||
"uuid",
|
||||
"name",
|
||||
"alternative_names",
|
||||
"times_played",
|
||||
"added"
|
||||
],
|
||||
@@ -238,6 +305,18 @@
|
||||
"SoundtrackWithSongs": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"alternative_names": {
|
||||
"description": "Other names the soundtrack is known by",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"cover_modified": {
|
||||
"description": "When the cover last changed, in Unix seconds. Missing when there is no cover.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"songs": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SongInfoForStats"
|
||||
@@ -267,6 +346,7 @@
|
||||
"soundtrack_id",
|
||||
"soundtrack_uuid",
|
||||
"soundtrack_name",
|
||||
"alternative_names",
|
||||
"soundtrack_played",
|
||||
"songs"
|
||||
],
|
||||
@@ -2663,6 +2743,515 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/soundtracks/{uuid}/alternative-names": {
|
||||
"put": {
|
||||
"description": "Replaces the soundtrack's alternative names, which search and the cover search also use. Names are trimmed, and empty names, duplicates and the soundtrack's own name (ignoring case) are dropped.",
|
||||
"operationId": "set-alternative-names",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UUID of the soundtrack",
|
||||
"in": "path",
|
||||
"name": "uuid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "UUID of the soundtrack",
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AlternativeNames"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Soundtrack"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "OK"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unprocessable Entity"
|
||||
},
|
||||
"500": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Set alternative names",
|
||||
"tags": [
|
||||
"library"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/soundtracks/{uuid}/cover": {
|
||||
"delete": {
|
||||
"description": "Deletes the soundtrack's cover, including one set by hand, so the automatic search looks for a new one.",
|
||||
"operationId": "delete-soundtrack-cover",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UUID of the soundtrack or song",
|
||||
"in": "path",
|
||||
"name": "uuid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "UUID of the soundtrack or song",
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unprocessable Entity"
|
||||
},
|
||||
"500": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Internal Server Error"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Remove the cover",
|
||||
"tags": [
|
||||
"covers"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"description": "Returns the soundtrack's cover image. cover_modified in the soundtrack responses tells when it last changed.",
|
||||
"operationId": "get-soundtrack-cover",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UUID of the soundtrack or song",
|
||||
"in": "path",
|
||||
"name": "uuid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "UUID of the soundtrack or song",
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"image/jpeg": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"image/png": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"image/webp": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "The cover image"
|
||||
},
|
||||
"206": {
|
||||
"content": {
|
||||
"image/jpeg": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"image/png": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"image/webp": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Part of the file, for a Range request"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unprocessable Entity"
|
||||
},
|
||||
"500": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Soundtrack cover",
|
||||
"tags": [
|
||||
"covers"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"description": "Stores the image as the soundtrack's cover. The automatic search never replaces it; delete it to go back to automatic.",
|
||||
"operationId": "upload-soundtrack-cover",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UUID of the soundtrack",
|
||||
"in": "path",
|
||||
"name": "uuid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "UUID of the soundtrack",
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"image/*": {
|
||||
"schema": {
|
||||
"contentMediaType": "application/octet-stream",
|
||||
"format": "binary",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
},
|
||||
"400": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unprocessable Entity"
|
||||
},
|
||||
"500": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Internal Server Error"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Upload a cover",
|
||||
"tags": [
|
||||
"covers"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/soundtracks/{uuid}/cover/source": {
|
||||
"put": {
|
||||
"description": "Downloads the cover of the given SteamGridDB game or AniList anime and stores it as the soundtrack's cover. The automatic search never replaces it. SteamGridDB needs STEAMGRIDDB_API_KEY on the server.",
|
||||
"operationId": "set-soundtrack-cover-source",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UUID of the soundtrack",
|
||||
"in": "path",
|
||||
"name": "uuid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "UUID of the soundtrack",
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CoverSource"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
},
|
||||
"400": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unprocessable Entity"
|
||||
},
|
||||
"500": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Internal Server Error"
|
||||
},
|
||||
"502": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Bad Gateway"
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Pick a cover",
|
||||
"tags": [
|
||||
"covers"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/statistics/songs/least-played": {
|
||||
"get": {
|
||||
"description": "Returns the least played songs with their soundtrack.",
|
||||
|
||||
Reference in new issue
Block a user