diff --git a/package.json b/package.json index 070d3b6..1d1f57c 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,14 @@ }, "private": true, "dependencies": { + "babel-plugin-transform-runtime": "^6.23.0", "bluebird": "^3.5.2", "express": "^4.16.3", "express-async-errors": "^3.0.0", "fs-extra": "^7.0.0", + "socket.io": "^2.1.1", "sqlite": "^3.0.0", - "babel-plugin-transform-runtime": "^6.23.0" + "sqlite3": "^4.0.3" }, "devDependencies": { "babel-core": "^6.26.3", diff --git a/src/common/async.js b/src/common/async.js index e32a895..eea02e7 100644 --- a/src/common/async.js +++ b/src/common/async.js @@ -42,7 +42,7 @@ async function insertDB (db, track) { } await db.run('INSERT INTO Track VALUES (NULL,?,?,?,?,?,?,?,?)', - [track.title, track.artist, track.file, track.album || null, track.genre || null, track.track || null, + [track.title, track.artist, track.file, track.album || '', track.genre || null, track.track || null, track.year || null, Math.floor(track.duration)]) return track diff --git a/src/common/download.js b/src/common/download.js index 3b6c8fd..7e1ba8c 100644 --- a/src/common/download.js +++ b/src/common/download.js @@ -82,29 +82,31 @@ function fetchVideo (data) { } async function getInfos (file) { - let id3 = await asn.promiseExec(`id3 "${file}"`) - let prds = id3.stdout.split('\n') - let data = {} + let formatData = await asn.promiseExec(`ffprobe -i "${file}" -show_entries format -v quiet -of json`) - // Get id3 tags - for (let i in prds) { - let line = prds[i] - let parts = line.split(': ') - if (parts.length) { - let tagtype = parts[0].toLowerCase() - let tagdata = line.substring(parts[0].length + 2) + let parsed = JSON.parse(formatData.stdout) + if (!parsed || !parsed.format) throw new Error('Failed to parse metadata!') + parsed = parsed.format + + let data = { + duration: parseFloat(parsed.duration) + } - if (tagtype === '') continue - if (tagtype === 'metadata' && tagdata === 'none found') throw new Error(`No metadata for file "${file}"!`) + if (parsed.tags) { + for (let k in parsed.tags) { + let tagtype = k.toLowerCase() + let value = parsed.tags[k] + + if (tagtype === 'date') tagtype = 'year' if (tagtype === 'track' || tagtype === 'year') { - if (tagdata.indexOf('/') !== -1) { - tagdata = tagdata.split('/')[0] + if (value.indexOf('/') !== -1) { + value = value.split('/')[0] } - tagdata = parseInt(tagdata) + value = parseInt(value) } - data[tagtype] = tagdata + data[tagtype] = value } } @@ -113,11 +115,6 @@ async function getInfos (file) { data.title = parsed.name } - // Get track length - let len = await asn.promiseExec(`ffprobe -i "${file}" -show_entries format=duration -v quiet -of csv="p=0"`) - len = parseFloat(len.stdout) - data.duration = len - return data } diff --git a/src/dbpopulate.js b/src/dbpopulate.js index 43b6bc4..9bbbd94 100644 --- a/src/dbpopulate.js +++ b/src/dbpopulate.js @@ -51,28 +51,38 @@ async function interactive (fpath, db) { return track } +async function handlePassed (db, fpath) { + let filePath = path.resolve(fpath) + let trackinf + + try { + trackinf = await dl.getInfos(filePath) + } catch (e) { + trackinf = await interactive(filePath, db) + } + + if (!trackinf) { + throw new Error('Nothing to do.') + } + + let ins = await asn.insertDB(db, trackinf) + if (!ins) { + throw new Error('A track of this description already exists in the database.') + } +} + async function run () { let db = await dbPromise if (process.argv[2] != null) { - let filePath = path.resolve(process.argv[2]) - let trackinf - - try { - trackinf = await dl.getInfos(filePath) - } catch (e) { - trackinf = await interactive(filePath, db) - } - - if (!trackinf) { - console.error('Nothing to do.') - return - } - - let ins = await asn.insertDB(db, trackinf) - if (!ins) { - console.error('A track of this description already exists in the database.') - return + for (let i = 2; i < process.argv.length; i++) { + let f = process.argv[i] + console.log('=> Passing', f) + try { + await handlePassed(db, f) + } catch (e) { + console.error('==>', e.message) + } } console.log('=> Done.') diff --git a/src/server.js b/src/server.js index 3ffeac1..911e632 100644 --- a/src/server.js +++ b/src/server.js @@ -2,6 +2,7 @@ import path from 'path' import sqlite from 'sqlite' import Promise from 'bluebird' import express from 'express' +import sockets from './server/sockets' require('express-async-errors') @@ -17,6 +18,9 @@ const port = process.env.PORT || 3000 const router = express.Router() +const http = require('http').Server(app) +const io = require('socket.io')(http) + router.get('/tracks', async (req, res) => { let page = parseInt(req.query.page) || 1 if (isNaN(page)) { @@ -152,6 +156,7 @@ app.use('/api', router) app.use('/file/track', express.static(path.resolve(values.directory))) app.use('/', express.static(path.join(process.cwd(), 'public'))) -app.listen(port, '127.0.0.1', function () { +http.listen(port, '127.0.0.1', function () { + sockets.start(io) console.log(`app running on port ${port}`) }) diff --git a/src/server/sockets.js b/src/server/sockets.js new file mode 100644 index 0000000..2ca1ec1 --- /dev/null +++ b/src/server/sockets.js @@ -0,0 +1,141 @@ + +let data = { + clients: {}, + + // Key: Controller + // Value: Controlee + // Music is played on Value's device. + control: {} +} + +function getSocketByDevice (devid) { + for (let id in data.clients) { + let sock = data.clients[i] + if (sock.device && sock.device === devid) { + return sock + } + } + + return null +} + +function getControlling (sockid) { + for (let id in data.control) { + let cl = data.control[id] + if (cl === sockid) return id + } + + return null +} + +function start (io) { + io.on('connection', function (socket) { + let sockid = socket.id + + socket.on('auth', function (device) { + if (getSocketByDevice(device) != null) { + return socket.emit('cerr', {error: 'devexists', code: 0}) + } + + socket.device = device + data.clients[sockid] = socket + }) + + socket.on('req:devs', function () { + let devices = [] + + for (let i in data.clients) { + let cl = data.clients[i] + if (cl.device === socket.device) continue + devices.push(devices) + } + + socket.emit('res:devs', devices) + }) + + // Controller wants to take control of Controlee + socket.on('ctrl:connect', function (ctrlee) { + let dev = getSocketByDevice(ctrlee) + if (!dev) return socket.emit('cerr', {error: 'devnull', code: 1}) + + // Device is already controlling/being controlled + if (data.control[sockid] || getControlling(dev.id)) return socket.emit('cerr', {error: 'devctrl', code: 2}) + + dev.emit('ctrl:connect', { device: socket.device }) + socket.emit('targ:success', { device: ctrlee }) + }) + + // Disconnect + socket.on('ctrl:disconnect', function (ctrlee) { + let id, ctrl + if (getControlling(sockid)) { + // If this is a controlee, disconnect from controller + id = getControlling(sockid) + ctrl = data.clients[id] + + delete data.control[id] + } else if (data.control[sockid]) { + // If this is a controller, disconnect from controlee + id = data.control[sockid] + ctrl = data.clients[id] + + delete data.control[sockid] + } + + if (!ctrl) return + + ctrl.emit('targ:disconnect', {}) + }) + + let directpass = ['track', 'pause', 'seek', 'volume', 'mute', 'queue', 'req:queue', 'res:queue'] + for (let i in directpass) { + let syn = 'sync:' + directpass[i] + + // Set pause/play state + // Sent from controlee to controller + socket.on(syn, function (data) { + let ctrl + + if (getControlling(sockid)) { + // If this is a controlee, send to controller + ctrl = data.clients[getControlling(sockid)] + } else if (data.control[sockid]) { + // If this is a controller, send to controlee + ctrl = data.clients[data.control[sockid]] + } + + ctrl && ctrl.emit('set:' + directpass[i], data) + }) + } + + // Synchronize timestamp + socket.on('sync:time', function (data) { + if (!getControlling(sockid)) return + let ctrl = data.clients[getControlling(sockid)] + ctrl && ctrl.emit('sync:time', data) + }) + + socket.on('disconnect', function () { + if (data.clients[sockid]) { + let dev = data.clients[sockid] + + // Controller loses connection + // Emit to controlee + if (data.control[sockid]) { + let clt = data.clients[sockid] + clt && clt.emit('ctrl:disconnect', {}) + + // Controlee loses connection + // Emit to controller + } else if (getControlling(sockid)) { + let clt = data.clients[getControlling(sockid)] + clt && clt.emit('targ:disconnect', {}) + } + + delete data.clients[sockid] + } + }) + }) +} + +module.exports = { start }