mirror of
https://github.com/IrosTheBeggar/mStream.git
synced 2025-10-27 07:31:02 +00:00
288 lines
8.4 KiB
JavaScript
288 lines
8.4 KiB
JavaScript
// This is designed to run as it's own process
|
|
// It takes in a json array
|
|
// {
|
|
// "vpath":"metal",
|
|
// "directory":"/path/to/metal/music",
|
|
// "dbSettings":{
|
|
// "dbPath":"/path/to/LATESTGREATEST.DB"
|
|
// }
|
|
// "albumArtDir": "/album/art/dir"
|
|
// }
|
|
|
|
// Parse input JSON
|
|
try{
|
|
var loadJson = JSON.parse(process.argv[process.argv.length-1], 'utf8');
|
|
}catch(error){
|
|
console.log('Cannot parse JSON input');
|
|
process.exit();
|
|
}
|
|
|
|
// Libraries
|
|
const metadata = require('music-metadata');
|
|
const fs = require('fs');
|
|
const fe = require('path');
|
|
const crypto = require('crypto');
|
|
|
|
// Setup DB layer
|
|
// The DB functions are dcoupled from this so they can easily be swapped out
|
|
const dbRead = require('../db-write/database-default-loki.js');
|
|
|
|
// Global Vars
|
|
var globalCurrentFileList = {}; // Map of file paths to metadata
|
|
var listOfFilesToParse = [];
|
|
var listOfFilesToDelete = [];
|
|
var mapOfDirectoryAlbumArt = {};
|
|
|
|
// Start the generator
|
|
const parseFilesGenerator = scanDirectory(loadJson.directory);
|
|
parseFilesGenerator.next();
|
|
|
|
// Scan the directory for new, modified, and deleted files
|
|
function *scanDirectory(directoryToScan){
|
|
yield dbRead.setup(loadJson.dbSettings.dbPath, function(){
|
|
parseFilesGenerator.next();
|
|
});
|
|
// Pull filelist from DB
|
|
pullFromDB();
|
|
// Loop through current files and compare them to the files pulled from the DB
|
|
recursiveScan(directoryToScan);
|
|
// Delete Files
|
|
for (var i=0; i < listOfFilesToDelete.length; i++) {
|
|
deleteFile(listOfFilesToDelete[i]);
|
|
}
|
|
// Delete all remaining files
|
|
for (var file in globalCurrentFileList) {
|
|
deleteFile(file);
|
|
}
|
|
// Parse and add files to DB
|
|
for (var i=0; i < listOfFilesToParse.length; i++) {
|
|
yield parseFile(listOfFilesToParse[i]);
|
|
}
|
|
|
|
yield dbRead.savedb(function(){
|
|
parseFilesGenerator.next();
|
|
})
|
|
// Exit
|
|
process.exit(0);
|
|
}
|
|
|
|
// Get all files form DB and add to globalCurrentFileList
|
|
function pullFromDB(){
|
|
dbRead.getVPathFiles(loadJson.vpath, function(rows){
|
|
for(var s of rows){
|
|
globalCurrentFileList[s.filepath] = s;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function recursiveScan(dir, fileTypesArray){
|
|
var files = fs.readdirSync( dir );
|
|
|
|
// loop through files
|
|
for (var i=0; i < files.length; i++) {
|
|
var filepath = fe.join(dir, files[i]);
|
|
try{
|
|
var stat = fs.statSync(filepath);
|
|
}catch(error){
|
|
// Bad file, ignore and continue
|
|
continue;
|
|
}
|
|
|
|
if(stat.isDirectory()){
|
|
recursiveScan(filepath);
|
|
}else{
|
|
// Make sure this is in our list of allowed files
|
|
var extension = getFileType(files[i]);
|
|
var fileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a"];
|
|
if (fileTypesArray.indexOf(extension) === -1 ) {
|
|
continue;
|
|
}
|
|
|
|
// Check if in globalCurrentFileList
|
|
if (!(filepath in globalCurrentFileList)){
|
|
// if not parse new file, add it to DB, and continue
|
|
listOfFilesToParse.push(filepath);
|
|
continue;
|
|
}
|
|
|
|
// check the file_modified_date
|
|
if(stat.mtime.getTime() !== globalCurrentFileList[filepath].modified){
|
|
listOfFilesToParse.push(filepath);
|
|
listOfFilesToDelete.push(filepath);
|
|
}
|
|
|
|
// Remove from globalCurrentFileList
|
|
delete globalCurrentFileList[filepath];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function parseFile(thisSong){
|
|
var filestat = fs.statSync(thisSong);
|
|
if(!filestat.isFile()){
|
|
// TODO: Something is fucky, log it
|
|
console.log('BAD FILE');
|
|
parseFilesGenerator.next();
|
|
return;
|
|
}
|
|
|
|
// Parse the file for metadata and store it in the DB
|
|
return metadata.parseFile(thisSong).then(function (thisMetadata) {
|
|
var songInfo = thisMetadata.common;
|
|
songInfo.filesize = filestat.size;
|
|
songInfo.created = filestat.birthtime.getTime();
|
|
songInfo.modified = filestat.mtime.getTime();
|
|
songInfo.filePath = thisSong;
|
|
songInfo.format = getFileType(thisSong);
|
|
return songInfo;
|
|
}).then(function (songInfo) {
|
|
// Calculate unique DB ID
|
|
return calculateHash(thisSong, songInfo);
|
|
}).then(function (songInfo) {
|
|
// Stores metadata of song in the database
|
|
return dbRead.insertEntries([songInfo], loadJson.vpath)
|
|
}).then(function () {
|
|
// Continue with next file
|
|
parseFilesGenerator.next();
|
|
}).catch(function (err) {
|
|
console.log("Warning: failed to parse file '%s': %s", thisSong, err.message);
|
|
parseFilesGenerator.next();
|
|
});
|
|
}
|
|
|
|
function calculateHash (thisSong, songInfo) {
|
|
return new Promise(function (resolve, reject) {
|
|
// Handle album art
|
|
// TODO: handle cases where multiple images in metadata
|
|
var bufferString = false;
|
|
var picFormat = false;
|
|
|
|
// Album art is in metadata
|
|
if (songInfo.picture && songInfo.picture[0]) {
|
|
bufferString = songInfo.picture[0].data.toString('utf8');
|
|
picFormat = songInfo.picture[0].format;
|
|
}
|
|
// Album art has been pulled from directory already
|
|
else if (mapOfDirectoryAlbumArt.hasOwnProperty(fe.dirname(thisSong)) && mapOfDirectoryAlbumArt[fe.dirname(thisSong)] !== false) {
|
|
songInfo.albumArtFilename = mapOfDirectoryAlbumArt[fe.dirname(thisSong)];
|
|
}
|
|
// Directory has not been scanned for album art yet
|
|
else if (!mapOfDirectoryAlbumArt.hasOwnProperty(fe.dirname(thisSong))){
|
|
var albumArt = checkDirectoryForAlbumArt(fe.dirname(thisSong));
|
|
if(albumArt){
|
|
songInfo.albumArtFilename = albumArt;
|
|
}
|
|
}
|
|
|
|
|
|
// Hash the file here and add the hash to the DB
|
|
var hash = crypto.createHash('sha256');
|
|
hash.setEncoding('hex');
|
|
var readableStream2 = fs.createReadStream(thisSong);
|
|
|
|
readableStream2.on('end', function () {
|
|
hash.end();
|
|
readableStream2.close();
|
|
|
|
songInfo.hash = String(hash.read());
|
|
|
|
if(bufferString !== false){
|
|
// Generate unique name based off hash of album art and metadata
|
|
var picHashString = crypto.createHash('sha256').update(bufferString).digest('hex');
|
|
songInfo.albumArtFilename = picHashString + '.' + picFormat;
|
|
// Cehck image-cache folder for filename and save if doesn't exist
|
|
if (!fs.existsSync(fe.join(loadJson.albumArtDir, songInfo.albumArtFilename))) {
|
|
// Save file sync
|
|
fs.writeFileSync(fe.join(loadJson.albumArtDir, songInfo.albumArtFilename), songInfo.picture[0].data);
|
|
}
|
|
}
|
|
|
|
resolve(songInfo);
|
|
});
|
|
|
|
readableStream2.pipe(hash);
|
|
});
|
|
}
|
|
|
|
function checkDirectoryForAlbumArt(directory){
|
|
var files = fs.readdirSync( directory );
|
|
var imageArray = [];
|
|
|
|
// loop through files
|
|
for (var i = 0; i < files.length; i++) {
|
|
var filepath = fe.join(directory, files[i]);
|
|
try{
|
|
var stat = fs.statSync(filepath);
|
|
}catch(error){
|
|
// Bad file, ignore and continue
|
|
continue;
|
|
}
|
|
|
|
if(stat.isDirectory()){
|
|
continue;
|
|
}
|
|
|
|
// Make sure its jpg/png
|
|
var extension = getFileType(files[i]);
|
|
var fileTypesArray = ["png", "jpg"];
|
|
if (fileTypesArray.indexOf(extension) === -1 ) {
|
|
continue;
|
|
}
|
|
imageArray.push(files[i]);
|
|
}
|
|
|
|
if(imageArray.length === 0){
|
|
mapOfDirectoryAlbumArt[directory] = false;
|
|
return;
|
|
}
|
|
|
|
var imageBuffer = false;
|
|
var picFormat = false;
|
|
|
|
// Only one image, assume it's album art
|
|
if(imageArray.length === 1){
|
|
imageBuffer = fs.readFileSync(fe.join(directory, imageArray[0]));
|
|
picFormat = getFileType(imageArray[0]);
|
|
}
|
|
|
|
// If there are multiple images, choose the first one with name cover, album, folder, etc
|
|
for (var i = 0; i < imageArray.length; i++) {
|
|
var imgMod = imageArray[i].toLowerCase();
|
|
if(imgMod === 'folder.jpg' || imgMod === 'cover.jpg' || imgMod === 'album.jpg' || imgMod === 'folder.png' || imgMod === 'cover.png' || imgMod === 'album.png'){
|
|
imageBuffer = fs.readFileSync(fe.join(directory, imageArray[i]));
|
|
picFormat = getFileType(imageArray[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// TODO: If none match, choose the largest ???
|
|
|
|
if(!imageBuffer){
|
|
mapOfDirectoryAlbumArt[directory] = false;
|
|
return;
|
|
}
|
|
|
|
var picHashString = crypto.createHash('sha256').update(imageBuffer.toString('utf8')).digest('hex');
|
|
var albumArtFilename = picHashString + '.' + picFormat;
|
|
|
|
// Cehck image-cache folder for filename and save if doesn't exist
|
|
if (!fs.existsSync(fe.join(loadJson.albumArtDir, albumArtFilename))) {
|
|
// Save file sync
|
|
fs.writeFileSync(fe.join(loadJson.albumArtDir, albumArtFilename), imageBuffer);
|
|
}
|
|
|
|
mapOfDirectoryAlbumArt[directory] = albumArtFilename;
|
|
return albumArtFilename;
|
|
}
|
|
|
|
function deleteFile(filepath){
|
|
dbRead.deleteFile(filepath, function(){ });
|
|
}
|
|
|
|
function getFileType(filename){
|
|
return filename.split(".").pop();
|
|
}
|