172 lines
4.9 KiB
JavaScript
172 lines
4.9 KiB
JavaScript
const SpotifyWebApi = require('spotify-web-api-node');
|
|
const NodeID3 = require('node-id3')
|
|
|
|
const fs = require('fs').promises;
|
|
const ReadLine = require('readline');
|
|
const path = require('path');
|
|
|
|
/*
|
|
{
|
|
"clientId": "",
|
|
"clientSecret": "",
|
|
"redirectUri": "https://lunasqu.ee/callback"
|
|
}
|
|
*/
|
|
const spotifyApi = new SpotifyWebApi(require('./credentials.json'));
|
|
const audPath = process.argv[2];
|
|
|
|
if (!audPath) {
|
|
throw new Error('No directory specified');
|
|
}
|
|
|
|
// Create the authorization URL
|
|
const rl = ReadLine.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
function ask(msg) {
|
|
return new Promise((resolve) => rl.question(msg, resolve));
|
|
}
|
|
|
|
async function setupSpotify() {
|
|
let credentials = {
|
|
token: null,
|
|
expires: null,
|
|
refresh: null,
|
|
};
|
|
|
|
try {
|
|
credentials = JSON.parse(await fs.readFile('.token.json'));
|
|
} catch (e) {}
|
|
|
|
if (!credentials.token) {
|
|
const authorizeURL = spotifyApi.createAuthorizeURL(['playlist-modify-private'], 'FPEPFERHR234234trGEWErjrjsdw3666sf06');
|
|
console.log('Authorize the application');
|
|
console.log(authorizeURL);
|
|
const code = await ask('Enter code []> ');
|
|
|
|
if (!code) {
|
|
process.exit(1);
|
|
}
|
|
|
|
const data = await spotifyApi.authorizationCodeGrant(code);
|
|
console.log('The access token has been received.');
|
|
|
|
// Set the access token on the API object to use it in later calls
|
|
spotifyApi.setAccessToken(data.body['access_token']);
|
|
spotifyApi.setRefreshToken(data.body['refresh_token']);
|
|
|
|
credentials.token = data.body['access_token'];
|
|
credentials.expires = Math.floor(Date.now() / 1000) + data.body['expires_in'];
|
|
credentials.refresh = data.body['refresh_token'];
|
|
|
|
await fs.writeFile('.token.json', JSON.stringify(credentials, undefined, 2));
|
|
} else {
|
|
console.log('The access token is present');
|
|
spotifyApi.setAccessToken(credentials.token);
|
|
spotifyApi.setRefreshToken(credentials.refresh);
|
|
}
|
|
|
|
if ((credentials.expires * 1000) < Date.now()) {
|
|
console.log('Refreshing access token');
|
|
try {
|
|
const refresh = await spotifyApi.refreshAccessToken();
|
|
credentials.token = refresh.body['access_token'];
|
|
credentials.expires = Math.floor(Date.now() / 1000) + refresh.body['expires_in'];
|
|
spotifyApi.setAccessToken(credentials.token);
|
|
spotifyApi.setRefreshToken(credentials.refresh);
|
|
} catch (e) {
|
|
console.error(e);
|
|
console.log('need to reauthorize');
|
|
credentials.token = null;
|
|
}
|
|
await fs.writeFile('.token.json', JSON.stringify(credentials, undefined, 2));
|
|
if (!credentials.token) {
|
|
return setupSpotify();
|
|
}
|
|
}
|
|
|
|
console.log('the token is good until', new Date(credentials.expires * 1000).toString());
|
|
}
|
|
|
|
async function readTags(file) {
|
|
return new Promise((resolve) => {
|
|
NodeID3.read(file, {noRaw: true, exclude: ['image']}, function(err, tags) {
|
|
if (err) return resolve(null)
|
|
if (tags.image) tags.image = null;
|
|
if (tags.private) tags.private = null;
|
|
resolve(tags);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function readAudioFiles() {
|
|
let fileAndMeta = [];
|
|
const contents = await fs.readdir(audPath);
|
|
for (const file of contents) {
|
|
const fullp = path.join(audPath, file);
|
|
const tags = await readTags(fullp);
|
|
fileAndMeta.push({
|
|
path: fullp,
|
|
tags: tags,
|
|
});
|
|
}
|
|
fileAndMeta = fileAndMeta.filter((item) => item.tags && item.tags.artist && item.tags.title);
|
|
await fs.writeFile('file-meta-cache.json', JSON.stringify(fileAndMeta, undefined, 2));
|
|
return fileAndMeta;
|
|
}
|
|
|
|
async function delay(time) {
|
|
return new Promise((resolve) => setTimeout(resolve, time));
|
|
}
|
|
|
|
let requestDelay = 100;
|
|
|
|
async function searchTrack(query) {
|
|
console.log('Searching for', query);
|
|
let returned
|
|
|
|
try {
|
|
returned = await spotifyApi.searchTracks(query, { limit: 5, market: 'EE' });
|
|
} catch (e) {
|
|
console.error(e);
|
|
console.log('We may have hit a rate limit');
|
|
console.log('Waiting 30 seconds for the rate limit window');
|
|
requestDelay = 1000;
|
|
await delay(30000);
|
|
return searchTrack(query);
|
|
}
|
|
|
|
await delay(requestDelay);
|
|
console.log('Got', returned.body.tracks.total, 'results');
|
|
return returned.body.tracks.items;
|
|
}
|
|
|
|
async function searchAllOfSpotify() {
|
|
await setupSpotify();
|
|
|
|
const audioFiles = await readAudioFiles();
|
|
let tracks = [];
|
|
let progress = 0;
|
|
let hits = 0;
|
|
|
|
for (const file of audioFiles) {
|
|
console.log('=> item %d of %d', (progress++), audioFiles.length);
|
|
const results = await searchTrack(`${file.tags.title} artist:${file.tags.artist}`);
|
|
if (results.length) {
|
|
hits++;
|
|
tracks.push({
|
|
file: file.path,
|
|
title: file.tags.title,
|
|
artist: file.tags.artist,
|
|
spotify: results,
|
|
});
|
|
}
|
|
}
|
|
console.log('Traversed %d audio files, of which %d had potential results from Spotify', audioFiles.length, hits);
|
|
await fs.writeFile('spotify.json', JSON.stringify(tracks, undefined, 2));
|
|
}
|
|
|
|
searchAllOfSpotify().catch(console.error).then(() => rl.close());
|