Skip to content

Instantly share code, notes, and snippets.

@ivesdebruycker
Created January 22, 2018 19:50
Show Gist options
  • Save ivesdebruycker/296ce3b2b33c95ca99bcee81b69d8d0e to your computer and use it in GitHub Desktop.
Save ivesdebruycker/296ce3b2b33c95ca99bcee81b69d8d0e to your computer and use it in GitHub Desktop.
Volumio playlists (app/plugins/music_service/mpd/index.js)
'use strict';
var libMpd = require('./lib/mpd.js');
var libQ = require('kew');
var libFast = require('fast.js');
var libFsExtra = require('fs-extra');
var exec = require('child_process').exec;
var nodetools = require('nodetools');
var convert = require('convert-seconds');
var pidof = require('pidof');
var parser = require('cue-parser');
var mm = require('musicmetadata');
var os = require('os');
var execSync = require('child_process').execSync;
var ignoreupdate = false;
//tracknumbers variable below adds track numbers to titles if set to true. Set to false for normal behavour.
var tracknumbers = false;
//compilation array below adds different strings used to describe albumartist in compilations or 'multiple artist' albums
var compilation = ['Various','various','Various Artists','various artists','VA','va'];
//atistsort variable below will list artists by albumartist if set to true or artist if set to false
var artistsort = true;
var dsd_autovolume = false;
// Define the ControllerMpd class
module.exports = ControllerMpd;
function ControllerMpd(context) {
// This fixed variable will let us refer to 'this' object at deeper scopes
this.context = context;
this.commandRouter = this.context.coreCommand;
this.logger = this.context.logger;
this.configManager = this.context.configManager;
this.config = new (require('v-conf'))();
}
// Public Methods ---------------------------------------------------------------------------------------
// These are 'this' aware, and return a promise
// Define a method to clear, add, and play an array of tracks
//MPD Play
ControllerMpd.prototype.play = function (N) {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::play ' + N);
return this.sendMpdCommand('play', [N]);
};
//MPD Add
ControllerMpd.prototype.add = function (data) {
this.commandRouter.pushToastMessage('success', data + self.commandRouter.getI18nString('COMMON.ADD_QUEUE_TEXT_1'));
return this.sendMpdCommand('add', [data]);
};
//MPD Remove
ControllerMpd.prototype.remove = function (position) {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::remove ' + position);
return this.sendMpdCommand('delete', [position]);
};
//MPD Next
ControllerMpd.prototype.next = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::next');
return this.sendMpdCommand('next', []);
};
//MPD Previous
ControllerMpd.prototype.previous = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::previous');
return this.sendMpdCommand('previous', []);
};
//MPD Seek
ControllerMpd.prototype.seek = function (timepos) {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::seek to ' + timepos);
return this.sendMpdCommand('seekcur', [timepos]);
};
//MPD Random
ControllerMpd.prototype.random = function (randomcmd) {
var string = randomcmd ? 1 : 0;
this.commandRouter.pushToastMessage('success', "Random", string === 1 ? self.commandRouter.getI18nString('COMMON.ON') : self.commandRouter.getI18nString('COMMON.OFF'));
return this.sendMpdCommand('random', [string])
};
//MPD Repeat
ControllerMpd.prototype.repeat = function (repeatcmd) {
var string = repeatcmd ? 1 : 0;
this.commandRouter.pushToastMessage('success', "Repeat", string === 1 ? self.commandRouter.getI18nString('COMMON.ON') : self.commandRouter.getI18nString('COMMON.ON'));
return this.sendMpdCommand('repeat', [string]);
};
// MPD clear
ControllerMpd.prototype.clear = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::clear');
return this.sendMpdCommand('clear', []);
};
// MPD enable output
ControllerMpd.prototype.enableOutput = function (output) {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'Enable Output ' + output);
return this.sendMpdCommand('enableoutput', [output]);
};
// MPD disable output
ControllerMpd.prototype.disableOutput = function (output) {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'Disable Output ' + output);
return this.sendMpdCommand('disableoutput', [output]);
};
//UpdateDB
ControllerMpd.prototype.updateMpdDB = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'Update mpd DB');
return this.sendMpdCommand('update', []);
};
ControllerMpd.prototype.addPlay = function (fileName) {
var self=this;
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::addPlay');
this.commandRouter.pushToastMessage('Success', '', fileName + self.commandRouter.getI18nString('COMMON.ADD_QUEUE_TEXT_1'));
//Add playlists and cue with load command
if (fileName.endsWith('.cue') || fileName.endsWith('.pls') || fileName.endsWith('.m3u')) {
this.logger.info('Adding Playlist: ' + fileName);
return this.sendMpdCommandArray([
{command: 'clear', parameters: []},
{command: 'load', parameters: [fileName]},
{command: 'play', parameters: []}
])
} else if (fileName.startsWith('albums')) {
self.logger.info("PLAYYYYYYYY");
return self.playAlbum(fileName);
} else {
return this.sendMpdCommandArray([
{command: 'clear', parameters: []},
{command: 'add', parameters: [fileName]},
{command: 'play', parameters: []}
])
}
/*.then(function() {
self.commandRouter.volumioPlay();
});*/
};
ControllerMpd.prototype.addPlayCue = function (data) {
//this.commandRouter.pushToastMessage('Success', '', data.uri + self.commandRouter.getI18nString('COMMON.ADD_QUEUE_TEXT_1'));
//Add playlists and cue with load command
//console.log(data);
if(data.number!==undefined)
{
this.logger.info('Adding CUE individual entry: ' + data.number + ' ' + data.uri);
this.commandRouter.addQueueItems([{
uri:'cue://'+data.uri+'@'+data.number,
service:'mpd'
}]);
var index=this.commandRouter.stateMachine.playQueue.arrayQueue.length;
this.commandRouter.volumioPlay(index);
}
var items=[];
/*
var cuesheet = parser.parse('/mnt/' + path);
list.push({
service: 'mpd',
type: 'song',
title: name,
icon: 'fa fa-list-ol',
uri: s0 + path
});
var tracks = cuesheet.files[0].tracks;
for (var j in tracks) {
list.push({
service: 'mpd',
type: 'cuesong',
title: tracks[j].title,
artist: tracks[j].performer,
album: path.substring(path.lastIndexOf("/") + 1),
number: tracks[j].number - 1,
icon: 'fa fa-music',
uri: s0 + path
});
}*/
/*this.commandRouter.addQueueItems();
return this.sendMpdCommandArray([
{command: 'clear', parameters: []},
{command: 'load', parameters: [data.uri]},
{command: 'play', parameters: [data.number]}
])*/
};
// MPD music library
ControllerMpd.prototype.getTracklist = function () {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::getTracklist');
return self.mpdReady
.then(function () {
return libQ.nfcall(self.clientMpd.sendCommand.bind(self.clientMpd), libMpd.cmd('listallinfo', []));
})
.then(function (objResult) {
var listInfo = self.parseListAllInfoResult(objResult);
return listInfo.tracks;
});
};
// Internal methods ---------------------------------------------------------------------------
// These are 'this' aware, and may or may not return a promise
// Parses the info out of the 'listallinfo' MPD command
// Metadata fields to roughly conform to Ogg Vorbis standards (http://xiph.org/vorbis/doc/v-comment.html)
ControllerMpd.prototype.parseListAllInfoResult = function (sInput) {
var arrayLines = sInput.split('\n');
var objReturn = {};
var curEntry = {};
objReturn.tracks = [];
objReturn.playlists = [];
var nLines = arrayLines.length;
for (var i = 0; i < nLines; i++) {
var arrayLineParts = libFast.map(arrayLines[i].split(':'), function (sPart) {
return sPart.trim();
});
if (arrayLineParts[0] === 'file') {
curEntry = {
'name': '',
'service': this.servicename,
'uri': arrayLineParts[1],
'browsepath': [this.displayname].concat(arrayLineParts[1].split('/').slice(0, -1)),
'artists': [],
'album': '',
'genres': [],
'performers': [],
'tracknumber': 0,
'date': '',
'duration': 0
};
objReturn.tracks.push(curEntry);
} else if (arrayLineParts[0] === 'playlist') {
// Do we even need to parse MPD playlists?
} else if (arrayLineParts[0] === 'Time') {
curEntry.duration = arrayLineParts[1];
} else if (arrayLineParts[0] === 'Title') {
curEntry.name = arrayLineParts[1];
} else if (arrayLineParts[0] === 'Artist') {
curEntry.artists = libFast.map(arrayLineParts[1].split(','), function (sArtist) {
// TODO - parse other options in artist string, such as "feat."
return sArtist.trim();
});
} else if (arrayLineParts[0] === 'AlbumArtist') {
curEntry.performers = libFast.map(arrayLineParts[1].split(','), function (sPerformer) {
return sPerformer.trim();
});
} else if (arrayLineParts[0] === 'Album') {
curEntry.album = arrayLineParts[1];
} else if (arrayLineParts[0] === 'Track') {
curEntry.tracknumber = Number(arrayLineParts[1]);
} else if (arrayLineParts[0] === 'Date') {
// TODO - parse into a date object
curEntry.date = arrayLineParts[1];
}
}
return objReturn;
};
// Define a method to get the MPD state
ControllerMpd.prototype.getState = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::getState');
var timeCurrentUpdate = Date.now();
this.timeLatestUpdate = timeCurrentUpdate;
var self = this;
return self.sendMpdCommand('status', [])
/*.then(function(data) {
return self.haltIfNewerUpdateRunning(data, timeCurrentUpdate);
})*/
.then(function (objState) {
var collectedState = self.parseState(objState);
// If there is a track listed as currently playing, get the track info
if (collectedState.position !== null) {
return self.sendMpdCommand('playlistinfo', [collectedState.position])
/*.then(function(data) {
return self.haltIfNewerUpdateRunning(data, timeCurrentUpdate);
})*/
.then(function (objTrackInfo) {
var trackinfo = self.parseTrackInfo(objTrackInfo);
collectedState.isStreaming = trackinfo.isStreaming != undefined ? trackinfo.isStreaming : false;
collectedState.title = trackinfo.title;
collectedState.artist = trackinfo.artist;
collectedState.album = trackinfo.album;
//collectedState.albumart = trackinfo.albumart;
collectedState.uri = trackinfo.uri;
collectedState.trackType = trackinfo.trackType;
return collectedState;
});
// Else return null track info
} else {
collectedState.isStreaming = false;
collectedState.title = null;
collectedState.artist = null;
collectedState.album = null;
//collectedState.albumart = null;
collectedState.uri = null;
return collectedState;
}
});
};
// Stop the current status update thread if a newer one exists
ControllerMpd.prototype.haltIfNewerUpdateRunning = function (data, timeCurrentThread) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::haltIfNewerUpdateRunning');
if (self.timeLatestUpdate > timeCurrentThread) {
return libQ.reject('Alert: Aborting status update - newer one detected');
} else {
return libQ.resolve(data);
}
};
// Announce updated MPD state
ControllerMpd.prototype.pushState = function (state) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::pushState');
return self.commandRouter.servicePushState(state, self.servicename);
};
// Pass the error if we don't want to handle it
ControllerMpd.prototype.pushError = function (sReason) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::pushError');
self.commandRouter.pushConsoleMessage(sReason);
// Return a resolved empty promise to represent completion
return libQ.resolve();
};
// Define a general method for sending an MPD command, and return a promise for its execution
ControllerMpd.prototype.sendMpdCommand = function (sCommand, arrayParameters) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::sendMpdCommand '+sCommand);
return self.mpdReady
.then(function () {
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'sending command...');
return libQ.nfcall(self.clientMpd.sendCommand.bind(self.clientMpd), libMpd.cmd(sCommand, arrayParameters));
})
.then(function (response) {
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'parsing response...');
var respobject = libMpd.parseKeyValueMessage.call(libMpd, response);
// If there's an error show an alert on UI
if ('error' in respobject) {
self.commandRouter.broadcastToastMessage('error', 'Error', respobject.error)
self.sendMpdCommand('clearerror', [])
}
return libQ.resolve(respobject);
});
};
// Define a general method for sending an array of MPD commands, and return a promise for its execution
// Command array takes the form [{command: sCommand, parameters: arrayParameters}, ...]
ControllerMpd.prototype.sendMpdCommandArray = function (arrayCommands) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::sendMpdCommandArray');
return self.mpdReady
.then(function () {
return libQ.nfcall(self.clientMpd.sendCommands.bind(self.clientMpd),
libFast.map(arrayCommands, function (currentCommand) {
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'COMMAND '+currentCommand);
return libMpd.cmd(currentCommand.command, currentCommand.parameters);
})
);
})
.then(libMpd.parseKeyValueMessage.bind(libMpd));
};
// Parse MPD's track info text into Volumio recognizable object
ControllerMpd.prototype.parseTrackInfo = function (objTrackInfo) {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::parseTrackInfo');
//self.commandRouter.logger.info(JSON.stringify("OBJTRACKINFO "+JSON.stringify(objTrackInfo)));
var resp = {};
if (objTrackInfo.Time === 0){
resp.isStreaming = true;
}
if (objTrackInfo.file != undefined) {
resp.uri = objTrackInfo.file;
resp.trackType = objTrackInfo.file.split('.').pop();
if (resp.uri.indexOf('cdda:///') >= 0) {
resp.trackType = 'CD Audio';
resp.title = resp.uri.replace('cdda:///', 'Track ');
}
else if (resp.uri.indexOf('http://') >= 0) {
resp.service='dirble';
}
} else {
resp.uri = null;
}
if (objTrackInfo.Title != undefined) {
resp.title = objTrackInfo.Title;
} else {
var file = objTrackInfo.file;
if(file!== undefined)
{
var filetitle = file.replace(/^.*\/(?=[^\/]*$)/, '');
resp.title = filetitle;
}
}
if (objTrackInfo.Artist != undefined) {
resp.artist = objTrackInfo.Artist;
} else if (objTrackInfo.Name != undefined) {
resp.artist = objTrackInfo.Name;
} else {
resp.artist = null;
}
if (objTrackInfo.Album != undefined) {
resp.album = objTrackInfo.Album;
} else {
resp.album = null;
}
var web;
if (objTrackInfo.Artist != undefined) {
if (objTrackInfo.Album != undefined) {
web = {artist: objTrackInfo.Artist, album: objTrackInfo.Album};
} else {
web = {artist: objTrackInfo.Artist};
}
}
var artUrl;
if (resp.isStreaming) {
artUrl = this.getAlbumArt(web);
} else {
artUrl = this.getAlbumArt(web, file);
}
resp.albumart = artUrl;
return resp;
};
// Parse MPD's text playlist into a Volumio recognizable playlist object
ControllerMpd.prototype.parsePlaylist = function (objQueue) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::parsePlaylist');
// objQueue is in form {'0': 'file: http://uk4.internet-radio.com:15938/', '1': 'file: http://2363.live.streamtheworld.com:80/KUSCMP128_SC'}
// We want to convert to a straight array of trackIds
return libQ.fcall(libFast.map, Object.keys(objQueue), function (currentKey) {
return convertUriToTrackId(objQueue[currentKey]);
});
};
// Parse MPD's text status into a Volumio recognizable status object
ControllerMpd.prototype.parseState = function (objState) {
var self = this;
//console.log(objState);
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::parseState');
// Pull track duration out of status message
var nDuration = null;
if ('time' in objState) {
var arrayTimeData = objState.time.split(':');
nDuration = Math.round(Number(arrayTimeData[1]));
}
// Pull the elapsed time
var nSeek = null;
if ('elapsed' in objState) {
nSeek = Math.round(Number(objState.elapsed) * 1000);
}
// Pull the queue position of the current track
var nPosition = null;
if ('song' in objState) {
nPosition = Number(objState.song);
}
// Pull audio metrics
var nBitDepth = null;
var nSampleRate = null;
var nChannels = null;
if ('audio' in objState) {
var objMetrics = objState.audio.split(':');
var nSampleRateRaw = Number(objMetrics[0]) / 1000;
nBitDepth = Number(objMetrics[1])+' bit';
nChannels = Number(objMetrics[2]);
if (objMetrics[1] == 'f') {
nBitDepth = '32f bit';
} else if (objMetrics[0] == 'dsd64') {
var nSampleRateRaw = 2.82 + ' MHz';
nBitDepth = '1 bit';
nChannels = 2;
} else if (objMetrics[0] == 'dsd128') {
var nSampleRateRaw = 5.64 + ' MHz';
nBitDepth = '1 bit';
nChannels = 2;
} else if (objMetrics[0] == 'dsd256') {
var nSampleRateRaw = 11.28 + ' MHz';
nBitDepth = '1 bit';
nChannels = 2;
} else if (objMetrics[0] == 'dsd512') {
var nSampleRateRaw = 22.58 + ' MHz';
nBitDepth = '1 bit';
nChannels = 2;
} else if (objMetrics[1] == 'dsd') {
if (nSampleRateRaw === 352.8) {
var nSampleRateRaw = 2.82 + ' MHz';
nBitDepth = '1 bit'
} else if (nSampleRateRaw === 705.6) {
var nSampleRateRaw = 5.64 + ' MHz';
nBitDepth = '1 bit'
} else if (nSampleRateRaw === 1411.2) {
var nSampleRateRaw = 11.2 + ' MHz';
nBitDepth = '1 bit'
} else {
var nSampleRateRaw = nSampleRateRaw + ' KHz';
}
} else {
var nSampleRateRaw = nSampleRateRaw + ' KHz';
}
nSampleRate = nSampleRateRaw;
}
var random = null;
if ('random' in objState) {
random = objState.random == 1;
}
var repeat = null;
if ('repeat' in objState) {
repeat = objState.repeat == 1;
}
var sStatus = null;
if ('state' in objState) {
sStatus = objState.state;
}
var updatedb = false;
if ('updating_db' in objState) {
updatedb = true;
}
return {
status: sStatus,
position: nPosition,
seek: nSeek,
duration: nDuration,
samplerate: nSampleRate,
bitdepth: nBitDepth,
channels: nChannels,
random: random,
updatedb: updatedb,
repeat: repeat
};
};
ControllerMpd.prototype.logDone = function (timeStart) {
var self = this;
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + '------------------------------ ' + (Date.now() - timeStart) + 'ms');
return libQ.resolve();
};
ControllerMpd.prototype.logStart = function (sCommand) {
var self = this;
self.commandRouter.pushConsoleMessage('\n' + '[' + Date.now() + '] ' + '---------------------------- ' + sCommand);
return libQ.resolve();
};
/*
* This method can be defined by every plugin which needs to be informed of the startup of Volumio.
* The Core controller checks if the method is defined and executes it on startup if it exists.
*/
ControllerMpd.prototype.onVolumioStart = function () {
var self = this;
this.commandRouter.sharedVars.registerCallback('alsa.outputdevice', this.outputDeviceCallback.bind(this));
// Connect to MPD only if process MPD is running
var configFile = self.commandRouter.pluginManager.getConfigurationFile(self.context, 'config.json');
self.config.loadFile(configFile);
pidof('mpd', function (err, pid) {
if (err) {
self.logger.info('Cannot initialize MPD Connection: MPD is not running');
} else {
if (pid) {
self.logger.info('MPD running with PID' + pid + ' ,establishing connection');
self.mpdEstablish();
} else {
self.logger.info('Cannot initialize MPD Connection: MPD is not running');
}
}
});
self.loadLibrarySettings();
dsd_autovolume = self.config.get('dsd_autovolume', false);
return libQ.resolve();
};
ControllerMpd.prototype.mpdEstablish = function () {
var self = this;
// TODO use names from the package.json instead
self.servicename = 'mpd';
self.displayname = 'MPD';
//getting configuration
// Save a reference to the parent commandRouter
self.commandRouter = self.context.coreCommand;
// Connect to MPD
self.mpdConnect();
// Make a promise for when the MPD connection is ready to receive events
self.mpdReady = libQ.nfcall(self.clientMpd.on.bind(self.clientMpd), 'ready');
// Catch and log errors
self.clientMpd.on('error', function (err) {
self.logger.error('MPD error: ' + err);
if (err = "{ [Error: This socket has been ended by the other party] code: 'EPIPE' }") {
// Wait 5 seconds before trying to reconnect
setTimeout(function () {
self.mpdEstablish();
}, 5000);
} else {
self.logger.error(err);
}
});
// This tracks the the timestamp of the newest detected status change
self.timeLatestUpdate = 0;
self.updateQueue();
// TODO remove pertaining function when properly found out we don't need em
//self.fswatch();
// When playback status changes
self.clientMpd.on('system', function (status) {
var timeStart = Date.now();
if (!ignoreupdate) {
self.logger.info('Mpd Status Update: '+status);
self.logStart('MPD announces state update')
.then(self.getState.bind(self))
.then(self.pushState.bind(self))
.fail(self.pushError.bind(self))
.done(function () {
return self.logDone(timeStart);
});
} else {
self.logger.info('Ignoring MPD Status Update');
}
});
self.clientMpd.on('system-playlist', function () {
var timeStart = Date.now();
if (!ignoreupdate) {
self.logStart('MPD announces system state update')
.then(self.updateQueue.bind(self))
.fail(self.pushError.bind(self))
.done(function () {
return self.logDone(timeStart);
});
} else {
self.logger.info('Ignoring MPD Status Update');
}
});
//Notify that The mpd DB has changed
self.clientMpd.on('system-database', function () {
//return self.commandRouter.fileUpdate();
//return self.reportUpdatedLibrary();
});
self.clientMpd.on('system-update', function () {
if (!ignoreupdate) {
self.sendMpdCommand('status', [])
.then(function (objState) {
var state = self.parseState(objState);
execSync("/bin/sync", { uid: 1000, gid: 1000});
return self.commandRouter.fileUpdate(state.updatedb);
});
} else {
self.logger.info('Ignoring MPD Status Update');
}
});
};
ControllerMpd.prototype.mpdConnect = function () {
var self = this;
var configFile = self.commandRouter.pluginManager.getConfigurationFile(self.context, 'config.json');
self.config = new (require('v-conf'))();
self.config.loadFile(configFile);
var nHost = self.config.get('nHost');
var nPort = self.config.get('nPort');
self.clientMpd = libMpd.connect({port: nPort, host: nHost});
};
ControllerMpd.prototype.outputDeviceCallback = function () {
var self = this;
var defer = libQ.defer();
self.context.coreCommand.pushConsoleMessage('Output device has changed, restarting MPD');
self.createMPDFile(function (error) {
if (error !== undefined && error !== null) {
self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('COMMON.CONFIGURATION_UPDATE'), self.commandRouter.getI18nString('COMMON.CONFIGURATION_UPDATE_ERROR'));
defer.resolve({});
}
else {
//self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('mpd_configuration_update'), self.commandRouter.getI18nString('mpd_playback_configuration_error'));
self.restartMpd(function (error) {
if (error !== null && error != undefined) {
self.logger.info('Cannot restart MPD: ' + error);
//self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('mpd_player_restart'), self.commandRouter.getI18nString('mpd_player_restart_error'));
}
else {
self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('COMMON.CONFIGURATION_UPDATE'), self.commandRouter.getI18nString('COMMON.PLAYER_RESTARTED'));
setTimeout(function(){self.mpdEstablish()}, 3000)
}
defer.resolve({});
});
}
});
}
ControllerMpd.prototype.savePlaybackOptions = function (data) {
var self = this;
var defer = libQ.defer();
self.config.set('dsd_autovolume', data['dsd_autovolume']);
self.config.set('volume_normalization', data['volume_normalization']);
self.config.set('audio_buffer_size', data['audio_buffer_size'].value);
self.config.set('buffer_before_play', data['buffer_before_play'].value);
dsd_autovolume = data['dsd_autovolume'];
//fixing dop
if (self.config.get('dop') == null) {
self.config.addConfigValue('dop', 'boolean', data['dop'].value);
} else {
self.config.set('dop', data['dop'].value);
}
if (self.config.get('persistent_queue') == null) {
self.config.addConfigValue('persistent_queue', 'boolean', data['persistent_queue']);
} else {
self.config.set('persistent_queue', data['persistent_queue']);
}
self.createMPDFile(function (error) {
if (error !== undefined && error !== null) {
//self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('mpd_configuration_update'), self.commandRouter.getI18nString('mpd_configuration_update_error'));
defer.resolve({});
}
else {
//self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('mpd_configuration_update'), self.commandRouter.getI18nString('mpd_playback_configuration_error'));
self.restartMpd(function (error) {
if (error !== null && error != undefined) {
self.logger.error('Cannot restart MPD: ' + error);
self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('PLAYBACK_OPTIONS.PLAYBACK_OPTIONS_TITLE'), self.commandRouter.getI18nString('COMMON.SETTINGS_SAVE_ERROR'));
}
else {
self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('PLAYBACK_OPTIONS.PLAYBACK_OPTIONS_TITLE'), self.commandRouter.getI18nString('COMMON.SETTINGS_SAVED_SUCCESSFULLY'));
}
defer.resolve({});
});
}
});
return defer.promise;
};
ControllerMpd.prototype.saveResampleOptions = function (data) {
var self = this;
var defer = libQ.defer();
self.createMPDFile(function (error) {
if (error !== undefined && error !== null) {
//self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('mpd_configuration_update'), self.commandRouter.getI18nString('mpd_configuration_update_error'));
defer.resolve({});
}
else {
//self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('mpd_configuration_update'), self.commandRouter.getI18nString('mpd_playback_configuration_error'));
self.restartMpd(function (error) {
if (error !== null && error != undefined) {
self.logger.error('Cannot restart MPD: ' + error);
//self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('mpd_player_restart'), self.commandRouter.getI18nString('mpd_player_restart_error'));
}
else
//self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('mpd_player_restart'), self.commandRouter.getI18nString('mpd_player_restart_success'));
defer.resolve({});
});
}
});
return defer.promise;
};
ControllerMpd.prototype.restartMpd = function (callback) {
var self = this;
if (callback) {
exec('/usr/bin/sudo /bin/systemctl restart mpd.service ', {uid:1000, gid:1000},
function (error, stdout, stderr) {
callback(error);
});
} else {
exec('/usr/bin/sudo /bin/systemctl restart mpd.service ', {uid:1000, gid:1000},
function (error, stdout, stderr) {
if (error){
self.logger.error('Cannot restart MPD: ' + error);
}
});
}
};
ControllerMpd.prototype.createMPDFile = function (callback) {
var self = this;
exec('/usr/bin/sudo /bin/chmod 777 /etc/mpd.conf', {uid:1000,gid:1000},
function (error, stdout, stderr) {
if(error != null) {
self.logger.info('Error setting mpd conf file perms: '+error);
} else {
self.logger.info('MPD Permissions set');
}
});
try {
fs.readFile(__dirname + "/mpd.conf.tmpl", 'utf8', function (err, data) {
if (err) {
return self.logger.error(err);
}
var outdev = self.getAdditionalConf('audio_interface', 'alsa_controller', 'outputdevice');
var mixer = self.getAdditionalConf('audio_interface', 'alsa_controller', 'mixer');
var resampling = self.getAdditionalConf('audio_interface', 'alsa_controller', 'resampling');
var resampling_bitdepth = self.getAdditionalConf('audio_interface', 'alsa_controller', 'resampling_target_bitdepth');
var resampling_samplerate = self.getAdditionalConf('audio_interface', 'alsa_controller', 'resampling_target_samplerate');
var resampling_quality = self.getAdditionalConf('audio_interface', 'alsa_controller', 'resampling_quality');
var mixerdev = '';
var mixerstrings = '';
if (outdev != 'softvolume' ) {
if (outdev.indexOf(',') >= 0) {
mixerdev = 'hw:'+outdev;
outdev = 'hw:'+outdev;
} else {
mixerdev = 'hw:'+outdev;
outdev = 'hw:'+outdev+',0';
}
} else {
mixerdev = 'SoftMaster';
}
var mpdvolume = self.getAdditionalConf('audio_interface', 'alsa_controller', 'mpdvolume');
if (mpdvolume == undefined) {
mpdvolume = false;
}
var conf1 = data.replace("${gapless_mp3_playback}", self.checkTrue('gapless_mp3_playback'));
var conf2 = conf1.replace("${device}", outdev);
var conf3 = conf2.replace("${volume_normalization}", self.checkTrue('volume_normalization'));
var conf4 = conf3.replace("${audio_buffer_size}", self.config.get('audio_buffer_size'));
var conf5 = conf4.replace("${buffer_before_play}", self.config.get('buffer_before_play'));
if (self.config.get('dop')){
var dop = 'yes';
} else {
var dop = 'no';
}
var conf6 = conf5.replace("${dop}", dop);
if (mixer) {
if (mixer.length > 0 && mpdvolume) {
mixerstrings = 'mixer_device "'+ mixerdev + '"' + os.EOL + ' mixer_control "'+ mixer +'"'+ os.EOL + ' mixer_type "hardware"'+ os.EOL;
}
}
var conf7 = conf6.replace("${mixer}", mixerstrings);
if(resampling){
var conf8 = conf7.replace("${sox}", 'resampler { ' + os.EOL + ' plugin "soxr"' + os.EOL + ' quality "' + resampling_quality + '"' + os.EOL + ' threads "0"' + os.EOL + '}');
var conf9 = conf8.replace("${format}", 'format "'+resampling_samplerate+':'+resampling_bitdepth+':2"');
} else {
var conf8 = conf7.replace("${sox}", 'resampler { ' + os.EOL + ' plugin "soxr"' + os.EOL + ' quality "high"' + os.EOL + ' threads "0"' + os.EOL + '}');
var conf9 = conf8.replace("${format}", "");
}
fs.writeFile("/etc/mpd.conf", conf9, 'utf8', function (err) {
if (err) return self.logger.error(err);
});
});
callback();
}
catch (err) {
callback(err);
}
};
ControllerMpd.prototype.checkTrue = function (config) {
var self = this;
var out = "no";
var value = self.config.get(config);
if(value){
out = "yes";
return out
} else {
return out
}
};
/*
* This method shall be defined by every plugin which needs to be configured.
*/
ControllerMpd.prototype.setConfiguration = function (configuration) {
//DO something intelligent
};
ControllerMpd.prototype.getConfigParam = function (key) {
var self = this;
return self.config.get(key);
};
ControllerMpd.prototype.setConfigParam = function (data) {
var self = this;
self.config.set(data.key, data.value);
};
ControllerMpd.prototype.listPlaylists = function (uri) {
var self = this;
var defer = libQ.defer();
var response={
"navigation": {
"lists": [
{
"availableListViews": [
"list"
],
"items": [
]
}
]
}
};
var promise = self.commandRouter.playListManager.listPlaylist();
promise.then(function (data) {
for (var i in data) {
var ithdata = data[i];
var playlist = {
"service": "mpd",
"type": 'playlist',
"title": ithdata,
"icon": 'fa fa-list-ol',
"uri": 'playlists/' + ithdata
};
response.navigation.lists[0].items.push(playlist);
}
defer.resolve(response);
});
return defer.promise;
};
ControllerMpd.prototype.browsePlaylist = function (uri) {
var self = this;
var defer = libQ.defer();
var response={
"navigation": {
"lists": [
{
"availableListViews": [
"list"
],
"items": [
]
}
],
"prev": {
"uri": "playlists"
}
}
};
var name = uri.split('/')[1];
var promise = self.commandRouter.playListManager.getPlaylistContent(name);
promise.then(function (data) {
var n = data.length;
for (var i = 0; i < n; i++) {
var ithdata = data[i];
var song = {
service: ithdata.service,
type: 'song',
title: ithdata.title,
artist: ithdata.artist,
album: ithdata.album,
albumart: ithdata.albumart,
uri: ithdata.uri
};
response.navigation.lists[0].items.push(song);
}
defer.resolve(response);
});
return defer.promise;
};
ControllerMpd.prototype.lsInfo = function (uri) {
var self = this;
var defer = libQ.defer();
var sections = uri.split('/');
var prev = '';
var folderToList = '';
var command = 'lsinfo';
if (sections.length > 1) {
prev = sections.slice(0, sections.length - 1).join('/');
folderToList = sections.slice(1).join('/');
command += ' "' + folderToList + '"';
}
var cmd = libMpd.cmd;
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(command, []), function (err, msg) {
var list = [];
if (msg) {
var s0 = sections[0] + '/';
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
//self.logger.info("LINE "+line);
if (line.indexOf('directory:') === 0) {
path = line.slice(11);
name = path.split('/').pop();
list.push({
type: 'folder',
title: name,
service:'mpd',
icon: 'fa fa-folder-open-o',
uri: s0 + path
});
}
else if (line.indexOf('playlist:') === 0) {
path = line.slice(10);
name = path.split('/').pop();
if (path.endsWith('.cue')) {
try {
var cuesheet = parser.parse('/mnt/' + path);
list.push({
service: 'mpd',
type: 'song',
title: name,
icon: 'fa fa-list-ol',
uri: s0 + path
});
var tracks = cuesheet.files[0].tracks;
for (var j in tracks) {
list.push({
service: 'mpd',
type: 'cuesong',
title: tracks[j].title,
artist: tracks[j].performer,
album: path.substring(path.lastIndexOf("/") + 1),
number: tracks[j].number - 1,
icon: 'fa fa-music',
uri: s0 + path
});
}
} catch (err) {
self.logger.info('Cue Parser - Cannot parse ' + path);
}
} else {
list.push({
service: 'mpd',
type: 'playlist',
title: name,
icon: 'fa fa-list-ol',
uri: "music-library/m3u/" + path
});
}
}
else if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var year,albumart,tracknumber,duration,composer,genre;
if(self.commandRouter.sharedVars.get('extendedMetas'))
{
year = self.searchFor(lines, i + 1, 'Date:');
if(year)
{
year=parseInt(year);
}
albumart = self.getAlbumArt({artist: artist, album: album},
self.getParentFolder('/mnt/' + path),'fa-tags');
tracknumber = self.searchFor(lines, i + 1, 'Track:');
if(tracknumber)
{
var split=tracknumber.split('/');
tracknumber=parseInt(split[0]);
}
duration = self.searchFor(lines, i + 1, 'Time:');
composer=artist;
genre = self.searchFor(lines, i + 1, 'Genre:');
}
if (title) {
title = title;
} else {
title = name;
}
list.push({
service: 'mpd',
type: 'song',
title: title,
artist: artist,
album: album,
icon: 'fa fa-music',
uri: s0 + path,
year:year,
albumart:albumart,
genre:genre,
tracknumber:tracknumber,
duration:duration,
composer:composer
});
}
}
}
else self.logger.info(err);
defer.resolve({
navigation: {
prev: {
uri: prev
},
lists: [{availableListViews:['list'],items:list}]
}
});
});
});
return defer.promise;
};
ControllerMpd.prototype.search = function (query) {
var self = this;
var defer = libQ.defer();
var commandArtist = 'search artist '+' "' + query.value + '"';
var commandAlbum = 'search album '+' "' + query.value + '"';
var commandSong = 'search title '+' "' + query.value + '"';
var artistcount = 0;
var albumcount = 0;
var trackcount = 0;
var deferArray=[];
deferArray.push(libQ.defer());
deferArray.push(libQ.defer());
deferArray.push(libQ.defer());
var cmd = libMpd.cmd;
//ARTIST
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(commandArtist, []), function (err, msg) {
var subList=[];
if (msg) {
var lines = msg.split('\n'); //var lines is now an array
var artistsfound = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith('file:')) {
var path = line.slice(5).trimLeft();
var artist = self.searchFor(lines, i + 1, 'Artist:');
//**********Check if artist is already found and exists in 'artistsfound' array
if (artistsfound.indexOf(artist) <0 ) { //Artist is not in 'artistsfound' array
artistcount ++;
artistsfound.push(artist);
subList.push({
service: 'mpd',
type: 'folder',
title: artist,
uri: 'artists://' + nodetools.urlEncode(artist),
albumart: self.getAlbumArt({artist: artist},undefined,'fa-users')
});
}
}
}
deferArray[0].resolve(subList);
}
else if(err) deferArray[0].reject(new Error('Artist:' +err));
else deferArray[0].resolve();
});
});
//ALBUM
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(commandAlbum, []), function (err, msg) {
var subList=[];
if (msg) {
var lines = msg.split('\n');
var albumsfound=[];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith('file:')) {
var path = line.slice(5).trimLeft();
var album = self.searchFor(lines, i + 1, 'Album:');
var artist = self.searchFor (lines, i + 1, 'AlbumArtist:');
//********Check if album and artist combination is already found and exists in 'albumsfound' array (Allows for duplicate album names)
if (album != undefined && artist != undefined && albumsfound.indexOf(album + artist) <0 ) { // Album/Artist is not in 'albumsfound' array
albumcount ++;
albumsfound.push(album + artist);
subList.push({
service: 'mpd',
type: 'folder',
title: album,
artist: artist,
album:'',
//Use the correct album / artist match
uri: 'albums://' + nodetools.urlEncode(artist) + '/'+ nodetools.urlEncode(album),
albumart: self.getAlbumArt({artist: artist, album: album}, self.getParentFolder('/mnt/' + path),'fa-tags')
});
}
}
}
deferArray[1].resolve(subList);
}
else if(err) deferArray[1].reject(new Error('Album:' +err));
else deferArray[1].resolve();
});
});
//SONG
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(commandSong, []), function (err, msg) {
var subList=[];
if (msg) {
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith('file:')) {
trackcount ++;
var path = line.slice(5).trimLeft();
var name = path.split('/');
var count = name.length;
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
if (title == undefined) {
title = name[count - 1];
}
subList.push({
service: 'mpd',
type: 'song',
title: title,
artist: artist,
album: album,
uri: 'music-library/' + path,
albumart : self.getAlbumArt({artist: artist, album: album}, self.getParentFolder('/mnt/' + path),'fa-tags')
});
}
}
deferArray[2].resolve(subList);
}
else if(err) deferArray[2].reject(new Error('Song:' +err));
else deferArray[2].resolve();
});
});
libQ.all(deferArray).then(function(values){
var list = [];
if(values[0])
{
var artistdesc = self.commandRouter.getI18nString('COMMON.ARTIST');
if (artistcount > 1) artistdesc = self.commandRouter.getI18nString('COMMON.ARTISTS');
list=[
{
"title": self.commandRouter.getI18nString('COMMON.FOUND') + " " + artistcount + " " + artistdesc + " '" + query.value +"'",
"availableListViews": [
"list",
"grid"
],
"items": []
}];
list[0].items=list[0].items.concat(values[0]);
}
if(values[1])
{
var albumdesc = self.commandRouter.getI18nString('COMMON.ALBUM');
if (albumcount > 1) albumdesc = self.commandRouter.getI18nString('COMMON.ALBUMS');
var albList=
{
"title": self.commandRouter.getI18nString('COMMON.FOUND') + " " + albumcount + " " + albumdesc + " '" + query.value +"'",
"availableListViews": [
"list",
"grid"
],
"items": []
};
albList.items=values[1];
list.push(albList);
}
if(values[2])
{
var trackdesc = self.commandRouter.getI18nString('COMMON.TRACK');
if (trackcount > 1) var trackdesc = self.commandRouter.getI18nString('COMMON.TRACKS');;
var songList=
{
"title": self.commandRouter.getI18nString('COMMON.FOUND') + " " + trackcount + " " + trackdesc + " '" + query.value +"'",
"availableListViews": [
"list"
],
"items": []
};
songList.items=values[2];
list.push(songList);
}
list=list.filter(function(v){return !!(v)==true;})
defer.resolve(list);
}).fail(function(err){
self.commandRouter.logger.info("PARSING RESPONSE ERROR "+err);
defer.resolve();
})
return defer.promise;
};
ControllerMpd.prototype.searchFor = function (lines, startFrom, beginning) {
var count = lines.length;
var i = startFrom;
while (i < count) {
var line = lines[i];
if(line!==undefined) {
if (line.indexOf(beginning) === 0)
return line.slice(beginning.length).trimLeft();
else if (line.indexOf('file:') === 0)
return '';
else if (line.indexOf('directory:') === 0)
return '';
}
i++;
}
};
ControllerMpd.prototype.updateQueue = function () {
var self = this;
var defer = libQ.defer();
var prev = '';
var folderToList = '';
var command = 'playlistinfo';
var list = [];
var cmd = libMpd.cmd;
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(command, []), function (err, msg) {
if (msg) {
var lines = msg.split('\n');
//self.commandRouter.volumioClearQueue();
var queue = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
var rawtitle = self.searchFor(lines, i + 1, 'Title:');
var tracknumber = self.searchFor(lines, i + 1, 'Pos:');
var path = line.slice(5).trimLeft();
if (rawtitle) {
var title = rawtitle;
} else {
var path = line.slice(5).trimLeft();
var name = path.split('/');
var title = name.slice(-1)[0];
}
var queueItem = {
uri: path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: tracknumber,
albumart: self.getAlbumArt({artist: artist, album: album}, path)
};
queue.push(queueItem);
}
}
//self.commandRouter.addQueueItems(queue);
}
else self.logger.info(err);
defer.resolve({
navigation: {
prev: {
uri: prev
},
list: list
}
});
});
});
return defer.promise;
};
ControllerMpd.prototype.getAlbumArt = function (data, path,icon) {
if(this.albumArtPlugin==undefined)
{
//initialization, skipped from second call
this.albumArtPlugin= this.commandRouter.pluginManager.getPlugin('miscellanea', 'albumart');
}
if(this.albumArtPlugin)
return this.albumArtPlugin.getAlbumArt(data,path,icon);
else
{
return "/albumart";
}
};
ControllerMpd.prototype.reportUpdatedLibrary = function () {
var self = this;
// TODO PUSH THIS MESSAGE TO ALL CONNECTED CLIENTS
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::DB Update Finished');
//return self.commandRouter.pushToastMessage('Success', 'ASF', ' Added');
};
ControllerMpd.prototype.getConfigurationFiles = function () {
var self = this;
return ['config.json'];
};
ControllerMpd.prototype.getAdditionalConf = function (type, controller, data, def) {
var self = this;
var setting = self.commandRouter.executeOnPlugin(type, controller, 'getConfigParam', data);
if (setting == undefined) {
setting = def;
}
return setting
};
ControllerMpd.prototype.setAdditionalConf = function (type, controller, data) {
var self = this;
return self.commandRouter.executeOnPlugin(type, controller, 'setConfigParam', data);
};
ControllerMpd.prototype.rescanDb = function () {
var self = this;
return self.sendMpdCommand('rescan', []);
};
ControllerMpd.prototype.updateDb = function () {
var self = this;
return self.sendMpdCommand('update', []);
};
ControllerMpd.prototype.getGroupVolume = function () {
var self = this;
return self.sendMpdCommand('status', [])
.then(function (objState) {
var state = self.parseState(objState);
if (state.volume != undefined) {
state.volume = groupvolume;
return libQ.resolve(groupvolume);
}
});
};
ControllerMpd.prototype.setGroupVolume = function (data) {
var self = this;
return self.sendMpdCommand('setvol', [data]);
};
ControllerMpd.prototype.syncGroupVolume = function (data) {
var self = this;
};
// --------------------------------- music services interface ---------------------------------------
ControllerMpd.prototype.explodeUri = function(uri) {
var self = this;
console.log(uri);
var defer=libQ.defer();
var items = [];
var cmd = libMpd.cmd;
if(uri.startsWith('cue://')) {
var splitted=uri.split('@');
var index=splitted[1];
var path='/mnt/' + splitted[0].substring(6);
var cuesheet = parser.parse(path);
var tracks = cuesheet.files[0].tracks;
var cueartist = tracks[index].performer;
var cuealbum = path.substring(path.lastIndexOf("/") + 1);
var cuenumber = tracks[index].number - 1;
var path = uri.substring(0, uri.lastIndexOf("/") + 1).replace('cue:/','');
defer.resolve({
uri:uri,service:'mpd',name: tracks[index].title,
artist: cueartist,
album: cuealbum,
number: cuenumber,
albumart:self.getAlbumArt({artist:cueartist,album: cuealbum},path)
});
}
else if(uri.startsWith('search://'))
{
//exploding search
var splitted=uri.split('/');
var argument=splitted[2]; //artist
var value=splitted[3]; //album
if(argument==='artist') {
var commandArtist = 'search artist '+' "' + value + '"';
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(commandArtist, []), function (err, msg) {
var subList=[];
if (msg) {
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith('file:')) {
var path = line.slice(5).trimLeft();
var name = path.split('/');
var count = name.length;
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1 + "HERE";
}
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
items.push({
uri: 'music-library/'+path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: 0,
albumart: self.getAlbumArt({artist:artist,album: album},uri),
duration: time,
trackType: 'mp3'
});
}
}
defer.resolve(items);
}
else if(err) defer.reject(new Error('Artist:' +err));
else defer.resolve(items);
});
});
}
else if(argument==='album') {
if (compilation.indexOf(value)>-1) { //artist is in Various Artists array
var commandArtist = 'search albumartist '+' "' + value + '"';
}
else {
var commandAlbum = 'search album '+' "' + value + '"';
}
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(commandAlbum, []), function (err, msg) {
var subList=[];
if (msg) {
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith('file:')) {
var path = line.slice(5).trimLeft();
var name = path.split('/');
var count = name.length;
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1 + "HERE";
}
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
items.push({
uri: 'music-library/' + path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: 0,
albumart: self.getAlbumArt({artist: artist, album: album}, uri),
duration: time,
trackType: 'mp3'
});
}
}
defer.resolve(items);
}
else if(err) defer.reject(new Error('Artist:' +err));
else defer.resolve(items);
});
});
}
else defer.reject(new Error());
}
else if(uri.startsWith('albums://')) {
//exploding search
var splitted = uri.split('/');
var artistName = nodetools.urlDecode(splitted[2]);
var albumName = nodetools.urlDecode(splitted[3]);
var cmd = libMpd.cmd;
if (compilation.indexOf(artistName)>-1) { //artist is in Various Artists array
var GetAlbum = "find album \""+albumName+"\"" + " albumartist \"" +artistName+"\"";
}
else {
var GetAlbum = "find album \""+albumName+"\"" + " artist \"" +artistName+"\"";
}
self.clientMpd.sendCommand(cmd(GetAlbum, []), function (err, msg) {
var list = [];
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var albumart=self.getAlbumArt({artist: artist, album: album,icon:'fa-dot-circle'}, self.getParentFolder('/mnt/'+path));
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
list.push({
uri: 'music-library/'+path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: 0,
albumart: albumart,
duration: time,
trackType: path.split('.').pop()
});
}
}
}
else self.logger.info(err);
defer.resolve(list);
});
}
else if(uri.startsWith('artists://')) {
/*
artists://AC%2FDC/Rock%20or%20Bust in service mpd
*/
var splitted = uri.split('/');
if(splitted.length===4) {
return this.explodeUri('albums://'+ splitted[2] + '/' + splitted[3]);
}
var artist = nodetools.urlDecode(splitted[2]);
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd("find artist \""+artist+"\"", []), function (err, msg) {
if(msg=='') {
self.clientMpd.sendCommand(cmd("find albumartist \""+artist+"\"", []), function (err, msg) {
self.exploderArtist(err,msg,defer);
});
}
else self.exploderArtist(err,msg,defer);
});
}
else if(uri.startsWith('genres://')) {
//exploding search
var splitted = uri.split('/');
var genreName = nodetools.urlDecode(splitted[2]);
var artistName = nodetools.urlDecode(splitted[3]);
var albumName = nodetools.urlDecode(splitted[4]);
if(splitted.length==4) {
var GetMatches = "find genre \"" + genreName + "\" artist \"" + artistName + "\"";
}
else if(splitted.length==5) {
if (compilation.indexOf(artistName)>-1) { //artist is in compilation array so only find album
var GetMatches = "find genre \"" + genreName + "\" album \"" + albumName + "\"";
}
else { //artist is NOT in compilation array so use artist
var GetMatches = "find genre \"" + genreName + "\" artist \"" + artistName + "\" album \"" + albumName + "\"";
}
}
else {
var GetMatches = "find genre \"" + genreName + "\"";
}
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd(GetMatches, []), function (err, msg) {
var list = [];
var albums=[],albumarts=[];
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var albumart=self.getAlbumArt({artist: artist, album: album}, self.getParentFolder('/mnt/'+path));
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
if(title!=='') {
list.push({
uri: 'music-library/'+path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: 0,
albumart: albumart,
duration: time,
trackType: path.split('.').pop()
});
}
}
}
defer.resolve(list);
}
else {
self.logger.info(err);
defer.reject(new Error());
}
});
}
else if (uri.startsWith('music-library/m3u/')) {
var name = uri.split('/')[2];
var cmd = libMpd.cmd;
var response=[];
self.clientMpd.sendCommand(cmd("listplaylistinfo", [name]), function (err, msg) {
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
var title = self.searchFor(lines, i + 1, 'Title:');
var albumart=self.getAlbumArt({artist: artist, album: album}, self.getParentFolder(path),'fa-dot-circle');
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
response.push({
uri: 'music-library/'+path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'song',
albumart: albumart,
duration: time
});
}
}
}
defer.resolve(response);
});
}
else {
if(uri.endsWith('.cue'))
{
try {
var uriPath='/mnt/'+self.sanitizeUri(uri);
var cuesheet = parser.parse(uriPath);
var tracks = cuesheet.files[0].tracks;
var list=[];
for (var j in tracks) {
list.push({
service: 'mpd',
name: tracks[j].title,
artist: tracks[j].performer,
album: uriPath.substring(uriPath.lastIndexOf("/") + 1),
number: tracks[j].number - 1,
uri: 'cue://'+uri+'@'+j,
albumart:'/albumart'
});
}
defer.resolve(list);
} catch (err) {
self.logger.info(err);
self.logger.info('Cue Parser - Cannot parse ' + uriPath);
}
}
else {
var uriPath='/mnt/'+self.sanitizeUri(uri);
self.commandRouter.logger.info('----------------------------'+uriPath);
var uris=self.scanFolder(uriPath);
var response=[];
libQ.all(uris)
.then(function(result)
{
for(var j in result)
{
self.commandRouter.logger.info("----->>>>> "+JSON.stringify(result[j]));
if(result!==undefined && result[j].uri!==undefined) {
response.push({
uri: self.fromPathToUri(result[j].uri),
service: 'mpd',
name: result[j].name,
artist: result[j].artist,
album: result[j].album,
type: 'track',
tracknumber: result[j].tracknumber,
albumart: result[j].albumart,
duration: result[j].duration,
samplerate: result[j].samplerate,
bitdepth: result[j].bitdepth,
trackType: result[j].trackType
});
}
}
defer.resolve(response);
}).fail(function(err)
{
self.commandRouter.logger.info("explodeURI: ERROR "+err);
defer.resolve([]);
});
}
}
return defer.promise;
};
ControllerMpd.prototype.exploderArtist=function(err,msg,defer) {
var self=this;
var list = [];
var albums=[],albumarts=[];
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var albumart=self.getAlbumArt({artist: artist, album: album}, self.getParentFolder('/mnt/'+path));
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
list.push({
uri: 'music-library/'+path,
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: 0,
albumart: albumart,
duration: time,
trackType: path.split('.').pop()
});
}
}
defer.resolve(list);
}
else {
self.logger.info(err);
defer.reject(new Error());
}
}
ControllerMpd.prototype.fromUriToPath = function (uri) {
var sections = uri.split('/');
var prev = '';
if (sections.length > 1) {
prev = sections.slice(1, sections.length).join('/');
}
return prev;
};
ControllerMpd.prototype.fromPathToUri = function (uri) {
var sections = uri.split('/');
var prev = '';
if (sections.length > 1) {
prev = sections.slice(1, sections.length).join('/');
}
return prev;
};
ControllerMpd.prototype.scanFolder=function(uri)
{
var self=this;
var uris=[];
try {
var stat=libFsExtra.statSync(uri);
} catch(err) {
console.log("scanFolder - failure to stat '" + uri + "'");
return uris;
}
if(stat.isDirectory())
{
var files=libFsExtra.readdirSync(uri);
for(var i in files)
uris=uris.concat(self.scanFolder(uri+'/'+files[i]));
}
else {
var defer=libQ.defer();
/*
var parser = mm(libFsExtra.createReadStream(uri), function (err, metadata) {
if (err) defer.resolve({});
else {
defer.resolve({
uri: 'music-library/'+self.fromPathToUri(uri),
service: 'mpd',
name: metadata.title,
artist: metadata.artist[0],
album: metadata.album,
type: 'track',
tracknumber: metadata.track.no,
albumart: self.getAlbumArt(
{artist:metadata.artist,
album: metadata.album},uri),
duration: metadata.duration
});
}
});*/
var sections = uri.split('/');
var folderToList = '';
var command = 'lsinfo';
if (sections.length > 1) {
folderToList = sections.slice(2).join('/');
command += ' "' + folderToList + '"';
}
var cmd = libMpd.cmd;
self.mpdReady.then(function () {
self.clientMpd.sendCommand(cmd(command, []), function (err, msg) {
var list = [];
if (msg) {
var s0 = sections[0] + '/';
var path;
var name;
var lines = msg.split('\n');
var isSolved=false;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
self.commandRouter.logger.info("ALBUMART "+self.getAlbumArt({artist:artist,album: album},uri));
self.commandRouter.logger.info("URI "+uri);
defer.resolve({
uri: 'music-library/'+self.fromPathToUri(uri),
service: 'mpd',
name: title,
artist: artist,
album: album,
type: 'track',
tracknumber: 0,
albumart: self.getAlbumArt({artist:artist,album: album},self.getAlbumArtPathFromUri(uri)),
duration: time,
trackType: uri.split('.').pop()
});
isSolved=true;
}
}
if(isSolved===false)
defer.resolve({});
}
else defer.resolve({});
});
});
return defer.promise;
}
return uris;
}
//----------------------- new play system ----------------------------
ControllerMpd.prototype.clearAddPlayTrack = function (track) {
var self = this;
var sections = track.uri.split('/');
var prev = '';
if(track.uri.startsWith('cue://'))
{
var uri1=track.uri.substring(6);
var splitted=uri1.split('@');
var index=splitted[1];
var uri=self.sanitizeUri(splitted[0]);
self.logger.info(uri);
self.logger.info(index);
return self.sendMpdCommand('stop',[])
.then(function()
{
return self.sendMpdCommand('clear',[]);
})
.then(function()
{
return self.sendMpdCommand('load "'+uri+'"',[]);
})
.then(function()
{
return self.sendMpdCommand('play',[index]);
});
}
else{
var uri=self.sanitizeUri(track.uri);
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::clearAddPlayTracks '+uri);
// Clear the queue, add the first track, and start playback
var defer = libQ.defer();
var cmd = libMpd.cmd;
return self.sendMpdCommand('stop',[])
.then(function()
{
return self.sendMpdCommand('clear',[]);
})
.then(function()
{
return self.sendMpdCommand('add "'+uri+'"',[]);
})
.then(function()
{
return self.sendMpdCommand('play',[]);
});
}
};
ControllerMpd.prototype.seek = function(position) {
var self=this;
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::seek');
var defer = libQ.defer();
var command = 'seek ';
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd(command, ['0',position/1000]), function (err, msg) {
if (msg) {
self.logger.info(msg);
}
else self.logger.info(err);
defer.resolve();
});
return defer.promise;
};
// MPD pause
ControllerMpd.prototype.pause = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::pause');
return this.sendMpdCommand('pause', []);
};
// MPD resume
ControllerMpd.prototype.resume = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::resume');
return this.sendMpdCommand('play', []);
};
// MPD stop
ControllerMpd.prototype.stop = function () {
this.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::stop');
return this.sendMpdCommand('stop', []);
};
ControllerMpd.prototype.sanitizeUri = function (uri) {
return uri.replace('music-library/', '').replace('mnt/', '');
}
ControllerMpd.prototype.reportUpdatedLibrary = function () {
var self = this;
// TODO PUSH THIS MESSAGE TO ALL CONNECTED CLIENTS
self.commandRouter.pushConsoleMessage('[' + Date.now() + '] ' + 'ControllerMpd::DB Update Finished');
return self.commandRouter.pushToastMessage('Success', 'ASF', ' Added');
};
ControllerMpd.prototype.getConfigurationFiles = function () {
var self = this;
return ['config.json'];
};
ControllerMpd.prototype.setAdditionalConf = function (type, controller, data) {
var self = this;
return self.commandRouter.executeOnPlugin(type, controller, 'setConfigParam', data);
};
ControllerMpd.prototype.getMyCollectionStats = function () {
var self = this;
var defer = libQ.defer();
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd("count", ["group", "artist"]), function (err, msg) {
if (err) defer.resolve({
artists: 0,
albums: 0,
songs: 0,
playtime: '00:00:00'
});
else {
var artistsCount = 0;
var songsCount = 0;
var playtimesCount = 0;
var splitted = msg.split('\n');
for (var i = 0; i < splitted.length - 1; i = i + 3) {
artistsCount++;
songsCount = songsCount + parseInt(splitted[i + 1].substring(7));
playtimesCount = playtimesCount + parseInt(splitted[i + 2].substring(10));
}
var convertedSecs = convert(playtimesCount);
self.clientMpd.sendCommand(cmd("list", ["album", "group", "albumartist"]), function (err, msg) {
if (!err) {
var splittedAlbum = msg.split('\n');
var albumsCount = 0;
for (var i = 0; i < splittedAlbum.length; i++) {
var line = splittedAlbum[i];
if (line.startsWith('Album:')) {
albumsCount++;
}
}
var response = {
artists: artistsCount,
albums: albumsCount,
songs: songsCount,
playtime: convertedSecs.hours + ':' + ('0' + convertedSecs.minutes).slice(-2) + ':' + ('0' + convertedSecs.seconds).slice(-2)
};
}
defer.resolve(response);
});
}
});
return defer.promise;
};
ControllerMpd.prototype.rescanDb = function () {
var self = this;
return self.sendMpdCommand('rescan', []);
};
ControllerMpd.prototype.getGroupVolume = function () {
var self = this;
var defer = libQ.defer();
return self.sendMpdCommand('status', [])
.then(function (objState) {
if (objState.volume) {
//console.log(objState.volume);
defer.resolve(objState.volume);
}
});
return defer.promise;
};
ControllerMpd.prototype.setGroupVolume = function (data) {
var self = this;
return self.sendMpdCommand('setvol', [data]);
};
ControllerMpd.prototype.syncGroupVolume = function (data) {
var self = this;
};
ControllerMpd.prototype.handleBrowseUri = function (curUri, previous) {
var self = this;
var response;
self.logger.info("CURURI: "+curUri);
var splitted=curUri.split('/');
//music-library
if (curUri.startsWith('music-library')) {
if (curUri.startsWith('music-library/m3u/')) {
// playlist
var defer = libQ.defer();
var resp={
"navigation": {
"lists": [
{
"availableListViews": [
"list"
],
"items": [
]
}
],
"prev": {
"uri": "music-library"
}
}
};
var name = curUri.split('/')[2];
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd("listplaylistinfo", [name]), function (err, msg) {
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
var title = self.searchFor(lines, i + 1, 'Title:');
var albumart=self.getAlbumArt({artist: artist, album: album}, self.getParentFolder(path),'fa-dot-circle');
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
resp.navigation.lists[0].items.push({
uri: 'music-library/'+path,
service: 'mpd',
title: title,
artist: artist,
album: album,
type: 'song',
albumart: albumart,
duration: time
});
}
}
}
defer.resolve(resp);
});
response = defer.promise;
} else {
response = self.lsInfo(curUri);
}
}
//playlist
else if (curUri.startsWith('playlists')) {
if (curUri == 'playlists'){
response = self.listPlaylists(curUri);
}
else {
response = self.browsePlaylist(curUri);
}
}
//albums
else if (curUri.startsWith('albums://')) {
if (curUri == 'albums://') { //Just list albums
response = self.listAlbums(curUri);
}
else {
if(splitted.length==3) {
response = self.listAlbumSongs(curUri,2,'albums://');
}
else {
response = self.listAlbumSongs(curUri,3,'albums://');
}
}
}
//artists
else if (curUri.startsWith('artists://')) {
if (curUri == 'artists://') {
response = self.listArtists(curUri);
}
else
{
if(splitted.length==3) { //No album name
response = self.listArtist(curUri,2,'artists://','artists://'); //Pass back to listArtist
}
else { //Has album name
response = self.listAlbumSongs(curUri,3,'artists://'+ splitted[2]); //Pass to listAlbumSongs with artist and album name
}
}
}
//genres
else if (curUri.startsWith('genres://')) {
if (curUri == 'genres://') {
response = self.listGenres(curUri);
}
else {
if(splitted.length==3) {
response = self.listGenre(curUri);
}
else if(splitted.length==4) {
response = self.listArtist(curUri,3,'genres://'+splitted[2],'genres://');
}
else if(splitted.length==5) {
response = self.listAlbumSongs(curUri,4,'genres://'+ splitted[2]);
}
else if(splitted.length=6) {
response = self.listAlbumSongs(curUri,4,'genres://'+splitted[4] +"/"+splitted[5]);
}
}
} else if (curUri.startsWith('m3u://')) {
console.log(curUri);
/*self.clientMpd.sendCommand(cmd("listplaylistinfo", [path]), function (err, msg) {
console.log(msg);
});*/
//listplaylistinfo "StuBru - De Week van de Eeuw 4 2016"
}
return response;
};
/**
*
* list album
*/
ControllerMpd.prototype.listAlbums = function () {
var self = this;
var defer = libQ.defer();
var response = {
"navigation": {
"lists": [
{
"availableListViews": [
"list",
"grid"
],
"items": [
]
}
]
}
};
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd("list album base \"NAS/Albums\"", ["group","albumartist"]), function (err, msg) {
if(err)
defer.reject(new Error('Cannot list albums'));
else {
var lines=msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line=lines[i];
if (line.indexOf('Album:') === 0) {
var albumName = line.slice(6).trim();
if(albumName!==undefined && albumName!=='') {
var artistName=lines[i+1].slice(13).trim();
var codedAlbumName = nodetools.urlEncode(albumName);
var album = {service:'mpd',type: 'folder', title: albumName, artist: artistName, albumart: self.getAlbumArt({artist:artistName,album:albumName},undefined,'fa-dot-circle-o'), uri: 'albums://' + artistName + '/' + codedAlbumName};
response.navigation.lists[0].items.push(album);
}
}
}
defer.resolve(response);
}
});
return defer.promise;
};
/**
*
* list album songs
*/
ControllerMpd.prototype.listAlbumSongs = function (uri,index,previous) {
var self = this;
var defer = libQ.defer();
var splitted = uri.split('/');
if (splitted[0] == 'genres:') { //genre
var genre = nodetools.urlDecode(splitted[2]);
var albumartist = nodetools.urlDecode(splitted[3]);
var albumName = nodetools.urlDecode(splitted[4]);
if (compilation.indexOf(albumartist)>-1) {
var findstring = "find album \"" + albumName + "\" genre \"" + genre + "\" ";
}
else {
var findstring = "find album \"" + albumName + "\" artist \"" + albumartist + "\" genre \"" + genre + "\" ";
}
}
else if (splitted[0] == 'albums:') { //album
var artist = nodetools.urlDecode(splitted[2]);
var albumName = nodetools.urlDecode(splitted[3]);
var findstring = "find album \"" + albumName + "\"" + " albumartist \"" + artist + "\" ";
}
else { //artist
var artist = nodetools.urlDecode(splitted[2]);
var albumName = nodetools.urlDecode(splitted[3]);
if (compilation.indexOf(artist)>-1) { //artist is in compilation array so use albumartist
var typeofartist = 'albumartist';
}
else { //artist is NOT in compilation array so use artist
var typeofartist = 'artist';
}
var findstring = "find album \"" + albumName + "\" " + typeofartist + " \"" + artist + "\" ";
}
var response={
"navigation": {
"lists": [
{
"availableListViews": [
"list"
],
"items": [
]
}
],
"prev": {
"uri": previous
}
}
};
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd(findstring , []), function (err, msg) {
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var albumart=self.getAlbumArt({artist: artist, album: album}, self.getParentFolder(path),'fa-dot-circle');
var time = parseInt(self.searchFor(lines, i + 1, 'Time:'));
if (title) {
title = title;
} else {
title = name;
}
response.navigation.lists[0].items.push({
uri: 'music-library/'+path,
service: 'mpd',
title: title,
artist: artist,
album: album,
type: 'song',
tracknumber: 0,
albumart: albumart,
duration: time,
trackType: path.split('.').pop()
});
}
}
}
else self.logger.info(err);
defer.resolve(response);
});
return defer.promise;
};
/**
*
* list artists
*/
ControllerMpd.prototype.listArtists = function () {
var self = this;
var defer = libQ.defer();
var response = {
"navigation": {
"lists": [{
"availableListViews": [
"list",
"grid"
],
"items": [
]
}]
}
};
var cmd = libMpd.cmd;
var artistlist = "artist";
var artistbegin = "Artist: ";
if (artistsort) {
artistlist = "albumartist";
artistbegin = "AlbumArtist: ";
}
self.clientMpd.sendCommand(cmd("list", [artistlist]), function (err, msg) { //List artists
if(err)
defer.reject(new Error('Cannot list artist'));
else
{
var splitted=msg.split('\n');
for(var i in splitted)
{
if(splitted[i].startsWith(artistbegin)) {
var artist=splitted[i].substring(artistbegin.length);
if(artist!=='')
{
var codedArtists=nodetools.urlEncode(artist);
var albumart=self.getAlbumArt({artist:codedArtists},undefined,'fa-users');
var item={
service: "mpd",
type: 'folder',
title: artist,
albumart: albumart,
uri: 'artists://' + codedArtists
}
response.navigation.lists[0].items.push(item);
}
}
}
defer.resolve(response);
}
});
return defer.promise;
};
/**
*
* list artist
*/
ControllerMpd.prototype.listArtist = function (curUri,index,previous,uriBegin) {
var self = this;
var defer = libQ.defer();
var splitted=curUri.split('/');
var response = {
"navigation": {
"lists": [{
"title": self.commandRouter.getI18nString('COMMON.ALBUMS') + " (" + nodetools.urlDecode(splitted[index]) + ")",
"icon": "fa icon",
"availableListViews": [
"list",
"grid"
],
"items": [
]
},
{
"title": self.commandRouter.getI18nString('COMMON.TRACKS') + " (" + nodetools.urlDecode(splitted[index]) + ")",
"icon": "fa icon",
"availableListViews": [
"list"
],
"items": [
]
}],
"prev": {
"uri": previous
}
}
};
self.mpdReady
.then(function() {
var artist=nodetools.urlDecode(splitted[index]);
var VA = 0;
var cmd = libMpd.cmd;
if (uriBegin === 'genres://') {
var genre = nodetools.urlDecode(splitted[2]);
var findartist = "find artist \"" + artist + "\" genre \"" + genre + "\" ";
}
else {
if (compilation.indexOf(artist)>-1) { //artist is in compilation array so use albumartist
var findartist = "find albumartist \"" + artist + "\"";
VA = 1;
}
else { //artist is NOT in compilation array so use artist
var findartist = "find artist \"" + artist + "\"";
}
}
self.clientMpd.sendCommand(cmd(findartist, []), function (err, msg) { //get data (msg)
if(msg=='') { //If there is no data (msg) get data first, else just parseListAlbum
self.clientMpd.sendCommand(cmd(findartist, []), function (err, msg) {
self.parseListAlbum(err,msg,defer,response,uriBegin,VA);
});
}
else {
self.parseListAlbum(err,msg,defer,response,uriBegin,VA);
}
});
});
return defer.promise;
};
ControllerMpd.prototype.parseListAlbum= function(err,msg,defer,response,uriBegin,VA) {
var self=this;
var list = [];
var albums=[],albumarts=[];
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
if (VA === 1) {
var artist = self.searchFor(lines, i + 1, 'AlbumArtist:');
}
else {
var artist = self.searchFor(lines, i + 1, 'Artist:');
}
var album = self.searchFor(lines, i + 1, 'Album:');
var genre = self.searchFor(lines, i + 1, 'Genre:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var albumart=self.getAlbumArt({artist: artist, album: album}, self.getParentFolder(path),'fa-dot-circle-o');
if (title) {
title = title;
} else {
title = name;
}
response.navigation.lists[1].items.push({
service: 'mpd',
type: 'song',
title: title,
artist: artist,
album: album,
albumart: albumart,
uri: 'music-library/'+path
});
if(albums.indexOf(album)===-1) {
albums.push(album);
albumarts.push();
var uri;
if(uriBegin==='artists://') {
uri='artists://' + nodetools.urlEncode(artist) +'/'+nodetools.urlEncode(album);
}
else if (uriBegin==='genres://') {
uri='genres://' + genre + '/' + nodetools.urlEncode(artist) +'/'+nodetools.urlEncode(album);
}
else {
uri=uriBegin + nodetools.urlEncode(album);
}
response.navigation.lists[0].items.push(
{
service:'mpd',
type: 'folder',
title: album,
artist: artist,
albumart: self.getAlbumArt({artist: artist, album: album}, self.getParentFolder(path),'fa-dot-circle-o'),
uri: uri
});
}
}
}
defer.resolve(response);
}
else
{
self.logger.info(err);
defer.reject(new Error());
}
}
/**
*
* list genres
*/
ControllerMpd.prototype.listGenres = function () {
var self = this;
var defer = libQ.defer();
var response = {
"navigation": {
"lists": [
{
"availableListViews": [
"list"
],
"items": [
]
}
]
}
};
var cmd = libMpd.cmd;
self.clientMpd.sendCommand(cmd("list", ["genre"]), function (err, msg) {
if(err)
defer.reject(new Error('Cannot list genres'));
else
{
var splitted=msg.split('\n');
for(var i in splitted)
{
if(splitted[i].startsWith('Genre:'))
{
var genreName=splitted[i].substring(7);
if(genreName!=='')
{
var albumart=self.getAlbumArt({},undefined,'fa-tags');
var album = {service:'mpd',type: 'folder', title: genreName, albumart:albumart, uri: 'genres://' + nodetools.urlEncode(genreName)};
response.navigation.lists[0].items.push(album);
}
}
}
defer.resolve(response);
}
});
return defer.promise;
};
/**
*
* list genre
*/
ControllerMpd.prototype.listGenre = function (curUri) {
var self = this;
var defer = libQ.defer();
var splitted=curUri.split('/');
var genreName=nodetools.urlDecode(splitted[2]);
var genreArtist=nodetools.urlDecode(splitted[3]);
var response={
"navigation": {
"lists": [
{
"title": self.commandRouter.getI18nString('COMMON.ARTISTS') + " " + self.commandRouter.getI18nString('COMMON.WITH') + " '" + genreName + "' " + self.commandRouter.getI18nString('COMMON.GENRE') + " " + self.commandRouter.getI18nString('COMMON.TRACKS'),
"icon": "fa icon",
"availableListViews": [
"list",
"grid"
],
"items": []
},
{
"title": self.commandRouter.getI18nString('COMMON.ALBUMS') + " " + self.commandRouter.getI18nString('COMMON.WITH') + " '" + genreName + "' " + self.commandRouter.getI18nString('COMMON.GENRE') + " " + self.commandRouter.getI18nString('COMMON.TRACKS'),
"icon": "fa icon",
"availableListViews": [
"list",
"grid"
],
"items": []
}
,{
"title": self.commandRouter.getI18nString('COMMON.TRACKS') + " - '" + genreName + "' " + self.commandRouter.getI18nString('COMMON.GENRE'),
"icon": "fa icon",
"availableListViews": [
"list"
],
"items": []
}
],
"prev": {
"uri": "genres://"
}
}
};
self.mpdReady
.then(function() {
var cmd = libMpd.cmd;
if (genreArtist != 'undefined') {
var findString = "find genre \"" + genreName + "\" artist \"" + genreArtist + "\" ";
}
else {
var findString = "find genre \"" + genreName + "\"";
}
self.clientMpd.sendCommand(cmd(findString, []), function (err, msg) {
var albums=[];
var albumsArt=[];
var artists=[];
var artistArt=[];
var list = [];
if (msg) {
var path;
var name;
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('file:') === 0) {
var path = line.slice(6);
var name = path.split('/').pop();
var artist = self.searchFor(lines, i + 1, 'Artist:');
var albumartist = self.searchFor(lines, i + 1, 'AlbumArtist:');
var album = self.searchFor(lines, i + 1, 'Album:');
//Include track number if tracknumber variable is set to 'true'
if (!tracknumbers) {
var title = self.searchFor(lines, i + 1, 'Title:');
}
else {
var title1 = self.searchFor(lines, i + 1, 'Title:');
var track = self.searchFor(lines, i + 1, 'Track:');
var title = track + " - " + title1;
}
var albumart = self.getAlbumArt({artist: albumartist, album: album}, self.getParentFolder(path),'fa-tags');;
if (title) {
title = title;
} else {
title = name;
}
if(title!=='') {
response.navigation.lists[2].items.push({
service: 'mpd',
type: 'song',
title: title,
artist: artist,
album: album,
albumart: albumart,
uri: 'music-library/' + path
});
}
if(albums.indexOf(album)===-1) {
albums.push(album);
albumsArt.push(albumart);
if(album!=='') {
response.navigation.lists[1].items.push({
service:'mpd',
type: 'folder',
title: album,
artist: albumartist,
albumart: albumart,
uri: 'genres://'+ genreName + '/' + nodetools.urlEncode(albumartist) +'/' + nodetools.urlEncode(album)});
}
}
if(artists.indexOf(artist)===-1) {
artists.push(artist);
artistArt.push()
if(artist!=='') {
response.navigation.lists[0].items.push({
service:'mpd',
type: 'folder',
title: artist,
albumart: self.getAlbumArt({artist:artist},undefined,'fa-users'),
uri: 'genres://' + nodetools.urlEncode(genreName)+'/'+nodetools.urlEncode(artist)});
}
}
}
}
defer.resolve(response);
}
else {
self.logger.info(err);
defer.reject(new Error());
}
});
});
return defer.promise;
};
ControllerMpd.prototype.getMixerControls = function () {
var self = this;
var cards = self.commandRouter.executeOnPlugin('audio_interface', 'alsa_controller', 'getMixerControls', '1');
cards.then(function (data) {
//console.log(data);
})
.fail(function () {
//console.log(data);
});
//console.log(cards)
};
ControllerMpd.prototype.getParentFolder = function (file) {
var index=file.lastIndexOf('/');
if(index>-1)
{
return file.substring(0,index);
}
else return '';
};
ControllerMpd.prototype.getAlbumArtPathFromUri = function (uri) {
var self = this;
var startIndex = 0;
var splitted = uri.split('/');
while (splitted[startIndex] === '') {
startIndex = startIndex + 1;
}
if (splitted[startIndex] === 'mnt') {
startIndex = startIndex + 1;
}
var result = '';
for (var i = startIndex; i < splitted.length - 1; i++) {
result = result + '/' + splitted[i];
}
return result;
}
ControllerMpd.prototype.prefetch = function (trackBlock) {
var self=this;
this.logger.info("DOING PREFETCH IN MPD");
var uri=this.sanitizeUri(trackBlock.uri);
this.logger.info(uri);
return this.sendMpdCommand('add "'+uri+'"',[])
.then(function(){
return self.sendMpdCommand('consume 1',[]);
});
}
ControllerMpd.prototype.goto=function(data){
if(data.type=='artist')
return this.listArtist('artists://'+nodetools.urlEncode(data.value),2,'')
else
return this.listAlbumSongs("albums://"+nodetools.urlEncode(data.value),2);
}
ControllerMpd.prototype.ignoreUpdate=function(data){
ignoreupdate = data;
}
ControllerMpd.prototype.ffwdRew=function(millisecs){
var self = this;
var defer = libQ.defer();
var cmd = libMpd.cmd;
var delta=millisecs/1000;
var param;
if(delta>0)
{
param='+'+delta;
}
else
{
param=delta;
}
//console.log("PARAM: "+param);
self.clientMpd.sendCommand(cmd("seekcur", [param]), function (err, msg) {
if(err)
defer.reject(new Error('Cannot seek '+millisecs));
else
{
defer.resolve();
}
});
return defer.promise;
};
ControllerMpd.prototype.loadLibrarySettings=function(){
var self = this;
var tracknumbersConf = this.config.get('tracknumbers', false);
var compilationConf = this.config.get('compilation', 'Various,various,Various Artists,various artists,VA,va')
var artistsortConf = this.config.get('artistsort', true);
tracknumbers = tracknumbersConf;
compilation = compilationConf.split(',');
artistsort = artistsort;
}
ControllerMpd.prototype.saveMusicLibraryOptions=function(data){
var self = this;
self.config.set('tracknumbers', data.tracknumbers);
self.config.set('compilation', data.compilation)
self.config.set('artistsort', data.artistsort.value);
tracknumbers = data.tracknumbers;
compilation = data.compilation.split(',');
artistsort = data.artistsort.value;
self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('APPEARANCE.MUSIC_LIBRARY_SETTINGS'), self.commandRouter.getI18nString('COMMON.CONFIGURATION_UPDATE'));
}
ControllerMpd.prototype.dsdVolume=function(){
var self = this;
if (dsd_autovolume) {
self.commandRouter.volumiosetvolume(100);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment