215 lines
4.6 KiB
Markdown
215 lines
4.6 KiB
Markdown
|
|
# Furumi Web Player API
|
||
|
|
|
||
|
|
Base URL: `http://<host>:<port>/api`
|
||
|
|
|
||
|
|
All endpoints require authentication when `--token` is set (via cookie `furumi_token=<token>` or query param `?token=<token>`).
|
||
|
|
|
||
|
|
All entity references use **slugs** — 12-character hex identifiers (not sequential IDs).
|
||
|
|
|
||
|
|
## Artists
|
||
|
|
|
||
|
|
### `GET /api/artists`
|
||
|
|
|
||
|
|
List all artists that have at least one track.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"slug": "a1b2c3d4e5f6",
|
||
|
|
"name": "Pink Floyd",
|
||
|
|
"album_count": 5,
|
||
|
|
"track_count": 42
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
Sorted alphabetically by name.
|
||
|
|
|
||
|
|
### `GET /api/artists/:slug`
|
||
|
|
|
||
|
|
Get artist details.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"slug": "a1b2c3d4e5f6",
|
||
|
|
"name": "Pink Floyd"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Errors:** `404` if not found.
|
||
|
|
|
||
|
|
### `GET /api/artists/:slug/albums`
|
||
|
|
|
||
|
|
List all albums by an artist.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"slug": "b2c3d4e5f6a7",
|
||
|
|
"name": "Wish You Were Here",
|
||
|
|
"year": 1975,
|
||
|
|
"track_count": 5,
|
||
|
|
"has_cover": true
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
Sorted by year (nulls last), then name.
|
||
|
|
|
||
|
|
### `GET /api/artists/:slug/tracks`
|
||
|
|
|
||
|
|
List all tracks by an artist across all albums.
|
||
|
|
|
||
|
|
**Response:** same as album tracks (see below).
|
||
|
|
|
||
|
|
Sorted by album year, album name, track number, title.
|
||
|
|
|
||
|
|
## Albums
|
||
|
|
|
||
|
|
### `GET /api/albums/:slug`
|
||
|
|
|
||
|
|
List all tracks in an album.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"slug": "c3d4e5f6a7b8",
|
||
|
|
"title": "Have a Cigar",
|
||
|
|
"track_number": 3,
|
||
|
|
"duration_secs": 312.5,
|
||
|
|
"artist_name": "Pink Floyd",
|
||
|
|
"album_name": "Wish You Were Here",
|
||
|
|
"album_slug": "b2c3d4e5f6a7",
|
||
|
|
"genre": "Progressive Rock"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
Sorted by track number (nulls last), then title. Fields `album_name`, `album_slug` may be `null` for tracks without an album.
|
||
|
|
|
||
|
|
### `GET /api/albums/:slug/cover`
|
||
|
|
|
||
|
|
Serve the album cover image from the `album_images` table.
|
||
|
|
|
||
|
|
**Response:** Binary image data with appropriate `Content-Type` (`image/jpeg`, `image/png`, etc.) and `Cache-Control: public, max-age=86400`.
|
||
|
|
|
||
|
|
**Errors:** `404` if no cover exists.
|
||
|
|
|
||
|
|
## Tracks
|
||
|
|
|
||
|
|
### `GET /api/tracks/:slug`
|
||
|
|
|
||
|
|
Get full track details.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"slug": "c3d4e5f6a7b8",
|
||
|
|
"title": "Have a Cigar",
|
||
|
|
"track_number": 3,
|
||
|
|
"duration_secs": 312.5,
|
||
|
|
"genre": "Progressive Rock",
|
||
|
|
"storage_path": "/music/storage/Pink Floyd/Wish You Were Here/03 - Have a Cigar.flac",
|
||
|
|
"artist_name": "Pink Floyd",
|
||
|
|
"artist_slug": "a1b2c3d4e5f6",
|
||
|
|
"album_name": "Wish You Were Here",
|
||
|
|
"album_slug": "b2c3d4e5f6a7",
|
||
|
|
"album_year": 1975
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Errors:** `404` if not found.
|
||
|
|
|
||
|
|
### `GET /api/tracks/:slug/cover`
|
||
|
|
|
||
|
|
Serve cover art for a specific track. Resolution order:
|
||
|
|
|
||
|
|
1. Album cover from `album_images` table (if the track belongs to an album with a cover)
|
||
|
|
2. Embedded cover art extracted from the audio file metadata (ID3/Vorbis/etc. via Symphonia)
|
||
|
|
3. `404` if no cover art is available
|
||
|
|
|
||
|
|
**Response:** Binary image data with `Content-Type` and `Cache-Control: public, max-age=86400`.
|
||
|
|
|
||
|
|
**Errors:** `404` if no cover art found.
|
||
|
|
|
||
|
|
## Streaming
|
||
|
|
|
||
|
|
### `GET /api/stream/:slug`
|
||
|
|
|
||
|
|
Stream the audio file for a track.
|
||
|
|
|
||
|
|
Supports HTTP **Range requests** for seeking:
|
||
|
|
- Full response: `200 OK` with `Content-Length` and `Accept-Ranges: bytes`
|
||
|
|
- Partial response: `206 Partial Content` with `Content-Range`
|
||
|
|
- Invalid range: `416 Range Not Satisfiable`
|
||
|
|
|
||
|
|
`Content-Type` is determined by the file extension (e.g. `audio/flac`, `audio/mpeg`).
|
||
|
|
|
||
|
|
**Errors:** `404` if track or file not found.
|
||
|
|
|
||
|
|
## Search
|
||
|
|
|
||
|
|
### `GET /api/search?q=<query>&limit=<n>`
|
||
|
|
|
||
|
|
Search across artists, albums, and tracks by name (case-insensitive substring match).
|
||
|
|
|
||
|
|
| Parameter | Required | Default | Description |
|
||
|
|
|-----------|----------|---------|-------------|
|
||
|
|
| `q` | yes | — | Search query |
|
||
|
|
| `limit` | no | 20 | Max results |
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"result_type": "artist",
|
||
|
|
"slug": "a1b2c3d4e5f6",
|
||
|
|
"name": "Pink Floyd",
|
||
|
|
"detail": null
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"result_type": "album",
|
||
|
|
"slug": "b2c3d4e5f6a7",
|
||
|
|
"name": "Wish You Were Here",
|
||
|
|
"detail": "Pink Floyd"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"result_type": "track",
|
||
|
|
"slug": "c3d4e5f6a7b8",
|
||
|
|
"name": "Have a Cigar",
|
||
|
|
"detail": "Pink Floyd"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
`detail` contains the artist name for albums and tracks, `null` for artists.
|
||
|
|
|
||
|
|
Sorted by result type (artist → album → track), then by name.
|
||
|
|
|
||
|
|
## Authentication
|
||
|
|
|
||
|
|
When `--token` / `FURUMI_PLAYER_TOKEN` is set:
|
||
|
|
|
||
|
|
- **Cookie:** `furumi_token=<token>` — set after login
|
||
|
|
- **Query parameter:** `?token=<token>` — redirects to player and sets cookie
|
||
|
|
|
||
|
|
When token is empty, authentication is disabled and all endpoints are public.
|
||
|
|
|
||
|
|
Unauthenticated requests receive `401 Unauthorized` with a login form.
|
||
|
|
|
||
|
|
## Error format
|
||
|
|
|
||
|
|
All errors return JSON:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "description of the error"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
With appropriate HTTP status code (`400`, `404`, `500`, etc.).
|