+ //
+ // This is just the small thumbnail, the full size image comes from getArtwork()
+ parseAlbumArtwork(doc: Document): string | undefined {
+ const imageElement = doc.querySelector('div.album-artwork img.photo');
+ if (!imageElement) return undefined;
+
+ return imageElement.src;
+ }
+
+ // The format is as follows:
+ //
+ //
('div.album-meta-data');
+ if (!albumMetadata) return undefined;
+
+ const titleHeading = albumMetadata.querySelector('h1.album-title');
+ if (!titleHeading) return undefined;
+
+ const title = titleHeading.textContent;
+
+ const artistSpans = albumMetadata.querySelectorAll('p.album-artist > span.album-artist');
+ if (artistSpans.length === 0) return undefined;
+
+ const artists: Artist[] = [];
+ for (const span of artistSpans) {
+ const anchor = span.querySelector('a');
+ if (!anchor) return undefined;
+
+ const id = anchor.href.match(this.entityPathPattern)?.[1];
+ if (!id) return undefined;
+
+ artists.push({
+ name: span.textContent?.trim(),
+ id,
+ });
+ }
+
+ const details = albumMetadata.querySelector('div.detail');
+ if (!details) return undefined;
+
+ let releaseDate: string | undefined;
+ let originalReleaseDate: string | undefined;
+
+ const releaseElements = details.querySelectorAll('p.release-day');
+
+ releaseElements.forEach((el) => {
+ const text = el.textContent?.trim();
+ if (!text) return;
+
+ if (text.startsWith('Original release date:')) {
+ originalReleaseDate = text.replace('Original release date:', '').trim();
+ } else if (text.startsWith('Release date:')) {
+ releaseDate = text.replace('Release date:', '').trim();
+ }
+ });
+
+ releaseDate = releaseDate || originalReleaseDate;
+ if (!releaseDate) return undefined;
+
+ const albumMeta: AlbumMeta = {
+ title,
+ artists,
+ releaseDate,
+ };
+
+ const labelAnchor = details.querySelector('p.label-name > a');
+ if (!labelAnchor) return albumMeta;
+
+ const catalogIdParagraph = details.querySelector('p.catalog-id');
+
+ let catalogNumber = undefined;
+ if (catalogIdParagraph) {
+ catalogNumber = catalogIdParagraph.textContent.trim().match(/^Catalog number: (.*?)$/)?.[1];
+ }
+
+ const labelId = labelAnchor.href.match(this.labelPathPattern)?.[1];
+ if (!labelId) return undefined;
+
+ albumMeta.label = {
+ name: labelAnchor.textContent,
+ id: labelId,
+ catalogNumber,
+ };
+
+ return albumMeta;
+ }
+
+ extractEmbeddedJson(webUrl: URL, maxTimestamp?: number): Promise> {
+ return this.fetchJSON(webUrl, {
+ policy: { maxTimestamp },
+ responseMutator: async (response) => {
+ const html = await response.text();
+
+ const parsedPackage = this.scrapePackage(html, webUrl);
+ return new Response(JSON.stringify(parsedPackage));
+ },
+ });
+ }
+}
+
+export class OtotoyReleaseLookup extends ReleaseLookup {
+ releaseUrl: URL | undefined;
+
+ constructReleaseApiUrl(): URL | undefined {
+ return undefined;
+ }
+
+ async getRawRelease(): Promise {
+ if (this.lookup.method === 'gtin') {
+ throw new ProviderError(this.provider.name, 'GTIN lookups are not supported');
+ }
+
+ // Entity is already defined for ID/URL lookups.
+ const webUrl = this.provider.constructUrl(this.entity!);
+ this.releaseUrl = webUrl;
+ const { content: release, timestamp } = await this.provider.extractEmbeddedJson(
+ webUrl,
+ this.options.snapshotMaxTimestamp,
+ );
+ this.updateCacheTime(timestamp);
+
+ return release;
+ }
+
+ convertRawRelease(albumPage: PackagePage): HarmonyRelease {
+ const { albumMeta, trackList } = albumPage;
+
+ const release: HarmonyRelease = {
+ title: albumMeta.title,
+ artists: albumMeta.artists.map(this.convertRawArtist.bind(this)),
+ labels: albumMeta.label ? [this.convertRawLabel(albumMeta.label)] : undefined,
+ releaseDate: parseISODateTime(albumMeta.releaseDate),
+ media: this.convertRawTracklist(trackList),
+ status: 'Official',
+ packaging: 'None',
+ externalLinks: [{
+ url: this.releaseUrl!.href,
+ types: ['paid download'],
+ }],
+ images: [this.getArtwork(albumPage.thumbUrl)],
+ info: this.generateReleaseInfo(),
+ };
+
+ return release;
+ }
+
+ convertRawTracklist(tracklist: Track[]): HarmonyMedium[] {
+ const result: HarmonyMedium[] = [];
+ let medium: HarmonyMedium = {
+ number: 1,
+ format: 'Digital Media',
+ tracklist: [],
+ };
+
+ // split flat tracklist into media
+ tracklist.forEach((item) => {
+ // store the previous medium and create a new one
+ if (item.discNumber && item.discNumber !== medium.number) {
+ result.push(medium);
+
+ medium = {
+ number: item.discNumber,
+ format: 'Digital Media',
+ tracklist: [],
+ };
+ }
+
+ medium.tracklist.push(this.convertRawTrack(item));
+ });
+
+ // store the final medium
+ result.push(medium);
+
+ return result;
+ }
+
+ convertRawTrack(rawTrack: Track): HarmonyTrack {
+ const result: HarmonyTrack = {
+ number: rawTrack.trackNumber,
+ title: rawTrack.title,
+ length: rawTrack.duration * 1000,
+ type: 'audio',
+ };
+
+ return result;
+ }
+
+ convertRawArtist(artist: Artist): ArtistCreditName {
+ return {
+ name: artist.name,
+ creditedName: artist.name,
+ externalIds: this.provider.makeExternalIds({ type: 'artist', id: artist.id }),
+ };
+ }
+
+ convertRawLabel(label: RawLabel): Label {
+ return {
+ name: label.name,
+ catalogNumber: label.catalogNumber,
+ externalIds: this.provider.makeExternalIds({ type: 'label', id: label.id }),
+ };
+ }
+
+ getArtwork(thumbUrl: string): Artwork {
+ // The artwork will typically be 320x320, for example:
+ //
+ // https://imgs.ototoy.jp/imgs/jacket/3195/00000003.3195617.1763132761.1492_320.jpg
+ //
+ // Replacing "_320" at the end with "orig" returns the original, full size image
+ //
+ // Source: https://github.com/qsniyg/maxurl
+ const imageUrl = thumbUrl.replace(/_[0-9]+(\.[^.]+)$/, 'orig$1');
+
+ return {
+ url: imageUrl,
+ thumbUrl,
+ types: ['front'],
+ noReferrer: true,
+ };
+ }
+}
diff --git a/providers/mod.ts b/providers/mod.ts
index 89582687..eebac239 100644
--- a/providers/mod.ts
+++ b/providers/mod.ts
@@ -8,6 +8,7 @@ import BeatportProvider from './Beatport/mod.ts';
import DeezerProvider from './Deezer/mod.ts';
import iTunesProvider from './iTunes/mod.ts';
import MusicBrainzProvider from './MusicBrainz/mod.ts';
+import OtotoyProvider from './Ototoy/mod.ts';
import SpotifyProvider from './Spotify/mod.ts';
import TidalProvider from './Tidal/mod.ts';
import MoraProvider from './Mora/mod.ts';
@@ -24,6 +25,7 @@ providers.addMultiple(
DeezerProvider,
iTunesProvider,
SpotifyProvider,
+ OtotoyProvider,
TidalProvider,
BandcampProvider,
BeatportProvider,
diff --git a/server/components/CoverImage.tsx b/server/components/CoverImage.tsx
index ab91f26c..33e67986 100644
--- a/server/components/CoverImage.tsx
+++ b/server/components/CoverImage.tsx
@@ -9,10 +9,17 @@ export function CoverImage({ artwork }: { artwork: Artwork }) {
description += ` (${artwork.comment})`;
}
+ const referrerPolicy = artwork.noReferrer ? 'no-referrer' : undefined;
+
return (
-
-
+
+
{artwork.comment}
diff --git a/server/components/ProviderIcon.tsx b/server/components/ProviderIcon.tsx
index 4fc14c73..4a059e2d 100644
--- a/server/components/ProviderIcon.tsx
+++ b/server/components/ProviderIcon.tsx
@@ -9,6 +9,7 @@ const providerIconMap: Record = {
itunes: 'brand-apple',
musicbrainz: 'brand-metabrainz',
mora: 'brand-mora',
+ ototoy: 'brand-ototoy',
spotify: 'brand-spotify',
tidal: 'brand-tidal',
};
diff --git a/server/icons/BrandOtotoy.tsx b/server/icons/BrandOtotoy.tsx
new file mode 100644
index 00000000..089a0e84
--- /dev/null
+++ b/server/icons/BrandOtotoy.tsx
@@ -0,0 +1,19 @@
+export default function IconBrandOtotoy({
+ size = 24,
+ color = 'currentColor',
+ ...props
+}) {
+ return (
+
+ );
+}
diff --git a/server/routes/icon-sprite.svg.tsx b/server/routes/icon-sprite.svg.tsx
index 10e2090e..a53ac988 100644
--- a/server/routes/icon-sprite.svg.tsx
+++ b/server/routes/icon-sprite.svg.tsx
@@ -1,6 +1,7 @@
import IconBrandBeatport from '@/server/icons/BrandBeatport.tsx';
import IconBrandIfpi from '@/server/icons/BrandIfpi.tsx';
import IconBrandMetaBrainz from '@/server/icons/BrandMetaBrainz.tsx';
+import IconBrandOtotoy from '@/server/icons/BrandOtotoy.tsx';
import IconBrandMora from '@/server/icons/BrandMora.tsx';
import IconBrandApple from 'tabler-icons/brand-apple.tsx';
import IconBrandBandcamp from 'tabler-icons/brand-bandcamp.tsx';
@@ -61,6 +62,7 @@ const icons: Icon[] = [
IconBrandIfpi,
IconBrandMetaBrainz,
IconBrandMora,
+ IconBrandOtotoy,
IconBrandSpotify,
IconBrandTidal,
IconPuzzle,
diff --git a/server/static/harmony.css b/server/static/harmony.css
index 674a1bcc..67c1522b 100644
--- a/server/static/harmony.css
+++ b/server/static/harmony.css
@@ -33,6 +33,7 @@
--deezer: #a238ff;
--mora: #02082a;
--musicbrainz: #ba478f;
+ --ototoy: #e07e01;
--spotify: #1db954;
--tidal: #000000;
}
@@ -468,6 +469,9 @@ label.mora, td.mora {
label.musicbrainz, td.musicbrainz {
background-color: var(--musicbrainz);
}
+label.ototoy, td.ototoy {
+ background-color: var(--ototoy);
+}
label.spotify, td.spotify {
background-color: var(--spotify);
}
diff --git a/testdata/https!/jp.ototoy/_/default/p/1822344 b/testdata/https!/jp.ototoy/_/default/p/1822344
new file mode 100644
index 00000000..e8d252ca
--- /dev/null
+++ b/testdata/https!/jp.ototoy/_/default/p/1822344
@@ -0,0 +1 @@
+{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512_320.jpg","albumMeta":{"title":"THE BOOK 3","artists":[{"name":"YOASOBI","id":"731939"}],"releaseDate":"2023-10-04","label":{"name":"YOASOBI","id":"856521","catalogNumber":"YOASOBI-081"}},"trackList":[{"title":"勇者","trackNumber":1,"duration":194},{"title":"Interlude \"Awakening\"","trackNumber":2,"duration":48},{"title":"祝福","trackNumber":3,"duration":192},{"title":"海のまにまに","trackNumber":4,"duration":256},{"title":"ミスター","trackNumber":5,"duration":185},{"title":"Interlude \"Worship\"","trackNumber":6,"duration":67},{"title":"アイドル","trackNumber":7,"duration":211},{"title":"セブンティーン","trackNumber":8,"duration":198},{"title":"アドベンチャー","trackNumber":9,"duration":198},{"title":"好きだ","trackNumber":10,"duration":217}]}
\ No newline at end of file
diff --git a/testdata/https!/jp.ototoy/_/default/p/3016055 b/testdata/https!/jp.ototoy/_/default/p/3016055
new file mode 100644
index 00000000..d4eefc1f
--- /dev/null
+++ b/testdata/https!/jp.ototoy/_/default/p/3016055
@@ -0,0 +1 @@
+{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3016/00000003.1756598333.417_320.jpg","albumMeta":{"title":"トゥイー・ボックスの人形劇場","artists":[{"name":"sasakure.UK","id":"153628"}],"releaseDate":"2025-09-01","label":{"name":"sasakuration","id":"215734"}},"trackList":[{"title":"トゥイー・ボックスの人形劇場","trackNumber":1,"duration":238}]}
\ No newline at end of file
diff --git a/testdata/https!/jp.ototoy/_/default/p/3228080 b/testdata/https!/jp.ototoy/_/default/p/3228080
new file mode 100644
index 00000000..466d4c97
--- /dev/null
+++ b/testdata/https!/jp.ototoy/_/default/p/3228080
@@ -0,0 +1 @@
+{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3228/00000003.3228080.1763569536.7217_320.jpg","albumMeta":{"title":"UNTIL","artists":[{"name":"Benjazzy","id":"666017"}],"releaseDate":"2025-11-19"},"trackList":[{"title":"UNITE","trackNumber":1,"duration":212},{"title":"BLINDLY","trackNumber":2,"duration":207},{"title":"TRAUMATIC","trackNumber":3,"duration":192},{"title":"NOOFFSEASON (feat. Watson, MIKADO & ¥ellow Bucks)","trackNumber":4,"duration":314},{"title":"1 2 3 (feat. CFN MALIK)","trackNumber":5,"duration":186},{"title":"PRIDE","trackNumber":6,"duration":239},{"title":"WWW (feat. Bonbero)","trackNumber":7,"duration":212},{"title":"UWASA (feat. JP THE WAVY)","trackNumber":8,"duration":186},{"title":"シケモク (feat. Daichi Yamamoto)","trackNumber":9,"duration":221},{"title":"NEVER CHANGE (feat. SZK)","trackNumber":10,"duration":227},{"title":"HATERS","trackNumber":11,"duration":137},{"title":"LIFE LINE","trackNumber":12,"duration":199},{"title":"THE BALANCE (feat. 般若)","trackNumber":13,"duration":192},{"title":"UNTIL","trackNumber":14,"duration":308}]}
\ No newline at end of file
diff --git a/testdata/https!/jp.ototoy/_/default/p/3237840 b/testdata/https!/jp.ototoy/_/default/p/3237840
new file mode 100644
index 00000000..757cac23
--- /dev/null
+++ b/testdata/https!/jp.ototoy/_/default/p/3237840
@@ -0,0 +1 @@
+{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1763733479.356_320.jpg","albumMeta":{"title":"Anthology Collection","artists":[{"name":"ザ・ビートルズ","id":"218489"}],"releaseDate":"2025-11-21","label":{"name":"UMC (Universal Music Catalogue)","id":"274620"}},"trackList":[{"title":"Free As A Bird (1995 Mix - Remastered)","discNumber":1,"trackNumber":1,"duration":265},{"title":"John Lennon Speech 1 (Remastered)","discNumber":1,"trackNumber":2,"duration":11},{"title":"That'll Be The Day (Remastered)","discNumber":1,"trackNumber":3,"duration":129},{"title":"In Spite Of All The Danger (Remastered)","discNumber":1,"trackNumber":4,"duration":164},{"title":"Paul McCartney Speech 1 (Remastered)","discNumber":1,"trackNumber":5,"duration":18},{"title":"Hallelujah, I Love Her So (Home Demo - Remastered)","discNumber":1,"trackNumber":6,"duration":73},{"title":"You'll Be Mine (Home Demo - Remastered)","discNumber":1,"trackNumber":7,"duration":98},{"title":"Cayenne (Home Demo - Remastered)","discNumber":1,"trackNumber":8,"duration":73},{"title":"Paul McCartney Speech 2 (Remastered)","discNumber":1,"trackNumber":9,"duration":7},{"title":"My Bonnie (Remastered)","discNumber":1,"trackNumber":10,"duration":162},{"title":"Ain't She Sweet (Remastered)","discNumber":1,"trackNumber":11,"duration":133},{"title":"Cry For A Shadow (Remastered)","discNumber":1,"trackNumber":12,"duration":142},{"title":"John Lennon Speech 2 (Remastered)","discNumber":1,"trackNumber":13,"duration":9},{"title":"Brian Epstein Speech 1 (Remastered)","discNumber":1,"trackNumber":14,"duration":18},{"title":"Searchin' (Decca Audition - Remastered)","discNumber":1,"trackNumber":15,"duration":179},{"title":"Three Cool Cats (Decca Audition - Remastered)","discNumber":1,"trackNumber":16,"duration":145},{"title":"The Sheik Of Araby (Decca Audition - Remastered)","discNumber":1,"trackNumber":17,"duration":103},{"title":"Like Dreamers Do (Decca Audition - Remastered)","discNumber":1,"trackNumber":18,"duration":155},{"title":"Hello Little Girl (Decca Audition - Remastered)","discNumber":1,"trackNumber":19,"duration":100},{"title":"Brian Epstein Speech 2 (Remastered)","discNumber":1,"trackNumber":20,"duration":32},{"title":"Besame Mucho (June 1962 Version - Remastered)","discNumber":1,"trackNumber":21,"duration":156},{"title":"Love Me Do (First Version - Remastered)","discNumber":1,"trackNumber":22,"duration":151},{"title":"How Do You Do It (Remastered)","discNumber":1,"trackNumber":23,"duration":117},{"title":"Please Please Me (First Version - Remastered)","discNumber":1,"trackNumber":24,"duration":119},{"title":"One After 909 (Takes 3, 4 And 5 - Remastered)","discNumber":1,"trackNumber":25,"duration":143},{"title":"One After 909 (Edit Of Takes 4 And 5 - Remastered)","discNumber":1,"trackNumber":26,"duration":175},{"title":"Lend Me Your Comb (BBC Live Recording - Remastered)","discNumber":1,"trackNumber":27,"duration":109},{"title":"I'll Get You (Live On Sunday Night At The London Palladium - Remastered)","discNumber":1,"trackNumber":28,"duration":128},{"title":"John Lennon Speech 3 (Remastered)","discNumber":1,"trackNumber":29,"duration":12},{"title":"I Saw Her Standing There (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":30,"duration":168},{"title":"From Me To You (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":31,"duration":125},{"title":"Money (That's What I Want) (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":32,"duration":172},{"title":"You Really Got A Hold On Me (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":33,"duration":177},{"title":"Roll Over Beethoven (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":34,"duration":141},{"title":"She Loves You (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":35,"duration":169},{"title":"Till There Was You (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":36,"duration":172},{"title":"Twist And Shout (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":37,"duration":187},{"title":"This Boy (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":38,"duration":141},{"title":"I Want To Hold Your Hand (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":39,"duration":156},{"title":"Speech (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":40,"duration":125},{"title":"Moonlight Bay (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":41,"duration":50},{"title":"Can't Buy Me Love (Take 2 With Solo From Take 1 - Remastered)","discNumber":1,"trackNumber":42,"duration":129},{"title":"All My Loving (Live On The Ed Sullivan Show - Remastered)","discNumber":1,"trackNumber":43,"duration":139},{"title":"You Can't Do That (Take 6 - Remastered)","discNumber":1,"trackNumber":44,"duration":162},{"title":"And I Love Her (Take 2 - Remastered)","discNumber":1,"trackNumber":45,"duration":112},{"title":"A Hard Day's Night (Take 1 - Remastered)","discNumber":1,"trackNumber":46,"duration":163},{"title":"I Wanna Be Your Man (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":47,"duration":107},{"title":"Long Tall Sally (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":48,"duration":105},{"title":"Boys (Live Session For Around The Beatles - Remastered)","discNumber":1,"trackNumber":49,"duration":109},{"title":"Shout (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":50,"duration":90},{"title":"I'll Be Back (Take 2 - Remastered)","discNumber":1,"trackNumber":51,"duration":72},{"title":"I'll Be Back (Take 3 - Remastered)","discNumber":1,"trackNumber":52,"duration":117},{"title":"You Know What To Do (Demo - Remastered)","discNumber":1,"trackNumber":53,"duration":118},{"title":"No Reply (Demo - Remastered)","discNumber":1,"trackNumber":54,"duration":106},{"title":"Mr Moonlight (Takes 1 And 4 - Remastered)","discNumber":1,"trackNumber":55,"duration":167},{"title":"Leave My Kitten Alone (Take 5 - Remastered)","discNumber":1,"trackNumber":56,"duration":176},{"title":"No Reply (Take 2 - Remastered)","discNumber":1,"trackNumber":57,"duration":148},{"title":"Eight Days A Week (Takes 1, 2 And 4 - Remastered)","discNumber":1,"trackNumber":58,"duration":85},{"title":"Eight Days A Week (Take 5 - Remastered)","discNumber":1,"trackNumber":59,"duration":167},{"title":"Kansas City / Hey-Hey-Hey-Hey! (Take 2 - Remastered)","discNumber":1,"trackNumber":60,"duration":164},{"title":"Real Love (1996 Mix / Remastered)","discNumber":2,"trackNumber":1,"duration":233},{"title":"Yes It Is (Takes 2 And 14 - Remastered)","discNumber":2,"trackNumber":2,"duration":110},{"title":"I'm Down (Take 1 - Remastered)","discNumber":2,"trackNumber":3,"duration":173},{"title":"You've Got To Hide Your Love Away (Take 5 - Remastered)","discNumber":2,"trackNumber":4,"duration":164},{"title":"If You've Got Trouble (Take 1 - Remastered)","discNumber":2,"trackNumber":5,"duration":168},{"title":"That Means A Lot (Take 1 - Remastered)","discNumber":2,"trackNumber":6,"duration":146},{"title":"Yesterday (Take 1 - Remastered)","discNumber":2,"trackNumber":7,"duration":153},{"title":"It's Only Love (Takes 3 And 2 - Remastered)","discNumber":2,"trackNumber":8,"duration":118},{"title":"I Feel Fine (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":9,"duration":135},{"title":"Ticket To Ride (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":10,"duration":164},{"title":"Yesterday (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":11,"duration":162},{"title":"Help! (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":12,"duration":174},{"title":"Everybody's Trying To Be My Baby (Live At Shea Stadium, New York - Remastered)","discNumber":2,"trackNumber":13,"duration":165},{"title":"Norwegian Wood (This Bird Has Flown) (Take 1 - Remastered)","discNumber":2,"trackNumber":14,"duration":119},{"title":"I'm Looking Through You (Take 1 - Remastered)","discNumber":2,"trackNumber":15,"duration":173},{"title":"12-Bar Original (Take 2 Edited - Remastered)","discNumber":2,"trackNumber":16,"duration":175},{"title":"Tomorrow Never Knows (Take 1 - Remastered)","discNumber":2,"trackNumber":17,"duration":194},{"title":"Got To Get You Into My Life (Take 5 - Remastered)","discNumber":2,"trackNumber":18,"duration":174},{"title":"And Your Bird Can Sing (Takes 1 and 2 - Remastered)","discNumber":2,"trackNumber":19,"duration":133},{"title":"Taxman (Take 11 - Remastered)","discNumber":2,"trackNumber":20,"duration":152},{"title":"Eleanor Rigby (Take 14 - Strings Only - Remastered)","discNumber":2,"trackNumber":21,"duration":126},{"title":"I'm Only Sleeping (Rehearsal - Remastered)","discNumber":2,"trackNumber":22,"duration":40},{"title":"I'm Only Sleeping (Take 1 - Remastered)","discNumber":2,"trackNumber":23,"duration":179},{"title":"Rock And Roll Music (Live In Tokyo - Remastered)","discNumber":2,"trackNumber":24,"duration":98},{"title":"She's A Woman (Live In Tokyo - Remastered)","discNumber":2,"trackNumber":25,"duration":176},{"title":"Strawberry Fields Forever (Home Demo Sequence - Remastered)","discNumber":2,"trackNumber":26,"duration":100},{"title":"Strawberry Fields Forever (Take 1 - Remastered)","discNumber":2,"trackNumber":27,"duration":154},{"title":"Strawberry Fields Forever (Take 7 And Edit Piece - Remastered)","discNumber":2,"trackNumber":28,"duration":253},{"title":"Penny Lane (Remix - Remastered)","discNumber":2,"trackNumber":29,"duration":192},{"title":"A Day In The Life (Takes 1, 2, 6 And Orchestra - Remastered)","discNumber":2,"trackNumber":30,"duration":304},{"title":"Good Morning Good Morning (Take 8 - Remastered)","discNumber":2,"trackNumber":31,"duration":159},{"title":"Only A Northern Song (Takes 3 And 12 - Remastered)","discNumber":2,"trackNumber":32,"duration":163},{"title":"Being For The Benefit Of Mr Kite! (Takes 1 And 2 - Remastered)","discNumber":2,"trackNumber":33,"duration":65},{"title":"Being For The Benefit Of Mr Kite! (Take 7 - Remastered)","discNumber":2,"trackNumber":34,"duration":153},{"title":"Lucy In The Sky With Diamonds (Takes 6, 7 And 8 - Remastered)","discNumber":2,"trackNumber":35,"duration":185},{"title":"Within You Without You (Instrumental - Remastered)","discNumber":2,"trackNumber":36,"duration":327},{"title":"Sgt Pepper's Lonely Hearts Club Band (Reprise - Take 5 - Remastered)","discNumber":2,"trackNumber":37,"duration":87},{"title":"You Know My Name (Look Up The Number) (Stereo Remix - Remastered)","discNumber":2,"trackNumber":38,"duration":343},{"title":"I Am The Walrus (Take 16 - Remastered)","discNumber":2,"trackNumber":39,"duration":241},{"title":"The Fool On The Hill (Demo - Remastered)","discNumber":2,"trackNumber":40,"duration":168},{"title":"Your Mother Should Know (Take 27 - Remastered)","discNumber":2,"trackNumber":41,"duration":182},{"title":"The Fool On The Hill (Take 4 - Remastered)","discNumber":2,"trackNumber":42,"duration":224},{"title":"Hello, Goodbye (Take 16 - Remastered)","discNumber":2,"trackNumber":43,"duration":197},{"title":"Lady Madonna (Takes 3 And 4 - Remastered)","discNumber":2,"trackNumber":44,"duration":141},{"title":"Across The Universe (Take 2 - Remastered)","discNumber":2,"trackNumber":45,"duration":210},{"title":"A Beginning (Remastered)","discNumber":3,"trackNumber":1,"duration":50},{"title":"Happiness Is A Warm Gun (Esher Demo With False Start - Remastered)","discNumber":3,"trackNumber":2,"duration":134},{"title":"Helter Skelter (Take 2 Edited - Remastered)","discNumber":3,"trackNumber":3,"duration":277},{"title":"Mean Mr Mustard (Esher Demo - Remastered)","discNumber":3,"trackNumber":4,"duration":118},{"title":"Polythene Pam (Esher Demo - Remastered)","discNumber":3,"trackNumber":5,"duration":86},{"title":"Glass Onion (Esher Demo - Remastered)","discNumber":3,"trackNumber":6,"duration":111},{"title":"Junk (Esher Demo - Remastered)","discNumber":3,"trackNumber":7,"duration":144},{"title":"Piggies (Esher Demo - Remastered)","discNumber":3,"trackNumber":8,"duration":120},{"title":"Honey Pie (Esher Demo Edited - Remastered)","discNumber":3,"trackNumber":9,"duration":79},{"title":"Don't Pass Me By (Take 3 With Take 5 Vocal - Remastered)","discNumber":3,"trackNumber":10,"duration":162},{"title":"Ob-La-Di, Ob-La-Da (First Version - Take 5 - Remastered)","discNumber":3,"trackNumber":11,"duration":176},{"title":"Good Night (Rehearsal And Take 34 - Remastered)","discNumber":3,"trackNumber":12,"duration":158},{"title":"Cry Baby Cry (Take 1 - Remastered)","discNumber":3,"trackNumber":13,"duration":166},{"title":"Blackbird (Take 4 - Remastered)","discNumber":3,"trackNumber":14,"duration":138},{"title":"Sexy Sadie (Take 6 - Remastered)","discNumber":3,"trackNumber":15,"duration":246},{"title":"While My Guitar Gently Weeps (Acoustic Version - Take 1 - Remastered)","discNumber":3,"trackNumber":16,"duration":207},{"title":"Hey Jude (Take 2 - Remastered)","discNumber":3,"trackNumber":17,"duration":261},{"title":"Not Guilty (Take 102 Edited - Remastered)","discNumber":3,"trackNumber":18,"duration":202},{"title":"Mother Nature's Son (Take 2 - Remastered)","discNumber":3,"trackNumber":19,"duration":197},{"title":"Glass Onion (Mono Mix - Remastered)","discNumber":3,"trackNumber":20,"duration":128},{"title":"Rocky Raccoon (Take 8 - Remastered)","discNumber":3,"trackNumber":21,"duration":252},{"title":"What's The New Mary Jane (Take 4 - Remastered)","discNumber":3,"trackNumber":22,"duration":372},{"title":"Step Inside Love / Los Paranoias (Studio Jam - Remastered)","discNumber":3,"trackNumber":23,"duration":150},{"title":"I'm So Tired (Edit Of Takes 3, 6 And 9 - Remastered)","discNumber":3,"trackNumber":24,"duration":134},{"title":"I Will (Take 1 - Remastered)","discNumber":3,"trackNumber":25,"duration":115},{"title":"Why Don't We Do It In The Road (Take 4 - Remastered)","discNumber":3,"trackNumber":26,"duration":135},{"title":"Julia (Take 2 - Remastered)","discNumber":3,"trackNumber":27,"duration":118},{"title":"I've Got A Feeling (Apple Studio - Remastered)","discNumber":3,"trackNumber":28,"duration":168},{"title":"She Came In Through The Bathroom Window (Apple Studio - Remastered)","discNumber":3,"trackNumber":29,"duration":216},{"title":"Dig A Pony (Apple Studio - Remastered)","discNumber":3,"trackNumber":30,"duration":258},{"title":"Two Of Us (Apple Studio - Remastered)","discNumber":3,"trackNumber":31,"duration":207},{"title":"For You Blue (Apple Studio - Remastered)","discNumber":3,"trackNumber":32,"duration":142},{"title":"Teddy Boy (Apple Studio - Remastered)","discNumber":3,"trackNumber":33,"duration":198},{"title":"Rip It Up / Shake, Rattle And Roll / Blue Suede Shoes (Medley - Apple Studio Jam - Remastered)","discNumber":3,"trackNumber":34,"duration":190},{"title":"The Long And Winding Road (Apple Studio - Remastered)","discNumber":3,"trackNumber":35,"duration":221},{"title":"Oh! Darling (Apple Studio - Remastered)","discNumber":3,"trackNumber":36,"duration":247},{"title":"All Things Must Pass (Demo - Remastered)","discNumber":3,"trackNumber":37,"duration":184},{"title":"Mailman, Bring Me No More Blues (Apple Studio Jam - Remastered)","discNumber":3,"trackNumber":38,"duration":116},{"title":"Get Back (Third Rooftop Performance - Remastered)","discNumber":3,"trackNumber":39,"duration":188},{"title":"Old Brown Shoe (Demo - Remastered)","discNumber":3,"trackNumber":40,"duration":182},{"title":"Octopus's Garden (Take 2 - Remastered)","discNumber":3,"trackNumber":41,"duration":169},{"title":"Maxwell's Silver Hammer (Take 5 - Remastered)","discNumber":3,"trackNumber":42,"duration":229},{"title":"Something (Demo - Remastered)","discNumber":3,"trackNumber":43,"duration":198},{"title":"Come Together (Take 1 - Remastered)","discNumber":3,"trackNumber":44,"duration":220},{"title":"Come And Get It (Demo - 1996 Remix - Remastered)","discNumber":3,"trackNumber":45,"duration":150},{"title":"Ain't She Sweet (Studio Jam - Remastered)","discNumber":3,"trackNumber":46,"duration":128},{"title":"Because (Vocals Mix - Remastered)","discNumber":3,"trackNumber":47,"duration":143},{"title":"Let It Be (Apple Studio - Remastered)","discNumber":3,"trackNumber":48,"duration":245},{"title":"I Me Mine (Take 16 - Remastered)","discNumber":3,"trackNumber":49,"duration":107},{"title":"The End (Remix With The Final Chord Of A Day In The Life - Remastered)","discNumber":3,"trackNumber":50,"duration":172},{"title":"I Saw Her Standing There (Take 2)","discNumber":4,"trackNumber":1,"duration":186},{"title":"Money (That's What I Want) (RM7 Undubbed)","discNumber":4,"trackNumber":2,"duration":168},{"title":"This Boy (Takes 12 And 13)","discNumber":4,"trackNumber":3,"duration":198},{"title":"Tell Me Why (Takes 4 And 5)","discNumber":4,"trackNumber":4,"duration":187},{"title":"If I Fell (Take 11)","discNumber":4,"trackNumber":5,"duration":158},{"title":"Matchbox (Take 1)","discNumber":4,"trackNumber":6,"duration":129},{"title":"Every Little Thing (Takes 6 And 7)","discNumber":4,"trackNumber":7,"duration":208},{"title":"I Need You (Take 1)","discNumber":4,"trackNumber":8,"duration":156},{"title":"I've Just Seen A Face (Take 3)","discNumber":4,"trackNumber":9,"duration":146},{"title":"In My Life (Take 1)","discNumber":4,"trackNumber":10,"duration":160},{"title":"Nowhere Man (First Version - Take 2)","discNumber":4,"trackNumber":11,"duration":144},{"title":"Got To Get You Into My Life (Second Version - Unnumbered Mix)","discNumber":4,"trackNumber":12,"duration":155},{"title":"Love You To (Take 7)","discNumber":4,"trackNumber":13,"duration":176},{"title":"Strawberry Fields Forever (Take 26)","discNumber":4,"trackNumber":14,"duration":200},{"title":"She's Leaving Home (Take 1 - Instrumental)","discNumber":4,"trackNumber":15,"duration":230},{"title":"Baby, You're A Rich Man (Takes 11 And 12)","discNumber":4,"trackNumber":16,"duration":366},{"title":"All You Need Is Love (Rehearsal For BBC Broadcast)","discNumber":4,"trackNumber":17,"duration":371},{"title":"The Fool On The Hill (Take 5 - Instrumental)","discNumber":4,"trackNumber":18,"duration":282},{"title":"I Am The Walrus (Take 19 - Strings, Brass, Clarinet Overdub)","discNumber":4,"trackNumber":19,"duration":296},{"title":"Hey Bulldog (Take 4 - Instrumental)","discNumber":4,"trackNumber":20,"duration":194},{"title":"Good Night (Take 10 With A Guitar Part From Take 5)","discNumber":4,"trackNumber":21,"duration":151},{"title":"While My Guitar Gently Weeps (Third Version - Take 27)","discNumber":4,"trackNumber":22,"duration":198},{"title":"(You're So Square) Baby I Don’t Care (Studio Jam)","discNumber":4,"trackNumber":23,"duration":43},{"title":"Helter Skelter (Second Version - Take 17)","discNumber":4,"trackNumber":24,"duration":218},{"title":"I Will (Take 29)","discNumber":4,"trackNumber":25,"duration":26},{"title":"Can You Take Me Back? (Take 1)","discNumber":4,"trackNumber":26,"duration":142},{"title":"Julia (Two Rehearsals)","discNumber":4,"trackNumber":27,"duration":266},{"title":"Get Back (Take 8)","discNumber":4,"trackNumber":28,"duration":231},{"title":"Octopus’s Garden (Rehearsal)","discNumber":4,"trackNumber":29,"duration":109},{"title":"Don’t Let Me Down (First Rooftop Performance)","discNumber":4,"trackNumber":30,"duration":207},{"title":"You Never Give Me Your Money (Take 36)","discNumber":4,"trackNumber":31,"duration":317},{"title":"Here Comes The Sun (Take 9)","discNumber":4,"trackNumber":32,"duration":221},{"title":"Something (Take 39 - Strings Only Instrumental)","discNumber":4,"trackNumber":33,"duration":155},{"title":"Free As A Bird (2025 Mix)","discNumber":4,"trackNumber":34,"duration":267},{"title":"Real Love (2025 Mix)","discNumber":4,"trackNumber":35,"duration":214},{"title":"Now And Then","discNumber":4,"trackNumber":36,"duration":249}]}
\ No newline at end of file
diff --git a/testdata/https!/jp.ototoy/_/default/p/709920 b/testdata/https!/jp.ototoy/_/default/p/709920
new file mode 100644
index 00000000..5dd2509e
--- /dev/null
+++ b/testdata/https!/jp.ototoy/_/default/p/709920
@@ -0,0 +1 @@
+{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/0709/00000003.1614153884.5336_320.jpg","albumMeta":{"title":"Gimme吟味virtuaる最高star!!!! (feat. さくらみこ, 白上フブキ, 夏色まつり & 宝鐘マリン)","artists":[{"name":"sasakure.UK","id":"153628"},{"name":"さくらみこ","id":"818883"},{"name":"白上フブキ","id":"693805"},{"name":"夏色まつり","id":"817278"},{"name":"宝鐘マリン","id":"799749"}],"releaseDate":"2021-02-25","label":{"name":"sasakuration","id":"215734"}},"trackList":[{"title":"Gimme吟味virtuaる最高star!!!! (feat. さくらみこ, 白上フブキ, 夏色まつり & 宝鐘マリン)","trackNumber":1,"duration":223}]}
\ No newline at end of file
diff --git a/utils/time.ts b/utils/time.ts
index ddf4124f..8d90ac8f 100644
--- a/utils/time.ts
+++ b/utils/time.ts
@@ -46,3 +46,9 @@ export function parseISODuration(duration: string): number {
const seconds = match[3] ? parseInt(match[3], 10) : 0;
return (hours * 60 * 60 + minutes * 60 + seconds) * 1000;
}
+
+/** Converts colon-separated duration strings (e.g. HH:mm:ss) to seconds */
+export function parseDuration(duration: string): number {
+ return duration.split(':')
+ .reduce((acc, val) => acc * 60 + Number(val), 0);
+}
From 374c228c7fd1ab8d58ffe040d35d3103a4669069 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Thu, 27 Nov 2025 13:43:54 -0500
Subject: [PATCH 02/15] refactor(ototoy): switch to deno-dom
---
deno.json | 3 +-
providers/Ototoy/mod.ts | 76 ++++++++++++++++++++++++-----------------
2 files changed, 45 insertions(+), 34 deletions(-)
diff --git a/deno.json b/deno.json
index 1ac5eec8..8f6d730a 100644
--- a/deno.json
+++ b/deno.json
@@ -12,6 +12,7 @@
},
"imports": {
"@/": "./",
+ "@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.56",
"@deno/gfm": "jsr:@deno/gfm@^0.8.0",
"@kellnerd/musicbrainz": "jsr:@kellnerd/musicbrainz@^0.4.1",
"@std/collections": "jsr:@std/collections@^1.0.10",
@@ -20,9 +21,7 @@
"@std/testing": "jsr:@std/testing@^1.0.9",
"@std/uuid": "jsr:@std/uuid@^1.0.6",
"$fresh/": "https://deno.land/x/fresh@1.6.8/",
- "@types/jsdom": "npm:@types/jsdom@^27.0.0",
"fresh/": "https://deno.land/x/fresh@1.6.8/",
- "jsdom": "npm:jsdom@^27.2.0",
"lande": "https://esm.sh/lande@1.0.10",
"preact": "https://esm.sh/preact@10.19.6",
"preact/": "https://esm.sh/preact@10.19.6/",
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index 6cc98044..a13181c9 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -13,8 +13,7 @@ import { type CacheEntry, MetadataProvider, ReleaseLookup } from '@/providers/ba
import { DurationPrecision, FeatureQuality, FeatureQualityMap } from '@/providers/features.ts';
import { parseISODateTime, PartialDate } from '@/utils/date.ts';
import { ProviderError, ResponseError } from '@/utils/errors.ts';
-// @deno-types="npm:@types/jsdom"
-import { JSDOM } from 'jsdom';
+import { DOMParser, HTMLDocument } from '@b-fuze/deno-dom';
import { parseDuration } from '../../utils/time.ts';
export default class OtotoyProvider extends MetadataProvider {
@@ -30,7 +29,7 @@ export default class OtotoyProvider extends MetadataProvider {
pathname: '/_/default/a/:id',
});
- readonly entityPathPattern = /\/_\/default\/(?:a|p)\/(\d+)$/;
+ readonly entityPathPattern = /\/_\/default\/[ap]\/(\d+)$/;
readonly labelUrlPattern = new URLPattern({
hostname: this.supportedUrls.hostname,
@@ -107,19 +106,22 @@ export default class OtotoyProvider extends MetadataProvider {
}
scrapePackage(html: string, webUrl: URL): PackagePage {
- const { document } = (new JSDOM(html)).window;
+ const doc = new DOMParser().parseFromString(html, 'text/html');
+ if (!doc) {
+ throw new ResponseError(this.name, `Failed to parse HTML`, webUrl);
+ }
- const thumbUrl = this.parseAlbumArtwork(document);
+ const thumbUrl = this.parseAlbumArtwork(doc);
if (!thumbUrl) {
throw new ResponseError(this.name, `Failed to extract album thumbnail`, webUrl);
}
- const albumMeta = this.parseAlbumMeta(document);
+ const albumMeta = this.parseAlbumMeta(doc);
if (!albumMeta) {
throw new ResponseError(this.name, `Failed to extract album metadata`, webUrl);
}
- const trackList = this.parseTracklist(document);
+ const trackList = this.parseTracklist(doc);
if (!trackList) {
throw new ResponseError(this.name, `Failed to extract tracklist`, webUrl);
}
@@ -142,11 +144,11 @@ export default class OtotoyProvider extends MetadataProvider {
//
//
// This is just the small thumbnail, the full size image comes from getArtwork()
- parseAlbumArtwork(doc: Document): string | undefined {
- const imageElement = doc.querySelector('div.album-artwork img.photo');
+ parseAlbumArtwork(doc: HTMLDocument): string | undefined {
+ const imageElement = doc.querySelector('div.album-artwork img.photo');
if (!imageElement) return undefined;
- return imageElement.src;
+ return imageElement.getAttribute('src') || undefined;
}
// The format is as follows:
@@ -169,16 +171,15 @@ export default class OtotoyProvider extends MetadataProvider {
//
//
// NOTE: `disc-row` is optional
- parseTracklist(doc: Document): Track[] | undefined {
- const trackListRows = doc.querySelectorAll('#tracklist tbody tr');
+ parseTracklist(doc: HTMLDocument): Track[] | undefined {
+ const trackListRows = doc.querySelectorAll('#tracklist tbody tr');
let currentDisc = null;
const tracks: Track[] = [];
for (const trackRow of trackListRows) {
if (trackRow.classList.contains('disc-row')) {
- const discText = trackRow.textContent || '';
- const match = discText.match(/\d+/);
+ const match = trackRow.textContent.match(/\d+/);
if (match) {
currentDisc = parseInt(match[0], 10);
@@ -190,17 +191,21 @@ export default class OtotoyProvider extends MetadataProvider {
const trackNumberCell = trackRow.querySelector('.num');
if (!trackNumberCell) continue;
- const trackNumber = trackNumberCell.textContent.trim();
+ const trackNumber = trackNumberCell.textContent?.trim() ?? '';
+ if (!trackNumber) return undefined;
const titleSpan = trackRow.querySelector("td.item span[id^='title-']");
if (!titleSpan) return undefined;
- const title = titleSpan.textContent.trim();
+ const title = titleSpan.textContent?.trim() ?? '';
+ if (!title) return undefined;
const durationCell = trackRow.querySelectorAll('td')[3];
if (!durationCell) continue;
- const duration = durationCell.textContent.trim();
+ const duration = durationCell.textContent?.trim() ?? '';
+ if (!duration) return undefined;
+
tracks.push({
title: title,
discNumber: currentDisc ?? undefined,
@@ -236,39 +241,43 @@ export default class OtotoyProvider extends MetadataProvider {
// * The release can have an "original" release date, a platform release date, or both. "Release date" is the preferred date.
// * In the case that only one date is present, sometimes "Original" is used, sometimes not. Whatever's available will
// be used.
- parseAlbumMeta(doc: Document): AlbumMeta | undefined {
- const albumMetadata = doc.querySelector('div.album-meta-data');
+ parseAlbumMeta(doc: HTMLDocument): AlbumMeta | undefined {
+ const albumMetadata = doc.querySelector('div.album-meta-data');
if (!albumMetadata) return undefined;
- const titleHeading = albumMetadata.querySelector('h1.album-title');
+ const titleHeading = albumMetadata.querySelector('h1.album-title');
if (!titleHeading) return undefined;
- const title = titleHeading.textContent;
+ const title = titleHeading.textContent?.trim();
+ if (!title) return undefined;
- const artistSpans = albumMetadata.querySelectorAll('p.album-artist > span.album-artist');
+ const artistSpans = Array.from(albumMetadata.querySelectorAll('p.album-artist > span.album-artist'));
if (artistSpans.length === 0) return undefined;
const artists: Artist[] = [];
for (const span of artistSpans) {
- const anchor = span.querySelector('a');
+ const anchor = span.querySelector('a');
if (!anchor) return undefined;
- const id = anchor.href.match(this.entityPathPattern)?.[1];
+ const id = anchor.getAttribute('href')?.match(this.entityPathPattern)?.[1];
if (!id) return undefined;
+ const name = span.textContent?.trim();
+ if (!name) return undefined;
+
artists.push({
- name: span.textContent?.trim(),
+ name,
id,
});
}
- const details = albumMetadata.querySelector('div.detail');
+ const details = albumMetadata.querySelector('div.detail');
if (!details) return undefined;
let releaseDate: string | undefined;
let originalReleaseDate: string | undefined;
- const releaseElements = details.querySelectorAll('p.release-day');
+ const releaseElements = details.querySelectorAll('p.release-day');
releaseElements.forEach((el) => {
const text = el.textContent?.trim();
@@ -290,21 +299,24 @@ export default class OtotoyProvider extends MetadataProvider {
releaseDate,
};
- const labelAnchor = details.querySelector('p.label-name > a');
+ const labelAnchor = details.querySelector('p.label-name > a');
if (!labelAnchor) return albumMeta;
- const catalogIdParagraph = details.querySelector('p.catalog-id');
+ const catalogIdParagraph = details.querySelector('p.catalog-id');
let catalogNumber = undefined;
if (catalogIdParagraph) {
- catalogNumber = catalogIdParagraph.textContent.trim().match(/^Catalog number: (.*?)$/)?.[1];
+ catalogNumber = catalogIdParagraph.textContent?.trim().match(/^Catalog number: (.*?)$/)?.[1];
}
- const labelId = labelAnchor.href.match(this.labelPathPattern)?.[1];
+ const labelId = labelAnchor.getAttribute('href')?.match(this.labelPathPattern)?.[1];
if (!labelId) return undefined;
+ const labelName = labelAnchor.textContent?.trim();
+ if (!labelName) return undefined;
+
albumMeta.label = {
- name: labelAnchor.textContent,
+ name: labelName,
id: labelId,
catalogNumber,
};
From b2007837ddc783328db6e3a97fd9835a43205cc3 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Tue, 2 Dec 2025 13:10:54 -0500
Subject: [PATCH 03/15] chore(ototoy): optimize SVG icon
---
server/icons/BrandOtotoy.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/icons/BrandOtotoy.tsx b/server/icons/BrandOtotoy.tsx
index 089a0e84..62742c19 100644
--- a/server/icons/BrandOtotoy.tsx
+++ b/server/icons/BrandOtotoy.tsx
@@ -6,14 +6,14 @@ export default function IconBrandOtotoy({
return (
);
}
From b9f174fe46b58ff0d55325384c9f2bacf4a2839e Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 20:00:35 -0500
Subject: [PATCH 04/15] refactor: move referrer policy to the top level
---
harmonizer/types.ts | 2 --
providers/Ototoy/mod.ts | 1 -
server/components/CoverImage.tsx | 11 ++---------
server/routes/_app.tsx | 1 +
4 files changed, 3 insertions(+), 12 deletions(-)
diff --git a/harmonizer/types.ts b/harmonizer/types.ts
index 5a36b4c7..de7cb762 100644
--- a/harmonizer/types.ts
+++ b/harmonizer/types.ts
@@ -103,8 +103,6 @@ export type Artwork = {
types?: ArtworkType[];
comment?: string;
provider?: ProviderName;
- /** Whether to set `referrerpolicy="no-referrer"` for the artwork URLs */
- noReferrer?: boolean;
};
export type ArtworkType = 'front' | 'back' | 'track';
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index a13181c9..9a048457 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -454,7 +454,6 @@ export class OtotoyReleaseLookup extends ReleaseLookup
-
-
+
+
{artwork.comment}
diff --git a/server/routes/_app.tsx b/server/routes/_app.tsx
index 3fc2360f..e8b0a78e 100644
--- a/server/routes/_app.tsx
+++ b/server/routes/_app.tsx
@@ -14,6 +14,7 @@ export default defineApp((_req, ctx) => {
Harmony
+
From f52d8149b98140021a63bccffb16b3cda9b05e87 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 20:01:24 -0500
Subject: [PATCH 05/15] chore: move Ototoy to the end of the provider list
---
providers/mod.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/providers/mod.ts b/providers/mod.ts
index eebac239..a32d4184 100644
--- a/providers/mod.ts
+++ b/providers/mod.ts
@@ -25,11 +25,11 @@ providers.addMultiple(
DeezerProvider,
iTunesProvider,
SpotifyProvider,
- OtotoyProvider,
TidalProvider,
BandcampProvider,
BeatportProvider,
MoraProvider,
+ OtotoyProvider,
);
/** Internal names of providers which are enabled by default (for GTIN lookups). */
From 740806c73ba43af785037d56ece851e43a60b3f0 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 20:05:50 -0500
Subject: [PATCH 06/15] chore(ototoy): cleanup URLPatterns
---
providers/Ototoy/mod.ts | 32 +++++++++++++++-----------------
1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index 9a048457..805ce9c3 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -21,12 +21,7 @@ export default class OtotoyProvider extends MetadataProvider {
readonly supportedUrls = new URLPattern({
hostname: 'ototoy.jp',
- pathname: '/_/default/p/:id',
- });
-
- readonly artistUrlPattern = new URLPattern({
- hostname: this.supportedUrls.hostname,
- pathname: '/_/default/a/:id',
+ pathname: '/_/default/:type(a|p)/:id(\\d+)',
});
readonly entityPathPattern = /\/_\/default\/[ap]\/(\d+)$/;
@@ -62,18 +57,21 @@ export default class OtotoyProvider extends MetadataProvider {
override extractEntityFromUrl(url: URL): EntityId | undefined {
const packageResult = this.supportedUrls.exec(url);
if (packageResult) {
- return {
- type: 'package',
- id: packageResult.pathname.groups.id!,
- };
- }
+ const type = packageResult.pathname.groups.type!;
+ const id = packageResult.pathname.groups.id!;
+ if (type == 'a') {
+ return {
+ type: 'artist',
+ id,
+ };
+ }
- const artistResult = this.artistUrlPattern.exec(url);
- if (artistResult) {
- return {
- type: 'artist',
- id: artistResult.pathname.groups.id!,
- };
+ if (type == 'p') {
+ return {
+ type: 'package',
+ id,
+ };
+ }
}
const labelResult = this.labelUrlPattern.exec(url);
From fe9b5159a9bd9ed3e98aa2af9d081437a8042749 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 20:11:39 -0500
Subject: [PATCH 07/15] chore(ototoy): cleanup unnecessary undefined checks
---
providers/Ototoy/mod.ts | 28 ++++++++++------------------
1 file changed, 10 insertions(+), 18 deletions(-)
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index 805ce9c3..0f977388 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -172,7 +172,7 @@ export default class OtotoyProvider extends MetadataProvider {
parseTracklist(doc: HTMLDocument): Track[] | undefined {
const trackListRows = doc.querySelectorAll('#tracklist tbody tr');
- let currentDisc = null;
+ let currentDisc = undefined;
const tracks: Track[] = [];
for (const trackRow of trackListRows) {
@@ -189,24 +189,21 @@ export default class OtotoyProvider extends MetadataProvider {
const trackNumberCell = trackRow.querySelector('.num');
if (!trackNumberCell) continue;
- const trackNumber = trackNumberCell.textContent?.trim() ?? '';
- if (!trackNumber) return undefined;
+ const trackNumber = trackNumberCell.textContent.trim();
const titleSpan = trackRow.querySelector("td.item span[id^='title-']");
if (!titleSpan) return undefined;
- const title = titleSpan.textContent?.trim() ?? '';
- if (!title) return undefined;
+ const title = titleSpan.textContent.trim();
const durationCell = trackRow.querySelectorAll('td')[3];
if (!durationCell) continue;
- const duration = durationCell.textContent?.trim() ?? '';
- if (!duration) return undefined;
+ const duration = durationCell.textContent.trim();
tracks.push({
title: title,
- discNumber: currentDisc ?? undefined,
+ discNumber: currentDisc,
trackNumber: parseInt(trackNumber, 10),
duration: parseDuration(duration),
});
@@ -246,8 +243,7 @@ export default class OtotoyProvider extends MetadataProvider {
const titleHeading = albumMetadata.querySelector('h1.album-title');
if (!titleHeading) return undefined;
- const title = titleHeading.textContent?.trim();
- if (!title) return undefined;
+ const title = titleHeading.textContent.trim();
const artistSpans = Array.from(albumMetadata.querySelectorAll('p.album-artist > span.album-artist'));
if (artistSpans.length === 0) return undefined;
@@ -260,8 +256,7 @@ export default class OtotoyProvider extends MetadataProvider {
const id = anchor.getAttribute('href')?.match(this.entityPathPattern)?.[1];
if (!id) return undefined;
- const name = span.textContent?.trim();
- if (!name) return undefined;
+ const name = span.textContent.trim();
artists.push({
name,
@@ -278,9 +273,7 @@ export default class OtotoyProvider extends MetadataProvider {
const releaseElements = details.querySelectorAll('p.release-day');
releaseElements.forEach((el) => {
- const text = el.textContent?.trim();
- if (!text) return;
-
+ const text = el.textContent.trim();
if (text.startsWith('Original release date:')) {
originalReleaseDate = text.replace('Original release date:', '').trim();
} else if (text.startsWith('Release date:')) {
@@ -304,14 +297,13 @@ export default class OtotoyProvider extends MetadataProvider {
let catalogNumber = undefined;
if (catalogIdParagraph) {
- catalogNumber = catalogIdParagraph.textContent?.trim().match(/^Catalog number: (.*?)$/)?.[1];
+ catalogNumber = catalogIdParagraph.textContent.trim().match(/^Catalog number: (.*?)$/)?.[1];
}
const labelId = labelAnchor.getAttribute('href')?.match(this.labelPathPattern)?.[1];
if (!labelId) return undefined;
- const labelName = labelAnchor.textContent?.trim();
- if (!labelName) return undefined;
+ const labelName = labelAnchor.textContent.trim();
albumMeta.label = {
name: labelName,
From dfa7abe5c982ca2c215353522166824581711bf4 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 20:12:35 -0500
Subject: [PATCH 08/15] chore(ototoy): rename extractEmbeddedJson
---
providers/Ototoy/mod.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index 0f977388..d611e418 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -314,8 +314,8 @@ export default class OtotoyProvider extends MetadataProvider {
return albumMeta;
}
- extractEmbeddedJson(webUrl: URL, maxTimestamp?: number): Promise> {
- return this.fetchJSON(webUrl, {
+ fetchAndScrapePackagePage(webUrl: URL, maxTimestamp?: number): Promise> {
+ return this.fetchJSON(webUrl, {
policy: { maxTimestamp },
responseMutator: async (response) => {
const html = await response.text();
@@ -342,7 +342,7 @@ export class OtotoyReleaseLookup extends ReleaseLookup(
+ const { content: release, timestamp } = await this.provider.fetchAndScrapePackagePage(
webUrl,
this.options.snapshotMaxTimestamp,
);
From 63c4981278d2278a65d9be65c75a6dbbf6725baf Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 20:22:45 -0500
Subject: [PATCH 09/15] chore(ototoy): update snapshots
---
providers/Ototoy/__snapshots__/mod.test.ts.snap | 10 ++--------
testdata/https!/jp.ototoy/_/default/p/3237840 | 2 +-
2 files changed, 3 insertions(+), 9 deletions(-)
diff --git a/providers/Ototoy/__snapshots__/mod.test.ts.snap b/providers/Ototoy/__snapshots__/mod.test.ts.snap
index 56c914e6..e346d3ac 100644
--- a/providers/Ototoy/__snapshots__/mod.test.ts.snap
+++ b/providers/Ototoy/__snapshots__/mod.test.ts.snap
@@ -25,7 +25,6 @@ snapshot[`OTOTOY provider > release lookup > single track release 1`] = `
],
images: [
{
- noReferrer: true,
thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3016/00000003.1756598333.417_320.jpg",
types: [
"front",
@@ -112,12 +111,11 @@ snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = `
],
images: [
{
- noReferrer: true,
- thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1763733479.356_320.jpg",
+ thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1764965039.7088_320.jpg",
types: [
"front",
],
- url: "https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1763733479.356orig.jpg",
+ url: "https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1764965039.7088orig.jpg",
},
],
info: {
@@ -1401,7 +1399,6 @@ snapshot[`OTOTOY provider > release lookup > multiple artists 1`] = `
],
images: [
{
- noReferrer: true,
thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/0709/00000003.1614153884.5336_320.jpg",
types: [
"front",
@@ -1488,7 +1485,6 @@ snapshot[`OTOTOY provider > release lookup > no label 1`] = `
],
images: [
{
- noReferrer: true,
thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3228/00000003.3228080.1763569536.7217_320.jpg",
types: [
"front",
@@ -1641,7 +1637,6 @@ snapshot[`OTOTOY provider > release lookup > original release date only 1`] = `
],
images: [
{
- noReferrer: true,
thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512_320.jpg",
types: [
"front",
@@ -1782,7 +1777,6 @@ snapshot[`OTOTOY provider > release lookup > catalog number 1`] = `
],
images: [
{
- noReferrer: true,
thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512_320.jpg",
types: [
"front",
diff --git a/testdata/https!/jp.ototoy/_/default/p/3237840 b/testdata/https!/jp.ototoy/_/default/p/3237840
index 757cac23..79cbe9b1 100644
--- a/testdata/https!/jp.ototoy/_/default/p/3237840
+++ b/testdata/https!/jp.ototoy/_/default/p/3237840
@@ -1 +1 @@
-{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1763733479.356_320.jpg","albumMeta":{"title":"Anthology Collection","artists":[{"name":"ザ・ビートルズ","id":"218489"}],"releaseDate":"2025-11-21","label":{"name":"UMC (Universal Music Catalogue)","id":"274620"}},"trackList":[{"title":"Free As A Bird (1995 Mix - Remastered)","discNumber":1,"trackNumber":1,"duration":265},{"title":"John Lennon Speech 1 (Remastered)","discNumber":1,"trackNumber":2,"duration":11},{"title":"That'll Be The Day (Remastered)","discNumber":1,"trackNumber":3,"duration":129},{"title":"In Spite Of All The Danger (Remastered)","discNumber":1,"trackNumber":4,"duration":164},{"title":"Paul McCartney Speech 1 (Remastered)","discNumber":1,"trackNumber":5,"duration":18},{"title":"Hallelujah, I Love Her So (Home Demo - Remastered)","discNumber":1,"trackNumber":6,"duration":73},{"title":"You'll Be Mine (Home Demo - Remastered)","discNumber":1,"trackNumber":7,"duration":98},{"title":"Cayenne (Home Demo - Remastered)","discNumber":1,"trackNumber":8,"duration":73},{"title":"Paul McCartney Speech 2 (Remastered)","discNumber":1,"trackNumber":9,"duration":7},{"title":"My Bonnie (Remastered)","discNumber":1,"trackNumber":10,"duration":162},{"title":"Ain't She Sweet (Remastered)","discNumber":1,"trackNumber":11,"duration":133},{"title":"Cry For A Shadow (Remastered)","discNumber":1,"trackNumber":12,"duration":142},{"title":"John Lennon Speech 2 (Remastered)","discNumber":1,"trackNumber":13,"duration":9},{"title":"Brian Epstein Speech 1 (Remastered)","discNumber":1,"trackNumber":14,"duration":18},{"title":"Searchin' (Decca Audition - Remastered)","discNumber":1,"trackNumber":15,"duration":179},{"title":"Three Cool Cats (Decca Audition - Remastered)","discNumber":1,"trackNumber":16,"duration":145},{"title":"The Sheik Of Araby (Decca Audition - Remastered)","discNumber":1,"trackNumber":17,"duration":103},{"title":"Like Dreamers Do (Decca Audition - Remastered)","discNumber":1,"trackNumber":18,"duration":155},{"title":"Hello Little Girl (Decca Audition - Remastered)","discNumber":1,"trackNumber":19,"duration":100},{"title":"Brian Epstein Speech 2 (Remastered)","discNumber":1,"trackNumber":20,"duration":32},{"title":"Besame Mucho (June 1962 Version - Remastered)","discNumber":1,"trackNumber":21,"duration":156},{"title":"Love Me Do (First Version - Remastered)","discNumber":1,"trackNumber":22,"duration":151},{"title":"How Do You Do It (Remastered)","discNumber":1,"trackNumber":23,"duration":117},{"title":"Please Please Me (First Version - Remastered)","discNumber":1,"trackNumber":24,"duration":119},{"title":"One After 909 (Takes 3, 4 And 5 - Remastered)","discNumber":1,"trackNumber":25,"duration":143},{"title":"One After 909 (Edit Of Takes 4 And 5 - Remastered)","discNumber":1,"trackNumber":26,"duration":175},{"title":"Lend Me Your Comb (BBC Live Recording - Remastered)","discNumber":1,"trackNumber":27,"duration":109},{"title":"I'll Get You (Live On Sunday Night At The London Palladium - Remastered)","discNumber":1,"trackNumber":28,"duration":128},{"title":"John Lennon Speech 3 (Remastered)","discNumber":1,"trackNumber":29,"duration":12},{"title":"I Saw Her Standing There (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":30,"duration":168},{"title":"From Me To You (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":31,"duration":125},{"title":"Money (That's What I Want) (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":32,"duration":172},{"title":"You Really Got A Hold On Me (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":33,"duration":177},{"title":"Roll Over Beethoven (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":34,"duration":141},{"title":"She Loves You (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":35,"duration":169},{"title":"Till There Was You (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":36,"duration":172},{"title":"Twist And Shout (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":37,"duration":187},{"title":"This Boy (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":38,"duration":141},{"title":"I Want To Hold Your Hand (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":39,"duration":156},{"title":"Speech (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":40,"duration":125},{"title":"Moonlight Bay (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":41,"duration":50},{"title":"Can't Buy Me Love (Take 2 With Solo From Take 1 - Remastered)","discNumber":1,"trackNumber":42,"duration":129},{"title":"All My Loving (Live On The Ed Sullivan Show - Remastered)","discNumber":1,"trackNumber":43,"duration":139},{"title":"You Can't Do That (Take 6 - Remastered)","discNumber":1,"trackNumber":44,"duration":162},{"title":"And I Love Her (Take 2 - Remastered)","discNumber":1,"trackNumber":45,"duration":112},{"title":"A Hard Day's Night (Take 1 - Remastered)","discNumber":1,"trackNumber":46,"duration":163},{"title":"I Wanna Be Your Man (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":47,"duration":107},{"title":"Long Tall Sally (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":48,"duration":105},{"title":"Boys (Live Session For Around The Beatles - Remastered)","discNumber":1,"trackNumber":49,"duration":109},{"title":"Shout (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":50,"duration":90},{"title":"I'll Be Back (Take 2 - Remastered)","discNumber":1,"trackNumber":51,"duration":72},{"title":"I'll Be Back (Take 3 - Remastered)","discNumber":1,"trackNumber":52,"duration":117},{"title":"You Know What To Do (Demo - Remastered)","discNumber":1,"trackNumber":53,"duration":118},{"title":"No Reply (Demo - Remastered)","discNumber":1,"trackNumber":54,"duration":106},{"title":"Mr Moonlight (Takes 1 And 4 - Remastered)","discNumber":1,"trackNumber":55,"duration":167},{"title":"Leave My Kitten Alone (Take 5 - Remastered)","discNumber":1,"trackNumber":56,"duration":176},{"title":"No Reply (Take 2 - Remastered)","discNumber":1,"trackNumber":57,"duration":148},{"title":"Eight Days A Week (Takes 1, 2 And 4 - Remastered)","discNumber":1,"trackNumber":58,"duration":85},{"title":"Eight Days A Week (Take 5 - Remastered)","discNumber":1,"trackNumber":59,"duration":167},{"title":"Kansas City / Hey-Hey-Hey-Hey! (Take 2 - Remastered)","discNumber":1,"trackNumber":60,"duration":164},{"title":"Real Love (1996 Mix / Remastered)","discNumber":2,"trackNumber":1,"duration":233},{"title":"Yes It Is (Takes 2 And 14 - Remastered)","discNumber":2,"trackNumber":2,"duration":110},{"title":"I'm Down (Take 1 - Remastered)","discNumber":2,"trackNumber":3,"duration":173},{"title":"You've Got To Hide Your Love Away (Take 5 - Remastered)","discNumber":2,"trackNumber":4,"duration":164},{"title":"If You've Got Trouble (Take 1 - Remastered)","discNumber":2,"trackNumber":5,"duration":168},{"title":"That Means A Lot (Take 1 - Remastered)","discNumber":2,"trackNumber":6,"duration":146},{"title":"Yesterday (Take 1 - Remastered)","discNumber":2,"trackNumber":7,"duration":153},{"title":"It's Only Love (Takes 3 And 2 - Remastered)","discNumber":2,"trackNumber":8,"duration":118},{"title":"I Feel Fine (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":9,"duration":135},{"title":"Ticket To Ride (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":10,"duration":164},{"title":"Yesterday (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":11,"duration":162},{"title":"Help! (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":12,"duration":174},{"title":"Everybody's Trying To Be My Baby (Live At Shea Stadium, New York - Remastered)","discNumber":2,"trackNumber":13,"duration":165},{"title":"Norwegian Wood (This Bird Has Flown) (Take 1 - Remastered)","discNumber":2,"trackNumber":14,"duration":119},{"title":"I'm Looking Through You (Take 1 - Remastered)","discNumber":2,"trackNumber":15,"duration":173},{"title":"12-Bar Original (Take 2 Edited - Remastered)","discNumber":2,"trackNumber":16,"duration":175},{"title":"Tomorrow Never Knows (Take 1 - Remastered)","discNumber":2,"trackNumber":17,"duration":194},{"title":"Got To Get You Into My Life (Take 5 - Remastered)","discNumber":2,"trackNumber":18,"duration":174},{"title":"And Your Bird Can Sing (Takes 1 and 2 - Remastered)","discNumber":2,"trackNumber":19,"duration":133},{"title":"Taxman (Take 11 - Remastered)","discNumber":2,"trackNumber":20,"duration":152},{"title":"Eleanor Rigby (Take 14 - Strings Only - Remastered)","discNumber":2,"trackNumber":21,"duration":126},{"title":"I'm Only Sleeping (Rehearsal - Remastered)","discNumber":2,"trackNumber":22,"duration":40},{"title":"I'm Only Sleeping (Take 1 - Remastered)","discNumber":2,"trackNumber":23,"duration":179},{"title":"Rock And Roll Music (Live In Tokyo - Remastered)","discNumber":2,"trackNumber":24,"duration":98},{"title":"She's A Woman (Live In Tokyo - Remastered)","discNumber":2,"trackNumber":25,"duration":176},{"title":"Strawberry Fields Forever (Home Demo Sequence - Remastered)","discNumber":2,"trackNumber":26,"duration":100},{"title":"Strawberry Fields Forever (Take 1 - Remastered)","discNumber":2,"trackNumber":27,"duration":154},{"title":"Strawberry Fields Forever (Take 7 And Edit Piece - Remastered)","discNumber":2,"trackNumber":28,"duration":253},{"title":"Penny Lane (Remix - Remastered)","discNumber":2,"trackNumber":29,"duration":192},{"title":"A Day In The Life (Takes 1, 2, 6 And Orchestra - Remastered)","discNumber":2,"trackNumber":30,"duration":304},{"title":"Good Morning Good Morning (Take 8 - Remastered)","discNumber":2,"trackNumber":31,"duration":159},{"title":"Only A Northern Song (Takes 3 And 12 - Remastered)","discNumber":2,"trackNumber":32,"duration":163},{"title":"Being For The Benefit Of Mr Kite! (Takes 1 And 2 - Remastered)","discNumber":2,"trackNumber":33,"duration":65},{"title":"Being For The Benefit Of Mr Kite! (Take 7 - Remastered)","discNumber":2,"trackNumber":34,"duration":153},{"title":"Lucy In The Sky With Diamonds (Takes 6, 7 And 8 - Remastered)","discNumber":2,"trackNumber":35,"duration":185},{"title":"Within You Without You (Instrumental - Remastered)","discNumber":2,"trackNumber":36,"duration":327},{"title":"Sgt Pepper's Lonely Hearts Club Band (Reprise - Take 5 - Remastered)","discNumber":2,"trackNumber":37,"duration":87},{"title":"You Know My Name (Look Up The Number) (Stereo Remix - Remastered)","discNumber":2,"trackNumber":38,"duration":343},{"title":"I Am The Walrus (Take 16 - Remastered)","discNumber":2,"trackNumber":39,"duration":241},{"title":"The Fool On The Hill (Demo - Remastered)","discNumber":2,"trackNumber":40,"duration":168},{"title":"Your Mother Should Know (Take 27 - Remastered)","discNumber":2,"trackNumber":41,"duration":182},{"title":"The Fool On The Hill (Take 4 - Remastered)","discNumber":2,"trackNumber":42,"duration":224},{"title":"Hello, Goodbye (Take 16 - Remastered)","discNumber":2,"trackNumber":43,"duration":197},{"title":"Lady Madonna (Takes 3 And 4 - Remastered)","discNumber":2,"trackNumber":44,"duration":141},{"title":"Across The Universe (Take 2 - Remastered)","discNumber":2,"trackNumber":45,"duration":210},{"title":"A Beginning (Remastered)","discNumber":3,"trackNumber":1,"duration":50},{"title":"Happiness Is A Warm Gun (Esher Demo With False Start - Remastered)","discNumber":3,"trackNumber":2,"duration":134},{"title":"Helter Skelter (Take 2 Edited - Remastered)","discNumber":3,"trackNumber":3,"duration":277},{"title":"Mean Mr Mustard (Esher Demo - Remastered)","discNumber":3,"trackNumber":4,"duration":118},{"title":"Polythene Pam (Esher Demo - Remastered)","discNumber":3,"trackNumber":5,"duration":86},{"title":"Glass Onion (Esher Demo - Remastered)","discNumber":3,"trackNumber":6,"duration":111},{"title":"Junk (Esher Demo - Remastered)","discNumber":3,"trackNumber":7,"duration":144},{"title":"Piggies (Esher Demo - Remastered)","discNumber":3,"trackNumber":8,"duration":120},{"title":"Honey Pie (Esher Demo Edited - Remastered)","discNumber":3,"trackNumber":9,"duration":79},{"title":"Don't Pass Me By (Take 3 With Take 5 Vocal - Remastered)","discNumber":3,"trackNumber":10,"duration":162},{"title":"Ob-La-Di, Ob-La-Da (First Version - Take 5 - Remastered)","discNumber":3,"trackNumber":11,"duration":176},{"title":"Good Night (Rehearsal And Take 34 - Remastered)","discNumber":3,"trackNumber":12,"duration":158},{"title":"Cry Baby Cry (Take 1 - Remastered)","discNumber":3,"trackNumber":13,"duration":166},{"title":"Blackbird (Take 4 - Remastered)","discNumber":3,"trackNumber":14,"duration":138},{"title":"Sexy Sadie (Take 6 - Remastered)","discNumber":3,"trackNumber":15,"duration":246},{"title":"While My Guitar Gently Weeps (Acoustic Version - Take 1 - Remastered)","discNumber":3,"trackNumber":16,"duration":207},{"title":"Hey Jude (Take 2 - Remastered)","discNumber":3,"trackNumber":17,"duration":261},{"title":"Not Guilty (Take 102 Edited - Remastered)","discNumber":3,"trackNumber":18,"duration":202},{"title":"Mother Nature's Son (Take 2 - Remastered)","discNumber":3,"trackNumber":19,"duration":197},{"title":"Glass Onion (Mono Mix - Remastered)","discNumber":3,"trackNumber":20,"duration":128},{"title":"Rocky Raccoon (Take 8 - Remastered)","discNumber":3,"trackNumber":21,"duration":252},{"title":"What's The New Mary Jane (Take 4 - Remastered)","discNumber":3,"trackNumber":22,"duration":372},{"title":"Step Inside Love / Los Paranoias (Studio Jam - Remastered)","discNumber":3,"trackNumber":23,"duration":150},{"title":"I'm So Tired (Edit Of Takes 3, 6 And 9 - Remastered)","discNumber":3,"trackNumber":24,"duration":134},{"title":"I Will (Take 1 - Remastered)","discNumber":3,"trackNumber":25,"duration":115},{"title":"Why Don't We Do It In The Road (Take 4 - Remastered)","discNumber":3,"trackNumber":26,"duration":135},{"title":"Julia (Take 2 - Remastered)","discNumber":3,"trackNumber":27,"duration":118},{"title":"I've Got A Feeling (Apple Studio - Remastered)","discNumber":3,"trackNumber":28,"duration":168},{"title":"She Came In Through The Bathroom Window (Apple Studio - Remastered)","discNumber":3,"trackNumber":29,"duration":216},{"title":"Dig A Pony (Apple Studio - Remastered)","discNumber":3,"trackNumber":30,"duration":258},{"title":"Two Of Us (Apple Studio - Remastered)","discNumber":3,"trackNumber":31,"duration":207},{"title":"For You Blue (Apple Studio - Remastered)","discNumber":3,"trackNumber":32,"duration":142},{"title":"Teddy Boy (Apple Studio - Remastered)","discNumber":3,"trackNumber":33,"duration":198},{"title":"Rip It Up / Shake, Rattle And Roll / Blue Suede Shoes (Medley - Apple Studio Jam - Remastered)","discNumber":3,"trackNumber":34,"duration":190},{"title":"The Long And Winding Road (Apple Studio - Remastered)","discNumber":3,"trackNumber":35,"duration":221},{"title":"Oh! Darling (Apple Studio - Remastered)","discNumber":3,"trackNumber":36,"duration":247},{"title":"All Things Must Pass (Demo - Remastered)","discNumber":3,"trackNumber":37,"duration":184},{"title":"Mailman, Bring Me No More Blues (Apple Studio Jam - Remastered)","discNumber":3,"trackNumber":38,"duration":116},{"title":"Get Back (Third Rooftop Performance - Remastered)","discNumber":3,"trackNumber":39,"duration":188},{"title":"Old Brown Shoe (Demo - Remastered)","discNumber":3,"trackNumber":40,"duration":182},{"title":"Octopus's Garden (Take 2 - Remastered)","discNumber":3,"trackNumber":41,"duration":169},{"title":"Maxwell's Silver Hammer (Take 5 - Remastered)","discNumber":3,"trackNumber":42,"duration":229},{"title":"Something (Demo - Remastered)","discNumber":3,"trackNumber":43,"duration":198},{"title":"Come Together (Take 1 - Remastered)","discNumber":3,"trackNumber":44,"duration":220},{"title":"Come And Get It (Demo - 1996 Remix - Remastered)","discNumber":3,"trackNumber":45,"duration":150},{"title":"Ain't She Sweet (Studio Jam - Remastered)","discNumber":3,"trackNumber":46,"duration":128},{"title":"Because (Vocals Mix - Remastered)","discNumber":3,"trackNumber":47,"duration":143},{"title":"Let It Be (Apple Studio - Remastered)","discNumber":3,"trackNumber":48,"duration":245},{"title":"I Me Mine (Take 16 - Remastered)","discNumber":3,"trackNumber":49,"duration":107},{"title":"The End (Remix With The Final Chord Of A Day In The Life - Remastered)","discNumber":3,"trackNumber":50,"duration":172},{"title":"I Saw Her Standing There (Take 2)","discNumber":4,"trackNumber":1,"duration":186},{"title":"Money (That's What I Want) (RM7 Undubbed)","discNumber":4,"trackNumber":2,"duration":168},{"title":"This Boy (Takes 12 And 13)","discNumber":4,"trackNumber":3,"duration":198},{"title":"Tell Me Why (Takes 4 And 5)","discNumber":4,"trackNumber":4,"duration":187},{"title":"If I Fell (Take 11)","discNumber":4,"trackNumber":5,"duration":158},{"title":"Matchbox (Take 1)","discNumber":4,"trackNumber":6,"duration":129},{"title":"Every Little Thing (Takes 6 And 7)","discNumber":4,"trackNumber":7,"duration":208},{"title":"I Need You (Take 1)","discNumber":4,"trackNumber":8,"duration":156},{"title":"I've Just Seen A Face (Take 3)","discNumber":4,"trackNumber":9,"duration":146},{"title":"In My Life (Take 1)","discNumber":4,"trackNumber":10,"duration":160},{"title":"Nowhere Man (First Version - Take 2)","discNumber":4,"trackNumber":11,"duration":144},{"title":"Got To Get You Into My Life (Second Version - Unnumbered Mix)","discNumber":4,"trackNumber":12,"duration":155},{"title":"Love You To (Take 7)","discNumber":4,"trackNumber":13,"duration":176},{"title":"Strawberry Fields Forever (Take 26)","discNumber":4,"trackNumber":14,"duration":200},{"title":"She's Leaving Home (Take 1 - Instrumental)","discNumber":4,"trackNumber":15,"duration":230},{"title":"Baby, You're A Rich Man (Takes 11 And 12)","discNumber":4,"trackNumber":16,"duration":366},{"title":"All You Need Is Love (Rehearsal For BBC Broadcast)","discNumber":4,"trackNumber":17,"duration":371},{"title":"The Fool On The Hill (Take 5 - Instrumental)","discNumber":4,"trackNumber":18,"duration":282},{"title":"I Am The Walrus (Take 19 - Strings, Brass, Clarinet Overdub)","discNumber":4,"trackNumber":19,"duration":296},{"title":"Hey Bulldog (Take 4 - Instrumental)","discNumber":4,"trackNumber":20,"duration":194},{"title":"Good Night (Take 10 With A Guitar Part From Take 5)","discNumber":4,"trackNumber":21,"duration":151},{"title":"While My Guitar Gently Weeps (Third Version - Take 27)","discNumber":4,"trackNumber":22,"duration":198},{"title":"(You're So Square) Baby I Don’t Care (Studio Jam)","discNumber":4,"trackNumber":23,"duration":43},{"title":"Helter Skelter (Second Version - Take 17)","discNumber":4,"trackNumber":24,"duration":218},{"title":"I Will (Take 29)","discNumber":4,"trackNumber":25,"duration":26},{"title":"Can You Take Me Back? (Take 1)","discNumber":4,"trackNumber":26,"duration":142},{"title":"Julia (Two Rehearsals)","discNumber":4,"trackNumber":27,"duration":266},{"title":"Get Back (Take 8)","discNumber":4,"trackNumber":28,"duration":231},{"title":"Octopus’s Garden (Rehearsal)","discNumber":4,"trackNumber":29,"duration":109},{"title":"Don’t Let Me Down (First Rooftop Performance)","discNumber":4,"trackNumber":30,"duration":207},{"title":"You Never Give Me Your Money (Take 36)","discNumber":4,"trackNumber":31,"duration":317},{"title":"Here Comes The Sun (Take 9)","discNumber":4,"trackNumber":32,"duration":221},{"title":"Something (Take 39 - Strings Only Instrumental)","discNumber":4,"trackNumber":33,"duration":155},{"title":"Free As A Bird (2025 Mix)","discNumber":4,"trackNumber":34,"duration":267},{"title":"Real Love (2025 Mix)","discNumber":4,"trackNumber":35,"duration":214},{"title":"Now And Then","discNumber":4,"trackNumber":36,"duration":249}]}
\ No newline at end of file
+{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1764965039.7088_320.jpg","albumMeta":{"title":"Anthology Collection","artists":[{"name":"ザ・ビートルズ","id":"218489"}],"releaseDate":"2025-11-21","label":{"name":"UMC (Universal Music Catalogue)","id":"274620"}},"trackList":[{"title":"Free As A Bird (1995 Mix - Remastered)","discNumber":1,"trackNumber":1,"duration":265},{"title":"John Lennon Speech 1 (Remastered)","discNumber":1,"trackNumber":2,"duration":11},{"title":"That'll Be The Day (Remastered)","discNumber":1,"trackNumber":3,"duration":129},{"title":"In Spite Of All The Danger (Remastered)","discNumber":1,"trackNumber":4,"duration":164},{"title":"Paul McCartney Speech 1 (Remastered)","discNumber":1,"trackNumber":5,"duration":18},{"title":"Hallelujah, I Love Her So (Home Demo - Remastered)","discNumber":1,"trackNumber":6,"duration":73},{"title":"You'll Be Mine (Home Demo - Remastered)","discNumber":1,"trackNumber":7,"duration":98},{"title":"Cayenne (Home Demo - Remastered)","discNumber":1,"trackNumber":8,"duration":73},{"title":"Paul McCartney Speech 2 (Remastered)","discNumber":1,"trackNumber":9,"duration":7},{"title":"My Bonnie (Remastered)","discNumber":1,"trackNumber":10,"duration":162},{"title":"Ain't She Sweet (Remastered)","discNumber":1,"trackNumber":11,"duration":133},{"title":"Cry For A Shadow (Remastered)","discNumber":1,"trackNumber":12,"duration":142},{"title":"John Lennon Speech 2 (Remastered)","discNumber":1,"trackNumber":13,"duration":9},{"title":"Brian Epstein Speech 1 (Remastered)","discNumber":1,"trackNumber":14,"duration":18},{"title":"Searchin' (Decca Audition - Remastered)","discNumber":1,"trackNumber":15,"duration":179},{"title":"Three Cool Cats (Decca Audition - Remastered)","discNumber":1,"trackNumber":16,"duration":145},{"title":"The Sheik Of Araby (Decca Audition - Remastered)","discNumber":1,"trackNumber":17,"duration":103},{"title":"Like Dreamers Do (Decca Audition - Remastered)","discNumber":1,"trackNumber":18,"duration":155},{"title":"Hello Little Girl (Decca Audition - Remastered)","discNumber":1,"trackNumber":19,"duration":100},{"title":"Brian Epstein Speech 2 (Remastered)","discNumber":1,"trackNumber":20,"duration":32},{"title":"Besame Mucho (June 1962 Version - Remastered)","discNumber":1,"trackNumber":21,"duration":156},{"title":"Love Me Do (First Version - Remastered)","discNumber":1,"trackNumber":22,"duration":151},{"title":"How Do You Do It (Remastered)","discNumber":1,"trackNumber":23,"duration":117},{"title":"Please Please Me (First Version - Remastered)","discNumber":1,"trackNumber":24,"duration":119},{"title":"One After 909 (Takes 3, 4 And 5 - Remastered)","discNumber":1,"trackNumber":25,"duration":143},{"title":"One After 909 (Edit Of Takes 4 And 5 - Remastered)","discNumber":1,"trackNumber":26,"duration":175},{"title":"Lend Me Your Comb (BBC Live Recording - Remastered)","discNumber":1,"trackNumber":27,"duration":109},{"title":"I'll Get You (Live On Sunday Night At The London Palladium - Remastered)","discNumber":1,"trackNumber":28,"duration":128},{"title":"John Lennon Speech 3 (Remastered)","discNumber":1,"trackNumber":29,"duration":12},{"title":"I Saw Her Standing There (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":30,"duration":168},{"title":"From Me To You (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":31,"duration":125},{"title":"Money (That's What I Want) (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":32,"duration":172},{"title":"You Really Got A Hold On Me (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":33,"duration":177},{"title":"Roll Over Beethoven (Live In Stockholm - Remastered)","discNumber":1,"trackNumber":34,"duration":141},{"title":"She Loves You (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":35,"duration":169},{"title":"Till There Was You (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":36,"duration":172},{"title":"Twist And Shout (Live From The Royal Variety Performance - Remastered)","discNumber":1,"trackNumber":37,"duration":187},{"title":"This Boy (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":38,"duration":141},{"title":"I Want To Hold Your Hand (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":39,"duration":156},{"title":"Speech (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":40,"duration":125},{"title":"Moonlight Bay (Live On The Morecambe And Wise Show - Remastered)","discNumber":1,"trackNumber":41,"duration":50},{"title":"Can't Buy Me Love (Take 2 With Solo From Take 1 - Remastered)","discNumber":1,"trackNumber":42,"duration":129},{"title":"All My Loving (Live On The Ed Sullivan Show - Remastered)","discNumber":1,"trackNumber":43,"duration":139},{"title":"You Can't Do That (Take 6 - Remastered)","discNumber":1,"trackNumber":44,"duration":162},{"title":"And I Love Her (Take 2 - Remastered)","discNumber":1,"trackNumber":45,"duration":112},{"title":"A Hard Day's Night (Take 1 - Remastered)","discNumber":1,"trackNumber":46,"duration":163},{"title":"I Wanna Be Your Man (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":47,"duration":107},{"title":"Long Tall Sally (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":48,"duration":105},{"title":"Boys (Live Session For Around The Beatles - Remastered)","discNumber":1,"trackNumber":49,"duration":109},{"title":"Shout (Live For Around The Beatles - Remastered)","discNumber":1,"trackNumber":50,"duration":90},{"title":"I'll Be Back (Take 2 - Remastered)","discNumber":1,"trackNumber":51,"duration":72},{"title":"I'll Be Back (Take 3 - Remastered)","discNumber":1,"trackNumber":52,"duration":117},{"title":"You Know What To Do (Demo - Remastered)","discNumber":1,"trackNumber":53,"duration":118},{"title":"No Reply (Demo - Remastered)","discNumber":1,"trackNumber":54,"duration":106},{"title":"Mr Moonlight (Takes 1 And 4 - Remastered)","discNumber":1,"trackNumber":55,"duration":167},{"title":"Leave My Kitten Alone (Take 5 - Remastered)","discNumber":1,"trackNumber":56,"duration":176},{"title":"No Reply (Take 2 - Remastered)","discNumber":1,"trackNumber":57,"duration":148},{"title":"Eight Days A Week (Takes 1, 2 And 4 - Remastered)","discNumber":1,"trackNumber":58,"duration":85},{"title":"Eight Days A Week (Take 5 - Remastered)","discNumber":1,"trackNumber":59,"duration":167},{"title":"Kansas City / Hey-Hey-Hey-Hey! (Take 2 - Remastered)","discNumber":1,"trackNumber":60,"duration":164},{"title":"Real Love (1996 Mix / Remastered)","discNumber":2,"trackNumber":1,"duration":233},{"title":"Yes It Is (Takes 2 And 14 - Remastered)","discNumber":2,"trackNumber":2,"duration":110},{"title":"I'm Down (Take 1 - Remastered)","discNumber":2,"trackNumber":3,"duration":173},{"title":"You've Got To Hide Your Love Away (Take 5 - Remastered)","discNumber":2,"trackNumber":4,"duration":164},{"title":"If You've Got Trouble (Take 1 - Remastered)","discNumber":2,"trackNumber":5,"duration":168},{"title":"That Means A Lot (Take 1 - Remastered)","discNumber":2,"trackNumber":6,"duration":146},{"title":"Yesterday (Take 1 - Remastered)","discNumber":2,"trackNumber":7,"duration":153},{"title":"It's Only Love (Takes 3 And 2 - Remastered)","discNumber":2,"trackNumber":8,"duration":118},{"title":"I Feel Fine (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":9,"duration":135},{"title":"Ticket To Ride (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":10,"duration":164},{"title":"Yesterday (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":11,"duration":162},{"title":"Help! (Live On Blackpool Night Out - Remastered)","discNumber":2,"trackNumber":12,"duration":174},{"title":"Everybody's Trying To Be My Baby (Live At Shea Stadium, New York - Remastered)","discNumber":2,"trackNumber":13,"duration":165},{"title":"Norwegian Wood (This Bird Has Flown) (Take 1 - Remastered)","discNumber":2,"trackNumber":14,"duration":119},{"title":"I'm Looking Through You (Take 1 - Remastered)","discNumber":2,"trackNumber":15,"duration":173},{"title":"12-Bar Original (Take 2 Edited - Remastered)","discNumber":2,"trackNumber":16,"duration":175},{"title":"Tomorrow Never Knows (Take 1 - Remastered)","discNumber":2,"trackNumber":17,"duration":194},{"title":"Got To Get You Into My Life (Take 5 - Remastered)","discNumber":2,"trackNumber":18,"duration":174},{"title":"And Your Bird Can Sing (Takes 1 and 2 - Remastered)","discNumber":2,"trackNumber":19,"duration":133},{"title":"Taxman (Take 11 - Remastered)","discNumber":2,"trackNumber":20,"duration":152},{"title":"Eleanor Rigby (Take 14 - Strings Only - Remastered)","discNumber":2,"trackNumber":21,"duration":126},{"title":"I'm Only Sleeping (Rehearsal - Remastered)","discNumber":2,"trackNumber":22,"duration":40},{"title":"I'm Only Sleeping (Take 1 - Remastered)","discNumber":2,"trackNumber":23,"duration":179},{"title":"Rock And Roll Music (Live In Tokyo - Remastered)","discNumber":2,"trackNumber":24,"duration":98},{"title":"She's A Woman (Live In Tokyo - Remastered)","discNumber":2,"trackNumber":25,"duration":176},{"title":"Strawberry Fields Forever (Home Demo Sequence - Remastered)","discNumber":2,"trackNumber":26,"duration":100},{"title":"Strawberry Fields Forever (Take 1 - Remastered)","discNumber":2,"trackNumber":27,"duration":154},{"title":"Strawberry Fields Forever (Take 7 And Edit Piece - Remastered)","discNumber":2,"trackNumber":28,"duration":253},{"title":"Penny Lane (Remix - Remastered)","discNumber":2,"trackNumber":29,"duration":192},{"title":"A Day In The Life (Takes 1, 2, 6 And Orchestra - Remastered)","discNumber":2,"trackNumber":30,"duration":304},{"title":"Good Morning Good Morning (Take 8 - Remastered)","discNumber":2,"trackNumber":31,"duration":159},{"title":"Only A Northern Song (Takes 3 And 12 - Remastered)","discNumber":2,"trackNumber":32,"duration":163},{"title":"Being For The Benefit Of Mr Kite! (Takes 1 And 2 - Remastered)","discNumber":2,"trackNumber":33,"duration":65},{"title":"Being For The Benefit Of Mr Kite! (Take 7 - Remastered)","discNumber":2,"trackNumber":34,"duration":153},{"title":"Lucy In The Sky With Diamonds (Takes 6, 7 And 8 - Remastered)","discNumber":2,"trackNumber":35,"duration":185},{"title":"Within You Without You (Instrumental - Remastered)","discNumber":2,"trackNumber":36,"duration":327},{"title":"Sgt Pepper's Lonely Hearts Club Band (Reprise - Take 5 - Remastered)","discNumber":2,"trackNumber":37,"duration":87},{"title":"You Know My Name (Look Up The Number) (Stereo Remix - Remastered)","discNumber":2,"trackNumber":38,"duration":343},{"title":"I Am The Walrus (Take 16 - Remastered)","discNumber":2,"trackNumber":39,"duration":241},{"title":"The Fool On The Hill (Demo - Remastered)","discNumber":2,"trackNumber":40,"duration":168},{"title":"Your Mother Should Know (Take 27 - Remastered)","discNumber":2,"trackNumber":41,"duration":182},{"title":"The Fool On The Hill (Take 4 - Remastered)","discNumber":2,"trackNumber":42,"duration":224},{"title":"Hello, Goodbye (Take 16 - Remastered)","discNumber":2,"trackNumber":43,"duration":197},{"title":"Lady Madonna (Takes 3 And 4 - Remastered)","discNumber":2,"trackNumber":44,"duration":141},{"title":"Across The Universe (Take 2 - Remastered)","discNumber":2,"trackNumber":45,"duration":210},{"title":"A Beginning (Remastered)","discNumber":3,"trackNumber":1,"duration":50},{"title":"Happiness Is A Warm Gun (Esher Demo With False Start - Remastered)","discNumber":3,"trackNumber":2,"duration":134},{"title":"Helter Skelter (Take 2 Edited - Remastered)","discNumber":3,"trackNumber":3,"duration":277},{"title":"Mean Mr Mustard (Esher Demo - Remastered)","discNumber":3,"trackNumber":4,"duration":118},{"title":"Polythene Pam (Esher Demo - Remastered)","discNumber":3,"trackNumber":5,"duration":86},{"title":"Glass Onion (Esher Demo - Remastered)","discNumber":3,"trackNumber":6,"duration":111},{"title":"Junk (Esher Demo - Remastered)","discNumber":3,"trackNumber":7,"duration":144},{"title":"Piggies (Esher Demo - Remastered)","discNumber":3,"trackNumber":8,"duration":120},{"title":"Honey Pie (Esher Demo Edited - Remastered)","discNumber":3,"trackNumber":9,"duration":79},{"title":"Don't Pass Me By (Take 3 With Take 5 Vocal - Remastered)","discNumber":3,"trackNumber":10,"duration":162},{"title":"Ob-La-Di, Ob-La-Da (First Version - Take 5 - Remastered)","discNumber":3,"trackNumber":11,"duration":176},{"title":"Good Night (Rehearsal And Take 34 - Remastered)","discNumber":3,"trackNumber":12,"duration":158},{"title":"Cry Baby Cry (Take 1 - Remastered)","discNumber":3,"trackNumber":13,"duration":166},{"title":"Blackbird (Take 4 - Remastered)","discNumber":3,"trackNumber":14,"duration":138},{"title":"Sexy Sadie (Take 6 - Remastered)","discNumber":3,"trackNumber":15,"duration":246},{"title":"While My Guitar Gently Weeps (Acoustic Version - Take 1 - Remastered)","discNumber":3,"trackNumber":16,"duration":207},{"title":"Hey Jude (Take 2 - Remastered)","discNumber":3,"trackNumber":17,"duration":261},{"title":"Not Guilty (Take 102 Edited - Remastered)","discNumber":3,"trackNumber":18,"duration":202},{"title":"Mother Nature's Son (Take 2 - Remastered)","discNumber":3,"trackNumber":19,"duration":197},{"title":"Glass Onion (Mono Mix - Remastered)","discNumber":3,"trackNumber":20,"duration":128},{"title":"Rocky Raccoon (Take 8 - Remastered)","discNumber":3,"trackNumber":21,"duration":252},{"title":"What's The New Mary Jane (Take 4 - Remastered)","discNumber":3,"trackNumber":22,"duration":372},{"title":"Step Inside Love / Los Paranoias (Studio Jam - Remastered)","discNumber":3,"trackNumber":23,"duration":150},{"title":"I'm So Tired (Edit Of Takes 3, 6 And 9 - Remastered)","discNumber":3,"trackNumber":24,"duration":134},{"title":"I Will (Take 1 - Remastered)","discNumber":3,"trackNumber":25,"duration":115},{"title":"Why Don't We Do It In The Road (Take 4 - Remastered)","discNumber":3,"trackNumber":26,"duration":135},{"title":"Julia (Take 2 - Remastered)","discNumber":3,"trackNumber":27,"duration":118},{"title":"I've Got A Feeling (Apple Studio - Remastered)","discNumber":3,"trackNumber":28,"duration":168},{"title":"She Came In Through The Bathroom Window (Apple Studio - Remastered)","discNumber":3,"trackNumber":29,"duration":216},{"title":"Dig A Pony (Apple Studio - Remastered)","discNumber":3,"trackNumber":30,"duration":258},{"title":"Two Of Us (Apple Studio - Remastered)","discNumber":3,"trackNumber":31,"duration":207},{"title":"For You Blue (Apple Studio - Remastered)","discNumber":3,"trackNumber":32,"duration":142},{"title":"Teddy Boy (Apple Studio - Remastered)","discNumber":3,"trackNumber":33,"duration":198},{"title":"Rip It Up / Shake, Rattle And Roll / Blue Suede Shoes (Medley - Apple Studio Jam - Remastered)","discNumber":3,"trackNumber":34,"duration":190},{"title":"The Long And Winding Road (Apple Studio - Remastered)","discNumber":3,"trackNumber":35,"duration":221},{"title":"Oh! Darling (Apple Studio - Remastered)","discNumber":3,"trackNumber":36,"duration":247},{"title":"All Things Must Pass (Demo - Remastered)","discNumber":3,"trackNumber":37,"duration":184},{"title":"Mailman, Bring Me No More Blues (Apple Studio Jam - Remastered)","discNumber":3,"trackNumber":38,"duration":116},{"title":"Get Back (Third Rooftop Performance - Remastered)","discNumber":3,"trackNumber":39,"duration":188},{"title":"Old Brown Shoe (Demo - Remastered)","discNumber":3,"trackNumber":40,"duration":182},{"title":"Octopus's Garden (Take 2 - Remastered)","discNumber":3,"trackNumber":41,"duration":169},{"title":"Maxwell's Silver Hammer (Take 5 - Remastered)","discNumber":3,"trackNumber":42,"duration":229},{"title":"Something (Demo - Remastered)","discNumber":3,"trackNumber":43,"duration":198},{"title":"Come Together (Take 1 - Remastered)","discNumber":3,"trackNumber":44,"duration":220},{"title":"Come And Get It (Demo - 1996 Remix - Remastered)","discNumber":3,"trackNumber":45,"duration":150},{"title":"Ain't She Sweet (Studio Jam - Remastered)","discNumber":3,"trackNumber":46,"duration":128},{"title":"Because (Vocals Mix - Remastered)","discNumber":3,"trackNumber":47,"duration":143},{"title":"Let It Be (Apple Studio - Remastered)","discNumber":3,"trackNumber":48,"duration":245},{"title":"I Me Mine (Take 16 - Remastered)","discNumber":3,"trackNumber":49,"duration":107},{"title":"The End (Remix With The Final Chord Of A Day In The Life - Remastered)","discNumber":3,"trackNumber":50,"duration":172},{"title":"I Saw Her Standing There (Take 2)","discNumber":4,"trackNumber":1,"duration":186},{"title":"Money (That's What I Want) (RM7 Undubbed)","discNumber":4,"trackNumber":2,"duration":168},{"title":"This Boy (Takes 12 And 13)","discNumber":4,"trackNumber":3,"duration":198},{"title":"Tell Me Why (Takes 4 And 5)","discNumber":4,"trackNumber":4,"duration":187},{"title":"If I Fell (Take 11)","discNumber":4,"trackNumber":5,"duration":158},{"title":"Matchbox (Take 1)","discNumber":4,"trackNumber":6,"duration":129},{"title":"Every Little Thing (Takes 6 And 7)","discNumber":4,"trackNumber":7,"duration":208},{"title":"I Need You (Take 1)","discNumber":4,"trackNumber":8,"duration":156},{"title":"I've Just Seen A Face (Take 3)","discNumber":4,"trackNumber":9,"duration":146},{"title":"In My Life (Take 1)","discNumber":4,"trackNumber":10,"duration":160},{"title":"Nowhere Man (First Version - Take 2)","discNumber":4,"trackNumber":11,"duration":144},{"title":"Got To Get You Into My Life (Second Version - Unnumbered Mix)","discNumber":4,"trackNumber":12,"duration":155},{"title":"Love You To (Take 7)","discNumber":4,"trackNumber":13,"duration":176},{"title":"Strawberry Fields Forever (Take 26)","discNumber":4,"trackNumber":14,"duration":200},{"title":"She's Leaving Home (Take 1 - Instrumental)","discNumber":4,"trackNumber":15,"duration":230},{"title":"Baby, You're A Rich Man (Takes 11 And 12)","discNumber":4,"trackNumber":16,"duration":366},{"title":"All You Need Is Love (Rehearsal For BBC Broadcast)","discNumber":4,"trackNumber":17,"duration":371},{"title":"The Fool On The Hill (Take 5 - Instrumental)","discNumber":4,"trackNumber":18,"duration":282},{"title":"I Am The Walrus (Take 19 - Strings, Brass, Clarinet Overdub)","discNumber":4,"trackNumber":19,"duration":296},{"title":"Hey Bulldog (Take 4 - Instrumental)","discNumber":4,"trackNumber":20,"duration":194},{"title":"Good Night (Take 10 With A Guitar Part From Take 5)","discNumber":4,"trackNumber":21,"duration":151},{"title":"While My Guitar Gently Weeps (Third Version - Take 27)","discNumber":4,"trackNumber":22,"duration":198},{"title":"(You're So Square) Baby I Don’t Care (Studio Jam)","discNumber":4,"trackNumber":23,"duration":43},{"title":"Helter Skelter (Second Version - Take 17)","discNumber":4,"trackNumber":24,"duration":218},{"title":"I Will (Take 29)","discNumber":4,"trackNumber":25,"duration":26},{"title":"Can You Take Me Back? (Take 1)","discNumber":4,"trackNumber":26,"duration":142},{"title":"Julia (Two Rehearsals)","discNumber":4,"trackNumber":27,"duration":266},{"title":"Get Back (Take 8)","discNumber":4,"trackNumber":28,"duration":231},{"title":"Octopus’s Garden (Rehearsal)","discNumber":4,"trackNumber":29,"duration":109},{"title":"Don’t Let Me Down (First Rooftop Performance)","discNumber":4,"trackNumber":30,"duration":207},{"title":"You Never Give Me Your Money (Take 36)","discNumber":4,"trackNumber":31,"duration":317},{"title":"Here Comes The Sun (Take 9)","discNumber":4,"trackNumber":32,"duration":221},{"title":"Something (Take 39 - Strings Only Instrumental)","discNumber":4,"trackNumber":33,"duration":155},{"title":"Free As A Bird (2025 Mix)","discNumber":4,"trackNumber":34,"duration":267},{"title":"Real Love (2025 Mix)","discNumber":4,"trackNumber":35,"duration":214},{"title":"Now And Then","discNumber":4,"trackNumber":36,"duration":249}]}
\ No newline at end of file
From 1b0b326f824549ad7d1b577bc19ab10617e8e198 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Sun, 14 Dec 2025 21:34:13 -0500
Subject: [PATCH 10/15] feat(ototoy): scrape track artists
---
.../Ototoy/__snapshots__/mod.test.ts.snap | 789 ++++++++++++++++++
providers/Ototoy/json_types.ts | 5 +-
providers/Ototoy/mod.test.ts | 26 +
providers/Ototoy/mod.ts | 34 +-
testdata/https!/jp.ototoy/_/default/p/1822344 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3016055 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3228080 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3237840 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3286704 | 1 +
testdata/https!/jp.ototoy/_/default/p/709920 | 2 +-
10 files changed, 847 insertions(+), 18 deletions(-)
create mode 100644 testdata/https!/jp.ototoy/_/default/p/3286704
diff --git a/providers/Ototoy/__snapshots__/mod.test.ts.snap b/providers/Ototoy/__snapshots__/mod.test.ts.snap
index e346d3ac..6c8eaaee 100644
--- a/providers/Ototoy/__snapshots__/mod.test.ts.snap
+++ b/providers/Ototoy/__snapshots__/mod.test.ts.snap
@@ -67,6 +67,7 @@ snapshot[`OTOTOY provider > release lookup > single track release 1`] = `
number: 1,
tracklist: [
{
+ artists: undefined,
length: 238000,
number: 1,
title: "トゥイー・ボックスの人形劇場",
@@ -153,360 +154,551 @@ snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = `
number: 1,
tracklist: [
{
+ artists: undefined,
length: 265000,
number: 1,
title: "Free As A Bird (1995 Mix - Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ジョン・レノン",
+ externalIds: [
+ {
+ id: "284005",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ジョン・レノン",
+ },
+ ],
length: 11000,
number: 2,
title: "John Lennon Speech 1 (Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ザ・クォーリー・メン",
+ externalIds: [
+ {
+ id: "1823343",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ザ・クォーリー・メン",
+ },
+ ],
length: 129000,
number: 3,
title: "That'll Be The Day (Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ザ・クォーリー・メン",
+ externalIds: [
+ {
+ id: "1823343",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ザ・クォーリー・メン",
+ },
+ ],
length: 164000,
number: 4,
title: "In Spite Of All The Danger (Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ポール・マッカートニー",
+ externalIds: [
+ {
+ id: "268135",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ポール・マッカートニー",
+ },
+ ],
length: 18000,
number: 5,
title: "Paul McCartney Speech 1 (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 73000,
number: 6,
title: "Hallelujah, I Love Her So (Home Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 98000,
number: 7,
title: "You'll Be Mine (Home Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 73000,
number: 8,
title: "Cayenne (Home Demo - Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ポール・マッカートニー",
+ externalIds: [
+ {
+ id: "268135",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ポール・マッカートニー",
+ },
+ ],
length: 7000,
number: 9,
title: "Paul McCartney Speech 2 (Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ザ・ビートルズ",
+ externalIds: [
+ {
+ id: "218489",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ザ・ビートルズ",
+ },
+ {
+ creditedName: "ビートルズとトニー・シェリダン",
+ externalIds: [
+ {
+ id: "274608",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ビートルズとトニー・シェリダン",
+ },
+ ],
length: 162000,
number: 10,
title: "My Bonnie (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 133000,
number: 11,
title: "Ain't She Sweet (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 142000,
number: 12,
title: "Cry For A Shadow (Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ジョン・レノン",
+ externalIds: [
+ {
+ id: "284005",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ジョン・レノン",
+ },
+ ],
length: 9000,
number: 13,
title: "John Lennon Speech 2 (Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ブライアン・エプスタイン",
+ externalIds: [
+ {
+ id: "1823344",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ブライアン・エプスタイン",
+ },
+ ],
length: 18000,
number: 14,
title: "Brian Epstein Speech 1 (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 179000,
number: 15,
title: "Searchin' (Decca Audition - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 145000,
number: 16,
title: "Three Cool Cats (Decca Audition - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 103000,
number: 17,
title: "The Sheik Of Araby (Decca Audition - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 155000,
number: 18,
title: "Like Dreamers Do (Decca Audition - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 100000,
number: 19,
title: "Hello Little Girl (Decca Audition - Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ブライアン・エプスタイン",
+ externalIds: [
+ {
+ id: "1823344",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ブライアン・エプスタイン",
+ },
+ ],
length: 32000,
number: 20,
title: "Brian Epstein Speech 2 (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 156000,
number: 21,
title: "Besame Mucho (June 1962 Version - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 151000,
number: 22,
title: "Love Me Do (First Version - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 117000,
number: 23,
title: "How Do You Do It (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 119000,
number: 24,
title: "Please Please Me (First Version - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 143000,
number: 25,
title: "One After 909 (Takes 3, 4 And 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 175000,
number: 26,
title: "One After 909 (Edit Of Takes 4 And 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 109000,
number: 27,
title: "Lend Me Your Comb (BBC Live Recording - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 128000,
number: 28,
title: "I'll Get You (Live On Sunday Night At The London Palladium - Remastered)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "ジョン・レノン",
+ externalIds: [
+ {
+ id: "284005",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ジョン・レノン",
+ },
+ ],
length: 12000,
number: 29,
title: "John Lennon Speech 3 (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 168000,
number: 30,
title: "I Saw Her Standing There (Live In Stockholm - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 125000,
number: 31,
title: "From Me To You (Live In Stockholm - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 172000,
number: 32,
title: "Money (That's What I Want) (Live In Stockholm - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 177000,
number: 33,
title: "You Really Got A Hold On Me (Live In Stockholm - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 141000,
number: 34,
title: "Roll Over Beethoven (Live In Stockholm - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 169000,
number: 35,
title: "She Loves You (Live From The Royal Variety Performance - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 172000,
number: 36,
title: "Till There Was You (Live From The Royal Variety Performance - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 187000,
number: 37,
title: "Twist And Shout (Live From The Royal Variety Performance - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 141000,
number: 38,
title: "This Boy (Live On The Morecambe And Wise Show - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 156000,
number: 39,
title: "I Want To Hold Your Hand (Live On The Morecambe And Wise Show - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 125000,
number: 40,
title: "Speech (Live On The Morecambe And Wise Show - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 50000,
number: 41,
title: "Moonlight Bay (Live On The Morecambe And Wise Show - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 129000,
number: 42,
title: "Can't Buy Me Love (Take 2 With Solo From Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 139000,
number: 43,
title: "All My Loving (Live On The Ed Sullivan Show - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 162000,
number: 44,
title: "You Can't Do That (Take 6 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 112000,
number: 45,
title: "And I Love Her (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 163000,
number: 46,
title: "A Hard Day's Night (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 107000,
number: 47,
title: "I Wanna Be Your Man (Live For Around The Beatles - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 105000,
number: 48,
title: "Long Tall Sally (Live For Around The Beatles - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 109000,
number: 49,
title: "Boys (Live Session For Around The Beatles - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 90000,
number: 50,
title: "Shout (Live For Around The Beatles - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 72000,
number: 51,
title: "I'll Be Back (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 117000,
number: 52,
title: "I'll Be Back (Take 3 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 118000,
number: 53,
title: "You Know What To Do (Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 106000,
number: 54,
title: "No Reply (Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 167000,
number: 55,
title: "Mr Moonlight (Takes 1 And 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 176000,
number: 56,
title: "Leave My Kitten Alone (Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 148000,
number: 57,
title: "No Reply (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 85000,
number: 58,
title: "Eight Days A Week (Takes 1, 2 And 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 167000,
number: 59,
title: "Eight Days A Week (Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 164000,
number: 60,
title: "Kansas City / Hey-Hey-Hey-Hey! (Take 2 - Remastered)",
@@ -519,270 +711,315 @@ snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = `
number: 2,
tracklist: [
{
+ artists: undefined,
length: 233000,
number: 1,
title: "Real Love (1996 Mix / Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 110000,
number: 2,
title: "Yes It Is (Takes 2 And 14 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 173000,
number: 3,
title: "I'm Down (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 164000,
number: 4,
title: "You've Got To Hide Your Love Away (Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 168000,
number: 5,
title: "If You've Got Trouble (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 146000,
number: 6,
title: "That Means A Lot (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 153000,
number: 7,
title: "Yesterday (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 118000,
number: 8,
title: "It's Only Love (Takes 3 And 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 135000,
number: 9,
title: "I Feel Fine (Live On Blackpool Night Out - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 164000,
number: 10,
title: "Ticket To Ride (Live On Blackpool Night Out - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 162000,
number: 11,
title: "Yesterday (Live On Blackpool Night Out - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 174000,
number: 12,
title: "Help! (Live On Blackpool Night Out - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 165000,
number: 13,
title: "Everybody's Trying To Be My Baby (Live At Shea Stadium, New York - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 119000,
number: 14,
title: "Norwegian Wood (This Bird Has Flown) (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 173000,
number: 15,
title: "I'm Looking Through You (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 175000,
number: 16,
title: "12-Bar Original (Take 2 Edited - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 194000,
number: 17,
title: "Tomorrow Never Knows (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 174000,
number: 18,
title: "Got To Get You Into My Life (Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 133000,
number: 19,
title: "And Your Bird Can Sing (Takes 1 and 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 152000,
number: 20,
title: "Taxman (Take 11 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 126000,
number: 21,
title: "Eleanor Rigby (Take 14 - Strings Only - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 40000,
number: 22,
title: "I'm Only Sleeping (Rehearsal - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 179000,
number: 23,
title: "I'm Only Sleeping (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 98000,
number: 24,
title: "Rock And Roll Music (Live In Tokyo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 176000,
number: 25,
title: "She's A Woman (Live In Tokyo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 100000,
number: 26,
title: "Strawberry Fields Forever (Home Demo Sequence - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 154000,
number: 27,
title: "Strawberry Fields Forever (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 253000,
number: 28,
title: "Strawberry Fields Forever (Take 7 And Edit Piece - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 192000,
number: 29,
title: "Penny Lane (Remix - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 304000,
number: 30,
title: "A Day In The Life (Takes 1, 2, 6 And Orchestra - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 159000,
number: 31,
title: "Good Morning Good Morning (Take 8 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 163000,
number: 32,
title: "Only A Northern Song (Takes 3 And 12 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 65000,
number: 33,
title: "Being For The Benefit Of Mr Kite! (Takes 1 And 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 153000,
number: 34,
title: "Being For The Benefit Of Mr Kite! (Take 7 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 185000,
number: 35,
title: "Lucy In The Sky With Diamonds (Takes 6, 7 And 8 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 327000,
number: 36,
title: "Within You Without You (Instrumental - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 87000,
number: 37,
title: "Sgt Pepper's Lonely Hearts Club Band (Reprise - Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 343000,
number: 38,
title: "You Know My Name (Look Up The Number) (Stereo Remix - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 241000,
number: 39,
title: "I Am The Walrus (Take 16 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 168000,
number: 40,
title: "The Fool On The Hill (Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 182000,
number: 41,
title: "Your Mother Should Know (Take 27 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 224000,
number: 42,
title: "The Fool On The Hill (Take 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 197000,
number: 43,
title: "Hello, Goodbye (Take 16 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 141000,
number: 44,
title: "Lady Madonna (Takes 3 And 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 210000,
number: 45,
title: "Across The Universe (Take 2 - Remastered)",
@@ -795,300 +1032,350 @@ snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = `
number: 3,
tracklist: [
{
+ artists: undefined,
length: 50000,
number: 1,
title: "A Beginning (Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 134000,
number: 2,
title: "Happiness Is A Warm Gun (Esher Demo With False Start - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 277000,
number: 3,
title: "Helter Skelter (Take 2 Edited - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 118000,
number: 4,
title: "Mean Mr Mustard (Esher Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 86000,
number: 5,
title: "Polythene Pam (Esher Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 111000,
number: 6,
title: "Glass Onion (Esher Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 144000,
number: 7,
title: "Junk (Esher Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 120000,
number: 8,
title: "Piggies (Esher Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 79000,
number: 9,
title: "Honey Pie (Esher Demo Edited - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 162000,
number: 10,
title: "Don't Pass Me By (Take 3 With Take 5 Vocal - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 176000,
number: 11,
title: "Ob-La-Di, Ob-La-Da (First Version - Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 158000,
number: 12,
title: "Good Night (Rehearsal And Take 34 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 166000,
number: 13,
title: "Cry Baby Cry (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 138000,
number: 14,
title: "Blackbird (Take 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 246000,
number: 15,
title: "Sexy Sadie (Take 6 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 207000,
number: 16,
title: "While My Guitar Gently Weeps (Acoustic Version - Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 261000,
number: 17,
title: "Hey Jude (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 202000,
number: 18,
title: "Not Guilty (Take 102 Edited - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 197000,
number: 19,
title: "Mother Nature's Son (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 128000,
number: 20,
title: "Glass Onion (Mono Mix - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 252000,
number: 21,
title: "Rocky Raccoon (Take 8 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 372000,
number: 22,
title: "What's The New Mary Jane (Take 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 150000,
number: 23,
title: "Step Inside Love / Los Paranoias (Studio Jam - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 134000,
number: 24,
title: "I'm So Tired (Edit Of Takes 3, 6 And 9 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 115000,
number: 25,
title: "I Will (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 135000,
number: 26,
title: "Why Don't We Do It In The Road (Take 4 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 118000,
number: 27,
title: "Julia (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 168000,
number: 28,
title: "I've Got A Feeling (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 216000,
number: 29,
title: "She Came In Through The Bathroom Window (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 258000,
number: 30,
title: "Dig A Pony (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 207000,
number: 31,
title: "Two Of Us (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 142000,
number: 32,
title: "For You Blue (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 33,
title: "Teddy Boy (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 190000,
number: 34,
title: "Rip It Up / Shake, Rattle And Roll / Blue Suede Shoes (Medley - Apple Studio Jam - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 221000,
number: 35,
title: "The Long And Winding Road (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 247000,
number: 36,
title: "Oh! Darling (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 184000,
number: 37,
title: "All Things Must Pass (Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 116000,
number: 38,
title: "Mailman, Bring Me No More Blues (Apple Studio Jam - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 188000,
number: 39,
title: "Get Back (Third Rooftop Performance - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 182000,
number: 40,
title: "Old Brown Shoe (Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 169000,
number: 41,
title: "Octopus's Garden (Take 2 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 229000,
number: 42,
title: "Maxwell's Silver Hammer (Take 5 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 43,
title: "Something (Demo - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 220000,
number: 44,
title: "Come Together (Take 1 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 150000,
number: 45,
title: "Come And Get It (Demo - 1996 Remix - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 128000,
number: 46,
title: "Ain't She Sweet (Studio Jam - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 143000,
number: 47,
title: "Because (Vocals Mix - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 245000,
number: 48,
title: "Let It Be (Apple Studio - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 107000,
number: 49,
title: "I Me Mine (Take 16 - Remastered)",
type: "audio",
},
{
+ artists: undefined,
length: 172000,
number: 50,
title: "The End (Remix With The Final Chord Of A Day In The Life - Remastered)",
@@ -1101,216 +1388,252 @@ snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = `
number: 4,
tracklist: [
{
+ artists: undefined,
length: 186000,
number: 1,
title: "I Saw Her Standing There (Take 2)",
type: "audio",
},
{
+ artists: undefined,
length: 168000,
number: 2,
title: "Money (That's What I Want) (RM7 Undubbed)",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 3,
title: "This Boy (Takes 12 And 13)",
type: "audio",
},
{
+ artists: undefined,
length: 187000,
number: 4,
title: "Tell Me Why (Takes 4 And 5)",
type: "audio",
},
{
+ artists: undefined,
length: 158000,
number: 5,
title: "If I Fell (Take 11)",
type: "audio",
},
{
+ artists: undefined,
length: 129000,
number: 6,
title: "Matchbox (Take 1)",
type: "audio",
},
{
+ artists: undefined,
length: 208000,
number: 7,
title: "Every Little Thing (Takes 6 And 7)",
type: "audio",
},
{
+ artists: undefined,
length: 156000,
number: 8,
title: "I Need You (Take 1)",
type: "audio",
},
{
+ artists: undefined,
length: 146000,
number: 9,
title: "I've Just Seen A Face (Take 3)",
type: "audio",
},
{
+ artists: undefined,
length: 160000,
number: 10,
title: "In My Life (Take 1)",
type: "audio",
},
{
+ artists: undefined,
length: 144000,
number: 11,
title: "Nowhere Man (First Version - Take 2)",
type: "audio",
},
{
+ artists: undefined,
length: 155000,
number: 12,
title: "Got To Get You Into My Life (Second Version - Unnumbered Mix)",
type: "audio",
},
{
+ artists: undefined,
length: 176000,
number: 13,
title: "Love You To (Take 7)",
type: "audio",
},
{
+ artists: undefined,
length: 200000,
number: 14,
title: "Strawberry Fields Forever (Take 26)",
type: "audio",
},
{
+ artists: undefined,
length: 230000,
number: 15,
title: "She's Leaving Home (Take 1 - Instrumental)",
type: "audio",
},
{
+ artists: undefined,
length: 366000,
number: 16,
title: "Baby, You're A Rich Man (Takes 11 And 12)",
type: "audio",
},
{
+ artists: undefined,
length: 371000,
number: 17,
title: "All You Need Is Love (Rehearsal For BBC Broadcast)",
type: "audio",
},
{
+ artists: undefined,
length: 282000,
number: 18,
title: "The Fool On The Hill (Take 5 - Instrumental)",
type: "audio",
},
{
+ artists: undefined,
length: 296000,
number: 19,
title: "I Am The Walrus (Take 19 - Strings, Brass, Clarinet Overdub)",
type: "audio",
},
{
+ artists: undefined,
length: 194000,
number: 20,
title: "Hey Bulldog (Take 4 - Instrumental)",
type: "audio",
},
{
+ artists: undefined,
length: 151000,
number: 21,
title: "Good Night (Take 10 With A Guitar Part From Take 5)",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 22,
title: "While My Guitar Gently Weeps (Third Version - Take 27)",
type: "audio",
},
{
+ artists: undefined,
length: 43000,
number: 23,
title: "(You're So Square) Baby I Don’t Care (Studio Jam)",
type: "audio",
},
{
+ artists: undefined,
length: 218000,
number: 24,
title: "Helter Skelter (Second Version - Take 17)",
type: "audio",
},
{
+ artists: undefined,
length: 26000,
number: 25,
title: "I Will (Take 29)",
type: "audio",
},
{
+ artists: undefined,
length: 142000,
number: 26,
title: "Can You Take Me Back? (Take 1)",
type: "audio",
},
{
+ artists: undefined,
length: 266000,
number: 27,
title: "Julia (Two Rehearsals)",
type: "audio",
},
{
+ artists: undefined,
length: 231000,
number: 28,
title: "Get Back (Take 8)",
type: "audio",
},
{
+ artists: undefined,
length: 109000,
number: 29,
title: "Octopus’s Garden (Rehearsal)",
type: "audio",
},
{
+ artists: undefined,
length: 207000,
number: 30,
title: "Don’t Let Me Down (First Rooftop Performance)",
type: "audio",
},
{
+ artists: undefined,
length: 317000,
number: 31,
title: "You Never Give Me Your Money (Take 36)",
type: "audio",
},
{
+ artists: undefined,
length: 221000,
number: 32,
title: "Here Comes The Sun (Take 9)",
type: "audio",
},
{
+ artists: undefined,
length: 155000,
number: 33,
title: "Something (Take 39 - Strings Only Instrumental)",
type: "audio",
},
{
+ artists: undefined,
length: 267000,
number: 34,
title: "Free As A Bird (2025 Mix)",
type: "audio",
},
{
+ artists: undefined,
length: 214000,
number: 35,
title: "Real Love (2025 Mix)",
type: "audio",
},
{
+ artists: undefined,
length: 249000,
number: 36,
title: "Now And Then",
@@ -1441,6 +1764,7 @@ snapshot[`OTOTOY provider > release lookup > multiple artists 1`] = `
number: 1,
tracklist: [
{
+ artists: undefined,
length: 223000,
number: 1,
title: "Gimme吟味virtuaる最高star!!!! (feat. さくらみこ, 白上フブキ, 夏色まつり & 宝鐘マリン)",
@@ -1515,84 +1839,281 @@ snapshot[`OTOTOY provider > release lookup > no label 1`] = `
number: 1,
tracklist: [
{
+ artists: undefined,
length: 212000,
number: 1,
title: "UNITE",
type: "audio",
},
{
+ artists: undefined,
length: 207000,
number: 2,
title: "BLINDLY",
type: "audio",
},
{
+ artists: undefined,
length: 192000,
number: 3,
title: "TRAUMATIC",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "Watson",
+ externalIds: [
+ {
+ id: "963757",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Watson",
+ },
+ {
+ creditedName: "MIKADO",
+ externalIds: [
+ {
+ id: "725029",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "MIKADO",
+ },
+ {
+ creditedName: "¥ellow Bucks",
+ externalIds: [
+ {
+ id: "671436",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "¥ellow Bucks",
+ },
+ ],
length: 314000,
number: 4,
title: "NOOFFSEASON (feat. Watson, MIKADO & ¥ellow Bucks)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "CFN MALIK",
+ externalIds: [
+ {
+ id: "1337903",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "CFN MALIK",
+ },
+ ],
length: 186000,
number: 5,
title: "1 2 3 (feat. CFN MALIK)",
type: "audio",
},
{
+ artists: undefined,
length: 239000,
number: 6,
title: "PRIDE",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "Bonbero",
+ externalIds: [
+ {
+ id: "693436",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Bonbero",
+ },
+ ],
length: 212000,
number: 7,
title: "WWW (feat. Bonbero)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "JP THE WAVY",
+ externalIds: [
+ {
+ id: "343797",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "JP THE WAVY",
+ },
+ ],
length: 186000,
number: 8,
title: "UWASA (feat. JP THE WAVY)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "Daichi Yamamoto",
+ externalIds: [
+ {
+ id: "464312",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Daichi Yamamoto",
+ },
+ ],
length: 221000,
number: 9,
title: "シケモク (feat. Daichi Yamamoto)",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "SZK",
+ externalIds: [
+ {
+ id: "1569279",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "SZK",
+ },
+ ],
length: 227000,
number: 10,
title: "NEVER CHANGE (feat. SZK)",
type: "audio",
},
{
+ artists: undefined,
length: 137000,
number: 11,
title: "HATERS",
type: "audio",
},
{
+ artists: undefined,
length: 199000,
number: 12,
title: "LIFE LINE",
type: "audio",
},
{
+ artists: [
+ {
+ creditedName: "Benjazzy",
+ externalIds: [
+ {
+ id: "666017",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Benjazzy",
+ },
+ {
+ creditedName: "般若",
+ externalIds: [
+ {
+ id: "214647",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "般若",
+ },
+ ],
length: 192000,
number: 13,
title: "THE BALANCE (feat. 般若)",
type: "audio",
},
{
+ artists: undefined,
length: 308000,
number: 14,
title: "UNTIL",
@@ -1679,60 +2200,70 @@ snapshot[`OTOTOY provider > release lookup > original release date only 1`] = `
number: 1,
tracklist: [
{
+ artists: undefined,
length: 194000,
number: 1,
title: "勇者",
type: "audio",
},
{
+ artists: undefined,
length: 48000,
number: 2,
title: 'Interlude "Awakening"',
type: "audio",
},
{
+ artists: undefined,
length: 192000,
number: 3,
title: "祝福",
type: "audio",
},
{
+ artists: undefined,
length: 256000,
number: 4,
title: "海のまにまに",
type: "audio",
},
{
+ artists: undefined,
length: 185000,
number: 5,
title: "ミスター",
type: "audio",
},
{
+ artists: undefined,
length: 67000,
number: 6,
title: 'Interlude "Worship"',
type: "audio",
},
{
+ artists: undefined,
length: 211000,
number: 7,
title: "アイドル",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 8,
title: "セブンティーン",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 9,
title: "アドベンチャー",
type: "audio",
},
{
+ artists: undefined,
length: 217000,
number: 10,
title: "好きだ",
@@ -1819,60 +2350,70 @@ snapshot[`OTOTOY provider > release lookup > catalog number 1`] = `
number: 1,
tracklist: [
{
+ artists: undefined,
length: 194000,
number: 1,
title: "勇者",
type: "audio",
},
{
+ artists: undefined,
length: 48000,
number: 2,
title: 'Interlude "Awakening"',
type: "audio",
},
{
+ artists: undefined,
length: 192000,
number: 3,
title: "祝福",
type: "audio",
},
{
+ artists: undefined,
length: 256000,
number: 4,
title: "海のまにまに",
type: "audio",
},
{
+ artists: undefined,
length: 185000,
number: 5,
title: "ミスター",
type: "audio",
},
{
+ artists: undefined,
length: 67000,
number: 6,
title: 'Interlude "Worship"',
type: "audio",
},
{
+ artists: undefined,
length: 211000,
number: 7,
title: "アイドル",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 8,
title: "セブンティーン",
type: "audio",
},
{
+ artists: undefined,
length: 198000,
number: 9,
title: "アドベンチャー",
type: "audio",
},
{
+ artists: undefined,
length: 217000,
number: 10,
title: "好きだ",
@@ -1891,3 +2432,251 @@ snapshot[`OTOTOY provider > release lookup > catalog number 1`] = `
title: "THE BOOK 3",
}
`;
+
+snapshot[`OTOTOY provider > release lookup > per-track artists 1`] = `
+{
+ artists: [
+ {
+ creditedName: "Various Artists",
+ externalIds: [
+ {
+ id: "375396",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Various Artists",
+ },
+ ],
+ externalLinks: [
+ {
+ types: [
+ "paid download",
+ ],
+ url: "https://ototoy.jp/_/default/p/3286704",
+ },
+ ],
+ images: [
+ {
+ thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3286/00000003.3286704.1765252577.9614_320.jpg",
+ types: [
+ "front",
+ ],
+ url: "https://imgs.ototoy.jp/imgs/jacket/3286/00000003.3286704.1765252577.9614orig.jpg",
+ },
+ ],
+ info: {
+ messages: [],
+ providers: [
+ {
+ apiUrl: undefined,
+ id: "3286704",
+ internalName: "ototoy",
+ lookup: {
+ method: "id",
+ value: "3286704",
+ },
+ name: "OTOTOY",
+ url: "https://ototoy.jp/_/default/p/3286704",
+ },
+ ],
+ },
+ labels: undefined,
+ media: [
+ {
+ format: "Digital Media",
+ number: 1,
+ tracklist: [
+ {
+ artists: [
+ {
+ creditedName: "ZトN",
+ externalIds: [
+ {
+ id: "880492",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ZトN",
+ },
+ ],
+ length: 184000,
+ number: 1,
+ title: "arigato",
+ type: "audio",
+ },
+ {
+ artists: [
+ {
+ creditedName: "ZトN",
+ externalIds: [
+ {
+ id: "880492",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ZトN",
+ },
+ {
+ creditedName: "5NBi",
+ externalIds: [
+ {
+ id: "1851346",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "5NBi",
+ },
+ ],
+ length: 146000,
+ number: 2,
+ title: "yami yami",
+ type: "audio",
+ },
+ {
+ artists: [
+ {
+ creditedName: "ZトN",
+ externalIds: [
+ {
+ id: "880492",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ZトN",
+ },
+ {
+ creditedName: "KUNG-FU",
+ externalIds: [
+ {
+ id: "807897",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "KUNG-FU",
+ },
+ ],
+ length: 192000,
+ number: 3,
+ title: "Believer",
+ type: "audio",
+ },
+ {
+ artists: [
+ {
+ creditedName: "ZトN",
+ externalIds: [
+ {
+ id: "880492",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ZトN",
+ },
+ {
+ creditedName: "retio",
+ externalIds: [
+ {
+ id: "1851348",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "retio",
+ },
+ ],
+ length: 170000,
+ number: 4,
+ title: "籠球Pick&Roll",
+ type: "audio",
+ },
+ {
+ artists: [
+ {
+ creditedName: "ZトN",
+ externalIds: [
+ {
+ id: "880492",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ZトN",
+ },
+ {
+ creditedName: "Sun'Tiger",
+ externalIds: [
+ {
+ id: "1329419",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "Sun'Tiger",
+ },
+ ],
+ length: 148000,
+ number: 5,
+ title: "kaisou (Remix)",
+ type: "audio",
+ },
+ {
+ artists: [
+ {
+ creditedName: "ZトN",
+ externalIds: [
+ {
+ id: "880492",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "ZトN",
+ },
+ {
+ creditedName: "KUNG-FU",
+ externalIds: [
+ {
+ id: "807897",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "KUNG-FU",
+ },
+ {
+ creditedName: "sumiya takuro",
+ externalIds: [
+ {
+ id: "1258599",
+ provider: "ototoy",
+ type: "artist",
+ },
+ ],
+ name: "sumiya takuro",
+ },
+ ],
+ length: 280000,
+ number: 6,
+ title: "i'm in Luv (Remix)",
+ type: "audio",
+ },
+ ],
+ },
+ ],
+ packaging: "None",
+ releaseDate: {
+ day: 12,
+ month: 12,
+ year: 2025,
+ },
+ status: "Official",
+ title: "Friends",
+}
+`;
diff --git a/providers/Ototoy/json_types.ts b/providers/Ototoy/json_types.ts
index 86ae1517..2849e089 100644
--- a/providers/Ototoy/json_types.ts
+++ b/providers/Ototoy/json_types.ts
@@ -13,17 +13,18 @@ export interface AlbumMeta {
export interface Artist {
name: string;
- id: string;
+ url: string;
}
export interface Label {
name: string;
- id: string;
+ url: string;
catalogNumber?: string;
}
export interface Track {
title: string;
+ artists?: Artist[];
trackNumber: number;
discNumber?: number;
/** Track duration in seconds */
diff --git a/providers/Ototoy/mod.test.ts b/providers/Ototoy/mod.test.ts
index 3c535951..5c03eb87 100644
--- a/providers/Ototoy/mod.test.ts
+++ b/providers/Ototoy/mod.test.ts
@@ -86,6 +86,32 @@ describe('OTOTOY provider', () => {
assertEquals(label.externalIds, provider.makeExternalIds({ type: 'label', id: '856521' }));
assertEquals(label.catalogNumber, 'YOASOBI-081');
},
+ }, {
+ description: 'per-track artists',
+ release: '3286704',
+ assert: async (release, ctx) => {
+ await assertSnapshot(ctx, release);
+
+ assertEquals(release.media.length, 1);
+ const media = release.media[0];
+
+ assertEquals(media.tracklist.length, 6);
+ const trackArtists = [
+ ['880492'],
+ ['880492', '1851346'],
+ ['880492', '807897'],
+ ['880492', '1851348'],
+ ['880492', '1329419'],
+ ['880492', '807897', '1258599'],
+ ];
+
+ media.tracklist.forEach((track, index) => {
+ const expectedArtists = trackArtists[index].flatMap((value) =>
+ provider.makeExternalIds({ type: 'artist', id: value })
+ );
+ assertEquals(track.artists!.flatMap((artist) => artist.externalIds![0]), expectedArtists);
+ });
+ },
}],
});
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index d611e418..8aa5a1e0 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -24,15 +24,13 @@ export default class OtotoyProvider extends MetadataProvider {
pathname: '/_/default/:type(a|p)/:id(\\d+)',
});
- readonly entityPathPattern = /\/_\/default\/[ap]\/(\d+)$/;
+ readonly origin = `https://${this.supportedUrls.hostname}`;
readonly labelUrlPattern = new URLPattern({
hostname: this.supportedUrls.hostname,
pathname: '/labels/:id',
});
- readonly labelPathPattern = /\/labels\/(\d+)$/;
-
override readonly features: FeatureQualityMap = {
// Seems to between 1500-3000, with 1500 being most common
'cover size': 1500,
@@ -194,6 +192,18 @@ export default class OtotoyProvider extends MetadataProvider {
const titleSpan = trackRow.querySelector("td.item span[id^='title-']");
if (!titleSpan) return undefined;
+ const artists: Artist[] = [];
+ const artistsHtml = trackRow.querySelectorAll('td.item a.artist');
+ for (const artist of artistsHtml) {
+ const path = artist.getAttribute('href');
+ if (!path) continue;
+
+ artists.push({
+ url: `${this.origin}${path}`,
+ name: artist.textContent.trim(),
+ });
+ }
+
const title = titleSpan.textContent.trim();
const durationCell = trackRow.querySelectorAll('td')[3];
@@ -203,6 +213,7 @@ export default class OtotoyProvider extends MetadataProvider {
tracks.push({
title: title,
+ artists: artists.length > 0 ? artists : undefined,
discNumber: currentDisc,
trackNumber: parseInt(trackNumber, 10),
duration: parseDuration(duration),
@@ -253,14 +264,14 @@ export default class OtotoyProvider extends MetadataProvider {
const anchor = span.querySelector('a');
if (!anchor) return undefined;
- const id = anchor.getAttribute('href')?.match(this.entityPathPattern)?.[1];
- if (!id) return undefined;
+ const path = anchor.getAttribute('href');
+ if (!path) return undefined;
const name = span.textContent.trim();
artists.push({
name,
- id,
+ url: `${this.origin}${path}`,
});
}
@@ -300,14 +311,14 @@ export default class OtotoyProvider extends MetadataProvider {
catalogNumber = catalogIdParagraph.textContent.trim().match(/^Catalog number: (.*?)$/)?.[1];
}
- const labelId = labelAnchor.getAttribute('href')?.match(this.labelPathPattern)?.[1];
- if (!labelId) return undefined;
+ const labelPath = labelAnchor.getAttribute('href');
+ if (!labelPath) return undefined;
const labelName = labelAnchor.textContent.trim();
albumMeta.label = {
name: labelName,
- id: labelId,
+ url: `${this.origin}${labelPath}`,
catalogNumber,
};
@@ -407,6 +418,7 @@ export class OtotoyReleaseLookup extends ReleaseLookup
Date: Mon, 15 Dec 2025 17:27:59 -0500
Subject: [PATCH 11/15] chore(ototoy): switch to URL constructors
---
providers/Ototoy/mod.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index 8aa5a1e0..f816058e 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -199,7 +199,7 @@ export default class OtotoyProvider extends MetadataProvider {
if (!path) continue;
artists.push({
- url: `${this.origin}${path}`,
+ url: new URL(path, this.origin).href,
name: artist.textContent.trim(),
});
}
@@ -271,7 +271,7 @@ export default class OtotoyProvider extends MetadataProvider {
artists.push({
name,
- url: `${this.origin}${path}`,
+ url: new URL(path, this.origin).href,
});
}
@@ -318,7 +318,7 @@ export default class OtotoyProvider extends MetadataProvider {
albumMeta.label = {
name: labelName,
- url: `${this.origin}${labelPath}`,
+ url: new URL(labelPath, this.origin).href,
catalogNumber,
};
From ab99fc38a387bb19fdb8f6e39067e8ca735bacc5 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Mon, 15 Dec 2025 17:32:46 -0500
Subject: [PATCH 12/15] chore(ototoy): add docs for track artist format
---
providers/Ototoy/mod.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index f816058e..4a1d5cd6 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -160,13 +160,19 @@ export default class OtotoyProvider extends MetadataProvider {
//
//
// Free As A Bird (1995 Mix - Remastered)
+ //
+ // The Beatles
+ //
//
//
04:25
//
//
//
//
- // NOTE: `disc-row` is optional
+ // NOTES:
+ // * `disc-row` is optional
+ // * Not all releases have per-track artists
+ // * Multiple `artist` anchors can appear within the parent span
parseTracklist(doc: HTMLDocument): Track[] | undefined {
const trackListRows = doc.querySelectorAll('#tracklist tbody tr');
From 349a664d9b55b4c5ac88cebeef1e6b9dd2ae6a51 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Mon, 15 Dec 2025 17:50:18 -0500
Subject: [PATCH 13/15] feat(ototoy): retain original release date
---
providers/Ototoy/json_types.ts | 3 ++-
providers/Ototoy/mod.ts | 13 +++++++++----
testdata/https!/jp.ototoy/_/default/p/1822344 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3016055 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3228080 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3237840 | 2 +-
testdata/https!/jp.ototoy/_/default/p/3286704 | 2 +-
testdata/https!/jp.ototoy/_/default/p/709920 | 2 +-
8 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/providers/Ototoy/json_types.ts b/providers/Ototoy/json_types.ts
index 2849e089..ca0802c6 100644
--- a/providers/Ototoy/json_types.ts
+++ b/providers/Ototoy/json_types.ts
@@ -7,7 +7,8 @@ export interface PackagePage {
export interface AlbumMeta {
title: string;
artists: Artist[];
- releaseDate: string;
+ originalReleaseDate?: string;
+ releaseDate?: string;
label?: Label;
}
diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts
index 4a1d5cd6..250626d7 100644
--- a/providers/Ototoy/mod.ts
+++ b/providers/Ototoy/mod.ts
@@ -253,6 +253,8 @@ export default class OtotoyProvider extends MetadataProvider {
// * The release can have an "original" release date, a platform release date, or both. "Release date" is the preferred date.
// * In the case that only one date is present, sometimes "Original" is used, sometimes not. Whatever's available will
// be used.
+ // * In the case of releases with only an "Original" date (e.g. https://ototoy.jp/_/default/p/1822344), that date seems
+ // to represent both the original and platform release dates.
parseAlbumMeta(doc: HTMLDocument): AlbumMeta | undefined {
const albumMetadata = doc.querySelector('div.album-meta-data');
if (!albumMetadata) return undefined;
@@ -298,13 +300,11 @@ export default class OtotoyProvider extends MetadataProvider {
}
});
- releaseDate = releaseDate || originalReleaseDate;
- if (!releaseDate) return undefined;
-
const albumMeta: AlbumMeta = {
title,
artists,
releaseDate,
+ originalReleaseDate,
};
const labelAnchor = details.querySelector('p.label-name > a');
@@ -371,11 +371,16 @@ export class OtotoyReleaseLookup extends ReleaseLookup
Date: Mon, 15 Dec 2025 17:55:30 -0500
Subject: [PATCH 14/15] chore(ototoy): simplify per-track artist test
---
providers/Ototoy/mod.test.ts | 16 +---------------
1 file changed, 1 insertion(+), 15 deletions(-)
diff --git a/providers/Ototoy/mod.test.ts b/providers/Ototoy/mod.test.ts
index 5c03eb87..951b7092 100644
--- a/providers/Ototoy/mod.test.ts
+++ b/providers/Ototoy/mod.test.ts
@@ -96,21 +96,7 @@ describe('OTOTOY provider', () => {
const media = release.media[0];
assertEquals(media.tracklist.length, 6);
- const trackArtists = [
- ['880492'],
- ['880492', '1851346'],
- ['880492', '807897'],
- ['880492', '1851348'],
- ['880492', '1329419'],
- ['880492', '807897', '1258599'],
- ];
-
- media.tracklist.forEach((track, index) => {
- const expectedArtists = trackArtists[index].flatMap((value) =>
- provider.makeExternalIds({ type: 'artist', id: value })
- );
- assertEquals(track.artists!.flatMap((artist) => artist.externalIds![0]), expectedArtists);
- });
+ assertEquals(media.tracklist[5].artists!.length, 3);
},
}],
});
From 7b150b55a84e8096fb790cea93afd3915356acc7 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Mon, 15 Dec 2025 18:06:29 -0500
Subject: [PATCH 15/15] chore(ototoy): shrink SVG icon
---
server/icons/BrandOtotoy.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/icons/BrandOtotoy.tsx b/server/icons/BrandOtotoy.tsx
index 62742c19..3ad26946 100644
--- a/server/icons/BrandOtotoy.tsx
+++ b/server/icons/BrandOtotoy.tsx
@@ -13,7 +13,7 @@ export default function IconBrandOtotoy({
fill={color}
{...props}
>
-
+
);
}