mp4box.appendBuffer(ab) returns offset that make an infinite loop
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 32/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Stale
- Tech stack
- javascript, typescript
- Domain
- audio-video-rtc
Research direction
Start from MP4Box.appendBuffer and the sample-extraction path it drives (setExtractionOptions/onSamples/start/stop), since the returned fileStart stops advancing and the read loop ping-pongs between the same two offsets. The 'Invalid box type' warnings logged by BoxParser around the 2 MB mark point at malformed or misaligned box parsing near that chunk boundary, so trace how a partial box at the end of a chunk is buffered and how the next fileStart is computed. Done looks like appendBuffer returning a strictly advancing offset (or the caller reaching flush at EOF) on the reporter's 383 MB file; the payload does not include a failing test or the sample video, so one must be obtained from the reporter first.
Written by the indexing model from the issue text.
Description
I try to ge the timecode from the sample of a video.
When it works, the appendBuffer(ab) returns the end of file and the readNextChunk() stop.
But on some video, it goes in infinite loop even if it correctly called onSamples.
With a 383mb m4v video, the process never exit. If you want the video to debug, tell me.
Output:
node index.js ./cam.m4v
appended 0 1049600
[0:00:00.012] [BoxParser] Invalid box type: ''
[0:00:00.012] [BoxParser] Invalid box type: '*d'
appended 1049600 2099200
{
"fps": 25
}
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
appended 3148800 2099200
appended 2099200 3148800
... NEVER STOP ...
Here is the code I use:
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const MP4Box = require('mp4box');
const getMp4boxMetadata = async file => {
const mp4box = MP4Box.createFile();
const { promise, resolve } = Promise.withResolvers();
mp4box.onError = function (err) {
error('mp4boxMetadata: failed', JSON.stringify({ file, err }));
resolve({});
};
mp4box.onReady = function (information) {
const videoTrack = information.tracks.find(t => t.type === 'video');
let fps;
if (videoTrack && videoTrack.samples_duration) {
fps = videoTrack.timescale * videoTrack.nb_samples / videoTrack.samples_duration;
}
const tcTrack = information.tracks.find(t => t.type === 'tmcd' || t.codec === 'tmcd' || t.name?.includes('timecode'));
if (!tcTrack) {
resolve({ fps });
return;
}
const handle = setTimeout(() => {
error('No onSamples found', JSON.stringify({ file }));
resolve({ fps });
}, 2000);
mp4box.onSamples = function (trackId, user, samples) {
clearTimeout(handle);
if (!samples.length) {
mp4box.stop();
resolve({ fps });
return;
}
const sample = samples[0];
const dataView = new DataView(sample.data.buffer, sample.data.byteOffset, sample.data.byteLength);
const totalFrames = dataView.getUint32(0, false); // Big-endian
const timecode = totalFrames / fps;
const hours = Math.floor(timecode / 3600);
const minutes = Math.floor((timecode % 3600) / 60);
const seconds = Math.floor(timecode % 60);
const frameCount = Math.floor((timecode * fps) % fps);
mp4box.stop();
resolve({ fps, timecode, hours, minutes, seconds, frames: frameCount });
};
mp4box.setExtractionOptions(tcTrack.id, null, { nbSamples: 1 });
mp4box.start();
};
// Load file chunk by chunk
let offset = 0;
const chunkSize = 1025 * 1024;
const fd = fs.openSync(file, 'r');
const stats = fs.statSync(file);
const readNextChunk = () => {
if (offset >= stats.size) {
console.log('flushing');
mp4box.flush();
fs.closeSync(fd);
return;
}
const buffer = Buffer.alloc(chunkSize);
const bytesRead = fs.readSync(fd, buffer, 0, chunkSize, offset);
// The data property needs an ArrayBuffer and fileStart initialized
const ab = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + bytesRead);
ab.fileStart = offset;
let newOffset = mp4box.appendBuffer(ab);
console.log('appended', offset, newOffset);
if (newOffset === offset) {
console.error('mp4boxMetadata: stuck, newOffset === offset, should not happen', JSON.stringify({ offset, newOffset }));
newOffset = offset + chunkSize;
}
offset = newOffset;
// Instead of while loop, use setImmediate to not block the event loop and allow callbacks
setImmediate(readNextChunk);
};
readNextChunk();
return promise;
};
const main = async () => {
const filePath = process.argv[2];
if (!filePath) {
error('Usage: node index.js <path-to-video-file>');
process.exit(1);
}
const resolvedPath = path.resolve(filePath);
if (!fs.existsSync(resolvedPath)) {
error(`File not found: ${resolvedPath}`);
process.exit(1);
}
try {
const result = await getMp4boxMetadata(resolvedPath);
console.log(JSON.stringify(result, null, 2));
} catch (err) {
console.error('An error occurred:', err);
process.exit(1);
}
};
main();
- Dominant language
- TypeScript
- Stars
- 2.5k
- Forks
- 390
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 8
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from gpac/mp4box.js
-
mp4box.js — 20-byte `sgpd` box with large `entry_count` causes unbounded loop and heap OOM abortOpen
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 45/100
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 48/100
gpac/mp4box.js#528 · 2 comments ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 40/100
Maintainers usually reply within 1 day
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 45/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 1/5 1-3 hours Newbie friendliness 88/100
danielw-sudo/agentuto#63 ·
Maintainers usually reply within 1 day
-
idea-scout idea:341
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
jeanibarz/agent-notebook#341 ·
Maintainers usually reply within 1 day
-
documentation good first issue roadmap
Difficulty 1/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
nightgauge/nightgauge#3077 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
LibreChat-AI/LibreChat#17915 ·
Maintainers usually reply within 1 day