incyclist-services 1.7.92 → 1.7.94

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.
@@ -374,6 +374,10 @@ let RLVDisplayService = (() => {
374
374
  if (time !== undefined) {
375
375
  this.currentVideo.buffering = `${time}s`;
376
376
  this.currentVideo.bufferTime = time;
377
+ if (time > 200) {
378
+ this.logEvent({ message: 'video conversion >200s' });
379
+ this.onVideoLoaded(0);
380
+ }
377
381
  }
378
382
  else if (progress !== undefined)
379
383
  this.currentVideo.buffering = `${progress}%`;
@@ -390,11 +394,12 @@ let RLVDisplayService = (() => {
390
394
  onVideoLoadError(error, video = this.currentVideo) {
391
395
  video.loaded = false;
392
396
  video.error = this.buildVideoError(error, video);
397
+ const isCurrentVideo = video === this.currentVideo;
393
398
  if (video.isInitial) {
394
- this.logEvent({ message: 'could not load video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error) });
399
+ this.logEvent({ message: 'could not load video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error), isCurrentVideo });
395
400
  }
396
401
  else {
397
- this.logEvent({ message: 'could not load next video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error) });
402
+ this.logEvent({ message: 'could not load next video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error), isCurrentVideo });
398
403
  const errIdx = this.videos.indexOf(video);
399
404
  if (errIdx !== -1) {
400
405
  this.videos.splice(errIdx);
@@ -159,11 +159,14 @@ let RouteCard = (() => {
159
159
  const videoUrl = descr.videoUrl;
160
160
  if (!descr.videoUrl)
161
161
  return true;
162
+ const normalizedUrl = videoUrl.replace('video:////', 'video:///').replace('file:////', 'file:///');
162
163
  let path = undefined;
163
- if (videoUrl.startsWith('file:///'))
164
- path = videoUrl.replace('file:///', '');
165
- if (videoUrl.startsWith('video:///'))
166
- path = videoUrl.replace('video:///', '');
164
+ if (normalizedUrl.startsWith('file:///'))
165
+ path = normalizedUrl.replace('file://', '');
166
+ if (normalizedUrl.startsWith('video:///'))
167
+ path = normalizedUrl.replace('video://', '');
168
+ if (path && /^\/[A-Za-z]:[\\/]/.test(path))
169
+ path = path.slice(1);
167
170
  if (path) {
168
171
  return await this.fileExists(path);
169
172
  }
@@ -65,6 +65,152 @@ const parseStsdCodec = (buf, stsdBox) => {
65
65
  return undefined;
66
66
  return buf.toString('ascii', entryOffset + 4, entryOffset + 8);
67
67
  };
68
+ const CHROMA_FORMAT_NAMES = {
69
+ 0: '4:0:0 (monochrome)', 1: '4:2:0', 2: '4:2:2', 3: '4:4:4'
70
+ };
71
+ const findSampleEntryChildBoxes = (buf, stsdBox) => {
72
+ const entryOffset = stsdBox.bodyStart + 8;
73
+ if (entryOffset + 4 > buf.length)
74
+ return [];
75
+ const entrySize = buf.readUInt32BE(entryOffset);
76
+ const childStart = entryOffset + 86;
77
+ const childEnd = Math.min(entryOffset + entrySize, buf.length);
78
+ if (childStart >= childEnd)
79
+ return [];
80
+ return findBoxes(buf, childStart, childEnd);
81
+ };
82
+ const AVC_PROFILE_NAMES = {
83
+ 66: 'Baseline', 77: 'Main', 88: 'Extended', 100: 'High',
84
+ 110: 'High 10', 122: 'High 4:2:2', 144: 'High 4:4:4', 244: 'High 4:4:4 Predictive'
85
+ };
86
+ const AVC_HIGH_FAMILY_PROFILES = new Set([100, 110, 122, 144, 244]);
87
+ const parseAvcC = (buf, avcCBox) => {
88
+ const start = avcCBox.bodyStart;
89
+ if (start + 4 > buf.length)
90
+ return undefined;
91
+ const profileIdc = buf.readUInt8(start + 1);
92
+ const levelIdc = buf.readUInt8(start + 3);
93
+ const details = {
94
+ profile: AVC_PROFILE_NAMES[profileIdc] ?? `unknown (${profileIdc})`,
95
+ profileId: profileIdc,
96
+ level: (levelIdc / 10).toFixed(1)
97
+ };
98
+ if (!AVC_HIGH_FAMILY_PROFILES.has(profileIdc))
99
+ return details;
100
+ try {
101
+ let offset = start + 5;
102
+ if (offset >= buf.length)
103
+ return details;
104
+ const numSps = buf.readUInt8(offset) & 0x1f;
105
+ offset += 1;
106
+ for (let i = 0; i < numSps; i++) {
107
+ if (offset + 2 > buf.length)
108
+ return details;
109
+ offset += 2 + buf.readUInt16BE(offset);
110
+ }
111
+ if (offset >= buf.length)
112
+ return details;
113
+ const numPps = buf.readUInt8(offset);
114
+ offset += 1;
115
+ for (let i = 0; i < numPps; i++) {
116
+ if (offset + 2 > buf.length)
117
+ return details;
118
+ offset += 2 + buf.readUInt16BE(offset);
119
+ }
120
+ if (offset + 3 > buf.length)
121
+ return details;
122
+ details.chromaFormat = CHROMA_FORMAT_NAMES[buf.readUInt8(offset) & 0x03];
123
+ details.bitDepthLuma = (buf.readUInt8(offset + 1) & 0x07) + 8;
124
+ details.bitDepthChroma = (buf.readUInt8(offset + 2) & 0x07) + 8;
125
+ }
126
+ catch { }
127
+ return details;
128
+ };
129
+ const HEVC_PROFILE_NAMES = {
130
+ 1: 'Main', 2: 'Main 10', 3: 'Main Still Picture', 4: 'Range Extensions',
131
+ 5: 'High Throughput', 6: 'Multiview Main', 7: 'Scalable Main', 8: '3D Main',
132
+ 9: 'Screen Content Coding', 10: 'Scalable Range Extensions'
133
+ };
134
+ const parseHvcC = (buf, hvcCBox) => {
135
+ const start = hvcCBox.bodyStart;
136
+ if (start + 19 > buf.length)
137
+ return undefined;
138
+ const byte1 = buf.readUInt8(start + 1);
139
+ const generalProfileIdc = byte1 & 0x1f;
140
+ const generalTierFlag = (byte1 >> 5) & 0x01;
141
+ const generalLevelIdc = buf.readUInt8(start + 12);
142
+ const chromaFormatIdc = buf.readUInt8(start + 16) & 0x03;
143
+ return {
144
+ profile: HEVC_PROFILE_NAMES[generalProfileIdc] ?? `unknown (${generalProfileIdc})`,
145
+ profileId: generalProfileIdc,
146
+ tier: generalTierFlag ? 'High' : 'Main',
147
+ level: (generalLevelIdc / 30).toFixed(1),
148
+ chromaFormat: CHROMA_FORMAT_NAMES[chromaFormatIdc],
149
+ bitDepthLuma: (buf.readUInt8(start + 17) & 0x07) + 8,
150
+ bitDepthChroma: (buf.readUInt8(start + 18) & 0x07) + 8
151
+ };
152
+ };
153
+ const AV1_PROFILE_NAMES = { 0: 'Main', 1: 'High', 2: 'Professional' };
154
+ const av1Level = (levelIdx) => `${2 + Math.floor(levelIdx / 4)}.${levelIdx % 4}`;
155
+ const av1BitDepth = (highBitdepth, twelveBit) => {
156
+ if (twelveBit)
157
+ return 12;
158
+ return highBitdepth ? 10 : 8;
159
+ };
160
+ const av1ChromaFormat = (monochrome, subsamplingX, subsamplingY) => {
161
+ if (monochrome)
162
+ return CHROMA_FORMAT_NAMES[0];
163
+ if (subsamplingX && subsamplingY)
164
+ return '4:2:0';
165
+ if (subsamplingX && !subsamplingY)
166
+ return '4:2:2';
167
+ return '4:4:4';
168
+ };
169
+ const parseAv1C = (buf, av1CBox) => {
170
+ const start = av1CBox.bodyStart;
171
+ if (start + 3 > buf.length)
172
+ return undefined;
173
+ const byte1 = buf.readUInt8(start + 1);
174
+ const seqProfile = (byte1 >> 5) & 0x07;
175
+ const seqLevelIdx0 = byte1 & 0x1f;
176
+ const byte2 = buf.readUInt8(start + 2);
177
+ const seqTier0 = (byte2 >> 7) & 0x01;
178
+ const highBitdepth = (byte2 >> 6) & 0x01;
179
+ const twelveBit = (byte2 >> 5) & 0x01;
180
+ const monochrome = (byte2 >> 4) & 0x01;
181
+ const chromaSubsamplingX = (byte2 >> 3) & 0x01;
182
+ const chromaSubsamplingY = (byte2 >> 2) & 0x01;
183
+ const bitDepth = av1BitDepth(highBitdepth, twelveBit);
184
+ const chromaFormat = av1ChromaFormat(monochrome, chromaSubsamplingX, chromaSubsamplingY);
185
+ return {
186
+ profile: AV1_PROFILE_NAMES[seqProfile] ?? `unknown (${seqProfile})`,
187
+ profileId: seqProfile,
188
+ tier: seqTier0 ? 'High' : 'Main',
189
+ level: av1Level(seqLevelIdx0),
190
+ chromaFormat,
191
+ bitDepthLuma: bitDepth,
192
+ bitDepthChroma: bitDepth,
193
+ monochrome: !!monochrome
194
+ };
195
+ };
196
+ const parseCodecDetails = (buf, codec, stsdBox) => {
197
+ if (!codec)
198
+ return undefined;
199
+ const childBoxes = findSampleEntryChildBoxes(buf, stsdBox);
200
+ if (codec === 'avc1' || codec === 'avc3') {
201
+ const avcCBox = findBox(childBoxes, 'avcC');
202
+ return avcCBox ? parseAvcC(buf, avcCBox) : undefined;
203
+ }
204
+ if (codec === 'hev1' || codec === 'hvc1') {
205
+ const hvcCBox = findBox(childBoxes, 'hvcC');
206
+ return hvcCBox ? parseHvcC(buf, hvcCBox) : undefined;
207
+ }
208
+ if (codec === 'av01') {
209
+ const av1CBox = findBox(childBoxes, 'av1C');
210
+ return av1CBox ? parseAv1C(buf, av1CBox) : undefined;
211
+ }
212
+ return undefined;
213
+ };
68
214
  const parseTkhdDimensions = (buf, tkhdBox) => {
69
215
  if (tkhdBox.bodyStart >= buf.length)
70
216
  return {};
@@ -88,18 +234,22 @@ const parseTrak = (buf, trakBox) => {
88
234
  const minfBox = findBox(mdiaChildren, 'minf');
89
235
  const handlerType = hdlrBox ? parseHandlerType(buf, hdlrBox) : undefined;
90
236
  let codec;
237
+ let codecDetails;
91
238
  if (minfBox) {
92
239
  const minfChildren = findBoxes(buf, minfBox.bodyStart, minfBox.bodyEnd);
93
240
  const stblBox = findBox(minfChildren, 'stbl');
94
241
  if (stblBox) {
95
242
  const stblChildren = findBoxes(buf, stblBox.bodyStart, stblBox.bodyEnd);
96
243
  const stsdBox = findBox(stblChildren, 'stsd');
97
- if (stsdBox)
244
+ if (stsdBox) {
98
245
  codec = parseStsdCodec(buf, stsdBox);
246
+ if (handlerType === 'vide')
247
+ codecDetails = parseCodecDetails(buf, codec, stsdBox);
248
+ }
99
249
  }
100
250
  }
101
251
  const dimensions = (handlerType === 'vide' && tkhdBox) ? parseTkhdDimensions(buf, tkhdBox) : {};
102
- return { handlerType, codec, ...dimensions };
252
+ return { handlerType, codec, codecDetails, ...dimensions };
103
253
  };
104
254
  const parseMp4Boxes = (headBuffer, tailBuffer) => {
105
255
  const result = { moovLocation: 'not-found', incomplete: true };
@@ -130,13 +280,15 @@ const applyMoov = (buf, moovBox, result) => {
130
280
  const moovChildren = findBoxes(buf, moovBox.bodyStart, moovBox.bodyEnd);
131
281
  const trakBoxes = moovChildren.filter(b => b.type === 'trak');
132
282
  trakBoxes.forEach(trakBox => {
133
- const { handlerType, codec, width, height } = parseTrak(buf, trakBox);
283
+ const { handlerType, codec, width, height, codecDetails } = parseTrak(buf, trakBox);
134
284
  if (handlerType === 'vide') {
135
285
  result.videoCodec = codec;
136
286
  if (width)
137
287
  result.width = width;
138
288
  if (height)
139
289
  result.height = height;
290
+ if (codecDetails)
291
+ result.codecDetails = codecDetails;
140
292
  }
141
293
  else if (handlerType === 'soun') {
142
294
  result.audioCodec = codec;
@@ -135,7 +135,7 @@ let VideoConversion = (() => {
135
135
  const frames = mapping?.length > 0 ? Math.max(...mapping.map(m => m.frame)) : 0;
136
136
  const framerate = this.route.details?.video?.framerate;
137
137
  const time = framerate ? Math.round(info.frames / framerate) : parseTime(info.timemark);
138
- const pct = frames ? Math.floor(info.frames / frames * 100) : undefined;
138
+ const pct = (frames && !isNaN(frames)) ? Math.floor(info.frames / frames * 100) : undefined;
139
139
  const cpu = this.getCpuStats(info);
140
140
  if (frames || time) {
141
141
  this.observer.emit('convert-progress', pct, info.frames, time);
@@ -371,6 +371,10 @@ let RLVDisplayService = (() => {
371
371
  if (time !== undefined) {
372
372
  this.currentVideo.buffering = `${time}s`;
373
373
  this.currentVideo.bufferTime = time;
374
+ if (time > 200) {
375
+ this.logEvent({ message: 'video conversion >200s' });
376
+ this.onVideoLoaded(0);
377
+ }
374
378
  }
375
379
  else if (progress !== undefined)
376
380
  this.currentVideo.buffering = `${progress}%`;
@@ -387,11 +391,12 @@ let RLVDisplayService = (() => {
387
391
  onVideoLoadError(error, video = this.currentVideo) {
388
392
  video.loaded = false;
389
393
  video.error = this.buildVideoError(error, video);
394
+ const isCurrentVideo = video === this.currentVideo;
390
395
  if (video.isInitial) {
391
- this.logEvent({ message: 'could not load video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error) });
396
+ this.logEvent({ message: 'could not load video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error), isCurrentVideo });
392
397
  }
393
398
  else {
394
- this.logEvent({ message: 'could not load next video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error) });
399
+ this.logEvent({ message: 'could not load next video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error), isCurrentVideo });
395
400
  const errIdx = this.videos.indexOf(video);
396
401
  if (errIdx !== -1) {
397
402
  this.videos.splice(errIdx);
@@ -156,11 +156,14 @@ let RouteCard = (() => {
156
156
  const videoUrl = descr.videoUrl;
157
157
  if (!descr.videoUrl)
158
158
  return true;
159
+ const normalizedUrl = videoUrl.replace('video:////', 'video:///').replace('file:////', 'file:///');
159
160
  let path = undefined;
160
- if (videoUrl.startsWith('file:///'))
161
- path = videoUrl.replace('file:///', '');
162
- if (videoUrl.startsWith('video:///'))
163
- path = videoUrl.replace('video:///', '');
161
+ if (normalizedUrl.startsWith('file:///'))
162
+ path = normalizedUrl.replace('file://', '');
163
+ if (normalizedUrl.startsWith('video:///'))
164
+ path = normalizedUrl.replace('video://', '');
165
+ if (path && /^\/[A-Za-z]:[\\/]/.test(path))
166
+ path = path.slice(1);
164
167
  if (path) {
165
168
  return await this.fileExists(path);
166
169
  }
@@ -62,6 +62,152 @@ const parseStsdCodec = (buf, stsdBox) => {
62
62
  return undefined;
63
63
  return buf.toString('ascii', entryOffset + 4, entryOffset + 8);
64
64
  };
65
+ const CHROMA_FORMAT_NAMES = {
66
+ 0: '4:0:0 (monochrome)', 1: '4:2:0', 2: '4:2:2', 3: '4:4:4'
67
+ };
68
+ const findSampleEntryChildBoxes = (buf, stsdBox) => {
69
+ const entryOffset = stsdBox.bodyStart + 8;
70
+ if (entryOffset + 4 > buf.length)
71
+ return [];
72
+ const entrySize = buf.readUInt32BE(entryOffset);
73
+ const childStart = entryOffset + 86;
74
+ const childEnd = Math.min(entryOffset + entrySize, buf.length);
75
+ if (childStart >= childEnd)
76
+ return [];
77
+ return findBoxes(buf, childStart, childEnd);
78
+ };
79
+ const AVC_PROFILE_NAMES = {
80
+ 66: 'Baseline', 77: 'Main', 88: 'Extended', 100: 'High',
81
+ 110: 'High 10', 122: 'High 4:2:2', 144: 'High 4:4:4', 244: 'High 4:4:4 Predictive'
82
+ };
83
+ const AVC_HIGH_FAMILY_PROFILES = new Set([100, 110, 122, 144, 244]);
84
+ const parseAvcC = (buf, avcCBox) => {
85
+ const start = avcCBox.bodyStart;
86
+ if (start + 4 > buf.length)
87
+ return undefined;
88
+ const profileIdc = buf.readUInt8(start + 1);
89
+ const levelIdc = buf.readUInt8(start + 3);
90
+ const details = {
91
+ profile: AVC_PROFILE_NAMES[profileIdc] ?? `unknown (${profileIdc})`,
92
+ profileId: profileIdc,
93
+ level: (levelIdc / 10).toFixed(1)
94
+ };
95
+ if (!AVC_HIGH_FAMILY_PROFILES.has(profileIdc))
96
+ return details;
97
+ try {
98
+ let offset = start + 5;
99
+ if (offset >= buf.length)
100
+ return details;
101
+ const numSps = buf.readUInt8(offset) & 0x1f;
102
+ offset += 1;
103
+ for (let i = 0; i < numSps; i++) {
104
+ if (offset + 2 > buf.length)
105
+ return details;
106
+ offset += 2 + buf.readUInt16BE(offset);
107
+ }
108
+ if (offset >= buf.length)
109
+ return details;
110
+ const numPps = buf.readUInt8(offset);
111
+ offset += 1;
112
+ for (let i = 0; i < numPps; i++) {
113
+ if (offset + 2 > buf.length)
114
+ return details;
115
+ offset += 2 + buf.readUInt16BE(offset);
116
+ }
117
+ if (offset + 3 > buf.length)
118
+ return details;
119
+ details.chromaFormat = CHROMA_FORMAT_NAMES[buf.readUInt8(offset) & 0x03];
120
+ details.bitDepthLuma = (buf.readUInt8(offset + 1) & 0x07) + 8;
121
+ details.bitDepthChroma = (buf.readUInt8(offset + 2) & 0x07) + 8;
122
+ }
123
+ catch { }
124
+ return details;
125
+ };
126
+ const HEVC_PROFILE_NAMES = {
127
+ 1: 'Main', 2: 'Main 10', 3: 'Main Still Picture', 4: 'Range Extensions',
128
+ 5: 'High Throughput', 6: 'Multiview Main', 7: 'Scalable Main', 8: '3D Main',
129
+ 9: 'Screen Content Coding', 10: 'Scalable Range Extensions'
130
+ };
131
+ const parseHvcC = (buf, hvcCBox) => {
132
+ const start = hvcCBox.bodyStart;
133
+ if (start + 19 > buf.length)
134
+ return undefined;
135
+ const byte1 = buf.readUInt8(start + 1);
136
+ const generalProfileIdc = byte1 & 0x1f;
137
+ const generalTierFlag = (byte1 >> 5) & 0x01;
138
+ const generalLevelIdc = buf.readUInt8(start + 12);
139
+ const chromaFormatIdc = buf.readUInt8(start + 16) & 0x03;
140
+ return {
141
+ profile: HEVC_PROFILE_NAMES[generalProfileIdc] ?? `unknown (${generalProfileIdc})`,
142
+ profileId: generalProfileIdc,
143
+ tier: generalTierFlag ? 'High' : 'Main',
144
+ level: (generalLevelIdc / 30).toFixed(1),
145
+ chromaFormat: CHROMA_FORMAT_NAMES[chromaFormatIdc],
146
+ bitDepthLuma: (buf.readUInt8(start + 17) & 0x07) + 8,
147
+ bitDepthChroma: (buf.readUInt8(start + 18) & 0x07) + 8
148
+ };
149
+ };
150
+ const AV1_PROFILE_NAMES = { 0: 'Main', 1: 'High', 2: 'Professional' };
151
+ const av1Level = (levelIdx) => `${2 + Math.floor(levelIdx / 4)}.${levelIdx % 4}`;
152
+ const av1BitDepth = (highBitdepth, twelveBit) => {
153
+ if (twelveBit)
154
+ return 12;
155
+ return highBitdepth ? 10 : 8;
156
+ };
157
+ const av1ChromaFormat = (monochrome, subsamplingX, subsamplingY) => {
158
+ if (monochrome)
159
+ return CHROMA_FORMAT_NAMES[0];
160
+ if (subsamplingX && subsamplingY)
161
+ return '4:2:0';
162
+ if (subsamplingX && !subsamplingY)
163
+ return '4:2:2';
164
+ return '4:4:4';
165
+ };
166
+ const parseAv1C = (buf, av1CBox) => {
167
+ const start = av1CBox.bodyStart;
168
+ if (start + 3 > buf.length)
169
+ return undefined;
170
+ const byte1 = buf.readUInt8(start + 1);
171
+ const seqProfile = (byte1 >> 5) & 0x07;
172
+ const seqLevelIdx0 = byte1 & 0x1f;
173
+ const byte2 = buf.readUInt8(start + 2);
174
+ const seqTier0 = (byte2 >> 7) & 0x01;
175
+ const highBitdepth = (byte2 >> 6) & 0x01;
176
+ const twelveBit = (byte2 >> 5) & 0x01;
177
+ const monochrome = (byte2 >> 4) & 0x01;
178
+ const chromaSubsamplingX = (byte2 >> 3) & 0x01;
179
+ const chromaSubsamplingY = (byte2 >> 2) & 0x01;
180
+ const bitDepth = av1BitDepth(highBitdepth, twelveBit);
181
+ const chromaFormat = av1ChromaFormat(monochrome, chromaSubsamplingX, chromaSubsamplingY);
182
+ return {
183
+ profile: AV1_PROFILE_NAMES[seqProfile] ?? `unknown (${seqProfile})`,
184
+ profileId: seqProfile,
185
+ tier: seqTier0 ? 'High' : 'Main',
186
+ level: av1Level(seqLevelIdx0),
187
+ chromaFormat,
188
+ bitDepthLuma: bitDepth,
189
+ bitDepthChroma: bitDepth,
190
+ monochrome: !!monochrome
191
+ };
192
+ };
193
+ const parseCodecDetails = (buf, codec, stsdBox) => {
194
+ if (!codec)
195
+ return undefined;
196
+ const childBoxes = findSampleEntryChildBoxes(buf, stsdBox);
197
+ if (codec === 'avc1' || codec === 'avc3') {
198
+ const avcCBox = findBox(childBoxes, 'avcC');
199
+ return avcCBox ? parseAvcC(buf, avcCBox) : undefined;
200
+ }
201
+ if (codec === 'hev1' || codec === 'hvc1') {
202
+ const hvcCBox = findBox(childBoxes, 'hvcC');
203
+ return hvcCBox ? parseHvcC(buf, hvcCBox) : undefined;
204
+ }
205
+ if (codec === 'av01') {
206
+ const av1CBox = findBox(childBoxes, 'av1C');
207
+ return av1CBox ? parseAv1C(buf, av1CBox) : undefined;
208
+ }
209
+ return undefined;
210
+ };
65
211
  const parseTkhdDimensions = (buf, tkhdBox) => {
66
212
  if (tkhdBox.bodyStart >= buf.length)
67
213
  return {};
@@ -85,18 +231,22 @@ const parseTrak = (buf, trakBox) => {
85
231
  const minfBox = findBox(mdiaChildren, 'minf');
86
232
  const handlerType = hdlrBox ? parseHandlerType(buf, hdlrBox) : undefined;
87
233
  let codec;
234
+ let codecDetails;
88
235
  if (minfBox) {
89
236
  const minfChildren = findBoxes(buf, minfBox.bodyStart, minfBox.bodyEnd);
90
237
  const stblBox = findBox(minfChildren, 'stbl');
91
238
  if (stblBox) {
92
239
  const stblChildren = findBoxes(buf, stblBox.bodyStart, stblBox.bodyEnd);
93
240
  const stsdBox = findBox(stblChildren, 'stsd');
94
- if (stsdBox)
241
+ if (stsdBox) {
95
242
  codec = parseStsdCodec(buf, stsdBox);
243
+ if (handlerType === 'vide')
244
+ codecDetails = parseCodecDetails(buf, codec, stsdBox);
245
+ }
96
246
  }
97
247
  }
98
248
  const dimensions = (handlerType === 'vide' && tkhdBox) ? parseTkhdDimensions(buf, tkhdBox) : {};
99
- return { handlerType, codec, ...dimensions };
249
+ return { handlerType, codec, codecDetails, ...dimensions };
100
250
  };
101
251
  export const parseMp4Boxes = (headBuffer, tailBuffer) => {
102
252
  const result = { moovLocation: 'not-found', incomplete: true };
@@ -126,13 +276,15 @@ const applyMoov = (buf, moovBox, result) => {
126
276
  const moovChildren = findBoxes(buf, moovBox.bodyStart, moovBox.bodyEnd);
127
277
  const trakBoxes = moovChildren.filter(b => b.type === 'trak');
128
278
  trakBoxes.forEach(trakBox => {
129
- const { handlerType, codec, width, height } = parseTrak(buf, trakBox);
279
+ const { handlerType, codec, width, height, codecDetails } = parseTrak(buf, trakBox);
130
280
  if (handlerType === 'vide') {
131
281
  result.videoCodec = codec;
132
282
  if (width)
133
283
  result.width = width;
134
284
  if (height)
135
285
  result.height = height;
286
+ if (codecDetails)
287
+ result.codecDetails = codecDetails;
136
288
  }
137
289
  else if (handlerType === 'soun') {
138
290
  result.audioCodec = codec;
@@ -132,7 +132,7 @@ let VideoConversion = (() => {
132
132
  const frames = mapping?.length > 0 ? Math.max(...mapping.map(m => m.frame)) : 0;
133
133
  const framerate = this.route.details?.video?.framerate;
134
134
  const time = framerate ? Math.round(info.frames / framerate) : parseTime(info.timemark);
135
- const pct = frames ? Math.floor(info.frames / frames * 100) : undefined;
135
+ const pct = (frames && !isNaN(frames)) ? Math.floor(info.frames / frames * 100) : undefined;
136
136
  const cpu = this.getCpuStats(info);
137
137
  if (frames || time) {
138
138
  this.observer.emit('convert-progress', pct, info.frames, time);
@@ -1,3 +1,13 @@
1
+ export type CodecDetails = {
2
+ profile?: string;
3
+ profileId?: number;
4
+ tier?: string;
5
+ level?: string;
6
+ chromaFormat?: string;
7
+ bitDepthLuma?: number;
8
+ bitDepthChroma?: number;
9
+ monochrome?: boolean;
10
+ };
1
11
  export type Mp4CodecInfo = {
2
12
  containerBrand?: string;
3
13
  videoCodec?: string;
@@ -6,5 +16,6 @@ export type Mp4CodecInfo = {
6
16
  height?: number;
7
17
  moovLocation?: 'head' | 'tail' | 'not-found';
8
18
  incomplete?: boolean;
19
+ codecDetails?: CodecDetails;
9
20
  };
10
21
  export declare const parseMp4Boxes: (headBuffer?: Buffer, tailBuffer?: Buffer) => Mp4CodecInfo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.7.92",
3
+ "version": "1.7.94",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },