From d5c9faa0a739a3dd12169fe99359dc8339b58d49 Mon Sep 17 00:00:00 2001 From: ltctceplrm <14954927+ltctceplrm@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:54:42 +0200 Subject: [PATCH 1/5] Add tracks to musicbrainz API --- src/api/apis/MusicBrainzAPI.ts | 40 +++++++++++++++++++++++++++------ src/models/MusicReleaseModel.ts | 7 ++++++ src/utils/Utils.ts | 22 ++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/api/apis/MusicBrainzAPI.ts b/src/api/apis/MusicBrainzAPI.ts index b567560..c512886 100644 --- a/src/api/apis/MusicBrainzAPI.ts +++ b/src/api/apis/MusicBrainzAPI.ts @@ -3,7 +3,7 @@ import type MediaDbPlugin from '../../main'; import type { MediaTypeModel } from '../../models/MediaTypeModel'; import { MusicReleaseModel } from '../../models/MusicReleaseModel'; import { MediaType } from '../../utils/MediaType'; -import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils'; +import { contactEmail, extractTracksFromMedia, mediaDbVersion, pluginName } from '../../utils/Utils'; import { APIModel } from '../APIModel'; // sadly no open api schema available @@ -136,19 +136,44 @@ export class MusicBrainzAPI extends APIModel { async getById(id: string): Promise { console.log(`MDB | api "${this.apiName}" queried by ID`); - const searchUrl = `https://musicbrainz.org/ws/2/release-group/${encodeURIComponent(id)}?inc=releases+artists+tags+ratings+genres&fmt=json`; - const fetchData = await requestUrl({ - url: searchUrl, + // 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 (fetchData.status !== 200) { - throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`); + 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 result = (await fetchData.json) as IdResponse; + const releaseData = await releaseResponse.json; + const tracks = extractTracksFromMedia(releaseData.media); return new MusicReleaseModel({ type: 'musicRelease', @@ -164,6 +189,7 @@ export class MusicBrainzAPI extends APIModel { 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: { diff --git a/src/models/MusicReleaseModel.ts b/src/models/MusicReleaseModel.ts index 900f233..456a6dc 100644 --- a/src/models/MusicReleaseModel.ts +++ b/src/models/MusicReleaseModel.ts @@ -11,6 +11,12 @@ export class MusicReleaseModel extends MediaTypeModel { image: string; rating: number; releaseDate: string; + tracks: { + number: number; + title: string; + duration: string; + featuredArtists: string[]; + }[]; userData: { personalRating: number; @@ -36,6 +42,7 @@ export class MusicReleaseModel extends MediaTypeModel { } this.type = this.getMediaType(); + this.tracks = obj.tracks ?? []; } getTags(): string[] { diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index eb4bbaa..aa9786d 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -296,3 +296,25 @@ export async function obsidianFetch(input: Request): Promise { text: async () => res.text, } as Response; } +export function extractTracksFromMedia(media: any[]): { + number: number; + title: string; + duration: string; + featuredArtists: string[]; +}[] { + if (!media || media.length === 0 || !media[0].tracks) return []; + + return media[0].tracks.map((track: any, index: number) => { + const title = track.title || track.recording?.title || 'Unknown Title'; + const rawLength = track.length || track.recording?.length; + const duration = rawLength ? new Date(rawLength).toISOString().substr(14, 5) : 'unknown'; + const featuredArtists = track['artist-credit']?.map((ac: { name: string }) => ac.name) ?? []; + + return { + number: index + 1, + title, + duration, + featuredArtists, + }; + }); +} From b6f8258484f24a294ca1f113a6c809a42d18c120 Mon Sep 17 00:00:00 2001 From: ltctceplrm <14954927+ltctceplrm@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:26:38 +0200 Subject: [PATCH 2/5] Add new mediatypes to package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f45d3bf..b5f3492 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "obsidian-media-db-plugin", "version": "0.8.0", - "description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.", + "description": "A plugin that can query multiple APIs for movies, series, anime, manga, books, comics, games, music and wiki articles, and import them into your vault.", "main": "main.js", "scripts": { "dev": "bun run automation/build/esbuild.dev.config.ts", From f51a1465b40ca9f4a94c274a7a389619cfa436d2 Mon Sep 17 00:00:00 2001 From: ltctceplrm <14954927+ltctceplrm@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:17:00 +0200 Subject: [PATCH 3/5] Added language and trackCount fields for music The language at musicbrainz is in the format of iso-639-2 so I use a package to translate it to a full name of the language. --- bun.lockb | Bin 181119 -> 181474 bytes package.json | 1 + src/api/apis/MusicBrainzAPI.ts | 5 ++++- src/models/MusicReleaseModel.ts | 6 +++++- src/utils/Utils.ts | 6 ++++++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index bc729f40d1a3ccd338ad90326193a048b3077605..88a3ee5f70111cd8f58262868a4e3975e46cf3ca 100755 GIT binary patch delta 31296 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 n` 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 Xrki*v$5l)8D{ a.name), + language: releaseData['text-representation'].language ? getLanguageName(releaseData['text-representation'].language) : 'Unknown', genres: result.genres.map(g => g.name), subType: result['primary-type'], + trackCount: releaseData.media[0]['track-count'] ?? 0, tracks: tracks, rating: result.rating.value * 2, diff --git a/src/models/MusicReleaseModel.ts b/src/models/MusicReleaseModel.ts index 456a6dc..a358089 100644 --- a/src/models/MusicReleaseModel.ts +++ b/src/models/MusicReleaseModel.ts @@ -1,6 +1,6 @@ import { MediaType } from '../utils/MediaType'; import type { ModelToData } from '../utils/Utils'; -import { mediaDbTag, migrateObject } from '../utils/Utils'; +import { mediaDbTag, migrateObject, getLanguageName } from '../utils/Utils'; import { MediaTypeModel } from './MediaTypeModel'; export type MusicReleaseData = ModelToData; @@ -8,9 +8,11 @@ export type MusicReleaseData = ModelToData; export class MusicReleaseModel extends MediaTypeModel { genres: string[]; artists: string[]; + language: string; image: string; rating: number; releaseDate: string; + trackCount: number; tracks: { number: number; title: string; @@ -42,7 +44,9 @@ export class MusicReleaseModel extends MediaTypeModel { } this.type = this.getMediaType(); + this.trackCount = obj.trackCount ?? 0; this.tracks = obj.tracks ?? []; + this.language = obj.language ?? ''; } getTags(): string[] { diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index aa9786d..10e6746 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,6 +1,7 @@ import type { TFile, TFolder, App } from 'obsidian'; import { requestUrl } from 'obsidian'; import type { MediaTypeModel } from '../models/MediaTypeModel'; +import { iso6392 } from 'iso-639-2'; export const pluginName: string = 'obsidian-media-db-plugin'; export const contactEmail: string = 'm.projects.code@gmail.com'; @@ -318,3 +319,8 @@ export function extractTracksFromMedia(media: any[]): { }; }); } +export function getLanguageName(code: string): string | null { + const language = iso6392.find(lang => lang.iso6392B === code || lang.iso6392T === code); + + return language?.name ?? null; +} From 6a16fe34cde0030fd3d7d50b28a1090041b2e975 Mon Sep 17 00:00:00 2001 From: ltctceplrm <14954927+ltctceplrm@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:26:44 +0200 Subject: [PATCH 4/5] Fixed genres Musicbrainz API renamed it from genres to tags --- src/api/apis/MusicBrainzAPI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/apis/MusicBrainzAPI.ts b/src/api/apis/MusicBrainzAPI.ts index 5bebd6d..f06ffe0 100644 --- a/src/api/apis/MusicBrainzAPI.ts +++ b/src/api/apis/MusicBrainzAPI.ts @@ -138,7 +138,7 @@ export class MusicBrainzAPI extends APIModel { 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 groupUrl = `https://musicbrainz.org/ws/2/release-group/${encodeURIComponent(id)}?inc=releases+artists+tags+ratings+genres+tags&fmt=json`; const groupResponse = await requestUrl({ url: groupUrl, headers: { @@ -189,7 +189,7 @@ export class MusicBrainzAPI extends APIModel { artists: result['artist-credit'].map(a => a.name), language: releaseData['text-representation'].language ? getLanguageName(releaseData['text-representation'].language) : 'Unknown', - genres: result.genres.map(g => g.name), + genres: result.tags.map(g => g.name), subType: result['primary-type'], trackCount: releaseData.media[0]['track-count'] ?? 0, tracks: tracks, From 6170e0760b550beccd7b362dee4525f51dd99f87 Mon Sep 17 00:00:00 2001 From: ltctceplrm <14954927+ltctceplrm@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:30:43 +0200 Subject: [PATCH 5/5] Revert "Fixed genres" This reverts commit 6a16fe34cde0030fd3d7d50b28a1090041b2e975. --- src/api/apis/MusicBrainzAPI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/apis/MusicBrainzAPI.ts b/src/api/apis/MusicBrainzAPI.ts index f06ffe0..5bebd6d 100644 --- a/src/api/apis/MusicBrainzAPI.ts +++ b/src/api/apis/MusicBrainzAPI.ts @@ -138,7 +138,7 @@ export class MusicBrainzAPI extends APIModel { 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+tags&fmt=json`; + 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: { @@ -189,7 +189,7 @@ export class MusicBrainzAPI extends APIModel { artists: result['artist-credit'].map(a => a.name), language: releaseData['text-representation'].language ? getLanguageName(releaseData['text-representation'].language) : 'Unknown', - genres: result.tags.map(g => g.name), + genres: result.genres.map(g => g.name), subType: result['primary-type'], trackCount: releaseData.media[0]['track-count'] ?? 0, tracks: tracks,