Merge pull request #202 from ltctceplrm/musictracks
Add extra metadata for musicbrainz API
This commit is contained in:
commit
6699805001
5 changed files with 78 additions and 9 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "obsidian-media-db-plugin",
|
"name": "obsidian-media-db-plugin",
|
||||||
"version": "0.8.0",
|
"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",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run automation/build/esbuild.dev.config.ts",
|
"dev": "bun run automation/build/esbuild.dev.config.ts",
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.32.0",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-only-warn": "^1.1.0",
|
"eslint-plugin-only-warn": "^1.1.0",
|
||||||
|
"iso-639-2": "^3.0.2",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
"openapi-fetch": "^0.14.0",
|
"openapi-fetch": "^0.14.0",
|
||||||
"openapi-typescript": "^7.8.0",
|
"openapi-typescript": "^7.8.0",
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import type MediaDbPlugin from '../../main';
|
||||||
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
import type { MediaTypeModel } from '../../models/MediaTypeModel';
|
||||||
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
|
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
|
||||||
import { MediaType } from '../../utils/MediaType';
|
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 { APIModel } from '../APIModel';
|
||||||
|
import { iso6392 } from 'iso-639-2';
|
||||||
|
|
||||||
// sadly no open api schema available
|
// sadly no open api schema available
|
||||||
|
|
||||||
|
|
@ -136,19 +137,44 @@ export class MusicBrainzAPI extends APIModel {
|
||||||
async getById(id: string): Promise<MediaTypeModel> {
|
async getById(id: string): Promise<MediaTypeModel> {
|
||||||
console.log(`MDB | api "${this.apiName}" queried by ID`);
|
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`;
|
// Fetch release group
|
||||||
const fetchData = await requestUrl({
|
const groupUrl = `https://musicbrainz.org/ws/2/release-group/${encodeURIComponent(id)}?inc=releases+artists+tags+ratings+genres&fmt=json`;
|
||||||
url: searchUrl,
|
const groupResponse = await requestUrl({
|
||||||
|
url: groupUrl,
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': `${pluginName}/${mediaDbVersion} (${contactEmail})`,
|
'User-Agent': `${pluginName}/${mediaDbVersion} (${contactEmail})`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fetchData.status !== 200) {
|
if (groupResponse.status !== 200) {
|
||||||
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
|
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({
|
return new MusicReleaseModel({
|
||||||
type: 'musicRelease',
|
type: 'musicRelease',
|
||||||
|
|
@ -162,8 +188,11 @@ export class MusicBrainzAPI extends APIModel {
|
||||||
image: 'https://coverartarchive.org/release-group/' + result.id + '/front-500.jpg',
|
image: 'https://coverartarchive.org/release-group/' + result.id + '/front-500.jpg',
|
||||||
|
|
||||||
artists: result['artist-credit'].map(a => a.name),
|
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.genres.map(g => g.name),
|
||||||
subType: result['primary-type'],
|
subType: result['primary-type'],
|
||||||
|
trackCount: releaseData.media[0]['track-count'] ?? 0,
|
||||||
|
tracks: tracks,
|
||||||
rating: result.rating.value * 2,
|
rating: result.rating.value * 2,
|
||||||
|
|
||||||
userData: {
|
userData: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { MediaType } from '../utils/MediaType';
|
import { MediaType } from '../utils/MediaType';
|
||||||
import type { ModelToData } from '../utils/Utils';
|
import type { ModelToData } from '../utils/Utils';
|
||||||
import { mediaDbTag, migrateObject } from '../utils/Utils';
|
import { mediaDbTag, migrateObject, getLanguageName } from '../utils/Utils';
|
||||||
import { MediaTypeModel } from './MediaTypeModel';
|
import { MediaTypeModel } from './MediaTypeModel';
|
||||||
|
|
||||||
export type MusicReleaseData = ModelToData<MusicReleaseModel>;
|
export type MusicReleaseData = ModelToData<MusicReleaseModel>;
|
||||||
|
|
@ -8,9 +8,17 @@ export type MusicReleaseData = ModelToData<MusicReleaseModel>;
|
||||||
export class MusicReleaseModel extends MediaTypeModel {
|
export class MusicReleaseModel extends MediaTypeModel {
|
||||||
genres: string[];
|
genres: string[];
|
||||||
artists: string[];
|
artists: string[];
|
||||||
|
language: string;
|
||||||
image: string;
|
image: string;
|
||||||
rating: number;
|
rating: number;
|
||||||
releaseDate: string;
|
releaseDate: string;
|
||||||
|
trackCount: number;
|
||||||
|
tracks: {
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
duration: string;
|
||||||
|
featuredArtists: string[];
|
||||||
|
}[];
|
||||||
|
|
||||||
userData: {
|
userData: {
|
||||||
personalRating: number;
|
personalRating: number;
|
||||||
|
|
@ -36,6 +44,9 @@ export class MusicReleaseModel extends MediaTypeModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.type = this.getMediaType();
|
this.type = this.getMediaType();
|
||||||
|
this.trackCount = obj.trackCount ?? 0;
|
||||||
|
this.tracks = obj.tracks ?? [];
|
||||||
|
this.language = obj.language ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
getTags(): string[] {
|
getTags(): string[] {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { TFile, TFolder, App } from 'obsidian';
|
import type { TFile, TFolder, App } from 'obsidian';
|
||||||
import { requestUrl } from 'obsidian';
|
import { requestUrl } from 'obsidian';
|
||||||
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
import type { MediaTypeModel } from '../models/MediaTypeModel';
|
||||||
|
import { iso6392 } from 'iso-639-2';
|
||||||
|
|
||||||
export const pluginName: string = 'obsidian-media-db-plugin';
|
export const pluginName: string = 'obsidian-media-db-plugin';
|
||||||
export const contactEmail: string = 'm.projects.code@gmail.com';
|
export const contactEmail: string = 'm.projects.code@gmail.com';
|
||||||
|
|
@ -296,3 +297,30 @@ export async function obsidianFetch(input: Request): Promise<Response> {
|
||||||
text: async () => res.text,
|
text: async () => res.text,
|
||||||
} as Response;
|
} 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;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue