-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Importing a tracklist from Apple Music is as easy as pasting the album URL into a new "Import From..." model. I have started with Apple Music, but I will add more soon. The Tracklist Editor still works entirely offline if you don't use the import feature. However, due to CORS the import feature cannot be part of the static web page. I will have to manually push changes to the importer. If that becomes a pain I can automate it with CD later. One known issue: If the album is semi-various artists, the importer will only catch a few tracks. Semi-various artists is a thing with Apple Music where albums that contain mostly one artist do not show that artist on those tracks. This causes havoc with the (probably brittle) regex that parses the raw HTML.
- Loading branch information
1 parent
b6fa988
commit e514040
Showing
10 changed files
with
473 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.serverless | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# package directories | ||
node_modules | ||
jspm_packages | ||
|
||
# Serverless directories | ||
.serverless |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
test: | ||
node apple-music.test.js | ||
|
||
dev: | ||
sls deploy --stage dev | ||
|
||
prod: test | ||
sls deploy --stage production |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Some of these instructions are mostly for my own benefit, but if you wanted to | ||
deploy your own copy for debugging or any other reason, go for it. | ||
|
||
# Run Tests | ||
|
||
```sh | ||
make test | ||
``` | ||
|
||
# Deploy | ||
|
||
Development: | ||
|
||
```bash | ||
make dev | ||
``` | ||
|
||
Production (will run tests first): | ||
|
||
```bash | ||
make prod | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
'use strict'; | ||
|
||
// Examples: | ||
// sls invoke -f apple-music -d '{"url":"https://music.apple.com/us/album/nye-2022-dj-mix/1600990821"}' | ||
// curl "https://sxxqp2lco7.execute-api.us-east-1.amazonaws.com/dev/apple-music?url=https%3A%2F%2Fmusic.apple.com%2Fus%2Falbum%2Fnye-2022-dj-mix%2F1600990821" | ||
|
||
const fetch = require('node-fetch'); | ||
|
||
module.exports.import = async (event) => { | ||
// Validate the URL to prevent mistakes and abuse. | ||
const querystring = event.queryStringParameters; | ||
const url = querystring.url; | ||
if (!url.startsWith('https://music.apple.com/us/album/')) { | ||
return response(400, { | ||
error: `Invalid URL: ${url}`, | ||
}); | ||
} | ||
|
||
// Fetch the raw HTML. | ||
const resp = await fetch(url); | ||
const body = await resp.text(); | ||
|
||
// Parse tracks. | ||
// | ||
// TODO(elliotchance): This is an ultra crude regexp that will probably break | ||
// in the future. | ||
// | ||
// There are a few layouts we need to test, specially various artist albums | ||
// have a different layout than a single artist album. | ||
|
||
// If we get more than one match, this must be a VA album. The regexp happens | ||
// to fall out that way, but also an album with one track could hardly be | ||
// considered various artists. Maybe this is possible - I haven't seen any | ||
// examples of this though. | ||
let matches = Array.from(body.matchAll(/songs-list-row__song-name">([^<]+).*?row__link".*?>([^<]+).*?row__length">([^<]+)/gs)); | ||
let decoder = (match, number) => ({ | ||
number, | ||
title: match[2].trim() + ' - ' + match[1].trim(), | ||
time: match[3].trim(), | ||
}); | ||
|
||
if (matches.length < 2) { | ||
matches = Array.from(body.matchAll(/songs-list-row__song-name".*?>(.*?)<.*?row__length".*?>(.*?)</gs)); | ||
decoder = (match, number) => ({ | ||
number, | ||
title: match[1].trim(), | ||
time: match[2].trim(), | ||
}); | ||
} | ||
|
||
let tracks = []; | ||
let number = 1; | ||
for (const match of matches) { | ||
tracks.push(decoder(match, number)); | ||
++number; | ||
} | ||
|
||
return response(200, { | ||
tracks, | ||
}); | ||
}; | ||
|
||
function response(statusCode, body) { | ||
return { | ||
statusCode, | ||
body: JSON.stringify(body), | ||
headers: { | ||
'Access-Control-Allow-Origin': '*', | ||
'Access-Control-Allow-Credentials': true, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
const importer = require('./apple-music').import; | ||
const assert = require('assert'); | ||
|
||
function assertAppleMusic(tt) { | ||
(async () => { | ||
const resp = JSON.parse((await importer({ | ||
queryStringParameters: { | ||
url: tt.url, | ||
} | ||
})).body); | ||
|
||
if (tt.error || resp.error) { | ||
assert.equal(resp.error, tt.error); | ||
return; | ||
} | ||
|
||
assert.deepEqual(resp.tracks, tt.tracks); | ||
})(); | ||
} | ||
|
||
// Bad URL | ||
assertAppleMusic({ | ||
url: 'https://notapple.com/1600990821', | ||
error: 'Invalid URL: https://notapple.com/1600990821', | ||
}); | ||
|
||
// Single track | ||
assertAppleMusic({ | ||
url: 'https://music.apple.com/us/album/go-on-then-love-feat-the-maine-odea-remix-odea-remix-single/1591618917', | ||
tracks: [ | ||
{ | ||
number: 1, | ||
title: 'Go On Then, Love (feat. The Maine) [Odea Remix]', | ||
time: '3:08' | ||
} | ||
], | ||
}); | ||
|
||
// Single artist | ||
assertAppleMusic({ | ||
url: 'https://music.apple.com/us/album/dreaming-out-loud/1440889649', | ||
tracks: [ | ||
{ number: 1, title: 'Say (All I Need)', time: '3:50' }, | ||
{ number: 2, title: 'Mercy', time: '4:01' }, | ||
{ number: 3, title: 'Stop and Stare', time: '3:44' }, | ||
{ number: 4, title: 'Apologize', time: '3:25' }, | ||
{ number: 5, title: 'Goodbye, Apathy', time: '3:32' }, | ||
{ number: 6, title: 'All Fall Down', time: '4:06' }, | ||
{ number: 7, title: 'Tyrant', time: '5:05' }, | ||
{ number: 8, title: 'Prodigal', time: '3:56' }, | ||
{ number: 9, title: "Won't Stop", time: '5:05' }, | ||
{ number: 10, title: 'All We Are', time: '4:23' }, | ||
{ number: 11, title: 'Someone to Save You', time: '4:13' }, | ||
{ number: 12, title: 'Come Home', time: '4:29' }, | ||
{ | ||
number: 13, | ||
title: 'Apologize (feat. OneRepublic)', | ||
time: '3:05' | ||
} | ||
], | ||
}); | ||
|
||
// Various artists | ||
assertAppleMusic({ | ||
url: 'https://music.apple.com/us/album/nye-2022-dj-mix/1600990821', | ||
tracks: [ | ||
{ | ||
number: 1, | ||
title: 'Westside Gunn - Praise God Intro (feat. AA Rashid) [Mixed]', | ||
time: '2:03' | ||
}, | ||
{ | ||
number: 2, | ||
title: "The S.S.O. Orchestra - Tonight's the Night (feat. Douglas Lucas & The Sugar Sisters) [Mixed]", | ||
time: '1:11' | ||
}, | ||
{ | ||
number: 3, | ||
title: 'Irish Coffee - The Show, Pt. 1 / Scrabble (Mixed)', | ||
time: '1:42' | ||
}, | ||
{ | ||
number: 4, | ||
title: "All The People - Cramp Your Style / Don't Play No Game That I Can't Win (feat. Santigold) [Remix] [Mixed]", | ||
time: '1:21' | ||
}, | ||
{ number: 5, title: 'Nas - Nasty (Mixed)', time: '1:01' }, | ||
{ | ||
number: 6, | ||
title: 'Johnny Pate - Shaft In Africa (Addis) [Mixed]', | ||
time: '1:19' | ||
}, | ||
{ | ||
number: 7, | ||
title: 'Freedom - Get Up and Dance (Mixed)', | ||
time: '1:12' | ||
}, | ||
{ | ||
number: 8, | ||
title: "Funky 4+1 - That's the Joint (Mixed)", | ||
time: '1:00' | ||
}, | ||
{ | ||
number: 9, | ||
title: 'Esther Williams - Last Night Changed It All (I Really Had a Ball) [Mixed]', | ||
time: '1:06' | ||
}, | ||
{ | ||
number: 10, | ||
title: "Jungle Brothers - Feelin' Alright / Love Vibration (Mixed)", | ||
time: '0:51' | ||
}, | ||
{ | ||
number: 11, | ||
title: 'Indeep - Last Night a D.J. Saved My Life / Agboju Logun (Mr Bongo 7" Edit) [Mixed]', | ||
time: '1:50' | ||
}, | ||
{ | ||
number: 12, | ||
title: 'Bobby Thurston - You Got What It Takes (Remix) [Mixed]', | ||
time: '2:18' | ||
}, | ||
{ | ||
number: 13, | ||
title: 'The Rebirth - Evil Vibrations (Remix) [Mixed]', | ||
time: '1:33' | ||
}, | ||
{ | ||
number: 14, | ||
title: 'Vaughan Mason and Crew - Bounce, Rock, Skate, Roll (Mixed)', | ||
time: '3:24' | ||
}, | ||
{ | ||
number: 15, | ||
title: 'Love Unlimited - I Did It for Love (Mixed)', | ||
time: '1:59' | ||
}, | ||
{ | ||
number: 16, | ||
title: 'King Sporty & The Root Rockers - Get on Down (Mixed)', | ||
time: '1:20' | ||
}, | ||
{ | ||
number: 17, | ||
title: 'El Turronero - Las Penas (Las Cañas) [Mixed]', | ||
time: '2:06' | ||
}, | ||
{ | ||
number: 18, | ||
title: "Sault - Don't Waste My Time (Mixed)", | ||
time: '1:47' | ||
}, | ||
{ | ||
number: 19, | ||
title: 'Can - Vitamin C (U.N.K.L.E. Mix) [Mixed]', | ||
time: '3:24' | ||
}, | ||
{ | ||
number: 20, | ||
title: 'Radiohead - Everything In Its Right Place (Mixed)', | ||
time: '3:54' | ||
}, | ||
{ | ||
number: 21, | ||
title: 'ID - ID1 (from NYE 2022: Zane Lowe) [Mixed]', | ||
time: '2:47' | ||
}, | ||
{ | ||
number: 22, | ||
title: 'Pete Le Freq - One More Step (Mixed)', | ||
time: '3:17' | ||
}, | ||
{ | ||
number: 23, | ||
title: 'ID - ID2 (from NYE 2022: Zane Lowe) [Mixed]', | ||
time: '4:10' | ||
}, | ||
{ number: 24, title: 'iZNiiK - Leave (Mixed)', time: '2:27' }, | ||
{ | ||
number: 25, | ||
title: 'Disclosure - In My Arms (Mixed)', | ||
time: '2:09' | ||
}, | ||
{ number: 26, title: 'SG Lewis - Time (Mixed)', time: '2:28' }, | ||
{ | ||
number: 27, | ||
title: 'Underworld - Two Months Off (Mixed)', | ||
time: '5:48' | ||
} | ||
], | ||
}); | ||
|
||
// TODO(elliotchance): These ones do not working. | ||
|
||
// Various artists with mostly the same artist (so this they don't show on most | ||
// of the tracks) | ||
// assertAppleMusic({ | ||
// url: 'https://music.apple.com/us/album/mirage/398383005', | ||
// tracks: [], | ||
// }); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"dependencies": { | ||
"node-fetch": "^2.6.6" | ||
} | ||
} |
Oops, something went wrong.