Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic support of empty edits #42

Merged
merged 1 commit into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 65 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,16 @@ $ npm install node-video-lib
const fs = require('fs');
const VideoLib = require('node-video-lib');

fs.open('/path/to/file', 'r', function(err, fd) {
try {
let movie = VideoLib.MovieParser.parse(fd);
// Work with movie
console.log('Duration:', movie.relativeDuration());
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
}
});
let fd = fs.openSync('/path/to/file', 'r');
try {
let movie = VideoLib.MovieParser.parse(fd);
// Work with movie
console.log('Duration:', movie.relativeDuration());
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
}
```

### Create MPEG-TS chunks
Expand All @@ -44,22 +43,21 @@ fs.open('/path/to/file', 'r', function(err, fd) {
const fs = require('fs');
const VideoLib = require('node-video-lib');

fs.open('/path/to/file', 'r', function(err, fd) {
try {
let movie = VideoLib.MovieParser.parse(fd);
let fragmentList = VideoLib.FragmentListBuilder.build(movie, 5);
for (let i = 0; i < fragmentList.count(); i++) {
let fragment = fragmentList.get(i);
let sampleBuffers = VideoLib.FragmentReader.readSamples(fragment, fd);
let buffer = VideoLib.HLSPacketizer.packetize(fragment, sampleBuffers);
// Now buffer contains MPEG-TS chunk
}
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
let fd = fs.openSync('/path/to/file', 'r');
try {
let movie = VideoLib.MovieParser.parse(fd);
let fragmentList = VideoLib.FragmentListBuilder.build(movie, 5);
for (let i = 0; i < fragmentList.count(); i++) {
let fragment = fragmentList.get(i);
let sampleBuffers = VideoLib.FragmentReader.readSamples(fragment, fd);
let buffer = VideoLib.HLSPacketizer.packetize(fragment, sampleBuffers);
// Now buffer contains MPEG-TS chunk
}
});
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
}
```

### Build MP4 file
Expand All @@ -68,24 +66,22 @@ fs.open('/path/to/file', 'r', function(err, fd) {
const fs = require('fs');
const VideoLib = require('node-video-lib');

fs.open('/path/to/file', 'r', function(err, fd) {
let fd = fs.openSync('/path/to/file', 'r');
try {
let movie = VideoLib.MovieParser.parse(fd);
let fw = fs.openSync('/path/to/output.mp4', 'w');
try {
let movie = VideoLib.MovieParser.parse(fd);
fs.open('/path/to/output.mp4', 'w', function(err, fw) {
try {
VideoLib.MP4Builder.build(movie, fd, fw);
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fw);
}
}
VideoLib.MP4Builder.build(movie, fd, fw);
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
fs.closeSync(fw);
}
});
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
}
```

### Create index file
Expand All @@ -94,26 +90,24 @@ fs.open('/path/to/file', 'r', function(err, fd) {
const fs = require('fs');
const VideoLib = require('node-video-lib');

fs.open('/path/to/file', 'r', function(err, fd) {
let fd = fs.openSync('/path/to/file', 'r');
try {
let movie = VideoLib.MovieParser.parse(fd);
let fragmentList = VideoLib.FragmentListBuilder.build(movie, 5);
console.log('Duration:', fragmentList.relativeDuration());
let fdi = fs.openSync('/path/to/index.idx', 'w');
try {
let movie = VideoLib.MovieParser.parse(fd);
let fragmentList = VideoLib.FragmentListBuilder.build(movie, 5);
console.log('Duration:', fragmentList.relativeDuration());
fs.open('/path/to/index.idx', 'w', function(err, fdi) {
try {
VideoLib.FragmentListIndexer.index(fragmentList, fdi);
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fdi);
}
});
VideoLib.FragmentListIndexer.index(fragmentList, fdi);
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
fs.closeSync(fdi);
}
});
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
}
```

### Create MPEG-TS chunks using index file
Expand All @@ -122,25 +116,23 @@ fs.open('/path/to/file', 'r', function(err, fd) {
const fs = require('fs');
const VideoLib = require('node-video-lib');

fs.open('/path/to/file', 'r', function(err, fd) {
fs.open('/path/to/index.idx', 'r', function(err, fdi) {
try {
let fragmentList = VideoLib.FragmentListIndexer.read(fdi);
console.log('Duration:', fragmentList.relativeDuration());
for (let i = 0; i < fragmentList.count(); i++) {
let fragment = fragmentList.get(i);
let sampleBuffers = VideoLib.FragmentReader.readSamples(fragment, fd);
let buffer = VideoLib.HLSPacketizer.packetize(fragment, sampleBuffers);
// Now buffer contains MPEG-TS chunk
}
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
fs.closeSync(fdi);
}
});
});
let fd = fs.openSync('/path/to/file', 'r');
let fdi = fs.openSync('/path/to/index.idx', 'r');
try {
let fragmentList = VideoLib.FragmentListIndexer.read(fdi);
console.log('Duration:', fragmentList.relativeDuration());
for (let i = 0; i < fragmentList.count(); i++) {
let fragment = fragmentList.get(i);
let sampleBuffers = VideoLib.FragmentReader.readSamples(fragment, fd);
let buffer = VideoLib.HLSPacketizer.packetize(fragment, sampleBuffers);
// Now buffer contains MPEG-TS chunk
}
} catch (ex) {
console.error('Error:', ex);
} finally {
fs.closeSync(fd);
fs.closeSync(fdi);
}
```

## Classes
Expand Down
22 changes: 22 additions & 0 deletions lib/mp4/atoms/atom-edts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const ContainerAtom = require('../container-atom');
const Utils = require('../utils');

const ATOM_CLASSES = {
elst: require('./atom-elst'),
};

class AtomEDTS extends ContainerAtom {

type() {
return Utils.ATOM_EDTS;
}

availableAtomClasses() {
return ATOM_CLASSES;
}

}

module.exports = AtomEDTS;
18 changes: 18 additions & 0 deletions lib/mp4/atoms/atom-elst.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const SampleTableAtom = require('../sample-table-atom');
const Utils = require('../utils');

class AtomELST extends SampleTableAtom {

type() {
return Utils.ATOM_ELST;
}

countMultiplier() {
return 3;
}

}

module.exports = AtomELST;
1 change: 1 addition & 0 deletions lib/mp4/atoms/atom-trak.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const Utils = require('../utils');
const ATOM_CLASSES = {
tkhd: require('./atom-tkhd'),
mdia: require('./atom-mdia'),
edts: require('./atom-edts'),
};

class AtomTRAK extends ContainerAtom {
Expand Down
10 changes: 10 additions & 0 deletions lib/mp4/builder-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ class BuilderImpl {
minfAtom.createAtom(Utils.ATOM_SMHD);
}

// Edits
let edtsAtom = trakAtom.createAtom(Utils.ATOM_EDTS);
let elstAtom = edtsAtom.createAtom(Utils.ATOM_ELST);
elstAtom.entries = [track.duration * this.movie.timescale / track.timescale, 0, 1 << 16];
if (track.samples.length > 0 && track.samples[0].timestamp > 0) {
// add an empty edit
let duration = track.samples[0].timestamp * this.movie.timescale / track.timescale;
elstAtom.entries.unshift(duration, -1, 1 << 16);
}

// Samples table
let stblAtom = minfAtom.createAtom(Utils.ATOM_STBL);

Expand Down
12 changes: 12 additions & 0 deletions lib/mp4/parser-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class ParserImpl {
if (mvhdAtom) {
this.movie.timescale = mvhdAtom.timescale;
this.movie.duration = mvhdAtom.duration;
} else {
throw new Error('MVHD atom not found');
}
}

Expand Down Expand Up @@ -169,6 +171,16 @@ class ParserImpl {
samplesPerChunk = samplesToChunk[1];
}

// check edit list table
let edtsAtom = trakAtom.getAtom(Utils.ATOM_EDTS);
if (edtsAtom !== null) {
let editEntries = ParserImpl._getEntries(edtsAtom, Utils.ATOM_ELST);
if (editEntries.length >= 3 && editEntries[1] === -1) {
// apply the first empty edit
currentTimestamp = editEntries[0] * track.timescale / this.movie.timescale;
}
}

// Build samples
let samples = new Array(sampleSizes.length);
let pos = 0;
Expand Down
11 changes: 10 additions & 1 deletion lib/mp4/sample-table-atom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const Atom = require('./atom');

const UINT_MINUS_ONE = -1 >>> 0;

class SampleTableAtom extends Atom {

constructor() {
Expand All @@ -19,6 +21,9 @@ class SampleTableAtom extends Atom {
this.entries = new Array(entryCount * this.countMultiplier());
for (let i = 0, l = this.entries.length; i < l; i++) {
this.entries[i] = buffer.readUInt32BE(8 + 4 * i);
if (this.entries[i] === UINT_MINUS_ONE) {
this.entries[i] = -1;
}
}
}

Expand All @@ -30,7 +35,11 @@ class SampleTableAtom extends Atom {
buffer.writeUInt32BE((this.entries.length / this.countMultiplier()) << 0, offset + 12);
// entries
for (let i = 0, l = this.entries.length; i < l; i++) {
buffer.writeUInt32BE(this.entries[i], offset + 16 + 4 * i);
let val = this.entries[i];
if (val === -1) {
val = UINT_MINUS_ONE;
}
buffer.writeUInt32BE(val, offset + 16 + 4 * i);
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/mp4/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ module.exports = {
ATOM_ESDS: 'esds',
ATOM_MDAT: 'mdat',
ATOM_FTYP: 'ftyp',
ATOM_EDTS: 'edts',
ATOM_ELST: 'elst',

TRACK_TYPE_VIDEO: 'vide',
TRACK_TYPE_AUDIO: 'soun',
Expand Down
Loading