Apply settings to track and generate filename & filepath

This commit is contained in:
RemixDev 2021-04-13 18:40:34 +02:00
parent 952f0eae3a
commit 4d6165b218
6 changed files with 485 additions and 19 deletions

View file

@ -2,9 +2,22 @@ const { Track, AlbumDoesntExists } = require('./types/Track.js')
const { streamTrack, generateStreamURL } = require('./decryption.js')
const { TrackFormats } = require('deezer-js')
const { USER_AGENT_HEADER } = require('./utils/index.js')
const { DEFAULTS } = require('./settings.js')
const { generatePath } = require('./utils/pathtemplates.js')
const got = require('got')
const fs = require('fs')
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'
}
async function getPreferredBitrate(track, bitrate, shouldFallback, uuid, listener){
bitrate = parseInt(bitrate)
if (track.localTrack) { return TrackFormats.LOCAL }
@ -12,17 +25,17 @@ async function getPreferredBitrate(track, bitrate, shouldFallback, uuid, listene
let falledBack = false
const formats_non_360 = {
"FLAC": TrackFormats.FLAC,
"MP3_320": TrackFormats.MP3_320,
"MP3_128": TrackFormats.MP3_128
[TrackFormats.FLAC]: "FLAC",
[TrackFormats.MP3_320]: "MP3_320",
[TrackFormats.MP3_128]: "MP3_128"
}
const formats_360 = {
"MP4_RA3": TrackFormats.MP4_RA3,
"MP4_RA2": TrackFormats.MP4_RA2,
"MP4_RA1": TrackFormats.MP4_RA1
[TrackFormats.MP4_RA3]: "MP4_RA3",
[TrackFormats.MP4_RA2]: "MP4_RA2",
[TrackFormats.MP4_RA1]: "MP4_RA1"
}
const is360Format = Object.values(formats_360).indexOf(bitrate) != -1
const is360Format = Object.keys(formats_360).indexOf(bitrate) != -1
let formats
if (!shouldFallback){
formats = {...formats_360, ...formats_non_360}
@ -33,8 +46,8 @@ async function getPreferredBitrate(track, bitrate, shouldFallback, uuid, listene
}
for (let i = 0; i < Object.keys(formats).length; i++){
let formatName = Object.keys(formats)[i]
let formatNumber = formats[formatName]
let formatNumber = Object.keys(formats)[i]
let formatName = formats[formatNumber]
if (formatNumber > bitrate) { continue }
if (Object.keys(track.filesizes).indexOf(`FILESIZE_${formatName}`) != -1){
@ -88,7 +101,7 @@ class Downloader {
constructor(dz, downloadObject, settings, listener){
this.dz = dz
this.downloadObject = downloadObject
this.settings = settings
this.settings = settings || DEFAULTS
this.bitrate = downloadObject.bitrate
this.listener = listener
@ -165,12 +178,26 @@ class Downloader {
track.bitrate = selectedFormat
track.album.bitrate = selectedFormat
// Generate covers URLs
// Apply Settings
track.applySettings(this.settings)
// Generate filename and filepath from metadata
// Remove Subfolders from filename and add it to filepath
let {
filename,
filepath,
artistPath,
coverPath,
extrasPath
} = generatePath(track, this.downloadObject, this.settings)
// Make sure the filepath exsists
fs.mkdirSync(filepath, { recursive: true })
let writepath = `${filepath}/${filename}${extensions[track.bitrate]}`
// Save extrasPath
if (extrasPath && !this.extrasPath) this.extrasPath = extrasPath
// Generate covers URLs
// Download and cache the coverart
// Save local album art
// Save artist art
@ -181,9 +208,9 @@ class Downloader {
// Download the track
console.log("Downloading")
track.downloadURL = generateStreamURL(track.id, track.MD5, track.mediaVersion, track.bitrate)
let stream = fs.createWriteStream('./writepath')
let stream = fs.createWriteStream(writepath)
await streamTrack(stream, track, 0, this.downloadObject, this.listener)
console.log("done")
console.log(filename)
// Adding tags
}

105
deemix/settings.js Normal file
View file

@ -0,0 +1,105 @@
const { TrackFormats } = require('deezer-js')
// Should the lib overwrite files?
const OverwriteOption = {
OVERWRITE: 'y', // Yes, overwrite the file
DONT_OVERWRITE: 'n', // No, don't overwrite the file
DONT_CHECK_EXT: 'e', // No, and don't check for extensions
KEEP_BOTH: 'b', // No, and keep both files
ONLY_TAGS: 't' // Overwrite only the tags
}
// What should I do with featured artists?
const FeaturesOption = {
NO_CHANGE: "0", // Do nothing
REMOVE_TITLE: "1", // Remove from track title
REMOVE_TITLE_ALBUM: "3", // Remove from track title and album title
MOVE_TITLE: "2" // Move to track title
}
const DEFAULTS = {
downloadLocation: "",
tracknameTemplate: "%artist% - %title%",
albumTracknameTemplate: "%tracknumber% - %title%",
playlistTracknameTemplate: "%position% - %artist% - %title%",
createPlaylistFolder: true,
playlistNameTemplate: "%playlist%",
createArtistFolder: false,
artistNameTemplate: "%artist%",
createAlbumFolder: true,
albumNameTemplate: "%artist% - %album%",
createCDFolder: true,
createStructurePlaylist: false,
createSingleFolder: false,
padTracks: true,
paddingSize: "0",
illegalCharacterReplacer: "_",
queueConcurrency: 3,
maxBitrate: String(TrackFormats.MP3_320),
fallbackBitrate: true,
fallbackSearch: false,
logErrors: true,
logSearched: false,
saveDownloadQueue: false,
overwriteFile: OverwriteOption.DONT_OVERWRITE,
createM3U8File: false,
playlistFilenameTemplate: "playlist",
syncedLyrics: false,
embeddedArtworkSize: 800,
embeddedArtworkPNG: false,
localArtworkSize: 1400,
localArtworkFormat: "jpg",
saveArtwork: true,
coverImageTemplate: "cover",
saveArtworkArtist: false,
artistImageTemplate: "folder",
jpegImageQuality: 80,
dateFormat: "Y-M-D",
albumVariousArtists: true,
removeAlbumVersion: false,
removeDuplicateArtists: false,
tagsLanguage: "",
featuredToTitle: FeaturesOption.NO_CHANGE,
titleCasing: "nothing",
artistCasing: "nothing",
executeCommand: "",
tags: {
title: true,
artist: true,
album: true,
cover: true,
trackNumber: true,
trackTotal: false,
discNumber: true,
discTotal: false,
albumArtist: true,
genre: true,
year: true,
date: true,
explicit: false,
isrc: true,
length: true,
barcode: true,
bpm: true,
replayGain: false,
label: true,
lyrics: false,
syncedLyrics: false,
copyright: false,
composer: false,
involvedPeople: false,
source: false,
savePlaylistAsCompilation: false,
useNullSeparator: false,
saveID3v1: true,
multiArtistSeparator: "default",
singleAlbumArtist: false,
coverDescriptionUTF8: false
}
}
module.exports = {
OverwriteOption,
FeaturesOption,
DEFAULTS
}

View file

@ -14,7 +14,7 @@ class Album {
this.artist = {"Main": []}
this.artists = []
this.mainArtist = null
this.date = new Date()
this.date = null
this.dateString = ""
this.trackTotal = "0"
this.discTotal = "0"
@ -84,6 +84,7 @@ class Album {
this.label = albumAPI.label || this.label
this.explicit = Boolean(albumAPI.explicit_lyrics || false)
if (albumAPI.release_date){
this.date = new Date()
this.date.year = albumAPI.release_date.substring(0,4)
this.date.month = albumAPI.release_date.substring(5,7)
this.date.day = albumAPI.release_date.substring(8,10)
@ -126,6 +127,7 @@ class Album {
this.pic.md5 = albumAPI_gw.ALB_PICTURE
}
if (albumAPI_gw.PHYSICAL_RELEASE_DATE){
this.date = new Date()
this.date.year = albumAPI_gw.PHYSICAL_RELEASE_DATE.substring(0,4)
this.date.month = albumAPI_gw.PHYSICAL_RELEASE_DATE.substring(5,7)
this.date.day = albumAPI_gw.PHYSICAL_RELEASE_DATE.substring(8,10)

View file

@ -10,7 +10,7 @@ class Artist {
this.save = true
}
ifVariousArtist(){
isVariousArtists(){
return this.id == VARIOUS_ARTISTS
}
}

View file

@ -5,6 +5,8 @@ const { Playlist } = require('./Playlist.js')
const { Picture } = require('./Picture.js')
const { Lyrics } = require('./Lyrics.js')
const { VARIOUS_ARTISTS } = require('./index.js')
const { changeCase } = require('../utils/index.js')
const { FeaturesOption } = require('../settings.js')
const {
generateReplayGainString,
@ -41,7 +43,6 @@ class Track {
this.position = null
this.searched = false
this.bitrate = 0
this.singleDownload = false
this.dateString = ""
this.artistsString = ""
this.mainArtistsString = ""
@ -277,8 +278,86 @@ class Track {
}
applySettings(settings){
// TODO: Applay settings
settings;
// Check if should save the playlist as a compilation
if (settings.tags.savePlaylistAsCompilation && this.playlist){
this.trackNumber = this.position
this.discNumber = "1"
this.album.makePlaylistCompilation(this.playlist)
} else {
if (this.album.date) this.date = this.album.date
}
this.dateString = this.date.format(settings.dateFormat)
this.album.dateString = this.album.date.format(settings.dateFormat)
if (this.playlist) this.playlist.dateString = this.playlist.date.format(settings.dateFormat)
// Check various artist option
if (settings.albumVariousArtists && this.album.variousArtists){
let artist = this.album.variousArtists
let isMainArtist = artist.role === "Main"
if (this.album.artists.indexOf(artist.name) == -1)
this.album.artists.push(artist.name)
if (isMainArtist || !this.album.artsit.Main.contains(artist.name) && !isMainArtist){
if (!this.album.artist[artist.role])
this.album.artist[artist.role] = []
this.album.artist[artist.role].push(artist.name)
}
}
this.album.mainArtist.save = (!this.album.mainArtist.isVariousArtists() || settings.albumVariousArtists && this.album.mainArtist.isVariousArtists())
// Check removeDuplicateArtists
if (settings.removeDuplicateArtists) this.removeDuplicateArtists()
// Check if user wants the feat in the title
if (settings.featuredToTitle == FeaturesOption.REMOVE_TITLE){
this.title = this.getCleanTitle()
}else if (settings.featuredToTitle == FeaturesOption.MOVE_TITLE){
this.title = this.getFeatTitle()
}else if (settings.featuredToTitle == FeaturesOption.REMOVE_TITLE_ALBUM){
this.title = this.getCleanTitle()
this.album.title = this.album.getCleanTitle()
}
// Remove (Album Version) from tracks that have that
if (settings.removeAlbumVersion && this.title.indexOf("Album Version") != -1){
this.title = this.title.replace(/ ?\(Album Version\)/g, '').trim()
}
// Change title and artist casing if needed
if (settings.titleCasing != "nothing"){
this.title = changeCase(this.title, settings.titleCasing)
} else if (settings.artistCasing != "nothing"){
this.mainArtist.name = changeCase(this.mainArtist.name, settings.artistCasing)
this.artists.forEach((artist, i) => {
this.artists[i] = changeCase(artist, settings.artistCasing)
})
Object.keys(this.artist).forEach((art_type) => {
this.artist[art_type].forEach((artist, i) => {
this.artist[art_type][i] = changeCase(artist, settings.artistCasing)
})
})
}
// Generate artist tag
if (settings.tags.multiArtistSeparator == "default"){
if (settings.featuredToTitle == FeaturesOption.MOVE_TITLE){
this.artistsString = this.artist.Main.join(", ")
} else {
this.artistString = this.artists.join(", ")
}
} else if (settings.tags.multiArtistSeparator == "andFeat"){
this.artsitsString = this.mainArtistsString
if (this.featArtistsString && settings.featuredToTitle != FeaturesOption.MOVE_TITLE)
this.artistsString += ` ${this.featArtistsString}`
} else {
let separator = settings.tags.multiArtistSeparator
if (settings.featuredToTitle == FeaturesOption.MOVE_TITLE){
this.artsitsString = this.artsit.Main.join(separator)
} else {
this.artsitsString = this.artists.join(separator)
}
}
}
}

View file

@ -0,0 +1,253 @@
const { TrackFormats } = require('deezer-js')
const bitrateLabels = {
[TrackFormats.MP4_RA3]: "360 HQ",
[TrackFormats.MP4_RA2]: "360 MQ",
[TrackFormats.MP4_RA1]: "360 LQ",
[TrackFormats.FLAC] : "FLAC",
[TrackFormats.MP3_320]: "320",
[TrackFormats.MP3_128]: "128",
[TrackFormats.DEFAULT]: "128",
[TrackFormats.LOCAL] : "MP3"
}
function fixName(txt, char='_'){
txt = txt+""
txt = txt.replace(/[\0/\\:*?"<>|]/g, char)
return txt.normalize('NFC')
}
function fixLongName(name){
if (name.indexOf('/') != -1){
let sepName = name.split('/')
name = ""
sepName.forEach((txt) => {
txt = fixLongName(txt)
name += `${txt}/`
})
name = name.slice(0, -1)
} else {
name = name.substring(0, 200)
}
return name
}
function antiDot(str){
while(str[str.length-1] == "." || str[str.length-1] == " " || str[str.length-1] == "\n"){
str = str.substring(0,str.length-1);
}
if(str.length < 1){
str = "dot";
}
return str;
}
function pad(num, max_val, settings) {
let paddingSize;
if (parseInt(settings.paddingSize) == 0) {
paddingSize = (max_val+"").length
} else{
paddingSize = ((10 ** (parseInt(settings.paddingSize) - 1))+"").length
}
if (paddingSize == 1) paddingSize = 2
if (settings.padTracks) return (num+"").padStart(paddingSize, "0")
return (num+"")
}
function generatePath(track, downloadObject, settings){
let filenameTemplate = "%artist% - %title%";
let singleTrack = false
if (downloadObject.type === "track"){
if (settings.createSingleFolder) filenameTemplate = settings.albumTracknameTemplate
else filenameTemplate = settings.tracknameTemplate
singleTrack = true
} else if (downloadObject.type === "album") {
filenameTemplate = settings.albumTracknameTemplate
} else {
filenameTemplate = settings.playlistTracknameTemplate
}
let filename = generateTrackName(filenameTemplate, track, settings)
let filepath, artistPath, coverPath, extrasPath
filepath = settings.downloadLocation || "."
if (settings.createPlaylistFolder && track.playlist && !settings.tags.savePlaylistAsCompilation)
filepath += `/${generatePlaylistName(settings.playlistNameTemplate, track.playlist, settings)}`
if (track.playlist && !settings.tags.savePlaylistAsCompilation)
extrasPath = filepath
if (
(settings.createArtistFolder && !track.playlist) ||
(settings.createArtistFolder && track.playlist && settings.tags.savePlaylistAsCompilation) ||
(settings.createArtistFolder && track.playlist && settings.createStructurePlaylist)
){
filepath += `/${generateArtistName(settings.artistNameTemplate, track.album.mainAritst, settings, track.album.rootArtist)}`
artistPath = filepath
}
if (settings.createAlbumFolder &&
(!singleTrack || singleTrack && settings.createSingleFolder) &&
(!track.playlist ||
(track.playlist && settings.tags.savePlaylistAsCompilation) ||
(track.playlist && settings.createStructurePlaylist)
)
){
filepath += `/${generateAlbumName(settings.albumNameTemplate, track.album, settings, track.playlist)}`
coverPath = filepath
}
if (!extrasPath) extrasPath = filepath
if (
parseInt(track.album.discTotal) > 1 && (
(settings.createAlbumFolder && settings.createCDFolder) &&
(!singleTrack || (singleTrack && settings.createSingleFolder)) &&
(!track.playlist ||
(track.playlist && settings.tags.savePlaylistAsCompilation)) ||
(track.playlist && settings.createStructurePlaylist)
)
)
filepath += `/CD${track.discNumber}`
// Remove Subfolders from filename and add it to filepath
if (filename.indexOf('/') != -1){
let tempPath = filename.substring(0, filename.indexOf('/'))
filepath += `/${tempPath}`
filename = filename.substring(tempPath.length+1)
}
return {
filename,
filepath,
artistPath,
coverPath,
extrasPath
}
}
function generateTrackName(filename, track, settings){
let c = settings.illegalCharacterReplacer
filename = filename.replaceAll("%title%", fixName(track.title, c))
filename = filename.replaceAll("%artist%", fixName(track.mainArtist.name, c))
filename = filename.replaceAll("%artists%", fixName(track.artists.join(", "), c))
filename = filename.replaceAll("%allartists%", fixName(track.artistsString, c))
filename = filename.replaceAll("%mainartists%", fixName(track.mainArtistsString, c))
if (track.featArtistsString) filename = filename.replaceAll("%featartists%", fixName('('+track.featArtistsString+')', c))
else filename = filename.replaceAll("%featartists%", '')
filename = filename.replaceAll("%album%", fixName(track.album.title, c))
filename = filename.replaceAll("%albumartist%", fixName(track.album.mainArtist.name, c))
filename = filename.replaceAll("%tracknumber%", pad(track.trackNumber, track.album.trackTotal, settings))
filename = filename.replaceAll("%tracktotal%", track.album.trackTotal)
filename = filename.replaceAll("%discnumber%", track.discNumber)
filename = filename.replaceAll("%disctotal%", track.album.discTotal)
if (track.album.genre.length) filename = filename.replaceAll("%genre%", fixName(track.album.genre[0], c))
else filename = filename.replaceAll("%genre%", "Unknown")
filename = filename.replaceAll("%year%", track.date.year)
filename = filename.replaceAll("%date%", track.dateString)
filename = filename.replaceAll("%bpm%", track.bpm)
filename = filename.replaceAll("%label%", fixName(track.album.label, c))
filename = filename.replaceAll("%isrc%", track.ISRC)
filename = filename.replaceAll("%upc%", track.album.barcode)
filename = filename.replaceAll("%explicit%", track.explicit ? "(Explicit)" : "")
filename = filename.replaceAll("%track_id%", track.id)
filename = filename.replaceAll("%album_id%", track.album.id)
filename = filename.replaceAll("%artist_id%", track.mainArtist.id)
if (track.playlist){
filename = filename.replaceAll("%playlist_id%", track.playlist.playlistID)
filename = filename.replaceAll("%position%", pad(track.position, track.playlist.trackTotal, settings))
} else {
filename = filename.replaceAll("%playlist_id%", '')
filename = filename.replaceAll("%position%", pad(track.position, track.album.trackTotal, settings))
}
filename = filename.replaceAll('\\', '/')
return antiDot(fixLongName(filename))
}
function generateAlbumName(foldername, album, settings, playlist){
let c = settings.illegalCharacterReplacer
if (playlist && settings.tags.savePlaylistAsCompilation){
foldername = foldername.replaceAll("%album_id%", "pl_" + playlist.playlistID)
foldername = foldername.replaceAll("%genre%", "Compile")
} else {
foldername = foldername.replaceAll("%album_id%", album.id)
if (album.genre.length) foldername = foldername.replaceAll("%genre%", fixName(album.genre[0], c))
else foldername = foldername.replaceAll("%genre%", "Unknown")
}
foldername = foldername.replaceAll("%album%", fixName(album.title, c))
foldername = foldername.replaceAll("%artist%", fixName(album.mainArtist.name, c))
foldername = foldername.replaceAll("%artist_id%", album.mainArtist.id)
if (album.rootArtist){
foldername = foldername.replaceAll("%root_artist%", fixName(album.rootArtist.name, c))
foldername = foldername.replaceAll("%root_artist_id%", album.rootArtist.id)
} else {
foldername = foldername.replaceAll("%root_artist%", fixName(album.mainArtist.name, c))
foldername = foldername.replaceAll("%root_artist_id%", album.mainArtist.id)
}
foldername = foldername.replaceAll("%tracktotal%", album.trackTotal)
foldername = foldername.replaceAll("%disctotal%", album.discTotal)
foldername = foldername.replaceAll("%type%", fixName(album.recordType.capitalize(), c))
foldername = foldername.replaceAll("%upc%", album.barcode)
foldername = foldername.replaceAll("%explicit%", album.explicit ? "(Explicit)" : "")
foldername = foldername.replaceAll("%label%", fixName(album.label, c))
foldername = foldername.replaceAll("%year%", album.date.year)
foldername = foldername.replaceAll("%date%", album.dateString)
foldername = foldername.replaceAll("%bitrate%", bitrateLabels[parseInt(album.bitrate)])
foldername = foldername.replaceAll('\\', '/')
return antiDot(fixLongName(foldername))
}
function generateArtistName(foldername, artist, settings, rootArtist){
let c = settings['illegalCharacterReplacer']
foldername = foldername.replaceAll("%artist%", fixName(artist.name, c))
foldername = foldername.replaceAll("%artist_id%", artist.id)
if (rootArtist){
foldername = foldername.replaceAll("%root_artist%", fixName(rootArtist.name, c))
foldername = foldername.replaceAll("%root_artist_id%", rootArtist.id)
} else {
foldername = foldername.replaceAll("%root_artist%", fixName(artist.name, c))
foldername = foldername.replaceAll("%root_artist_id%", artist.id)
}
foldername = foldername.replaceAll('\\', '/')
return antiDot(fixLongName(foldername))
}
function generatePlaylistName(foldername, playlist, settings){
let c = settings['illegalCharacterReplacer']
foldername = foldername.replaceAll("%playlist%", fixName(playlist.title, c))
foldername = foldername.replaceAll("%playlist_id%", fixName(playlist.playlistID, c))
foldername = foldername.replaceAll("%owner%", fixName(playlist.owner['name'], c))
foldername = foldername.replaceAll("%owner_id%", playlist.owner['id'])
foldername = foldername.replaceAll("%year%", playlist.date.year)
foldername = foldername.replaceAll("%date%", playlist.dateString)
foldername = foldername.replaceAll("%explicit%", playlist.explicit ? "(Explicit)" : "")
foldername = foldername.replaceAll('\\', '/')
return antiDot(fixLongName(foldername))
}
function generateDownloadItemName(foldername, queueItem, settings){
let c = settings['illegalCharacterReplacer']
foldername = foldername.replaceAll("%title%", fixName(queueItem.title, c))
foldername = foldername.replaceAll("%artist%", fixName(queueItem.artist, c))
foldername = foldername.replaceAll("%size%", queueItem.size)
foldername = foldername.replaceAll("%type%", fixName(queueItem.type, c))
foldername = foldername.replaceAll("%id%", fixName(queueItem.id, c))
foldername = foldername.replaceAll("%bitrate%", bitrateLabels[parseInt(queueItem.bitrate)])
foldername = foldername.replaceAll('\\', '/').replace('/', c)
return antiDot(fixLongName(foldername))
}
module.exports = {
generatePath,
generateTrackName,
generateAlbumName,
generateArtistName,
generatePlaylistName,
generateDownloadItemName
}