Skip to content

Instantly share code, notes, and snippets.

@dnicolson
Created August 6, 2020 22:18
Show Gist options
  • Save dnicolson/8afbb33344845d72e873fe3a33b84406 to your computer and use it in GitHub Desktop.
Save dnicolson/8afbb33344845d72e873fe3a33b84406 to your computer and use it in GitHub Desktop.
Sets the grouping field of an iTunes/Music selection with artist MusicBrainz tags via Node.js with a couple of OSA calls
const osa = require('osa2');
const fetch = require('node-fetch');
const cliProgress = require('cli-progress');
// Return a list of unique artists based on the selection
const fetchArtists = osa(() =>
(Application('iTunes') || Application('Music'))
.selection()
.map((x) => x.artist())
.filter((v, i, a) => a.indexOf(v) === i),
);
// Update the grouping tag for the artist
const setGrouping = osa((artist, tags) => {
const songs = (Application('iTunes') || Application('Music')).playlists
.whose({ name: 'Library' })[0]
.tracks.whose({ artist: { _equals: artist } });
for (const song of Array(songs)) {
song.grouping.set(tags);
}
});
// Return the tags for an artist or throw an error
const fetchTags = (artist) =>
new Promise((resolve, reject) => {
(async () => {
try {
const response = await fetch(
`http://musicbrainz.org/ws/2/artist/?fmt=json&query=artist:${encodeURIComponent(artist)}`,
);
if (response.status !== 200) {
throw new Error(`HTTP resonse ${response.status}`);
}
const data = await response.json();
resolve(
data.artists[0].tags
.filter((t) => t.count > 0)
.map((t) => t.name)
.join(', '),
);
} catch (e) {
if (e.message.match(/'filter'/)) {
reject('Artist not found');
} else if (e.message.match(/'tags'/)) {
reject('Tags not found');
}
reject(e.message);
}
})();
});
// Ensure each interation happens no more than once every 1000ms
const rateLimitCheck = async () => {
if (!this.lastRequestTime) {
this.lastRequestTime = 0;
}
const timeDiff = Date.now() - this.lastRequestTime;
if (timeDiff < 1000) {
await new Promise((r) => setTimeout(r, 1000 - timeDiff));
}
this.lastRequestTime = Date.now();
};
// Main function
(async () => {
const artists = await fetchArtists();
const missingArtists = [];
const bar1 = new cliProgress.SingleBar({ stopOnComplete: true }, cliProgress.Presets.shades_classic);
bar1.start(artists.length, 0);
for (const artist of artists) {
await rateLimitCheck();
try {
const tags = await fetchTags(artist);
await setGrouping(artist, tags);
} catch (error) {
missingArtists.push([artist, error.message ?? error]);
continue;
} finally {
bar1.increment();
}
}
console.table(missingArtists);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment