100 lines
2.7 KiB
JavaScript
100 lines
2.7 KiB
JavaScript
import { spawn } from 'child_process'
|
|
import createDebug from 'debug'
|
|
import { EventEmitter } from 'events'
|
|
import process from 'process'
|
|
|
|
const debug = createDebug('meter')
|
|
|
|
export class AlsaMeter extends EventEmitter {
|
|
constructor (opts = {}) {
|
|
super()
|
|
if (!opts.input) this.input = { host: 'alsa', device: 'default' }
|
|
else if (typeof opts.input === 'string') this.input = { host: 'alsa', input: opts.input }
|
|
else this.input = opts.input
|
|
// setInterval(() => console.log(this._lastMessage), 1000)
|
|
}
|
|
|
|
open () {
|
|
if (!this._opening) this._opening = this._open()
|
|
return this._opening
|
|
}
|
|
|
|
async _open () {
|
|
const cmd = 'ffmpeg'
|
|
let args = [
|
|
'-nostats',
|
|
'-f',
|
|
this.input.host,
|
|
'-i',
|
|
this.input.device,
|
|
'-filter_complex',
|
|
'ebur128=peak=true',
|
|
'-f',
|
|
'null',
|
|
'-'
|
|
]
|
|
debug('spawn: ' + `${cmd} ${args.map(a => `"${a}"`).join(' ')}`)
|
|
this.proc = spawn(cmd, args)
|
|
|
|
// const onclose = (ev) => {
|
|
// this._closed = true
|
|
// this.proc.kill()
|
|
// }
|
|
// process.on('exit', onclose.bind('exit'))
|
|
// process.on('SIGINT', onclose.bind('SIGINT'))
|
|
// process.on('SIGTERM', onclose.bind('SIGTERM'))
|
|
|
|
this.proc.stderr.on('data', msg => {
|
|
this.parseMessage(msg)
|
|
})
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.proc.stderr.once('data', msg => {
|
|
this._open = true
|
|
resolve()
|
|
})
|
|
})
|
|
}
|
|
|
|
parseMessage (msg) {
|
|
let str = msg.toString('utf-8')
|
|
let match = str.match(/\[.*\](.*)/)
|
|
if (!match || match.length !== 2) return null
|
|
str = match[1].trim()
|
|
let parts = str.split(/([a-zA-Z]+:)/).filter(f => f)
|
|
let last = null
|
|
let map = parts.reduce((agg, token, i) => {
|
|
token = token.trim()
|
|
if (last) {
|
|
agg[last] = token
|
|
last = null
|
|
} else {
|
|
last = token.replace(/[:\s]/g, '')
|
|
}
|
|
return agg
|
|
}, {})
|
|
|
|
let res = {}
|
|
if (map.LRA) res.LRA = parseFloat(map.LRA.replace('LU', ''))
|
|
if (map.t) res.t = parseFloat(map.t)
|
|
if (map.TARGET) res.TARGET = parseFloat(map.TARGET.replace('LUFS', ''))
|
|
if (map.I) res.I = parseFloat(map.I.replace('LUFS', ''))
|
|
if (map.M) res.M = parseFloat(map.M)
|
|
if (map.S) res.S = parseFloat(map.S)
|
|
for (let key of ['FTPK', 'TPK']) {
|
|
if (map[key]) {
|
|
let parts = map[key].split(/\s+/)
|
|
if (parts.length >= 2) {
|
|
res[key] = [parseFloat(parts[0]), parseFloat(parts[1])]
|
|
} else {
|
|
res[key] = null
|
|
}
|
|
}
|
|
}
|
|
if (Object.keys(res).length) {
|
|
// debug(Object.entries(res).map(([k, v]) => `${k}: ${v}`).join(' | '))
|
|
this._lastMessage = res
|
|
this.emit('meter', res)
|
|
}
|
|
}
|
|
}
|