diff --git a/deno.json b/deno.json index 1ed85809..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", diff --git a/providers/Ototoy/__snapshots__/mod.test.ts.snap b/providers/Ototoy/__snapshots__/mod.test.ts.snap new file mode 100644 index 00000000..6c8eaaee --- /dev/null +++ b/providers/Ototoy/__snapshots__/mod.test.ts.snap @@ -0,0 +1,2682 @@ +export const snapshot = {}; + +snapshot[`OTOTOY provider > release lookup > single track release 1`] = ` +{ + artists: [ + { + creditedName: "sasakure.UK", + externalIds: [ + { + id: "153628", + provider: "ototoy", + type: "artist", + }, + ], + name: "sasakure.UK", + }, + ], + externalLinks: [ + { + types: [ + "paid download", + ], + url: "https://ototoy.jp/_/default/p/3016055", + }, + ], + images: [ + { + thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3016/00000003.1756598333.417_320.jpg", + types: [ + "front", + ], + url: "https://imgs.ototoy.jp/imgs/jacket/3016/00000003.1756598333.417orig.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: undefined, + id: "3016055", + internalName: "ototoy", + lookup: { + method: "id", + value: "3016055", + }, + name: "OTOTOY", + url: "https://ototoy.jp/_/default/p/3016055", + }, + ], + }, + labels: [ + { + catalogNumber: undefined, + externalIds: [ + { + id: "215734", + provider: "ototoy", + type: "label", + }, + ], + name: "sasakuration", + }, + ], + media: [ + { + format: "Digital Media", + number: 1, + tracklist: [ + { + artists: undefined, + length: 238000, + number: 1, + title: "トゥイー・ボックスの人形劇場", + type: "audio", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + day: 1, + month: 9, + year: 2025, + }, + status: "Official", + title: "トゥイー・ボックスの人形劇場", +} +`; + +snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = ` +{ + artists: [ + { + creditedName: "ザ・ビートルズ", + externalIds: [ + { + id: "218489", + provider: "ototoy", + type: "artist", + }, + ], + name: "ザ・ビートルズ", + }, + ], + externalLinks: [ + { + types: [ + "paid download", + ], + url: "https://ototoy.jp/_/default/p/3237840", + }, + ], + images: [ + { + 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.1764965039.7088orig.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: undefined, + id: "3237840", + internalName: "ototoy", + lookup: { + method: "id", + value: "3237840", + }, + name: "OTOTOY", + url: "https://ototoy.jp/_/default/p/3237840", + }, + ], + }, + labels: [ + { + catalogNumber: undefined, + externalIds: [ + { + id: "274620", + provider: "ototoy", + type: "label", + }, + ], + name: "UMC (Universal Music Catalogue)", + }, + ], + media: [ + { + format: "Digital Media", + 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)", + type: "audio", + }, + ], + }, + { + format: "Digital Media", + 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)", + type: "audio", + }, + ], + }, + { + format: "Digital Media", + 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)", + type: "audio", + }, + ], + }, + { + format: "Digital Media", + 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", + type: "audio", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + day: 21, + month: 11, + year: 2025, + }, + status: "Official", + title: "Anthology Collection", +} +`; + +snapshot[`OTOTOY provider > release lookup > multiple artists 1`] = ` +{ + artists: [ + { + creditedName: "sasakure.UK", + externalIds: [ + { + id: "153628", + provider: "ototoy", + type: "artist", + }, + ], + name: "sasakure.UK", + }, + { + creditedName: "さくらみこ", + externalIds: [ + { + id: "818883", + provider: "ototoy", + type: "artist", + }, + ], + name: "さくらみこ", + }, + { + creditedName: "白上フブキ", + externalIds: [ + { + id: "693805", + provider: "ototoy", + type: "artist", + }, + ], + name: "白上フブキ", + }, + { + creditedName: "夏色まつり", + externalIds: [ + { + id: "817278", + provider: "ototoy", + type: "artist", + }, + ], + name: "夏色まつり", + }, + { + creditedName: "宝鐘マリン", + externalIds: [ + { + id: "799749", + provider: "ototoy", + type: "artist", + }, + ], + name: "宝鐘マリン", + }, + ], + externalLinks: [ + { + types: [ + "paid download", + ], + url: "https://ototoy.jp/_/default/p/709920", + }, + ], + images: [ + { + thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/0709/00000003.1614153884.5336_320.jpg", + types: [ + "front", + ], + url: "https://imgs.ototoy.jp/imgs/jacket/0709/00000003.1614153884.5336orig.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: undefined, + id: "709920", + internalName: "ototoy", + lookup: { + method: "id", + value: "709920", + }, + name: "OTOTOY", + url: "https://ototoy.jp/_/default/p/709920", + }, + ], + }, + labels: [ + { + catalogNumber: undefined, + externalIds: [ + { + id: "215734", + provider: "ototoy", + type: "label", + }, + ], + name: "sasakuration", + }, + ], + media: [ + { + format: "Digital Media", + number: 1, + tracklist: [ + { + artists: undefined, + length: 223000, + number: 1, + title: "Gimme吟味virtuaる最高star!!!! (feat. さくらみこ, 白上フブキ, 夏色まつり & 宝鐘マリン)", + type: "audio", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + day: 25, + month: 2, + year: 2021, + }, + status: "Official", + title: "Gimme吟味virtuaる最高star!!!! (feat. さくらみこ, 白上フブキ, 夏色まつり & 宝鐘マリン)", +} +`; + +snapshot[`OTOTOY provider > release lookup > no label 1`] = ` +{ + artists: [ + { + creditedName: "Benjazzy", + externalIds: [ + { + id: "666017", + provider: "ototoy", + type: "artist", + }, + ], + name: "Benjazzy", + }, + ], + externalLinks: [ + { + types: [ + "paid download", + ], + url: "https://ototoy.jp/_/default/p/3228080", + }, + ], + images: [ + { + thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/3228/00000003.3228080.1763569536.7217_320.jpg", + types: [ + "front", + ], + url: "https://imgs.ototoy.jp/imgs/jacket/3228/00000003.3228080.1763569536.7217orig.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: undefined, + id: "3228080", + internalName: "ototoy", + lookup: { + method: "id", + value: "3228080", + }, + name: "OTOTOY", + url: "https://ototoy.jp/_/default/p/3228080", + }, + ], + }, + labels: undefined, + media: [ + { + format: "Digital Media", + 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", + type: "audio", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + day: 19, + month: 11, + year: 2025, + }, + status: "Official", + title: "UNTIL", +} +`; + +snapshot[`OTOTOY provider > release lookup > original release date only 1`] = ` +{ + artists: [ + { + creditedName: "YOASOBI", + externalIds: [ + { + id: "731939", + provider: "ototoy", + type: "artist", + }, + ], + name: "YOASOBI", + }, + ], + externalLinks: [ + { + types: [ + "paid download", + ], + url: "https://ototoy.jp/_/default/p/1822344", + }, + ], + images: [ + { + thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512_320.jpg", + types: [ + "front", + ], + url: "https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512orig.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: undefined, + id: "1822344", + internalName: "ototoy", + lookup: { + method: "id", + value: "1822344", + }, + name: "OTOTOY", + url: "https://ototoy.jp/_/default/p/1822344", + }, + ], + }, + labels: [ + { + catalogNumber: "YOASOBI-081", + externalIds: [ + { + id: "856521", + provider: "ototoy", + type: "label", + }, + ], + name: "YOASOBI", + }, + ], + media: [ + { + format: "Digital Media", + 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: "好きだ", + type: "audio", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + day: 4, + month: 10, + year: 2023, + }, + status: "Official", + title: "THE BOOK 3", +} +`; + +snapshot[`OTOTOY provider > release lookup > catalog number 1`] = ` +{ + artists: [ + { + creditedName: "YOASOBI", + externalIds: [ + { + id: "731939", + provider: "ototoy", + type: "artist", + }, + ], + name: "YOASOBI", + }, + ], + externalLinks: [ + { + types: [ + "paid download", + ], + url: "https://ototoy.jp/_/default/p/1822344", + }, + ], + images: [ + { + thumbUrl: "https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512_320.jpg", + types: [ + "front", + ], + url: "https://imgs.ototoy.jp/imgs/jacket/1822/00000003.1695204881.8512orig.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: undefined, + id: "1822344", + internalName: "ototoy", + lookup: { + method: "id", + value: "1822344", + }, + name: "OTOTOY", + url: "https://ototoy.jp/_/default/p/1822344", + }, + ], + }, + labels: [ + { + catalogNumber: "YOASOBI-081", + externalIds: [ + { + id: "856521", + provider: "ototoy", + type: "label", + }, + ], + name: "YOASOBI", + }, + ], + media: [ + { + format: "Digital Media", + 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: "好きだ", + type: "audio", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + day: 4, + month: 10, + year: 2023, + }, + status: "Official", + 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 new file mode 100644 index 00000000..ca0802c6 --- /dev/null +++ b/providers/Ototoy/json_types.ts @@ -0,0 +1,33 @@ +export interface PackagePage { + thumbUrl: string; + albumMeta: AlbumMeta; + trackList: Track[]; +} + +export interface AlbumMeta { + title: string; + artists: Artist[]; + originalReleaseDate?: string; + releaseDate?: string; + label?: Label; +} + +export interface Artist { + name: string; + url: string; +} + +export interface Label { + name: string; + url: string; + catalogNumber?: string; +} + +export interface Track { + title: string; + artists?: Artist[]; + trackNumber: number; + discNumber?: number; + /** Track duration in seconds */ + duration: number; +} diff --git a/providers/Ototoy/mod.test.ts b/providers/Ototoy/mod.test.ts new file mode 100644 index 00000000..951b7092 --- /dev/null +++ b/providers/Ototoy/mod.test.ts @@ -0,0 +1,107 @@ +import { describeProvider, makeProviderOptions } from '@/providers/test_spec.ts'; +import { stubProviderLookups } from '@/providers/test_stubs.ts'; +import { assert } from 'std/assert/assert.ts'; +import { afterAll, describe } from '@std/testing/bdd'; +import { assertSnapshot } from '@std/testing/snapshot'; + +import { assertEquals } from 'std/assert/assert_equals.ts'; +import OtotoyProvider from './mod.ts'; + +describe('OTOTOY provider', () => { + const provider = new OtotoyProvider(makeProviderOptions()); + const lookupStub = stubProviderLookups(provider); + + describeProvider(provider, { + urls: [{ + description: 'package page', + url: new URL('https://ototoy.jp/_/default/p/3102862'), + id: { type: 'package', id: '3102862' }, + isCanonical: true, + }, { + description: 'artist page', + url: new URL('https://ototoy.jp/_/default/a/693805'), + id: { type: 'artist', id: '693805' }, + isCanonical: true, + }], + releaseLookup: [{ + description: 'single track release', + release: '3016055', + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + + const trackCount = release.media.flatMap((medium) => medium.tracklist).length; + assertEquals(trackCount, 1, 'Release should have 1 track'); + }, + }, { + description: 'multi-disc release', + release: '3237840', + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + + assertEquals(release.media.length, 4, 'Release should have 4 discs'); + assertEquals(release.media[0].tracklist.length, 60, 'Disc 1 should have 60 tracks'); + assertEquals(release.media[1].tracklist.length, 45, 'Disc 2 should have 45 tracks'); + assertEquals(release.media[2].tracklist.length, 50, 'Disc 3 should have 50 tracks'); + assertEquals(release.media[3].tracklist.length, 36, 'Disc 4 should have 36 tracks'); + }, + }, { + description: 'multiple artists', + release: '709920', + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + + const artists = release.artists.flatMap((artist) => artist.name); + assertEquals(artists.length, 5, 'Should have 5 artists'); + assertEquals(artists, ['sasakure.UK', 'さくらみこ', '白上フブキ', '夏色まつり', '宝鐘マリン']); + }, + }, { + description: 'no label', + release: '3228080', + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + assert(!release.labels); + }, + }, { + description: 'original release date only', + release: '1822344', + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + + assertEquals(release.releaseDate, { + day: 4, + month: 10, + year: 2023, + }); + }, + }, { + description: 'catalog number', + release: '1822344', + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + + assert(release.labels, 'Release should have a label entry'); + const label = release.labels[0]; + + assertEquals(label.name, 'YOASOBI'); + 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); + assertEquals(media.tracklist[5].artists!.length, 3); + }, + }], + }); + + afterAll(() => { + lookupStub.restore(); + }); +}); diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts new file mode 100644 index 00000000..250626d7 --- /dev/null +++ b/providers/Ototoy/mod.ts @@ -0,0 +1,472 @@ +import type { AlbumMeta, Artist, Label as RawLabel, PackagePage, Track } from './json_types.ts'; +import type { + ArtistCreditName, + Artwork, + EntityId, + HarmonyMedium, + HarmonyRelease, + HarmonyTrack, + Label, + LinkType, +} from '@/harmonizer/types.ts'; +import { type CacheEntry, MetadataProvider, ReleaseLookup } from '@/providers/base.ts'; +import { DurationPrecision, FeatureQuality, FeatureQualityMap } from '@/providers/features.ts'; +import { parseISODateTime, PartialDate } from '@/utils/date.ts'; +import { ProviderError, ResponseError } from '@/utils/errors.ts'; +import { DOMParser, HTMLDocument } from '@b-fuze/deno-dom'; +import { parseDuration } from '../../utils/time.ts'; + +export default class OtotoyProvider extends MetadataProvider { + readonly name = 'OTOTOY'; + + readonly supportedUrls = new URLPattern({ + hostname: 'ototoy.jp', + pathname: '/_/default/:type(a|p)/:id(\\d+)', + }); + + readonly origin = `https://${this.supportedUrls.hostname}`; + + readonly labelUrlPattern = new URLPattern({ + hostname: this.supportedUrls.hostname, + pathname: '/labels/:id', + }); + + override readonly features: FeatureQualityMap = { + // Seems to between 1500-3000, with 1500 being most common + 'cover size': 1500, + 'duration precision': DurationPrecision.SECONDS, + 'GTIN lookup': FeatureQuality.MISSING, + 'MBID resolving': FeatureQuality.PRESENT, + }; + + readonly entityTypeMap = { + artist: 'artist', + release: 'package', + label: 'label', + }; + + override readonly launchDate: PartialDate = { + year: 2004, + month: 8, + }; + + readonly releaseLookup = OtotoyReleaseLookup; + + override extractEntityFromUrl(url: URL): EntityId | undefined { + const packageResult = this.supportedUrls.exec(url); + if (packageResult) { + const type = packageResult.pathname.groups.type!; + const id = packageResult.pathname.groups.id!; + if (type == 'a') { + return { + type: 'artist', + id, + }; + } + + if (type == 'p') { + return { + type: 'package', + id, + }; + } + } + + const labelResult = this.labelUrlPattern.exec(url); + if (labelResult) { + return { + type: 'label', + id: labelResult.pathname.groups.id!, + }; + } + } + + constructUrl(entity: EntityId): URL { + switch (entity.type) { + case 'artist': { + return new URL(`https://ototoy.jp/_/default/a/${entity.id}`); + } + case 'package': { + return new URL(`https://ototoy.jp/_/default/p/${entity.id}`); + } + case 'label': { + return new URL(`https://ototoy.jp/labels/${entity.id}`); + } + } + + throw new ProviderError(this.name, `Unable to determine type of entity ID '${entity.id}'`); + } + + override getLinkTypesForEntity(_entity: EntityId): LinkType[] { + return ['paid download']; + } + + scrapePackage(html: string, webUrl: URL): PackagePage { + const doc = new DOMParser().parseFromString(html, 'text/html'); + if (!doc) { + throw new ResponseError(this.name, `Failed to parse HTML`, webUrl); + } + + const thumbUrl = this.parseAlbumArtwork(doc); + if (!thumbUrl) { + throw new ResponseError(this.name, `Failed to extract album thumbnail`, webUrl); + } + + const albumMeta = this.parseAlbumMeta(doc); + if (!albumMeta) { + throw new ResponseError(this.name, `Failed to extract album metadata`, webUrl); + } + + const trackList = this.parseTracklist(doc); + if (!trackList) { + throw new ResponseError(this.name, `Failed to extract tracklist`, webUrl); + } + + return { + thumbUrl, + albumMeta, + trackList, + }; + } + + // The format is as follows: + // + //
+ // + //
+ // + // This is just the small thumbnail, the full size image comes from getArtwork() + parseAlbumArtwork(doc: HTMLDocument): string | undefined { + const imageElement = doc.querySelector('div.album-artwork img.photo'); + if (!imageElement) return undefined; + + return imageElement.getAttribute('src') || undefined; + } + + // The format is as follows: + // + // + // + // + // + // + // + // + // + // + // + // + //
DISC 1
+ // 1 + // + // Free As A Bird (1995 Mix - Remastered) + // + // The Beatles + // + // 04:25
+ // + // 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'); + + let currentDisc = undefined; + const tracks: Track[] = []; + + for (const trackRow of trackListRows) { + if (trackRow.classList.contains('disc-row')) { + const match = trackRow.textContent.match(/\d+/); + + if (match) { + currentDisc = parseInt(match[0], 10); + } + + continue; + } + + const trackNumberCell = trackRow.querySelector('.num'); + if (!trackNumberCell) continue; + + const trackNumber = trackNumberCell.textContent.trim(); + + 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: new URL(path, this.origin).href, + name: artist.textContent.trim(), + }); + } + + const title = titleSpan.textContent.trim(); + + const durationCell = trackRow.querySelectorAll('td')[3]; + if (!durationCell) continue; + + const duration = durationCell.textContent.trim(); + + tracks.push({ + title: title, + artists: artists.length > 0 ? artists : undefined, + discNumber: currentDisc, + trackNumber: parseInt(trackNumber, 10), + duration: parseDuration(duration), + }); + } + + return tracks; + } + + // The format is as follows: + // + //
+ //

Foo album

+ //

+ // + // + // Bar artist + // + //

+ //
+ //

Release date: 2025-11-24

+ //

Original release date: 2025-11-24

+ //

Label: Baz label

+ //

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 path = anchor.getAttribute('href'); + if (!path) return undefined; + + const name = span.textContent.trim(); + + artists.push({ + name, + url: new URL(path, this.origin).href, + }); + } + + 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.startsWith('Original release date:')) { + originalReleaseDate = text.replace('Original release date:', '').trim(); + } else if (text.startsWith('Release date:')) { + releaseDate = text.replace('Release date:', '').trim(); + } + }); + + const albumMeta: AlbumMeta = { + title, + artists, + releaseDate, + originalReleaseDate, + }; + + 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 labelPath = labelAnchor.getAttribute('href'); + if (!labelPath) return undefined; + + const labelName = labelAnchor.textContent.trim(); + + albumMeta.label = { + name: labelName, + url: new URL(labelPath, this.origin).href, + catalogNumber, + }; + + return albumMeta; + } + + fetchAndScrapePackagePage(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.fetchAndScrapePackagePage( + webUrl, + this.options.snapshotMaxTimestamp, + ); + this.updateCacheTime(timestamp); + + return release; + } + + convertRawRelease(albumPage: PackagePage): HarmonyRelease { + const { albumMeta, trackList } = albumPage; + + // OTOTOY might only provide one type of date, see `parseAlbumMeta()` + // + // For now, `originalReleaseDate` has no special interpretation, and is just used as a fallback when there's no + // platform release date specified. + const releaseDate = albumMeta.releaseDate || albumMeta.originalReleaseDate; + const release: HarmonyRelease = { + title: albumMeta.title, + artists: albumMeta.artists.map(this.convertRawArtist.bind(this)), + labels: albumMeta.label ? [this.convertRawLabel(albumMeta.label)] : undefined, + releaseDate: releaseDate ? parseISODateTime(releaseDate) : undefined, + 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, + artists: rawTrack.artists?.map(this.convertRawArtist.bind(this)), + length: rawTrack.duration * 1000, + type: 'audio', + }; + + return result; + } + + convertRawArtist(artist: Artist): ArtistCreditName { + return { + name: artist.name, + creditedName: artist.name, + externalIds: this.provider.makeExternalIdsFromUrl(artist.url), + }; + } + + convertRawLabel(label: RawLabel): Label { + return { + name: label.name, + catalogNumber: label.catalogNumber, + externalIds: this.provider.makeExternalIdsFromUrl(label.url), + }; + } + + 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'], + }; + } +} diff --git a/providers/mod.ts b/providers/mod.ts index 89582687..a32d4184 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'; @@ -28,6 +29,7 @@ providers.addMultiple( BandcampProvider, BeatportProvider, MoraProvider, + OtotoyProvider, ); /** Internal names of providers which are enabled by default (for GTIN lookups). */ 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..3ad26946 --- /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/_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 + 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..9e66f353 --- /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","url":"https://ototoy.jp/_/default/a/731939"}],"originalReleaseDate":"2023-10-04","label":{"name":"YOASOBI","url":"https://ototoy.jp/labels/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..b6c91085 --- /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","url":"https://ototoy.jp/_/default/a/153628"}],"originalReleaseDate":"2025-09-01","label":{"name":"sasakuration","url":"https://ototoy.jp/labels/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..5b26d724 --- /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","url":"https://ototoy.jp/_/default/a/666017"}],"originalReleaseDate":"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)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/963757","name":"Watson"},{"url":"https://ototoy.jp/_/default/a/725029","name":"MIKADO"},{"url":"https://ototoy.jp/_/default/a/671436","name":"¥ellow Bucks"}],"trackNumber":4,"duration":314},{"title":"1 2 3 (feat. CFN MALIK)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/1337903","name":"CFN MALIK"}],"trackNumber":5,"duration":186},{"title":"PRIDE","trackNumber":6,"duration":239},{"title":"WWW (feat. Bonbero)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/693436","name":"Bonbero"}],"trackNumber":7,"duration":212},{"title":"UWASA (feat. JP THE WAVY)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/343797","name":"JP THE WAVY"}],"trackNumber":8,"duration":186},{"title":"シケモク (feat. Daichi Yamamoto)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/464312","name":"Daichi Yamamoto"}],"trackNumber":9,"duration":221},{"title":"NEVER CHANGE (feat. SZK)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/1569279","name":"SZK"}],"trackNumber":10,"duration":227},{"title":"HATERS","trackNumber":11,"duration":137},{"title":"LIFE LINE","trackNumber":12,"duration":199},{"title":"THE BALANCE (feat. 般若)","artists":[{"url":"https://ototoy.jp/_/default/a/666017","name":"Benjazzy"},{"url":"https://ototoy.jp/_/default/a/214647","name":"般若"}],"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..bf561baf --- /dev/null +++ b/testdata/https!/jp.ototoy/_/default/p/3237840 @@ -0,0 +1 @@ +{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3237/00000003.3237840.1764965039.7088_320.jpg","albumMeta":{"title":"Anthology Collection","artists":[{"name":"ザ・ビートルズ","url":"https://ototoy.jp/_/default/a/218489"}],"releaseDate":"2025-11-21","originalReleaseDate":"2025-11-21","label":{"name":"UMC (Universal Music Catalogue)","url":"https://ototoy.jp/labels/274620"}},"trackList":[{"title":"Free As A Bird (1995 Mix - Remastered)","discNumber":1,"trackNumber":1,"duration":265},{"title":"John Lennon Speech 1 (Remastered)","artists":[{"url":"https://ototoy.jp/_/default/a/284005","name":"ジョン・レノン"}],"discNumber":1,"trackNumber":2,"duration":11},{"title":"That'll Be The Day (Remastered)","artists":[{"url":"https://ototoy.jp/_/default/a/1823343","name":"ザ・クォーリー・メン"}],"discNumber":1,"trackNumber":3,"duration":129},{"title":"In Spite Of All The Danger (Remastered)","artists":[{"url":"https://ototoy.jp/_/default/a/1823343","name":"ザ・クォーリー・メン"}],"discNumber":1,"trackNumber":4,"duration":164},{"title":"Paul McCartney Speech 1 (Remastered)","artists":[{"url":"https://ototoy.jp/_/default/a/268135","name":"ポール・マッカートニー"}],"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)","artists":[{"url":"https://ototoy.jp/_/default/a/268135","name":"ポール・マッカートニー"}],"discNumber":1,"trackNumber":9,"duration":7},{"title":"My Bonnie (Remastered)","artists":[{"url":"https://ototoy.jp/_/default/a/218489","name":"ザ・ビートルズ"},{"url":"https://ototoy.jp/_/default/a/274608","name":"ビートルズとトニー・シェリダン"}],"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)","artists":[{"url":"https://ototoy.jp/_/default/a/284005","name":"ジョン・レノン"}],"discNumber":1,"trackNumber":13,"duration":9},{"title":"Brian Epstein Speech 1 (Remastered)","artists":[{"url":"https://ototoy.jp/_/default/a/1823344","name":"ブライアン・エプスタイン"}],"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)","artists":[{"url":"https://ototoy.jp/_/default/a/1823344","name":"ブライアン・エプスタイン"}],"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)","artists":[{"url":"https://ototoy.jp/_/default/a/284005","name":"ジョン・レノン"}],"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/3286704 b/testdata/https!/jp.ototoy/_/default/p/3286704 new file mode 100644 index 00000000..8702e80a --- /dev/null +++ b/testdata/https!/jp.ototoy/_/default/p/3286704 @@ -0,0 +1 @@ +{"thumbUrl":"https://imgs.ototoy.jp/imgs/jacket/3286/00000003.3286704.1765252577.9614_320.jpg","albumMeta":{"title":"Friends","artists":[{"name":"Various Artists","url":"https://ototoy.jp/_/default/a/375396"}],"originalReleaseDate":"2025-12-12"},"trackList":[{"title":"arigato","artists":[{"url":"https://ototoy.jp/_/default/a/880492","name":"ZトN"}],"trackNumber":1,"duration":184},{"title":"yami yami","artists":[{"url":"https://ototoy.jp/_/default/a/880492","name":"ZトN"},{"url":"https://ototoy.jp/_/default/a/1851346","name":"5NBi"}],"trackNumber":2,"duration":146},{"title":"Believer","artists":[{"url":"https://ototoy.jp/_/default/a/880492","name":"ZトN"},{"url":"https://ototoy.jp/_/default/a/807897","name":"KUNG-FU"}],"trackNumber":3,"duration":192},{"title":"籠球Pick&Roll","artists":[{"url":"https://ototoy.jp/_/default/a/880492","name":"ZトN"},{"url":"https://ototoy.jp/_/default/a/1851348","name":"retio"}],"trackNumber":4,"duration":170},{"title":"kaisou (Remix)","artists":[{"url":"https://ototoy.jp/_/default/a/880492","name":"ZトN"},{"url":"https://ototoy.jp/_/default/a/1329419","name":"Sun'Tiger"}],"trackNumber":5,"duration":148},{"title":"i'm in Luv (Remix)","artists":[{"url":"https://ototoy.jp/_/default/a/880492","name":"ZトN"},{"url":"https://ototoy.jp/_/default/a/807897","name":"KUNG-FU"},{"url":"https://ototoy.jp/_/default/a/1258599","name":"sumiya takuro"}],"trackNumber":6,"duration":280}]} \ 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..1279bcf1 --- /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","url":"https://ototoy.jp/_/default/a/153628"},{"name":"さくらみこ","url":"https://ototoy.jp/_/default/a/818883"},{"name":"白上フブキ","url":"https://ototoy.jp/_/default/a/693805"},{"name":"夏色まつり","url":"https://ototoy.jp/_/default/a/817278"},{"name":"宝鐘マリン","url":"https://ototoy.jp/_/default/a/799749"}],"originalReleaseDate":"2021-02-25","label":{"name":"sasakuration","url":"https://ototoy.jp/labels/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); +}