spotifylookup/metasearcher.js

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());