create playlist
This commit is contained in:
parent
035a88627e
commit
d4229ce2f8
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
*.json
|
*.json
|
||||||
*.html
|
*.html
|
||||||
|
!callback.html
|
||||||
!package.json
|
!package.json
|
||||||
!package-lock.json
|
!package-lock.json
|
||||||
|
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
Looks for your local music files on Spotify and helps you create a playlist. This is useful for migrating your local music to Spotify. **Your tracks need to have valid metadata (artist and title is the minimum)!**.
|
Looks for your local music files on Spotify and helps you create a playlist. This is useful for migrating your local music to Spotify. **Your tracks need to have valid metadata (artist and title is the minimum)!**.
|
||||||
|
|
||||||
Feel free to use `https://lunasqu.ee/callback` as a callback url. It just prints query parameters to html. even if i wanted to i couldn't steal your code as i do not know your client secret.
|
Feel free to use `https://lunasqu.ee/callback` as a callback url or put `callback.html` on your own server. It just prints query parameters to html. even if i wanted to i couldn't steal your code as i do not know your client secret.
|
||||||
|
|
||||||
1. create Spotify app: https://developer.spotify.com/dashboard/login
|
1. create Spotify app: https://developer.spotify.com/dashboard/login
|
||||||
2. `npm i`
|
2. `npm i`
|
||||||
3. put your Spotify credentials in `credentials.json` following the example.
|
3. put your Spotify credentials in `credentials.json` following the example.
|
||||||
4. `node metasearcher.js /path/to/music/directory` scan the directory for music files and create a `spotify.json` file from matches.
|
4. `node metasearcher /path/to/music/directory` scan the directory for music files and create a `spotify.json` file from matches.
|
||||||
5. `node resultvisualizer.js` create a html table from the `spotify.json` file.
|
5. `node resultvisualizer` create a html table from the `spotify.json` file. Select the tracks from the HTML file and press "Export selection"
|
||||||
|
6. Put `track-selection.json` in this directory and run `node composeplaylist create/add [playlist name/id]` to put all of them into a playlist!
|
||||||
|
|
||||||
|
Next time you run `resultvisualizer` your previous selection will be automagically checked!
|
||||||
|
34
bits/frontend.js
Normal file
34
bits/frontend.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
(function() {
|
||||||
|
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const button = document.querySelector('#export');
|
||||||
|
const selectedTracks = [];
|
||||||
|
checkboxes.forEach((box) => {
|
||||||
|
const meta = [
|
||||||
|
box.getAttribute('data-file'), box.getAttribute('data-spotify')
|
||||||
|
];
|
||||||
|
function clickBox() {
|
||||||
|
if (box.checked) {
|
||||||
|
selectedTracks.push(meta);
|
||||||
|
} else {
|
||||||
|
const i = selectedTracks.indexOf(meta);
|
||||||
|
if (i > -1) {
|
||||||
|
selectedTracks.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
box.addEventListener('change', ($ev) => clickBox());
|
||||||
|
box.parentElement.parentElement.addEventListener('click', () => box.click());
|
||||||
|
clickBox();
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener('click', ($ev) => {
|
||||||
|
$ev.preventDefault();
|
||||||
|
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(selectedTracks));
|
||||||
|
var downloadAnchorNode = document.createElement('a');
|
||||||
|
downloadAnchorNode.setAttribute("href", dataStr);
|
||||||
|
downloadAnchorNode.setAttribute("download", "track-selection.json");
|
||||||
|
document.body.appendChild(downloadAnchorNode);
|
||||||
|
downloadAnchorNode.click();
|
||||||
|
downloadAnchorNode.remove();
|
||||||
|
});
|
||||||
|
})();
|
29
bits/psuedoframework.js
Normal file
29
bits/psuedoframework.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module.exports.createDocument = function(stylesheet, content) {
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Spotify results</title>
|
||||||
|
<style>${stylesheet}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${content}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.$ = function(name, content, classList) {
|
||||||
|
return {
|
||||||
|
toString: () => `<${name}${classList && classList.length ? ` class="${classList.join(',')}"` : ''}>${content ? content.toString() : ''}</${name}>`,
|
||||||
|
append: function ($el) {
|
||||||
|
content = content ? content + $el.toString() : $el.toString();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
html: function(text) {
|
||||||
|
content = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
93
bits/spotify.js
Normal file
93
bits/spotify.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
const SpotifyWebApi = require('spotify-web-api-node');
|
||||||
|
const ReadLine = require('readline');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Create the authorization URL
|
||||||
|
const rl = ReadLine.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const root = path.join(__dirname, '..');
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"clientId": "",
|
||||||
|
"clientSecret": "",
|
||||||
|
"redirectUri": "https://lunasqu.ee/callback"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const spotifyApi = new SpotifyWebApi(require(path.join(root, 'credentials.json')));
|
||||||
|
|
||||||
|
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(path.join(root, '.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(path.join(root, '.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(path.join(root, '.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());
|
||||||
|
rl.close();
|
||||||
|
return spotifyApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setupSpotify,
|
||||||
|
spotifyApi,
|
||||||
|
};
|
17
callback.html
Normal file
17
callback.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Callback</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span id="content" style="white-space: pre; font-size: 2rem;"></span>
|
||||||
|
<script defer>
|
||||||
|
const elem = document.querySelector('#content');
|
||||||
|
const lines = window.location.search.substring(1).split('&');
|
||||||
|
elem.textContent = lines.map(line => line.split('=').join(': ')).join('\n');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
59
composeplaylist.js
Normal file
59
composeplaylist.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const fs = require('fs/promises');
|
||||||
|
const path = require('path');
|
||||||
|
const { setupSpotify, spotifyApi } = require(path.join(__dirname, 'bits', 'spotify'));
|
||||||
|
|
||||||
|
const selectionFile = process.argv[4]
|
||||||
|
? path.resolve(process.argv[4])
|
||||||
|
: path.join(__dirname, 'track-selection.json');
|
||||||
|
|
||||||
|
const operation = process.argv[2];
|
||||||
|
if (!operation || !['create', 'add'].includes(operation)) {
|
||||||
|
console.error('Invalid operation! Operations: create, add');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlist = process.argv[3];
|
||||||
|
if (!playlist) {
|
||||||
|
console.error('No playlist ID or name provided');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readSelection() {
|
||||||
|
let spotifyUris = [];
|
||||||
|
let chunkedUris = [];
|
||||||
|
let discardFiles = [];
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
const content = JSON.parse(await fs.readFile(selectionFile));
|
||||||
|
content.forEach((item) => {
|
||||||
|
spotifyUris.push(item[1]);
|
||||||
|
discardFiles.push(item[0]);
|
||||||
|
total++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (spotifyUris.length >= 100) {
|
||||||
|
while (spotifyUris.length >= 100) {
|
||||||
|
chunkedUris.push(spotifyUris.splice(0, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spotifyUris.length) {
|
||||||
|
chunkedUris.push(spotifyUris);
|
||||||
|
}
|
||||||
|
|
||||||
|
await setupSpotify();
|
||||||
|
let playlistId = playlist;
|
||||||
|
|
||||||
|
if (operation === 'create') {
|
||||||
|
const newPlaylist = await spotifyApi.createPlaylist(playlist, { public: false });
|
||||||
|
playlistId = newPlaylist.body.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const uris of chunkedUris) {
|
||||||
|
await spotifyApi.addTracksToPlaylist(playlistId, uris);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(operation === 'create' ? 'Created a playlist with' : 'Added', total.length, 'tracks');
|
||||||
|
}
|
||||||
|
|
||||||
|
readSelection().catch(console.error);
|
@ -1,95 +1,14 @@
|
|||||||
const SpotifyWebApi = require('spotify-web-api-node');
|
const NodeID3 = require('node-id3');
|
||||||
const NodeID3 = require('node-id3')
|
|
||||||
|
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const ReadLine = require('readline');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { setupSpotify, spotifyApi } = require(path.join(__dirname, 'bits', 'spotify'));
|
||||||
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"clientId": "",
|
|
||||||
"clientSecret": "",
|
|
||||||
"redirectUri": "https://lunasqu.ee/callback"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const spotifyApi = new SpotifyWebApi(require('./credentials.json'));
|
|
||||||
const audPath = process.argv[2];
|
const audPath = process.argv[2];
|
||||||
|
|
||||||
if (!audPath) {
|
if (!audPath) {
|
||||||
throw new Error('No directory specified');
|
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) {
|
async function readTags(file) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
NodeID3.read(file, {noRaw: true, exclude: ['image']}, function(err, tags) {
|
NodeID3.read(file, {noRaw: true, exclude: ['image']}, function(err, tags) {
|
||||||
@ -168,4 +87,4 @@ async function searchAllOfSpotify() {
|
|||||||
await fs.writeFile('spotify.json', JSON.stringify(tracks, undefined, 2));
|
await fs.writeFile('spotify.json', JSON.stringify(tracks, undefined, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
searchAllOfSpotify().catch(console.error).then(() => rl.close());
|
searchAllOfSpotify().catch(console.error);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
const fs = require('fs/promises');
|
const fs = require('fs/promises');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { createDocument, $ } = require(path.join(__dirname, 'bits', 'psuedoframework'));
|
||||||
|
const fescriptpath = path.join(__dirname, 'bits', 'frontend.js');
|
||||||
|
|
||||||
const stylesheet = `
|
const stylesheet = `
|
||||||
html, body {
|
html, body {
|
||||||
@ -23,55 +25,82 @@ body {
|
|||||||
table tr:nth-child(odd) {
|
table tr:nth-child(odd) {
|
||||||
background-color: #030a38;
|
background-color: #030a38;
|
||||||
}
|
}
|
||||||
|
tr {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.meta .file {
|
.meta .file {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.meta .title, .meta .artist {
|
.meta .title, .meta .artist {
|
||||||
font-size: .6rem;
|
font-size: .6rem;
|
||||||
}
|
}
|
||||||
|
input[type=checkbox] {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font-size: 2rem;
|
||||||
|
padding: 1.6rem 6rem;
|
||||||
|
margin: 1rem;
|
||||||
|
background-color: #1DB954;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color .15s linear;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #25df67;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function createDocument(content) {
|
async function getPreviousSelection() {
|
||||||
return `
|
let selection;
|
||||||
<!DOCTYPE html>
|
try {
|
||||||
<html>
|
selection = JSON.parse(await fs.readFile(path.join(__dirname, 'track-selection.json')));
|
||||||
<head>
|
} catch (e) {
|
||||||
<meta charset="utf-8">
|
selection = [];
|
||||||
<title>Spotify results</title>
|
}
|
||||||
<style>${stylesheet}</style>
|
return selection;
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
${content}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function $(name, content, classList) {
|
const checkCache = [];
|
||||||
return {
|
function selectionContainsURI(list, uri) {
|
||||||
toString: () => `<${name}${classList && classList.length ? ` class="${classList.join(',')}"` : ''}>${content ? content.toString() : ''}</${name}>`,
|
if (list.length === 0) {
|
||||||
append: function ($el) {
|
return false;
|
||||||
content = content ? content + $el.toString() : $el.toString();
|
}
|
||||||
return this;
|
|
||||||
},
|
// Only check a single one with the same spotify uri
|
||||||
html: function(text) {
|
if (checkCache.includes(uri)) {
|
||||||
content = text;
|
return false;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
};
|
const result = list.some((item) => item[1] === uri);
|
||||||
|
if (result) {
|
||||||
|
checkCache.push(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generate() {
|
async function generate() {
|
||||||
|
const fescript = await fs.readFile(fescriptpath);
|
||||||
|
const selection = await getPreviousSelection();
|
||||||
const wrapper = $('div', null, ['wrapper']);
|
const wrapper = $('div', null, ['wrapper']);
|
||||||
const table = $('table');
|
const table = $('table');
|
||||||
const header = $('thead', `
|
const header = $('thead', `
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Choose</th>
|
||||||
<th>Original File</th>
|
<th>Original File</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Album</th>
|
<th>Album</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Artist</th>
|
<th>Artist</th>
|
||||||
|
<th>Preview</th>
|
||||||
<th>Meta</th>
|
<th>Meta</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -83,18 +112,24 @@ async function generate() {
|
|||||||
let row = [];
|
let row = [];
|
||||||
for (const sfItem of entry.spotify) {
|
for (const sfItem of entry.spotify) {
|
||||||
row.push([
|
row.push([
|
||||||
|
'<input type="checkbox" ' +
|
||||||
|
`data-spotify="${sfItem.uri}" ` +
|
||||||
|
`data-file="${entry.file}" ` +
|
||||||
|
(selectionContainsURI(selection, sfItem.uri) ? 'checked' : '') +
|
||||||
|
'>',
|
||||||
'',
|
'',
|
||||||
sfItem.album.album_type,
|
sfItem.album.album_type,
|
||||||
sfItem.album.name,
|
sfItem.album.name,
|
||||||
sfItem.name,
|
sfItem.name,
|
||||||
sfItem.artists.map((item) => item.name).join(', '),
|
sfItem.artists.map((item) => item.name).join(', '),
|
||||||
|
sfItem.preview_url ? `<audio preload="none" controls src="${sfItem.preview_url}"></audio>` : '',
|
||||||
`<a href="${sfItem.uri}" target="_blank">` +
|
`<a href="${sfItem.uri}" target="_blank">` +
|
||||||
`<img src="${sfItem.album.images[sfItem.album.images.length - 1].url}">` +
|
`<img src="${sfItem.album.images[sfItem.album.images.length - 1].url}">` +
|
||||||
'</a>'
|
'</a>'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
row[0][0] = $('div',
|
row[0][1] = $('div',
|
||||||
$('span', path.basename(entry.file), ['file']) +
|
$('span', path.basename(entry.file), ['file']) +
|
||||||
'<br>' +
|
'<br>' +
|
||||||
$('span', `Title: ${entry.title}`, ['title']) +
|
$('span', `Title: ${entry.title}`, ['title']) +
|
||||||
@ -116,8 +151,10 @@ async function generate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows.forEach((item) => tbody.append(item));
|
rows.forEach((item) => tbody.append(item));
|
||||||
wrapper.append(table.append(header).append(tbody));
|
wrapper.append(table.append(header).append(tbody))
|
||||||
await fs.writeFile('test.html', createDocument(wrapper));
|
.append(`<button id="export">Export selection</button>`)
|
||||||
|
.append(`<script defer>${fescript}</script>`);
|
||||||
|
await fs.writeFile('search-results.html', createDocument(stylesheet, wrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
generate().catch(console.error);
|
generate().catch(console.error);
|
||||||
|
Loading…
Reference in New Issue
Block a user