4 * | | | | ___ ___ ____| | ___ __ _ __| | ___ _ __
5 * | | | | / _ \ / _ \|_ /| | / _ \ / _` | / _` | / _ \| '__|
6 * | |__| || __/| __/ / / | || (_) || (_| || (_| || __/| |
7 * |_____/ \___| \___|/___||_| \___/ \__,_| \__,_| \___||_|
11 * Original work by ZzMTV <https://boerse.to/members/zzmtv.3378614/>
14 const express = require('express');
15 const app = express();
16 const server = require('http').createServer(app);
17 const mflac = require('./lib/flac-metadata');
18 const io = require('socket.io').listen(server, {log: false, wsEngine: 'ws'});
19 const fs = require('fs-extra');
20 const async = require('async');
21 const request = require('requestretry').defaults({maxAttempts: 2147483647, retryDelay: 1000, timeout: 8000});
22 const os = require('os');
23 const ID3Writer = require('./lib/browser-id3-writer');
24 const Deezer = require('./deezer-api');
25 const path = require('path');
26 const crypto = require('crypto');
27 const logger = require('./logger.js');
28 const Spotify = require('spotify-web-api-node');
29 const authCredentials = require('./authCredentials.js')
30 const queue = require('queue')
35 if(process.env.APPDATA){
36 homedata = os.homedir();
37 userdata = process.env.APPDATA + path.sep + "Deezloader Remix" + path.sep;
38 }else if(process.platform == "darwin"){
39 homedata = os.homedir();
40 userdata = homedata + '/Library/Application Support/Deezloader Remix/';
41 }else if(process.platform == "android"){
42 homedata = os.homedir() + "/storage/shared";
43 userdata = homedata + "/Deezloader Remix/";
45 homedata = os.homedir();
46 userdata = homedata + '/.config/Deezloader Remix/';
49 if(!fs.existsSync(userdata+"config.json")){
50 fs.outputFileSync(userdata+"config.json",fs.readFileSync(__dirname+path.sep+"default.json",'utf8'));
53 var spotifyApi = new Spotify(authCredentials);
56 const configFileLocation = userdata+"config.json";
57 const autologinLocation = userdata+"autologin";
58 const coverArtFolder = os.tmpdir() + path.sep + 'deezloader-imgs' + path.sep;
59 const defaultDownloadDir = homedata + path.sep + "Music" + path.sep + 'Deezloader' + path.sep;
60 const defaultSettings = require('./default.json').userDefined;
62 // Setup the folders START
63 var mainFolder = defaultDownloadDir;
65 // Settings update fix
66 var configFile = require(userdata+path.sep+"config.json");
67 for (let x in defaultSettings){
68 if (typeof configFile.userDefined[x] != typeof defaultSettings[x]){
69 configFile.userDefined[x] = defaultSettings[x]
73 if (configFile.userDefined.downloadLocation != "") {
74 mainFolder = configFile.userDefined.downloadLocation;
80 // Route and Create server
81 app.use('/', express.static(__dirname + '/public/'));
82 server.listen(configFile.serverPort);
83 logger.info('Server is running @ localhost:' + configFile.serverPort);
85 //Autologin encryption/decryption
86 var ekey = "62I9smDurjvfOdn2JhUdi99yeoAhxikw";
88 function alencrypt(input) {
89 let iv = crypto.randomBytes(16);
90 let data = new Buffer(input).toString('binary');
91 key = new Buffer(ekey, "utf8");
92 let cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
94 encrypted = cipher.update(data, 'utf8', 'binary') + cipher.final('binary');
95 let encoded = new Buffer(iv, 'binary').toString('hex') + new Buffer(encrypted, 'binary').toString('hex');
100 function aldecrypt(encoded) {
101 let combined = new Buffer(encoded, 'hex');
102 key = new Buffer(ekey, "utf8");
104 let iv = new Buffer(16);
105 combined.copy(iv, 0, 0, 16);
106 edata = combined.slice(16).toString('binary');
107 // Decipher encrypted data
108 let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
109 let decrypted, plaintext;
110 plaintext = (decipher.update(edata, 'binary', 'utf8') + decipher.final('utf8'));
115 // START sockets clusterfuck
116 io.sockets.on('connection', function (socket) {
117 socket.downloadQueue = {};
118 socket.currentItem = null;
119 socket.lastQueueId = null;
120 socket.trackQueue = queue({
123 socket.trackQueue.concurrency = configFile.userDefined.queueConcurrency;
125 socket.on("login", function (username, password, autologin) {
126 Deezer.init(username, password, function (err) {
128 socket.emit("login", {error: err.message});
129 logger.error("Failed to login, "+err);
132 let data = username + "\n" + password;
133 fs.outputFile(autologinLocation, alencrypt(data) , function(){
135 logger.info("Added autologin successfully");
137 logger.info("Failed to add autologin file");
141 logger.info("Logging in");
142 socket.emit("login", {username: Deezer.userName, picture: Deezer.userPicture, email: username});
143 logger.info("Logged in successfully");
148 socket.on("autologin", function(){
149 fs.readFile(autologinLocation, function(err, data){
151 logger.info("No auto login found");
155 var fdata = aldecrypt(data.toString('utf8'));
158 logger.warn("Invalid autologin file, deleting");
159 fs.unlink(autologinLocation,function(){
163 fdata = fdata.split('\n');
164 socket.emit("autologin",fdata[0],fdata[1]);
168 socket.on("logout", function(){
169 logger.info("Logged out");
170 fs.unlink(autologinLocation,function(){
175 Deezer.onDownloadProgress = function (track, progress) {
176 if (!track.trackSocket) {
179 if(track.trackSocket.currentItem && track.trackSocket.currentItem.type == "track"){
181 if (!track.trackSocket.currentItem.percentage) {
182 track.trackSocket.currentItem.percentage = 0;
184 if (parseInt(track.SNG_ID)<0){
185 complete = track.FILESIZE;
186 }else if(track.format == 9){
187 complete = track.FILESIZE_FLAC;
189 if (track.FILESIZE_MP3_320) {
190 complete = track.FILESIZE_MP3_320;
191 } else if (track.FILESIZE_MP3_256) {
192 complete = track.FILESIZE_MP3_256;
194 complete = track.FILESIZE_MP3_128 || 0;
197 let percentage = (progress / complete) * 100;
198 if ((percentage - track.trackSocket.currentItem.percentage > 1) || (progress == complete)) {
199 track.trackSocket.currentItem.percentage = percentage;
200 track.trackSocket.emit("downloadProgress", {
201 queueId: track.trackSocket.currentItem.queueId,
202 percentage: track.trackSocket.currentItem.percentage
208 function addToQueue(object) {
209 socket.downloadQueue[object.queueId] = object;
210 socket.emit('addToQueue', object);
211 queueDownload(getNextDownload());
214 function getNextDownload() {
215 if (socket.currentItem != null || Object.keys(socket.downloadQueue).length == 0) {
216 if (Object.keys(socket.downloadQueue).length == 0 && socket.currentItem == null) {
217 socket.emit("emptyDownloadQueue", {});
221 socket.currentItem = socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
222 return socket.currentItem;
225 function socketDownloadTrack(data){
226 if(parseInt(data.id)>0){
227 Deezer.getTrack(data.id, data.settings.maxBitrate, data.settings.fallbackBitrate, function (track, err) {
232 let queueId = "id" + Math.random().toString(36).substring(2);
234 name: track["SNG_TITLE"],
235 artist: track["ART_NAME"],
243 data.settings.trackInfo= slimDownTrackInfo(track);
244 if (track["VERSION"]) _track.name = _track.name + " " + track["VERSION"];
245 _track.settings = data.settings || {};
246 addToQueue(JSON.parse(JSON.stringify(_track)));
249 Deezer.getLocalTrack(data.id, function (track, err) {
254 let queueId = "id" + Math.random().toString(36).substring(2);
256 name: track["SNG_TITLE"],
257 artist: track["ART_NAME"],
265 data.settings.trackInfo= slimDownTrackInfo(track);
266 if (track["VERSION"]) _track.name = _track.name + " " + track["VERSION"];
267 _track.settings = data.settings || {};
268 addToQueue(JSON.parse(JSON.stringify(_track)));
272 socket.on("downloadtrack", data=>{socketDownloadTrack(data)});
274 function socketDownloadPlaylist(data){
275 Deezer.getPlaylist(data.id, function (playlist, err) {
280 let queueId = "id" + Math.random().toString(36).substring(2);
282 name: playlist["title"],
283 size: playlist.nb_tracks,
285 artist: playlist.creator.name,
290 cover: playlist["picture_small"],
292 _playlist.settings = data.settings || {};
293 Deezer.getAdvancedPlaylistTracks(data.id, function (playlist, err) {
298 _playlist.size = playlist.data.length
299 _playlist.tracks = playlist.data
300 addToQueue(JSON.parse(JSON.stringify(_playlist)));
304 socket.on("downloadplaylist", data=>{socketDownloadPlaylist(data)});
306 function socketDownloadAlbum(data){
307 Deezer.getAlbum(data.id, function (album, err) {
312 let queueId = "id" + Math.random().toString(36).substring(2);
314 name: album["title"],
315 label: album["label"],
316 artist: album["artist"].name,
317 size: album.tracks.data.length,
324 data.settings.albumInfo = slimDownAlbumInfo(album)
325 _album.settings = data.settings || {};
326 Deezer.getAdvancedAlbumTracks(data.id, function (playlist, err) {
331 _album.size = playlist.data.length
332 _album.tracks = playlist.data
333 addToQueue(JSON.parse(JSON.stringify(_album)));
337 socket.on("downloadalbum", data=>{socketDownloadAlbum(data)});
339 function socketDownloadArtist(data){
340 Deezer.getArtistAlbums(data.id, function (albums, err) {
345 (function sendAllAlbums(i) {
346 setTimeout(function () {
347 data.id = albums.data[albums.data.length-1-i].id;
348 socketDownloadAlbum(data);
349 if (--i+1) sendAllAlbums(i);
351 })(albums.data.length-1);
354 socket.on("downloadartist", data=>{socketDownloadArtist(data)});
356 socket.on("downloadspotifyplaylist", function (data) {
357 spotifyApi.clientCredentialsGrant().then(function(creds) {
358 spotifyApi.setAccessToken(creds.body['access_token']);
359 return spotifyApi.getPlaylist(data.settings.currentSpotifyUser, data.id, {fields: "id,name,owner,images,tracks(total,items(track.artists,track.name,track.album))"})
360 }).then(function(resp) {
361 let queueId = "id" + Math.random().toString(36).substring(2);
363 name: resp.body["name"],
364 artist: (resp.body["owner"]["display_name"] ? resp.body["owner"]["display_name"] : resp.body["owner"]["id"]),
365 size: resp.body["tracks"]["total"],
370 type: "spotifyplaylist",
371 cover: (resp.body["images"] ? resp.body["images"][0]["url"] : null),
372 tracks: resp.body["tracks"]["items"]
374 _playlist.settings = data.settings || {};
375 addToQueue(JSON.parse(JSON.stringify(_playlist)));
382 //currentItem: the current item being downloaded at that moment such as a track or an album
383 //downloadQueue: the tracks in the queue to be downloaded
384 //lastQueueId: the most recent queueId
385 //queueId: random number generated when user clicks download on something
386 function queueDownload(downloading) {
387 if (!downloading) return;
389 // New batch emits new message
390 if (socket.lastQueueId != downloading.queueId) {
391 if (downloading.type != "spotifyplaylist"){
392 socket.emit("downloadStarted", {queueId: downloading.queueId});
394 socket.lastQueueId = downloading.queueId;
397 logger.info(`Registered ${downloading.type}: ${downloading.id} | ${downloading.artist} - ${downloading.name}`);
398 switch(downloading.type){
400 let alternativeID = 0;
401 if (downloading.settings.trackInfo.FALLBACK)
402 if (downloading.settings.trackInfo.FALLBACK.SNG_ID)
403 alternativeID = downloading.settings.trackInfo.FALLBACK.SNG_ID;
404 downloadTrack({id: downloading.id, fallback: (alternativeID == 0 ? null : alternativeID), name: downloading.name, artist: downloading.artist, queueId: downloading.queueId}, downloading.settings, null, function (err, track) {
406 downloading.failed++;
408 downloading.downloaded++;
410 downloading.settings = null;
411 socket.emit("updateQueue", downloading);
412 socket.emit("downloadProgress", {
413 queueId: downloading.queueId,
416 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
417 socket.currentItem = null;
418 queueDownload(getNextDownload());
422 downloading.playlistContent = downloading.tracks.map((t,i) => {
424 if (t.FALLBACK.SNG_ID)
425 return {id: t.SNG_ID, fallback: t.FALLBACK.SNG_ID, name: (t.VERSION ? t.SNG_TITLE + " "+t.VERSION : t.SNG_TITLE), artist: t.ART_NAME, index: i+"", queueId: downloading.queueId}
427 return {id: t.SNG_ID, name: (t.VERSION ? t.SNG_TITLE+" "+t.VERSION : t.SNG_TITLE), artist: t.ART_NAME, index: i+"", queueId: downloading.queueId}
430 downloading.settings.albName = downloading.name;
431 downloading.settings.artName = downloading.artist;
432 downloading.errorLog = "";
433 downloading.searchedLog = "";
434 downloading.playlistArr = Array(downloading.size);
435 filePath = mainFolder;
436 if (downloading.settings.createArtistFolder || downloading.settings.createAlbumFolder) {
437 if (downloading.settings.createArtistFolder) {
438 filePath += antiDot(fixName(downloading.settings.artName)) + path.sep;
440 if (downloading.settings.createAlbumFolder) {
441 filePath += antiDot(fixName(settingsRegexAlbum(downloading.settings.foldername,downloading.settings.artName,downloading.settings.albName,downloading.settings.albumInfo.release_date.slice(0, 4),downloading.settings.albumInfo.record_type,downloading.settings.albumInfo.explicit_lyrics,downloading.settings.albumInfo.label))) + path.sep;
443 } else if (downloading.settings.artName) {
444 filePath += antiDot(fixName(settingsRegexAlbum(downloading.settings.foldername,downloading.settings.artName,downloading.settings.albName,downloading.settings.albumInfo.release_date.slice(0, 4),downloading.settings.albumInfo.record_type,downloading.settings.albumInfo.explicit_lyrics,downloading.settings.albumInfo.label))) + path.sep;
446 downloading.finished = new Promise((resolve,reject)=>{
447 downloading.playlistContent.every(function (t) {
448 socket.trackQueue.push(cb=>{
449 if (!socket.downloadQueue[downloading.queueId]) {
453 logger.info(`Now downloading: ${t.artist} - ${t.name}`)
454 downloadTrack(t, downloading.settings, null, function (err, track) {
456 downloading.downloaded++;
457 downloading.playlistArr[track.playlistData[0]] = track.playlistData[1].split(filePath)[1];
458 if (track.searched) downloading.searchedLog += `${t.artist} - ${t.name}\r\n`
460 downloading.failed++;
461 downloading.errorLog += `${t.id} | ${t.artist} - ${t.name} | ${err}\r\n`;
463 socket.emit("downloadProgress", {
464 queueId: downloading.queueId,
465 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
467 socket.emit("updateQueue", downloading);
468 if (downloading.downloaded + downloading.failed == downloading.size)
476 downloading.finished.then(()=>{
477 if (downloading.countPerAlbum) {
478 if (Object.keys(socket.downloadQueue).length > 1 && Object.keys(socket.downloadQueue)[1] == downloading.queueId) {
479 socket.downloadQueue[downloading.queueId].download = downloading.downloaded;
481 socket.emit("updateQueue", downloading);
483 logger.info("Album finished "+downloading.name);
484 socket.emit("downloadProgress", {
485 queueId: downloading.queueId,
488 if (downloading.settings.logErrors){
489 if (downloading.errorLog != ""){
490 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
491 fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
493 if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
496 if (downloading.settings.logSearched){
497 if (downloading.searchedLog != ""){
498 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
499 fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
501 if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
504 if (downloading.settings.createM3UFile){
505 fs.writeFileSync(filePath + "playlist.m3u", downloading.playlistArr.join("\r\n"));
507 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
508 socket.currentItem = null;
509 queueDownload(getNextDownload());
511 if (err) return logger.error(err.stack);
512 logger.info("Stopping the album queue");
513 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
514 socket.currentItem = null;
515 queueDownload(getNextDownload());
519 downloading.playlistContent = downloading.tracks.map((t,i) => {
521 if (t.FALLBACK.SNG_ID)
522 return {id: t.SNG_ID, fallback: t.FALLBACK.SNG_ID, name: (t.VERSION ? t.SNG_TITLE + " "+t.VERSION : t.SNG_TITLE), artist: t.ART_NAME, index: i+"", queueId: downloading.queueId}
524 return {id: t.SNG_ID, name: (t.VERSION ? t.SNG_TITLE+" "+t.VERSION : t.SNG_TITLE), artist: t.ART_NAME, index: i+"", queueId: downloading.queueId}
527 downloading.settings.plName = downloading.name;
528 downloading.errorLog = ""
529 downloading.searchedLog = "";
530 downloading.playlistArr = Array(downloading.size);
531 downloading.settings.playlist = {
532 fullSize: downloading.playlistContent.length
534 filePath = mainFolder+antiDot(fixName(downloading.settings.plName)) + path.sep
535 downloading.finished = new Promise((resolve,reject)=>{
536 downloading.playlistContent.every(function (t) {
537 socket.trackQueue.push(cb=>{
538 if (!socket.downloadQueue[downloading.queueId]) {
542 logger.info(`Now downloading: ${t.artist} - ${t.name}`)
543 downloadTrack(t, downloading.settings, null, function (err, track) {
545 downloading.downloaded++;
546 downloading.playlistArr[track.playlistData[0]] = track.playlistData[1].split(filePath)[1];
547 if (track.searched) downloading.searchedLog += `${t.artist} - ${t.name}\r\n`
549 downloading.failed++;
550 downloading.errorLog += `${t.id} | ${t.artist} - ${t.name} | ${err}\r\n`;
552 socket.emit("downloadProgress", {
553 queueId: downloading.queueId,
554 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
556 socket.emit("updateQueue", downloading);
557 if (downloading.downloaded + downloading.failed == downloading.size)
565 downloading.finished.then(()=>{
566 logger.info("Playlist finished "+downloading.name);
567 socket.emit("downloadProgress", {
568 queueId: downloading.queueId,
571 if (downloading.settings.logErrors){
572 if (downloading.errorLog != ""){
573 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
574 fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
576 if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
579 if (downloading.settings.logSearched){
580 if (downloading.searchedLog != ""){
581 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
582 fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
584 if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
587 if (downloading.settings.createM3UFile){
588 fs.writeFileSync(filePath + "playlist.m3u", downloading.playlistArr.join("\r\n"));
590 if (downloading.settings.saveArtwork){
591 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
592 let imgPath = filePath + antiDot(fixName(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name)))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
593 if (downloading.cover){
594 downloading.cover = downloading.cover.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
595 request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
597 logger.error(error.stack);
600 fs.outputFile(imgPath,body,'binary',function(err){
602 logger.error(err.stack);
605 logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
610 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
611 socket.currentItem = null;
612 queueDownload(getNextDownload());
614 if (err) return logger.error(err.stack);
615 logger.info("Stopping the playlist queue");
616 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
617 socket.currentItem = null;
618 queueDownload(getNextDownload());
621 case "spotifyplaylist":
622 spotifyApi.clientCredentialsGrant().then(function(creds) {
623 downloading.settings.plName = downloading.name;
624 downloading.playlistArr = Array(downloading.size);
625 spotifyApi.setAccessToken(creds.body['access_token']);
626 numPages=Math.floor((downloading.size-1)/100);
628 downloading.playlistContent = new Array(downloading.size);
629 downloading.tracks.map((t,i)=>{
630 downloading.playlistContent[i]=new Promise(function(resolve, reject) {
631 Deezer.track2ID(t.track.artists[0].name, t.track.name, t.track.album.name, function (response,err){
636 if (downloading.size>100){
637 for (let offset = 1; offset<=numPages; offset++){
638 pages.push(new Promise(function(resolvePage) {
639 spotifyApi.getPlaylistTracks(downloading.settings.currentSpotifyUser, downloading.id, {fields: "items(track.artists,track.name,track.album)", offset: offset*100}).then(function(resp) {
640 resp.body['items'].forEach((t, index) => {
641 downloading.playlistContent[(offset*100)+index] = new Promise(function(resolve, reject) {
642 Deezer.track2ID(t.track.artists[0].name, t.track.name, t.track.album.name, function (response,err){
652 logger.info("Waiting for all pages");
653 Promise.all(pages).then((val)=>{
654 logger.info("Waiting for all tracks to be converted");
655 return Promise.all(downloading.playlistContent)
657 if (!socket.downloadQueue[downloading.queueId]) {
658 logger.info("Stopping the playlist queue");
659 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
660 socket.currentItem = null;
661 queueDownload(getNextDownload());
664 logger.info("All tracks converted, starting download");
665 socket.emit("downloadStarted", {queueId: downloading.queueId});
666 downloading.errorLog = "";
667 downloading.searchedLog = "";
668 downloading.settings.playlist = {
669 fullSize: values.length
671 filePath = mainFolder+antiDot(fixName(downloading.settings.plName)) + path.sep
672 downloading.finished = new Promise((resolve,reject)=>{
673 values.every(function (t) {
674 t.index = values.indexOf(t)+""
675 t.queueId = downloading.queueId
676 socket.trackQueue.push(cb=>{
677 if (!socket.downloadQueue[downloading.queueId]) {
681 logger.info(`Now downloading: ${t.artist} - ${t.name}`)
682 downloadTrack(t, downloading.settings, null, function (err, track) {
684 downloading.downloaded++;
685 downloading.playlistArr[track.playlistData[0]] = track.playlistData[1].split(filePath)[1];
686 if (track.searched) downloading.searchedLog += `${t.artist} - ${t.name}\r\n`
688 downloading.failed++;
689 downloading.errorLog += `${t.id} | ${t.artist} - ${t.name} | ${err}\r\n`;
691 socket.emit("downloadProgress", {
692 queueId: downloading.queueId,
693 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
695 if (downloading.downloaded + downloading.failed == downloading.size)
697 socket.emit("updateQueue", downloading);
704 downloading.finished.then(()=>{
705 logger.info("Playlist finished "+downloading.name);
706 socket.emit("downloadProgress", {
707 queueId: downloading.queueId,
710 if (downloading.settings.logErrors){
711 if (downloading.errorLog != ""){
712 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
713 fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
715 if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
718 if (downloading.settings.logSearched){
719 if (downloading.searchedLog != ""){
720 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
721 fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
723 if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
726 if (downloading.settings.createM3UFile){
727 fs.writeFileSync(filePath + "playlist.m3u", downloading.playlistArr.join("\r\n"));
729 if (downloading.settings.saveArtwork){
730 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
731 let imgPath = filePath + antiDot(fixName(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name)))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
732 if (downloading.cover){
733 request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
735 logger.error(error.stack);
738 fs.outputFile(imgPath,body,'binary',function(err){
740 logger.error(err.stack);
743 logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
748 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
749 socket.currentItem = null;
750 queueDownload(getNextDownload());
752 if (err) return logger.error(err.stack);
753 logger.info("Stopping the playlist queue");
754 if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
755 socket.currentItem = null;
756 queueDownload(getNextDownload());
759 logger.error('Something went wrong!'+err.stack);
762 logger.error('Something went wrong!'+err.stack);
768 socket.on("getChartsCountryList", function (data) {
769 Deezer.getChartsTopCountry(function (charts, err) {
774 charts = charts.data || [];
779 for (let i = 0; i < charts.length; i++) {
781 country: charts[i].title.replace("Top ", ""),
782 picture_small: charts[i].picture_small,
783 picture_medium: charts[i].picture_medium,
784 picture_big: charts[i].picture_big
788 socket.emit("getChartsCountryList", {countries: countries, selected: data.selected});
792 function socketGetChartsTrackListByCountry(country){
794 socket.emit("getChartsTrackListByCountry", {err: "No country passed"});
797 Deezer.getChartsTopCountry(function (charts, err) {
800 charts = charts.data || [];
805 for (let i = 0; i < charts.length; i++) {
806 countries.push(charts[i].title.replace("Top ", ""));
809 if (countries.indexOf(country) == -1) {
810 socket.emit("getChartsTrackListByCountry", {err: "Country not found"});
813 let playlistId = charts[countries.indexOf(country)].id;
814 Deezer.getPlaylistTracks(playlistId, function (tracks, err) {
816 socket.emit("getChartsTrackListByCountry", {err: err});
819 socket.emit("getChartsTrackListByCountry", {
820 playlist: charts[countries.indexOf(country)],
826 socket.on("getChartsTrackListByCountry", function (data) {socketGetChartsTrackListByCountry(data.country)});
828 function socketGetMePlaylistList(){
829 logger.info("Loading Personal Playlists")
830 Deezer.getMePlaylists(function (data, err) {
835 data = data.data || [];
840 for (let i = 0; i < data.length; i++) {
842 title: data[i].title,
843 image: data[i].picture_small,
844 songs: data[i].nb_tracks,
849 if (configFile.userDefined.spotifyUser){
850 spotifyApi.clientCredentialsGrant().then(function(creds) {
851 spotifyApi.setAccessToken(creds.body['access_token']);
852 spotifyApi.getUserPlaylists(configFile.userDefined.spotifyUser, {fields: "total"}).then(data=>{
853 let total = data.body.total
854 let numPages=Math.floor((total-1)/20);
856 let playlistList = new Array(total);
857 for (let offset = 0; offset<=numPages; offset++){
858 pages.push(new Promise(function(resolvePage) {
859 spotifyApi.getUserPlaylists(configFile.userDefined.spotifyUser, {fields: "items(images,name,owner.id,tracks.total,uri)", offset: offset*20}).then(data=>{
860 data.body.items.forEach((playlist, i)=>{
861 playlistList[(offset*20)+i] = {
862 title: playlist.name,
863 image: (playlist.images[0] ? playlist.images[0].url : ""),
864 songs: playlist.tracks.total,
873 Promise.all(pages).then(()=>{
874 playlists = playlists.concat(playlistList);
875 logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`);
876 socket.emit("getMePlaylistList", {playlists: playlists});
879 logger.error(err.stack);
882 logger.error(err.stack);
885 logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`);
886 socket.emit("getMePlaylistList", {playlists: playlists});
890 socket.on("getMePlaylistList", function (d) {socketGetMePlaylistList()});
892 socket.on("search", function (data) {
893 data.type = data.type || "track";
894 if (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) data.type = "track";
896 // Remove "feat." "ft." and "&" (causes only problems)
897 data.text = data.text
898 .replace(/ feat[\.]? /g, " ")
899 .replace(/ ft[\.]? /g, " ")
900 .replace(/\(feat[\.]? /g, " ")
901 .replace(/\(ft[\.]? /g, " ")
903 .replace(/–/g, "-");
905 Deezer.search(encodeURIComponent(data.text), data.type, function (searchObject, err) {
907 socket.emit("search", {type: data.type, items: searchObject.data});
909 socket.emit("search", {type: data.type, items: []});
914 socket.on("getTrackList", function (data) {
915 if (!data.type || (["playlist", "album", "artist", "spotifyplaylist"].indexOf(data.type) == -1) || !data.id) {
916 socket.emit("getTrackList", {err: -1, response: {}, id: data.id, reqType: data.type});
919 if (data.type == 'artist') {
920 Deezer.getArtistAlbums(data.id, function (response, err) {
922 socket.emit("getTrackList", {err: "wrong id artist", response: {}, id: data.id, reqType: data.type});
925 socket.emit("getTrackList", {response: response, id: data.id, reqType: data.type});
927 }else if(data.type == "spotifyplaylist"){
928 spotyUser = data.id.slice(data.id.indexOf("user:")+5);
929 spotyUser = spotyUser.substring(0, spotyUser.indexOf(":"));
930 playlistID = data.id.slice(data.id.indexOf("playlist:")+9);
932 spotifyApi.clientCredentialsGrant().then(function(creds) {
933 spotifyApi.setAccessToken(creds.body['access_token']);
934 return spotifyApi.getPlaylistTracks(spotyUser, playlistID, {fields: "items(track(artists,name,duration_ms,preview_url,explicit)),total"})
935 }).then(function(resp) {
936 numPages=Math.floor((resp.body["total"]-1)/100);
938 let response = new Array(resp.body["total"]);
939 resp.body["items"].map((t,i)=>{
941 explicit_lyrics: t.track.explicit,
942 preview: t.track.preview_url,
945 name: t.track.artists[0].name
947 duration: Math.floor(t.track.duration_ms/1000)
950 if (resp.body["total"]>100){
951 for (let offset = 1; offset<=numPages; offset++){
952 pages.push(new Promise(function(resolvePage) {
953 spotifyApi.getPlaylistTracks(spotyUser, playlistID, {fields: "items(track(artists,name,duration_ms,preview_url,explicit))", offset: offset*100}).then(function(resp){
954 resp.body['items'].forEach((t, index) => {
955 response[index+offset*100]={
956 explicit_lyrics: t.track.explicit,
957 preview: t.track.preview_url,
960 name: t.track.artists[0].name
962 duration: Math.floor(t.track.duration_ms/1000)
970 Promise.all(pages).then((val)=>{
971 socket.emit("getTrackList", {response: {'data': response}, id: data.id, reqType: data.type});
975 let reqType = data.type.charAt(0).toUpperCase() + data.type.slice(1);
976 Deezer["get" + reqType + "Tracks"](data.id, function (response, err) {
978 socket.emit("getTrackList", {err: "wrong id "+reqType, response: {}, id: data.id, reqType: data.type});
981 socket.emit("getTrackList", {response: response, id: data.id, reqType: data.type});
986 function socketCancelDownload(queueId){
992 if (socket.downloadQueue[queueId]){
994 delete socket.downloadQueue[queueId];
996 if (socket.currentItem && socket.currentItem.queueId == queueId) {
997 cancelSuccess = Deezer.cancelDecryptTrack(queueId);
998 socket.trackQueue = queue({
1000 concurrency: socket.trackQueue.concurrency
1002 cancel = cancel || cancelSuccess;
1005 socket.emit("cancelDownload", {queueId: queueId});
1008 socket.on("cancelDownload", function (data) {socketCancelDownload(data.queueId)});
1010 socket.on("cancelAllDownloads", function(data){
1011 data.queueList.forEach(x=>{
1012 socketCancelDownload(x);
1016 socket.on("downloadAlreadyInQueue", function (data) {
1020 let isInQueue = checkIfAlreadyInQueue(data.id);
1022 socket.emit("downloadAlreadyInQueue", {alreadyInQueue: true, id: data.id, queueId: isInQueue});
1024 socket.emit("downloadAlreadyInQueue", {alreadyInQueue: false, id: data.id});
1028 socket.on("getUserSettings", function () {
1029 let settings = configFile.userDefined;
1030 if (!settings.downloadLocation) {
1031 settings.downloadLocation = mainFolder;
1033 socket.emit('getUserSettings', {settings: settings});
1036 socket.on("saveSettings", function (settings) {
1037 if (settings.userDefined.downloadLocation == defaultDownloadDir) {
1038 settings.userDefined.downloadLocation = "";
1040 settings.userDefined.downloadLocation = path.resolve(settings.userDefined.downloadLocation + path.sep) + path.sep;
1041 mainFolder = settings.userDefined.downloadLocation;
1044 if (settings.userDefined.queueConcurrency < 1) settings.userDefined.queueConcurrency = 1;
1046 if (settings.userDefined.queueConcurrency != socket.trackQueue.concurrency){
1047 socket.trackQueue.concurrency = settings.userDefined.queueConcurrency;
1050 if (settings.userDefined.chartsCountry != configFile.userDefined.chartsCountry){
1051 socket.emit("setChartsCountry", {selected: settings.userDefined.chartsCountry});
1052 socketGetChartsTrackListByCountry(settings.userDefined.chartsCountry);
1055 if (settings.userDefined.spotifyUser != configFile.userDefined.spotifyUser){
1056 socketGetMePlaylistList(settings.userDefined.spotifyUser);
1059 configFile.userDefined = settings.userDefined;
1060 fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
1062 logger.info("Settings updated");
1067 function downloadTrack(t, settings, altmetadata, callback) {
1068 if (!socket.downloadQueue[t.queueId]) {
1069 logger.error(`Failed to download ${t.artist} - ${t.name}: Not in queue`);
1070 callback(new Error("Not in queue"));
1074 logger.error(`Failed to download ${t.artist} - ${t.name}: Wrong ID`);
1075 callback(new Error("Wrong ID"));
1078 settings = settings || {};
1080 temp = new Promise((resolve, reject)=>{
1081 if (!settings.trackInfo){
1082 logger.info("Getting track data");
1083 if (parseInt(t.id)<0){
1084 Deezer.getLocalTrack(t.id, function (trackInfo, err) {
1087 logger.warn("Failed to download track, searching for alternative");
1088 Deezer.track2ID(t.artist, t.name, null, data=>{
1092 t.artist = data.artist;
1094 downloadTrack(t, settings, null, callback);
1096 logger.error(`Failed to download ${t.artist} - ${t.name}: Searched alternative; Not found`);
1097 callback(new Error("Searched alternative; Not found"));
1101 logger.error(`Failed to download ${t.artist} - ${t.name}: ${err}`);
1109 Deezer.getTrack(t.id, settings.maxBitrate, settings.fallbackBitrate, function (trackInfo, err) {
1112 logger.warn("Failed to download track, falling on alternative");
1115 downloadTrack(t, settings, null, callback);
1116 }else if(!t.searched){
1117 logger.warn("Failed to download track, searching for alternative");
1118 Deezer.track2ID(t.artist, t.name, null, data=>{
1122 t.artist = data.artist;
1124 downloadTrack(t, settings, null, callback);
1126 logger.error(`Failed to download ${t.artist} - ${t.name}: Searched alternative; Not found`);
1127 callback(new Error("Searched alternative; Not found"));
1131 logger.error(`Failed to download ${t.artist} - ${t.name}: ${err}`);
1140 resolve(settings.trackInfo);
1145 track.trackSocket = socket;
1146 temp = new Promise((resolve, reject)=>{
1147 if (parseInt(t.id)>0){
1148 if (!settings.albumInfo){
1149 logger.info("Getting album data");
1150 Deezer.getAlbum(track["ALB_ID"], function(res, err){
1152 logger.warn("Album not found, trying to reach deeper");
1153 Deezer.getAAlbum(track["ALB_ID"], function(res, err){
1156 logger.warn("Failed to download track, falling on alternative");
1159 settings.trackInfo = null;
1160 downloadTrack(t, settings, null, callback);
1161 }else if(!t.searched){
1162 logger.warn("Failed to download track, searching for alternative");
1163 Deezer.track2ID(t.artist, t.name, null, data=>{
1167 t.artist = data.artist;
1169 downloadTrack(t, settings, null, callback);
1171 logger.error(`Failed to download ${t.artist} - ${t.name}: Searched alternative album; Not found`);
1172 callback(new Error("Searched alternative album; Not found"));
1176 logger.error(`Failed to download ${t.artist} - ${t.name}: ${err}`);
1188 resolve(settings.albumInfo)
1191 resolve({artist:{}})
1194 temp.then(albumres=>{
1195 let ajson = albumres;
1196 if (ajson.totalDiskNumber){
1197 temp = new Promise((resolve, reject) =>{
1198 resolve(ajson.totalDiskNumber)
1201 if ((settings.tags.discTotal || settings.createCDFolder) && parseInt(t.id)>0){
1202 logger.info("Getting total disc number");
1203 temp = new Promise((resolve, reject) =>{
1204 Deezer.getATrack(ajson.tracks.data[ajson.tracks.data.length-1].id, function(tres){
1205 resolve(tres.disk_number);
1209 temp = new Promise((resolve, reject) =>{
1214 temp.then(discTotal=>{
1215 let totalDiskNumber = discTotal;
1216 if (settings.tags.bpm && parseInt(t.id)>0){
1217 logger.info("Getting BPM");
1218 temp = new Promise((resolve, reject) =>{
1219 Deezer.getATrack(t.id, function(tres, err){
1220 if (err) resolve(0);
1225 temp = new Promise((resolve, reject) =>{
1231 let metadata = parseMetadata(track, ajson, totalDiskNumber, settings, parseInt(t.index), altmetadata);
1232 let filename = fixName(`${metadata.artist} - ${metadata.title}`);
1233 if (settings.filename) {
1234 filename = fixName(settingsRegex(metadata, settings.filename, settings.playlist));
1236 let filepath = mainFolder;
1238 if (settings.createArtistFolder || settings.createAlbumFolder) {
1239 if(settings.plName){
1240 filepath += antiDot(fixName(settings.plName)) + path.sep;
1242 if (settings.createArtistFolder) {
1243 if(settings.artName){
1244 filepath += antiDot(fixName(settings.artName)) + path.sep;
1246 filepath += antiDot(fixName(metadata.albumArtist)) + path.sep;
1248 artistPath = filepath;
1251 if (settings.createAlbumFolder) {
1252 if(settings.artName){
1253 filepath += antiDot(fixName(settingsRegexAlbum(settings.foldername,settings.artName,settings.albName,metadata.year,metadata.rtype,metadata.albumExplicit,metadata.publisher))) + path.sep;
1255 filepath += antiDot(fixName(settingsRegexAlbum(settings.foldername,metadata.albumArtist,metadata.album,metadata.year,metadata.rtype,metadata.albumExplicit,metadata.publisher))) + path.sep;
1258 } else if (settings.plName) {
1259 filepath += antiDot(fixName(settings.plName)) + path.sep;
1260 } else if (settings.artName) {
1261 filepath += antiDot(fixName(settingsRegexAlbum(settings.foldername,settings.artName,settings.albName,metadata.year,metadata.rtype,metadata.albumExplicit,metadata.publisher))) + path.sep;
1263 let coverpath = filepath;
1264 if (metadata.discTotal > 1 && (settings.artName || settings.createAlbumFolder) && settings.createCDFolder){
1265 filepath += `CD${metadata.discNumber + path.sep}`
1268 if(track.format == 9){
1269 writePath = filepath + filename + '.flac';
1271 writePath = filepath + filename + '.mp3';
1273 if(track["LYRICS_SYNC_JSON"] && settings.syncedlyrics){
1274 let lyricsbuffer = "";
1275 for(let i=0;i<track["LYRICS_SYNC_JSON"].length;i++){
1276 if(track["LYRICS_SYNC_JSON"][i].lrc_timestamp){
1277 lyricsbuffer += track["LYRICS_SYNC_JSON"][i].lrc_timestamp+track["LYRICS_SYNC_JSON"][i].line+"\r\n";
1278 }else if(i+1 < track["LYRICS_SYNC_JSON"].length){
1279 lyricsbuffer += track["LYRICS_SYNC_JSON"][i+1].lrc_timestamp+track["LYRICS_SYNC_JSON"][i].line+"\r\n";
1282 fs.outputFile(writePath.substring(0,writePath.lastIndexOf('.'))+".lrc",lyricsbuffer,function(){});
1284 let playlistData = [0,""]
1285 if (settings.createM3UFile && (settings.plName || settings.albName)) {
1287 playlistData = [parseInt(t.index), writePath];
1289 playlistData = [metadata.trackNumber-1, writePath];
1292 if (fs.existsSync(writePath)) {
1293 logger.info("Already downloaded: " + metadata.artist + ' - ' + metadata.title);
1294 callback(null, {playlistData: playlistData, searched: t.searched});
1297 logger.info('Downloading file to ' + writePath);
1300 temp = new Promise((resolve, reject)=>{
1301 if (metadata.image) {
1303 //If its not from an album but a playlist.
1304 if(!(settings.albName || settings.createAlbumFolder)){
1305 imgPath = coverArtFolder + (metadata.barcode ? fixName(metadata.barcode) : fixName(`${metadata.albumArtist} - ${metadata.album}`))+(settings.PNGcovers ? ".png" : ".jpg");
1307 if (settings.saveArtwork)
1308 imgPath = coverpath + fixName(settingsRegexCover(settings.coverImageTemplate,settings.artName,settings.albName))+(settings.PNGcovers ? ".png" : ".jpg");
1310 imgPath = coverArtFolder + fixName(metadata.barcode ? fixName(metadata.barcode) : fixName(`${metadata.albumArtist} - ${metadata.album}`))+(settings.PNGcovers ? ".png" : ".jpg");
1312 if(fs.existsSync(imgPath)){
1313 metadata.imagePath = (imgPath).replace(/\\/g, "/");
1314 logger.info("Starting the download process CODE:1");
1317 request.get(metadata.image, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1319 logger.error(error.stack);
1320 metadata.image = undefined;
1321 metadata.imagePath = undefined;
1324 fs.outputFile(imgPath,body,'binary',function(err){
1326 logger.error(err.stack);
1327 metadata.image = undefined;
1328 metadata.imagePath = undefined;
1331 metadata.imagePath = (imgPath).replace(/\\/g, "/");
1332 logger.info("Starting the download process CODE:2");
1338 metadata.image = undefined;
1339 logger.info("Starting the download process CODE:3");
1344 temp = new Promise((resolve, reject)=>{
1345 if (metadata.artistImage && settings.saveArtworkArtist) {
1347 if(settings.createArtistFolder){
1348 imgPath = artistPath + antiDot(fixName(settingsRegexArtistCover(settings.artistImageTemplate,metadata.albumArtist)))+(settings.PNGcovers ? ".png" : ".jpg");
1349 if(fs.existsSync(imgPath)){
1352 request.get(metadata.artistImage, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1354 logger.error(error.stack);
1357 if (body.indexOf("unauthorized")>-1) return resolve();
1358 fs.outputFile(imgPath,body,'binary',function(err){
1360 logger.error(err.stack);
1363 logger.info("Saved Artist Image");
1377 if(parseInt(t.id)>0)
1378 tempPath = writePath+".temp"
1380 tempPath = writePath;
1381 logger.info("Downloading and decrypting");
1382 Deezer.decryptTrack(tempPath, track, t.queueId, function (err) {
1383 if (err && err.message == "aborted") {
1384 logger.info("Track got aborted");
1385 t.trackSocket = null
1386 callback(null, {playlistData: playlistData, searched: t.searched});
1391 logger.warn("Failed to download: " + metadata.artist + " - " + metadata.title+", falling on alternative");
1394 settings.trackInfo = null;
1395 downloadTrack(t, settings, metadata, callback);
1396 }else if(!t.searched){
1397 logger.warn("Failed to download track, searching for alternative");
1398 Deezer.track2ID(t.artist, t.name, null, data=>{
1401 t.artist = data.artist;
1403 downloadTrack(t, settings, metadata, callback);
1406 logger.error(`Failed to download ${t.artist} - ${t.name}: ${err}`);
1411 logger.info("Downloaded: " + metadata.artist + " - " + metadata.title);
1412 if (parseInt(t.id)>0){
1413 if(track.format == 9){
1414 let flacComments = [];
1415 if (settings.tags.title)
1416 flacComments.push('TITLE=' + metadata.title);
1417 if (settings.tags.album)
1418 flacComments.push('ALBUM=' + metadata.album);
1419 if (settings.tags.albumArtist)
1420 flacComments.push('ALBUMARTIST=' + metadata.albumArtist);
1421 if (settings.tags.trackNumber)
1422 flacComments.push('TRACKNUMBER=' + metadata.trackNumber);
1423 if (settings.tags.discNumber)
1424 flacComments.push('DISCNUMBER=' + metadata.discNumber);
1425 if (settings.tags.trackTotal)
1426 flacComments.push('TRACKTOTAL=' + metadata.trackTotal);
1427 if (settings.tags.explicit)
1428 flacComments.push('ITUNESADVISORY=' + metadata.explicit);
1429 if (settings.tags.isrc)
1430 flacComments.push('ISRC=' + metadata.ISRC);
1431 if (settings.tags.artist && metadata.artists)
1432 metadata.artists.forEach(x=>{
1433 flacComments.push('ARTIST=' + x);
1435 if (settings.tags.discTotal)
1436 flacComments.push('DISCTOTAL='+splitNumber(metadata.discTotal,true));
1437 if (settings.tags.length)
1438 flacComments.push('LENGTH=' + metadata.length);
1439 if (settings.tags.barcode && metadata.barcode)
1440 flacComments.push('BARCODE=' + metadata.barcode);
1441 if (metadata.unsynchronisedLyrics && settings.tags.unsynchronisedLyrics)
1442 flacComments.push('LYRICS='+metadata.unsynchronisedLyrics.lyrics);
1443 if (metadata.genre && settings.tags.genre)
1444 metadata.genre.forEach(x=>{
1445 flacComments.push('GENRE=' + x);
1447 if (metadata.copyright && settings.tags.copyright)
1448 flacComments.push('COPYRIGHT=' + metadata.copyright);
1449 if (0 < parseInt(metadata.year)){
1450 if (settings.tags.year)
1451 flacComments.push('YEAR=' + metadata.year);
1452 if (settings.tags.date)
1453 flacComments.push('DATE=' + metadata.date);
1455 if (0 < parseInt(metadata.bpm) && settings.tags.bpm)
1456 flacComments.push('BPM=' + metadata.bpm);
1457 if(metadata.publisher && settings.tags.publisher)
1458 flacComments.push('PUBLISHER=' + metadata.publisher);
1459 if(metadata.composer && settings.tags.composer)
1460 metadata.composer.forEach(x=>{
1461 flacComments.push('COMPOSER=' + x);
1463 if(metadata.musicpublisher && settings.tags.musicpublisher)
1464 metadata.musicpublisher.forEach(x=>{
1465 flacComments.push('ORGANIZATION=' + x);
1467 if(metadata.mixer && settings.tags.mixer)
1468 metadata.mixer.forEach(x=>{
1469 flacComments.push('MIXER=' + x);
1471 if(metadata.author && settings.tags.author)
1472 metadata.author.forEach(x=>{
1473 flacComments.push('AUTHOR=' + x);
1475 if(metadata.writer && settings.tags.writer)
1476 metadata.writer.forEach(x=>{
1477 flacComments.push('WRITER=' + x);
1479 if(metadata.engineer && settings.tags.engineer)
1480 metadata.engineer.forEach(x=>{
1481 flacComments.push('ENGINEER=' + x);
1483 if(metadata.producer && settings.tags.producer)
1484 metadata.producer.forEach(x=>{
1485 flacComments.push('PRODUCER=' + x);
1487 if(metadata.replayGain && settings.tags.replayGain)
1488 flacComments.push('REPLAYGAIN_TRACK_GAIN=' + metadata.replayGain);
1490 const reader = fs.createReadStream(tempPath);
1491 const writer = fs.createWriteStream(writePath);
1492 let processor = new mflac.Processor({parseMetaDataBlocks: true});
1493 let vendor = 'reference libFLAC 1.2.1 20070917';
1495 if(metadata.imagePath && settings.tags.cover){
1496 cover = fs.readFileSync(metadata.imagePath);
1498 let mdbVorbisPicture;
1499 let mdbVorbisComment;
1500 processor.on('preprocess', function(mdb){
1501 // Remove existing VORBIS_COMMENT and PICTURE blocks, if any.
1502 if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type) {
1504 } else if (mflac.Processor.MDB_TYPE_PICTURE === mdb.type) {
1508 mdbVorbisComment = mflac.data.MetaDataBlockVorbisComment.create(false, vendor, flacComments);
1509 processor.push(mdbVorbisComment.publish());
1511 mdbVorbisPicture = mflac.data.MetaDataBlockPicture.create(false, 3, `image/${(settings.PNGcovers ? "png" : "jpeg")}`, '', settings.artworkSize, settings.artworkSize, 24, 0, cover);
1512 processor.push(mdbVorbisPicture.publish());
1516 processor.on('postprocess', (mdb) => {
1517 if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type && null !== mdb.vendor) {
1518 vendor = mdb.vendor;
1521 reader.on('end', () => {
1522 fs.remove(tempPath);
1524 reader.pipe(processor).pipe(writer);
1526 const songBuffer = fs.readFileSync(tempPath);
1527 const writer = new ID3Writer(songBuffer);
1528 if (settings.tags.title)
1529 writer.setFrame('TIT2', metadata.title);
1530 if (settings.tags.artist)
1531 writer.setFrame('TPE1', [metadata.artists]);
1532 if (settings.tags.album)
1533 writer.setFrame('TALB', metadata.album)
1534 if (settings.tags.albumArtist && metadata.albumArtist)
1535 writer.setFrame('TPE2', metadata.albumArtist)
1536 if (settings.tags.trackNumber)
1537 writer.setFrame('TRCK', (settings.tags.trackTotal ? metadata.trackNumber+"/"+metadata.trackTotal : metadata.trackNumber))
1538 if (settings.tags.discNumber)
1539 writer.setFrame('TPOS', (settings.tags.discTotal ? metadata.discNumber+"/"+metadata.discTotal : metadata.discNumber))
1540 if (settings.tags.isrc)
1541 writer.setFrame('TSRC', metadata.ISRC);
1543 if (settings.tags.length)
1544 writer.setFrame('TLEN', metadata.length);
1545 if (settings.tags.barcode && metadata.barcode)
1546 writer.setFrame('TXXX', {
1547 description: 'BARCODE',
1548 value: metadata.barcode
1550 if(metadata.imagePath && settings.tags.cover){
1551 const coverBuffer = fs.readFileSync(metadata.imagePath);
1552 writer.setFrame('APIC', {
1558 if(metadata.unsynchronisedLyrics && settings.tags.unsynchronisedLyrics)
1559 writer.setFrame('USLT', metadata.unsynchronisedLyrics);
1560 if(metadata.publisher && settings.tags.publisher)
1561 writer.setFrame('TPUB', metadata.publisher);
1562 if(metadata.genre && settings.tags.genre)
1563 writer.setFrame('TCON', [metadata.genre]);
1564 if(metadata.copyright && settings.tags.copyright)
1565 writer.setFrame('TCOP', metadata.copyright);
1566 if (0 < parseInt(metadata.year)) {
1567 if (settings.tags.date)
1568 writer.setFrame('TDAT', metadata.date);
1569 if (settings.tags.year)
1570 writer.setFrame('TYER', metadata.year);
1572 if (0 < parseInt(metadata.bpm) && settings.tags.bpm)
1573 writer.setFrame('TBPM', metadata.bpm);
1574 if(metadata.composer && settings.tags.composer)
1575 writer.setFrame('TCOM', [metadata.composer]);
1576 if(metadata.replayGain && settings.tags.replayGain)
1577 writer.setFrame('TXXX', {
1578 description: 'REPLAYGAIN_TRACK_GAIN',
1579 value: metadata.replayGain
1582 const taggedSongBuffer = Buffer.from(writer.arrayBuffer);
1583 fs.writeFileSync(writePath, taggedSongBuffer);
1584 fs.remove(tempPath);
1587 callback(null, {playlistData: playlistData, searched: t.searched});
1597 function checkIfAlreadyInQueue(id) {
1599 Object.keys(socket.downloadQueue).forEach(x=>{
1600 if (socket.downloadQueue[x].id == id) {
1601 exists = socket.downloadQueue[i].queueId;
1604 if (socket.currentItem && (socket.currentItem.id == id)) {
1605 exists = socket.currentItem.queueId;
1614 * Updates individual parameters in the settings file
1618 function updateSettingsFile(config, value) {
1619 configFile.userDefined[config] = value;
1621 fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
1623 logger.info("Settings updated");
1625 // FIXME: Endless Loop, due to call from initFolders()...crashes soon after startup
1630 function fixName (txt) {
1631 const regEx = /[\0\/\\:*?"<>|]/g;
1632 return txt.replace(regEx, '_');
1635 function antiDot(str){
1636 while(str[str.length-1] == "." || str[str.length-1] == " " || str[str.length-1] == "\n"){
1637 str = str.substring(0,str.length-1);
1642 return fixName(str);
1646 * Initialize the temp folder for covers and main folder for downloads
1648 function initFolders() {
1649 // Check if main folder exists
1650 if (!fs.existsSync(mainFolder)) {
1651 mainFolder = defaultDownloadDir;
1652 updateSettingsFile('downloadLocation', defaultDownloadDir);
1654 //fs.removeSync(coverArtFolder);
1655 fs.ensureDirSync(coverArtFolder);
1659 * Creates the name of the tracks replacing wildcards to correct metadata
1663 * @returns {XML|string|*}
1665 function settingsRegex(metadata, filename, playlist) {
1666 filename = filename.replace(/%title%/g, metadata.title);
1667 filename = filename.replace(/%album%/g, metadata.album);
1668 filename = filename.replace(/%artist%/g, metadata.artist);
1669 filename = filename.replace(/%year%/g, metadata.year);
1670 filename = filename.replace(/%label%/g, metadata.publisher);
1671 if(typeof metadata.trackNumber != 'undefined'){
1672 if(configFile.userDefined.padtrck){
1673 filename = filename.replace(/%number%/g, pad(metadata.trackNumber, metadata.trackTotal));
1675 filename = filename.replace(/%number%/g, metadata.trackNumber);
1678 filename = filename.replace(/%number%/g, '');
1680 filename = filename.replace(/%explicit%/g, (metadata.explicit ? (filename.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit Version)") : ""));
1681 return filename.trim();
1685 * Creates the name of the albums folder replacing wildcards to correct metadata
1688 * @returns {XML|string|*}
1690 function settingsRegexAlbum(foldername, artist, album, year, rtype, explicit, publisher) {
1691 foldername = foldername.replace(/%album%/g, album);
1692 foldername = foldername.replace(/%artist%/g, artist);
1693 foldername = foldername.replace(/%year%/g, year);
1695 foldername = foldername.replace(/%type%/g, rtype[0].toUpperCase() + rtype.substring(1));
1697 foldername = foldername.replace(/%type%/g, "");
1699 foldername = foldername.replace(/%label%/g, publisher);
1700 foldername = foldername.replace(/%explicit%/g, (explicit ? (foldername.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit)") : ""));
1701 return foldername.trim();
1704 function settingsRegexCover(foldername, artist, name) {
1705 foldername = foldername.replace(/%name%/g, name);
1706 foldername = foldername.replace(/%artist%/g, artist);
1710 function settingsRegexArtistCover(foldername, artist) {
1711 foldername = foldername.replace(/%artist%/g, artist);
1716 * I really don't understand what this does ... but it does something
1719 * @returns {String|string|*}
1721 function pad(str, max) {
1722 str = str.toString();
1723 max = max.toString();
1724 return str.length < max.length || str.length == 1 ? pad("0" + str, max) : str;
1728 * Splits the %number%
1732 function splitNumber(str,total){
1733 str = str.toString();
1734 let i = str.indexOf("/");
1736 return str.slice(i+1, str.length);
1738 return str.slice(0, i);
1742 return i > 0 ? str.slice(0, i) : str;
1745 function slimDownTrackInfo(trackOld){
1747 track['SNG_ID'] = trackOld["SNG_ID"]
1748 track['ARTISTS'] = trackOld["ARTISTS"]
1749 track["ALB_ID"] = trackOld["ALB_ID"]
1750 track["ALB_PICTURE"] = trackOld["ALB_PICTURE"]
1751 track["ART_PICTURE"] = trackOld["ART_PICTURE"]
1752 track["ALB_TITLE"] = trackOld["ALB_TITLE"]
1753 track["ART_NAME"] = trackOld["ART_NAME"]
1754 track["BPM"] = trackOld["BPM"]
1755 track["COPYRIGHT"] = trackOld["COPYRIGHT"]
1756 track["DISK_NUMBER"] = trackOld["DISK_NUMBER"]
1757 track["DURATION"] = trackOld["DURATION"]
1758 track["EXPLICIT_LYRICS"] = trackOld["EXPLICIT_LYRICS"]
1759 track["GAIN"] = trackOld["GAIN"]
1760 track["ISRC"] = trackOld["ISRC"]
1761 track["TYPE"] = trackOld["TYPE"]
1762 track["LYRICS_SYNC_JSON"] = trackOld["LYRICS_SYNC_JSON"]
1763 track["LYRICS_TEXT"] = trackOld["LYRICS_TEXT"]
1764 track["PHYSICAL_RELEASE_DATE"] = trackOld["PHYSICAL_RELEASE_DATE"]
1765 track["SNG_CONTRIBUTORS"] = trackOld["SNG_CONTRIBUTORS"]
1766 track["SNG_TITLE"] = trackOld["SNG_TITLE"]
1767 track["TRACK_NUMBER"] = trackOld["TRACK_NUMBER"]
1768 track["VERSION"] = trackOld["VERSION"]
1769 track["FILESIZE_FLAC"] = trackOld["FILESIZE_FLAC"]
1770 track["FILESIZE_MP3_320"] = trackOld["FILESIZE_MP3_320"]
1771 track["FILESIZE_MP3_256"] = trackOld["FILESIZE_MP3_256"]
1772 track["FILESIZE_MP3_128"] = trackOld["FILESIZE_MP3_128"]
1773 track.FILESIZE = trackOld.FILESIZE
1774 track["FALLBACK"] = trackOld["FALLBACK"]
1775 track.downloadUrl = trackOld.downloadUrl
1776 track.format = trackOld.format
1780 function slimDownAlbumInfo(ajsonOld){
1783 ajson.artist.name = ajsonOld.artist.name
1784 ajson.artist.picture_small = ajsonOld.artist.picture_small
1785 ajson.nb_tracks = ajsonOld.nb_tracks
1786 ajson.upc = ajsonOld.upc
1787 ajson.record_type = ajsonOld.record_type
1788 ajson.label = ajsonOld.label
1789 ajson.genres = ajsonOld.genres
1790 ajson.explicit_lyrics = ajsonOld.explicit_lyrics
1791 ajson.release_date = ajsonOld.release_date
1793 data: ajsonOld.tracks.data.map(x=>{
1797 ajson.tracks.total = ajsonOld.tracks.total
1801 function swichReleaseType(id){
1814 function parseMetadata(track, ajson, totalDiskNumber, settings, position, altmetadata){
1816 if (track["VERSION"]) track["SNG_TITLE"] += " " + track["VERSION"];
1817 if (settings.removeAlbumVersion){
1818 if(track["SNG_TITLE"].indexOf("Album Version")>-1){
1819 track["SNG_TITLE"] = track["SNG_TITLE"].replace(/\(Album Version\)/g,"")
1820 track["SNG_TITLE"].trim()
1824 metadata = altmetadata;
1825 if(track["LYRICS_TEXT"] && !metadata.unsynchronisedLyrics){
1826 metadata.unsynchronisedLyrics = {
1828 lyrics: track["LYRICS_TEXT"]
1833 title: track["SNG_TITLE"],
1834 artist: track["ART_NAME"],
1835 album: track["ALB_TITLE"],
1836 trackNumber: track["TRACK_NUMBER"],
1837 discNumber: track["DISK_NUMBER"],
1838 explicit: track["EXPLICIT_LYRICS"],
1839 ISRC: track["ISRC"],
1840 albumArtist: ajson.artist.name,
1841 trackTotal: ajson.nb_tracks,
1842 rtype: ajson.record_type,
1844 length: track["DURATION"]
1846 if(track["COPYRIGHT"]){
1847 metadata.copyright = track["COPYRIGHT"];
1849 if (!metadata.rtype){
1850 metadata.rtype = swichReleaseType(track["TYPE"])
1852 if (ajson.explicit_lyrics){
1853 metadata.albumExplicit = ajson.explicit_lyrics;
1855 if(track["SNG_CONTRIBUTORS"]){
1856 if(track["SNG_CONTRIBUTORS"].composer){
1857 metadata.composer = [];
1858 Array.from(new Set(track["SNG_CONTRIBUTORS"].composer)).forEach(function(x){
1859 if(metadata.composer.indexOf(x) == -1)
1860 metadata.composer.push(x);
1862 let separator = settings.multitagSeparator;
1863 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1864 if (track.format != 9) metadata.composer = metadata.composer.join(separator);
1866 if(track["SNG_CONTRIBUTORS"].musicpublisher){
1867 metadata.musicpublisher = [];
1868 Array.from(new Set(track["SNG_CONTRIBUTORS"].musicpublisher)).forEach(function(x){
1869 if(metadata.musicpublisher.indexOf(x) == -1)
1870 metadata.musicpublisher.push(x);
1872 let separator = settings.multitagSeparator;
1873 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1874 if (track.format != 9) metadata.musicpublisher = metadata.musicpublisher.join(separator);
1876 if(track["SNG_CONTRIBUTORS"].producer){
1877 metadata.producer = [];
1878 Array.from(new Set(track["SNG_CONTRIBUTORS"].producer)).forEach(function(x){
1879 if(metadata.producer.indexOf(x) == -1)
1880 metadata.producer.push(x);
1882 let separator = settings.multitagSeparator;
1883 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1884 if (track.format != 9) metadata.producer = metadata.producer.join(separator);
1886 if(track["SNG_CONTRIBUTORS"].engineer){
1887 metadata.engineer = [];
1888 Array.from(new Set(track["SNG_CONTRIBUTORS"].engineer)).forEach(function(x){
1889 if(metadata.engineer.indexOf(x) == -1)
1890 metadata.engineer.push(x);
1892 let separator = settings.multitagSeparator;
1893 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1894 if (track.format != 9) metadata.engineer = metadata.engineer.join(separator);
1896 if(track["SNG_CONTRIBUTORS"].writer){
1897 metadata.writer = [];
1898 Array.from(new Set(track["SNG_CONTRIBUTORS"].writer)).forEach(function(x){
1899 if(metadata.writer.indexOf(x) == -1)
1900 metadata.writer.push(x);
1902 let separator = settings.multitagSeparator;
1903 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1904 if (track.format != 9) metadata.writer = metadata.writer.join(separator);
1906 if(track["SNG_CONTRIBUTORS"].author){
1907 metadata.author = [];
1908 Array.from(new Set(track["SNG_CONTRIBUTORS"].author)).forEach(function(x){
1909 if(metadata.author.indexOf(x) == -1)
1910 metadata.author.push(x);
1912 let separator = settings.multitagSeparator;
1913 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1914 if (track.format != 9) metadata.author = metadata.author.join(separator);
1916 if(track["SNG_CONTRIBUTORS"].mixer){
1917 metadata.mixer = [];
1918 Array.from(new Set(track["SNG_CONTRIBUTORS"].mixer)).forEach(function(x){
1919 if(metadata.mixer.indexOf(x) == -1)
1920 metadata.mixer.push(x);
1922 let separator = settings.multitagSeparator;
1923 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
1924 if (track.format != 9) metadata.mixer = metadata.mixer.join(separator);
1927 if(track["LYRICS_TEXT"]){
1928 metadata.unsynchronisedLyrics = {
1930 lyrics: track["LYRICS_TEXT"]
1933 if (track["GAIN"]) {
1934 metadata.replayGain = track["GAIN"];
1937 metadata.publisher = ajson.label;
1939 if (0 < parseInt(track["BPM"])) {
1940 metadata.bpm = track["BPM"];
1942 let separator = settings.multitagSeparator;
1943 if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16))
1944 if(track['ARTISTS']){
1945 metadata.artists = [];
1947 track['ARTISTS'].forEach(function(artist){
1948 artistArray.push(artist['ART_NAME']);
1950 Array.from(new Set(artistArray)).forEach(function(artist){
1951 if(metadata.artists.indexOf(artist) == -1)
1952 metadata.artists.push(artist);
1954 if (track.format != 9) metadata.artists = metadata.artists.join(separator);
1956 if(ajson.genres && ajson.genres.data[0] && ajson.genres.data[0].name){
1957 metadata.genre = [];
1959 ajson.genres.data.forEach(function(genre){
1960 genreArray.push(genre.name);
1962 Array.from(new Set(genreArray)).forEach(function(genre){
1963 if(metadata.genre.indexOf(genre) == -1)
1964 metadata.genre.push(genre);
1966 if (track.format != 9) metadata.genre = metadata.genre.join(separator);
1968 if (track["ALB_PICTURE"]) {
1969 metadata.image = Deezer.albumPicturesHost + track["ALB_PICTURE"]+"/"+settings.artworkSize+"x"+settings.artworkSize+"-000000-80-0-0"+(settings.PNGcovers ? ".png" : ".jpg");
1971 if (ajson.artist.picture_small) {
1972 metadata.artistImage = ajson.artist.picture_small.split("56x56-000000-80-0-0.jpg")[0]+settings.artworkSize+"x"+settings.artworkSize+"-000000-80-0-0"+(settings.PNGcovers ? ".png" : ".jpg");
1974 if (ajson.release_date) {
1975 metadata.year = ajson.release_date.slice(0, 4);
1977 day: ajson.release_date.slice(5,7),
1978 month: ajson.release_date.slice(8,10),
1979 year: (settings.dateFormatYear == "2" ? ajson.release_date.slice(2, 4) : ajson.release_date.slice(0, 4))
1981 } else if(track["PHYSICAL_RELEASE_DATE"]){
1982 metadata.year = track["PHYSICAL_RELEASE_DATE"].slice(0, 4);
1984 day: track["PHYSICAL_RELEASE_DATE"].slice(5,7),
1985 month: track["PHYSICAL_RELEASE_DATE"].slice(8,10),
1986 year: (settings.dateFormatYear == "2" ? track["PHYSICAL_RELEASE_DATE"].slice(2, 4) : track["PHYSICAL_RELEASE_DATE"].slice(0, 4))
1991 switch (settings.dateFormat){
1992 case "0": date = `${metadata.date.year}-${metadata.date.month}-${metadata.date.day}`; break;
1993 case "1": date = `${metadata.date.day}-${metadata.date.month}-${metadata.date.year}`; break;
1994 case "2": date = `${metadata.date.month}-${metadata.date.day}-${metadata.date.year}`; break;
1995 case "3": date = `${metadata.date.year}-${metadata.date.day}-${metadata.date.month}`; break;
1996 default: date = `${metadata.date.day}-${metadata.date.month}-${metadata.date.year}`; break;
1998 metadata.date = date;
2000 if(settings.plName && !(settings.createArtistFolder || settings.createAlbumFolder) && !settings.numplaylistbyalbum){
2001 metadata.trackNumber = (position+1).toString();
2002 metadata.trackTotal = settings.playlist.fullSize;
2003 metadata.discNumber = "1";
2004 metadata.discTotal = "1";
2006 if (totalDiskNumber){
2007 metadata.discTotal = totalDiskNumber;
2013 process.on('unhandledRejection', function (err) {
2014 logger.error(err.stack);
2016 // Show crash error in console for debugging
2017 process.on('uncaughtException', function (err) {
2018 logger.error(err.stack);
2022 module.exports.mainFolder = mainFolder;
2023 module.exports.defaultSettings = defaultSettings;
2024 module.exports.defaultDownloadDir = defaultDownloadDir;