const { ObjectId } = require('mongodb'); const bcrypt = require("bcryptjs"); const ffmpeg = require("fluent-ffmpeg"); const fs = require("fs"); const notifier = require("../notifier"); const server = require("../../server"); const config = server.config; const connector = require("./CONNECTOR"); var dbo; connector.connect().then((ret) => { dbo = ret; }); notifier.on("metadata_excluded", item => { insert_artist_if_not_exists(item, artist => { insert_album_if_not_exists(item, artist, album => { insert_track_if_not_exists(item, album); }); }); }); notifier.on("pathdata_excluded", item => { insert_box_if_not_exists(item, box => { insert_video_if_not_exists(item, box); }); }); notifier.on("check_video_details", item => { if (!item.tracks) { get_video_tracks(item, tracks => { item.tracks = tracks; update_video(item); }); } if (!item.thumbnail) { get_video_thumbnail(item, thumbnail => { item.thumbnail = thumbnail; update_video(item); }); } }); /* MODULES */ const albums = require("./albums"); exports.albums = albums; const radios = require("./radios"); exports.radios = radios; const tracks = require("./tracks"); exports.tracks = tracks; const artists = require("./artists"); exports.artists = artists; const boxes = require("./boxes"); exports.boxes = boxes; const progress = require("./progress"); exports.progress = progress; const videos = require("./videos"); exports.videos = videos; const users = require("./users"); exports.users = users; const system = require("./system"); exports.system = system; const share = require("./share"); exports.share = share; exports.artist_count = function (callback) { return dbo.collection("artists").countDocuments(callback); }; exports.album_count = function (callback) { return dbo.collection("albums").countDocuments(callback); }; exports.track_count = function (callback) { return dbo.collection("tracks").countDocuments(callback); }; exports.box_count = function (callback) { return dbo.collection("boxes").countDocuments(callback); }; exports.video_count = function (callback) { return dbo.collection("videos").countDocuments(callback); }; exports.users_count = function (callback) { return dbo.collection("users").countDocuments(callback); }; /* USER */ exports.addUser = function (user, callback) { bcrypt.genSalt(10, function (err, salt) { bcrypt.hash(user.password, salt, function (err, hash) { user.password = hash; if (!user.roles) { user.roles = ["user"]; } user.name = user.name.toLowerCase(); user.player = {}; user.fullname = user.name; dbo.collection("users").insertOne(user, err => { if (err) throw err; dbo.collection("users").findOne({ name: user.name }, (err, result) => { if (err) throw err; delete result.password; callback(result); }); }); }); }); }; exports.deleteUser = function (id, callback) { dbo .collection("users") .deleteOne({ _id: ObjectId(id) }, (err, result) => { if (err) throw err; callback(result); }); }; exports.updateUserRole = function (user, callback) { dbo .collection("users") .updateOne({ name: user.name }, { $set: { roles: user.roles } }, err => { if (err) throw err; callback(); }); }; exports.updateUserAccess = function (name) { dbo .collection("users") .updateOne( { name: name }, { $set: { last_access: new Date().toLocaleString() } } ); }; exports.updateUserPassword = function (user, newPassowrd) { bcrypt.genSalt(10, function (err, salt) { bcrypt.hash(newPassowrd, salt, function (err, hash) { dbo .collection("users") .updateOne({ name: user }, { $set: { password: hash } }, err => { if (err) throw err; }); }); }); }; exports.updateUserConfig = function (user, body, callback) { dbo.collection("users").updateOne( { name: user.name }, { $set: { mobile_bpm: body.mobile_bpm, desktop_bpm: body.desktop_bpm, video_lang: body.video_lang, video_quality: body.video_quality, fullname: body.fullname } }, err => { if (err) throw err; if (callback) { callback(); } } ); }; exports.updateUserSettings = function (user, callback) { dbo .collection("users") .updateOne( { _id: user._id }, { $set: { player: user.player } }, err => { if (err) throw err; if (callback) { callback(); } } ); }; exports.userByName = function (name, callback) { dbo.collection("users").findOne({ name: name.toLowerCase() }, (err, result) => { if (err) throw err; callback(result); }); }; exports.userById = function (id, callback) { dbo .collection("users") .findOne({ _id: ObjectId(id) }, (err, result) => { if (err) throw err; callback(result); }); }; /* HISTORY */ exports.historyList = function (id, callback) { dbo .collection("history") .find( { userId: id, type: { $in: ['album', 'artist', 'box', 'radio'] } }, { projection: { _id: false, userId: false } } ) .sort({ _id: -1 }) .limit(12) .toArray((err, result) => { if (err) throw err; callback(result); }); }; exports.clearHistory = function (id, callback) { dbo .collection("history") .deleteMany({ userId: id }, (err, result) => { if (err) throw err; callback(result); }); }; exports.updateHistory = function (item, callback) { dbo .collection("history") .deleteMany({ userId: item.userId, id: item.id, type: { $in: ['album', 'artist', 'box', 'radio'] } }, err => { if (err) throw err; dbo .collection("history") .insertOne(item, err => { if (err) throw err; callback(); } ); }); }; exports.search = function (term, callback) { let r = []; dbo .collection("artists") .find( { name: { $regex: term, $options: "i" } }, { projection: { _id: true, name: true, "covers.cover256": true } } ) .toArray((err, result) => { if (result && result.length > 0) { result.forEach(item => { item.type = "artist"; item.search_rank = item.name.toLowerCase().indexOf(term); }); r = r.concat(result); } dbo .collection("albums") .find( { title: { $regex: term, $options: "i" } }, { projection: { _id: true, title: true, "covers.cover128": true } } ) .toArray((err, result) => { if (result && result.length > 0) { result.forEach(item => { item.type = "album"; item.parent = {}; item.search_rank = item.title.toLowerCase().indexOf(term); }); r = r.concat(result); } dbo .collection("tracks") .aggregate([ { $lookup: { from: "albums", localField: "album_id", foreignField: "_id", as: "parent" } }, { $unwind: "$parent" }, { $project: { album_id: false, bitrate: false, disk: false, duration: false, mime: false, path: false, genre: false, track: false, "parent.artist_id": false, "parent.year": false, "parent.covers.cover32": false, "parent.covers.cover128": false, "parent.covers.cover256": false, "parent.covers.cover512": false } }, { $match: { title: { $regex: term, $options: "i" } } }]) .toArray((err, result) => { if (result && result.length > 0) { result.forEach(item => { item.type = "track"; item.search_rank = item.title.toLowerCase().indexOf(term); }); r = r.concat(result); } dbo .collection("boxes") .find( { title: { $regex: term, $options: "i" } }, { projection: { _id: true, title: true, "covers.cover128": true } } ) .toArray((err, result) => { if (result && result.length > 0) { result.forEach(item => { item.type = "box"; item.search_rank = item.title.toLowerCase().indexOf(term); }); r = r.concat(result); } dbo .collection("videos") .aggregate([ { $lookup: { from: "boxes", localField: "box_id", foreignField: "_id", as: "parent" } }, { $unwind: "$parent" }, { $project: { path: false, mime: false, tracks: false, box_id: false, "parent.box_id": false, "parent.year": false, "parent.path": false, "parent.title": false, "parent.covers.cover32": false, "parent.covers.cover64": false, "parent.covers.cover128": false, "parent.covers.cover256": false, "parent.covers.cover512": false } }, { $match: { title: { $regex: term, $options: "i" } } }] ) .toArray((err, result) => { if (result && result.length > 0) { result.forEach(item => { item.type = "video"; item.search_rank = item.title .toLowerCase() .indexOf(term); }); r = r.concat(result); } dbo .collection("radios") .find( { name: { $regex: term, $options: "i" } }, { projection: { _id: true, name: true } } ) .toArray((err, result) => { if (result && result.length > 0) { result.forEach(item => { item.type = "radio"; item.search_rank = item.name .toLowerCase() .indexOf(term); }); r = r.concat(result); } callback( r .sort((a, b) => { return a.search_rank - b.search_rank; }) .splice(0, 50) ); }); }); }); }); }); }); }; /* CHAT */ exports.messageList = function (callback) { dbo .collection("chat") .aggregate([ { $lookup: { from: "users", localField: "user_id", foreignField: "_id", as: "user" } }, { $project: { "user.password": false, "user.player": false } }, { $unwind: "$user" }, { $sort: { _id: 1 } } ]) .toArray((err, result) => { if (err) throw err; callback(result.splice(0, 100)); }); }; exports.writeMessage = function (user, message, callback) { dbo .collection("chat") .insertOne( { user_id: user._id, message: message, timestamp: Date.now() }, (err, result) => { if (err) throw err; callback(result.ops); } ); }; exports.deleteMessage = function (id, user, callback) { dbo.collection("chat").deleteOne({ _id: ObjectId(id), user_id: user._id }, err => { if (err) throw err; callback(); }); }; /* FUNCTIONS */ function insert_artist_if_not_exists(item, callback) { dbo.collection("artists").findOne({ name: item.artist }, (err, result) => { if (err) throw err; if (result) { callback(result); } else { let artist = { name: item.artist, insert_on: Date.now() } if (item.owner) { artist.owner_id = item.owner._id; } dbo .collection("artists") .updateOne( { name: item.artist }, { $set: artist }, { upsert: true }, err => { if (err) throw err; insert_artist_if_not_exists(item, callback); } ); } }); } function insert_album_if_not_exists(item, artist, callback) { dbo .collection("albums") .findOne({ artist_id: artist._id, title: item.album }, (err, result) => { if (err) throw err; if (result) { callback(result); } else { let album = { artist_id: artist._id, artist_name: artist.name, title: item.album.trim(), year: item.year, insert_on: Date.now() } if (item.covers) { album.cover32 = item.covers.cover32; album.cover64 = item.covers.cover64; album.cover128 = item.covers.cover128; album.cover256 = item.covers.cover256; } if (item.owner) { album.owner_id = item.owner._id; } dbo.collection("albums").updateOne( { artist_id: artist._id, title: item.album }, { $set: album }, { upsert: true }, err => { if (err) throw err; insert_album_if_not_exists(item, artist, callback); } ); } }); } function insert_track_if_not_exists(item, album, callback) { let short_path = "" if (item.path.indexOf(config.upload_folder) == 0) { short_path = item.path.replace(config.upload_folder, ""); } else { short_path = item.path.replace(config.music_folder, ""); } dbo.collection("tracks").updateOne( { path: short_path }, { $set: { title: item.title, path: short_path, album_id: album._id, duration: item.duration, bitrate: item.bitrate, mime: item.mime, disk: item.disk, track: item.track, genre: item.genre, insert_on: Date.now() } }, { upsert: true }, err => { if (err) throw err; dbo.collection("tracks").findOne({ path: short_path }, (err, result) => { if (err) throw err; if (callback) { callback(result); } }); } ); } function insert_box_if_not_exists(item, callback) { let short_path = item.box_path.replace(config.video_folder, ""); dbo .collection("boxes") .findOne({ title: item.box, year: item.year }, (err, result) => { if (err) throw err; if (result) { callback(result); } else { let box = { title: item.box, path: short_path, year: item.year, cover64: item.cover64, cover128: item.cover128, cover256: item.cover256, cover512: item.cover512, insert_on: Date.now() } if (item.owner) { box.owner_id = item.owner._id; } dbo .collection("boxes") .updateOne( { title: item.box }, { $set: box }, { upsert: true }, err => { if (err) throw err; insert_box_if_not_exists(item, callback); } ); } }); } function insert_video_if_not_exists(item, box, callback) { item.path = item.path.replace(config.video_folder, ""); dbo.collection("videos").updateOne( { path: item.path }, { $set: { title: item.title, path: item.path, box_id: box._id, mime: item.mime, insert_on: Date.now() } }, { upsert: true }, err => { if (err) throw err; dbo.collection("videos").findOne({ path: item.path }, (err, result) => { if (err) throw err; if (callback) { callback(result); } if (!result.tracks) { get_video_tracks(item, tracks => { result.tracks = tracks; update_video(result); }); } if (!result.thumbnail) { get_video_thumbnail(result, thumbnail => { result.thumbnail = thumbnail; update_video(result); }); } }); } ); } function update_video(video, callback) { process.stdout.write("updating video: " + video.title + "\n"); let val = {}; if (video.tracks) { val.tracks = video.tracks; } if (video.thumbnail) { val.thumbnail = video.thumbnail; } if (!val) { if (callback) { callback(); } return; } dbo .collection("videos") .updateOne({ _id: video._id }, { $set: val }, { upsert: false }, err => { if (err) throw err; if (callback) { callback(); } }); } function get_video_thumbnail(video, callback) { let path = config.video_folder + video.path; if (!fs.existsSync(path)) { path = config.upload_folder + video.path; } ffmpeg(path) .on("end", () => { process.stdout.write("Thumbnail for '" + video.title + "' created\n"); let thumbnail = config.cache_folder + "/video_covers/" + video._id.toString() + ".png"; if (!fs.existsSync(thumbnail)) { callback(null); return; } let bitmap = fs.readFileSync(thumbnail); callback("data:image/png;base64, " + bitmap.toString("base64")); fs.unlinkSync(thumbnail); }) .on("error", err => { process.stdout.write( "Thumbnail error for " + video.title + ": " + err.message + "\n" ); callback(null); }) .screenshot({ timestamps: ["20%"], filename: video._id.toString(), folder: config.cache_folder + "/video_covers", size: "346x180" }); } function get_video_tracks(video, callback) { let path = config.video_folder + video.path; if (!fs.existsSync(path)) { path = config.upload_folder + video.path; } ffmpeg(path).ffprobe((err, data) => { if (!data) { return callback([]); } let audio_streams = data.streams.filter(f => f.codec_type == "audio"); let return_value = audio_streams.map(m => { return { index: m.index, lang: (m.tags && m.tags.language ? m.tags.language : m.index ? m.index : "" ).toString(), title: (m.tags && m.tags.title ? m.tags.title : m.index ? m.index : "" ).toString() }; }); callback(return_value); }); }