223 lines
6.8 KiB
JavaScript
223 lines
6.8 KiB
JavaScript
|
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;
|