Added date variable to tracks
[DeezloaderRemix.git] / app / app.js
1 /*
2 *   _____                _                 _             _____                _
3 *  |  __ \              | |               | |           |  __ \              (_)
4 *  | |  | | ___  ___ ___| | ___   __ _  __| | ___ _ __  | |__) |___ _ __ ___  ___  __
5 *  | |  | |/ _ \/ _ \_  / |/ _ \ / _` |/ _` |/ _ \ '__| |  _  // _ \ '_ ` _ \| \ \/ /
6 *  | |__| |  __/  __// /| | (_) | (_| | (_| |  __/ |    | | \ \  __/ | | | | | |>  <
7 *  |_____/ \___|\___/___|_|\___/ \__,_|\__,_|\___|_|    |_|  \_\___|_| |_| |_|_/_/\_\
8 *
9 **/
10
11 // Server stuff
12 const express = require('express')
13 const app = express()
14 const server = require('http').createServer(app)
15 const io = require('socket.io').listen(server, {log: false, wsEngine: 'ws'})
16 // Music tagging stuff
17 const mflac = require('./lib/flac-metadata')
18 const ID3Writer = require('./lib/browser-id3-writer')
19 const deezerApi = require('deezer-api')
20 const spotifyApi = require('spotify-web-api-node')
21 // App stuff
22 const fs = require('fs-extra')
23 const async = require('async')
24 const request = require('request-promise')
25 const requestOld = require('request')
26 const os = require('os')
27 const path = require('path')
28 const logger = require('./utils/logger.js')
29 const queue = require('queue')
30 const localpaths = require('./utils/localpaths.js')
31 const package = require('./package.json')
32 const stq = require('sequential-task-queue')
33
34 // Main Constants
35 // Files
36 const configFileLocation = localpaths.user+"config.json"
37 // Folders
38 var coverArtFolder = 'deezloader-imgs' + path.sep
39 var defaultDownloadFolder = path.sep + 'Deezloader Music' + path.sep
40
41 if(process.platform == "android"){
42         coverArtFolder = localpaths.user + coverArtFolder
43         fs.ensureFileSync(coverArtFolder + '.nomedia');
44         defaultDownloadFolder = localpaths.home + path.sep + 'Music' + defaultDownloadFolder
45 }else{
46         coverArtFolder = os.tmpdir() + path.sep + coverArtFolder
47         defaultDownloadFolder = localpaths.home + defaultDownloadFolder
48 }
49
50 // Default settings
51 const defaultSettings = require('./default.json').userDefined
52 // Spotify Files
53 const spotifySupport = fs.existsSync(localpaths.user+"authCredentials.js")
54 if (spotifySupport){
55         var authCredentials = require(localpaths.user+'authCredentials.js')
56         var Spotify = new spotifyApi(authCredentials)
57 }
58
59 // Setup the folders START
60 var mainFolder = defaultDownloadFolder
61
62 // First run, create config file
63 if(!fs.existsSync(configFileLocation)){
64         logger.info("Can't find config.json, creating one now!")
65         fs.outputFileSync(configFileLocation, fs.readFileSync(__dirname+path.sep+"default.json",'utf8'))
66 }
67
68 // See if all settings are there after update
69 var configFile = require(configFileLocation);
70 for (let x in defaultSettings){
71         if (typeof configFile.userDefined[x] != typeof defaultSettings[x]){
72                 configFile.userDefined[x] = defaultSettings[x]
73         }
74 }
75 // Set default download folder if not userDefined
76 if (configFile.userDefined.downloadLocation != "") {
77         mainFolder = configFile.userDefined.downloadLocation
78 }
79
80 initFolders();
81
82 // Route and create server
83 app.use('/', express.static(__dirname + '/public/'))
84 server.listen(configFile.serverPort)
85 logger.info('Server is running @ localhost:' + configFile.serverPort)
86
87 var dqueue = new stq.SequentialTaskQueue()
88 var downloadQueue = {}
89 var trackQueue = queue({
90         autostart: true
91 })
92 trackQueue.concurrency = configFile.userDefined.queueConcurrency
93
94 // START sockets clusterfuck
95 io.sockets.on('connection', function (s) {
96         logger.info("Connection received!")
97
98         // Check for updates
99         request({
100                 url: "https://notabug.org/RemixDevs/DeezloaderRemix/raw/master/update.json",
101                 rejectUnauthorized: false,
102                 json: true
103         })
104         .then(body=>{
105                 logger.info("Checking for updates")
106                 let [currentVersion_MAJOR, currentVersion_MINOR, currentVersion_PATCH] = package.version.split(".").map(x=>parseInt(x))
107                 let [lastVersion_MAJOR, lastVersion_MINOR, lastVersion_PATCH] = body.version.split(".").map(x=>parseInt(x))
108                 if (
109                         lastVersion_MAJOR>currentVersion_MAJOR ||
110                         lastVersion_MAJOR==currentVersion_MAJOR && lastVersion_MINOR>currentVersion_MINOR ||
111                         lastVersion_MAJOR==currentVersion_MAJOR && lastVersion_MINOR==currentVersion_MINOR && lastVersion_PATCH>currentVersion_PATCH
112                 ){
113                         logger.info("Update Available")
114                         s.emit("messageUpdate", {title: `Version ${lastVersion_MAJOR}.${lastVersion_MINOR}.${lastVersion_PATCH} is available!`, msg: body.changelog, lastVersion: body.version})
115                 }else{
116                         logger.info("Running the latest version!")
117                 }
118         })
119         .catch(error=>{
120                 logger.error(`UpdateCheck failed: ${error.stack ? error.stack : error}`)
121         })
122
123         // Connection dependet variables
124         s.Deezer = new deezerApi()
125         s.spotifyUser = null
126
127         s.emit("checkAutologin")
128         s.emit("getDefaultSettings", defaultSettings, defaultDownloadFolder)
129         s.emit("populateDownloadQueue", downloadQueue)
130
131         if(process.platform != "android"){
132                 const captcha = require('./utils/captcha');
133                 captcha.callbackResponse = function (data) {
134                         s.emit("getCaptcha", data)
135                 };
136         }
137
138         // Function for logging in
139         s.on("login", async function (username, password, captchaResponse) {
140                 try{
141                         logger.info("Logging in");
142                         await s.Deezer.login(username, password, captchaResponse)
143                         s.emit("login", {user: s.Deezer.user})
144                         logger.info("Logged in successfully")
145                         // Save session login so next time login is not needed
146                         // This is the same method used by the official website
147                         s.emit('getCookies', s.Deezer.getCookies())
148                 }catch(err){
149                         s.emit("login", {error: err.message})
150                         logger.error(`Login failed: ${err.message}`)
151                 }
152         })
153
154         // Function for login with userToken
155         s.on("loginViaUserToken", async function (userToken) {
156                 try{
157                         logger.info("Logging in");
158                         await s.Deezer.loginViaArl(userToken)
159                         s.emit("login", {user: s.Deezer.user})
160                         logger.info("Logged in successfully")
161                         s.emit('getCookies', s.Deezer.getCookies())
162                 }catch(err){
163                         s.emit("login", {error: err.message})
164                         logger.error(`Login failed: ${err.message}`)
165                 }
166         });
167
168         // Function for autologin
169         s.on("autologin", async function(jar, email){
170                 try{
171                         await s.Deezer.loginViaCookies(jar, email)
172                         s.emit('login', {user: s.Deezer.user})
173                 }catch(err){
174                         s.emit('login', {error: err.message})
175                         logger.error(`Autologin failed: ${err.message}`)
176                         return
177                 }
178         })
179
180         // Function for logout
181         s.on("logout", function(){
182                 logger.info("Logged out")
183                 // Creating new object to clear the cookies
184                 s.Deezer = new deezerApi()
185                 return
186         })
187
188         // Returns list of charts available
189         s.on("getChartsCountryList", async function (data) {
190                 try{
191                         let charts = await s.Deezer.legacyGetChartsTopCountry()
192                         charts = charts.data || []
193                         let countries = []
194                         for (let i = 0; i < charts.length; i++) {
195                                 let obj = {
196                                         country: charts[i].title.replace("Top ", ""),
197                                         picture_small: charts[i].picture_small,
198                                         picture_medium: charts[i].picture_medium,
199                                         picture_big: charts[i].picture_big,
200                                         playlistId: charts[i].id
201                                 }
202                                 countries.push(obj)
203                         }
204                         s.emit("getChartsCountryList", {countries: countries, selected: data.selected})
205                 }catch(err){
206                         logger.error(`getChartsCountryList failed: ${err.stack}`)
207                         return
208                 }
209         })
210
211         // Returns chart tracks from Playlist ID
212         async function getChartsTrackListById(playlistId){
213                 if (typeof playlistId === 'undefined') {
214                         s.emit("getChartsTrackListByCountry", {err: "Can't find that playlist"})
215                         return
216                 }
217                 try{
218                         let tracks = await s.Deezer.legacyGetPlaylistTracks(playlistId)
219                         s.emit("getChartsTrackListByCountry", {
220                                 playlistId: playlistId,
221                                 tracks: tracks.data
222                         })
223                 }catch(err){
224                         s.emit("getChartsTrackListByCountry", {err: err})
225                         logger.error(`getChartsTrackListById failed: ${err.stack}`)
226                         return
227                 }
228         }
229
230         // Returns chart tracks from country name
231         async function getChartsTrackListByCountry(country){
232                 if (typeof country === 'undefined') {
233                         s.emit("getChartsTrackListByCountry", {err: "No country passed"})
234                         return
235                 }
236                 try{
237                         let charts = await s.Deezer.legacyGetChartsTopCountry()
238                         charts = charts.data || []
239                         let countries = []
240                         for (let i = 0; i < charts.length; i++) {
241                                 countries.push(charts[i].title.replace("Top ", ""))
242                         }
243                         if (countries.indexOf(country) == -1) {
244                                 s.emit("getChartsTrackListByCountry", {err: "Country not found"})
245                                 return
246                         }
247                         let playlistId = charts[countries.indexOf(country)].id
248                         await getChartsTrackListById(playlistId)
249                 }catch(err){
250                         logger.error(`getChartsTrackListByCountry failed: ${err.stack}`)
251                         return
252                 }
253         }
254         s.on("getChartsTrackListByCountry", function (data) {getChartsTrackListByCountry(data.country)})
255
256         // Returns list of playlists
257         async function getMyPlaylistList(spotUser=null){
258                 if (spotUser && s.spotifyUser != spotUser) s.spotifyUser = spotUser
259                 try{
260                         logger.info("Loading Personal Playlists")
261                         let data = await s.Deezer.legacyGetUserPlaylists(s.Deezer.user.id)
262                         data = data.data || []
263                         let playlists = []
264                         for (let i = 0; i < data.length; i++) {
265                                 let obj = {
266                                         title: data[i].title,
267                                         image: data[i].picture_small,
268                                         songs: data[i].nb_tracks,
269                                         link: data[i].link
270                                 }
271                                 playlists.push(obj)
272                         }
273                         if (s.spotifyUser && spotifySupport){
274                                 try{
275                                         let creds = await Spotify.clientCredentialsGrant()
276                                         Spotify.setAccessToken(creds.body['access_token'])
277                                         let first = true
278                                         let offset = 0
279                                         do{
280                                                 let data = await Spotify.getUserPlaylists(s.spotifyUser, {fields: "items(images,name,owner.id,tracks.total,uri),total", offset: offset*20})
281                                                 if (first){
282                                                         var total = data.body.total
283                                                         var numPages=Math.floor((total-1)/20)
284                                                         var playlistList = new Array(total)
285                                                         first = false
286                                                 }
287                                                 data.body.items.forEach((playlist, i) => {
288                                                         playlistList[(offset*20)+i] = {
289                                                                 title: playlist.name,
290                                                                 image: (playlist.images[0] ? playlist.images[0].url : ""),
291                                                                 songs: playlist.tracks.total,
292                                                                 link: playlist.uri,
293                                                                 spotify: true
294                                                         }
295                                                 })
296                                                 offset++
297                                         }while(offset<=numPages)
298                                         playlists = playlists.concat(playlistList)
299                                 }catch(err){
300                                         logger.error(`Spotify playlist failed loading: ${err}`)
301                                 }
302                         }
303                         logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`)
304                         s.emit("getMyPlaylistList", {playlists: playlists})
305                 }catch(err){
306                         logger.error(`getMyPlaylistList failed: ${err}`)
307                         return
308                 }
309         }
310         s.on("getMyPlaylistList", function (d) {getMyPlaylistList(d.spotifyUser)})
311
312         // Returns search results from a query
313         s.on("search", async function (data) {
314                 data.type = data.type || "track"
315                 if (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) data.type = "track"
316
317                 // Remove "feat." "ft." and "&" (causes only problems)
318                 data.text = data.text
319                         .replace(/ feat[\.]? /g, " ")
320                         .replace(/ ft[\.]? /g, " ")
321                         .replace(/\(feat[\.]? /g, " ")
322                         .replace(/\(ft[\.]? /g, " ")
323                         .replace(/\&/g, "")
324                         .replace(/–/g, "-")
325                         .replace(/—/g, "-")
326
327                 try {
328                         let searchObject = await s.Deezer.legacySearch(encodeURIComponent(data.text), data.type)
329                         s.emit("search", {type: data.type, items: searchObject.data})
330                 } catch (err) {
331                         s.emit("search", {type: data.type, items: []})
332                         logger.error(`search failed: ${err.stack}`)
333                         return
334                 }
335         })
336
337         // Returns list of tracks from an album/playlist or the list of albums from an artist
338         s.on("getTrackList", async function (data) {
339                 if (!data.type || (["playlist", "album", "artist", "spotifyplaylist"].indexOf(data.type) == -1) || !data.id) {
340                         s.emit("getTrackList", {err: -1, response: {}, id: data.id, reqType: data.type})
341                         return
342                 }
343                 if (data.type == 'artist') {
344                         try{
345                                 let response = await s.Deezer.legacyGetArtist(data.id)
346                                 let tracks = await s.Deezer.legacyGetArtistAlbums(data.id)
347                                 response.data = tracks.data
348                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
349                         }catch(err){
350                                 s.emit("getTrackList", {err: "wrong artist id", response: {}, id: data.id, reqType: data.type})
351                                 logger.error(`getTrackList failed: ${err.stack}`)
352                                 return
353                         }
354                 }else if(data.type == "spotifyplaylist" && spotifySupport){
355                         try{
356                                 let creds = await Spotify.clientCredentialsGrant()
357                                 Spotify.setAccessToken(creds.body.access_token)
358                                 let first = true
359                                 let offset = 0
360                                 var response = {}
361                                 let resp0 = await Spotify.getPlaylist(data.id, {fields: "images,name,owner"})
362                                 response.title = resp0.body.name
363                                 response.image = resp0.body.images[0].url
364                                 response.owner = resp0.body.owner.display_name
365                                 do{
366                                         let resp = await Spotify.getPlaylistTracks(data.id, {fields: "items(track(artists,name,duration_ms,preview_url,explicit)),total", offset: offset*100})
367                                         if (first){
368                                                 var numPages=Math.floor((resp.body.total-1)/100)
369                                                 response.data = new Array(resp.body.total)
370                                                 first = false
371                                         }
372                                         resp.body.items.forEach((t, index) => {
373                                                 response.data[index+offset*100]={
374                                                         explicit_lyrics: t.track.explicit,
375                                                         preview: t.track.preview_url,
376                                                         title: t.track.name,
377                                                         artist: {
378                                                                 name: t.track.artists[0].name
379                                                         },
380                                                         duration: Math.floor(t.track.duration_ms/1000)
381                                                 }
382                                         })
383                                         offset++
384                                 }while(offset<=numPages)
385                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
386                         }catch(err){
387                                 logger.error(`getTrackList failed: ${err.stack}`)
388                         }
389                 }else{
390                         let reqType = data.type.charAt(0).toUpperCase() + data.type.slice(1)
391                         try{
392                                 let response = await s.Deezer["legacyGet" + reqType](data.id)
393                                 let tracks = await s.Deezer["legacyGet" + reqType + "Tracks"](data.id)
394                                 response.data = tracks.data
395                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
396                         }catch(err){
397                                 s.emit("getTrackList", {err: "wrong id "+reqType, response: {}, id: data.id, reqType: data.type})
398                                 logger.error(`getTrackList failed: ${err.stack}`)
399                                 return
400                         }
401                 }
402         })
403
404         // Sends settings saved by the user to the frontend
405         s.on("getUserSettings", function () {
406                 let settings = configFile.userDefined
407                 if (!settings.downloadLocation) {
408                         settings.downloadLocation = mainFolder
409                 }
410                 s.emit('getUserSettings', {settings: settings})
411         });
412
413         // Saves locally the settings comming from the frontend
414         s.on("saveSettings", function (settings, spotifyUser) {
415                 if (settings.userDefined.downloadLocation == defaultDownloadFolder) {
416                         settings.userDefined.downloadLocation = ""
417                 } else {
418                         settings.userDefined.downloadLocation = path.resolve(settings.userDefined.downloadLocation + path.sep) + path.sep
419                         mainFolder = settings.userDefined.downloadLocation
420                 }
421
422                 if (settings.userDefined.queueConcurrency < 1) settings.userDefined.queueConcurrency = 1
423
424                 if (settings.userDefined.queueConcurrency != trackQueue.concurrency){
425                         trackQueue.concurrency = settings.userDefined.queueConcurrency
426                 }
427
428                 if (spotifyUser != s.spotifyUser){
429                         s.spotifyUser = spotifyUser
430                         getMyPlaylistList(spotifyUser)
431                 }
432
433                 configFile.userDefined = settings.userDefined;
434                 fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
435                         if (err) return
436                         logger.info("Settings updated")
437                         initFolders()
438                 });
439         });
440
441         s.on("analyzetrack", async (id)=>{
442                 s.emit("analyzetrack", await s.Deezer.legacyGetTrack(id))
443         })
444
445         s.on("analyzealbum", async (id)=>{
446                 s.emit("analyzealbum", await s.Deezer.legacyGetAlbum(id))
447         })
448
449         /*
450          * Downloading section of the app
451         */
452
453         // Gets data from the frontend and creates the track object
454         async function downloadTrack(data){
455                 logger.info(`Added to Queue ${data.id}`)
456                 try{
457                         var track = await s.Deezer.getTrack(data.id)
458                         data.settings.filename = data.settings.trackNameTemplate
459                         data.settings.foldername = data.settings.albumNameTemplate
460                         let _track = {
461                                 name: track.title,
462                                 artist: track.artist.name,
463                                 size: 1,
464                                 downloaded: 0,
465                                 failed: 0,
466                                 queueId: `id${Math.random().toString(36).substring(2)}`,
467                                 id: `${track.id}:${data.bitrate}`,
468                                 bitrate: data.bitrate+"",
469                                 type: 'track',
470                                 settings: data.settings || {},
471                                 obj: track,
472                         }
473                         addToQueue(_track)
474                 }catch(err){
475                         logger.error(`downloadTrack failed: ${err.stack ? err.stack : err}`)
476                         message = ""
477                         if (err.message){
478                                 switch (err.message){
479                                         case "DataException: no data":
480                                                 message = "Not Found"
481                                         break
482                                         default:
483                                                 message = err.message
484                                         break
485                                 }
486                         }
487                         s.emit("toast", `Track ${data.id} download failed: ${message}`)
488                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
489                         return
490                 }
491         }
492         s.on("downloadtrack", async data=>{await downloadTrack(data)})
493
494         // Gets data from the frontend and creates the album object
495         async function downloadAlbum(data){
496                 logger.info(`Added to Queue ${data.id}`)
497                 try{
498                         var album = await s.Deezer.legacyGetAlbum(data.id)
499                         if (data.settings.tags.discTotal || data.settings.createCDFolder){
500                                 var discTotal = await s.Deezer.getAlbum(data.id)
501                                 album.discTotal = discTotal.discTotal
502                         }
503                         if (album.nb_tracks == 1 && data.settings.downloadSinglesAsTracks){
504                                 var track = await s.Deezer.getTrack(album.tracks.data[0].id)
505                                 data.settings.filename = data.settings.trackNameTemplate
506                                 data.settings.foldername = data.settings.albumNameTemplate
507                                 let _track = {
508                                         name: track.title,
509                                         artist: track.artist.name,
510                                         size: 1,
511                                         downloaded: 0,
512                                         failed: 0,
513                                         queueId: `id${Math.random().toString(36).substring(2)}`,
514                                         id: `${track.id}:${data.bitrate}`,
515                                         urlId: data.id,
516                                         bitrate: data.bitrate+"",
517                                         type: 'track',
518                                         settings: data.settings || {},
519                                         obj: track,
520                                 }
521                                 addToQueue(_track)
522                         }else{
523                                 album.tracks = await s.Deezer.getAlbumTracks(data.id)
524                                 data.settings.filename = data.settings.albumTrackNameTemplate
525                                 data.settings.foldername = data.settings.albumNameTemplate
526                                 let _album = {
527                                         name: album.title,
528                                         artist: album.artist.name,
529                                         size: album.tracks.length,
530                                         downloaded: 0,
531                                         failed: 0,
532                                         queueId: `id${Math.random().toString(36).substring(2)}`,
533                                         id: `${album.id}:${data.bitrate}`,
534                                         bitrate: data.bitrate+"",
535                                         type: 'album',
536                                         settings: data.settings || {},
537                                         obj: album,
538                                 }
539                                 addToQueue(_album)
540                         }
541                         return
542                 }catch(err){
543                         logger.error(`downloadAlbum failed: ${err.stack ? err.stack : err}`)
544                         message = ""
545                         if (err.message){
546                                 switch (err.message){
547                                         case "DataException: no data":
548                                                 message = "Not Found"
549                                         break
550                                         default:
551                                                 message = err.message
552                                         break
553                                 }
554                         }
555                         s.emit("toast", `Album ${data.id} download failed: ${message}`)
556                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
557                         return
558                 }
559         }
560         s.on("downloadalbum", async data=>{await downloadAlbum(data)});
561
562         // Gets data from the frontend and creates for each album an album object
563         async function downloadArtist(data){
564                 logger.info(`Added to Queue ${data.id}`)
565                 try{
566                         var albums = await s.Deezer.legacyGetArtistAlbums(data.id);
567                         (function sendAllAlbums(i) {
568                                 setTimeout(function () {
569                                         data.id = albums.data[albums.data.length-1-i].id
570                                         downloadAlbum(JSON.parse(JSON.stringify(data)))
571                                         if (--i+1) sendAllAlbums(i)
572                                 }, 100)
573                         })(albums.data.length-1)
574                 }catch(err){
575                         logger.error(`downloadArtist failed: ${err.stack ? err.stack : err}`)
576                         message = ""
577                         if (err.message){
578                                 switch (err.message){
579                                         case "DataException: no data":
580                                                 message = "Not Found"
581                                         break
582                                         default:
583                                                 message = err.message
584                                         break
585                                 }
586                         }
587                         s.emit("toast", `Artist ${data.id} download failed: ${message}`)
588                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
589                         return
590                 }
591         }
592         s.on("downloadartist", async data=>{ await downloadArtist(data)});
593
594         // Gets data from the frontend and creates the playlist object
595         async function downloadPlaylist(data){
596                 logger.info(`Added to Queue ${data.id}`)
597                 try{
598                         var playlist = await s.Deezer.legacyGetPlaylist(data.id)
599                         data.settings.filename = data.settings.playlistTrackNameTemplate
600                         data.settings.foldername = data.settings.albumNameTemplate
601                         playlist.tracks = await s.Deezer.getPlaylistTracks(data.id)
602                         let _playlist = {
603                                 name: playlist.title,
604                                 artist: playlist.creator.name,
605                                 size: playlist.tracks.length,
606                                 downloaded: 0,
607                                 failed: 0,
608                                 queueId: `id${Math.random().toString(36).substring(2)}`,
609                                 id: `${playlist.id}:${data.bitrate}`,
610                                 bitrate: data.bitrate+"",
611                                 type: "playlist",
612                                 settings: data.settings || {},
613                                 obj: playlist,
614                         }
615                         addToQueue(_playlist)
616                 }catch(err){
617                         logger.error(`downloadPlaylist failed: ${err.stack ? err.stack : err}`)
618                         message = ""
619                         if (err.message){
620                                 switch (err.message){
621                                         case "DataException: no data":
622                                                 message = "Not Found"
623                                         break
624                                         default:
625                                                 message = err.message
626                                         break
627                                 }
628                         }
629                         s.emit("toast", `Playlist ${data.id} download failed: ${message}`)
630                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
631                         return
632                 }
633         }
634         s.on("downloadplaylist", data=>{downloadPlaylist(data)});
635
636         // Gets data from the frontend and creates the object fot the artist top tracks
637         async function downloadArtistTop(data){
638                 logger.info(`Added to Queue ${data.id}`)
639                 try{
640                         var artist = await s.Deezer.legacyGetArtist(data.id)
641                         data.settings.filename = data.settings.playlistTrackNameTemplate
642                         data.settings.foldername = data.settings.albumNameTemplate
643                         artist.tracks = await s.Deezer.getArtistTopTracks(data.id)
644                         let _playlist = {
645                                 name: artist.name + " Most played tracks",
646                                 artist: artist.name,
647                                 size: artist.tracks.length,
648                                 downloaded: 0,
649                                 failed: 0,
650                                 queueId: `id${Math.random().toString(36).substring(2)}`,
651                                 id: `${artist.id}:${data.bitrate}`,
652                                 bitrate: data.bitrate+"",
653                                 type: "playlist",
654                                 settings: data.settings || {},
655                                 obj: artist,
656                         }
657                         addToQueue(_playlist)
658                 }catch(err){
659                         logger.error(`downloadArtistTop failed: ${err.stack ? err.stack : err}`)
660                         message = ""
661                         if (err.message){
662                                 switch (err.message){
663                                         case "DataException: no data":
664                                                 message = "Not Found"
665                                         break
666                                         default:
667                                                 message = err.message
668                                         break
669                                 }
670                         }
671                         s.emit("toast", `ArtistTop ${data.id} download failed: ${message}`)
672                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
673                         return
674                 }
675         }
676         s.on("downloadartisttop", data=>{downloadArtistTop(data)});
677
678         // Gets data from the frontend and creates the spotify playlist object
679         async function downloadSpotifyPlaylist(data){
680                 logger.info(`Added to Queue ${data.id}`)
681                 if (spotifySupport){
682                         try{
683                                 let creds = await Spotify.clientCredentialsGrant()
684                                 Spotify.setAccessToken(creds.body['access_token'])
685                                 var offset = 0
686                                 data.settings.filename = data.settings.playlistTrackNameTemplate
687                                 data.settings.foldername = data.settings.albumNameTemplate
688                                 var resp = await Spotify.getPlaylist(data.id, {fields: "id,name,owner,images,tracks(total)"})
689                                 var _playlist = {
690                                         name: resp.body.name,
691                                         artist: (resp.body.owner.display_name ? resp.body.owner.display_name : resp.body.owner.id),
692                                         size: resp.body.tracks.total,
693                                         downloaded: 0,
694                                         failed: 0,
695                                         queueId: `id${Math.random().toString(36).substring(2)}`,
696                                         settings: data.settings || {},
697                                         id: `${resp.body.id}:${data.bitrate}`,
698                                         bitrate: data.bitrate+"",
699                                         type: "spotifyplaylist",
700                                         obj: resp.body
701                                 }
702                                 var numPages=Math.floor((_playlist.size-1)/100)
703                                 var trackList = new Array(_playlist.size)
704                                 do{
705                                         var resp = await Spotify.getPlaylistTracks(data.id, {fields: "items(track(artists,name,album,external_ids))", offset: offset*100})
706                                         resp.body.items.forEach((track, i) => {
707                                                 trackList[i+(offset*100)] = track.track
708                                         })
709                                         offset++
710                                 }while(offset<=numPages)
711                                 _playlist.obj.tracks = trackList
712                                 addToQueue(_playlist)
713                         }catch(err){
714                                 logger.error(`downloadSpotifyPlaylist failed: ${err.stack ? err.stack : err}`)
715                                 if (err.message && err.message == "Bad Request"){
716                                         s.emit("message", {title: "You setted it up wrong!", msg: "It seems like you setted the authCredentials.js file wrong...<br>Make sure you keep the ' around the IDs and that the Secret and Client ID are copied correctly<br>After that you should restart the app to make it work.<br><br>If you need the guide again <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">Here it is</a>"})
717                                 }else{
718                                         s.emit("toast", `SpotifyPlaylist ${data.id} failed: ${err.message ? err.message : err}`)
719                                 }
720                                 s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
721                                 return
722                         }
723                 }else{
724                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files and then restart the app to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
725                 }
726         }
727         s.on("downloadspotifyplaylist", data=>{downloadSpotifyPlaylist(data)})
728
729         // Gets data from the frontend and creates data for the deezer track object
730         async function downloadSpotifyTrack(data){
731                 logger.info(`Added to Queue ${data.id}`)
732                 if (spotifySupport){
733                         try{
734                                 let creds = await Spotify.clientCredentialsGrant()
735                                 Spotify.setAccessToken(creds.body['access_token'])
736                                 var resp = await Spotify.getTrack(data.id, {fields: "external_ids,artists,album,name"})
737                                 deezerId = await convertSpotify2Deezer(resp.body)
738                                 if (deezerId != 0){
739                                         data.id = deezerId
740                                         downloadTrack(data)
741                                 }else{
742                                         s.emit("toast", "Can't find the track on Deezer!")
743                                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
744                                         logger.error(`Can't find the track on Deezer!`)
745                                 }
746                         }catch(err){
747                                 logger.error(`downloadSpotifyTrack failed: ${err.stack ? err.stack : err}`)
748                                 return
749                         }
750                 }else{
751                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files and then restart the app to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
752                 }
753         }
754         s.on("downloadspotifytrack", data=>{downloadSpotifyTrack(data)})
755
756         // Gets data from the frontend and creates data for the deezer track object
757         async function downloadSpotifyAlbum(data){
758                 logger.info(`Added to Queue ${data.id}`)
759                 if (spotifySupport){
760                         try{
761                                 let creds = await Spotify.clientCredentialsGrant()
762                                 Spotify.setAccessToken(creds.body['access_token'])
763                                 var resp = await Spotify.getAlbum(data.id, {fields: "external_ids,artists,name"})
764                                 deezerId = await convertSpotifyAlbum2Deezer(resp.body)
765                                 if (deezerId != 0){
766                                         data.id = deezerId
767                                         downloadAlbum(data)
768                                 }else{
769                                         s.emit("toast", "Can't find the album on Deezer!")
770                                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
771                                         logger.error(`Can't find the album on Deezer!`)
772                                 }
773                         }catch(err){
774                                 logger.error(`downloadSpotifyAlbum failed: ${err.stack ? err.stack : err}`)
775                                 return
776                         }
777                 }else{
778                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files and then restart the app to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
779                 }
780         }
781         s.on("downloadspotifyalbum", data=>{downloadSpotifyAlbum(data)})
782
783         // Converts the spotify track to a deezer one
784         // It tries first with the isrc (best way of conversion)
785         // Fallbacks to the old way, using search
786         async function convertSpotify2Deezer(track){
787                 if (!track) return 0
788                 try{
789                         if (track.external_ids.isrc){
790                                 let resp = await s.Deezer.legacyGetTrackByISRC(track.external_ids.isrc)
791                                 if (resp.title)
792                                         return resp.id
793                                 else
794                                         logger.warn("ISRC track is not on Deezer, falling back to old method")
795                         }
796                 }catch(err){
797                         logger.warn("ISRC not found, falling back to old method")
798                 }
799                 return convertMetadata2Deezer(track.artists[0].name, track.name, track.album.name)
800         }
801
802         // Tries to get track id from pure luck
803         async function convertMetadata2Deezer(artist, track, album){
804                 let resp
805                 artist = artist.replace(/–/g,"-").replace(/’/g, "'")
806                 track = track.replace(/–/g,"-").replace(/’/g, "'")
807                 album = album.replace(/–/g,"-").replace(/’/g, "'")
808                 try{
809                         resp = await s.Deezer.legacySearch(`artist:"${artist}" track:"${track}" album:"${album}"`, "track", 1)
810                 }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
811                 if (resp.data[0]) return resp.data[0].id
812                 try{
813                         resp = await s.Deezer.legacySearch(encodeURIComponent(`artist:"${artist}" track:"${track}"`), "track", 1)
814                 }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
815                 if (resp.data[0]) return resp.data[0].id
816                 if (track.indexOf("(") < track.indexOf(")")){
817                         try{
818                                 resp = await s.Deezer.legacySearch(encodeURIComponent(`artist:"${artist}" track:"${track.split("(")[0]}"`), "track", 1)
819                         }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
820                         if (resp.data[0]) return resp.data[0].id
821                 }else if (track.indexOf(" - ")>0){
822                         try{
823                                 resp = await s.Deezer.legacySearch(encodeURIComponent(`artist:"${artist}" track:"${track.split(" - ")[0]}"`), "track", 1)
824                         }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
825                         if (resp.data[0]) return resp.data[0].id
826                 }else{
827                         return 0
828                 }
829                 return 0
830         }
831
832         // Converts the spotify album to a deezer one
833         // It tries first with the upc (best way of conversion)
834         // Fallbacks to the old way, using search
835         async function convertSpotifyAlbum2Deezer(album){
836                 if (!album) return 0
837                 try{
838                         if (album.external_ids.upc){
839                                 if (! isNaN(album.external_ids.upc)) album.external_ids.upc = parseInt(album.external_ids.upc)
840                                 let resp = await s.Deezer.legacyGetAlbumByUPC(album.external_ids.upc)
841                                 if (resp.title)
842                                         return resp.id
843                                 else
844                                         logger.warn("UPC album is not on Deezer, falling back to old method")
845                         }
846                 }catch(err){
847                         logger.warn("UPC not found, falling back to old method")
848                 }
849                 return convertAlbumMetadata2Deezer(album.artists[0].name, album.name)
850         }
851
852         // Tries to get album id from pure luck
853         async function convertAlbumMetadata2Deezer(artist, album){
854                 let resp
855                 artist = artist.replace(/–/g,"-").replace(/’/g, "'")
856                 album = album.replace(/–/g,"-").replace(/’/g, "'")
857                 try{
858                         resp = await s.Deezer.legacySearch(`artist:"${artist}" album:"${album}"`, "album", 1)
859                 }catch(err){logger.err(`ConvertAlbumFromMetadata: ${err.stack ? err.stack : err}`)}
860                 if (resp.data[0]) return resp.data[0].id
861                 return 0
862         }
863
864         // All the above functions call this function
865         // It adds the object to an array and adds the promise for the download to the object itself
866         function addToQueue(object) {
867                 downloadQueue[object.queueId] = object
868                 io.sockets.emit('addToQueue', object)
869                 downloadQueue[object.queueId].downloadQueuePromise = dqueue.push(addNextDownload, { args: object })
870         }
871
872         // Wrapper for queue download
873         function addNextDownload(obj, token){
874                 return new Promise(async (resolve, reject) => {
875                         await queueDownload(obj)
876                         resolve()
877                 }).then(() => new Promise((resolve, reject) => {
878                         if (token.cancelled)
879                                 reject()
880                         else
881                                 resolve()
882                 }))
883         }
884
885         // Cancels download
886         // TODO: Might check this one, could be a little buggy
887         function cancelDownload(queueId, cleanAll=false){
888                 if (!queueId) return
889                 let cancel = false
890                 let cancelSuccess
891                 if (downloadQueue[queueId]){
892                         cancel = true;
893                         if (downloadQueue[queueId].downloadQueuePromise) downloadQueue[queueId].downloadQueuePromise.cancel()
894                         if (downloadQueue[Object.keys(downloadQueue)[0]].queueId == queueId) {
895                                 trackQueue = queue({
896                                         autostart: true,
897                                         concurrency: trackQueue.concurrency
898                                 })
899                         }
900                         delete downloadQueue[queueId]
901                 }
902
903                 if (cancel) {
904                         io.sockets.emit("cancelDownload", {queueId: queueId, cleanAll: cleanAll});
905                 }
906         }
907         s.on("cancelDownload", function (data) {cancelDownload(data.queueId)});
908
909         s.on("cancelAllDownloads", function(data){
910                 data.queueList.forEach(x=>{
911                         cancelDownload(x, true);
912                 })
913                 io.sockets.emit("cancelAllDownloads")
914         })
915
916         /*function getNextDownload() {
917                 if (s.currentItem != null || Object.keys(downloadQueue).length == 0) {
918                         if (Object.keys(downloadQueue).length == 0 && s.currentItem == null) {
919                                 s.emit("emptyDownloadQueue", {})
920                         }
921                         return null
922                 }
923                 s.currentItem = downloadQueue[Object.keys(downloadQueue)[0]]
924                 return s.currentItem
925         }*/
926
927         //downloadQueue: the tracks in the queue to be downloaded
928         //queueId: random number generated when user clicks download on something
929         async function queueDownload(downloading) {
930                 if (!downloading) return
931
932                 if (downloading.type != "spotifyplaylist"){
933                         io.sockets.emit("downloadStarted", {queueId: downloading.queueId})
934                 }
935
936                 downloading.errorLog = "";
937                 downloading.searchedLog = "";
938
939                 logger.info(`Registered ${downloading.type}: ${downloading.id} | ${downloading.artist} - ${downloading.name}`);
940                 switch(downloading.type){
941                         /*
942                         *  TRACK DOWNLOAD
943                         */
944                         case "track":
945                                 var downloadPromise = new Promise(async (resolve,reject)=>{
946                                         try{
947                                                 await downloadTrackObject(downloading.obj, downloading.queueId, downloading.settings)
948                                                 downloading.downloaded++
949                                         }catch(err){
950                                                 logger.error(`[${downloading.obj.artist.name} - ${downloading.obj.title}] ${err}`)
951                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
952                                                 downloading.failed++
953                                         }
954                                         io.sockets.emit("updateQueue", {
955                                                 name: downloading.name,
956                                                 artist: downloading.artist,
957                                                 size: downloading.size,
958                                                 downloaded: downloading.downloaded,
959                                                 failed: downloading.failed,
960                                                 queueId: downloading.queueId,
961                                                 id: downloading.id,
962                                                 type: downloading.type,
963                                                 errorLog: downloading.errorLog,
964                                         })
965                                         io.sockets.emit("downloadProgress", {
966                                                 queueId: downloading.queueId,
967                                                 percentage: 100
968                                         })
969                                         resolve()
970                                 })
971                                 try{
972                                         await downloadPromise
973                                 }catch(err){
974                                         if (err) logger.error(`queueDownload:track failed: ${err.stack ? err.stack : err}`)
975                                         logger.info("Downloading Stopped")
976                                 }
977                         break
978                         /*
979                         *  ALBUM DOWNLOAD
980                         */
981                         case "album":
982                                 downloading.settings.albName = downloading.name;
983                                 downloading.settings.artName = downloading.artist;
984                                 downloading.playlistArr = Array(downloading.size);
985                                 let ajson = {
986                                         artist : downloading.obj.artist,
987                                         nb_tracks : downloading.obj.nb_tracks,
988                                         upc : downloading.obj.upc,
989                                         record_type : downloading.obj.record_type,
990                                         explicit_lyrics : downloading.obj.explicit_lyrics,
991                                         label : downloading.obj.label,
992                                         release_date : downloading.obj.release_date,
993                                         genres : downloading.obj.genres,
994                                         discTotal: downloading.obj.discTotal ? downloading.obj.discTotal : null
995                                 }
996                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
997                                         downloading.obj.tracks.every(function (t) {
998                                                 trackQueue.push(async cb=>{
999                                                         if (!downloadQueue[downloading.queueId]) {
1000                                                                 reject()
1001                                                                 return false
1002                                                         }
1003                                                         t.ajson = ajson
1004                                                         logger.info(`Now downloading: ${t.artist.name} - ${t.title}`)
1005                                                         try{
1006                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1007                                                                 downloading.downloaded++
1008                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(downloading.filePath)[1]
1009                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1010                                                         }catch(err){
1011                                                                 downloading.failed++
1012                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1013                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1014                                                         }
1015                                                         io.sockets.emit("downloadProgress", {
1016                                                                 queueId: downloading.queueId,
1017                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1018                                                         });
1019                                                         io.sockets.emit("updateQueue", {
1020                                                                 name: downloading.name,
1021                                                                 artist: downloading.artist,
1022                                                                 size: downloading.size,
1023                                                                 downloaded: downloading.downloaded,
1024                                                                 failed: downloading.failed,
1025                                                                 queueId: downloading.queueId,
1026                                                                 id: downloading.id,
1027                                                                 type: downloading.type,
1028                                                                 errorLog: downloading.errorLog,
1029                                                         })
1030                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1031                                                         cb()
1032                                                 })
1033                                                 return true
1034                                         })
1035                                 })
1036                                 try{
1037                                         await downloading.downloadPromise
1038                                         logger.info("Album finished downloading: "+downloading.name);
1039                                         io.sockets.emit("downloadProgress", {
1040                                                 queueId: downloading.queueId,
1041                                                 percentage: 100
1042                                         });
1043                                         if (downloading.settings.logErrors){
1044                                                 if (downloading.errorLog != ""){
1045                                                         if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1046                                                         fs.writeFileSync(downloading.filePath+"notFound.txt",downloading.errorLog)
1047                                                 }else{
1048                                                         if (fs.existsSync(downloading.filePath+"notFound.txt")) fs.unlinkSync(downloading.filePath+"notFound.txt");
1049                                                 }
1050                                         }
1051                                         if (downloading.settings.logSearched){
1052                                                 if (downloading.searchedLog != ""){
1053                                                         if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1054                                                         fs.writeFileSync(downloading.filePath+"alternativeSongs.txt",downloading.searchedLog)
1055                                                 }else{
1056                                                         if (fs.existsSync(downloading.filePath+"alternativeSongs.txt")) fs.unlinkSync(downloading.filePath+"alternativeSongs.txt");
1057                                                 }
1058                                         }
1059                                         if (downloading.settings.createM3UFile){
1060                                                 fs.writeFileSync(downloading.filePath+"playlist.m3u8", downloading.playlistArr.join("\r\n"));
1061                                         }
1062                                 }catch(err){
1063                                         if (err) logger.error(`queueDownload:album failed: ${err.stack ? err.stack : err}`)
1064                                         logger.info("Stopping the album queue");
1065                                 }
1066                         break
1067                         /*
1068                         *  PLAYLIST DOWNLOAD
1069                         */
1070                         case "playlist":
1071                                 downloading.settings.plName = downloading.name;
1072                                 downloading.playlistArr = Array(downloading.size);
1073                                 downloading.settings.playlist = {
1074                                         name:   downloading.name,
1075                                         artist: downloading.artist,
1076                                         cover: downloading.obj.picture_small.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`),
1077                                         fullSize: downloading.obj.tracks.length
1078                                 };
1079                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
1080                                         downloading.obj.tracks.every(function (t, index) {
1081                                                 trackQueue.push(async cb=>{
1082                                                         if (!downloadQueue[downloading.queueId]) {
1083                                                                 reject()
1084                                                                 return false
1085                                                         }
1086                                                         try{
1087                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1088                                                                 downloading.downloaded++
1089                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(downloading.filePath)[1]
1090                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1091                                                         }catch(err){
1092                                                                 downloading.failed++
1093                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1094                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1095                                                         }
1096                                                         io.sockets.emit("downloadProgress", {
1097                                                                 queueId: downloading.queueId,
1098                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1099                                                         });
1100                                                         io.sockets.emit("updateQueue", {
1101                                                                 name: downloading.name,
1102                                                                 artist: downloading.artist,
1103                                                                 size: downloading.size,
1104                                                                 downloaded: downloading.downloaded,
1105                                                                 failed: downloading.failed,
1106                                                                 queueId: downloading.queueId,
1107                                                                 id: downloading.id,
1108                                                                 type: downloading.type,
1109                                                                 errorLog: downloading.errorLog,
1110                                                         })
1111                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1112                                                         cb()
1113                                                 })
1114                                                 return true
1115                                         })
1116                                 })
1117                                 try{
1118                                         await downloading.downloadPromise
1119                                         logger.info("Playlist finished "+downloading.name);
1120                                         io.sockets.emit("downloadProgress", {
1121                                                 queueId: downloading.queueId,
1122                                                 percentage: 100
1123                                         });
1124                                         if (downloading.settings.logErrors){
1125                                                 if (downloading.errorLog != ""){
1126                                                         if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1127                                                         fs.writeFileSync(downloading.filePath+"notFound.txt",downloading.errorLog)
1128                                                 }else{
1129                                                         if (fs.existsSync(downloading.filePath+"notFound.txt")) fs.unlinkSync(downloading.filePath+"notFound.txt");
1130                                                 }
1131                                         }
1132                                         if (downloading.settings.logSearched){
1133                                                 if (downloading.searchedLog != ""){
1134                                                         if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1135                                                         fs.writeFileSync(downloading.filePath+"alternativeSongs.txt",downloading.searchedLog)
1136                                                 }else{
1137                                                         if (fs.existsSync(downloading.filePath+"alternativeSongs.txt")) fs.unlinkSync(downloading.filePath+"alternativeSongs.txt");
1138                                                 }
1139                                         }
1140                                         if (downloading.settings.createM3UFile){
1141                                                 fs.writeFileSync(downloading.filePath + "playlist.m3u8", downloading.playlistArr.join("\r\n"));
1142                                         }
1143                                         if (downloading.settings.saveArtwork){
1144                                                 if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1145                                                 let imgPath = downloading.filePath + antiDot(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
1146                                                 if (downloading.obj.picture_small){
1147                                                         downloading.cover = downloading.obj.picture_small.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
1148                                                         request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1149                                                                 if(error){
1150                                                                         logger.error(error.stack);
1151                                                                         return;
1152                                                                 }
1153                                                                 fs.outputFile(imgPath,body,'binary',function(err){
1154                                                                         if(err){
1155                                                                                 logger.error(err.stack);
1156                                                                                 return;
1157                                                                         }
1158                                                                         logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
1159                                                                 })
1160                                                         });
1161                                                 }
1162                                         }
1163                                 }catch(err){
1164                                         if (err) logger.error(`queueDownload:playlist failed: ${err.stack ? err.stack : err}`)
1165                                         logger.info("Stopping the playlist queue")
1166                                 }
1167                         break
1168                         /*
1169                         *  SPOTIFY PLAYLIST DOWNLOAD
1170                         */
1171                         case "spotifyplaylist":
1172                                 downloading.settings.plName = downloading.name
1173                                 downloading.playlistArr = Array(downloading.size)
1174                                 downloading.playlistContent = new Array(downloading.size)
1175                                 logger.info("Waiting for all tracks to be converted");
1176                                 const convert = async () =>{
1177                                         await asyncForEach(downloading.obj.tracks, async (t,i)=>{
1178                                                 if (!downloadQueue[downloading.queueId]) return false
1179                                                 try{
1180                                                         downloading.playlistContent[i] = await convertSpotify2Deezer(t)
1181                                                 }catch(err){
1182                                                         logger.error(`queueDownload:spotifyplaylist failed during conversion: ${err.stack ? err.stack : err}`)
1183                                                 }
1184                                         })
1185                                 }
1186                                 await convert()
1187                                 if (!downloadQueue[downloading.queueId]) {
1188                                         logger.info("Stopping the playlist queue")
1189                                         break
1190                                 }
1191                                 downloading.trackList = await s.Deezer.getTracks(downloading.playlistContent)
1192                                 logger.info("All tracks converted, starting download")
1193                                 io.sockets.emit("downloadStarted", {queueId: downloading.queueId})
1194                                 downloading.settings.playlist = {
1195                                         name:   downloading.name,
1196                                         artist: downloading.artist,
1197                                         cover: downloading.obj.images[0].url.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`),
1198                                         fullSize: downloading.trackList.length
1199                                 }
1200                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
1201                                         downloading.trackList.every(function (t, index) {
1202                                                 trackQueue.push(async cb=>{
1203                                                         if (!downloadQueue[downloading.queueId]) {
1204                                                                 reject()
1205                                                                 return false
1206                                                         }
1207                                                         t.position = index
1208                                                         if (t.id==0 && downloading.obj.tracks[t.position] != null){
1209                                                                 t.title = downloading.obj.tracks[t.position].name
1210                                                                 t.album = {id: 0, title: downloading.obj.tracks[t.position].album.name}
1211                                                                 t.artist = {id: 0, name: downloading.obj.tracks[t.position].artists[0].name}
1212                                                         }
1213                                                         try{
1214                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1215                                                                 downloading.downloaded++
1216                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(downloading.filePath)[1]
1217                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1218                                                         }catch(err){
1219                                                                 downloading.failed++
1220                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1221                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1222                                                         }
1223                                                         io.sockets.emit("downloadProgress", {
1224                                                                 queueId: downloading.queueId,
1225                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1226                                                         });
1227                                                         io.sockets.emit("updateQueue", {
1228                                                                 name: downloading.name,
1229                                                                 artist: downloading.artist,
1230                                                                 size: downloading.size,
1231                                                                 downloaded: downloading.downloaded,
1232                                                                 failed: downloading.failed,
1233                                                                 queueId: downloading.queueId,
1234                                                                 id: downloading.id,
1235                                                                 type: downloading.type,
1236                                                                 errorLog: downloading.errorLog,
1237                                                         })
1238                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1239                                                         cb()
1240                                                 })
1241                                                 return true
1242                                         })
1243                                 })
1244                                 try{
1245                                         await downloading.downloadPromise
1246                                         logger.info("Playlist finished "+downloading.name);
1247                                         io.sockets.emit("downloadProgress", {
1248                                                 queueId: downloading.queueId,
1249                                                 percentage: 100
1250                                         });
1251                                         if (downloading.settings.logErrors){
1252                                                 if (downloading.errorLog != ""){
1253                                                         if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1254                                                         fs.writeFileSync(downloading.filePath+"notFound.txt",downloading.errorLog)
1255                                                 }else{
1256                                                         if (fs.existsSync(downloading.filePath+"notFound.txt")) fs.unlinkSync(downloading.filePath+"notFound.txt");
1257                                                 }
1258                                         }
1259                                         if (downloading.settings.logSearched){
1260                                                 if (downloading.searchedLog != ""){
1261                                                         if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1262                                                         fs.writeFileSync(downloading.filePath+"alternativeSongs.txt",downloading.searchedLog)
1263                                                 }else{
1264                                                         if (fs.existsSync(downloading.filePath+"alternativeSongs.txt")) fs.unlinkSync(downloading.filePath+"alternativeSongs.txt");
1265                                                 }
1266                                         }
1267                                         if (downloading.settings.createM3UFile){
1268                                                 fs.writeFileSync(downloading.filePath + "playlist.m3u8", downloading.playlistArr.join("\r\n"));
1269                                         }
1270                                         if (downloading.settings.saveArtwork){
1271                                                 if (!fs.existsSync(downloading.filePath)) fs.mkdirSync(downloading.filePath);
1272                                                 let imgPath = downloading.filePath + antiDot(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
1273                                                 if (downloading.obj.images){
1274                                                         downloading.cover = downloading.obj.images[0].url.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
1275                                                         request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1276                                                                 if(error){
1277                                                                         logger.error(error.stack);
1278                                                                         return;
1279                                                                 }
1280                                                                 fs.outputFile(imgPath,body,'binary',function(err){
1281                                                                         if(err){
1282                                                                                 logger.error(err.stack);
1283                                                                                 return;
1284                                                                         }
1285                                                                         logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
1286                                                                 })
1287                                                         });
1288                                                 }
1289                                         }
1290                                 }catch(err){
1291                                         if (err) logger.error(`queueDownload:spotifyplaylist failed: ${err.stack ? err.stack : err}`)
1292                                         logger.info("Stopping the playlist queue")
1293                                 }
1294                         break
1295                 }
1296                 if (downloading && downloadQueue[Object.keys(downloadQueue)[0]] && (Object.keys(downloadQueue)[0] == downloading.queueId)) delete downloadQueue[Object.keys(downloadQueue)[0]]
1297                 if (Object.keys(downloadQueue).length == 0) {
1298                         io.sockets.emit("emptyDownloadQueue", {})
1299                 }
1300         }
1301
1302         // This function takes the track object and does all the stuff to download it
1303         async function downloadTrackObject(track, queueId, settings) {
1304                 if (!downloadQueue[queueId]) {
1305                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Not in queue`)
1306                         throw new Error("Not in queue")
1307                         return false
1308                 }
1309                 if (parseInt(track.id) == 0){
1310                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Song not Found`)
1311                         throw new Error("Song not Found")
1312                         return false
1313                 }
1314
1315                 /* Album information is necessary for the following tags:
1316                  * album_artist
1317                  * album_artist_picture
1318                  * trackTotal
1319                  * recordType
1320                  * barcode
1321                  * explicit
1322                  * label
1323                  * genres
1324                  * date
1325                 */
1326                 if (parseInt(track.id)>0){
1327                         var ajson
1328                         if (!track.ajson){
1329                                 try{
1330                                         logger.info(`[${track.artist.name} - ${track.title}] Getting album info`)
1331                                         ajson = await s.Deezer.legacyGetAlbum(track.album.id)
1332                                 }catch(err){
1333                                         logger.warn(`[${track.artist.name} - ${track.title}] Album not found, trying to reach deeper`)
1334                                         try{
1335                                                 ajson = await s.Deezer.getAlbum(track.album.id)
1336                                                 ajson.fromNewAPI = true
1337                                         } catch(err){
1338                                                 if(track.fallbackId){
1339                                                         logger.warn(`[${track.artist.name} - ${track.title}] Failed to download track, falling on alternative`)
1340                                                         track = await s.Deezer.getTrack(track.fallbackId)
1341                                                         return downloadTrackObject(track, queueId, settings)
1342                                                 }else if(!track.searched && settings.fallbackSearch){
1343                                                         logger.warn(`[${track.artist.name} - ${track.title}] Failed to download track, searching for alternative`)
1344                                                         var _trackID = await convertMetadata2Deezer(track.artist.name, track.title, track.album.title)
1345                                                         if (_trackID != "0"){
1346                                                                 track = await s.Deezer.getTrack()
1347                                                                 track.searched = true
1348                                                                 return downloadTrackObject(track, queueId, settings)
1349                                                         }else{
1350                                                                 logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Alternative not found`)
1351                                                                 return
1352                                                         }
1353                                                 }else{
1354                                                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: ${err}`)
1355                                                         return
1356                                                 }
1357                                         }
1358                                 }
1359                         }else{
1360                                 ajson = track.ajson
1361                         }
1362                         if (!ajson.fromNewAPI){
1363                                 // Aquiring discTotal (only if necessary)
1364                                 if (settings.tags.discTotal || settings.createCDFolder){
1365                                         if (settings.savePlaylistAsCompilation && settings.plName){
1366                                                 track.album.discTotal = 1
1367                                         }else{
1368                                                 if (!ajson.discTotal){
1369                                                         logger.info(`[${track.artist.name} - ${track.title}] Getting total disc number`);
1370                                                         var discTotal = await s.Deezer.getAlbum(ajson.id)
1371                                                         track.album.discTotal = discTotal.discTotal
1372                                                 }else{
1373                                                         track.album.discTotal = ajson.discTotal
1374                                                 }
1375                                         }
1376                                 }
1377                                 if (settings.savePlaylistAsCompilation && settings.plName){
1378                                         track.album.artist = {
1379                                                 name: settings.playlist.artist,
1380                                                 picture: "",
1381                                         }
1382                                         track.album.title = settings.playlist.name
1383                                         track.album.trackTotal = settings.playlist.fullSize
1384                                         track.album.recordType = "Compilation"
1385                                 }else{
1386                                         track.album.artist = {
1387                                                 id: ajson.artist.id,
1388                                                 name: ajson.artist.name,
1389                                                 picture: ajson.artist.picture_small.substring(46,ajson.artist.picture_small.length-24),
1390                                         }
1391                                         track.album.trackTotal = ajson.nb_tracks
1392                                         if (ajson.record_type){
1393                                                 track.album.recordType = ajson.record_type
1394                                         }else{
1395                                                 track.album.recordType = switchReleaseType(track.album.recordType)
1396                                         }
1397                                 }
1398                                 track.album.barcode = ajson.upc
1399                                 if (ajson.explicit_lyrics)
1400                                         track.album.explicit = ajson.explicit_lyrics;
1401                                 if(ajson.label)
1402                                         track.album.label = ajson.label;
1403                                 if (ajson.release_date) {
1404                                         track.date = {
1405                                                 day: ajson.release_date.slice(8,10),
1406                                                 month: ajson.release_date.slice(5,7),
1407                                                 year: ajson.release_date.slice(0, 4),
1408                                                 slicedYear: (settings.dateFormatYear == "2" ? ajson.release_date.slice(2, 4) : ajson.release_date.slice(0, 4))
1409                                         }
1410                                 }else if(!track.date){
1411                                         track.date = {
1412                                                 day: 0,
1413                                                 month: 0,
1414                                                 year: 0,
1415                                                 slicedYear: 0
1416                                         }
1417                                 }
1418                                 if(ajson.genres && ajson.genres.data[0] && ajson.genres.data[0].name){
1419                                         track.album.genre = []
1420                                         ajson.genres.data.forEach(function(genre){
1421                                                 if (track.album.genre.indexOf(genre.name) == -1)
1422                                                         track.album.genre.push(genre.name)
1423                                         })
1424                                 }
1425                         }else{
1426                                 // Missing barcode, genre
1427                                 track.album = ajson
1428                                 track.date = track.album.date
1429                                 track.date.slicedYear = (settings.dateFormatYear == "2" ? track.date.year.slice(2, 4) : track.date.year.slice(0, 4))
1430                                 if (settings.savePlaylistAsCompilation && settings.plName){
1431                                         track.album.artist = {
1432                                                 name: settings.playlist.artist,
1433                                                 picture: "",
1434                                         }
1435                                         track.album.title = settings.playlist.name
1436                                         track.album.trackTotal = settings.playlist.fullSize
1437                                         track.album.recordType = "Compilation"
1438                                 }else{
1439                                         track.trackTotal = track.album.trackTotal
1440                                         track.album.recordType = "Album"
1441                                 }
1442                                 // TODO: Make a loop for each artist
1443                         }
1444
1445                         if (!track.date.slicedYear){
1446                                 track.date.slicedYear = settings.dateFormatYear == "2" ? track.date.year.slice(2, 4) : track.date.year.slice(0, 4)
1447                         }
1448
1449                         // Acquiring bpm (only if necessary)
1450                         if (settings.tags.bpm){
1451                                 logger.info(`[${track.artist.name} - ${track.title}] Getting BPM`);
1452                                 track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1453                                 try{
1454                                         track.bpm = track.legacyTrack.bpm
1455                                 }catch(err){
1456                                         track.bpm = 0
1457                                 }
1458                         }else{
1459                                 track.bpm = 0
1460                         }
1461
1462                         // Acquiring ReplayGain value (only if necessary)
1463                         if (settings.tags.replayGain){
1464                                 logger.info(`[${track.artist.name} - ${track.title}] Getting track gain`);
1465                                 if (!track.legacyTrack) track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1466                                 try{
1467                                         track.replayGain = track.legacyTrack.gain
1468                                 }catch(err){
1469                                         track.replayGain = 0
1470                                 }
1471                         }else{
1472                                 track.replayGain = 0
1473                         }
1474
1475                         // Acquiring discNumber value (only if necessary)
1476                         if (settings.tags.discNumber && !track.discNumber){
1477                                 if (settings.savePlaylistAsCompilation && settings.plName){
1478                                         track.discNumber = 1
1479                                 }else{
1480                                         logger.info(`[${track.artist.name} - ${track.title}] Getting disc number`);
1481                                         if (!track.legacyTrack) track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1482                                         track.discNumber = track.legacyTrack.disk_number
1483                                 }
1484                         }
1485
1486                         let separator = settings.multitagSeparator
1487                         if (separator == "null") separator = String.fromCharCode(0)
1488
1489                         // Autoremoves (Album Version) from the title
1490                         if (settings.removeAlbumVersion){
1491                                 if(track.title.indexOf("Album Version")>-1){
1492                                         track.title = track.title.replace(/ ?\(Album Version\)/g,"")
1493                                 }
1494                         }
1495
1496                         // See if you already have the artist picture
1497                         if (!track.album.artist.picture && !(settings.savePlaylistAsCompilation && settings.plName)){
1498                                 if (track.artist.name == track.album.artist.name && !track.album.artist.picture){
1499                                         track.album.artist.picture = track.artist.picture
1500                                 }else{
1501                                         let found = false
1502                                         if (track.artists){
1503                                                 track.artists.forEach(x=>{
1504                                                         if(!found && x.name == track.album.artist.name){
1505                                                                 track.album.artist.picture = x.picture
1506                                                                 found = true
1507                                                         }
1508                                                 })
1509                                         }
1510                                         if(settings.saveArtworkArtist && !found){
1511                                                 artist = await s.Deezer.legacyGetArtist(track.album.artist.id)
1512                                                 track.album.artist.picture = artist.picture_small.substring(46,ajson.artist.picture_small.length-24)
1513                                         }
1514                                 }
1515                         }
1516                         if (!track.album.artist.picture) track.album.artist.picture = ""
1517                         track.album.artist.pictureUrl = `${s.Deezer.artistPicturesHost}${track.album.artist.picture}/${settings.artworkSize}x${settings.artworkSize}-000000-80-0-0${(settings.PNGcovers ? ".png" : ".jpg")}`
1518                         if (settings.savePlaylistAsCompilation && settings.plName){
1519                                 track.album.pictureUrl = settings.playlist.cover
1520                         }else{
1521                                 track.album.pictureUrl = `${s.Deezer.albumPicturesHost}${track.album.picture}/${settings.artworkSize}x${settings.artworkSize}-000000-80-0-0${(settings.PNGcovers ? ".png" : ".jpg")}`
1522                         }
1523
1524                         // Auto detect aviable track format from settings
1525                         switch(downloadQueue[queueId].bitrate.toString()){
1526                                 case "9":
1527                                         track.selectedFormat = 9
1528                                         track.selectedFilesize = track.filesize.flac
1529                                         if (track.filesize.flac>0) break
1530                                         if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1531                                 case "3":
1532                                         track.selectedFormat = 3
1533                                         track.selectedFilesize = track.filesize.mp3_320
1534                                         if (track.filesize.mp3_320>0) break
1535                                         if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1536                                 case "1":
1537                                         track.selectedFormat = 1
1538                                         track.selectedFilesize = track.filesize.mp3_128
1539                                         if (track.filesize.mp3_128>0) break
1540                                         if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1541                                 default:
1542                                         track.selectedFormat = 8
1543                                         track.selectedFilesize = track.filesize.default
1544                         }
1545                         track.album.bitrate = track.selectedFormat
1546
1547                         if(track.contributor){
1548                                 if(track.contributor.composer){
1549                                         track.composerString = uniqueArray(track.contributor.composer)
1550                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.composerString = track.composerString.join(separator)
1551                                 }
1552                                 if(track.contributor.musicpublisher){
1553                                         track.musicpublisherString = uniqueArray(track.contributor.musicpublisher)
1554                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.musicpublisherString = track.musicpublisherString.join(separator)
1555                                 }
1556                                 if(track.contributor.producer){
1557                                         track.producerString = uniqueArray(track.contributor.producer)
1558                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.producerString = track.producerString.join(separator)
1559                                 }
1560                                 if(track.contributor.engineer){
1561                                         track.engineerString = uniqueArray(track.contributor.engineer)
1562                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.engineerString = track.engineerString.join(separator)
1563                                 }
1564                                 if(track.contributor.writer){
1565                                         track.writerString = uniqueArray(track.contributor.writer)
1566                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.writerString = track.writerString.join(separator)
1567                                 }
1568                                 if(track.contributor.author){
1569                                         track.authorString = uniqueArray(track.contributor.author)
1570                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.authorString = track.authorString.join(separator)
1571                                 }
1572                                 if(track.contributor.mixer){
1573                                         track.mixerString = uniqueArray(track.contributor.mixer)
1574                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.mixerString = track.mixerString.join(separator)
1575                                 }
1576                         }
1577
1578                         if(track.artists || track.artistsString){
1579                                 if (!track.artistsString){
1580                                         track.artistsString = []
1581                                         artistArray = []
1582                                         track.artists.forEach(function(artist){
1583                                                 artistArray.push(artist.name)
1584                                         })
1585                                 }else{
1586                                         if (! Array.isArray(track.artistsString)){
1587                                                 track.artistsString = [track.artistsString,]
1588                                         }
1589                                         artistArray = track.artistsString
1590                                 }
1591                                 track.artistsString = uniqueArray(artistArray)
1592                                 let posMainArtist = track.artistsString.indexOf(track.album.artist.name)
1593                                 if (posMainArtist !== -1 && posMainArtist !== 0){
1594                                         let element = track.artistsString[posMainArtist]
1595                                         track.artistsString.splice(posMainArtist, 1)
1596                                         track.artistsString.splice(0, 0, element)
1597                                 }
1598                                 if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.artistsString = track.artistsString.join(separator)
1599                         }
1600                         if (track.album.genre){
1601                                 if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0)))
1602                                         track.album.genreString = track.album.genre.join(separator)
1603                                 else
1604                                         track.album.genreString = track.album.genre
1605                         }
1606
1607                         if (track.date){
1608                                 let date
1609                                 switch (settings.dateFormat){
1610                                         case "0": date = `${track.date.slicedYear}-${track.date.month}-${track.date.day}`; break;
1611                                         case "1": date = `${track.date.day}-${track.date.month}-${track.date.slicedYear}`; break;
1612                                         case "2": date = `${track.date.month}-${track.date.day}-${track.date.slicedYear}`; break;
1613                                         case "3": date = `${track.date.slicedYear}-${track.date.day}-${track.date.month}`; break;
1614                                         default: date = `${track.date.slicedYear}-${track.date.month}-${track.date.day}`; break;
1615                                 }
1616                                 track.dateString = date;
1617                                 track.id3dateString = `${track.date.day}${track.date.month}`;
1618                         }
1619                 }else{
1620                         track.date = {year: 0,day: 0,month: 0}
1621                         track.selectedFilesize = track.filesize
1622                         track.selectedFormat = 3
1623                         track.album.bitrate = 3
1624                 }
1625                 track.album.date = track.dateString
1626                 track.album.year = track.date.year
1627
1628                 // TODO: Move to a separate function
1629                 // Generating file name
1630                 if (settings.saveFullArtists && settings.multitagSeparator != null){
1631                         let filename = antiDot(fixName(`${track.artistsString} - ${track.title}`));
1632                 }else{
1633                         let filename = antiDot(fixName(`${track.artist.name} - ${track.title}`));
1634                 }
1635                 if (settings.filename) {
1636                         filename = antiDot(fixName(settingsRegex(track, settings.filename, settings.playlist)))
1637                 }
1638
1639                 filename = antiDot(fixName(filename))
1640
1641                 // TODO: Move to a separate function
1642                 // Generating file path
1643                 let filepath = mainFolder;
1644                 let artistPath;
1645                 if ((settings.createArtistFolder || settings.createAlbumFolder) && (!settings.plName || (settings.createFoldersPlaylist || settings.savePlaylistAsCompilation) && settings.plName)) {
1646
1647                         if(settings.plName && settings.createFoldersPlaylist && !settings.savePlaylistAsCompilation){
1648                                 filepath += antiDot(fixName(settings.plName)) + path.sep;
1649                                 downloadQueue[queueId].filePath = filepath
1650                         }
1651
1652                         if (settings.createArtistFolder) {
1653                                 if(settings.artName){
1654                                         filepath += antiDot(fixName(settings.artName)) + path.sep;
1655                                 }else{
1656                                         filepath += antiDot(fixName(track.album.artist.name)) + path.sep;
1657                                 }
1658                                 artistPath = filepath;
1659                         }
1660
1661                         if (settings.createAlbumFolder) {
1662                                 if(settings.artName){
1663                                         filepath += antiDot(fixName(settingsRegexAlbum(track.album, settings.foldername))) + path.sep;
1664                                 }else{
1665                                         filepath += antiDot(fixName(settingsRegexAlbum(track.album, settings.foldername))) + path.sep;
1666                                 }
1667                         }
1668                 } else if (settings.plName) {
1669                         filepath += antiDot(fixName(settings.plName)) + path.sep;
1670                 } else if (settings.artName) {
1671                         filepath += antiDot(fixName(settingsRegexAlbum(track.album, settings.foldername))) + path.sep;
1672                 }
1673                 let coverpath = filepath;
1674                 if(!(settings.plName && settings.createFoldersPlaylist && !settings.savePlaylistAsCompilation)) downloadQueue[queueId].filePath = filepath
1675
1676                 if (track.album.discTotal > 1 && (settings.artName || settings.createAlbumFolder) && settings.createCDFolder && (!settings.plName || settings.createFoldersPlaylist && settings.plName)){
1677                         filepath += `CD${track.discNumber + path.sep}`
1678                 }
1679
1680                 let writePath;
1681                 if(track.selectedFormat == 9){
1682                         writePath = filepath + filename + '.flac';
1683                 }else{
1684                         writePath = filepath + filename + '.mp3';
1685                 }
1686
1687                 if ((settings.syncedlyrics || settings.tags.unsynchronisedLyrics) && track.lyricsId>0){
1688                         let lyr = await s.Deezer.getLyrics(track.id)
1689                         track.syncLyrics = lyr.syncLyrics
1690                         track.unsyncLyrics = lyr.unsyncLyrics
1691                 }
1692
1693                 if(track.syncLyrics && settings.syncedlyrics){
1694                         fs.outputFile(writePath.substring(0,writePath.lastIndexOf('.'))+".lrc",track.syncLyrics,function(){});
1695                 }
1696
1697                 track.playlistData = [0,""]
1698                 if (settings.createM3UFile && (settings.plName || settings.albName)) {
1699                         if (typeof track.position != 'undefined'){
1700                                 track.playlistData = [parseInt(track.position), writePath];
1701                         }else{
1702                                 track.playlistData = [track.trackNumber-1, writePath];
1703                         }
1704                 }
1705                 if (fs.existsSync(writePath)) {
1706                         logger.info(`[${track.artist.name} - ${track.title}] Already downloaded`);
1707                         return;
1708                 }else{
1709                         logger.info(`[${track.artist.name} - ${track.title}] Downloading file to ${writePath}`);
1710                 }
1711                 // Get cover image
1712                 if (track.album.pictureUrl) {
1713                         let imgPath;
1714                         //If its not from an album but a playlist.
1715                         if(settings.albName || settings.createAlbumFolder){
1716                                 if (settings.saveArtwork && !settings.plName)
1717                                         imgPath = coverpath + settingsRegexCover(settings.coverImageTemplate,track.album.artist.name,track.album.title)+(settings.PNGcovers ? ".png" : ".jpg")
1718                                 else
1719                                         imgPath = coverArtFolder + ((settings.savePlaylistAsCompilation && settings.plName) ? fixName(`${track.album.artist.name} - ${track.album.title}`) : track.album.barcode ? fixName(track.album.barcode) : fixName(`${track.album.artist.name} - ${track.album.title}`))+(settings.PNGcovers ? ".png" : ".jpg")
1720                         }else{
1721                                 imgPath = coverArtFolder + ((settings.savePlaylistAsCompilation && settings.plName) ? fixName(`${track.album.artist.name} - ${track.album.title}`) : track.album.barcode ? fixName(track.album.barcode) : fixName(`${track.album.artist.name} - ${track.album.title}`))+(settings.PNGcovers ? ".png" : ".jpg")
1722                         }
1723                         if(fs.existsSync(imgPath)){
1724                                 track.album.picturePath = (imgPath).replace(/\\/g, "/")
1725                                 logger.info(`[${track.artist.name} - ${track.title}] Cover already downloaded`)
1726                         }else{
1727                                 try{
1728                                         var body = await request.get(track.album.pictureUrl, {strictSSL: false,encoding: 'binary'})
1729                                         fs.outputFileSync(imgPath,body,'binary')
1730                                         track.album.picturePath = (imgPath).replace(/\\/g, "/")
1731                                         logger.info(`[${track.artist.name} - ${track.title}] Cover downloaded!`)
1732                                 }catch(error){
1733                                         logger.error(`[${track.artist.name} - ${track.title}] Cannot download Album Image: ${error}`)
1734                                         logger.error(`Album art link: ${track.album.pictureUrl}`)
1735                                         track.album.pictureUrl = undefined
1736                                         track.album.picturePath = undefined
1737                                 }
1738                         }
1739                 }else{
1740                         track.album.pictureUrl = undefined
1741                         logger.info(`[${track.artist.name} - ${track.title}] No cover found`)
1742                 }
1743
1744                 // Get Artist Image
1745                 if (parseInt(track.id)>0 && track.album.artist.pictureUrl && settings.saveArtworkArtist) {
1746                         let imgPath;
1747                         if(settings.createArtistFolder && artistPath){
1748                                 imgPath = artistPath + antiDot(settingsRegexArtistCover(settings.artistImageTemplate,track.album.artist.name))+(settings.PNGcovers ? ".png" : ".jpg");
1749                                 if(!fs.existsSync(imgPath)){
1750                                         try{
1751                                                 var body = await request.get(track.album.artist.pictureUrl, {strictSSL: false,encoding: 'binary'})
1752                                                 if (body.indexOf("unauthorized")>-1) throw new Error("Unauthorized")
1753                                                 fs.outputFileSync(imgPath,body,'binary')
1754                                                 logger.info(`[${track.artist.name} - ${track.title}] Saved Artist Image`)
1755                                         }catch(err){
1756                                                 logger.error(`[${track.artist.name} - ${track.title}] Cannot download Artist Image: ${err}`)
1757                                         }
1758                                 }
1759                         }
1760                 }
1761
1762                 logger.info(`[${track.artist.name} - ${track.title}] Starting the download process`)
1763
1764                 let tempPath
1765                 if(parseInt(track.id)>0)
1766                         tempPath = `${filepath}${track.id}_${track.selectedFormat}.temp`
1767                 else
1768                         tempPath = writePath
1769
1770                 logger.info(`[${track.artist.name} - ${track.title}] Downloading track`)
1771                 var downloadingPromise = new Promise((resolve, reject)=>{
1772                         let req = requestOld.get({url: track.getDownloadUrl(track.selectedFormat), strictSSL: false, headers: s.Deezer.httpHeaders, encoding: 'binary'}, function (error, response, body) {
1773                                 if (error){
1774                                         logger.error(`[${track.artist.name} - ${track.title}] Downloading error: ${error}`)
1775                                         reject("Downloading error: "+error)
1776                                         return false
1777                                 }
1778                                 if (!downloadQueue[queueId]){
1779                                         fs.remove(tempPath)
1780                                         reject("Not in Queue")
1781                                         return false
1782                                 }
1783                                 if (body.length == 0){
1784                                         fs.remove(tempPath)
1785                                         reject("Track is Empty")
1786                                         return false
1787                                 }
1788                                 logger.info(`[${track.artist.name} - ${track.title}] Decrypting track`)
1789                                 var decryptedSource = s.Deezer.decryptDownload(Buffer.from(body, 'binary'), track.id)
1790                                 try{
1791                                         fs.outputFileSync(tempPath,decryptedSource)
1792                                         resolve()
1793                                 }catch(err){
1794                                         logger.error(`[${track.artist.name} - ${track.title}] Decryption error: ${err}`)
1795                                         reject(err)
1796                                         return false
1797                                 }
1798                         }).on("data", function(data) {
1799                                 if (!downloadQueue[queueId]){
1800                                         reject("Not in Queue")
1801                                         return false
1802                                 }
1803                         })
1804                         if((downloadQueue[queueId]) && downloadQueue[queueId].type == "track"){
1805                                 let chunkLength = 0
1806                                 req.on("data", function(data) {
1807                                         if (!downloadQueue[queueId]){
1808                                                 reject("Not in Queue")
1809                                         }
1810                                         chunkLength += data.length
1811                                         try{
1812                                                 if (!downloadQueue[queueId].percentage) {
1813                                                         downloadQueue[queueId].percentage = 0
1814                                                 }
1815                                                 let complete = track.selectedFilesize
1816                                                 let percentage = (chunkLength / complete) * 100;
1817                                                 if ((percentage - downloadQueue[queueId].percentage > 1) || (chunkLength == complete)) {
1818                                                         downloadQueue[queueId].percentage = percentage
1819                                                         io.sockets.emit("downloadProgress", {
1820                                                                 queueId: queueId,
1821                                                                 percentage: downloadQueue[queueId].percentage-5
1822                                                         })
1823                                                 }
1824                                         }catch(err){}
1825                                 })
1826                         }
1827                 })
1828
1829                 try{
1830                         await downloadingPromise
1831                 }catch(err){
1832                         if (err==="Track is Empty"){
1833                                 if(track.fallbackId && track.fallbackId != "0"){
1834                                         logger.warn(`[${track.artist.name} - ${track.title}] Track is empty, falling on alternative`)
1835                                         var _track = await s.Deezer.getTrack(track.fallbackId)
1836                                         track.id = _track.id
1837                                         track.fallbackId = _track.fallbackId
1838                                         track.filesize = _track.filesize
1839                                         track.duration = _track.duration
1840                                         track.MD5 = _track.MD5
1841                                         track.mediaVersion = _track.mediaVersion
1842                                         return downloadTrackObject(track, queueId, settings)
1843                                 }else if(!track.searched && settings.fallbackSearch){
1844                                         logger.warn(`[${track.artist.name} - ${track.title}] Track is empty, searching for alternative`)
1845                                         _trackId = await convertMetadata2Deezer(track.artist.name, track.title, track.album.title)
1846                                         if (_trackId != "0"){
1847                                                 _track = await s.Deezer.getTrack(_trackId)
1848                                                 track.id = _track.id
1849                                                 track.fallbackId = _track.fallbackId
1850                                                 track.filesize = _track.filesize
1851                                                 track.duration = _track.duration
1852                                                 track.MD5 = _track.MD5
1853                                                 track.mediaVersion = _track.mediaVersion
1854                                                 track.searched = true
1855                                                 return downloadTrackObject(track, queueId, settings)
1856                                         }else{
1857                                                 logger.error(`[${track.artist.name} - ${track.title}] No alternative found`)
1858                                                 throw new Error("No Alternative Found")
1859                                                 return
1860                                         }
1861                                 }else{
1862                                         logger.error(`[${track.artist.name} - ${track.title}] Downloading error: Track is Empty`)
1863                                         throw new Error("Track is Empty")
1864                                         return
1865                                 }
1866                         }else{
1867                                 throw new Error(err)
1868                                 return
1869                         }
1870                 }
1871
1872                 logger.info(`[${track.artist.name} - ${track.title}] Adding Tags`)
1873                 if (parseInt(track.id)>0){
1874                         if(track.selectedFormat == 9){
1875                                 let flacComments = [];
1876                                 if (settings.tags.title)
1877                                         flacComments.push('TITLE=' + track.title);
1878                                 if (settings.tags.album)
1879                                         flacComments.push('ALBUM=' + track.album.title);
1880                                 if (settings.tags.albumArtist)
1881                                         flacComments.push('ALBUMARTIST=' + track.album.artist.name);
1882                                 if (settings.tags.trackNumber)
1883                                         flacComments.push('TRACKNUMBER=' + track.trackNumber);
1884                                 if (settings.tags.discNumber)
1885                                         flacComments.push('DISCNUMBER=' + track.discNumber);
1886                                 if (settings.tags.trackTotal)
1887                                         flacComments.push('TRACKTOTAL=' + track.album.trackTotal);
1888                                 if (settings.tags.explicit)
1889                                         flacComments.push('ITUNESADVISORY=' + track.explicit);
1890                                 if (settings.tags.isrc)
1891                                         flacComments.push('ISRC=' + track.ISRC);
1892                                 if (settings.tags.artist && track.artistsString)
1893                                         if (Array.isArray(track.artistsString)){
1894                                                 track.artistsString.forEach(x=>{
1895                                                         flacComments.push('ARTIST=' + x);
1896                                                 });
1897                                         }else{
1898                                                 flacComments.push('ARTIST=' + track.artistsString);
1899                                         }
1900                                 if (settings.tags.discTotal)
1901                                         flacComments.push('DISCTOTAL='+track.album.discTotal);
1902                                 if (settings.tags.length)
1903                                         flacComments.push('LENGTH=' + track.duration);
1904                                 if (settings.tags.barcode && track.album.barcode)
1905                                         flacComments.push('BARCODE=' + track.album.barcode);
1906                                 if (track.unsyncLyrics && settings.tags.unsynchronisedLyrics)
1907                                         flacComments.push('LYRICS='+track.unsyncLyrics.lyrics);
1908                                 if (track.album.genreString && settings.tags.genre)
1909                                         if (Array.isArray(track.album.genreString)){
1910                                                 track.album.genreString.forEach(x=>{
1911                                                         flacComments.push('GENRE=' + x);
1912                                                 });
1913                                         }else{
1914                                                 flacComments.push('GENRE=' + track.album.genreString);
1915                                         }
1916                                 if (track.copyright && settings.tags.copyright)
1917                                         flacComments.push('COPYRIGHT=' + track.copyright);
1918                                 if (0 < parseInt(track.date.year)){
1919                                         if (settings.tags.date)
1920                                                 flacComments.push('DATE=' + track.dateString);
1921                                         else if (settings.tags.year)
1922                                                 flacComments.push('DATE=' + track.date.year);
1923                                 }
1924                                 if (0 < parseInt(track.bpm) && settings.tags.bpm)
1925                                         flacComments.push('BPM=' + track.bpm);
1926                                 if(track.album.label && settings.tags.publisher)
1927                                         flacComments.push('PUBLISHER=' + track.album.label);
1928                                 if(track.composerString && settings.tags.composer)
1929                                         if (Array.isArray(track.composerString)){
1930                                                 track.composerString.forEach(x=>{
1931                                                         flacComments.push('COMPOSER=' + x);
1932                                                 });
1933                                         }else{
1934                                                 flacComments.push('COMPOSER=' + track.composerString);
1935                                         }
1936                                 if(track.musicpublisherString && settings.tags.musicpublisher)
1937                                         if (Array.isArray(track.musicpublisherString)){
1938                                                 track.musicpublisherString.forEach(x=>{
1939                                                         flacComments.push('ORGANIZATION=' + x);
1940                                                 });
1941                                         }else{
1942                                                 flacComments.push('ORGANIZATION=' + track.musicpublisherString);
1943                                         }
1944                                 if(track.mixerString && settings.tags.mixer)
1945                                         if (Array.isArray(track.mixerString)){
1946                                                 track.mixerString.forEach(x=>{
1947                                                         flacComments.push('MIXER=' + x);
1948                                                 });
1949                                         }else{
1950                                                 flacComments.push('MIXER=' + track.mixerString);
1951                                         }
1952                                 if(track.authorString && settings.tags.author)
1953                                         if (Array.isArray(track.authorString)){
1954                                                 track.authorString.forEach(x=>{
1955                                                         flacComments.push('AUTHOR=' + x);
1956                                                 });
1957                                         }else{
1958                                                 flacComments.push('AUTHOR=' + track.authorString);
1959                                         }
1960                                 if(track.writerString && settings.tags.writer)
1961                                         if (Array.isArray(track.writerString)){
1962                                                 track.writerString.forEach(x=>{
1963                                                         flacComments.push('WRITER=' + x);
1964                                                 });
1965                                         }else{
1966                                                 flacComments.push('WRITER=' + track.writerString);
1967                                         }
1968                                 if(track.engineerString && settings.tags.engineer)
1969                                         if (Array.isArray(track.engineerString)){
1970                                                 track.engineerString.forEach(x=>{
1971                                                         flacComments.push('ENGINEER=' + x);
1972                                                 });
1973                                         }else{
1974                                                 flacComments.push('ENGINEER=' + track.engineerString);
1975                                         }
1976                                 if(track.producerString && settings.tags.producer)
1977                                         if (Array.isArray(track.producerString)){
1978                                                 track.producerString.forEach(x=>{
1979                                                         flacComments.push('PRODUCER=' + x);
1980                                                 });
1981                                         }else{
1982                                                 flacComments.push('PRODUCER=' + track.producerString);
1983                                         }
1984                                 if(track.replayGain && settings.tags.replayGain)
1985                                         flacComments.push('REPLAYGAIN_TRACK_GAIN=' + track.replayGain);
1986
1987                                 const reader = fs.createReadStream(tempPath);
1988                                 const writer = fs.createWriteStream(writePath);
1989                                 let processor = new mflac.Processor({parseMetaDataBlocks: true});
1990                                 let vendor = 'reference libFLAC 1.2.1 20070917';
1991                                 let cover = null;
1992                                 if(track.album.picturePath && settings.tags.cover){
1993                                         cover = fs.readFileSync(track.album.picturePath)
1994                                 }
1995                                 let mdbVorbisPicture
1996                                 let mdbVorbisComment
1997                                 processor.on('preprocess', (mdb) => {
1998                                         // Remove existing VORBIS_COMMENT and PICTURE blocks, if any.
1999                                         if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type) {
2000                                                 mdb.remove();
2001                                         } else if (mflac.Processor.MDB_TYPE_PICTURE === mdb.type) {
2002                                                 mdb.remove();
2003                                         }
2004                                         if (mdb.isLast) {
2005                                                 if(cover){
2006                                                         mdbVorbisPicture = mflac.data.MetaDataBlockPicture.create(true, 3, `image/${(settings.PNGcovers ? "png" : "jpeg")}`, '', settings.artworkSize, settings.artworkSize, 24, 0, cover);
2007                                                 }
2008                                                 mdbVorbisComment = mflac.data.MetaDataBlockVorbisComment.create(!cover, vendor, flacComments);
2009                                                 mdb.isLast = false;
2010                                         }
2011                                 });
2012                                 processor.on('postprocess', (mdb) => {
2013                                         if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type && null !== mdb.vendor) {
2014                                                 vendor = mdb.vendor;
2015                                         }
2016                                         if (mdbVorbisPicture && mdbVorbisComment) {
2017                                                         processor.push(mdbVorbisComment.publish());
2018                                                         processor.push(mdbVorbisPicture.publish());
2019                                                 }else if(mdbVorbisComment){
2020                                                         processor.push(mdbVorbisComment.publish());
2021                                         }
2022                                 });
2023                                 reader.on('end', () => {
2024                                         fs.remove(tempPath);
2025                                 });
2026                                 await reader.pipe(processor).pipe(writer);
2027                         }else{
2028                                 const songBuffer = fs.readFileSync(tempPath);
2029                                 const writer = new ID3Writer(songBuffer);
2030                                 if (settings.tags.title)
2031                                         writer.setFrame('TIT2', track.title)
2032                                 if (settings.tags.artist)
2033                                         writer.setFrame('TPE1', [track.artistsString])
2034                                 if (settings.tags.album)
2035                                         writer.setFrame('TALB', track.album.title)
2036                                 if (settings.tags.albumArtist && track.album.artist)
2037                                         writer.setFrame('TPE2', track.album.artist.name)
2038                                 if (settings.tags.trackNumber)
2039                                         writer.setFrame('TRCK', (settings.tags.trackTotal ? track.trackNumber+"/"+track.album.trackTotal : track.trackNumber))
2040                                 if (settings.tags.discNumber)
2041                                         writer.setFrame('TPOS', (settings.tags.discTotal ? track.discNumber+"/"+track.album.discTotal : track.discNumber))
2042                                 if (settings.tags.isrc)
2043                                         writer.setFrame('TSRC', track.ISRC);
2044
2045                                 if (settings.tags.length)
2046                                         writer.setFrame('TLEN', track.duration);
2047                                 if (settings.tags.barcode && track.album.barcode)
2048                                         writer.setFrame('TXXX', {
2049                                                 description: 'BARCODE',
2050                                                 value: track.album.barcode
2051                                         });
2052                                 if(track.album.picturePath && settings.tags.cover){
2053                                         const coverBuffer = fs.readFileSync(track.album.picturePath);
2054                                         writer.setFrame('APIC', {
2055                                                 type: 3,
2056                                                 data: coverBuffer,
2057                                                 description: ''
2058                                         });
2059                                 }
2060                                 if(track.unsyncLyrics && settings.tags.unsynchronisedLyrics)
2061                                         writer.setFrame('USLT', track.unsyncLyrics);
2062                                 if(track.album.label && settings.tags.publisher)
2063                                         writer.setFrame('TPUB', track.album.label);
2064                                 if(track.album.genreString && settings.tags.genre)
2065                                         writer.setFrame('TCON', [track.album.genreString]);
2066                                 if(track.copyright && settings.tags.copyright)
2067                                         writer.setFrame('TCOP', track.copyright);
2068                                 if (0 < parseInt(track.date.year)) {
2069                                         if (settings.tags.date)
2070                                                 writer.setFrame('TDAT', track.id3dateString);
2071                                         if (settings.tags.year)
2072                                                 writer.setFrame('TYER', track.date.year);
2073                                 }
2074                                 if (0 < parseInt(track.bpm) && settings.tags.bpm)
2075                                         writer.setFrame('TBPM', track.bpm);
2076                                 if(track.composerString && settings.tags.composer)
2077                                         writer.setFrame('TCOM', [track.composerString]);
2078                                 if(track.replayGain && settings.tags.replayGain)
2079                                         writer.setFrame('TXXX', {
2080                                                 description: 'REPLAYGAIN_TRACK_GAIN',
2081                                                 value: track.replayGain
2082                                         });
2083                                 writer.addTag();
2084                                 const taggedSongBuffer = Buffer.from(writer.arrayBuffer);
2085                                 fs.writeFileSync(writePath, taggedSongBuffer);
2086                                 fs.remove(tempPath);
2087                         }
2088                 }
2089                 logger.info(`[${track.artist.name} - ${track.title}] Downloaded`)
2090         }
2091 })
2092
2093 // Helper functions
2094
2095 /**
2096  * Updates individual parameters in the settings file
2097  * @param config
2098  * @param value
2099  */
2100 function updateSettingsFile(config, value) {
2101         configFile.userDefined[config] = value;
2102
2103         fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
2104                 if (err) return;
2105                 logger.info("Settings updated");
2106
2107                 // FIXME: Endless Loop, due to call from initFolders()...crashes soon after startup
2108                 // initFolders();
2109         });
2110 }
2111
2112 function fixName (txt) {
2113         txt = txt+""
2114         const regEx = /[\0\/\\:*?"<>|]/g;
2115         txt = txt.replace(regEx, '_');
2116         txt = txt.slice(0,200);
2117         return txt;
2118 }
2119
2120 function antiDot(str){
2121         while(str[str.length-1] == "." || str[str.length-1] == " " || str[str.length-1] == "\n"){
2122                 str = str.substring(0,str.length-1);
2123         }
2124         if(str.length < 1){
2125                 str = "dot";
2126         }
2127         return str;
2128 }
2129
2130 /**
2131  * Initialize the temp folder for covers and main folder for downloads
2132  */
2133 function initFolders() {
2134         // Check if main folder exists
2135         if (!fs.existsSync(mainFolder)) {
2136                 mainFolder = defaultDownloadFolder;
2137                 updateSettingsFile('downloadLocation', defaultDownloadFolder);
2138         }
2139         //fs.removeSync(coverArtFolder);
2140         //fs.ensureFolderSync(coverArtFolder);
2141 }
2142
2143 /**
2144  * Creates the name of the tracks replacing wildcards to correct metadata
2145  * @param track
2146  * @param filename
2147  * @param playlist
2148  * @returns {XML|string|*}
2149  */
2150 function settingsRegex(track, filename, playlist) {
2151         try{
2152                 filename = filename.replace(/%title%/g, fixName(track.title));
2153                 filename = filename.replace(/%album%/g, fixName(track.album.title));
2154                 filename = filename.replace(/%artist%/g, fixName(((configFile.userDefined.saveFullArtists && configFile.userDefined.multitagSeparator != null) ? track.artistsString : track.artist.name)));
2155                 filename = filename.replace(/%year%/g, fixName(track.date.year));
2156                 filename = filename.replace(/%date%/g, fixName(track.album.date));
2157                 filename = filename.replace(/%label%/g, fixName(track.album.label));
2158                 if(typeof track.trackNumber != 'undefined'){
2159                         if(configFile.userDefined.padtrck){
2160                                  filename = filename.replace(/%number%/g, fixName(pad(track.trackNumber, (parseInt(configFile.userDefined.paddingSize)>0 ? parseInt(configFile.userDefined.paddingSize) : track.album.trackTotal))));
2161                         }else{
2162                                 filename = filename.replace(/%number%/g, fixName(track.trackNumber));
2163                         }
2164                 } else {
2165                         filename = filename.replace(/%number%/g, '');
2166                 }
2167                 if (playlist && typeof track.position != 'undefined'){
2168                         if(configFile.userDefined.padtrck){
2169                                  filename = filename.replace(/%position%/g, fixName(pad(track.position+1, (parseInt(configFile.userDefined.paddingSize)>0 ? parseInt(configFile.userDefined.paddingSize) : playlist.fullSize))));
2170                         }else{
2171                                 filename = filename.replace(/%position%/g, fixName(track.position+1));
2172                         }
2173                 } else {
2174                         filename = filename.replace(/%position%/g, '');
2175                 }
2176                 filename = filename.replace(/%disc%/g, fixName(track.discNumber));
2177                 filename = filename.replace(/%explicit%/g, fixName((track.explicit==="1" ? (filename.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit Version)") : "")));
2178                 filename = filename.replace(/%genre%/g, fixName(track.album.genre ? (Array.isArray(track.album.genre) ? track.album.genre[0] : track.album.genre) : "Unknown"));
2179                 filename = filename.replace(/[/\\]/g, path.sep)
2180                 return filename.trim();
2181         }catch(e){
2182                 logger.error("settingsRegex failed: "+e)
2183         }
2184 }
2185
2186 /**
2187  * Creates the name of the albums folder replacing wildcards to correct metadata
2188  * @param metadata
2189  * @param foldername
2190  * @returns {XML|string|*}
2191  */
2192 function settingsRegexAlbum(album, foldername) {
2193         try{
2194                 foldername = foldername.replace(/%album%/g, fixName(album.title))
2195                 foldername = foldername.replace(/%artist%/g, fixName(album.artist.name))
2196                 foldername = foldername.replace(/%year%/g, fixName(album.year))
2197                 foldername = foldername.replace(/%date%/g, fixName(album.date))
2198                 switch(album.bitrate){
2199                         case 9:
2200                                 foldername = foldername.replace(/%bitrate%/g, "FLAC")
2201                         break
2202                         case 3:
2203                                 foldername = foldername.replace(/%bitrate%/g, "320")
2204                         break
2205                         case 1:
2206                                 foldername = foldername.replace(/%bitrate%/g, "128")
2207                         break
2208                         default:
2209                                 foldername = foldername.replace(/%bitrate%/g, "128")
2210                 }
2211                 if (album.recordType){
2212                         foldername = foldername.replace(/%type%/g, fixName(album.recordType[0].toUpperCase() + album.recordType.substring(1)))
2213                 }else{
2214                         foldername = foldername.replace(/%type%/g, "")
2215                 }
2216                 foldername = foldername.replace(/%label%/g, fixName(album.label))
2217                 foldername = foldername.replace(/%explicit%/g, fixName((album.explicit ? (foldername.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit) ") : "")))
2218                 foldername = foldername.replace(/%genre%/g, fixName(album.genres ? (Array.isArray(album.genres) ? album.genres[0] : album.genres) : "Unknown"))
2219                 foldername = foldername.replace(/[/\\]/g, path.sep)
2220                 return foldername.trim();
2221         }catch(e){
2222                 logger.error("settingsRegexAlbum failed: "+e)
2223         }
2224
2225 }
2226
2227 function settingsRegexCover(foldername, artist, name) {
2228         foldername = foldername.replace(/%name%/g, fixName(name));
2229         foldername = foldername.replace(/%artist%/g, fixName(artist));
2230         foldername = foldername.replace(/[/\\]/g, path.sep)
2231         return foldername;
2232 }
2233
2234 function settingsRegexArtistCover(foldername, artist) {
2235         foldername = foldername.replace(/%artist%/g, fixName(artist));
2236         foldername = foldername.replace(/[/\\]/g, path.sep)
2237         return foldername;
2238 }
2239
2240 /**
2241  * Pad number with 0s so max and str have the same nuber of characters
2242  * @param str
2243  * @param max
2244  * @returns {String|string|*}
2245  */
2246 function pad(str, max) {
2247         str = str.toString();
2248         max = max.toString();
2249         return str.length < max.length || str.length == 1 ? pad("0" + str, max) : str;
2250 }
2251
2252 /**
2253  * Splits the %number%
2254  * @param string str
2255  * @return string
2256  */
2257 function splitNumber(str,total){
2258         str = str.toString();
2259         let i = str.indexOf("/");
2260         if(total && i > 0){
2261                 return str.slice(i+1, str.length);
2262         }else if(i > 0){
2263                 return str.slice(0, i);
2264         }else{
2265                 return str;
2266         }
2267         return i > 0 ? str.slice(0, i) : str;
2268 }
2269
2270 function switchReleaseType(id){
2271         switch (id.toString()) {
2272                 case "0":
2273                         return "Album";
2274                 case "1":
2275                         return "Single";
2276                 case "3":
2277                         return "EP";
2278                 default:
2279                         return "Album";
2280         }
2281 }
2282
2283 function uniqueArray(origin, removeDupes=true){
2284         destination = []
2285         Array.from(new Set(origin)).forEach(function(x){
2286                 if(destination.indexOf(x) == -1)
2287                         destination.push(x);
2288         })
2289         if (removeDupes){
2290                 destination.forEach((name,index)=>{
2291                         destination.forEach((name2,index2)=>{
2292                                 if(!(index===index2) && (name.toLowerCase().indexOf(name2.toLowerCase())!== -1)){
2293                                         destination.splice(index, 1);
2294                                 }
2295                         })
2296                 })
2297         }
2298         return destination
2299 }
2300
2301 async function asyncForEach(array, callback) {
2302         for (let index = 0; index < array.length; index++) {
2303                 await callback(array[index], index, array);
2304         }
2305 }
2306
2307 // Show crash error in console for debugging
2308 process.on('unhandledRejection', function (err) {
2309         if (err) logger.error(err.stack ? err.stack : err)
2310
2311 })
2312 process.on('uncaughtException', function (err) {
2313         if (err) logger.error(err.stack ? err.stack : err)
2314 })
2315
2316 // Exporting vars
2317 module.exports.mainFolder = mainFolder
2318 module.exports.defaultSettings = defaultSettings
2319 module.exports.defaultDownloadFolder = defaultDownloadFolder