obsidian-media-db-sync/src/api/apis/MusicBrainzAPI.ts
2025-08-10 13:59:18 +02:00

203 lines
5.4 KiB
TypeScript

import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
import { MediaType } from '../../utils/MediaType';
import { contactEmail, extractTracksFromMedia, mediaDbVersion, pluginName } from '../../utils/Utils';
import { APIModel } from '../APIModel';
// sadly no open api schema available
interface Tag {
name: string;
count: number;
}
interface Genre {
name: string;
count: number;
id: string;
disambiguation: string;
}
interface Release {
id: string;
'status-id': string;
title: string;
status: string;
}
interface SearchResponse {
id: string;
'type-id': string;
score: number;
'primary-type-id': string;
'artists-credit-id': string;
count: number;
title: string;
'first-release-date': string;
'primary-type': string;
'artist-credit': {
name: string;
artist: {
id: string;
name: string;
'short-name': string;
};
}[];
releases: Release[];
tags: Tag[];
}
interface IdResponse {
id: string;
tags: Tag[];
'primary-type-id': string;
'artist-credit': {
name: string;
artist: {
tags: Tag[];
type: string;
id: string;
name: string;
'short-name': string;
country: string;
};
}[];
title: string;
genres: Genre[];
'first-release-date': string;
releases: Release[];
'primary-type': string;
rating: {
value: number;
'votes-count': number;
};
}
export class MusicBrainzAPI extends APIModel {
plugin: MediaDbPlugin;
apiDateFormat: string = 'YYYY-MM-DD';
constructor(plugin: MediaDbPlugin) {
super();
this.plugin = plugin;
this.apiName = 'MusicBrainz API';
this.apiDescription = 'Free API for music albums.';
this.apiUrl = 'https://musicbrainz.org/';
this.types = [MediaType.MusicRelease];
}
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
console.log(`MDB | api "${this.apiName}" queried by Title`);
const searchUrl = `https://musicbrainz.org/ws/2/release-group?query=${encodeURIComponent(title)}&limit=20&fmt=json`;
const fetchData = await requestUrl({
url: searchUrl,
headers: {
'User-Agent': `${pluginName}/${mediaDbVersion} (${contactEmail})`,
},
});
// console.debug(fetchData);
if (fetchData.status !== 200) {
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
}
const data = (await fetchData.json) as {
'release-groups': SearchResponse[];
};
// console.debug(data);
const ret: MediaTypeModel[] = [];
for (const result of data['release-groups']) {
ret.push(
new MusicReleaseModel({
type: 'musicRelease',
title: result.title,
englishTitle: result.title,
year: new Date(result['first-release-date']).getFullYear().toString(),
releaseDate: this.plugin.dateFormatter.format(result['first-release-date'], this.apiDateFormat) ?? 'unknown',
dataSource: this.apiName,
url: 'https://musicbrainz.org/release-group/' + result.id,
id: result.id,
image: 'https://coverartarchive.org/release-group/' + result.id + '/front-500.jpg',
artists: result['artist-credit'].map(a => a.name),
subType: result['primary-type'],
}),
);
}
return ret;
}
async getById(id: string): Promise<MediaTypeModel> {
console.log(`MDB | api "${this.apiName}" queried by ID`);
// Fetch release group
const groupUrl = `https://musicbrainz.org/ws/2/release-group/${encodeURIComponent(id)}?inc=releases+artists+tags+ratings+genres&fmt=json`;
const groupResponse = await requestUrl({
url: groupUrl,
headers: {
'User-Agent': `${pluginName}/${mediaDbVersion} (${contactEmail})`,
},
});
if (groupResponse.status !== 200) {
throw Error(`MDB | Received status code ${groupResponse.status} from ${this.apiName}.`);
}
const result = (await groupResponse.json) as IdResponse;
// Get ID of the first release
const firstRelease = result.releases?.[0];
if (!firstRelease) {
throw Error('MDB | No releases found in release group.');
}
// Fetch recordings for the first release
const releaseUrl = `https://musicbrainz.org/ws/2/release/${firstRelease.id}?inc=recordings+artists&fmt=json`;
console.log(`MDB | Fetching release recordings from: ${releaseUrl}`);
const releaseResponse = await requestUrl({
url: releaseUrl,
headers: {
'User-Agent': `${pluginName}/${mediaDbVersion} (${contactEmail})`,
},
});
if (releaseResponse.status !== 200) {
throw Error(`MDB | Received status code ${releaseResponse.status} from ${this.apiName}.`);
}
const releaseData = await releaseResponse.json;
const tracks = extractTracksFromMedia(releaseData.media);
return new MusicReleaseModel({
type: 'musicRelease',
title: result.title,
englishTitle: result.title,
year: new Date(result['first-release-date']).getFullYear().toString(),
releaseDate: this.plugin.dateFormatter.format(result['first-release-date'], this.apiDateFormat) ?? 'unknown',
dataSource: this.apiName,
url: 'https://musicbrainz.org/release-group/' + result.id,
id: result.id,
image: 'https://coverartarchive.org/release-group/' + result.id + '/front-500.jpg',
artists: result['artist-credit'].map(a => a.name),
genres: result.genres.map(g => g.name),
subType: result['primary-type'],
tracks: tracks,
rating: result.rating.value * 2,
userData: {
personalRating: 0,
},
});
}
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.MusicBrainzAPI_disabledMediaTypes;
}
}