Download quality fallback, shuffle, repeat, lastfm

This commit is contained in:
exttex
2020-09-07 19:12:45 +02:00
parent a77d8437bd
commit 27b55a4876
15 changed files with 246 additions and 715 deletions

View File

@ -61,6 +61,7 @@ class DeezerAPI {
}
}
return data.data;
}

View File

@ -13,8 +13,8 @@ class Track {
this.artistString = this.artists.map((a) => a.name).join(', ');
this.album = new Album(json);
this.trackNumber = json.TRACK_NUMBER;
this.diskNumber = json.DISK_NUMBER;
this.trackNumber = parseInt((json.TRACK_NUMBER || 0).toString(), 10);
this.diskNumber = parseInt((json.DISK_NUMBER || 0).toString(), 10);
this.explicit = json['EXPLICIT_LYRICS'] == 1 ? true:false;
this.lyricsId = json.LYRICS_ID;
@ -63,7 +63,7 @@ class Album {
//Type
this.type = 'album';
if (json.TYPE && json.TYPE.toString() == "0") this.type = 'single';
if (!json.ARTISTS_ALBUMS_IS_OFFICIAL) this.type = 'featured';
if (json.ROLE_ID == 5) this.type = 'featured';
//Helpers
this.artistString = this.artists.map((a) => a.name).join(', ');

View File

@ -54,7 +54,7 @@ class Downloads {
generateTrackPath(track, quality) {
//Generate filename
let fn = this.settings.downloadFilename + (quality == 9 ? '.flac' : '.mp3');
let fn = this.settings.downloadFilename;
//Disable feats for single artist
let feats = '';
@ -108,12 +108,15 @@ class Downloads {
//Save to DB
if (this.download) {
await new Promise((res, rej) => {
// this.db.update({_id: this.download.id}, {state: 3}, (e) => {
// res();
// });
this.db.remove({_id: this.download.id}, (e) => {
this.db.update({_id: this.download.id}, {
state: this.download.state,
fallback: this.download.fallback,
}, (e) => {
res();
});
// this.db.remove({_id: this.download.id}, (e) => {
// res();
// });
});
}
@ -144,7 +147,7 @@ class Downloads {
if (!docs) return;
for (let d of docs) {
if (d.state < 3) this.downloads.push(Download.fromDB(d, () => {this._downloadDone();}));
if (d.state < 3 && d.state >= 0) this.downloads.push(Download.fromDB(d, () => {this._downloadDone();}));
//TODO: Ignore for now completed
}
res();
@ -196,7 +199,9 @@ class Download {
//1 - downloading
//2 - post-processing
//3 - done
//-1 - download error
this.state = 0;
this.fallback = false;
this._request;
//Post Processing Promise
@ -213,7 +218,8 @@ class Download {
path: this.path,
quality: this.quality,
track: this.track,
state: this.state
state: this.state,
fallback: this.fallback
}
}
@ -221,6 +227,7 @@ class Download {
//Create download from DB document
static fromDB(doc, onDone) {
let d = new Download(doc.track, doc.path, doc.quality, onDone);
d.fallback = doc.fallback ? true : false; //Null check
d.state = doc.state;
return d;
}
@ -240,8 +247,24 @@ class Download {
//Get download info
if (!this.url) this.url = Track.getUrl(this.track.streamUrl, this.quality);
this._request = https.get(this.url, {headers: {'Range': `bytes=${start}-`}}, (r) => {
//Error
if (r.statusCode >= 400) {
//Fallback on error
if (this.quality > 1) {
if (this.quality == 3) this.quality = 1;
if (this.quality == 9) this.quality = 3;
this.url = null;
this.fallback = true;
return this.start();
};
//Error
this.state = -1;
console.log(`Undownloadable track ID: ${this.track.id}`);
return this.onDone();
}
//On download done
r.on('end', () => {
if (this.downloaded != this.size) return;
@ -288,6 +311,7 @@ class Download {
} catch (e) {};
//Decrypt
this.path += (this.quality == 9) ? '.flac' : '.mp3';
decryptor.decryptFile(decryptor.getKey(this.track.id), tmp, `${tmp}.DEC`);
fs.promises.copyFile(`${tmp}.DEC`, this.path);
//Delete encrypted

View File

@ -3,6 +3,7 @@ const path = require('path');
const https = require('https');
const fs = require('fs');
const axios = require('axios').default;
const LastfmAPI = require('lastfmapi');
const {DeezerAPI, DeezerDecryptionStream} = require('./deezer');
const {Settings} = require('./settings');
const {Track, Album, Artist, Playlist, DeezerProfile, SearchResults, DeezerLibrary, DeezerPage, Lyrics} = require('./definitions');
@ -21,6 +22,12 @@ app.use(express.static(path.join(__dirname, '../client', 'dist')));
//Server
const server = require('http').createServer(app);
const io = require('socket.io').listen(server);
//LastFM
//plz don't steal creds, it's just lastfm
let lastfm = new LastfmAPI({
api_key: 'b6ab5ae967bcd8b10b23f68f42493829',
secret: '861b0dff9a8a574bec747f9dab8b82bf'
});
//Get playback info
app.get('/playback', async (req, res) => {
@ -425,20 +432,58 @@ app.delete('/downloads/:index', async (req, res) => {
res.status(200).end();
});
//Log listen to deezer
app.put('/log/:id', async (req, res) => {
await deezer.callApi('log.listen', {
params: {
timestamp: Math.floor(new Date() / 1000),
ts_listen: Math.floor(new Date() / 1000),
type: 1,
stat: {seek: 0, pause: 0, sync: 0},
media: {id: req.params.id.toString(), type: 'song', format: 'MP3_128'}
}
});
//Log listen to deezer & lastfm
app.post('/log', async (req, res) => {
//LastFM
if (settings.lastFM)
lastfm.track.scrobble({
artist: req.body.artists[0].name,
track: req.body.title,
timestamp: Math.floor((new Date()).getTime() / 1000)
});
//Deezer
if (settings.logListen)
await deezer.callApi('log.listen', {
params: {
timestamp: Math.floor(new Date() / 1000),
ts_listen: Math.floor(new Date() / 1000),
type: 1,
stat: {seek: 0, pause: 0, sync: 0},
media: {id: req.body.id, type: 'song', format: 'MP3_128'}
}
});
res.status(200).end();
});
//Last.FM authorization callback
app.get('/lastfm', async (req, res) => {
//Got token
if (req.query.token) {
let token = req.query.token;
await new Promise((res, rej) => {
lastfm.authenticate(token, (err, sess) => {
if (err) res();
//Save to settings
settings.lastFM = {
name: sess.username,
key: sess.key
};
settings.save();
res();
});
});
authorizeLastFM();
//Redirect to homepage
return res.redirect('/');
}
//Get auth url
res.json({
url: lastfm.getAuthenticationUrl({cb: `http://${req.socket.remoteAddress}:${settings.port}/lastfm`})
}).end();
});
//Redirect to index on unknown path
app.all('*', (req, res) => {
res.redirect('/');
@ -490,6 +535,12 @@ async function qualityFallback(info, quality = 3) {
}
}
//Autorize lastfm with saved credentials
function authorizeLastFM() {
if (!settings.lastFM) return;
lastfm.setSessionCredentials(settings.lastFM.name, settings.lastFM.key);
}
//ecb = Error callback
async function createServer(electron = false, ecb) {
//Prepare globals
@ -526,6 +577,9 @@ async function createServer(electron = false, ecb) {
});
}, 350);
//LastFM
authorizeLastFM();
//Start server
server.on('error', (e) => {
ecb(e);

View File

@ -24,6 +24,7 @@ class Settings {
this.downloadFilename = '%0trackNumber%. %artists% - %title%';
this.logListen = false;
this.lastFM = null;
}
//Based on electorn app.getPath
@ -80,7 +81,8 @@ class Settings {
Object.assign(this, JSON.parse(data));
}
} catch (e) {
console.error(`Error loading settings: ${e}. Using defaults.`)
console.error(`Error loading settings: ${e}. Using defaults.`);
this.save();
}
this.electron = e;