Skip to content

Commit

Permalink
Merge pull request #42 from gkozlenko/empty-edit-support
Browse files Browse the repository at this point in the history
Basic support of empty edits
  • Loading branch information
gkozlenko authored Apr 20, 2024
2 parents 7d5d3c9 + 3f2fb6a commit cbf7089
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 74 deletions.
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

0 comments on commit cbf7089

Please sign in to comment.