use ffprobe for tags

This commit is contained in:
Evert Prants 2018-11-07 18:28:08 +02:00
parent c00c18d29f
commit 2b39800228
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
6 changed files with 197 additions and 42 deletions

View File

@ -11,12 +11,14 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"babel-plugin-transform-runtime": "^6.23.0",
"bluebird": "^3.5.2", "bluebird": "^3.5.2",
"express": "^4.16.3", "express": "^4.16.3",
"express-async-errors": "^3.0.0", "express-async-errors": "^3.0.0",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"socket.io": "^2.1.1",
"sqlite": "^3.0.0", "sqlite": "^3.0.0",
"babel-plugin-transform-runtime": "^6.23.0" "sqlite3": "^4.0.3"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.3", "babel-core": "^6.26.3",

View File

@ -42,7 +42,7 @@ async function insertDB (db, track) {
} }
await db.run('INSERT INTO Track VALUES (NULL,?,?,?,?,?,?,?,?)', 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)]) track.year || null, Math.floor(track.duration)])
return track return track

View File

@ -82,29 +82,31 @@ function fetchVideo (data) {
} }
async function getInfos (file) { async function getInfos (file) {
let id3 = await asn.promiseExec(`id3 "${file}"`) let formatData = await asn.promiseExec(`ffprobe -i "${file}" -show_entries format -v quiet -of json`)
let prds = id3.stdout.split('\n')
let data = {}
// Get id3 tags let parsed = JSON.parse(formatData.stdout)
for (let i in prds) { if (!parsed || !parsed.format) throw new Error('Failed to parse metadata!')
let line = prds[i] parsed = parsed.format
let parts = line.split(': ')
if (parts.length) {
let tagtype = parts[0].toLowerCase()
let tagdata = line.substring(parts[0].length + 2)
if (tagtype === '') continue let data = {
if (tagtype === 'metadata' && tagdata === 'none found') throw new Error(`No metadata for file "${file}"!`) duration: parseFloat(parsed.duration)
}
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 (tagtype === 'track' || tagtype === 'year') {
if (tagdata.indexOf('/') !== -1) { if (value.indexOf('/') !== -1) {
tagdata = tagdata.split('/')[0] 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 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 return data
} }

View File

@ -51,28 +51,38 @@ async function interactive (fpath, db) {
return track 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 () { async function run () {
let db = await dbPromise let db = await dbPromise
if (process.argv[2] != null) { if (process.argv[2] != null) {
let filePath = path.resolve(process.argv[2]) for (let i = 2; i < process.argv.length; i++) {
let trackinf let f = process.argv[i]
console.log('=> Passing', f)
try { try {
trackinf = await dl.getInfos(filePath) await handlePassed(db, f)
} catch (e) { } catch (e) {
trackinf = await interactive(filePath, db) console.error('==>', e.message)
} }
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
} }
console.log('=> Done.') console.log('=> Done.')

View File

@ -2,6 +2,7 @@ import path from 'path'
import sqlite from 'sqlite' import sqlite from 'sqlite'
import Promise from 'bluebird' import Promise from 'bluebird'
import express from 'express' import express from 'express'
import sockets from './server/sockets'
require('express-async-errors') require('express-async-errors')
@ -17,6 +18,9 @@ const port = process.env.PORT || 3000
const router = express.Router() const router = express.Router()
const http = require('http').Server(app)
const io = require('socket.io')(http)
router.get('/tracks', async (req, res) => { router.get('/tracks', async (req, res) => {
let page = parseInt(req.query.page) || 1 let page = parseInt(req.query.page) || 1
if (isNaN(page)) { if (isNaN(page)) {
@ -152,6 +156,7 @@ app.use('/api', router)
app.use('/file/track', express.static(path.resolve(values.directory))) app.use('/file/track', express.static(path.resolve(values.directory)))
app.use('/', express.static(path.join(process.cwd(), 'public'))) 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}`) console.log(`app running on port ${port}`)
}) })

141
src/server/sockets.js Normal file
View File

@ -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 }