@invintusmedia/tomp4 1.0.4 → 1.0.5
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/README.md +16 -0
- package/dist/tomp4.js +12 -6
- package/package.json +1 -1
- package/src/hls.js +10 -4
- package/src/index.d.ts +13 -2
- package/src/index.js +1 -1
- package/src/ts-to-mp4.js +10 -4
package/README.md
CHANGED
|
@@ -66,6 +66,22 @@ info.videoCodec // "H.264/AVC"
|
|
|
66
66
|
info.audioCodec // "AAC"
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
### progress callback
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
const mp4 = await toMp4(url, {
|
|
73
|
+
onProgress: (msg, info) => {
|
|
74
|
+
if (info?.percent !== undefined) {
|
|
75
|
+
console.log(`${info.percent}% - ${msg}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
// 10% - Downloading: 10%
|
|
80
|
+
// 50% - Downloaded 5.2 MB
|
|
81
|
+
// 60% - Frames: 300 video, 450 audio
|
|
82
|
+
// 100% - Complete
|
|
83
|
+
```
|
|
84
|
+
|
|
69
85
|
### use the result
|
|
70
86
|
|
|
71
87
|
```js
|
package/dist/tomp4.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* toMp4.js v1.0.
|
|
2
|
+
* toMp4.js v1.0.5
|
|
3
3
|
* Convert MPEG-TS and fMP4 to standard MP4
|
|
4
4
|
* https://github.com/TVWIT/toMp4.js
|
|
5
5
|
* MIT License
|
|
@@ -1214,6 +1214,7 @@
|
|
|
1214
1214
|
function convertTsToMp4(tsData, options = {}) {
|
|
1215
1215
|
const log = options.onProgress || (() => {});
|
|
1216
1216
|
|
|
1217
|
+
log(`Parsing...`, { phase: 'convert', percent: 52 });
|
|
1217
1218
|
const parser = new TSParser();
|
|
1218
1219
|
parser.parse(tsData);
|
|
1219
1220
|
parser.finalize();
|
|
@@ -1223,7 +1224,7 @@
|
|
|
1223
1224
|
const audioInfo = getCodecInfo(parser.audioStreamType);
|
|
1224
1225
|
|
|
1225
1226
|
// Log parsing results
|
|
1226
|
-
log(`Parsed ${debug.packets} TS packets
|
|
1227
|
+
log(`Parsed ${debug.packets} TS packets`, { phase: 'convert', percent: 55 });
|
|
1227
1228
|
log(`PAT: ${debug.patFound ? '✓' : '✗'}, PMT: ${debug.pmtFound ? '✓' : '✗'}`);
|
|
1228
1229
|
log(`Video: ${parser.videoPid ? `PID ${parser.videoPid}` : 'none'} → ${videoInfo.name}`);
|
|
1229
1230
|
const audioDetails = [];
|
|
@@ -1266,7 +1267,7 @@
|
|
|
1266
1267
|
);
|
|
1267
1268
|
}
|
|
1268
1269
|
|
|
1269
|
-
log(`Frames: ${parser.videoAccessUnits.length} video, ${parser.audioAccessUnits.length} audio
|
|
1270
|
+
log(`Frames: ${parser.videoAccessUnits.length} video, ${parser.audioAccessUnits.length} audio`, { phase: 'convert', percent: 60 });
|
|
1270
1271
|
if (debug.audioPesStarts) {
|
|
1271
1272
|
log(`Audio: ${debug.audioPesStarts} PES starts → ${debug.audioPesCount || 0} processed → ${debug.audioFramesInPes || 0} ADTS frames${debug.audioSkipped ? ` (${debug.audioSkipped} skipped)` : ''}`);
|
|
1272
1273
|
}
|
|
@@ -1281,6 +1282,8 @@
|
|
|
1281
1282
|
log(`Timestamps normalized: -${offsetMs}ms offset`);
|
|
1282
1283
|
}
|
|
1283
1284
|
|
|
1285
|
+
log(`Processing...`, { phase: 'convert', percent: 70 });
|
|
1286
|
+
|
|
1284
1287
|
// Apply time range clipping if specified
|
|
1285
1288
|
if (options.startTime !== undefined || options.endTime !== undefined) {
|
|
1286
1289
|
const startTime = options.startTime || 0;
|
|
@@ -1301,14 +1304,17 @@
|
|
|
1301
1304
|
parser.videoDts = clipResult.video.map(au => au.dts);
|
|
1302
1305
|
parser.audioPts = clipResult.audio.map(au => au.pts);
|
|
1303
1306
|
|
|
1304
|
-
log(`Clipped: ${clipResult.actualStartTime.toFixed(2)}s - ${clipResult.actualEndTime.toFixed(2)}s (${clipResult.video.length} video, ${clipResult.audio.length} audio frames)
|
|
1307
|
+
log(`Clipped: ${clipResult.actualStartTime.toFixed(2)}s - ${clipResult.actualEndTime.toFixed(2)}s (${clipResult.video.length} video, ${clipResult.audio.length} audio frames)`, { phase: 'convert', percent: 80 });
|
|
1305
1308
|
}
|
|
1306
1309
|
|
|
1310
|
+
log(`Building MP4...`, { phase: 'convert', percent: 85 });
|
|
1307
1311
|
const builder = new MP4Builder(parser);
|
|
1308
1312
|
const { width, height } = builder.getVideoDimensions();
|
|
1309
1313
|
log(`Dimensions: ${width}x${height}`);
|
|
1310
1314
|
|
|
1311
|
-
|
|
1315
|
+
const result = builder.build();
|
|
1316
|
+
log(`Complete`, { phase: 'convert', percent: 100 });
|
|
1317
|
+
return result;
|
|
1312
1318
|
}
|
|
1313
1319
|
|
|
1314
1320
|
default convertTsToMp4;
|
|
@@ -1751,7 +1757,7 @@
|
|
|
1751
1757
|
toMp4.isMpegTs = isMpegTs;
|
|
1752
1758
|
toMp4.isFmp4 = isFmp4;
|
|
1753
1759
|
toMp4.isStandardMp4 = isStandardMp4;
|
|
1754
|
-
toMp4.version = '1.0.
|
|
1760
|
+
toMp4.version = '1.0.5';
|
|
1755
1761
|
|
|
1756
1762
|
return toMp4;
|
|
1757
1763
|
});
|
package/package.json
CHANGED
package/src/hls.js
CHANGED
|
@@ -280,9 +280,11 @@ async function downloadHls(source, options = {}) {
|
|
|
280
280
|
toDownload = toDownload.slice(0, options.maxSegments);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
const totalSegments = toDownload.length;
|
|
284
|
+
log(`Downloading ${totalSegments} segment${totalSegments > 1 ? 's' : ''}...`);
|
|
284
285
|
|
|
285
|
-
// Download
|
|
286
|
+
// Download segments with progress tracking
|
|
287
|
+
let completedSegments = 0;
|
|
286
288
|
const buffers = await Promise.all(
|
|
287
289
|
toDownload.map(async (seg, i) => {
|
|
288
290
|
const url = seg.url || seg; // Handle both HlsSegment objects and plain URLs
|
|
@@ -290,7 +292,11 @@ async function downloadHls(source, options = {}) {
|
|
|
290
292
|
if (!resp.ok) {
|
|
291
293
|
throw new Error(`Segment ${i + 1} failed: ${resp.status}`);
|
|
292
294
|
}
|
|
293
|
-
|
|
295
|
+
const buffer = new Uint8Array(await resp.arrayBuffer());
|
|
296
|
+
completedSegments++;
|
|
297
|
+
const percent = Math.round((completedSegments / totalSegments) * 50); // Download is 0-50%
|
|
298
|
+
log(`Downloading: ${percent}%`, { phase: 'download', percent, segment: completedSegments, totalSegments });
|
|
299
|
+
return buffer;
|
|
294
300
|
})
|
|
295
301
|
);
|
|
296
302
|
|
|
@@ -303,7 +309,7 @@ async function downloadHls(source, options = {}) {
|
|
|
303
309
|
offset += buf.length;
|
|
304
310
|
}
|
|
305
311
|
|
|
306
|
-
log(`Downloaded ${(totalSize / 1024 / 1024).toFixed(2)} MB
|
|
312
|
+
log(`Downloaded ${(totalSize / 1024 / 1024).toFixed(2)} MB`, { phase: 'download', percent: 50 });
|
|
307
313
|
|
|
308
314
|
// Return with metadata for precise clipping
|
|
309
315
|
combined._hlsTimeRange = hasTimeRange ? {
|
package/src/index.d.ts
CHANGED
|
@@ -37,9 +37,20 @@ declare module '@invintusmedia/tomp4' {
|
|
|
37
37
|
segments: string[];
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export interface ProgressInfo {
|
|
41
|
+
/** Current phase: 'download' or 'convert' */
|
|
42
|
+
phase: 'download' | 'convert';
|
|
43
|
+
/** Progress percentage (0-100) */
|
|
44
|
+
percent: number;
|
|
45
|
+
/** Current segment (download phase only) */
|
|
46
|
+
segment?: number;
|
|
47
|
+
/** Total segments (download phase only) */
|
|
48
|
+
totalSegments?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
export interface ToMp4Options {
|
|
41
|
-
/** Progress callback */
|
|
42
|
-
onProgress?: (message: string) => void;
|
|
52
|
+
/** Progress callback - receives message string and optional progress info */
|
|
53
|
+
onProgress?: (message: string, info?: ProgressInfo) => void;
|
|
43
54
|
/** Suggested filename for downloads */
|
|
44
55
|
filename?: string;
|
|
45
56
|
/** HLS quality: 'highest', 'lowest', or bandwidth number */
|
package/src/index.js
CHANGED
package/src/ts-to-mp4.js
CHANGED
|
@@ -1193,6 +1193,7 @@ export function analyzeTsData(tsData) {
|
|
|
1193
1193
|
export function convertTsToMp4(tsData, options = {}) {
|
|
1194
1194
|
const log = options.onProgress || (() => {});
|
|
1195
1195
|
|
|
1196
|
+
log(`Parsing...`, { phase: 'convert', percent: 52 });
|
|
1196
1197
|
const parser = new TSParser();
|
|
1197
1198
|
parser.parse(tsData);
|
|
1198
1199
|
parser.finalize();
|
|
@@ -1202,7 +1203,7 @@ export function convertTsToMp4(tsData, options = {}) {
|
|
|
1202
1203
|
const audioInfo = getCodecInfo(parser.audioStreamType);
|
|
1203
1204
|
|
|
1204
1205
|
// Log parsing results
|
|
1205
|
-
log(`Parsed ${debug.packets} TS packets
|
|
1206
|
+
log(`Parsed ${debug.packets} TS packets`, { phase: 'convert', percent: 55 });
|
|
1206
1207
|
log(`PAT: ${debug.patFound ? '✓' : '✗'}, PMT: ${debug.pmtFound ? '✓' : '✗'}`);
|
|
1207
1208
|
log(`Video: ${parser.videoPid ? `PID ${parser.videoPid}` : 'none'} → ${videoInfo.name}`);
|
|
1208
1209
|
const audioDetails = [];
|
|
@@ -1245,7 +1246,7 @@ export function convertTsToMp4(tsData, options = {}) {
|
|
|
1245
1246
|
);
|
|
1246
1247
|
}
|
|
1247
1248
|
|
|
1248
|
-
log(`Frames: ${parser.videoAccessUnits.length} video, ${parser.audioAccessUnits.length} audio
|
|
1249
|
+
log(`Frames: ${parser.videoAccessUnits.length} video, ${parser.audioAccessUnits.length} audio`, { phase: 'convert', percent: 60 });
|
|
1249
1250
|
if (debug.audioPesStarts) {
|
|
1250
1251
|
log(`Audio: ${debug.audioPesStarts} PES starts → ${debug.audioPesCount || 0} processed → ${debug.audioFramesInPes || 0} ADTS frames${debug.audioSkipped ? ` (${debug.audioSkipped} skipped)` : ''}`);
|
|
1251
1252
|
}
|
|
@@ -1260,6 +1261,8 @@ export function convertTsToMp4(tsData, options = {}) {
|
|
|
1260
1261
|
log(`Timestamps normalized: -${offsetMs}ms offset`);
|
|
1261
1262
|
}
|
|
1262
1263
|
|
|
1264
|
+
log(`Processing...`, { phase: 'convert', percent: 70 });
|
|
1265
|
+
|
|
1263
1266
|
// Apply time range clipping if specified
|
|
1264
1267
|
if (options.startTime !== undefined || options.endTime !== undefined) {
|
|
1265
1268
|
const startTime = options.startTime || 0;
|
|
@@ -1280,14 +1283,17 @@ export function convertTsToMp4(tsData, options = {}) {
|
|
|
1280
1283
|
parser.videoDts = clipResult.video.map(au => au.dts);
|
|
1281
1284
|
parser.audioPts = clipResult.audio.map(au => au.pts);
|
|
1282
1285
|
|
|
1283
|
-
log(`Clipped: ${clipResult.actualStartTime.toFixed(2)}s - ${clipResult.actualEndTime.toFixed(2)}s (${clipResult.video.length} video, ${clipResult.audio.length} audio frames)
|
|
1286
|
+
log(`Clipped: ${clipResult.actualStartTime.toFixed(2)}s - ${clipResult.actualEndTime.toFixed(2)}s (${clipResult.video.length} video, ${clipResult.audio.length} audio frames)`, { phase: 'convert', percent: 80 });
|
|
1284
1287
|
}
|
|
1285
1288
|
|
|
1289
|
+
log(`Building MP4...`, { phase: 'convert', percent: 85 });
|
|
1286
1290
|
const builder = new MP4Builder(parser);
|
|
1287
1291
|
const { width, height } = builder.getVideoDimensions();
|
|
1288
1292
|
log(`Dimensions: ${width}x${height}`);
|
|
1289
1293
|
|
|
1290
|
-
|
|
1294
|
+
const result = builder.build();
|
|
1295
|
+
log(`Complete`, { phase: 'convert', percent: 100 });
|
|
1296
|
+
return result;
|
|
1291
1297
|
}
|
|
1292
1298
|
|
|
1293
1299
|
export default convertTsToMp4;
|