Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/using-seerr/settings/artwork-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: Artwork Providers
description: Configure the artwork providers Seerr uses for music artists.
sidebar_position: 8
---

# Artwork Providers

Seerr fetches artist artwork from a third-party provider. The
**Settings → Metadata Providers** page exposes an **Artwork Providers
Configuration** section where administrators can tune the connection details
for each provider.

A **Test** button at the top and bottom of the page exercises every configured
artwork provider and updates the status badge (`TheAudioDB`). The badge shows
one of:

- **Operational** — the test request succeeded.
- **Not tested** — no test has been run yet in this session.
- **Failed** — the test request errored; a toast describes which provider
failed so the issue can be fixed without scrolling back up.

## TheAudioDB

[TheAudioDB](https://www.theaudiodb.com) provides artist images (thumbnail and
background) keyed by MusicBrainz artist MBID. The public API requires a key,
and TheAudioDB publishes a free test key (`195003`) suitable for low-volume
use. Patrons of the project receive a personal key with higher limits.

:::warning
The default `195003` key is the **shared community test key** published by
TheAudioDB. It is rate-limited aggressively and may be revoked or throttled
without notice. For anything beyond casual personal use you should
[become a patron of TheAudioDB](https://www.patreon.com/theaudiodb) and
replace it with your own key.
:::

| Field | Default | Description |
| --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **API key** | `195003` | Your TheAudioDB API key. Defaults to the public test key shared by the community; supply your own from [theaudiodb.com](https://www.theaudiodb.com) for production use and higher limits. |
| **Max requests per second** | `25` | Outbound rate limit. |
| **Max concurrent requests** | `20` | Cap on in-flight requests. |

Responses are cached for six hours.

## Saving and Testing

The **Save** button under "Artwork Providers Configuration" persists changes to
`config/settings.json`. The **Test** buttons at the top and bottom of the page
make a single request per provider:

- TheAudioDB is tested with a known artist MBID
(`cc197bad-dc9c-440d-a5b5-d52ba2e14234` — Coldplay) using the currently
configured API key.

If a test fails, a toast is shown for that provider and its status badge is
updated.
83 changes: 83 additions & 0 deletions seerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,34 @@ components:
type: string
enum: [tvdb, tmdb]
example: 'tvdb'
ArtworkProvidersSettings:
type: object
properties:
theAudioDb:
type: object
properties:
apiKey:
type: string
example: '195003'
maxRPS:
type: integer
minimum: 1
example: 25
maxRequests:
type: integer
minimum: 1
example: 20
ArtworkProvidersTestResponse:
type: object
properties:
success:
type: boolean
tests:
type: object
properties:
theAudioDb:
type: string
enum: ['ok', 'failed', 'not tested']
TautulliSettings:
type: object
properties:
Expand Down Expand Up @@ -2723,6 +2751,61 @@ paths:
message:
type: string
example: 'Successfully connected to TVDB'
/settings/artwork-providers:
get:
summary: Get Artwork Provider settings
description: Retrieves current settings for the TheAudioDB client used by music.
tags:
- settings
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ArtworkProvidersSettings'
put:
summary: Update Artwork Provider settings
description: Updates Artwork Provider settings with the provided values.
tags:
- settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ArtworkProvidersSettings'
responses:
'200':
description: 'Values were successfully updated'
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
- $ref: '#/components/schemas/ArtworkProvidersSettings'
/settings/artwork-providers/test:
post:
summary: Test Artwork Provider connectivity
description: Probes TheAudioDB using the currently saved settings. Returns per-provider status.
tags:
- settings
responses:
'200':
description: All providers responded successfully
content:
application/json:
schema:
$ref: '#/components/schemas/ArtworkProvidersTestResponse'
'500':
description: One or more providers failed to respond
content:
application/json:
schema:
$ref: '#/components/schemas/ArtworkProvidersTestResponse'
/settings/tautulli:
get:
summary: Get Tautulli settings
Expand Down
100 changes: 100 additions & 0 deletions server/api/theaudiodb/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import ExternalAPI from '@server/api/externalapi';
import cacheManager from '@server/lib/cache';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import type { TadbArtistResponse } from './interfaces';

export const THE_AUDIO_DB_BASE_URL = 'https://www.theaudiodb.com/api/v1/json';
export const THE_AUDIO_DB_DEFAULT_API_KEY = '195003';
export const THE_AUDIO_DB_DEFAULT_MAX_RPS = 25;
export const THE_AUDIO_DB_DEFAULT_MAX_REQUESTS = 20;

class TheAudioDb extends ExternalAPI {
// 6 hours, matching the cache's stdTtl and the public docs.
private readonly CACHE_TTL = 21600;
private readonly apiKey: string;

constructor() {
const { theAudioDb } = getSettings().artworkProviders;
const apiKey = theAudioDb.apiKey;
const maxRPS =
theAudioDb.maxRPS > 0 ? theAudioDb.maxRPS : THE_AUDIO_DB_DEFAULT_MAX_RPS;
const maxRequests =
theAudioDb.maxRequests > 0
? theAudioDb.maxRequests
: THE_AUDIO_DB_DEFAULT_MAX_REQUESTS;

super(
THE_AUDIO_DB_BASE_URL,
{},
{
nodeCache: cacheManager.getCache('tadb').data,
rateLimit: {
maxRequests,
maxRPS,
},
}
);

this.apiKey = apiKey;
}

private createEmptyResponse() {
return { artistThumb: null, artistBackground: null };
}

public async getArtistImages(
id: string
): Promise<{ artistThumb: string | null; artistBackground: string | null }> {
if (!this.apiKey) {
return this.createEmptyResponse();
}
try {
const data = await this.get<TadbArtistResponse>(
`/${this.apiKey}/artist-mb.php`,
{ params: { i: id } },
this.CACHE_TTL
);

return {
artistThumb: data.artists?.[0]?.strArtistThumb || null,
artistBackground: data.artists?.[0]?.strArtistFanart || null,
};
} catch (error) {
logger.error('Failed to fetch artist images', {
label: 'TheAudioDb',
id,
error: error instanceof Error ? error.message : 'Unknown error',
});
return this.createEmptyResponse();
}
}

public hasApiKey(): boolean {
return Boolean(this.apiKey);
}

public async testConnection(): Promise<boolean> {
if (!this.apiKey) {
return false;
}
try {
// Hit a known MusicBrainz artist ID (Coldplay) to verify the API key
// is accepted and the upstream responds with a parseable payload.
const data = await this.get<TadbArtistResponse>(
`/${this.apiKey}/artist-mb.php`,
{ params: { i: 'cc197bad-dc9c-440d-a5b5-d52ba2e14234' } },
0
);
return Array.isArray(data.artists);
} catch (error) {
logger.error('TheAudioDB connection test failed', {
label: 'TheAudioDb',
error: error instanceof Error ? error.message : 'Unknown error',
});
return false;
}
}
}

export default TheAudioDb;
8 changes: 8 additions & 0 deletions server/api/theaudiodb/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface TadbArtist {
strArtistThumb: string | null;
strArtistFanart: string | null;
}

export interface TadbArtistResponse {
artists?: TadbArtist[];
}
5 changes: 5 additions & 0 deletions server/lib/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import NodeCache from 'node-cache';

export type AvailableCacheIds =
| 'tmdb'
| 'tadb'
| 'radarr'
| 'sonarr'
| 'rt'
Expand Down Expand Up @@ -49,6 +50,10 @@ class CacheManager {
stdTtl: 21600,
checkPeriod: 60 * 30,
}),
tadb: new Cache('tadb', 'The Audio Database API', {
stdTtl: 21600,
checkPeriod: 60 * 30,
}),
radarr: new Cache('radarr', 'Radarr API'),
sonarr: new Cache('sonarr', 'Sonarr API'),
lidarr: new Cache('lidarr', 'Lidarr API'),
Expand Down
29 changes: 29 additions & 0 deletions server/lib/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ export interface MetadataSettings {
anime: MetadataProviderType;
}

export interface TheAudioDbSettings {
apiKey: string;
maxRPS: number;
maxRequests: number;
}

export interface ArtworkProvidersSettings {
theAudioDb: TheAudioDbSettings;
}

export interface ProxySettings {
enabled: boolean;
hostname: string;
Expand Down Expand Up @@ -390,6 +400,7 @@ export interface AllSettings {
jobs: Record<JobId, JobSettings>;
network: NetworkSettings;
metadataSettings: MetadataSettings;
artworkProviders: ArtworkProvidersSettings;
migrations: string[];
}

Expand Down Expand Up @@ -459,6 +470,13 @@ class Settings {
tv: MetadataProviderType.TMDB,
anime: MetadataProviderType.TMDB,
},
artworkProviders: {
theAudioDb: {
apiKey: '195003',
maxRPS: 25,
maxRequests: 20,
},
},
radarr: [],
sonarr: [],
lidarr: [],
Expand Down Expand Up @@ -684,6 +702,17 @@ class Settings {
);
}

get artworkProviders(): ArtworkProvidersSettings {
return this.data.artworkProviders;
}

set artworkProviders(data: ArtworkProvidersSettings) {
this.data.artworkProviders = mergeSettings(
this.data.artworkProviders,
data
);
}

get radarr(): RadarrSettings[] {
return this.data.radarr;
}
Expand Down
Loading