On this page
Developer guide

Metadata

The Metadata context enriches catalog entities with data from external music APIs: Discogs, Last.fm, Spotify, MusicBrainz, and TasteDive. It handles metadata matching (pairing scanned files with external records), cover art retrieval, and audio file tag parsing (FLAC, ID3, OGG, WAV).

Domain Models

Model Type Purpose
CoverArt Value Object Typed cover art container (type, MIME, description, image data)
ExtractedMetadata Value Object Parsed audio tags from a media file
MatchQuality Value Object Match scoring between a scanned file and an external record
MetadataMatch Value Object A match result pairing a local file with an external record

Commands and Handlers

Command / Message Handler Purpose
ExtractAlbumCoverCommand ExtractAlbumCoverHandler Extract and store cover art from an album
SyncMetadataCommand SyncMetadataHandler The admin sync: without a source, one SyncLibraryMessage per library; with the genres source, only SyncGenresMessage. Returns the number of jobs queued
SyncLibraryMessage SyncLibraryHandler Enrich all entities in a library
SyncAlbumMessage SyncAlbumHandler Enrich a single album
SyncArtistMessage SyncArtistHandler Enrich a single artist
SyncGenresMessage SyncGenresHandler Queue a forced sync of every album and its songs; returns the number queued
SyncSongMessage SyncSongHandler Enrich a single song

Automatic Sync of New Albums

While the metadata.auto_sync system setting is on, Catalog ingest asks Metadata to sync each album it creates. FilesDiscoveredHandler calls AlbumMetadataSyncRequestInterface::requestSync() once the flush that stores the new album has run, before it adds the album's songs or reports failed files. A retry or rescan finds the album and creates none, so it requests nothing. Because a retry would not request the sync again, the request is best-effort: if it throws, ingest logs the error with the album ID and carries on. A full library metadata sync from the admin panel picks the album up.

AlbumMetadataSyncRequester reads the setting on every call. When the setting is on, it calls MetadataSyncOrchestrator::syncAlbum() for each album. The orchestrator dispatches SyncAlbumMessage, which runs later from the swoole_task queue, or from the Redis async transport outside the Swoole server, so ingest does not wait for the external lookups. The setting is off by default.

The admin sync, POST /api/admin/metadata/trigger-sync, and app:metadata:sync dispatch SyncMetadataCommand and ignore the setting. The command runs it through JobMonitorAdministrationInterface::runInline(), which records the run in the job monitor. An unknown source is rejected with a 422 response or an invalid-input exit.

Ports

Port Purpose
MetadataAdminPortInterface Sync status and provider list for the admin API and app:metadata:status and app:metadata:providers
AlbumMetadataSyncRequestInterface Published contract for Catalog ingest: requests a sync of newly created albums while metadata.auto_sync is on

API Endpoints

All endpoints are prefixed with /api.

Method Path Controller Purpose
GET /api/metadata/search/artist MetadataSearchController Search external APIs for an artist
GET /api/metadata/search/album MetadataSearchController Search external APIs for an album
GET /api/metadata/search/song MetadataSearchController Search external APIs for a song
GET /api/metadata/browse/artist/{mbid} MetadataBrowseController Browse MusicBrainz artist details
GET /api/metadata/browse/release-group/{mbid} MetadataBrowseController Browse MusicBrainz release group details
POST /api/metadata/extract MetadataSyncController Extract metadata for a media file
POST /api/metadata/match MetadataSyncController Match a scanned file against external records

Admin — Metadata

Routes under /api/admin/metadata (controller MetadataAdminController, gated ROLE_ADMIN).

Method Path Controller Purpose
GET /api/admin/metadata/sync-status MetadataAdminController Current enrichment/sync status
POST /api/admin/metadata/trigger-sync MetadataAdminController Queue a sync for every library, or with source: genres only the genre sync; returns the number of jobs queued
GET /api/admin/metadata/providers MetadataAdminController List configured metadata providers

Cross-Context Dependencies

Direction Context Relationship
Depends on Shared Uses Uuid for identifiers, and SystemSettingsPortInterface for metadata.auto_sync
Depends on Catalog Updates albums, artists, and songs with enriched data
Depends on Library MetadataSyncOrchestrator looks up libraries through LibraryPortInterface
Depended on by Catalog Ingest requests a sync of each new album through AlbumMetadataSyncRequestInterface (the Metadata Album Sync Request Contract Deptrac layer)

Infrastructure

External API Adapters

Each adapter lives under Infrastructure/Api/<Provider>/ and includes its own DTOs for request/response mapping.

Adapter Location
DiscogsAdapter Infrastructure/Api/Discogs/
LastFmAdapter Infrastructure/Api/LastFm/
SpotifyAdapter Infrastructure/Api/Spotify/
MusicBrainzAdapter Infrastructure/Api/MusicBrainz/
TasteDiveAdapter Infrastructure/Api/TasteDive/
CoverArtArchiveAdapter Infrastructure/Api/CoverArtArchive/

Audio Format Readers

Reader Formats
FlacReader / FlacParser FLAC
Id3Reader / Id3Parser MP3 (ID3v2 tags)
OggReader / OggParser OGG Vorbis
WavReader WAV

A FormatDetector selects the correct reader based on file MIME type.

Matching

The Infrastructure/Matching/ directory contains strategies and validators for pairing scanned files with external records. MatchingStrategy defines the matching contract, and Validator/ contains domain-specific validation rules.