diff --git a/bun.lockb b/bun.lockb index bc729f4..88a3ee5 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index f45d3bf..1d4f84f 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", @@ -34,6 +34,7 @@ "eslint": "^9.32.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-only-warn": "^1.1.0", + "iso-639-2": "^3.0.2", "obsidian": "latest", "openapi-fetch": "^0.14.0", "openapi-typescript": "^7.8.0", diff --git a/src/api/apis/MusicBrainzAPI.ts b/src/api/apis/MusicBrainzAPI.ts index b567560..5bebd6d 100644 --- a/src/api/apis/MusicBrainzAPI.ts +++ b/src/api/apis/MusicBrainzAPI.ts @@ -3,8 +3,9 @@ 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, getLanguageName, mediaDbVersion, pluginName } from '../../utils/Utils'; import { APIModel } from '../APIModel'; +import { iso6392 } from 'iso-639-2'; // sadly no open api schema available @@ -136,19 +137,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 fetchData.json) as IdResponse; + 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', @@ -162,8 +188,11 @@ export class MusicBrainzAPI extends APIModel { image: 'https://coverartarchive.org/release-group/' + result.id + '/front-500.jpg', 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), subType: result['primary-type'], + trackCount: releaseData.media[0]['track-count'] ?? 0, + tracks: tracks, rating: result.rating.value * 2, userData: { diff --git a/src/models/MusicReleaseModel.ts b/src/models/MusicReleaseModel.ts index 900f233..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,17 @@ 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; + duration: string; + featuredArtists: string[]; + }[]; userData: { personalRating: number; @@ -36,6 +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 eb4bbaa..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'; @@ -296,3 +297,30 @@ 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, + }; + }); +} +export function getLanguageName(code: string): string | null { + const language = iso6392.find(lang => lang.iso6392B === code || lang.iso6392T === code); + + return language?.name ?? null; +}