Attribute Source

An AttributeSource is the plugin component that enriches tracks, artists, and albums with metadata. It is called after a track is indexed and whenever an attribute task is run.

Registration

ctx.registerAttributeSource({
	id: "my-source",
	enable(apiCtx) {
		/* register attribute definitions */
	},
	getName() {
		return "My Attribute Source";
	},
	async getTrackAttributeValues(helper) {
		/* return metadata */
	},
	async getArtistAttributeValues(helper) {
		/* return metadata */
	},
	async getAlbumAttributeValues(helper) {
		/* return metadata */
	},
});

Interface

enable(apiCtx)

Called once when the source is registered. Use the AttributeSourceApiContext to declare the attributes your source will produce:

enable(apiCtx) {
    apiCtx.registerTrackAttributes([
        { key: "title", type: "string", supportsMultiple: false },
        { key: "genre", type: "string", supportsMultiple: true },
        { key: "front", type: "buffer", supportsMultiple: false },
    ]);
    apiCtx.registerArtistAttributes([
        { key: "name", type: "string", supportsMultiple: false },
    ]);
    apiCtx.registerAlbumAttributes([
        { key: "title", type: "string", supportsMultiple: false },
        { key: "front", type: "buffer", supportsMultiple: false },
    ]);
}

Declaring attributes here makes them available as filter targets in users/Playlists and users/Search if the currently configured search source supports it. an attribute source cannot return any attributes that it hasn't registered.

getTrackAttributeValues(helper)

Called for each track during attribution. Returns a TrackMetadata object:

async getTrackAttributeValues(helper) {
    const identity = await helper.getIdentity("my-identifier", null);
    if (!identity) {
        return { attributes: null, artists: null };
    }

    const data = await fetchFromMyApi(identity.identity);

    return {
        attributes: [
            { key: "title", value: data.title },
            { key: "genre", value: data.genre },
        ],
        artists: [
            {
                pluginId: null,
                identityId: "my-artist-identifier",
                identity: data.artistId,
                attributes: [{ key: "name", value: data.artistName }],
            },
        ],
    };
}

The TrackAttributionHelper gives you:

Method Purpose
getIdentity(id, pluginId) Get an identity produced by a named users/Identifiers
getCompletedAttributeKeys() Keys already filled by earlier sources - lets you skip redundant lookups
getTrackUuid() The server's internal UUID for this track
getPluginId() / getLibraryId() / getTrackId() The track's library coordinates
getAudioProducer() Access the raw audio (for reading embedded tags, etc.)

getArtistAttributeValues(helper) / getAlbumAttributeValues(helper)

Same pattern. The ArtistInformationHelper and AlbumInformationHelper provide getIdentity() and their respective UUID getter.

Attribute types

See users/Attributes for the full type list. Buffer attributes (type: "buffer") carry binary data:

{ key: "front", value: { extension: "jpg", buffer: imageBuffer } }
// or lazy:
{ key: "front", value: { extension: "jpg", buffer: async () => fetchImage() } }

Source ordering

All registered attribute sources run in registration order for each entity. Later sources see which keys are already filled via getCompletedAttributeKeys() but cannot read the values set by earlier sources. Attribute values are preserved per-plugin and per-attribute source, so two attribute sources can safely register and populate attributes with the same key.

In the event that there are multiple definitions for the same attribute key, you can select which source should take priority in Settings → Attribute Sources.