gopro-hilight-extract
Version:
extract HiLight tags for GoPro video files
113 lines (94 loc) • 3.14 kB
JavaScript
const fs = require('fs');
const bento4 = require('bento4-installer');
const { spawn } = require('child_process');
const path = require('path');
const tmpDir = './';
function extract(inputPath, outPath) {
const atomPath = 'moov/udta/HMMT';
const process = spawn(bento4.mp4extract, ['--payload-only', atomPath, inputPath, outPath]);
return new Promise((resolve, reject) => {
process.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
process.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
process.on('close', (code) => {
if(code === 0) {
resolve();
return;
}
reject({code});
console.log(`child process exited with code ${code}`);
});
});
}
/**
*
* In particular, the tags are stored in a box with type HMMT in the User Data Box (udta) of the Movie Box (moov) of the MPEG-4 container.
* See ISO/IEC 14496-12 for details on these “boxes”.
* The HMMT box seems to be a non-standard (GoPro-specific) ISO/IEC 14496-12 box.
* Its data consists of one or more 32-bit integers. The first integer contains the number of available HiLight tags.
* All subsequent integers resemble an ordered list of HiLight tags. Each HiLight tag is represented as a millisecond value.
*
* Ref: https://superuser.com/questions/881661/how-where-does-a-gopro-camera-store-hilight-tags
* @param inputPath
* @return {Promise<{count: number, tags: number[]}>} tags is an array of millisecond
*/
async function parseHiLightTag(inputPath) {
const sizeOfInteger = 4;
const buf = Buffer.alloc(sizeOfInteger);
const fd = fs.openSync(inputPath, 'r');
// Data-frame: <4 byte of 'tag size'> | <4 byte array for tags>
// tag size
fs.readSync(fd, buf, 0, sizeOfInteger, null);
const count = buf.readUInt32BE(0);
// tag array
let number = 0;
const tags = [];
do {
buf.fill(0);
fs.readSync(fd, buf, 0, sizeOfInteger, null);
const number = buf.readUInt32BE(0);
if(number) {
tags.push(number);
}
} while (number !== 0);
// Check
if(tags.length !== count) {
console.log('Warning: tag siz is not coincide with meta data')
}
fs.closeSync(fd);
return {count, tags}
}
function clearTmp(outPath) {
if(fs.existsSync(outPath)) {
fs.unlinkSync(outPath);
}
}
/**
*
* @param {string} filePath
* @return {Promise<{count: number, tags: number[]}>} tags is an array of millisecond
*/
async function parse(filePath) {
const outPath = path.join(tmpDir, path.basename(filePath) + '_' + new Date().getTime() + '.bin');
const result = {
count: 0,
tags: [],
};
try {
await extract(filePath, outPath);
const {count, tags} = await parseHiLightTag(outPath);
result.count = count;
result.tags = tags;
} catch (e) {
clearTmp(outPath);
return Promise.reject(e);
}
clearTmp(outPath);
return result;
}
module.exports = {
parse,
};