This commit is contained in:
commit
cff48aaada
8
.drone.yml
Normal file
8
.drone.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: node:latest
|
||||||
|
commands:
|
||||||
|
- npm install
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
cache/
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# WebPlay Server
|
||||||
|
[![Build Status](https://drone.anufrij.de/api/badges/WebPlay/server/status.svg)](https://drone.anufrij.de/WebPlay/server)
|
||||||
|
|
||||||
|
WebPlay Server provides REST-API for your media files. In then next step, you can use [WebPlay Client](/WebPlay/client) to access your content.
|
||||||
|
|
||||||
|
## How to install your onw instance
|
||||||
|
[Documentation](/WebPlay/docker#requirements)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
Join our Matrix room: <a href="https://matrix.to/#/#WebPlay:matrix.anufrij.de">#WebPlay:matrix.anufrij.de</a>
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
<a href="https://www.paypal.me/ArtemAnufrij">PayPal</a> | <a href="https://liberapay.com/Artem/donate">LiberaPay</a> | <a href="https://www.patreon.com/ArtemAnufrij">Patreon</a>
|
65
config.json
Normal file
65
config.json
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"port": 31204,
|
||||||
|
"music_folder": "/webplay/music",
|
||||||
|
"video_folder": "/webplay/videos",
|
||||||
|
"cache_folder": "/webplay/cache",
|
||||||
|
"upload_folder": "/webplay/uploads",
|
||||||
|
"domain": "",
|
||||||
|
"allowed_domains": [
|
||||||
|
"https://webplay.rocks",
|
||||||
|
"http://localhost"
|
||||||
|
],
|
||||||
|
"database": {
|
||||||
|
"host": "database",
|
||||||
|
"port": 27017,
|
||||||
|
"name": "webplay"
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"host": "redis",
|
||||||
|
"port": 6379
|
||||||
|
},
|
||||||
|
"album_cover_files": [
|
||||||
|
"cover.jpg",
|
||||||
|
"Cover.jpg",
|
||||||
|
"cover.jpeg",
|
||||||
|
"Cover.jpeg",
|
||||||
|
"cover.png",
|
||||||
|
"Cover.png",
|
||||||
|
"folder.jpg",
|
||||||
|
"Folder.jpg",
|
||||||
|
"folder.png",
|
||||||
|
"Folder.png",
|
||||||
|
"front.jpg",
|
||||||
|
"Front.jpg",
|
||||||
|
"front.png",
|
||||||
|
"front.png",
|
||||||
|
"album.jpg",
|
||||||
|
"Album.jpg",
|
||||||
|
"album.png",
|
||||||
|
"Album.png"
|
||||||
|
],
|
||||||
|
"artist_cover_files": [
|
||||||
|
"artist.jpg",
|
||||||
|
"Artist.jpg",
|
||||||
|
"artist.jpeg",
|
||||||
|
"Artist.jpeg",
|
||||||
|
"artist.png",
|
||||||
|
"Artist.png",
|
||||||
|
"band.jpg",
|
||||||
|
"Band.jpg",
|
||||||
|
"band.jpeg",
|
||||||
|
"Band.jpeg",
|
||||||
|
"band.png",
|
||||||
|
"Band.png"
|
||||||
|
],
|
||||||
|
"box_cover_files": [
|
||||||
|
"cover.jpg",
|
||||||
|
"Cover.jpg",
|
||||||
|
"cover.png",
|
||||||
|
"Cover.png",
|
||||||
|
"poster.jpg",
|
||||||
|
"Poster.jpg",
|
||||||
|
"poster.png",
|
||||||
|
"Poster.png"
|
||||||
|
]
|
||||||
|
}
|
7215
package-lock.json
generated
Normal file
7215
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
package.json
Normal file
40
package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "webplay_server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "WebPlay Backend",
|
||||||
|
"author": "Artem Anufrij <artem.anufrij@live.de>",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"backend": "cross-env NODE_ENV=development npx nodemon ./server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"body-parser": "^1.19.1",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.17.2",
|
||||||
|
"express-fileupload": "^1.3.1",
|
||||||
|
"express-session": "^1.17.2",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"mime-types": "^2.1.34",
|
||||||
|
"mongodb": "^4.3.1",
|
||||||
|
"music-metadata": "^7.11.8",
|
||||||
|
"node-fdkaac": "^1.4.1",
|
||||||
|
"node-id3": "^0.2.3",
|
||||||
|
"node-lame": "^1.3.2",
|
||||||
|
"passport": "^0.5.2",
|
||||||
|
"passport-jwt": "^4.0.0",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"redis": "^4.5.1",
|
||||||
|
"request": "^2.88.2",
|
||||||
|
"sharp": "0.27.2",
|
||||||
|
"socket.io": "^4.4.1",
|
||||||
|
"systeminformation": "^5.11.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"nodemon": "^2.0.15"
|
||||||
|
}
|
||||||
|
}
|
88
router/activitypub.js
Normal file
88
router/activitypub.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = new express.Router();
|
||||||
|
|
||||||
|
const database = require("../services/database");
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const config = server.config;
|
||||||
|
|
||||||
|
|
||||||
|
router.route("/:user")
|
||||||
|
.get((req, res) => {
|
||||||
|
database.userByName(req.params.user.toLowerCase(), user => {
|
||||||
|
if (user == undefined) {
|
||||||
|
return req.status(404).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = "https://" + config.domain + "/users/" + user.name;
|
||||||
|
let inbox = "https://" + config.domain + "/users/" + user.name + "/inbox";
|
||||||
|
let outbox = "https://" + config.domain + "/users/" + user.name + "/outbox";
|
||||||
|
let followers = "https://" + config.domain + "/users/" + user.name + "/followers";
|
||||||
|
let following = "https://" + config.domain + "/users/" + user.name + "/followinig";
|
||||||
|
let liked = "https://" + config.domain + "/users/" + user.name + "/liked";
|
||||||
|
|
||||||
|
let response = {
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"type": "Person",
|
||||||
|
"id": id,
|
||||||
|
"name": "Artem",
|
||||||
|
"preferredUsername": user.name,
|
||||||
|
"summary": "cajon player and e-bass junkie",
|
||||||
|
"inbox": inbox,
|
||||||
|
"outbox": outbox,
|
||||||
|
"followers": followers,
|
||||||
|
"following": following,
|
||||||
|
"liked": liked
|
||||||
|
}
|
||||||
|
res.json(response).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:user/inbox")
|
||||||
|
.get((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:user/outbox")
|
||||||
|
.get((req, res) => {
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:user/followers")
|
||||||
|
.get((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
router.route("/:user/following")
|
||||||
|
.get((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:user/liked")
|
||||||
|
.get((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
console.log(req);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
119
router/album.js
Normal file
119
router/album.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
console.log("router/album INIT");
|
||||||
|
const express = require("express");
|
||||||
|
const database = require("../services/database");
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const passport = server.passport;
|
||||||
|
const checkGuest = server.checkGuest;
|
||||||
|
const resize_image_for_album = require("../services/cover/resizer").resize_image_for_album
|
||||||
|
|
||||||
|
const router = new express.Router();
|
||||||
|
|
||||||
|
router.route("/favourites")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.albums.favourites(req.user._id, result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
router.route("/page/:page")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/album GET albums page " + req.params.page + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.albums.collection(req.params.page, filter, result => {
|
||||||
|
process.stdout.write("router/album GET albums page " + req.params.page + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/newest/:count")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/album GET newest " + req.params.count + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.albums.newest(parseInt(req.params.count), filter, result => {
|
||||||
|
process.stdout.write("router/album GET newest " + req.params.count + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/album GET album by id " + req.params.id + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.albums.byId(req.params.id, filter, result => {
|
||||||
|
process.stdout.write("router/album GET album by id " + req.params.id + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.user.roles.indexOf("admin") >= 0 || req.user.roles.indexOf("moderator") >= 0) {
|
||||||
|
database.albums.update(req.body);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/filter/:term")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("router/album GET filter by term " + req.params.term + "\n");
|
||||||
|
database.albums.filter(req.params.term, result => {
|
||||||
|
process.stdout.write("router/album GET filter by term " + req.params.term + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
router.route("/:id/cover")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.files) {
|
||||||
|
if (req.files.file) {
|
||||||
|
resize_image_for_album(req.files.file.data, (result) => {
|
||||||
|
database.albums.updateCovers({ _id: req.params.id }, result);
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.albums.updateCovers({ _id: req.params.id }, {});
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/move")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let source = {};
|
||||||
|
let target = {};
|
||||||
|
database.albums.byId(req.body.source, undefined, (result) => {
|
||||||
|
if (result != undefined && (req.user.roles.indexOf("admin") > -1) || result.owner_id == req.user._id) {
|
||||||
|
source = result;
|
||||||
|
database.albums.byId(req.body.target, undefined, (result) => {
|
||||||
|
if (result && (req.user.roles.indexOf("admin") > -1 || result.owner_id == req.user._id)) {
|
||||||
|
target = result;
|
||||||
|
for (let i = 0; i < source.tracks.length; i++) {
|
||||||
|
let track = source.tracks[i]
|
||||||
|
track.album_id = target._id
|
||||||
|
database.tracks.moveTo(track);
|
||||||
|
}
|
||||||
|
database.albums.delete(source, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
102
router/artist.js
Normal file
102
router/artist.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
console.log("router/artist INIT");
|
||||||
|
const express = require("express");
|
||||||
|
console.log("router/artist REQUIRE database");
|
||||||
|
const database = require("../services/database");
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const passport = server.passport;
|
||||||
|
const checkGuest = server.checkGuest;
|
||||||
|
const resize_image_for_artist = require("../services/cover/resizer").resize_image_for_artist
|
||||||
|
|
||||||
|
const router = new express.Router();
|
||||||
|
|
||||||
|
router.route("/favourites")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.artists.favourites(req.user._id, result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/page/:page")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/artist GET artists page " + req.params.page + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.artists.collection(req.params.page, filter, result => {
|
||||||
|
process.stdout.write("router/artist GET artists page " + req.params.page + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:id")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/artist GET by id " + req.params.id + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.artists.byId(req.params.id, filter, result => {
|
||||||
|
process.stdout.write("router/artist GET by id " + req.params.id + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/filter/:term")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("router/artist GET filter by term " + req.params.term + "\n");
|
||||||
|
database.artists.filter(req.params.term, result => {
|
||||||
|
process.stdout.write("router/artist GET filter by term " + req.params.term + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
router.route("/:id/cover")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.files) {
|
||||||
|
if (req.files.file) {
|
||||||
|
resize_image_for_artist(req.files.file.data, (result) => {
|
||||||
|
database.artists.updateCovers({ _id: req.params.id }, result);
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.artists.updateCovers({ _id: req.params.id }, {});
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/move")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let source = {};
|
||||||
|
let target = {};
|
||||||
|
database.artists.byId(req.body.source, undefined, (result) => {
|
||||||
|
if (result && req.user.roles.indexOf("admin") > -1) {
|
||||||
|
source = result;
|
||||||
|
database.artists.byId(req.body.target, undefined, (result) => {
|
||||||
|
if (result && req.user.roles.indexOf("admin") > -1) {
|
||||||
|
target = result;
|
||||||
|
for (let i = 0; i < source.albums.length; i++) {
|
||||||
|
let album = source.albums[i];
|
||||||
|
album.artist_id = target._id;
|
||||||
|
album.artist_name = target.name;
|
||||||
|
database.albums.moveTo(album);
|
||||||
|
}
|
||||||
|
database.artists.delete(source, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
module.exports = router;
|
118
router/box.js
Normal file
118
router/box.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
console.log("router/box INIT");
|
||||||
|
const express = require("express");
|
||||||
|
const database = require("../services/database");
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const passport = server.passport;
|
||||||
|
const checkGuest = server.checkGuest;
|
||||||
|
const resize_image_for_box = require("../services/cover/resizer").resize_image_for_box
|
||||||
|
|
||||||
|
const router = new express.Router();
|
||||||
|
|
||||||
|
router.route("/favourites")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.boxes.favourites(req.user._id, result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/page/:page")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/box GET boxes page " + req.params.page + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.boxes.collection(req.params.page, filter, result => {
|
||||||
|
process.stdout.write("router/box GET boxes page " + req.params.page + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/newest/:count")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/box GET newest " + req.params.count + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.boxes.newest(parseInt(req.params.count), filter, result => {
|
||||||
|
process.stdout.write("router/box GET newest " + req.params.count + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:id")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
process.stdout.write("router/box GET by id " + req.params.id + "\n");
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.boxes.byId(req.params.id, filter, result => {
|
||||||
|
process.stdout.write("router/box GET by id " + req.params.id + " DB result\n");
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
}).put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.user.roles.indexOf("admin") >= 0 || req.user.roles.indexOf("moderator") >= 0) {
|
||||||
|
database.boxes.update(req.body);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/filter/:term")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("router/boxes GET filter by term " + req.params.term + "\n");
|
||||||
|
database.boxes.filter(req.params.term, result => {
|
||||||
|
process.stdout.write("router/boxes GET filter by term " + req.params.term + " DB result\n");
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
router.route("/:id/cover")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.files) {
|
||||||
|
if (req.files.file) {
|
||||||
|
resize_image_for_box(req.files.file.data, (result) => {
|
||||||
|
database.boxes.updateCovers({ _id: req.params.id }, result);
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.boxes.updateCovers({ _id: req.params.id }, {});
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/move")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let source = {};
|
||||||
|
let target = {};
|
||||||
|
database.boxes.byId(req.body.source, undefined, (result) => {
|
||||||
|
if (result && (req.user.roles.indexOf("admin") > -1 || result.owner_id == req.user._id)) {
|
||||||
|
source = result;
|
||||||
|
database.boxes.byId(req.body.target, undefined, (result) => {
|
||||||
|
if (result && (req.user.roles.indexOf("admin") > -1 || result.owner_id == req.user._id)) {
|
||||||
|
target = result;
|
||||||
|
for (let i = 0; i < source.videos.length; i++) {
|
||||||
|
let video = source.videos[i]
|
||||||
|
video.box_id = target._id
|
||||||
|
database.videos.moveTo(video);
|
||||||
|
}
|
||||||
|
database.boxes.delete(source, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
module.exports = router;
|
16
router/index.js
Normal file
16
router/index.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
exports.albumRouter = require("./album");
|
||||||
|
exports.artistRouter = require("./artist");
|
||||||
|
exports.trackRouter = require("./track");
|
||||||
|
exports.radioRouter = require("./radio")
|
||||||
|
exports.boxRouter = require("./box");
|
||||||
|
exports.videoRouter = require("./video");
|
||||||
|
exports.scanRouter = require("./scan");
|
||||||
|
exports.userRouter = require("./user");
|
||||||
|
exports.loginRouter = require("./login");
|
||||||
|
exports.infoRouter = require("./info");
|
||||||
|
exports.systemRouter = require("./system")
|
||||||
|
exports.statusRouter = require("./status");
|
||||||
|
exports.settingsRouter = require("./settings");
|
||||||
|
exports.searchRouter = require("./search");
|
||||||
|
// exports.activitypubRouter = require("./activitypub");
|
||||||
|
// exports.wellknownRouter = require("./well-known");
|
33
router/info.js
Normal file
33
router/info.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
console.log("router/info INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var router = new express.Router();
|
||||||
|
var database = require("../services/database");
|
||||||
|
|
||||||
|
let info = {}
|
||||||
|
info.stats = {}
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("")
|
||||||
|
.get((req, res) => {
|
||||||
|
database.artist_count((q, c) => {
|
||||||
|
info.stats.artists = c;
|
||||||
|
database.album_count((q, c) => {
|
||||||
|
info.stats.albums = c;
|
||||||
|
database.track_count((q, c) => {
|
||||||
|
info.stats.tracks = c;
|
||||||
|
database.box_count((q, c) => {
|
||||||
|
info.stats.boxes = c;
|
||||||
|
database.video_count((q, c) => {
|
||||||
|
info.stats.videos = c;
|
||||||
|
database.users_count((q, c) => {
|
||||||
|
info.stats.users = c;
|
||||||
|
res.json(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
59
router/login.js
Normal file
59
router/login.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
console.log("router/login INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var router = new express.Router();
|
||||||
|
var bcrypt = require("bcryptjs");
|
||||||
|
var database = require("../services/database");
|
||||||
|
var jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
var server = require("../server");
|
||||||
|
var passport = server.passport;
|
||||||
|
var secret = server.secret;
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/login")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let user = req.user;
|
||||||
|
database.historyList(user._id, history => {
|
||||||
|
user.history = history;
|
||||||
|
database.users.favourites(user._id, favourites => {
|
||||||
|
user.favourites = favourites;
|
||||||
|
res.status(200).json(req.user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
database.userByName(req.body.username, user => {
|
||||||
|
if (!user) {
|
||||||
|
process.stdout.write("no user in DB\n");
|
||||||
|
return res.status(401).send({
|
||||||
|
success: false,
|
||||||
|
msg: "Authentication failed. Wrong user."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bcrypt.compare(req.body.password, user.password, (err, isMatch) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (isMatch) {
|
||||||
|
var token = jwt.sign(user, secret);
|
||||||
|
user.token = "JWT " + token;
|
||||||
|
process.stdout.write("pass ok\n");
|
||||||
|
database.updateUserAccess(req.body.username);
|
||||||
|
database.historyList(user._id, history => {
|
||||||
|
user.history = history;
|
||||||
|
database.users.favourites(user._id, favourites => {
|
||||||
|
user.favourites = favourites;
|
||||||
|
res.status(200).json(user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
process.stdout.write("pass fail\n");
|
||||||
|
res.status(401).send({
|
||||||
|
success: false,
|
||||||
|
msg: "Authentication failed. Wrong password."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
71
router/radio.js
Normal file
71
router/radio.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
console.log("router/radio INIT");
|
||||||
|
const express = require("express");
|
||||||
|
console.log("router/radio REQUIRE END");
|
||||||
|
|
||||||
|
const database = require("../services/database");
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const passport = server.passport;
|
||||||
|
const checkGuest = server.checkGuest;
|
||||||
|
const resize_image_for_radio = require("../services/cover/resizer").resize_image_for_radio;
|
||||||
|
|
||||||
|
console.log("router/radio CREATE instances");
|
||||||
|
|
||||||
|
var router = new express.Router();
|
||||||
|
|
||||||
|
console.log("router/radio READY");
|
||||||
|
router
|
||||||
|
.route("")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
database.radios.collection(result => {
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("add radio\n");
|
||||||
|
if (
|
||||||
|
req.body.name == "" ||
|
||||||
|
!req.body.url ||
|
||||||
|
!req.body.url.startsWith("http")
|
||||||
|
) {
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
let newRadio = {
|
||||||
|
name: req.body.name,
|
||||||
|
url: req.body.url
|
||||||
|
};
|
||||||
|
database.radios.add(newRadio, () => {
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("delete radio\n");
|
||||||
|
database.radios.delete(req.query.id, () => {
|
||||||
|
database.radios.collection(result => {
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:id/cover")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("update radio cover\n");
|
||||||
|
if (req.files) {
|
||||||
|
database.radios.byId(req.params.id, radio => {
|
||||||
|
if (radio) {
|
||||||
|
if (req.files.file) {
|
||||||
|
resize_image_for_radio(req.files.file.data, (result) => {
|
||||||
|
radio.cover32 = result.cover32;
|
||||||
|
radio.cover64 = result.cover64;
|
||||||
|
radio.cover128 = result.cover128;
|
||||||
|
database.radios.update(radio);
|
||||||
|
res.json(radio).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
48
router/scan.js
Normal file
48
router/scan.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
console.log("router/scan INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var file_scanner = require("../services/files_scanner");
|
||||||
|
var cover_excluder = require("../services/cover_excluder");
|
||||||
|
|
||||||
|
console.log("router/scan LOAD server settings");
|
||||||
|
var server = require("../server");
|
||||||
|
var status = server.status;
|
||||||
|
var config = server.config;
|
||||||
|
var passport = server.passport;
|
||||||
|
|
||||||
|
var router = new express.Router();
|
||||||
|
|
||||||
|
console.log("router/scan SET routers");
|
||||||
|
router
|
||||||
|
.route("/music")
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (!status.scanning_music) {
|
||||||
|
status.scanning_music = true;
|
||||||
|
process.stdout.write("music scann started…\n");
|
||||||
|
res.send("music scann started…");
|
||||||
|
file_scanner.scann_local_music_files(config.music_folder);
|
||||||
|
setTimeout(() => {
|
||||||
|
file_scanner.scann_local_music_files(config.upload_folder);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
res.send("please wait. server is scanning for music files…");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/video")
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (!status.scanning_video) {
|
||||||
|
status.scanning_video = true;
|
||||||
|
process.stdout.write("video scann started…\n");
|
||||||
|
res.send("video scann started…");
|
||||||
|
file_scanner.scann_local_video_files(config.video_folder);
|
||||||
|
setTimeout(() => {
|
||||||
|
file_scanner.scann_local_video_files(config.upload_folder);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
res.send("please wait. server is scanning for video files…");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("router/scan EXPORT routers");
|
||||||
|
module.exports = router;
|
17
router/search.js
Normal file
17
router/search.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
console.log("router/search INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var server = require("../server");
|
||||||
|
var database = require("../services/database");
|
||||||
|
var passport = server.passport;
|
||||||
|
|
||||||
|
var router = new express.Router();
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:term")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.search(req.params.term.toLowerCase(), result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
26
router/settings.js
Normal file
26
router/settings.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
console.log("router/settings INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var database = require("../services/database");
|
||||||
|
var router = new express.Router();
|
||||||
|
|
||||||
|
var server = require("../server");
|
||||||
|
|
||||||
|
router.route("/").get((req, res) => {
|
||||||
|
database.system.allows((result) => {
|
||||||
|
console.log(result);
|
||||||
|
res.json(result)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/lists").get((req, res) => {
|
||||||
|
let lists = {
|
||||||
|
audio_quality: server.lists.audio_quality,
|
||||||
|
video_lang: server.lists.lang,
|
||||||
|
video_quality: server.lists.video_quality,
|
||||||
|
visibility: server.lists.visibility,
|
||||||
|
user_role: server.lists.user_role
|
||||||
|
}
|
||||||
|
res.json(lists);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
18
router/status.js
Normal file
18
router/status.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
console.log("router/status INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var router = new express.Router();
|
||||||
|
|
||||||
|
var server = require("../server");
|
||||||
|
var status = server.status;
|
||||||
|
|
||||||
|
var tag_excluder = require("../services/tag_excluder");
|
||||||
|
|
||||||
|
router.route("").get((req, res) => {
|
||||||
|
status.scann_buffer = tag_excluder.get_buffer_size();
|
||||||
|
status.parsing_music_data = tag_excluder.parsing_music_data();
|
||||||
|
status.parsing_video_data = tag_excluder.parsing_video_data();
|
||||||
|
status.parsing_cover = tag_excluder.parsing_cover();
|
||||||
|
res.json(status);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
97
router/system.js
Normal file
97
router/system.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
console.log("router/system INIT");
|
||||||
|
const express = require("express");
|
||||||
|
const router = new express.Router();
|
||||||
|
const database = require("../services/database");
|
||||||
|
const redis = require("../services/redis")
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const secret = server.secret;
|
||||||
|
const passport = server.passport;
|
||||||
|
const config = server.config
|
||||||
|
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("")
|
||||||
|
.get((req, res) => {
|
||||||
|
database.system.allows((result) => {
|
||||||
|
res.json(result).end();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.user.roles.indexOf("admin") > -1) {
|
||||||
|
database.system.setAllows(req.body, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/domains")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.user.roles.indexOf("admin") > -1) {
|
||||||
|
let domains = {
|
||||||
|
const: config.allowed_domains,
|
||||||
|
dynamic: []
|
||||||
|
}
|
||||||
|
database.system.domains((result) => {
|
||||||
|
result.forEach(domain => {
|
||||||
|
domains.dynamic.push(domain);
|
||||||
|
});
|
||||||
|
res.json(domains).end();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(403).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.user.roles.indexOf("admin") > -1) {
|
||||||
|
database.system.setDomains(req.body, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/setup")
|
||||||
|
.get((req, res) => {
|
||||||
|
database.users.collection(users => {
|
||||||
|
if (users && users.length > 0) {
|
||||||
|
res.status(204).end();
|
||||||
|
} else {
|
||||||
|
res.status(200).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post((req, res) => {
|
||||||
|
process.stdout.write("add admin user\n");
|
||||||
|
database.users.collection(users => {
|
||||||
|
if (users && users.length > 0) {
|
||||||
|
res.status(403).end();
|
||||||
|
} else {
|
||||||
|
let newUser = {
|
||||||
|
name: req.body.username,
|
||||||
|
password: req.body.password,
|
||||||
|
roles: ["admin"]
|
||||||
|
};
|
||||||
|
database.addUser(newUser, result => {
|
||||||
|
var token = jwt.sign(result, secret);
|
||||||
|
result.token = "JWT " + token;
|
||||||
|
result.history = [];
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/reset/redis")
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.user.roles.indexOf("admin") > -1) {
|
||||||
|
redis.flushAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
222
router/track.js
Normal file
222
router/track.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
console.log("router/track INIT");
|
||||||
|
const express = require("express");
|
||||||
|
const fs = require("fs");
|
||||||
|
const meta_data = require("music-metadata");
|
||||||
|
const database = require("../services/database");
|
||||||
|
const node_id3 = require("node-id3");
|
||||||
|
const Lame = require("node-lame").Lame;
|
||||||
|
const Fdkaac = require("node-fdkaac").Fdkaac;
|
||||||
|
const notifier = require("../services/notifier");
|
||||||
|
const server = require("../server");
|
||||||
|
const passport = server.passport;
|
||||||
|
const config = server.config;
|
||||||
|
const checkGuest = server.checkGuest;
|
||||||
|
|
||||||
|
const router = new express.Router();
|
||||||
|
const audio_cache_dir = config.cache_folder + "/audio_cache/";
|
||||||
|
const resizer = require("../services/cover/resizer");
|
||||||
|
|
||||||
|
if (!fs.existsSync(audio_cache_dir)) {
|
||||||
|
fs.mkdirSync(audio_cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.lists.audio_quality.forEach(bmp => {
|
||||||
|
let dir = audio_cache_dir + bmp;
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdir(dir, () => { });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.route("")
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.files) {
|
||||||
|
let track = JSON.parse(req.body.track);
|
||||||
|
let url_user = config.upload_folder + "/" + req.user._id;
|
||||||
|
let folder = Math.floor(Date.now() / 1000 / 60);
|
||||||
|
let url_folder = url_user + "/" + folder
|
||||||
|
let file = url_folder + "/" + req.files.file.name;
|
||||||
|
|
||||||
|
req.files.file.mv(file).then(() => {
|
||||||
|
meta_data
|
||||||
|
.parseFile(file, { duration: true })
|
||||||
|
.then(meta => {
|
||||||
|
let item = {
|
||||||
|
path: file,
|
||||||
|
artist: track.artist,
|
||||||
|
album: track.album,
|
||||||
|
title: track.title,
|
||||||
|
duration: meta.format.duration,
|
||||||
|
bitrate: meta.format.bitrate,
|
||||||
|
mime: req.files.file.mimetype,
|
||||||
|
year: track.year,
|
||||||
|
track: track.track,
|
||||||
|
disk: meta.common.disk,
|
||||||
|
genre: track.genre || meta.common.genre,
|
||||||
|
owner: req.user
|
||||||
|
};
|
||||||
|
if (meta.common.picture && meta.common.picture.length > 0) {
|
||||||
|
resizer.resize_image_for_album(meta.common.picture[0].data, (covers) => {
|
||||||
|
item.covers = covers;
|
||||||
|
notifier.emit("metadata_excluded", item);
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notifier.emit("metadata_excluded", item);
|
||||||
|
res.status(200).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
process.stdout.write(file);
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/most_listened")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.tracks.mostListened(filter, (result) => {
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:id/stream")
|
||||||
|
.get((req, res) => {
|
||||||
|
database.tracks.byId(req.params.id, result => {
|
||||||
|
process.stdout.write("audio stream: " + result.title);
|
||||||
|
if (!fs.existsSync(config.music_folder + result.path)) {
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
stream(req, res, config.music_folder + result.path, result.mime);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:id/stream/:rate")
|
||||||
|
.get((req, res) => {
|
||||||
|
if (!server.lists.audio_quality.includes(req.params.rate)) {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
let music = audio_cache_dir + req.params.rate + "/" + req.params.id + ".mp3";
|
||||||
|
if (fs.existsSync(music)) {
|
||||||
|
stream(req, res, music, "audio/mpeg");
|
||||||
|
} else {
|
||||||
|
convert(req.params.id, req.params.rate).then((file) => {
|
||||||
|
if (file) {
|
||||||
|
stream(req, res, file.path, file.mime);
|
||||||
|
} else {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/convert/:rate")
|
||||||
|
.put((req, res) => {
|
||||||
|
let music = audio_cache_dir + req.params.rate + "/" + req.params.id + ".mp3";
|
||||||
|
if (!fs.existsSync(music)) {
|
||||||
|
convert(req.params.id, req.params.rate);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
function stream(req, res, music, mime) {
|
||||||
|
process.stdout.write("audio stream: " + music + "\n");
|
||||||
|
let stat = fs.statSync(music);
|
||||||
|
|
||||||
|
if (req.headers.range) {
|
||||||
|
var range = req.headers.range;
|
||||||
|
var parts = range.replace(/bytes=/, "").split("-");
|
||||||
|
var partialstart = parts[0];
|
||||||
|
var partialend = parts[1];
|
||||||
|
|
||||||
|
var start = parseInt(partialstart, 10);
|
||||||
|
var end = partialend ? parseInt(partialend, 10) : stat.size - 1;
|
||||||
|
var chunksize = end - start + 1;
|
||||||
|
var readStream = fs.createReadStream(music, { start: start, end: end });
|
||||||
|
res.writeHead(206, {
|
||||||
|
"Content-Range": "bytes " + start + "-" + end + "/" + stat.size,
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
"Content-Length": chunksize,
|
||||||
|
"Content-Type": mime
|
||||||
|
});
|
||||||
|
readStream.pipe(res);
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, {
|
||||||
|
"Content-Length": stat.size,
|
||||||
|
"Content-Type": mime
|
||||||
|
});
|
||||||
|
fs.createReadStream(music).pipe(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert(id, rate) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
database.tracks.byId(id, result => {
|
||||||
|
let full_path = config.music_folder + result.path;
|
||||||
|
let cache_path = audio_cache_dir + rate + "/" + id + ".mp3";
|
||||||
|
if (!fs.existsSync(full_path)) {
|
||||||
|
full_path = config.upload_folder + result.path;
|
||||||
|
if (!fs.existsSync(full_path)) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.mime == "audio/mpeg" && result.bitrate / 1000 <= rate) {
|
||||||
|
process.stdout.write("stream non converted file: " + result.titel + "\n");
|
||||||
|
resolve({ path: full_path, mime: result.mime });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process.stdout.write("audio convert: " + result.title + " (" + result.mime + ")\n");
|
||||||
|
if (result.mime.includes("mp4")) {
|
||||||
|
process.stdout.write("audio decode: " + result.title + " (" + result.mime + ")\n");
|
||||||
|
const decoder = new Fdkaac({ output: "buffer" }).setFile(full_path);
|
||||||
|
decoder
|
||||||
|
.decode()
|
||||||
|
.then(() => {
|
||||||
|
const buffer = decoder.getBuffer();
|
||||||
|
const lame_encoder = new Lame({
|
||||||
|
output: cache_path,
|
||||||
|
bitrate: rate
|
||||||
|
}).setBuffer(buffer);
|
||||||
|
|
||||||
|
lame_encoder
|
||||||
|
.encode()
|
||||||
|
.then(() => {
|
||||||
|
node_id3.removeTags(cache_path);
|
||||||
|
resolve({ path: cache_path, mime: "audio/mpeg" });
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
process.stdout.write(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
process.stdout.write(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const lame_encoder = new Lame({ output: cache_path, bitrate: rate }).setFile(full_path);
|
||||||
|
lame_encoder
|
||||||
|
.encode()
|
||||||
|
.then(() => {
|
||||||
|
node_id3.removeTags(cache_path);
|
||||||
|
resolve({ path: cache_path, mime: "audio/mpeg" });
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
process.stdout.write(err);
|
||||||
|
process.stdout.write("stream original");
|
||||||
|
resolve({ path: full_path, mime: result.mime });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = router;
|
172
router/user.js
Normal file
172
router/user.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
console.log("router/user INIT");
|
||||||
|
var express = require("express");
|
||||||
|
var database = require("../services/database");
|
||||||
|
var bcrypt = require("bcryptjs");
|
||||||
|
var router = new express.Router();
|
||||||
|
|
||||||
|
var server = require("../server");
|
||||||
|
var passport = server.passport;
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.users.collection(result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("add user\n");
|
||||||
|
if (
|
||||||
|
req.user.roles.includes("admin") ||
|
||||||
|
req.user.roles.includes("moderator")
|
||||||
|
) {
|
||||||
|
let newUser = {
|
||||||
|
name: req.body.name,
|
||||||
|
password: req.body.password
|
||||||
|
};
|
||||||
|
database.addUser(newUser, () => {
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(401).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("delete user\n");
|
||||||
|
if (req.user.roles.includes("admin") || req.user._id == req.query.id) {
|
||||||
|
database.userById(req.query.id, (user) => {
|
||||||
|
if (user.roles.includes("admin")) {
|
||||||
|
res.status(403).end();
|
||||||
|
} else {
|
||||||
|
database.deleteUser(req.query.id, () => {
|
||||||
|
database.users.collection(result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res.status(401).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("update user\n");
|
||||||
|
if (req.user.roles.includes("admin")) {
|
||||||
|
if (req.body.newPassword) {
|
||||||
|
database.updateUserPassword(req.body.name, req.body.newPassword);
|
||||||
|
} else {
|
||||||
|
database.updateUserRole(req.body, () => {
|
||||||
|
res.status(202).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
res.status(401).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:name/exists")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (
|
||||||
|
req.user.roles.includes("admin") ||
|
||||||
|
req.user.roles.includes("moderator")
|
||||||
|
) {
|
||||||
|
database.userByName(req.params.name, user => {
|
||||||
|
res.json({ exists: user != null }).end();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(401).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/update")
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).end();
|
||||||
|
}
|
||||||
|
if (!req.body.oldPassword) {
|
||||||
|
database.updateUserConfig(req.user, req.body);
|
||||||
|
process.stdout.write("config changed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body.oldPassword && req.user.password) {
|
||||||
|
bcrypt.compare(req.body.oldPassword, req.user.password, function (err, isMatch) {
|
||||||
|
if (err) throw err;
|
||||||
|
if (isMatch) {
|
||||||
|
database.updateUserPassword(req.user.name, req.body.newPassword);
|
||||||
|
process.stdout.write("password changed\n");
|
||||||
|
res.status(202);
|
||||||
|
} else {
|
||||||
|
process.stdout.write("no match\n");
|
||||||
|
res.status(422);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/favourites")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
process.stdout.write("router/user GET favourites\n");
|
||||||
|
database.user.favourites(id, result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let item = req.body;
|
||||||
|
item.userId = req.user._id;
|
||||||
|
process.stdout.write("router/user POST favourites\n");
|
||||||
|
database.users.insertFavourite(item, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let item = {};
|
||||||
|
item.itemId = req.query.itemId;
|
||||||
|
item.userId = req.user._id;
|
||||||
|
process.stdout.write("router/user DELETE favourites " + req.query.itemId + "\n");
|
||||||
|
database.users.deleteFavourite(item, () => {
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/history")
|
||||||
|
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.historyList(req.user._id, result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
let item = req.body;
|
||||||
|
item.userId = req.user._id;
|
||||||
|
database.updateHistory(item, () => {
|
||||||
|
database.historyList(req.user._id, result => {
|
||||||
|
res.json(result).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.delete(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
database.clearHistory(req.user._id, () => {
|
||||||
|
process.stdout.write("history cleared for '" + req.user.name + "'\n");
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/settings")
|
||||||
|
.put(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
req.user.player.repeat = req.body.repeat;
|
||||||
|
req.user.player.shuffle = req.body.shuffle;
|
||||||
|
database.updateUserSettings(req.user, () => {
|
||||||
|
res.status(202).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
209
router/video.js
Normal file
209
router/video.js
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
console.log("router/video INIT");
|
||||||
|
const express = require("express");
|
||||||
|
const fs = require("fs");
|
||||||
|
const ffmpeg = require("fluent-ffmpeg");
|
||||||
|
const database = require("../services/database");
|
||||||
|
const notifier = require("../services/notifier");
|
||||||
|
const server = require("../server");
|
||||||
|
const passport = server.passport;
|
||||||
|
const config = server.config;
|
||||||
|
|
||||||
|
const router = new express.Router();
|
||||||
|
|
||||||
|
const video_cache_dir = config.cache_folder + "/video_cache/";
|
||||||
|
|
||||||
|
const checkGuest = server.checkGuest;
|
||||||
|
const resize_image_for_box = require("../services/cover/resizer").resize_image_for_box
|
||||||
|
|
||||||
|
if (!fs.existsSync(video_cache_dir)) {
|
||||||
|
fs.mkdirSync(video_cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.route("")
|
||||||
|
.post(passport.authenticate("jwt", { session: false }), (req, res) => {
|
||||||
|
if (req.files) {
|
||||||
|
let file = undefined;
|
||||||
|
let cover = undefined;
|
||||||
|
if (Array.isArray(req.files.file)) {
|
||||||
|
file = req.files.file[0];
|
||||||
|
cover = req.files.file[1];
|
||||||
|
} else {
|
||||||
|
file = req.files.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
let box = JSON.parse(req.body.box);
|
||||||
|
let url_user = config.upload_folder + "/" + req.user._id;
|
||||||
|
let folder = Math.floor(Date.now() / 1000 / 60);
|
||||||
|
let url_folder = url_user + "/" + folder
|
||||||
|
let file_path = url_folder + "/" + file.name;
|
||||||
|
let item = {
|
||||||
|
path: file_path.replace(config.upload_folder, ""),
|
||||||
|
box: box.title,
|
||||||
|
box_path: url_folder,
|
||||||
|
title: req.body.title,
|
||||||
|
mime: file.mimetype,
|
||||||
|
year: box.year,
|
||||||
|
owner: req.user
|
||||||
|
};
|
||||||
|
|
||||||
|
//SAVE VIDEO FILE
|
||||||
|
function saveVideoFile() {
|
||||||
|
file.mv(file_path).then(() => {
|
||||||
|
notifier.emit("pathdata_excluded", item);
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (cover) {
|
||||||
|
resize_image_for_box(cover.data, (result) => {
|
||||||
|
item.cover32 = result.cover32;
|
||||||
|
item.cover64 = result.cover64;
|
||||||
|
item.cover128 = result.cover128;
|
||||||
|
item.cover256 = result.cover256;
|
||||||
|
saveVideoFile();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
saveVideoFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/most_viewed")
|
||||||
|
.get(checkGuest, (req, res) => {
|
||||||
|
let filter = undefined;
|
||||||
|
if (req.user._id == -1) {
|
||||||
|
filter = ['global'];
|
||||||
|
}
|
||||||
|
database.videos.mostViewed(filter, (result) => {
|
||||||
|
res.json(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/stream")
|
||||||
|
.get((req, res) => {
|
||||||
|
database.videos.byId(req.params.id, result => {
|
||||||
|
process.stdout.write("video stream: " + result.title + "\n");
|
||||||
|
let full_path = config.video_folder + result.path;
|
||||||
|
if (!fs.existsSync(full_path)) {
|
||||||
|
full_path = config.upload_folder + result.path;
|
||||||
|
if (!fs.existsSync(full_path)) {
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream(req, res, full_path, result.mime);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/stream/:rate/:audioIndex")
|
||||||
|
.get((req, res) => {
|
||||||
|
if (!server.lists.video_quality.includes(req.params.rate)) {
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
let video = getFileName(req);
|
||||||
|
if (fs.existsSync(video)) {
|
||||||
|
stream(req, res, video, "video/mpeg");
|
||||||
|
} else {
|
||||||
|
convert(req, res, video, () => {
|
||||||
|
stream(req, res, video, "video/mp4");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.route("/:id/convert/:rate/:audioIndex")
|
||||||
|
.get((req, res) => {
|
||||||
|
if (!server.lists.video_quality.includes(req.params.rate)) {
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
let video = getFileName(req);
|
||||||
|
if (!fs.existsSync(video)) {
|
||||||
|
convert(req, res, video);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
function convert(req, res, video, callback = null) {
|
||||||
|
database.videos.byId(req.params.id, result => {
|
||||||
|
process.stdout.write("video stream: " + result.title + "\n");
|
||||||
|
let full_path = config.video_folder + result.path;
|
||||||
|
if (!fs.existsSync(full_path)) {
|
||||||
|
full_path = config.upload_folder + result.path;
|
||||||
|
if (!fs.existsSync(full_path)) {
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ffmpeg(full_path)
|
||||||
|
.audioBitrate(128)
|
||||||
|
.audioChannels(2)
|
||||||
|
.videoCodec("libvpx")
|
||||||
|
.size("?x" + req.params.rate)
|
||||||
|
.videoBitrate(req.params.rate * 2)
|
||||||
|
.outputOptions([
|
||||||
|
"-cpu-used " + server.config.cpu_cores,
|
||||||
|
"-map 0:v:0",
|
||||||
|
"-map 0:a:" + req.params.audioIndex
|
||||||
|
])
|
||||||
|
.output(video)
|
||||||
|
.on("start", commandLine => {
|
||||||
|
process.stdout.write(
|
||||||
|
"Spawned Ffmpeg with command: " + commandLine + "\n"
|
||||||
|
);
|
||||||
|
if (callback) {
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
if (fs.existsSync(video) && fs.statSync(video).size > 3000000) {
|
||||||
|
clearInterval(interval);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", e => {
|
||||||
|
process.stdout.write(e);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stream(req, res, video, mime) {
|
||||||
|
process.stdout.write("video stream: " + video + "\n");
|
||||||
|
let stat = fs.statSync(video);
|
||||||
|
|
||||||
|
if (req.headers.range) {
|
||||||
|
var range = req.headers.range;
|
||||||
|
var parts = range.replace(/bytes=/, "").split("-");
|
||||||
|
var partialstart = parts[0];
|
||||||
|
var partialend = parts[1];
|
||||||
|
|
||||||
|
var start = parseInt(partialstart, 10);
|
||||||
|
var end = partialend ? parseInt(partialend, 10) : stat.size - 1;
|
||||||
|
var chunksize = end - start + 1;
|
||||||
|
var readStream = fs.createReadStream(video, { start: start, end: end });
|
||||||
|
res.writeHead(206, {
|
||||||
|
"Content-Range": "bytes " + start + "-" + end + "/" + stat.size,
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
"Content-Length": chunksize,
|
||||||
|
"Content-Type": mime
|
||||||
|
});
|
||||||
|
readStream.pipe(res);
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, {
|
||||||
|
"Content-Length": stat.size,
|
||||||
|
"Content-Type": mime
|
||||||
|
});
|
||||||
|
fs.createReadStream(video).pipe(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileName(req) {
|
||||||
|
return (
|
||||||
|
video_cache_dir +
|
||||||
|
req.params.id +
|
||||||
|
"-" +
|
||||||
|
req.params.rate +
|
||||||
|
"-" +
|
||||||
|
req.params.audioIndex +
|
||||||
|
".webm"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = router;
|
49
router/well-known.js
Normal file
49
router/well-known.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = new express.Router();
|
||||||
|
|
||||||
|
const server = require("../server");
|
||||||
|
const config = server.config;
|
||||||
|
const database = require("../services/database");
|
||||||
|
|
||||||
|
|
||||||
|
router.route("/webfinger")
|
||||||
|
.get((req, res) => {
|
||||||
|
if (!req.query.resource) {
|
||||||
|
return res.status(400).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource = req.query.resource.toLowerCase();
|
||||||
|
let name = resource.replace("acct:", "").replace("@" + config.domain, "").toLowerCase();
|
||||||
|
|
||||||
|
if (name.length == 0) {
|
||||||
|
return res.status(400).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
database.userByName(name, (user) => {
|
||||||
|
if (user == undefined) {
|
||||||
|
return res.status(404).end();
|
||||||
|
}
|
||||||
|
let response = {
|
||||||
|
"subject": resource,
|
||||||
|
"aliases": [
|
||||||
|
"https://" + config.domain + "/#/users/" + user.name
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "http://webfinger.net/rel/profile-page",
|
||||||
|
"type": "text/html",
|
||||||
|
"href": "https://" + config.domain + "/#/users/" + user.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "self",
|
||||||
|
"type": "application/activity+json",
|
||||||
|
"href": "https://" + config.domain + "/users/" + user.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
res.json(response).end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
153
server.js
Normal file
153
server.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
process.stdout.write("server INIT\n");
|
||||||
|
const express = require("express");
|
||||||
|
const fileUpload = require('express-fileupload');
|
||||||
|
const cors = require("cors");
|
||||||
|
const fs = require("fs");
|
||||||
|
const si = require("systeminformation");
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
const passport = require("passport");
|
||||||
|
const JwtStrategy = require("passport-jwt").Strategy;
|
||||||
|
const ExtractJwt = require("passport-jwt").ExtractJwt;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
JWT
|
||||||
|
*/
|
||||||
|
const secret = "secret";
|
||||||
|
|
||||||
|
const jwt_opts = {
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme("JWT"),
|
||||||
|
secretOrKey: secret,
|
||||||
|
ignoreExpiration: true
|
||||||
|
};
|
||||||
|
|
||||||
|
passport.use(
|
||||||
|
new JwtStrategy(jwt_opts, (user, done) => {
|
||||||
|
database.userByName(user.name, res => {
|
||||||
|
if (res && user.name == res.name && user.password == res.password) {
|
||||||
|
delete res.password;
|
||||||
|
return done(null, res);
|
||||||
|
} else {
|
||||||
|
return done(null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// CONFIG AND STATUS OBJECTS
|
||||||
|
process.stdout.write("server LOAD config\n");
|
||||||
|
const config = JSON.parse(fs.readFileSync("./config.json", "utf8"));
|
||||||
|
if (!fs.existsSync(config.cache_folder)) {
|
||||||
|
fs.mkdirSync(config.cache_folder);
|
||||||
|
}
|
||||||
|
if (process.env.DOMAIN) {
|
||||||
|
config.domain = process.env.DOMAIN;
|
||||||
|
}
|
||||||
|
config.base_path = __dirname;
|
||||||
|
const status = {
|
||||||
|
scanning_music: false,
|
||||||
|
scanning_video: false
|
||||||
|
};
|
||||||
|
|
||||||
|
process.stdout.write("server DEF arrays\n");
|
||||||
|
const lists = {
|
||||||
|
audio_quality: ["64", "96", "128", "192", "256", "320"],
|
||||||
|
video_quality: ["480", "720", "1080"],
|
||||||
|
user_role: ["admin", "moderator", "user"],
|
||||||
|
lang: ["ENG", "GER", "RUS"],
|
||||||
|
visibility: ["global", "instance", "owner", "hidden"]
|
||||||
|
};
|
||||||
|
|
||||||
|
process.stdout.write("server READ cpu cores\n");
|
||||||
|
si.cpu(function (data) {
|
||||||
|
config.cpu_cores = data.cores;
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.config = config;
|
||||||
|
exports.status = status;
|
||||||
|
exports.lists = lists;
|
||||||
|
exports.passport = passport;
|
||||||
|
exports.secret = secret;
|
||||||
|
exports.checkGuest = function checkGuest(req, res, next) {
|
||||||
|
passport.authenticate('jwt', { session: false, }, async (error, user) => {
|
||||||
|
if (user) {
|
||||||
|
delete user.password;
|
||||||
|
req.user = user;
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
database.system.allows((allows) => {
|
||||||
|
if (allows.guests) {
|
||||||
|
req.user = { _id: -1, name: "Guest" }
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return res.status(403).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(req, res, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
require("./services/cleanup_utiles");
|
||||||
|
const database = require("./services/database");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const server = require("http").createServer(app);
|
||||||
|
|
||||||
|
app.use(bodyParser.json({ limit: "500mb" }));
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
app.use(fileUpload({
|
||||||
|
createParentPath: true
|
||||||
|
}));
|
||||||
|
const corsOptions = {
|
||||||
|
origin: (origin, callback) => {
|
||||||
|
if (!origin || config.allowed_domains.indexOf(origin.replace(/:\d*$/g, "")) !== -1) {
|
||||||
|
callback(null, true);
|
||||||
|
} else {
|
||||||
|
database.system.domains((domains) => {
|
||||||
|
if (domains.indexOf(origin.replace(/:\d*$/g, "")) !== -1) {
|
||||||
|
callback(null, true);
|
||||||
|
} else {
|
||||||
|
callback("Origin not allowed: " + origin);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: "GET,POST,PUT,DELETE",
|
||||||
|
allowedHeaders: "*",
|
||||||
|
credentials: true
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
|
// ROUTERS
|
||||||
|
process.stdout.write("server LOAD routers\n");
|
||||||
|
const router = require("./router");
|
||||||
|
process.stdout.write("server USE routers\n");
|
||||||
|
app.use("/api/albums", router.albumRouter);
|
||||||
|
app.use("/api/artists", router.artistRouter);
|
||||||
|
app.use("/api/boxes", router.boxRouter);
|
||||||
|
app.use("/api/info", router.infoRouter);
|
||||||
|
app.use("/api/radios", router.radioRouter);
|
||||||
|
app.use("/api/scan", router.scanRouter);
|
||||||
|
app.use("/api/search", router.searchRouter);
|
||||||
|
app.use("/api/settings", router.settingsRouter);
|
||||||
|
app.use("/api/status", router.statusRouter);
|
||||||
|
app.use("/api/system", router.systemRouter);
|
||||||
|
app.use("/api/tracks", router.trackRouter);
|
||||||
|
app.use("/api/user", router.loginRouter);
|
||||||
|
app.use("/api/user", router.userRouter);
|
||||||
|
app.use("/api/videos", router.videoRouter);
|
||||||
|
// app.use("/well-known", router.wellknownRouter);
|
||||||
|
// app.use("/users", router.activitypubRouter);
|
||||||
|
|
||||||
|
process.stdout.write("server START listening on Port " + config.port + "\n");
|
||||||
|
server.listen(config.port, function () {
|
||||||
|
process.stdout.write("Server startet on Port " + config.port + "...\n");
|
||||||
|
process.stdout.write("Music Folder: " + config.music_folder + "\n");
|
||||||
|
process.stdout.write("Video Folder: " + config.video_folder + "\n");
|
||||||
|
process.stdout.write("Cache Folder: " + config.cache_folder + "\n");
|
||||||
|
process.stdout.write("Upload Folder: " + config.upload_folder + "\n");
|
||||||
|
process.stdout.write("Domain: " + config.domain + "\n");
|
||||||
|
});
|
58
services/cleanup_utiles.js
Normal file
58
services/cleanup_utiles.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
var fs = require("fs");
|
||||||
|
var notifier = require("./notifier");
|
||||||
|
|
||||||
|
var server = require("../server");
|
||||||
|
var config = server.config;
|
||||||
|
|
||||||
|
var album_cover_dir = config.cache_folder + "/album_covers/";
|
||||||
|
var album_cover_32 = album_cover_dir + "32/";
|
||||||
|
var album_cover_96 = album_cover_dir + "96/";
|
||||||
|
var album_cover_64 = album_cover_dir + "64/";
|
||||||
|
var album_cover_128 = album_cover_dir + "128/";
|
||||||
|
var album_cover_256 = album_cover_dir + "256/";
|
||||||
|
|
||||||
|
var artist_cover_dir = config.cache_folder + "/artist_covers/";
|
||||||
|
var artist_cover_128 = artist_cover_dir + "128/";
|
||||||
|
var artist_cover_256 = artist_cover_dir + "256/";
|
||||||
|
var artist_cover_512 = artist_cover_dir + "512/";
|
||||||
|
|
||||||
|
var box_cover_dir = config.cache_folder + "/box_covers/";
|
||||||
|
var box_cover_32 = box_cover_dir + "32/";
|
||||||
|
var box_cover_64 = box_cover_dir + "64/";
|
||||||
|
var box_cover_128 = box_cover_dir + "128/";
|
||||||
|
var box_cover_256 = box_cover_dir + "256/";
|
||||||
|
|
||||||
|
var video_cover_dir = config.cache_folder + "/video_covers/";
|
||||||
|
var video_cache_dir = config.cache_folder + "/realtime/";
|
||||||
|
|
||||||
|
var audio_cache_dir = config.cache_folder + "/audio_cache/";
|
||||||
|
var audio_cache_128 = audio_cache_dir + "128/";
|
||||||
|
|
||||||
|
notifier.on("track_deleted", id => {
|
||||||
|
fs.unlink(audio_cache_128 + id + ".mp3", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("artist_deleted", id => {
|
||||||
|
fs.unlink(artist_cover_128 + id, () => {});
|
||||||
|
fs.unlink(artist_cover_256 + id, () => {});
|
||||||
|
fs.unlink(artist_cover_512 + id, () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("album_deleted", id => {
|
||||||
|
fs.unlink(album_cover_32 + id, () => {});
|
||||||
|
fs.unlink(album_cover_64 + id, () => {});
|
||||||
|
fs.unlink(album_cover_96 + id, () => {});
|
||||||
|
fs.unlink(album_cover_128 + id, () => {});
|
||||||
|
fs.unlink(album_cover_256 + id, () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("box_deleted", id => {
|
||||||
|
fs.unlink(box_cover_32 + id, () => {});
|
||||||
|
fs.unlink(box_cover_64 + id, () => {});
|
||||||
|
fs.unlink(box_cover_128 + id, () => {});
|
||||||
|
fs.unlink(box_cover_256 + id, () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("video_deleted", id => {
|
||||||
|
fs.unlink(video_cover_dir + "/" + id + ".png", () => {});
|
||||||
|
});
|
96
services/cover/resizer.js
Normal file
96
services/cover/resizer.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
const sharp = require("sharp");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
RADIO COVER
|
||||||
|
*/
|
||||||
|
const radio_cover_sizes = [
|
||||||
|
{ width: 128, height: 128, fit: "cover" },
|
||||||
|
{ width: 64, height: 64, fit: "cover" },
|
||||||
|
{ width: 32, height: 32, fit: "cover" }
|
||||||
|
];
|
||||||
|
exports.resize_image_for_radio = function (image, callback) {
|
||||||
|
resizer(image, radio_cover_sizes, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
ALBUM COVER
|
||||||
|
*/
|
||||||
|
const album_cover_sizes = [
|
||||||
|
{ width: 256, height: 256, fit: "cover" },
|
||||||
|
{ width: 128, height: 128, fit: "cover" },
|
||||||
|
{ width: 64, height: 64, fit: "cover" },
|
||||||
|
{ width: 32, height: 32, fit: "cover" }
|
||||||
|
];
|
||||||
|
exports.resize_image_for_album = function (image, callback) {
|
||||||
|
if (typeof image === 'string') {
|
||||||
|
let data = fs.readFileSync(image);
|
||||||
|
resizer(data, album_cover_sizes, callback);
|
||||||
|
} else {
|
||||||
|
resizer(image, album_cover_sizes, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
ARTIST COVER
|
||||||
|
*/
|
||||||
|
const artist_cover_sizes = [
|
||||||
|
{ width: 1024, height: 512, fit: "cover" },
|
||||||
|
{ width: 512, height: 256, fit: "cover" },
|
||||||
|
{ width: 256, height: 128, fit: "cover" },
|
||||||
|
{ width: 128, height: 64, fit: "cover" },
|
||||||
|
{ width: 64, height: 32, fit: "cover" }
|
||||||
|
];
|
||||||
|
exports.resize_image_for_artist = function (image, callback) {
|
||||||
|
if (typeof image === 'string') {
|
||||||
|
console.log("resizing: " + image);
|
||||||
|
let data = fs.readFileSync(image);
|
||||||
|
resizer(data, artist_cover_sizes, callback);
|
||||||
|
} else {
|
||||||
|
resizer(image, artist_cover_sizes, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
BOX COVER
|
||||||
|
*/
|
||||||
|
const box_cover_sizes = [
|
||||||
|
{ width: 256, height: 362, fit: "cover" },
|
||||||
|
{ width: 128, height: 181, fit: "cover" },
|
||||||
|
{ width: 64, height: 90, fit: "cover" },
|
||||||
|
{ width: 32, height: 45, fit: "cover" }
|
||||||
|
];
|
||||||
|
exports.resize_image_for_box = function (image, callback) {
|
||||||
|
if (typeof image === 'string') {
|
||||||
|
let data = fs.readFileSync(image);
|
||||||
|
resizer(data, box_cover_sizes, callback);
|
||||||
|
} else {
|
||||||
|
resizer(image, box_cover_sizes, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function resizer(image, array, callback) {
|
||||||
|
let result = {}
|
||||||
|
let runner = (image, index) => {
|
||||||
|
if (index < array.length) {
|
||||||
|
let size = array[index];
|
||||||
|
sharp(image)
|
||||||
|
.resize(size)
|
||||||
|
.png()
|
||||||
|
.toBuffer()
|
||||||
|
.then(data => {
|
||||||
|
result["cover" + size.width] = "data:image/png;base64, " + Buffer.from(data).toString("base64");
|
||||||
|
runner(data, ++index);
|
||||||
|
}).catch(err=>{
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (callback) {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runner(image, 0);
|
||||||
|
}
|
269
services/cover_excluder.js
Normal file
269
services/cover_excluder.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
console.log("service/cover_excluder INIT");
|
||||||
|
console.log("service/cover_excluder REQUIRE fs");
|
||||||
|
const fs = require("fs");
|
||||||
|
console.log("service/cover_excluder REQUIRE path");
|
||||||
|
const path = require("path");
|
||||||
|
console.log("service/cover_excluder REQUIRE request");
|
||||||
|
const request = require("request");
|
||||||
|
|
||||||
|
console.log("service/cover_excluder INCLUDE notifier");
|
||||||
|
const notifier = require("./notifier");
|
||||||
|
console.log("service/cover_excluder INCLUDE database");
|
||||||
|
const database = require("./database");
|
||||||
|
console.log("service/cover_excluder INCLUDE music_brainz");
|
||||||
|
const music_brainz = require("./music_brainz");
|
||||||
|
console.log("service/cover_excluder INCLUDE the_movie_db");
|
||||||
|
const the_movie_db = require("./the_movie_db");
|
||||||
|
console.log("service/cover_excluder INCLUDE resizer")
|
||||||
|
const resizer = require("../services/cover/resizer")
|
||||||
|
|
||||||
|
|
||||||
|
console.log("service/cover_excluder LOAD server settings")
|
||||||
|
const server = require("../server");
|
||||||
|
const status = server.status;
|
||||||
|
const config = server.config;
|
||||||
|
const cache_folder = config.cache_folder + "/";
|
||||||
|
|
||||||
|
|
||||||
|
console.log("service/cover_excluder SET notifiers")
|
||||||
|
notifier.on("exclude_metadata_finished", () => {
|
||||||
|
process.stdout.write("music scann finished…\n");
|
||||||
|
checkMissingAlbumCovers();
|
||||||
|
checkMissingArtistCovers();
|
||||||
|
status.scanning_music = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("exclude_pathdata_finished", () => {
|
||||||
|
process.stdout.write("video scann finished…\n");
|
||||||
|
checkMissingBoxCovers();
|
||||||
|
status.scanning_video = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("metadata_picture_excluded", album => {
|
||||||
|
resizer.resize_image_for_album(album.picture[0].data, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("cover from metadata…\n");
|
||||||
|
database.albums.updateCovers(album, covers);
|
||||||
|
} else {
|
||||||
|
process.stdout.write("no cover from metadata…\n");
|
||||||
|
music_brainz.find_album_cover(album);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("no_metadata_picture_excluded", album => {
|
||||||
|
music_brainz.find_album_cover(album);
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("found_music_brainz_album_cover", result => {
|
||||||
|
process.stdout.write("MB album result: " + result.mb + "\n");
|
||||||
|
|
||||||
|
let album_id = result.album._id.toString();
|
||||||
|
let tmp_file = cache_folder + album_id;
|
||||||
|
|
||||||
|
let writer = fs.createWriteStream(tmp_file);
|
||||||
|
|
||||||
|
writer.on("finish", () => {
|
||||||
|
resizer.resize_image_for_album(tmp_file, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("album cover from musicbrainz…\n");
|
||||||
|
database.albums.updateCovers(result.album, covers);
|
||||||
|
}
|
||||||
|
fs.unlinkSync(tmp_file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request(result.mb).pipe(writer);
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("found_music_brainz_artist_cover", result => {
|
||||||
|
process.stdout.write("MB artist result: " + result.mb + "\n");
|
||||||
|
|
||||||
|
let artist_id = result.artist._id.toString();
|
||||||
|
let tmp_file = cache_folder + artist_id;
|
||||||
|
let writer = fs.createWriteStream(tmp_file);
|
||||||
|
|
||||||
|
writer.on("finish", () => {
|
||||||
|
resizer.resize_image_for_artist(tmp_file, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("artist cover from musicbrainz…\n");
|
||||||
|
database.artists.updateCovers(result.artist, covers);
|
||||||
|
}
|
||||||
|
fs.unlinkSync(tmp_file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request(result.mb).pipe(writer);
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("found_movie_db_box_cover", box => {
|
||||||
|
process.stdout.write("TMVD box result: " + box.tmdb + "\n");
|
||||||
|
|
||||||
|
let box_id = box._id.toString();
|
||||||
|
let tmp_file = cache_folder + box_id;
|
||||||
|
let writer = fs.createWriteStream(tmp_file);
|
||||||
|
|
||||||
|
writer.on("finish", () => {
|
||||||
|
resizer.resize_image_for_box(tmp_file, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("box covers from tmvd…\n");
|
||||||
|
database.boxes.updateCovers(box, covers);
|
||||||
|
}
|
||||||
|
fs.unlinkSync(tmp_file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request(box.tmdb).pipe(writer);
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkMissingAlbumCovers() {
|
||||||
|
process.stdout.write("started cover excluding for albums…\n");
|
||||||
|
database.albums.collection(-1, null, albums => {
|
||||||
|
albums.forEach(album => {
|
||||||
|
database.albums.tracks(album._id, album_tracks => {
|
||||||
|
if (!album_tracks || !album_tracks.tracks || album.covers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
album.tracks = album_tracks.tracks;
|
||||||
|
let cover_found = false;
|
||||||
|
album.tracks.forEach(track => {
|
||||||
|
if (cover_found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = config.music_folder + track.path;
|
||||||
|
let directory = path.dirname(file_path);
|
||||||
|
|
||||||
|
config.album_cover_files.forEach(file => {
|
||||||
|
let img = directory + "/" + file;
|
||||||
|
if (fs.existsSync(img)) {
|
||||||
|
cover_found = true;
|
||||||
|
resizer.resize_image_for_album(img, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("covers from file…\n");
|
||||||
|
database.albums.updateCovers(album, covers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!cover_found) {
|
||||||
|
export_from_id3(album);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function export_from_id3(album) {
|
||||||
|
album.tracks.forEach(track => {
|
||||||
|
track.parent = album;
|
||||||
|
});
|
||||||
|
notifier.emit("album_cover_request", album.tracks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMissingArtistCovers() {
|
||||||
|
process.stdout.write("started cover excluding for artists…\n");
|
||||||
|
database.artists.collection(-1, undefined, artists => {
|
||||||
|
artists.forEach(artist => {
|
||||||
|
database.artists.tracks(artist._id.toString(), true, artist_full => {
|
||||||
|
if (!artist_full || !artist_full.albums || artist.covers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cover_found = false;
|
||||||
|
artist_full.albums.forEach(album => {
|
||||||
|
album.tracks.forEach(track => {
|
||||||
|
if (cover_found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config.artist_cover_files.forEach(file => {
|
||||||
|
if (cover_found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let file_path = config.music_folder + track.path;
|
||||||
|
let directory = path.dirname(file_path);
|
||||||
|
let img = directory + "/" + file;
|
||||||
|
if (fs.existsSync(img)) {
|
||||||
|
cover_found = true;
|
||||||
|
resizer.resize_image_for_artist(img, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("artist covers from file…\n");
|
||||||
|
database.artists.updateCovers(artist, covers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
directory = path.dirname(directory);
|
||||||
|
img = directory + "/" + file;
|
||||||
|
if (fs.existsSync(img)) {
|
||||||
|
cover_found = true;
|
||||||
|
resizer.resize_image_for_artist(img, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("artist covers from file…\n");
|
||||||
|
database.artists.updateCovers(artist, covers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
directory = path.dirname(directory);
|
||||||
|
img = directory + "/" + file;
|
||||||
|
if (fs.existsSync(img)) {
|
||||||
|
cover_found = true;
|
||||||
|
resizer.resize_image_for_artist(img, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("artist covers from file…\n");
|
||||||
|
database.artists.updateCovers(artist, covers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!cover_found) {
|
||||||
|
music_brainz.find_artist_cover(artist_full);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMissingBoxCovers() {
|
||||||
|
process.stdout.write("started cover excluding for boxes…\n");
|
||||||
|
database.boxes.collection(-1, undefined, boxes => {
|
||||||
|
boxes.forEach(box => {
|
||||||
|
database.boxes.videos(box._id.toString(), true, box_full => {
|
||||||
|
if (box_full.covers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cover_found = false;
|
||||||
|
let directory = config.video_folder + box_full.path;
|
||||||
|
config.box_cover_files.forEach(file => {
|
||||||
|
if (cover_found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let img = directory + "/" + file;
|
||||||
|
if (fs.existsSync(img)) {
|
||||||
|
cover_found = true;
|
||||||
|
resizer.resize_image_for_box(img, covers => {
|
||||||
|
if (Object.keys(covers).length > 0) {
|
||||||
|
process.stdout.write("box covers from file…\n");
|
||||||
|
database.boxes.updateCovers(box_full, covers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!cover_found) {
|
||||||
|
the_movie_db.find_box_cover(box);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
25
services/database/CONNECTOR.js
Normal file
25
services/database/CONNECTOR.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const { MongoClient } = require('mongodb');
|
||||||
|
const server = require("../../server");
|
||||||
|
const config = server.config;
|
||||||
|
|
||||||
|
var dbo;
|
||||||
|
const url = "mongodb://" + config.database.host + ":" + config.database.port + "/";
|
||||||
|
const database = config.database.name;
|
||||||
|
|
||||||
|
exports.connect = async function () {
|
||||||
|
if (dbo) {
|
||||||
|
console.log("DB CONNECTED")
|
||||||
|
return dbo;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
console.log("DB CONNECTING:" + config.database.host + ":" + config.database.port)
|
||||||
|
const client = await MongoClient.connect(url);
|
||||||
|
dbo = client.db(database);
|
||||||
|
return dbo;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`MongoDB connection failed with > ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
302
services/database/albums.js
Normal file
302
services/database/albums.js
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
const redis = require("../redis")
|
||||||
|
|
||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
dbo.collection("albums").createIndex({ artist_name: 1, year: 1, title: 1 });
|
||||||
|
// TEMPORARY
|
||||||
|
dbo.collection("albums").updateMany({}, { $unset: { cover32: 1, cover64: 1, cover128: 1, cover256: 1, cover512: 1 } });
|
||||||
|
updateArtistName();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateArtistName() {
|
||||||
|
dbo.collection("albums")
|
||||||
|
.find({ artist_name: undefined })
|
||||||
|
.toArray((err, result) => {
|
||||||
|
result.forEach(item => {
|
||||||
|
dbo.collection("artists")
|
||||||
|
.findOne({ _id: item.artist_id })
|
||||||
|
.then(artist => {
|
||||||
|
if (artist) {
|
||||||
|
dbo.collection("albums")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: item._id },
|
||||||
|
{ $set: { artist_id: artist._id, artist_name: artist.name } },
|
||||||
|
{ upsert: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.collection = function (page, filter, callback) {
|
||||||
|
process.stdout.write("services/db_manager ALBUMS Collection: " + page + "\n");
|
||||||
|
let redis_key = "albumsCollection_" + (filter || '') + '_' + page;
|
||||||
|
|
||||||
|
redis.get(redis_key, (value) => {
|
||||||
|
if (value) {
|
||||||
|
process.stdout.write("services/db_manager ALBUMS Collection REDIS: " + page + "\n");
|
||||||
|
callback(value);
|
||||||
|
} else {
|
||||||
|
let aggregate = [
|
||||||
|
{ $project: { "covers.cover256": false, "covers.cover64": false, "covers.cover32": false } },
|
||||||
|
{ $match: { visibility: { $not: { $eq: 'hidden' } } } },
|
||||||
|
{ $sort: { artist_name: 1, year: 1, title: 1 } },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push({
|
||||||
|
$match: { visibility: { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page > -1) {
|
||||||
|
let pageSize = 12;
|
||||||
|
let skip = (page - 1) * pageSize;
|
||||||
|
aggregate.push(
|
||||||
|
{ $skip: skip },
|
||||||
|
{ $limit: pageSize });
|
||||||
|
}
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.aggregate(aggregate, { allowDiskUse: true })
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(album => {
|
||||||
|
album.type = "album";
|
||||||
|
album.tracks = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stdout.write("services/db_manager ALBUMS Collection MONGO: " + page + "\n");
|
||||||
|
callback(result);
|
||||||
|
redis.set(redis_key, result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.favourites = function (id, callback) {
|
||||||
|
dbo.collection("favourites")
|
||||||
|
.find({ userId: id, type: "album" })
|
||||||
|
.toArray((err, favourites) => {
|
||||||
|
if (err) throw err;
|
||||||
|
let aggregate = [
|
||||||
|
{ $match: { _id: { $in: favourites.map(m => m.itemId) } } },
|
||||||
|
{ $project: { "covers.cover256": false, "covers.cover64": false, "covers.cover32": false } },
|
||||||
|
]
|
||||||
|
dbo.collection("albums")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
result.forEach(album => {
|
||||||
|
album.type = "album";
|
||||||
|
album.tracks = [];
|
||||||
|
});
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.newest = function (count, filter, callback) {
|
||||||
|
let aggregate = [
|
||||||
|
{ $project: { "covers.cover256": false, "covers.cover128": false, "covers.cover32": false } },
|
||||||
|
{ $match: { visibility: { $not: { $eq: 'hidden' } } } },
|
||||||
|
{ $sort: { _id: -1 } },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push({
|
||||||
|
$match: { visibility: { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
aggregate.push({ $limit: count })
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.aggregate(aggregate, {
|
||||||
|
allowDiskUse: true
|
||||||
|
})
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(album => {
|
||||||
|
album.type = "album";
|
||||||
|
album.tracks = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.byId = function (id, filter, callback) {
|
||||||
|
process.stdout.write("services/db_manager ALBUM by id: " + id + "\n");
|
||||||
|
let redis_key = "albumId_" + (filter || '') + '_' + id;
|
||||||
|
|
||||||
|
redis.get(redis_key, (value) => {
|
||||||
|
if (value) {
|
||||||
|
process.stdout.write("services/db_manager ALBUM by id REDIS: " + id + "\n");
|
||||||
|
callback(value);
|
||||||
|
} else {
|
||||||
|
let aggregate = [
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "tracks",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "album_id",
|
||||||
|
as: "tracks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $match: { _id: ObjectId(id) } }
|
||||||
|
]
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push({
|
||||||
|
$match: { visibility: { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(album => {
|
||||||
|
album.type = "album";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stdout.write("services/db_manager ALBUM by id MONGO: " + id + "\n");
|
||||||
|
callback(result[0]);
|
||||||
|
redis.set(redis_key, result[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.filter = function (term, callback) {
|
||||||
|
let aggregate = [
|
||||||
|
{ $project: { 'parent.covers': false, 'covers.cover32': false, 'covers.cover256': false } },
|
||||||
|
{ $match: { title: { $regex: term, $options: "i" } }, },
|
||||||
|
{ $limit: 6 }
|
||||||
|
]
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.tracks = function (id, callback) {
|
||||||
|
process.stdout.write("services/db_manager TRACKS by id: " + id + "\n");
|
||||||
|
|
||||||
|
let request = [
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "tracks",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "album_id",
|
||||||
|
as: "tracks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $match: { _id: ObjectId(id) } }
|
||||||
|
];
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.aggregate(request)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (result) {
|
||||||
|
callback(result[0]);
|
||||||
|
} else {
|
||||||
|
if (err) {
|
||||||
|
process.stderr("services/db_manager ALBUM by id ERROR: " + err.message);
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delete = function (album, callback) {
|
||||||
|
dbo.collection("albums")
|
||||||
|
.deleteOne({ _id: ObjectId(album._id) }, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.update = function (album, callback) {
|
||||||
|
dbo.collection("albums")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: ObjectId(album._id) },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
visibility: album.visibility
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateCovers = function (album, covers, callback) {
|
||||||
|
dbo.collection("albums")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: ObjectId(album._id) },
|
||||||
|
{ $set: { covers: covers } },
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.moveTo = function (album, callback) {
|
||||||
|
process.stdout.write("services/db_manager ALBUM '" + album._id + "' move to '" + album.artist_id + "'\n");
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: ObjectId(album._id) },
|
||||||
|
{ $set: { artist_id: ObjectId(album.artist_id), artist_name: album.artist_name } },
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.empty = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("albums")
|
||||||
|
.aggregate([
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "tracks",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "album_id",
|
||||||
|
as: "tracks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.toArray((err, result) => {
|
||||||
|
callback(result.filter(f => !f.tracks || f.tracks.length == 0));
|
||||||
|
});
|
||||||
|
};
|
264
services/database/artists.js
Normal file
264
services/database/artists.js
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
const redis = require("../redis")
|
||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
dbo.collection("artists").createIndex({ name: 1 });
|
||||||
|
// TEMPORARY
|
||||||
|
dbo.collection("artists").updateMany({}, { $unset: { cover32: 1, cover64: 1, cover128: 1, cover256: 1, cover512: 1 } })
|
||||||
|
});
|
||||||
|
|
||||||
|
let artist_lookup_album = {
|
||||||
|
$lookup: {
|
||||||
|
from: "albums",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "artist_id",
|
||||||
|
as: "albums"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let artist_lookup_tracks = {
|
||||||
|
$lookup: {
|
||||||
|
from: "tracks",
|
||||||
|
localField: "albums._id",
|
||||||
|
foreignField: "album_id",
|
||||||
|
as: "albums.tracks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let artists_project = {
|
||||||
|
$project: {
|
||||||
|
"covers.cover64": false,
|
||||||
|
"covers.cover128": false,
|
||||||
|
"covers.cover512": false,
|
||||||
|
"covers.cover1024": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let artist_project = {
|
||||||
|
$project: {
|
||||||
|
"albums.tracks.path": false,
|
||||||
|
"albums.tracks.bitrate": false,
|
||||||
|
"albums.tracks.album_id": false,
|
||||||
|
"albums.tracks.mime": false,
|
||||||
|
"albums.artist_id": false,
|
||||||
|
"albums.covers.cover256": false,
|
||||||
|
"albums.covers.cover512": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.collection = function (page, filter, callback) {
|
||||||
|
process.stdout.write("services/db_manager ARTISTS Collection: " + page + "\n");
|
||||||
|
let redis_key = "artistsCollection_" + (filter || '') + '_' + page;
|
||||||
|
|
||||||
|
redis.get(redis_key, (value) => {
|
||||||
|
if (value) {
|
||||||
|
process.stdout.write("services/db_manager ARTISTS Collection REDIS: " + page + "\n");
|
||||||
|
callback(value);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let aggregate = []
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push(
|
||||||
|
artist_lookup_album, {
|
||||||
|
$match: { "albums.visibility": { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
aggregate.push(
|
||||||
|
artists_project,
|
||||||
|
{ $sort: { name: 1 } }
|
||||||
|
)
|
||||||
|
if (page > -1) {
|
||||||
|
let pageSize = 12;
|
||||||
|
let skip = (page - 1) * pageSize;
|
||||||
|
aggregate.push(
|
||||||
|
{ $skip: skip },
|
||||||
|
{ $limit: pageSize });
|
||||||
|
}
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("artists")
|
||||||
|
.aggregate(aggregate, { allowDiskUse: true })
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "artist";
|
||||||
|
item.albums = [];
|
||||||
|
item.tracks = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stdout.write("services/db_manager ARTISTS Collection MONGO: " + page + "\n");
|
||||||
|
callback(result);
|
||||||
|
redis.set(redis_key, result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.favourites = function (id, callback) {
|
||||||
|
dbo.collection("favourites")
|
||||||
|
.find({ userId: id, type: "artist" })
|
||||||
|
.toArray((err, favourites) => {
|
||||||
|
if (err) throw err;
|
||||||
|
let aggregate = [
|
||||||
|
{ $match: { _id: { $in: favourites.map(m => m.itemId) } } },
|
||||||
|
artists_project,
|
||||||
|
]
|
||||||
|
dbo.collection("artists")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "artist";
|
||||||
|
item.albums = [];
|
||||||
|
item.tracks = [];
|
||||||
|
});
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.byId = function (id, filter, callback) {
|
||||||
|
process.stdout.write("services/db_manager ARTIST by id: " + id + "\n");
|
||||||
|
let redis_key = "artistId_" + (filter || '') + '_' + id;
|
||||||
|
|
||||||
|
redis.get(redis_key, (value) => {
|
||||||
|
if (value) {
|
||||||
|
process.stdout.write("services/db_manager ARTIST by id REDIS: " + id + "\n");
|
||||||
|
callback(value);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let aggregate = [
|
||||||
|
artist_lookup_album,
|
||||||
|
{
|
||||||
|
$unwind: { path: "$albums" }
|
||||||
|
},
|
||||||
|
artist_lookup_tracks,
|
||||||
|
artist_project,
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: "$_id",
|
||||||
|
name: { $first: "$name" },
|
||||||
|
covers: { $first: "$covers" },
|
||||||
|
albums: { $push: "$albums" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $match: { _id: ObjectId(id) } },
|
||||||
|
];
|
||||||
|
dbo
|
||||||
|
.collection("artists")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "artist";
|
||||||
|
item.tracks = [];
|
||||||
|
if (filter) {
|
||||||
|
item.albums = item.albums.filter(album => { return filter.indexOf(album.visibility) > -1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stdout.write("services/db_manager ARTIST by id MONGO: " + id + "\n");
|
||||||
|
callback(result[0]);
|
||||||
|
redis.set(redis_key, result[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.filter = function (term, callback) {
|
||||||
|
let aggregate = [
|
||||||
|
{ $project: { 'covers.cover64': false, 'covers.cover512': false } },
|
||||||
|
{ $match: { name: { $regex: term, $options: "i" } }, },
|
||||||
|
{ $limit: 6 }
|
||||||
|
]
|
||||||
|
dbo
|
||||||
|
.collection("artists")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.tracks = function (id, showPath, callback) {
|
||||||
|
let request = [
|
||||||
|
artist_lookup_album,
|
||||||
|
{
|
||||||
|
$unwind: { path: "$albums" }
|
||||||
|
},
|
||||||
|
artist_lookup_tracks,
|
||||||
|
{ $match: { _id: ObjectId(id) } },
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: "$_id",
|
||||||
|
name: { $first: "$name" },
|
||||||
|
albums: { $push: "$albums" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!showPath) {
|
||||||
|
request.push({
|
||||||
|
$project: {
|
||||||
|
"albums.tracks.path": false,
|
||||||
|
"albums.tracks.bitrate": false,
|
||||||
|
"albums.tracks.album_id": false,
|
||||||
|
"albums.tracks.mime": false,
|
||||||
|
"albums.artist_id": false,
|
||||||
|
"albums.covers": false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("artists")
|
||||||
|
.aggregate(request)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (result) {
|
||||||
|
callback(result[0]);
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delete = function (artist, callback) {
|
||||||
|
dbo.collection("artists")
|
||||||
|
.deleteOne({ _id: ObjectId(artist._id) }, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.updateCovers = function (artist, covers, callback) {
|
||||||
|
dbo.collection("artists").updateOne(
|
||||||
|
{ _id: ObjectId(artist._id) },
|
||||||
|
{ $set: { covers: covers } },
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.empty = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("artists")
|
||||||
|
.aggregate([
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "albums",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "artist_id",
|
||||||
|
as: "albums"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.toArray((err, result) => {
|
||||||
|
callback(result.filter(f => !f.albums || f.albums.length == 0));
|
||||||
|
});
|
||||||
|
};
|
273
services/database/boxes.js
Normal file
273
services/database/boxes.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
const redis = require("../redis")
|
||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
dbo.collection("boxes").createIndex({ title: 1 });
|
||||||
|
// TEMPORARY
|
||||||
|
dbo.collection("boxes").updateMany({}, { $unset: { cover32: 1, cover64: 1, cover128: 1, cover256: 1, cover512: 1 } })
|
||||||
|
});
|
||||||
|
|
||||||
|
let box_project = {
|
||||||
|
$project: {
|
||||||
|
"videos.path": false,
|
||||||
|
"videos.box_id": false,
|
||||||
|
"videos.mime": false,
|
||||||
|
path: false,
|
||||||
|
"covers.cover32": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let boxes_project = {
|
||||||
|
$project: {
|
||||||
|
path: false,
|
||||||
|
"covers.cover32": false,
|
||||||
|
"covers.cover64": false,
|
||||||
|
"covers.cover256": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let box_lookup_videos = {
|
||||||
|
$lookup: {
|
||||||
|
from: "videos",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "box_id",
|
||||||
|
as: "videos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.collection = function (page, filter, callback) {
|
||||||
|
process.stdout.write("services/db_manager BOXES Collection: " + page + "\n");
|
||||||
|
let redis_key = "boxesCollection_" + (filter || '') + '_' + page;
|
||||||
|
|
||||||
|
redis.get(redis_key, (value) => {
|
||||||
|
if (value) {
|
||||||
|
process.stdout.write("services/db_manager BOXES Collection REDIS: " + page + "\n");
|
||||||
|
callback(value);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
let aggregate = [
|
||||||
|
boxes_project,
|
||||||
|
{ $match: { visibility: { $not: { $eq: 'hidden' } } } },
|
||||||
|
{ $sort: { title: 1 } },
|
||||||
|
];
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push({
|
||||||
|
$match: { visibility: { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (page > -1) {
|
||||||
|
let pageSize = 12;
|
||||||
|
let skip = (page - 1) * pageSize;
|
||||||
|
aggregate.push(
|
||||||
|
{ $skip: skip },
|
||||||
|
{ $limit: pageSize });
|
||||||
|
}
|
||||||
|
dbo
|
||||||
|
.collection("boxes")
|
||||||
|
.aggregate(aggregate, {
|
||||||
|
allowDiskUse: true
|
||||||
|
})
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "box";
|
||||||
|
item.videos = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stdout.write("services/db_manager BOXES Collection MONGO: " + page + "\n");
|
||||||
|
callback(result);
|
||||||
|
redis.set(redis_key, result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.favourites = function (id, callback) {
|
||||||
|
dbo.collection("favourites")
|
||||||
|
.find({ userId: id, type: "box" })
|
||||||
|
.toArray((err, favourites) => {
|
||||||
|
if (err) throw err;
|
||||||
|
let aggregate = [
|
||||||
|
{ $match: { _id: { $in: favourites.map(m => m.itemId) } } },
|
||||||
|
boxes_project,
|
||||||
|
]
|
||||||
|
dbo.collection("boxes")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "box";
|
||||||
|
item.videos = [];
|
||||||
|
});
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.newest = function (count, filter, callback) {
|
||||||
|
let aggregate = [
|
||||||
|
boxes_project,
|
||||||
|
{ $sort: { _id: -1 } },
|
||||||
|
{ $match: { visibility: { $not: { $eq: 'hidden' } } } }
|
||||||
|
];
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push({
|
||||||
|
$match: { visibility: { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
aggregate.push({ $limit: count })
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("boxes")
|
||||||
|
.aggregate(aggregate, {
|
||||||
|
allowDiskUse: true
|
||||||
|
})
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "box";
|
||||||
|
item.videos = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.byId = function (id, filter, callback) {
|
||||||
|
process.stdout.write("services/db_manager BOX by id: " + id + "\n");
|
||||||
|
let redis_key = "boxId_" + (filter || '') + '_' + id;
|
||||||
|
|
||||||
|
redis.get(redis_key, (value) => {
|
||||||
|
if (value) {
|
||||||
|
process.stdout.write("services/db_manager BOX by id REDIS: " + id + "\n");
|
||||||
|
callback(value);
|
||||||
|
} else {
|
||||||
|
let aggregate = [
|
||||||
|
box_lookup_videos,
|
||||||
|
box_project,
|
||||||
|
{ $match: { _id: ObjectId(id) } }
|
||||||
|
];
|
||||||
|
if (filter) {
|
||||||
|
aggregate.push({
|
||||||
|
$match: { visibility: { $in: filter } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("boxes")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (result) {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "box";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.stdout.write("services/db_manager BOX by id MONGO: " + id + "\n");
|
||||||
|
callback(result[0]);
|
||||||
|
redis.set(redis_key, result[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.filter = function (term, callback) {
|
||||||
|
let aggregate = [
|
||||||
|
{ $project: { 'covers.cover256': false } },
|
||||||
|
{ $match: { title: { $regex: term, $options: "i" } }, },
|
||||||
|
{ $limit: 6 }
|
||||||
|
]
|
||||||
|
dbo
|
||||||
|
.collection("boxes")
|
||||||
|
.aggregate(aggregate)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.empty = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("boxes")
|
||||||
|
.aggregate([box_lookup_videos])
|
||||||
|
.toArray((err, result) => {
|
||||||
|
callback(result.filter(f => !f.videos || f.videos.length == 0));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.videos = function (id, showPath, callback) {
|
||||||
|
let request = [
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "videos",
|
||||||
|
localField: "_id",
|
||||||
|
foreignField: "box_id",
|
||||||
|
as: "videos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $match: { _id: ObjectId(id) } }
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!showPath) {
|
||||||
|
request.push({
|
||||||
|
$project: {
|
||||||
|
"videos.mime": false,
|
||||||
|
"videos.path": false,
|
||||||
|
"videos.box_id": false,
|
||||||
|
path: false,
|
||||||
|
"covers.cover32": false,
|
||||||
|
"covers.cover64": false,
|
||||||
|
"covers.cover128": false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("boxes")
|
||||||
|
.aggregate(request)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
callback(result[0]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delete = function (box, callback) {
|
||||||
|
dbo.collection("boxes").deleteOne({ _id: box._id }, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.update = function (box, callback) {
|
||||||
|
dbo.collection("boxes").updateOne(
|
||||||
|
{ _id: ObjectId(box._id) },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
visibility: box.visibility
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateCovers = function (box, covers, callback) {
|
||||||
|
dbo.collection("boxes").updateOne(
|
||||||
|
{ _id: ObjectId(box._id) },
|
||||||
|
{ $set: { covers: covers } },
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
734
services/database/index.js
Normal file
734
services/database/index.js
Normal file
@ -0,0 +1,734 @@
|
|||||||
|
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 videos = require("./videos");
|
||||||
|
exports.videos = videos;
|
||||||
|
|
||||||
|
const users = require("./users");
|
||||||
|
exports.users = users;
|
||||||
|
|
||||||
|
const system = require("./system");
|
||||||
|
exports.system = system;
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
79
services/database/radios.js
Normal file
79
services/database/radios.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.collection = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("radios")
|
||||||
|
.find({})
|
||||||
|
.sort({ name: 1 })
|
||||||
|
.toArray((err, result) => {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.type = "radio";
|
||||||
|
});
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.byId = function (id, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("radios")
|
||||||
|
.findOne({ _id: ObjectId(id) }, (err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.add = function (radio, callback) {
|
||||||
|
dbo.collection("radios").updateOne(
|
||||||
|
{ url: radio.url },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
name: radio.name,
|
||||||
|
url: radio.url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ upsert: true },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
dbo.collection("radios").findOne({ url: radio.url }, (err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delete = function (id, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("radios")
|
||||||
|
.deleteOne({ _id: ObjectId(id) }, (err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.update = function (radio, callback) {
|
||||||
|
dbo.collection("radios").updateOne(
|
||||||
|
{ _id: radio._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
name: radio.name,
|
||||||
|
url: radio.url,
|
||||||
|
cover32: radio.cover32,
|
||||||
|
cover64: radio.cover64,
|
||||||
|
cover128: radio.cover128
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
59
services/database/system.js
Normal file
59
services/database/system.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.domains = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("system")
|
||||||
|
.findOne({ key: 'domains' })
|
||||||
|
.then(result => {
|
||||||
|
if (result) {
|
||||||
|
callback(result.value);
|
||||||
|
} else {
|
||||||
|
callback([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.allows = function (callback) {
|
||||||
|
dbo.collection("system")
|
||||||
|
.findOne({ key: 'allows' })
|
||||||
|
.then(allows => {
|
||||||
|
if (!allows) {
|
||||||
|
callback({
|
||||||
|
"guests": false,
|
||||||
|
"register": false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(allows.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.setAllows = function (allows, callback) {
|
||||||
|
dbo.collection("system")
|
||||||
|
.updateOne(
|
||||||
|
{ key: "allows" },
|
||||||
|
{ $set: { value: allows } },
|
||||||
|
{ upsert: true }, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.setDomains = function (domains, callback) {
|
||||||
|
dbo.collection("system")
|
||||||
|
.updateOne(
|
||||||
|
{ key: "domains" },
|
||||||
|
{ $set: { value: domains } },
|
||||||
|
{ upsert: true }, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
113
services/database/tracks.js
Normal file
113
services/database/tracks.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
dbo.collection("tracks").createIndex({ path: 1, album_id: 1, "disk.no": 1, "track.no": 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = require("../../server");
|
||||||
|
const config = server.config;
|
||||||
|
|
||||||
|
exports.collection = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("tracks")
|
||||||
|
.find()
|
||||||
|
.toArray((err, result) => {
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.byId = function (id, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("tracks")
|
||||||
|
.findOne({ _id: ObjectId(id) }, (err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delete = function (track, callback) {
|
||||||
|
dbo.collection("tracks").deleteOne(track, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.exists = function (path, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("tracks")
|
||||||
|
.find({ path: path.replace(config.music_folder, "") })
|
||||||
|
.limit(1)
|
||||||
|
.toArray((err, result) => {
|
||||||
|
callback(result.length > 0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.moveTo = function (track, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("tracks")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: ObjectId(track._id) },
|
||||||
|
{ $set: { album_id: ObjectId(track.album_id) } },
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.mostListened = function (filter, callback) {
|
||||||
|
let aggregate = [{
|
||||||
|
$group: {
|
||||||
|
_id: "$id",
|
||||||
|
title: { $last: "$title" },
|
||||||
|
covers: { $last: "$covers" },
|
||||||
|
parent: { $last: "$parent" },
|
||||||
|
counter: { $sum: 1 },
|
||||||
|
album: { $last: "$album" }
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
if (filter) {
|
||||||
|
aggregate
|
||||||
|
.unshift({
|
||||||
|
$lookup: {
|
||||||
|
from: "albums",
|
||||||
|
let: { album_id: "$parent._id" },
|
||||||
|
pipeline: [{
|
||||||
|
$match: {
|
||||||
|
$expr: {
|
||||||
|
$eq: ["$_id", { $toObjectId: "$$album_id" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
as: "album"
|
||||||
|
}
|
||||||
|
}, { $unwind: "$album" })
|
||||||
|
aggregate
|
||||||
|
.push({
|
||||||
|
$project: {
|
||||||
|
"album.cover256": false,
|
||||||
|
"album.cover128": false,
|
||||||
|
"album.cover64": false,
|
||||||
|
"album.cover32": false
|
||||||
|
}
|
||||||
|
}, { $match: { "album.visibility": { $in: filter } } });
|
||||||
|
} else {
|
||||||
|
aggregate.unshift({ $match: { type: 'track' } });
|
||||||
|
}
|
||||||
|
aggregate.push({ $sort: { counter: -1, _id: -1 } }, { $limit: 6 })
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("history")
|
||||||
|
.aggregate(aggregate, {
|
||||||
|
allowDiskUse: true
|
||||||
|
})
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
49
services/database/users.js
Normal file
49
services/database/users.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
const connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
dbo.collection("favourites").createIndex({ userId: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.collection = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("users")
|
||||||
|
.find({}, { password: false })
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.favourites = function (id, callback) {
|
||||||
|
dbo.collection("favourites")
|
||||||
|
.find({ userId: id })
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.insertFavourite = function (item, callback) {
|
||||||
|
item.itemId = ObjectId(item.itemId);
|
||||||
|
dbo
|
||||||
|
.collection("favourites")
|
||||||
|
.insertOne(item, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.deleteFavourite = function (item, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("favourites")
|
||||||
|
.deleteMany({ userId: item.userId, itemId: ObjectId(item.itemId) }, (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
118
services/database/videos.js
Normal file
118
services/database/videos.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
const { ObjectId } = require('mongodb');
|
||||||
|
var connector = require("./CONNECTOR");
|
||||||
|
var dbo;
|
||||||
|
connector.connect().then((ret) => {
|
||||||
|
dbo = ret;
|
||||||
|
dbo.collection("videos").createIndex({ box_id: 1, title: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = require("../../server");
|
||||||
|
var config = server.config;
|
||||||
|
|
||||||
|
exports.exists = function (path, callback) {
|
||||||
|
let short_path = path.replace(config.video_folder, "");
|
||||||
|
dbo.collection("videos").findOne({ path: short_path }, (err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.collection = function (callback) {
|
||||||
|
dbo
|
||||||
|
.collection("videos")
|
||||||
|
.find()
|
||||||
|
.toArray((err, result) => {
|
||||||
|
result.forEach(item => {
|
||||||
|
item.thumbnail = "";
|
||||||
|
});
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.byId = function (id, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("videos")
|
||||||
|
.findOne({ _id: ObjectId(id) }, (err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delete = function (video, callback) {
|
||||||
|
dbo.collection("videos")
|
||||||
|
.deleteOne({ _id: ObjectId(video._id) }, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.moveTo = function (video, callback) {
|
||||||
|
dbo
|
||||||
|
.collection("videos")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: ObjectId(video._id) },
|
||||||
|
{ $set: { box_id: ObjectId(video.box_id) } },
|
||||||
|
{ upsert: false },
|
||||||
|
err => {
|
||||||
|
if (err) throw err;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.mostViewed = function (filter, callback) {
|
||||||
|
let aggregate = [{
|
||||||
|
$group: {
|
||||||
|
_id: "$id",
|
||||||
|
title: { $last: "$title" },
|
||||||
|
thumbnail: { $last: "$thumbnail" },
|
||||||
|
parent: { $last: "$parent" },
|
||||||
|
counter: { $sum: 1 },
|
||||||
|
box: { $last: "$box" }
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
if (filter) {
|
||||||
|
aggregate
|
||||||
|
.unshift({
|
||||||
|
$lookup: {
|
||||||
|
from: "boxes",
|
||||||
|
let: { box_id: "$parent._id" },
|
||||||
|
pipeline: [{
|
||||||
|
$match: {
|
||||||
|
$expr: {
|
||||||
|
$eq: ["$_id", { $toObjectId: "$$box_id" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
as: "box"
|
||||||
|
}
|
||||||
|
}, { $unwind: "$box" })
|
||||||
|
aggregate.push({
|
||||||
|
$project: {
|
||||||
|
"box.cover256": false,
|
||||||
|
"box.cover128": false,
|
||||||
|
"box.cover64": false,
|
||||||
|
"box.cover32": false,
|
||||||
|
"box.path": false
|
||||||
|
}
|
||||||
|
}, { $match: { "box.visibility": { $in: filter } } });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
aggregate.unshift({ $match: { type: 'video' } });
|
||||||
|
}
|
||||||
|
aggregate.push(
|
||||||
|
{ $sort: { counter: -1, _id: -1 } },
|
||||||
|
{ $limit: 12 });
|
||||||
|
|
||||||
|
dbo
|
||||||
|
.collection("history")
|
||||||
|
.aggregate(aggregate, {
|
||||||
|
allowDiskUse: true
|
||||||
|
})
|
||||||
|
.toArray((err, result) => {
|
||||||
|
if (err) throw err;
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
};
|
135
services/files_scanner.js
Normal file
135
services/files_scanner.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
var fs = require("fs");
|
||||||
|
var mime = require("mime-types");
|
||||||
|
var notifier = require("./notifier");
|
||||||
|
var database = require("./database");
|
||||||
|
|
||||||
|
var server = require("../server");
|
||||||
|
var config = server.config;
|
||||||
|
|
||||||
|
/*
|
||||||
|
MUSIC
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.scann_local_music_files = async function (path) {
|
||||||
|
remove_non_exists_tracks();
|
||||||
|
scann_local_music_files(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function remove_non_exists_tracks() {
|
||||||
|
database.tracks.collection(tracks => {
|
||||||
|
tracks.forEach(track => {
|
||||||
|
let path = config.music_folder + track.path;
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
path = config.upload_folder + track.path;
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
database.tracks.delete(track, () => {
|
||||||
|
notifier.emit("track_deleted", track._id);
|
||||||
|
process.stdout.write("track deleted: " + path + "\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
process.stdout.write("check empty albums\n");
|
||||||
|
database.albums.empty(albums => {
|
||||||
|
albums.forEach(album => {
|
||||||
|
database.albums.delete(album, () => {
|
||||||
|
notifier.emit("album_deleted", album._id);
|
||||||
|
process.stdout.write("album deleted: " + album.title + "\n");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
process.stdout.write("check empty artists\n");
|
||||||
|
database.artists.empty(artists => {
|
||||||
|
artists.forEach(artist => {
|
||||||
|
database.artists.delete(artist, () => {
|
||||||
|
notifier.emit("artist_deleted", artist._id);
|
||||||
|
process.stdout.write("artist deleted: " + artist.name + "\n");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scann_local_music_files(path) {
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs.readdirSync(path).forEach(child => {
|
||||||
|
if (!child.startsWith(".")) {
|
||||||
|
var full_path = path + "/" + child;
|
||||||
|
if (fs.lstatSync(full_path).isDirectory()) {
|
||||||
|
//setTimeout(() => {
|
||||||
|
scann_local_music_files(full_path);
|
||||||
|
//}, 1000);
|
||||||
|
} else {
|
||||||
|
var mime_type = mime.lookup(full_path);
|
||||||
|
if (
|
||||||
|
mime_type &&
|
||||||
|
mime_type.startsWith("audio/") &&
|
||||||
|
mime_type.indexOf("x-mpegurl") == -1 &&
|
||||||
|
mime_type.indexOf("x-scpls") == -1
|
||||||
|
) {
|
||||||
|
let item = { path: full_path, mime: mime_type };
|
||||||
|
notifier.emit("music_file_found", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
VIDEO
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.scann_local_video_files = async function (path) {
|
||||||
|
remove_non_exists_videos();
|
||||||
|
scann_local_video_files(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function remove_non_exists_videos() {
|
||||||
|
database.videos.collection(videos => {
|
||||||
|
videos.forEach(video => {
|
||||||
|
let path = config.video_folder + video.path;
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
path = config.upload_folder + video.path;
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
database.videos.delete(video, () => {
|
||||||
|
notifier.emit("video_deleted", video._id);
|
||||||
|
process.stdout.write("video deleted: " + path + "\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
process.stdout.write("check empty boxes\n");
|
||||||
|
database.boxes.empty(boxes => {
|
||||||
|
boxes.forEach(box => {
|
||||||
|
database.boxes.delete(box, () => {
|
||||||
|
notifier.emit("box_deleted", box._id);
|
||||||
|
process.stdout.write("box deleted: " + box.title + "\n");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scann_local_video_files(path) {
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs.readdirSync(path).forEach(child => {
|
||||||
|
if (!child.startsWith(".")) {
|
||||||
|
var full_path = path + "/" + child;
|
||||||
|
if (fs.lstatSync(full_path).isDirectory()) {
|
||||||
|
//setTimeout(() => {
|
||||||
|
scann_local_video_files(full_path);
|
||||||
|
//}, 1000);
|
||||||
|
} else {
|
||||||
|
var mime_type = mime.lookup(full_path);
|
||||||
|
if (mime_type && mime_type.startsWith("video/")) {
|
||||||
|
let item = { path: full_path, mime: mime_type };
|
||||||
|
notifier.emit("video_file_found", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
215
services/music_brainz.js
Normal file
215
services/music_brainz.js
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
var request = require("request");
|
||||||
|
var notifier = require("./notifier");
|
||||||
|
|
||||||
|
var album_cover_queue = [];
|
||||||
|
var album_cover_requst_is_running = false;
|
||||||
|
|
||||||
|
var artist_cover_queue = [];
|
||||||
|
var artist_cover_queue_is_running = false;
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"WebPlay/0.1.0 (https://gitea.com/WebPlay)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.find_album_cover = function(album) {
|
||||||
|
album_cover_queue.push(album);
|
||||||
|
console.log("MB Album: " + album.title + " by " + album.artist_name);
|
||||||
|
run_album_cover_request();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.find_artist_cover = function(artist) {
|
||||||
|
artist_cover_queue.push(artist);
|
||||||
|
console.log("MB Artist: " + artist.name);
|
||||||
|
run_artist_cover_request();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ARTIST COVER
|
||||||
|
async function run_artist_cover_request() {
|
||||||
|
console.log("started request for artist covers");
|
||||||
|
if (artist_cover_queue_is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
artist_cover_queue_is_running = true;
|
||||||
|
while (artist_cover_queue && artist_cover_queue.length > 0) {
|
||||||
|
await sleep(1500);
|
||||||
|
let artist = artist_cover_queue.shift();
|
||||||
|
console.log("SHIFT Artist: " + artist.name);
|
||||||
|
for (let i = 0; i < artist.albums.length; i++) {
|
||||||
|
await sleep(1500);
|
||||||
|
if (artist.image_downloaded) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let album = artist.albums[i];
|
||||||
|
let album_title = album.title.replace("&", "%26").replace("/", "_");
|
||||||
|
let artist_name = artist.name.replace("&", "%26").replace("/", "_");
|
||||||
|
let url = `https://musicbrainz.org/ws/2/release/?query=release:${album_title} AND artist:${artist_name}&fmt=json`;
|
||||||
|
|
||||||
|
options.url = url;
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let json = json_parser(body, url);
|
||||||
|
if (!json) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (json.releases && json.releases.length > 0) {
|
||||||
|
let release = json.releases[0];
|
||||||
|
if (release["artist-credit"] && release["artist-credit"].length > 0) {
|
||||||
|
let artist_id = release["artist-credit"][0].artist.id;
|
||||||
|
console.log(artist_id);
|
||||||
|
get_image_by_artist_id(artist, artist_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
artist_cover_queue_is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get_image_by_artist_id(artist, artist_id) {
|
||||||
|
let url = `https://musicbrainz.org/ws/2/artist/${artist_id}?inc=url-rels&fmt=json`;
|
||||||
|
console.log(url);
|
||||||
|
options.url = url;
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
let json = json_parser(body, url);
|
||||||
|
if (!json) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!json.relations) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
json.relations.forEach(relation => {
|
||||||
|
if (relation.type == "image" && relation.url && relation.url.resource) {
|
||||||
|
let resource = relation.url.resource;
|
||||||
|
if (resource.includes("commons.wikimedia.org")) {
|
||||||
|
get_image_by_wikimedia(artist, resource);
|
||||||
|
} else {
|
||||||
|
artist.image_downloaded = true;
|
||||||
|
notifier.emit("found_music_brainz_artist_cover", {
|
||||||
|
artist: artist,
|
||||||
|
mb: resource
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_image_by_wikimedia(artist, url) {
|
||||||
|
let regex = RegExp("(?<=File:)[^<]*", "g");
|
||||||
|
let result = regex.exec(url);
|
||||||
|
if (result) {
|
||||||
|
console.log(result[0]);
|
||||||
|
let file = result[0];
|
||||||
|
let url = `https://en.wikipedia.org/w/api.php?action=query&titles=File:${file}&prop=imageinfo&iiprop=url&iiurlwidth=600&iiurlheight=600&format=json`;
|
||||||
|
console.log(url);
|
||||||
|
options.url = url;
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
let json = json_parser(body, url);
|
||||||
|
if (!json) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.query.pages["-1"].imageinfo[0].thumburl) {
|
||||||
|
console.log(json.query.pages["-1"].imageinfo[0].thumburl);
|
||||||
|
artist.image_downloaded = true;
|
||||||
|
notifier.emit("found_music_brainz_artist_cover", {
|
||||||
|
artist: artist,
|
||||||
|
mb: json.query.pages["-1"].imageinfo[0].thumburl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALBUM COVER
|
||||||
|
async function run_album_cover_request() {
|
||||||
|
console.log("started request for album covers");
|
||||||
|
if (album_cover_requst_is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
album_cover_requst_is_running = true;
|
||||||
|
while (album_cover_queue && album_cover_queue.length > 0) {
|
||||||
|
await sleep(1500);
|
||||||
|
let album = album_cover_queue.shift();
|
||||||
|
console.log("SHIFT Album: " + album.title);
|
||||||
|
let album_title = album.title.replace("&", "%26").replace("/", "_");
|
||||||
|
let artist_name = album.artist_name.replace("&", "%26").replace("/", "_");
|
||||||
|
|
||||||
|
let url = `https://musicbrainz.org/ws/2/release/?query=release:${album_title} AND artist:${artist_name}&fmt=json`;
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
|
options.url = url;
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
let json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(body);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (json.releases && json.releases.length > 0) {
|
||||||
|
let release_id = json.releases[0].id;
|
||||||
|
let title = json.releases[0].title;
|
||||||
|
console.log(release_id + ": " + title);
|
||||||
|
get_album_cover_url(album, release_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
album_cover_requst_is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_album_cover_url(album, id) {
|
||||||
|
let url = "https://coverartarchive.org/release/" + id;
|
||||||
|
options.url = url;
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = JSON.parse(body);
|
||||||
|
if (json.images && json.images.length > 0) {
|
||||||
|
if (json.images[0].thumbnails.large) {
|
||||||
|
notifier.emit("found_music_brainz_album_cover", {
|
||||||
|
album: album,
|
||||||
|
mb: json.images[0].thumbnails.large
|
||||||
|
});
|
||||||
|
} else if (json.images[0].image) {
|
||||||
|
notifier.emit("found_music_brainz_album_cover", {
|
||||||
|
album: album,
|
||||||
|
mb: json.images[0].image
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
function json_parser(body, url) {
|
||||||
|
let json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(body);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
console.log(url);
|
||||||
|
console.log("BODY============BEGIN");
|
||||||
|
console.log(body);
|
||||||
|
console.log("BODY============END");
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
4
services/notifier.js
Normal file
4
services/notifier.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
var events = require('events');
|
||||||
|
var notifier = new events.EventEmitter();
|
||||||
|
|
||||||
|
module.exports = notifier;
|
36
services/redis/index.js
Normal file
36
services/redis/index.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const { createClient } = require('redis');
|
||||||
|
const server = require("../../server");
|
||||||
|
const config = server.config;
|
||||||
|
|
||||||
|
const redisUrl = "redis://" + config.redis.host + ":" + config.redis.port
|
||||||
|
|
||||||
|
const client = createClient({
|
||||||
|
url: redisUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', (err) => console.log('Redis Client Error', err));
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
client.flushAll();
|
||||||
|
|
||||||
|
const expire = 57600; // 24h
|
||||||
|
|
||||||
|
exports.set = function (key, value) {
|
||||||
|
if (value) {
|
||||||
|
client.set(key, JSON.stringify(value));
|
||||||
|
client.expire(key, expire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.get = function (key, callback) {
|
||||||
|
process.stdout.write("services/redis get '" + key + "'\n");
|
||||||
|
client.get(key).then(value => {
|
||||||
|
callback(JSON.parse(value));
|
||||||
|
});
|
||||||
|
client.expire(key, expire);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.flushAll = function () {
|
||||||
|
client.flushAll();
|
||||||
|
process.stdout.write("services/redis flushAll()\n");
|
||||||
|
}
|
262
services/tag_excluder.js
Normal file
262
services/tag_excluder.js
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
var path = require("path");
|
||||||
|
var notifier = require("./notifier");
|
||||||
|
var meta_data = require("music-metadata");
|
||||||
|
var db_manager = require("./database");
|
||||||
|
var server = require("../server");
|
||||||
|
var config = server.config;
|
||||||
|
|
||||||
|
var music_files_data = [];
|
||||||
|
var music_files_cover = [];
|
||||||
|
var parsing_music_data = 0;
|
||||||
|
var parsing_cover = 0;
|
||||||
|
|
||||||
|
var video_files_data = [];
|
||||||
|
var parsing_video_data = 0;
|
||||||
|
|
||||||
|
var exclude_meta_cover_is_running = false;
|
||||||
|
var exclude_meta_data_is_running = false;
|
||||||
|
var exclude_path_data_is_running = false;
|
||||||
|
|
||||||
|
var sleep_dur = 10;
|
||||||
|
|
||||||
|
exports.parsing_music_data = function() {
|
||||||
|
return parsing_music_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parsing_video_data = function() {
|
||||||
|
return parsing_video_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parsing_cover = function() {
|
||||||
|
return parsing_cover;
|
||||||
|
};
|
||||||
|
|
||||||
|
var music_timeout_handler_data;
|
||||||
|
var music_timeout_handler_cover;
|
||||||
|
var video_timeout_handler_data;
|
||||||
|
|
||||||
|
exports.get_buffer_size = () => {
|
||||||
|
return music_files_data.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
notifier.on("music_file_found", file => {
|
||||||
|
music_files_data.push(file);
|
||||||
|
if (music_timeout_handler_data) {
|
||||||
|
clearTimeout(music_timeout_handler_data);
|
||||||
|
}
|
||||||
|
music_timeout_handler_data = setTimeout(exclude_meta_data, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
notifier.on("album_cover_request", track => {
|
||||||
|
music_files_cover.push(track);
|
||||||
|
if (music_timeout_handler_cover) {
|
||||||
|
clearTimeout(music_timeout_handler_cover);
|
||||||
|
}
|
||||||
|
music_timeout_handler_cover = setTimeout(exclude_meta_cover, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function exclude_meta_data() {
|
||||||
|
if (exclude_meta_data_is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process.stdout.write("Start excluding meta data...\n");
|
||||||
|
exclude_meta_data_is_running = true;
|
||||||
|
|
||||||
|
while (music_files_data && music_files_data.length > 0) {
|
||||||
|
while (parsing_music_data >= 1) {
|
||||||
|
await sleep(sleep_dur);
|
||||||
|
}
|
||||||
|
parsing_music_data++;
|
||||||
|
let file = music_files_data.shift();
|
||||||
|
if (!file) {
|
||||||
|
parsing_music_data--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_manager.tracks.exists(file.path, exists => {
|
||||||
|
if (exists) {
|
||||||
|
parsing_music_data--;
|
||||||
|
} else {
|
||||||
|
let options = {
|
||||||
|
skipCovers: true,
|
||||||
|
skipPostHeaders: true,
|
||||||
|
duration: true
|
||||||
|
};
|
||||||
|
meta_data
|
||||||
|
.parseFile(file.path, options)
|
||||||
|
.then(meta => {
|
||||||
|
let title = (meta.common.title || "").trim();
|
||||||
|
let album = (meta.common.album || "").trim();
|
||||||
|
let artist = (
|
||||||
|
meta.common.albumartist ||
|
||||||
|
meta.common.artist ||
|
||||||
|
""
|
||||||
|
).trim();
|
||||||
|
|
||||||
|
if (title == "") {
|
||||||
|
let ext = path.extname(file.path);
|
||||||
|
title = path.basename(file.path, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (album == "") {
|
||||||
|
let dir = path.dirname(file.path);
|
||||||
|
album = path.basename(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = {
|
||||||
|
path: file.path,
|
||||||
|
artist: artist,
|
||||||
|
album: album,
|
||||||
|
title: title,
|
||||||
|
duration: meta.format.duration,
|
||||||
|
bitrate: meta.format.bitrate,
|
||||||
|
mime: file.mime,
|
||||||
|
year: meta.common.year,
|
||||||
|
track: meta.common.track,
|
||||||
|
disk: meta.common.disk,
|
||||||
|
genre: meta.common.genre
|
||||||
|
};
|
||||||
|
notifier.emit("metadata_excluded", item);
|
||||||
|
parsing_music_data--;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
process.stdout.write(file.path);
|
||||||
|
console.log(err);
|
||||||
|
parsing_music_data--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write("End excluding meta data...\n");
|
||||||
|
exclude_meta_data_is_running = false;
|
||||||
|
|
||||||
|
notifier.emit("exclude_metadata_finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exclude_meta_cover() {
|
||||||
|
if (exclude_meta_cover_is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process.stdout.write("Start excluding meta COVER...\n");
|
||||||
|
exclude_meta_cover_is_running = true;
|
||||||
|
|
||||||
|
while (music_files_cover && music_files_cover.length > 0) {
|
||||||
|
while (parsing_cover >= server.config.cpu_cores) {
|
||||||
|
await sleep(50);
|
||||||
|
}
|
||||||
|
parsing_cover++;
|
||||||
|
let track = music_files_cover.shift();
|
||||||
|
if (!track) {
|
||||||
|
parsing_cover--;
|
||||||
|
process.stdout.write("file is null");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let path = config.music_folder + track.path;
|
||||||
|
meta_data
|
||||||
|
.parseFile(path)
|
||||||
|
.then(meta => {
|
||||||
|
parsing_cover--;
|
||||||
|
if (meta.common.picture) {
|
||||||
|
track.parent.picture = meta.common.picture;
|
||||||
|
notifier.emit("metadata_picture_excluded", track.parent);
|
||||||
|
} else {
|
||||||
|
notifier.emit("no_metadata_picture_excluded", track.parent);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
music_files_cover.splice(0, 1);
|
||||||
|
parsing_cover--;
|
||||||
|
notifier.emit("no_metadata_picture_excluded", track.parent);
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exclude_meta_cover_is_running = false;
|
||||||
|
process.stdout.write("End excluding meta COVER...\n");
|
||||||
|
|
||||||
|
notifier.emit("exclude_metacover_finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
notifier.on("video_file_found", file => {
|
||||||
|
video_files_data.push(file);
|
||||||
|
if (video_timeout_handler_data) {
|
||||||
|
clearTimeout(video_timeout_handler_data);
|
||||||
|
}
|
||||||
|
video_timeout_handler_data = setTimeout(exclude_path_data, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function exclude_path_data() {
|
||||||
|
if (exclude_path_data_is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process.stdout.write("Start excluding path data...\n");
|
||||||
|
exclude_path_data_is_running = true;
|
||||||
|
|
||||||
|
while (video_files_data && video_files_data.length > 0) {
|
||||||
|
while (parsing_video_data >= 1) {
|
||||||
|
await sleep(sleep_dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsing_video_data++;
|
||||||
|
let video = video_files_data.shift();
|
||||||
|
if (!video) {
|
||||||
|
parsing_video_data--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = video.path;
|
||||||
|
db_manager.videos.exists(file_path, exists => {
|
||||||
|
if (exists) {
|
||||||
|
parsing_video_data--;
|
||||||
|
if (!exists.thumbnail || !exists.tracks) {
|
||||||
|
notifier.emit("check_video_details", exists);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dir = path.dirname(file_path);
|
||||||
|
let box = path.basename(dir).trim();
|
||||||
|
let year = 0;
|
||||||
|
|
||||||
|
let regex_year = /\s?\([12]\d\d\d\)$/g;
|
||||||
|
let regex_season = /^seasons?\s*\d+$/gi;
|
||||||
|
let result = regex_season.exec(box);
|
||||||
|
if (result) {
|
||||||
|
let season_dir = path.dirname(dir);
|
||||||
|
let parent = path.basename(season_dir);
|
||||||
|
box = parent + " - " + box;
|
||||||
|
} else {
|
||||||
|
process.stdout.write("check box title: " + box + "\n");
|
||||||
|
result = regex_year.exec(box);
|
||||||
|
if (result) {
|
||||||
|
box = box.replace(result[0], "");
|
||||||
|
year = result[0]
|
||||||
|
.replace("(", "")
|
||||||
|
.replace(")", "")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ext = path.extname(file_path);
|
||||||
|
let title = path.basename(file_path, ext);
|
||||||
|
|
||||||
|
let item = {
|
||||||
|
path: file_path,
|
||||||
|
box: box,
|
||||||
|
box_path: dir,
|
||||||
|
title: title,
|
||||||
|
mime: video.mime,
|
||||||
|
year: year
|
||||||
|
};
|
||||||
|
notifier.emit("pathdata_excluded", item);
|
||||||
|
parsing_video_data--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write("End excluding path data...\n");
|
||||||
|
exclude_path_data_is_running = false;
|
||||||
|
notifier.emit("exclude_pathdata_finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||||
|
}
|
146
services/the_movie_db.js
Normal file
146
services/the_movie_db.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
var request = require("request");
|
||||||
|
var notifier = require("./notifier");
|
||||||
|
|
||||||
|
var box_cover_queue = [];
|
||||||
|
var box_cover_requst_is_running = false;
|
||||||
|
|
||||||
|
var key = "3f3692d9c336994625a838a95b9a2ed0";
|
||||||
|
var poster_root = "https://image.tmdb.org/t/p/w500";
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"WebPlay/0.1.0 (https://gitea.com/WebPlay)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.find_box_cover = function (box) {
|
||||||
|
box_cover_queue.push(box);
|
||||||
|
process.stdout.write("TMDB Box: " + box.title + "\n");
|
||||||
|
run_box_cover_request();
|
||||||
|
};
|
||||||
|
|
||||||
|
async function run_box_cover_request() {
|
||||||
|
process.stdout.write("started request for box covers\n");
|
||||||
|
if (box_cover_requst_is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
box_cover_requst_is_running = true;
|
||||||
|
while (box_cover_queue && box_cover_queue.length > 0) {
|
||||||
|
await sleep(1500);
|
||||||
|
let box = box_cover_queue.shift();
|
||||||
|
process.stdout.write("SHIFT box: " + box.title + "\n");
|
||||||
|
|
||||||
|
let elements = check_for_season(box.title);
|
||||||
|
|
||||||
|
if (elements.season > 0) {
|
||||||
|
process.stdout.write("Has SEASONS: " + box.title)
|
||||||
|
cover_by_season_number(box, elements);
|
||||||
|
} else {
|
||||||
|
process.stdout.write("Has NO SEASONS: " + box.title)
|
||||||
|
cover_by_movie_title(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
box_cover_requst_is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cover_by_movie_title(box) {
|
||||||
|
options.url = `https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${box.title.replace(' ', '%20')}&page=1&include_adult=false`;
|
||||||
|
if (box.year > 0) {
|
||||||
|
options.url = `https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${box.title}&page=1&primary_release_year=${box.year}&include_adult=false`;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write("\nSTART: " + options.url + "\n");
|
||||||
|
try {
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write("PARSE BODY FOR: " + box.title + "\n");
|
||||||
|
process.stdout.write(body);
|
||||||
|
process.stdout.write("\n\n");
|
||||||
|
|
||||||
|
let json = JSON.parse(body);
|
||||||
|
if (
|
||||||
|
json.results &&
|
||||||
|
json.results.length > 0 &&
|
||||||
|
json.results[0].poster_path
|
||||||
|
) {
|
||||||
|
box.tmdb = poster_root + json.results[0].poster_path;
|
||||||
|
notifier.emit("found_movie_db_box_cover", box);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
process.stdout.write("\nERROR: " + box.title + "\n");
|
||||||
|
process.stdout.write(options.url + "\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cover_by_season_number(box, elements) {
|
||||||
|
options.url = `https://api.themoviedb.org/3/search/tv?api_key=${key}&query=${elements.title
|
||||||
|
}&page=1`;
|
||||||
|
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = JSON.parse(body);
|
||||||
|
|
||||||
|
if (json.results && json.results.length > 0) {
|
||||||
|
let id = json.results[0].id;
|
||||||
|
cover_by_tv_id(id, elements.season, box);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cover_by_tv_id(id, season, box) {
|
||||||
|
options.url = `https://api.themoviedb.org/3/tv/${id}/season/${season}?api_key=${key}`;
|
||||||
|
|
||||||
|
request(options, (err, res, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = JSON.parse(body);
|
||||||
|
if (json.poster_path) {
|
||||||
|
box.tmdb = poster_root + json.poster_path;
|
||||||
|
notifier.emit("found_movie_db_box_cover", box);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_for_season(title) {
|
||||||
|
let return_value = { title: title, season: 0 };
|
||||||
|
let regex_season = /\s*\-?\s*seasons?\s*\d+$/gi;
|
||||||
|
|
||||||
|
let result = regex_season.exec(title);
|
||||||
|
if (result) {
|
||||||
|
return_value.title = title.replace(result[0], "");
|
||||||
|
|
||||||
|
let regex_season_no = /\d*$/g;
|
||||||
|
result = regex_season_no.exec(title);
|
||||||
|
return_value.season = parseInt(result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user