Skip to content

Commit

Permalink
ExtendedTrackItem - Improve/Fix MusicBrainz lookups to use track inst…
Browse files Browse the repository at this point in the history
…ead of recording
  • Loading branch information
Inrixia committed Oct 7, 2024
1 parent 0b5aef5 commit fab593c
Show file tree
Hide file tree
Showing 22 changed files with 331 additions and 260 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"typescript": "^5.5.4"
},
"dependencies": {
"@inrixia/neptune-plugins": "file:",
"dasha": "^3.0.3",
"flac-stream-tagger": "^1.0.9",
"idb": "^8.0.0",
Expand Down
12 changes: 6 additions & 6 deletions plugins/ListenBrainz/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ const makeTrackPayload = async ({ metaTags, playbackStart, playbackContext, extT
},
};

const recording = await extTrackItem.recording();
const releaseTrack = await extTrackItem.releaseTrack();
const additional_info = {
recording_mbid: recording?.id,
isrc: extTrackItem.trackItem.isrc,
tracknumber: extTrackItem.trackItem.trackNumber,
recording_mbid: releaseTrack?.id,
isrc: extTrackItem.tidalTrack.isrc ?? releaseTrack?.recording.isrcs?.[0],
tracknumber: extTrackItem.tidalTrack.trackNumber,
music_service: MusicServiceDomain.TIDAL,
origin_url: extTrackItem.trackItem.url,
duration: extTrackItem.trackItem.duration,
origin_url: extTrackItem.tidalTrack.url,
duration: extTrackItem.tidalTrack.duration,
media_player: "Tidal Desktop",
submission_client: "Neptune Scrobbler",
};
Expand Down
86 changes: 53 additions & 33 deletions plugins/_lib/Caches/ExtendedTrackItem.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { ItemId, Album } from "neptune-types/tidal";
import type { PlaybackContext } from "../AudioQualityTypes";
import { MusicBrainz, Release, Recording } from "../api/musicbrainz";
import { MediaItem, MediaItemCache } from "./MediaItemCache";
import { AlbumCache } from "./AlbumCache";
import { libTrace } from "../trace";
import getPlaybackControl from "../getPlaybackControl";

import type { IRecording, IReleaseMatch, ITrack } from "musicbrainz-api";
import { requestJsonCached } from "../nativeBridge/request";

export class ExtendedMediaItem {
private _album?: Album;
private _recording?: Recording;
private _releaseAlbum?: Release;
private _releaseTrack?: ITrack;
private _releaseAlbum?: IReleaseMatch;

private static readonly _cache: Record<ItemId, ExtendedMediaItem> = {};
private constructor(public readonly trackId: ItemId, public readonly trackItem: MediaItem) {}
private constructor(public readonly trackId: ItemId, public readonly tidalTrack: MediaItem) {}

public static current(playbackContext?: PlaybackContext) {
playbackContext ??= getPlaybackControl()?.playbackContext;
Expand All @@ -24,55 +24,75 @@ export class ExtendedMediaItem {
if (trackId === undefined) return undefined;
const trackItem = await MediaItemCache.ensure(trackId);
if (trackItem === undefined) return undefined;
return this._cache[trackId] ?? (this._cache[trackId] = new this(trackId, trackItem));
return new this(trackId, trackItem);
}

public async isrcs(): Promise<Set<string> | undefined> {
let isrcs = [];

const recording = await this.recording();
if (recording?.isrcs) isrcs.push(...recording.isrcs);
const recording = await this.releaseTrack();
if (recording?.recording.isrcs) isrcs.push(...recording.recording.isrcs);

const trackItem = this.trackItem;
const trackItem = this.tidalTrack;
if (trackItem.isrc) isrcs.push(trackItem.isrc);

return new Set(isrcs);
}
public async album(): Promise<Album | undefined> {
if (this._album !== undefined) return this._album;
return (this._album = await AlbumCache.get(this.trackItem.album?.id));

public tidalAlbum(): Promise<Album | undefined> {
return AlbumCache.get(this.tidalTrack.album?.id);
}
public async recording(): Promise<Recording | undefined> {
if (this._recording !== undefined) return this._recording;

this._recording = await MusicBrainz.getRecording(this.trackItem.isrc).catch(libTrace.warn.withContext("MusicBrainz.getRecording"));
if (this._recording !== undefined) return this._recording;
public async releaseAlbum() {
if (this._releaseAlbum !== undefined) return this._releaseAlbum;

const trackItem = this.trackItem;
if (trackItem === undefined) return undefined;
const albumId = this.tidalTrack.album?.id;
if (albumId === undefined) return undefined;
const album = await this.tidalAlbum();
if (album?.upc === undefined) return undefined;
return (this._releaseAlbum = await requestJsonCached<{ releases: IReleaseMatch[] }>(`https://musicbrainz.org/ws/2/release/?query=barcode:${album.upc}&fmt=json`)
.then(({ releases }) => releases[0])
.catch(libTrace.warn.withContext("MusicBrainz.getUPCReleases")));
}

const releaseAlbum = await this.releaseAlbum();
const albumRelease = await MusicBrainz.getAlbumRelease(releaseAlbum?.id).catch(libTrace.warn.withContext("MusicBrainz.getAlbumRelease"));
public async releaseTrack(): Promise<ITrack | undefined> {
if (this._releaseTrack !== undefined) return this._releaseTrack;

const volumeNumber = (trackItem.volumeNumber ?? 1) - 1;
const trackNumber = (trackItem.trackNumber ?? 1) - 1;
if (this.tidalTrack.isrc !== undefined) {
// Lookup the recording from MusicBrainz by ISRC
const recording = await requestJsonCached<{ recordings: IRecording[] }>(`https://musicbrainz.org/ws/2/isrc/${this.tidalTrack.isrc}?fmt=json`)
.then(({ recordings }) => recordings[0])
.catch(libTrace.warn.withContext("MusicBrainz.getISRCRecordings"));
if (recording === undefined) return undefined;

return (this._recording = albumRelease?.media?.[volumeNumber]?.tracks?.[trackNumber]?.recording);
}
public async releaseAlbum() {
if (this._releaseAlbum !== undefined) return this._releaseAlbum;
// If a recording exists then fetch the full recording details including media for title resolution
const release = await requestJsonCached<IRecording>(`https://musicbrainz.org/ws/2/recording/${recording.id}?inc=releases+media&fmt=json`)
.then(({ releases }) => releases?.filter((release) => release.country === "XW")[0] ?? releases?.[0])
.catch(libTrace.warn.withContext("MusicBrainz.getISRCRecordings"));
if (release === undefined) return undefined;

return (this._releaseTrack = release.media?.[0].tracks?.[0]);
}

const releaseAlbum = await this.releaseAlbum();
if (releaseAlbum === undefined) return undefined;

const albumRelease = await requestJsonCached<{ releases: IReleaseMatch[] }>(`https://musicbrainz.org/ws/2/release/${releaseAlbum.id}?inc=recordings+isrcs&fmt=json`)
.then(({ releases }) => releases[0])
.catch(libTrace.warn.withContext("MusicBrainz.getReleaseAlbum"));

const album = await this.album();
const upcData = await MusicBrainz.getUPCData(album?.upc).catch(libTrace.warn.withContext("MusicBrainz.getUPCData"));
const volumeNumber = (this.tidalTrack.volumeNumber ?? 1) - 1;
const trackNumber = (this.tidalTrack.trackNumber ?? 1) - 1;

return (this._releaseAlbum = upcData?.releases?.[0]);
return (this._releaseTrack = albumRelease?.media?.[volumeNumber]?.tracks?.[trackNumber]);
}

public async everything() {
return {
trackItem: this.trackItem,
album: await this.album(),
tidalTrack: this.tidalTrack,
tidalAlbum: await this.tidalAlbum(),
releaseTrack: await this.releaseTrack(),
releaseAlbum: await this.releaseAlbum(),
recording: await this.recording(),
};
}
}
18 changes: 18 additions & 0 deletions plugins/_lib/Caches/SharedNativeCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { cacheEnsure, cacheRej, cacheRes } from "../nativeBridge";

export class SharedNativeCache<K extends string | number | symbol, V> {
constructor(public readonly name?: string) {}

public async ensure(key: K, generator: () => Promise<V>): Promise<V> {
const [exists, value] = await cacheEnsure<V>(key, this.name);
if (exists) return value;
try {
const result = await generator();
cacheRes(key, result, this.name);
return result;
} catch (err) {
cacheRej(key, err, this.name);
throw err;
}
}
}
2 changes: 1 addition & 1 deletion plugins/_lib/MaxTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class MaxTrack {
if (maxTrack !== undefined) return maxTrack;

const extTrackItem = await ExtendedMediaItem.get(itemId);
const trackItem = extTrackItem?.trackItem;
const trackItem = extTrackItem?.tidalTrack;
if (trackItem === undefined || trackItem.contentType !== "track" || this.hasHiRes(trackItem)) return false;

const isrcs = await extTrackItem?.isrcs();
Expand Down
20 changes: 0 additions & 20 deletions plugins/_lib/api/musicbrainz/index.ts

This file was deleted.

6 changes: 0 additions & 6 deletions plugins/_lib/api/musicbrainz/types/ISRCData.ts

This file was deleted.

9 changes: 0 additions & 9 deletions plugins/_lib/api/musicbrainz/types/Recording.ts

This file was deleted.

67 changes: 0 additions & 67 deletions plugins/_lib/api/musicbrainz/types/ReleaseData.ts

This file was deleted.

75 changes: 0 additions & 75 deletions plugins/_lib/api/musicbrainz/types/UPCData.ts

This file was deleted.

4 changes: 0 additions & 4 deletions plugins/_lib/api/musicbrainz/types/index.ts

This file was deleted.

Loading

0 comments on commit fab593c

Please sign in to comment.