@invintusmedia/tomp4 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tomp4.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * toMp4.js v1.0.7
2
+ * toMp4.js v1.0.8
3
3
  * Convert MPEG-TS and fMP4 to standard MP4
4
4
  * https://github.com/TVWIT/toMp4.js
5
5
  * MIT License
@@ -114,17 +114,21 @@
114
114
  // Clip audio to the REQUESTED time range (not from keyframe)
115
115
  // Audio doesn't need keyframe pre-roll
116
116
  const audioStartPts = startPts;
117
- const audioEndPts = Math.min(endPts, lastFramePts);
117
+ const audioEndPts = Math.min(endPts, lastFramePts + 90000); // Include audio slightly past last video
118
118
  const clippedAudio = audioAUs.filter(au => au.pts >= audioStartPts && au.pts < audioEndPts);
119
119
 
120
- // Normalize all timestamps so keyframe starts at 0
120
+ // Normalize video timestamps so keyframe starts at 0
121
121
  const offset = keyframePts;
122
122
  for (const au of clippedVideo) {
123
123
  au.pts -= offset;
124
124
  au.dts -= offset;
125
125
  }
126
+
127
+ // Normalize audio timestamps so it starts at 0 (matching video playback start after preroll)
128
+ // Audio doesn't have preroll, so it should start at PTS 0 to sync with video after edit list
129
+ const audioOffset = audioStartPts; // Use requested start, not keyframe
126
130
  for (const au of clippedAudio) {
127
- au.pts -= offset;
131
+ au.pts -= audioOffset;
128
132
  }
129
133
 
130
134
  return {
@@ -752,7 +756,7 @@
752
756
  toMp4.isMpegTs = isMpegTs;
753
757
  toMp4.isFmp4 = isFmp4;
754
758
  toMp4.isStandardMp4 = isStandardMp4;
755
- toMp4.version = '1.0.7';
759
+ toMp4.version = '1.0.8';
756
760
 
757
761
  return toMp4;
758
762
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invintusmedia/tomp4",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Convert MPEG-TS, fMP4, and HLS streams to MP4 with clipping support - pure JavaScript, zero dependencies",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
package/src/index.js CHANGED
@@ -316,7 +316,7 @@ toMp4.transcode = transcode;
316
316
  toMp4.isWebCodecsSupported = isWebCodecsSupported;
317
317
 
318
318
  // Version (injected at build time for dist, read from package.json for ESM)
319
- toMp4.version = '1.0.7';
319
+ toMp4.version = '1.0.8';
320
320
 
321
321
  // Export
322
322
  export {
package/src/muxers/mp4.js CHANGED
@@ -513,15 +513,19 @@ export class MP4Muxer {
513
513
  if (this.parser.audioPts.length === 0) return null;
514
514
 
515
515
  const firstAudioPts = this.parser.audioPts[0];
516
+
517
+ // When clipping with preroll, audio is normalized to start at PTS 0
518
+ // (matching video playback start after edit list), so no edit list needed
516
519
  if (firstAudioPts === 0) return null;
517
520
 
521
+ // For non-clipped content, handle any timestamp offset
518
522
  const mediaTime = Math.round(firstAudioPts * this.audioTimescale / 90000);
519
- const duration = this.audioSampleSizes.length * this.audioSampleDuration;
523
+ const audioDuration = this.audioSampleSizes.length * this.audioSampleDuration;
520
524
 
521
525
  const elstData = new Uint8Array(16);
522
526
  const view = new DataView(elstData.buffer);
523
527
  view.setUint32(0, 1);
524
- view.setUint32(4, Math.round(duration * this.videoTimescale / this.audioTimescale));
528
+ view.setUint32(4, Math.round(audioDuration * this.videoTimescale / this.audioTimescale));
525
529
  view.setInt32(8, mediaTime);
526
530
  view.setUint16(12, 1);
527
531
  view.setUint16(14, 0);
@@ -534,8 +538,8 @@ export class MP4Muxer {
534
538
  const data = new Uint8Array(80);
535
539
  const view = new DataView(data.buffer);
536
540
  view.setUint32(8, 257);
537
- const audioDuration = this.audioSampleSizes.length * this.audioSampleDuration;
538
- view.setUint32(16, Math.round(audioDuration * this.videoTimescale / this.audioTimescale));
541
+ // Use playback duration to match video track (for proper sync with preroll)
542
+ view.setUint32(16, this.calculatePlaybackDuration());
539
543
  view.setUint16(32, 0x0100);
540
544
  view.setUint32(36, 0x00010000); view.setUint32(52, 0x00010000); view.setUint32(68, 0x40000000);
541
545
  return createFullBox('tkhd', 0, 3, data);
package/src/ts-to-mp4.js CHANGED
@@ -93,17 +93,21 @@ function clipAccessUnits(videoAUs, audioAUs, startTime, endTime) {
93
93
  // Clip audio to the REQUESTED time range (not from keyframe)
94
94
  // Audio doesn't need keyframe pre-roll
95
95
  const audioStartPts = startPts;
96
- const audioEndPts = Math.min(endPts, lastFramePts);
96
+ const audioEndPts = Math.min(endPts, lastFramePts + 90000); // Include audio slightly past last video
97
97
  const clippedAudio = audioAUs.filter(au => au.pts >= audioStartPts && au.pts < audioEndPts);
98
98
 
99
- // Normalize all timestamps so keyframe starts at 0
99
+ // Normalize video timestamps so keyframe starts at 0
100
100
  const offset = keyframePts;
101
101
  for (const au of clippedVideo) {
102
102
  au.pts -= offset;
103
103
  au.dts -= offset;
104
104
  }
105
+
106
+ // Normalize audio timestamps so it starts at 0 (matching video playback start after preroll)
107
+ // Audio doesn't have preroll, so it should start at PTS 0 to sync with video after edit list
108
+ const audioOffset = audioStartPts; // Use requested start, not keyframe
105
109
  for (const au of clippedAudio) {
106
- au.pts -= offset;
110
+ au.pts -= audioOffset;
107
111
  }
108
112
 
109
113
  return {