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;