mirror of
https://gitlab.com/RemixDev/deemix-js.git
synced 2024-12-29 10:56:18 +00:00
611 lines
22 KiB
JavaScript
611 lines
22 KiB
JavaScript
const { Track, AlbumDoesntExists } = require('./types/Track.js')
|
|
const { StaticPicture } = require('./types/Picture.js')
|
|
const { streamTrack, generateStreamURL, DownloadCanceled } = require('./decryption.js')
|
|
const { tagID3, tagFLAC } = require('./tagger.js')
|
|
const { USER_AGENT_HEADER, pipeline } = require('./utils/index.js')
|
|
const { DEFAULTS, OverwriteOption } = require('./settings.js')
|
|
const { generatePath, generateAlbumName, generateArtistName, generateDownloadObjectName } = require('./utils/pathtemplates.js')
|
|
const { TrackFormats } = require('deezer-js')
|
|
const got = require('got')
|
|
const fs = require('fs')
|
|
const { tmpdir } = require('os')
|
|
const { queue, each } = require('async')
|
|
|
|
const extensions = {
|
|
[TrackFormats.FLAC]: '.flac',
|
|
[TrackFormats.LOCAL]: '.mp3',
|
|
[TrackFormats.MP3_320]: '.mp3',
|
|
[TrackFormats.MP3_128]: '.mp3',
|
|
[TrackFormats.DEFAULT]: '.mp3',
|
|
[TrackFormats.MP4_RA3]: '.mp4',
|
|
[TrackFormats.MP4_RA2]: '.mp4',
|
|
[TrackFormats.MP4_RA1]: '.mp4'
|
|
}
|
|
|
|
const TEMPDIR = tmpdir()+`/deemix-imgs`
|
|
fs.mkdirSync(TEMPDIR, { recursive: true })
|
|
|
|
async function downloadImage(url, path, overwrite){
|
|
if (fs.existsSync(path) && ![OverwriteOption.OVERWRITE, OverwriteOption.ONLY_TAGS, OverwriteOption.KEEP_BOTH].includes(overwrite)) return path
|
|
|
|
const downloadStream = got.stream(url, { headers: {'User-Agent': USER_AGENT_HEADER}, timeout: 30000})
|
|
const fileWriterStream = fs.createWriteStream(path)
|
|
|
|
await pipeline(downloadStream, fileWriterStream)
|
|
return path
|
|
}
|
|
|
|
async function getPreferredBitrate(track, bitrate, shouldFallback, uuid, listener){
|
|
bitrate = parseInt(bitrate)
|
|
if (track.localTrack) { return TrackFormats.LOCAL }
|
|
|
|
let falledBack = false
|
|
|
|
const formats_non_360 = {
|
|
[TrackFormats.FLAC]: "FLAC",
|
|
[TrackFormats.MP3_320]: "MP3_320",
|
|
[TrackFormats.MP3_128]: "MP3_128"
|
|
}
|
|
const formats_360 = {
|
|
[TrackFormats.MP4_RA3]: "MP4_RA3",
|
|
[TrackFormats.MP4_RA2]: "MP4_RA2",
|
|
[TrackFormats.MP4_RA1]: "MP4_RA1"
|
|
}
|
|
|
|
const is360Format = Object.keys(formats_360).includes(bitrate)
|
|
let formats
|
|
if (!shouldFallback){
|
|
formats = {...formats_360, ...formats_non_360}
|
|
}else if (is360Format){
|
|
formats = {...formats_360}
|
|
}else{
|
|
formats = {...formats_non_360}
|
|
}
|
|
|
|
for (let i = 0; i < Object.keys(formats).length; i++){
|
|
let formatNumber = Object.keys(formats).reverse()[i]
|
|
let formatName = formats[formatNumber]
|
|
|
|
if (formatNumber > bitrate) { continue }
|
|
if (Object.keys(track.filesizes).includes(`FILESIZE_${formatName}`)){
|
|
if (parseInt(track.filesizes[`FILESIZE_${formatName}`]) != 0) return formatNumber
|
|
if (!track.filesizes[`FILESIZE_${formatName}_TESTED`]){
|
|
let request
|
|
try {
|
|
request = got.get(
|
|
generateStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber),
|
|
{ headers: {'User-Agent': USER_AGENT_HEADER}, timeout: 30000 }
|
|
).on("response", (response)=>{
|
|
track.filesizes[`FILESIZE_${formatName}`] = response.headers["content-length"]
|
|
track.filesizes[`FILESIZE_${formatName}_TESTED`] = true
|
|
request.cancel()
|
|
}).on("error", (e)=>{
|
|
throw e
|
|
})
|
|
|
|
await request
|
|
} catch (e){
|
|
if (e.isCanceled) { return formatNumber }
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!shouldFallback){
|
|
throw new PreferredBitrateNotFound
|
|
}else if (!falledBack){
|
|
falledBack = true
|
|
if (listener && uuid){
|
|
listener.send("queueUpdate", {
|
|
uuid,
|
|
bitrateFallback: true,
|
|
data:{
|
|
id: track.id,
|
|
title: track.title,
|
|
artist: track.mainArtist.name
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
}
|
|
if (is360Format) throw new TrackNot360
|
|
return TrackFormats.DEFAULT
|
|
}
|
|
|
|
class Downloader {
|
|
constructor(dz, downloadObject, settings, listener){
|
|
this.dz = dz
|
|
this.downloadObject = downloadObject
|
|
this.settings = settings || DEFAULTS
|
|
this.bitrate = downloadObject.bitrate
|
|
this.listener = listener
|
|
|
|
this.extrasPath = null
|
|
this.playlistCovername = null
|
|
this.playlistURLs = []
|
|
}
|
|
|
|
async start(){
|
|
if (!this.downloadObject.isCanceled){
|
|
if (this.downloadObject.__type__ === "Single"){
|
|
let track = await this.downloadWrapper({
|
|
trackAPI_gw: this.downloadObject.single.trackAPI_gw,
|
|
trackAPI: this.downloadObject.single.trackAPI,
|
|
albumAPI: this.downloadObject.single.albumAPI
|
|
})
|
|
await this.afterDownloadSingle(track)
|
|
} else if (this.downloadObject.__type__ === "Collection") {
|
|
let tracks = []
|
|
|
|
let q = queue(async (data) => {
|
|
let {track, pos} = data
|
|
tracks[pos] = await this.downloadWrapper({
|
|
trackAPI_gw: track,
|
|
albumAPI: this.downloadObject.collection.albumAPI,
|
|
playlistAPI: this.downloadObject.collection.playlistAPI
|
|
})
|
|
}, this.settings.queueConcurrency)
|
|
|
|
this.downloadObject.collection.tracks_gw.forEach((track, pos) => {
|
|
q.push({track, pos})
|
|
})
|
|
|
|
await q.drain()
|
|
await this.afterDownloadCollection(tracks)
|
|
}
|
|
}
|
|
|
|
if (this.listener){
|
|
if (this.downloadObject.isCanceled){
|
|
this.listener.send('currentItemCancelled', this.downloadObject.uuid)
|
|
this.listener.send("removedFromQueue", this.downloadObject.uuid)
|
|
} else {
|
|
this.listener.send("finishDownload", this.downloadObject.uuid)
|
|
}
|
|
}
|
|
}
|
|
|
|
async download(extraData, track){
|
|
let returnData = {}
|
|
const { trackAPI_gw, trackAPI, albumAPI, playlistAPI } = extraData
|
|
if (this.downloadObject.isCanceled) throw new DownloadCanceled
|
|
if (trackAPI_gw.SNG_ID == "0") throw new DownloadFailed("notOnDeezer")
|
|
|
|
let itemName = `[${trackAPI_gw.ART_NAME} - ${trackAPI_gw.SNG_TITLE.trim()}]`
|
|
|
|
// Generate track object
|
|
if (!track){
|
|
track = new Track()
|
|
console.log(`${itemName} Getting tags`)
|
|
try{
|
|
await track.parseData(
|
|
this.dz,
|
|
trackAPI_gw.SNG_ID,
|
|
trackAPI_gw,
|
|
trackAPI,
|
|
null, // albumAPI_gw
|
|
albumAPI,
|
|
playlistAPI
|
|
)
|
|
} catch (e){
|
|
if (e instanceof AlbumDoesntExists) { throw new DownloadFailed('albumDoesntExists') }
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
}
|
|
if (this.downloadObject.isCanceled) throw new DownloadCanceled
|
|
|
|
itemName = `[${track.mainArtist.name} - ${track.title}]`
|
|
|
|
// Check if the track is encoded
|
|
if (track.MD5 === "") throw new DownloadFailed("notEncoded", track)
|
|
|
|
// Check the target bitrate
|
|
console.log(`${itemName} Getting bitrate`)
|
|
let selectedFormat
|
|
try{
|
|
selectedFormat = await getPreferredBitrate(
|
|
track,
|
|
this.bitrate,
|
|
true, // fallbackBitrate
|
|
this.downloadObject.uuid, this.listener
|
|
)
|
|
}catch (e){
|
|
if (e instanceof PreferredBitrateNotFound) { throw new DownloadFailed("wrongBitrate", track) }
|
|
if (e instanceof TrackNot360) { throw new DownloadFailed("no360RA") }
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
track.bitrate = selectedFormat
|
|
track.album.bitrate = selectedFormat
|
|
|
|
// Apply Settings
|
|
track.applySettings(this.settings)
|
|
|
|
// Generate filename and filepath from metadata
|
|
let {
|
|
filename,
|
|
filepath,
|
|
artistPath,
|
|
coverPath,
|
|
extrasPath
|
|
} = generatePath(track, this.downloadObject, this.settings)
|
|
if (this.downloadObject.isCanceled) throw new DownloadCanceled
|
|
|
|
// Make sure the filepath exsists
|
|
fs.mkdirSync(filepath, { recursive: true })
|
|
let extension = extensions[track.bitrate]
|
|
let writepath = `${filepath}/${filename}${extension}`
|
|
|
|
// Save extrasPath
|
|
if (extrasPath && !this.extrasPath) this.extrasPath = extrasPath
|
|
|
|
// Generate covers URLs
|
|
let embeddedImageFormat = `jpg-${this.settings.jpegImageQuality}`
|
|
if (this.settings.embeddedArtworkPNG) embeddedImageFormat = 'png'
|
|
|
|
track.album.embeddedCoverURL = track.album.pic.getURL(this.settings.embeddedArtworkSize, embeddedImageFormat)
|
|
let ext = track.album.embeddedCoverURL.slice(-4)
|
|
if (ext.charAt(0) != '.') ext = '.jpg'
|
|
track.album.embeddedCoverPath = `${TEMPDIR}/${track.album.isPlaylist ? 'pl'+track.playlist.id : 'alb'+track.album.id}_${this.settings.embeddedArtworkSize}${ext}`
|
|
|
|
// Download and cache the coverart
|
|
track.album.embeddedCoverPath = await downloadImage(track.album.embeddedCoverURL, track.album.embeddedCoverPath)
|
|
console.log(`${itemName} Albumart downloaded`)
|
|
|
|
// Save local album art
|
|
if (coverPath){
|
|
returnData.albumURLs = []
|
|
this.settings.localArtworkFormat.split(',').forEach((picFormat) => {
|
|
if (['png', 'jpg'].includes(picFormat)){
|
|
let extendedFormat = picFormat
|
|
if (extendedFormat == 'jpg') extendedFormat += `-${this.settings.jpegImageQuality}`
|
|
let url = track.album.pic.getURL(this.settings.localArtworkSize, extendedFormat)
|
|
// Skip non deezer pictures at the wrong format
|
|
if (track.album.pic instanceof StaticPicture && picFormat != 'jpg') return
|
|
returnData.albumURLs.push({url, ext: picFormat})
|
|
}
|
|
})
|
|
returnData.albumPath = coverPath
|
|
returnData.albumFilename = generateAlbumName(this.settings.coverImageTemplate, track.album, this.settings, track.playlist)
|
|
}
|
|
|
|
// Save artist art
|
|
if (artistPath){
|
|
returnData.artistURLs = []
|
|
this.settings.localArtworkFormat.split(',').forEach((picFormat) => {
|
|
// Deezer doesn't support png artist images
|
|
if (picFormat === 'jpg'){
|
|
let extendedFormat = `${picFormat}-${this.settings.jpegImageQuality}`
|
|
let url = track.album.pic.getURL(this.settings.localArtworkSize, extendedFormat)
|
|
// Skip non deezer pictures at the wrong format
|
|
if (track.album.mainArtist.pic.md5 == "") return
|
|
returnData.artistURLs.push({url, ext: picFormat})
|
|
}
|
|
})
|
|
returnData.artistPath = artistPath
|
|
returnData.artistFilename = generateArtistName(this.settings.artistImageTemplate, track.album.mainArtist, this.settings, track.album.rootArtist)
|
|
}
|
|
|
|
// Save playlist art
|
|
if (track.playlist){
|
|
if (this.playlistURLs.length == 0){
|
|
this.settings.localArtworkFormat.split(',').forEach((picFormat) => {
|
|
if (['png', 'jpg'].includes(picFormat)){
|
|
let extendedFormat = picFormat
|
|
if (extendedFormat == 'jpg') extendedFormat += `-${this.settings.jpegImageQuality}`
|
|
let url = track.playlist.pic.getURL(this.settings.localArtworkSize, extendedFormat)
|
|
// Skip non deezer pictures at the wrong format
|
|
if (track.playlist.pic instanceof StaticPicture && picFormat != 'jpg') return
|
|
this.playlistURLs.push({url, ext: picFormat})
|
|
}
|
|
})
|
|
}
|
|
if (!this.playlistCovername){
|
|
track.playlist.bitrate = track.bitrate
|
|
track.playlist.dateString = track.playlist.date.format(this.settings.dateFormat)
|
|
this.playlistCovername = generateAlbumName(this.settings.coverImageTemplate, track.playlist, this.settings, track.playlist)
|
|
}
|
|
}
|
|
|
|
// Save lyrics in lrc file
|
|
if (this.settings.syncedLyrics && track.lyrics.sync){
|
|
if (!fs.existsSync(`${filepath}/${filename}.lrc`) || [OverwriteOption.OVERWRITE, OverwriteOption.ONLY_TAGS].includes(this.settings.overwriteFile))
|
|
fs.writeFileSync(`${filepath}/${filename}.lrc`, track.lyrics.sync)
|
|
}
|
|
|
|
// Check for overwrite settings
|
|
let trackAlreadyDownloaded = fs.existsSync(writepath)
|
|
|
|
// Don't overwrite and don't mind extension
|
|
if (!trackAlreadyDownloaded && this.settings.overwriteFile == OverwriteOption.DONT_CHECK_EXT){
|
|
let extensions = ['.mp3', '.flac', '.opus', '.m4a']
|
|
let baseFilename = `${filepath}/${filename}`
|
|
for (let i = 0; i < extensions.length; i++){
|
|
let ext = extensions[i]
|
|
trackAlreadyDownloaded = fs.existsSync(baseFilename+ext)
|
|
if (trackAlreadyDownloaded) break
|
|
}
|
|
}
|
|
|
|
// Don't overwrite and keep both files
|
|
if (trackAlreadyDownloaded && this.settings.overwriteFile == OverwriteOption.KEEP_BOTH){
|
|
let baseFilename = `${filepath}/${filename}`
|
|
let currentFilename
|
|
let c = 0
|
|
do {
|
|
c++
|
|
currentFilename = `${baseFilename} (${c})${extension}`
|
|
} while (fs.existsSync(currentFilename))
|
|
trackAlreadyDownloaded = false
|
|
writepath = currentFilename
|
|
}
|
|
|
|
// Download the track
|
|
if (!trackAlreadyDownloaded || this.settings.overwriteFile == OverwriteOption.OVERWRITE){
|
|
console.log(`${itemName} Downloading`)
|
|
track.downloadURL = generateStreamURL(track.id, track.MD5, track.mediaVersion, track.bitrate)
|
|
let stream = fs.createWriteStream(writepath)
|
|
try {
|
|
await streamTrack(stream, track, 0, this.downloadObject, this.listener)
|
|
} catch (e){
|
|
fs.unlinkSync(writepath)
|
|
if (e instanceof got.HTTPError) throw new DownloadFailed('notAvailable', track)
|
|
throw e
|
|
}
|
|
} else {
|
|
console.log(`${itemName} Skipping track as it's already downloaded`)
|
|
this.downloadObject.completeTrackProgress(this.listener)
|
|
}
|
|
|
|
// Adding tags
|
|
if (!trackAlreadyDownloaded || [OverwriteOption.ONLY_TAGS, OverwriteOption.OVERWRITE].includes(this.settings.overwriteFile) && !track.local){
|
|
console.log(`${itemName} Tagging file`)
|
|
if (extension == '.mp3'){
|
|
tagID3(writepath, track, this.settings.tags)
|
|
} else if (extension == '.flac'){
|
|
tagFLAC(writepath, track, this.settings.tags)
|
|
}
|
|
}
|
|
|
|
if (track.searched) returnData.searched = true
|
|
this.downloadObject.downloaded += 1
|
|
this.downloadObject.files.push(String(writepath))
|
|
if (this.listener)
|
|
this.listener.send('updateQueue', {
|
|
uuid: this.downloadObject.uuid,
|
|
downloaded: true,
|
|
downloadPath: String(writepath),
|
|
extrasPath: String(this.extrasPath)
|
|
})
|
|
returnData.filename = writepath.slice(extrasPath.length+1)
|
|
returnData.data = {
|
|
id: track.id,
|
|
title: track.title,
|
|
artist: track.mainArtist.name
|
|
}
|
|
return returnData
|
|
}
|
|
|
|
async downloadWrapper(extraData, track){
|
|
const { trackAPI_gw } = extraData
|
|
// Temp metadata to generate logs
|
|
let tempTrack = {
|
|
id: trackAPI_gw.SNG_ID,
|
|
title: trackAPI_gw.SNG_TITLE.trim(),
|
|
artist: trackAPI_gw.ART_NAME
|
|
}
|
|
if (trackAPI_gw.VERSION && trackAPI_gw.SNG_TITLE.includes(trackAPI_gw.VERSION))
|
|
tempTrack.title += ` ${trackAPI_gw.VERSION.trim()}`
|
|
|
|
let itemName = `[${tempTrack.artist} - ${tempTrack.title}]`
|
|
let result
|
|
try {
|
|
result = await this.download(extraData, track)
|
|
} catch (e){
|
|
if (e instanceof DownloadFailed){
|
|
if (e.track){
|
|
let track = e.track
|
|
if (track.fallbackID != 0){
|
|
console.warn(`${itemName} ${e.message} Using fallback id.`)
|
|
let newTrack = await this.dz.gw.get_track_with_fallback(track.fallbackID)
|
|
track.parseEssentialData(newTrack)
|
|
track.retriveFilesizes(this.dz)
|
|
return await this.downloadWrapper(extraData, track)
|
|
}
|
|
if (!track.searched && this.settings.fallbackSearch){
|
|
console.warn(`${itemName} ${e.message} Searching for alternative.`)
|
|
let searchedID = this.dz.api.get_track_id_from_metadata(track.mainArtist.name, track.title, track.album.title)
|
|
if (searchedID != "0"){
|
|
let newTrack = await this.dz.gw.get_track_with_fallback(track.fallbackID)
|
|
track.parseEssentialData(newTrack)
|
|
track.retriveFilesizes(this.dz)
|
|
track.searched = true
|
|
if (this.listener) this.listener.send('queueUpdate', {
|
|
uuid: this.downloadObject.uuid,
|
|
searchFallback: true,
|
|
data: {
|
|
id: track.id,
|
|
title: track.title,
|
|
artist: track.mainArtist.name
|
|
}
|
|
})
|
|
return await this.downloadWrapper(extraData, track)
|
|
}
|
|
}
|
|
e.errid += "NoAlternative"
|
|
e.message = errorMessages[e.errid]
|
|
}
|
|
console.error(`${itemName} ${e.message}`)
|
|
result = {error:{
|
|
message: e.message,
|
|
errid: e.errid,
|
|
data: tempTrack
|
|
}}
|
|
} else if (! (e instanceof DownloadCanceled)){
|
|
console.error(`${itemName} ${e.message}`)
|
|
result = {error:{
|
|
message: e.message,
|
|
data: tempTrack
|
|
}}
|
|
}
|
|
}
|
|
|
|
if (result.error){
|
|
this.downloadObject.completeTrackProgress(this.listener)
|
|
this.downloadObject.failed += 1
|
|
this.downloadObject.errors.push(result.error)
|
|
if (this.listener){
|
|
let error = result.error
|
|
this.listener.send("updateQueue", {
|
|
uuid: this.downloadObject.uuid,
|
|
failed: true,
|
|
data: error.data,
|
|
error: error.message,
|
|
errid: error.errid || null
|
|
})
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
async afterDownloadSingle(track){
|
|
if (!this.extrasPath) this.extrasPath = this.settings.downloadLocation
|
|
|
|
// Save local album artwork
|
|
if (this.settings.saveArtwork && track.albumPath)
|
|
await each(track.albumURLs, async (image) => {
|
|
await downloadImage(image.url, `${track.albumPath}/${track.albumFilename}.${image.ext}`, this.settings.overwriteFile)
|
|
})
|
|
|
|
// Save local artist artwork
|
|
if (this.settings.saveArtworkArtist && track.artistPath)
|
|
await each(track.artistURLs, async (image) => {
|
|
await downloadImage(image.url, `${track.artistPath}/${track.artistFilename}.${image.ext}`, this.settings.overwriteFile)
|
|
})
|
|
|
|
// Create searched logfile
|
|
if (this.settings.logSearched && track.searched){
|
|
let filename = `${track.data.artist} - ${track.data.title}`
|
|
let searchedFile = fs.readFileSync(`${this.extrasPath}/searched.txt`).toString()
|
|
if (searchedFile.indexOf(filename) == -1){
|
|
if (searchedFile != "") searchedFile += "\r\n"
|
|
searchedFile += filename + "\r\n"
|
|
fs.writeFileSync(`${this.extrasPath}/searched.txt`, searchedFile)
|
|
}
|
|
}
|
|
|
|
// Execute command after download
|
|
}
|
|
|
|
async afterDownloadCollection(tracks){
|
|
if (!this.extrasPath) this.extrasPath = this.settings.downloadLocation
|
|
let playlist = []
|
|
let errors = ""
|
|
let searched = ""
|
|
|
|
await each(tracks, async (track, i) => {
|
|
if (!track) return
|
|
|
|
if (track.error){
|
|
if (!track.error.data) track.error.data = {id: "0", title: 'Unknown', artist: 'Unknown'}
|
|
errors += `${track.error.data.id} | ${track.error.data.artist} - ${track.error.data.title} | ${track.error.message}\r\n`
|
|
}
|
|
|
|
if (track.searched) searched += `${track.data.artist} - ${track.data.title}\r\n`
|
|
|
|
// Save local album artwork
|
|
if (this.settings.saveArtwork && track.albumPath)
|
|
await each(track.albumURLs, async (image) => {
|
|
await downloadImage(image.url, `${track.albumPath}/${track.albumFilename}.${image.ext}`, this.settings.overwriteFile)
|
|
})
|
|
|
|
// Save local artist artwork
|
|
if (this.settings.saveArtworkArtist && track.artistPath)
|
|
await each(track.artistURLs, async (image) => {
|
|
await downloadImage(image.url, `${track.artistPath}/${track.artistFilename}.${image.ext}`, this.settings.overwriteFile)
|
|
})
|
|
|
|
// Save filename for playlist file
|
|
playlist[i] = track.filename || ""
|
|
})
|
|
|
|
// Create errors logfile
|
|
if (this.settings.logErrors && errors != "")
|
|
fs.writeFileSync(`${this.extrasPath}/errors.txt`, errors)
|
|
|
|
// Create searched logfile
|
|
if (this.settings.logSearched && searched != "")
|
|
fs.writeFileSync(`${this.extrasPath}/searched.txt`, searched)
|
|
|
|
// Save Playlist Artwork
|
|
if (this.settings.saveArtwork && this.playlistCovername && !this.settings.tags.savePlaylistAsCompilation)
|
|
await each(this.playlistURLs, async (image) => {
|
|
await downloadImage(image.url, `${this.extrasPath}/${this.playlistCovername}.${image.ext}`, this.settings.overwriteFile)
|
|
})
|
|
|
|
// Create M3U8 File
|
|
if (this.settings.createM3U8File){
|
|
let filename = generateDownloadObjectName(this.settings.playlistFilenameTemplate, this.downloadObject, this.settings) || "playlist"
|
|
fs.writeFileSync(`${this.extrasPath}/${filename}.m3u8`, playlist.join('\n'))
|
|
}
|
|
|
|
// Execute command after download
|
|
}
|
|
}
|
|
|
|
class DownloadError extends Error {
|
|
constructor() {
|
|
super()
|
|
this.name = "DownloadError"
|
|
}
|
|
}
|
|
|
|
const errorMessages = {
|
|
notOnDeezer: "Track not available on Deezer!",
|
|
notEncoded: "Track not yet encoded!",
|
|
notEncodedNoAlternative: "Track not yet encoded and no alternative found!",
|
|
wrongBitrate: "Track not found at desired bitrate.",
|
|
wrongBitrateNoAlternative: "Track not found at desired bitrate and no alternative found!",
|
|
no360RA: "Track is not available in Reality Audio 360.",
|
|
notAvailable: "Track not available on deezer's servers!",
|
|
notAvailableNoAlternative: "Track not available on deezer's servers and no alternative found!",
|
|
noSpaceLeft: "No space left on target drive, clean up some space for the tracks.",
|
|
albumDoesntExists: "Track's album does not exsist, failed to gather info."
|
|
}
|
|
|
|
class DownloadFailed extends DownloadError {
|
|
constructor(errid, track) {
|
|
super()
|
|
this.errid = errid
|
|
this.message = errorMessages[errid]
|
|
this.name = "DownloadFailed"
|
|
this.track = track
|
|
}
|
|
}
|
|
|
|
class TrackNot360 extends Error {
|
|
constructor() {
|
|
super()
|
|
this.name = "TrackNot360"
|
|
}
|
|
}
|
|
|
|
class PreferredBitrateNotFound extends Error {
|
|
constructor() {
|
|
super()
|
|
this.name = "PreferredBitrateNotFound"
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
Downloader,
|
|
DownloadError,
|
|
DownloadFailed,
|
|
getPreferredBitrate,
|
|
TrackNot360,
|
|
PreferredBitrateNotFound
|
|
}
|