Simple script to parse ffprobe data with custom real-time markers
const readline = require('readline'); | |
const fs = require('fs'); | |
const argv = process.argv.slice(2); | |
if (argv.length !== 1) { | |
console.error('Invalid list of arguments'); | |
process.exit(1); | |
} | |
const filename = argv[0]; | |
const lineReader = readline.createInterface({ | |
input: fs.createReadStream(filename) | |
}); | |
const FRAME_START_MARKER = '[FRAME]'; | |
const FRAME_END_MARKER = '[/FRAME]'; | |
const REAL_TIME_START_MARKER = '---'; | |
const REAL_TIME_END_MARKER = '--- '; | |
const AGGREGATION_TIME_SEC = 5; | |
function cutRealTime(line) { | |
let trimmedLine = line; | |
let startOfRealTime; | |
let lastRealTime; | |
while ((startOfRealTime = trimmedLine.indexOf(REAL_TIME_START_MARKER)) >= 0) { | |
endOfRealTime = trimmedLine.indexOf('--- ', startOfRealTime + 3); | |
lastRealTime = trimmedLine.substr( | |
startOfRealTime + REAL_TIME_START_MARKER.length, | |
endOfRealTime - REAL_TIME_START_MARKER.length | |
); | |
trimmedLine = trimmedLine.substr(endOfRealTime + REAL_TIME_END_MARKER.length); | |
} | |
return [trimmedLine, lastRealTime]; | |
} | |
let isFrameStarted = false; | |
let frame; | |
let lineNumber = 0; | |
let lastRealTime; | |
let endOfRealTime; | |
let prevCodedPictureNumber = -1; | |
let buffers = { | |
pkt_dts_time: {}, | |
real_time: {}, | |
}; | |
lineReader.on('line', line => { | |
lineNumber++; | |
[line, realTime] = cutRealTime(line); | |
if (realTime) lastRealTime = realTime; | |
if (line === FRAME_START_MARKER) { | |
if (isFrameStarted) { | |
process.stderr.write(`Intersection of ${FRAME_START_MARKER} blocks was found on line ${lineNumber}\n`); | |
process.exit(1); | |
} | |
frame = {}; | |
isFrameStarted = true; | |
} else if (line === FRAME_END_MARKER) { | |
if (!isFrameStarted) { | |
process.stderr.write(`${FRAME_END_MARKER} was found before ${FRAME_START_MARKER} at line ${lineNumber}\n`); | |
process.exit(1); | |
} | |
isFrameStarted = false; | |
if (frame['media_type'] === 'video') { | |
if (!lastRealTime) { | |
return; | |
} | |
const realTimeFloored = Math.floor(Date.parse(lastRealTime) / 1000); | |
const realTimeNormalized = realTimeFloored - (realTimeFloored % AGGREGATION_TIME_SEC); | |
if (!buffers.real_time[realTimeNormalized]) { | |
buffers.real_time[realTimeNormalized] = 1; | |
} else { | |
buffers.real_time[realTimeNormalized]++; | |
} | |
const flooredpktDtsTime = Math.floor(parseFloat(frame['pkt_dts_time'])); | |
const pktDtsTimeNormalized = flooredpktDtsTime - (flooredpktDtsTime % AGGREGATION_TIME_SEC); | |
if (!buffers.pkt_dts_time[pktDtsTimeNormalized]) { | |
buffers.pkt_dts_time[pktDtsTimeNormalized] = 1; | |
} else { | |
buffers.pkt_dts_time[pktDtsTimeNormalized]++; | |
} | |
const codedPictureNumber = parseInt(frame['coded_picture_number']); | |
if (prevCodedPictureNumber >= 0) { | |
if (codedPictureNumber > prevCodedPictureNumber + 1) { | |
process.stderr.write(`coded_picture_number: ${prevCodedPictureNumber} -> ${codedPictureNumber}`); | |
} | |
} | |
prevCodedPictureNumber = codedPictureNumber; | |
} | |
} else { | |
if (isFrameStarted) { | |
const keyAndValue = line.split('='); | |
frame[keyAndValue[0]] = keyAndValue[1]; | |
} | |
} | |
}); | |
lineReader.on('close', () => { | |
const realTimeKeys = Object.keys(buffers.real_time); | |
const realTimeStart = parseInt(realTimeKeys[0]); | |
const realTimeEnd = parseInt(realTimeKeys[realTimeKeys.length - 1]); | |
for (let time = realTimeStart; time < realTimeEnd; time += AGGREGATION_TIME_SEC) { | |
let realTimeVal = (buffers.real_time[time] !== undefined) ? buffers.real_time[time] : 0; | |
let normalizedPktDtsTime = time - realTimeStart; | |
let pktDtsTimeVal = (buffers.pkt_dts_time[normalizedPktDtsTime] !== undefined) ? | |
buffers.pkt_dts_time[normalizedPktDtsTime] : 0; | |
process.stdout.write(`${time} ${realTimeVal} ${pktDtsTimeVal}\n`); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment