@apocaliss92/nodelink-js 0.1.8 → 0.1.9

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 CHANGED
@@ -1,6 +1,12 @@
1
- # @apocaliss92/nodelink-js
1
+ <p align="center">
2
+ <img src="assets/icon.png" alt="nodelink.js" width="128" height="128">
3
+ </p>
2
4
 
3
- A TypeScript library for interacting with Reolink IP cameras and NVRs using the proprietary Baichuan protocol and CGI API.
5
+ <h1 align="center">nodelink.js</h1>
6
+
7
+ <p align="center">
8
+ A TypeScript library for interacting with Reolink IP cameras and NVRs using the proprietary Baichuan protocol and CGI API.
9
+ </p>
4
10
 
5
11
  ## Credits
6
12
 
@@ -381,10 +387,7 @@ CAMERA_PASSWORD=your-password
381
387
  The library supports custom loggers:
382
388
 
383
389
  ```typescript
384
- import {
385
- ReolinkBaichuanApi,
386
- createLogger,
387
- } from "@apocaliss92/nodelink-js";
390
+ import { ReolinkBaichuanApi, createLogger } from "@apocaliss92/nodelink-js";
388
391
 
389
392
  const logger = createLogger({ level: "debug" });
390
393
 
@@ -9,7 +9,7 @@ import {
9
9
  runMultifocalDiagnosticsConsecutively,
10
10
  sampleStreams,
11
11
  testChannelStreams
12
- } from "./chunk-MC2BRLLE.js";
12
+ } from "./chunk-TZFZ5WJX.js";
13
13
  export {
14
14
  collectCgiDiagnostics,
15
15
  collectMultifocalDiagnostics,
@@ -22,4 +22,4 @@ export {
22
22
  sampleStreams,
23
23
  testChannelStreams
24
24
  };
25
- //# sourceMappingURL=DiagnosticsTools-MTXG65O3.js.map
25
+ //# sourceMappingURL=DiagnosticsTools-EC7DADEQ.js.map
@@ -267,17 +267,20 @@ function parseRecordingFileName(fileName) {
267
267
  let widthRaw;
268
268
  let heightRaw;
269
269
  let hexValue = "";
270
+ let sizeHex;
270
271
  if (parts.length === 6) {
271
272
  startDate = parts[1] ?? "";
272
273
  startTime = parts[2] ?? "";
273
274
  endTime = parts[3] ?? "";
274
275
  hexValue = parts[4] ?? "";
276
+ sizeHex = parts[5];
275
277
  } else if (parts.length === 7) {
276
278
  startDate = parts[1] ?? "";
277
279
  startTime = parts[2] ?? "";
278
280
  endTime = parts[3] ?? "";
279
281
  animalTypeRaw = parts[4];
280
282
  hexValue = parts[5] ?? "";
283
+ sizeHex = parts[6];
281
284
  } else if (parts.length === 9) {
282
285
  devType = "hub";
283
286
  startDate = parts[1] ?? "";
@@ -287,6 +290,7 @@ function parseRecordingFileName(fileName) {
287
290
  widthRaw = parts[5];
288
291
  heightRaw = parts[6];
289
292
  hexValue = parts[7] ?? "";
293
+ sizeHex = parts[8];
290
294
  } else {
291
295
  return void 0;
292
296
  }
@@ -317,6 +321,12 @@ function parseRecordingFileName(fileName) {
317
321
  if (animalTypeRaw != null) parsed.animalTypeRaw = animalTypeRaw;
318
322
  if (widthRaw != null) parsed.widthRaw = widthRaw;
319
323
  if (heightRaw != null) parsed.heightRaw = heightRaw;
324
+ if (sizeHex && /^[0-9a-fA-F]+$/.test(sizeHex)) {
325
+ const sizeBytes = parseInt(sizeHex, 16);
326
+ if (Number.isFinite(sizeBytes) && sizeBytes > 0) {
327
+ parsed.sizeBytes = sizeBytes;
328
+ }
329
+ }
320
330
  return parsed;
321
331
  }
322
332
 
@@ -2178,15 +2188,18 @@ var ReolinkCgiApi = class _ReolinkCgiApi {
2178
2188
  if (detectionClasses.length === 0) {
2179
2189
  detectionClasses.push("motion");
2180
2190
  }
2191
+ const sizeBytes = typeof vodFile.size === "string" ? parseInt(vodFile.size, 10) : vodFile.size;
2181
2192
  const result = {
2182
2193
  fileName: vodFile.name,
2183
2194
  id: vodFile.name,
2184
- sizeBytes: vodFile.size,
2185
2195
  startTime,
2186
2196
  endTime,
2187
2197
  recordType: vodFile.type,
2188
2198
  detectionClasses
2189
2199
  };
2200
+ if (Number.isFinite(sizeBytes)) {
2201
+ result.sizeBytes = sizeBytes;
2202
+ }
2190
2203
  if (parsed) {
2191
2204
  result.parsedFileName = parsed;
2192
2205
  }
@@ -4043,13 +4056,31 @@ var BaichuanVideoStream = class _BaichuanVideoStream extends EventEmitter {
4043
4056
  searchStart = bodyEnd + Buffer.from("</body>").length;
4044
4057
  dataToParse = rawCandidate.subarray(searchStart);
4045
4058
  }
4059
+ let encryptLen;
4060
+ if (frame.extension && frame.extension.length > 0) {
4061
+ try {
4062
+ const extDec = this.client.tryDecryptXml(
4063
+ frame.extension,
4064
+ frame.header.channelId,
4065
+ enc
4066
+ );
4067
+ const encryptLenMatch = extDec.match(
4068
+ /<encryptLen>(\d+)<\/encryptLen>/i
4069
+ );
4070
+ if (encryptLenMatch && encryptLenMatch[1]) {
4071
+ encryptLen = parseInt(encryptLenMatch[1], 10);
4072
+ }
4073
+ } catch {
4074
+ }
4075
+ }
4046
4076
  const dataAfterXml = this.chooseDecryptedOrRawCandidate({
4047
4077
  raw: dataToParse,
4048
4078
  enc,
4049
4079
  channelId: frame.header.channelId,
4050
4080
  // Some NVR/Hub streams appear to include non-media bytes even when payloadOffset is present.
4051
4081
  // Allow a one-time resync at startup to avoid delaying the first keyframe.
4052
- allowResync: frame.payload.length === 0 || totalFramesReceived <= 10 && totalMediaPackets === 0
4082
+ allowResync: frame.payload.length === 0 || totalFramesReceived <= 10 && totalMediaPackets === 0,
4083
+ ...encryptLen !== void 0 ? { encryptLen } : {}
4053
4084
  });
4054
4085
  if (this.bcMediaCodec.getRemainingBuffer().length === 0 && dataAfterXml.length <= 600) {
4055
4086
  const s = _BaichuanVideoStream.scoreBcMediaLike(dataAfterXml);
@@ -4162,15 +4193,38 @@ var BaichuanVideoStream = class _BaichuanVideoStream extends EventEmitter {
4162
4193
  }
4163
4194
  }
4164
4195
  };
4165
- const prependParamSetsIfNeeded = (annexB, videoType) => {
4196
+ const prependParamSetsIfNeeded = (annexB, videoType, isPframe = false) => {
4166
4197
  if (videoType === "H264") {
4167
4198
  const nals = splitAnnexBToNalPayloads(annexB);
4168
4199
  if (nals.length === 0) return annexB;
4169
4200
  const types = nals.map((n) => (n[0] ?? 0) & 31);
4170
- if (types.includes(7) && types.includes(8)) return annexB;
4171
4201
  const hasVcl = types.some(
4172
4202
  (t) => t === 1 || t === 5 || t === 19 || t === 20
4173
4203
  );
4204
+ if (isPframe && !hasVcl) {
4205
+ if (dbg.traceNativeStream) {
4206
+ this.logger?.warn(
4207
+ `[BaichuanVideoStream] Dropping P-frame without VCL (only param sets): types=${types.join(",")}`
4208
+ );
4209
+ }
4210
+ return Buffer.alloc(0);
4211
+ }
4212
+ if (types.includes(7) && types.includes(8)) {
4213
+ let ppsIdFromSlice = null;
4214
+ for (const nal of nals) {
4215
+ const t = (nal[0] ?? 0) & 31;
4216
+ if (t === 1 || t === 5) {
4217
+ ppsIdFromSlice = parseSlicePpsIdFromNal(nal);
4218
+ break;
4219
+ }
4220
+ }
4221
+ if (ppsIdFromSlice != null && ppsIdFromSlice <= 255) {
4222
+ this.lastPrependedPpsId = ppsIdFromSlice;
4223
+ } else {
4224
+ this.lastPrependedPpsId = -1;
4225
+ }
4226
+ return annexB;
4227
+ }
4174
4228
  if (!hasVcl) return annexB;
4175
4229
  let ppsId = null;
4176
4230
  for (const nal of nals) {
@@ -4217,11 +4271,19 @@ var BaichuanVideoStream = class _BaichuanVideoStream extends EventEmitter {
4217
4271
  const nals = splitAnnexBToNalPayloads2(annexB);
4218
4272
  if (nals.length === 0) return annexB;
4219
4273
  const types = nals.map((n) => getH265NalType(n)).filter((t) => t !== null);
4220
- if (types.includes(32) && types.includes(33) && types.includes(34))
4221
- return annexB;
4222
4274
  const hasVcl = types.some(
4223
4275
  (t) => t >= 0 && t <= 9 || t >= 16 && t <= 23
4224
4276
  );
4277
+ if (isPframe && !hasVcl) {
4278
+ if (dbg.traceNativeStream) {
4279
+ this.logger?.warn(
4280
+ `[BaichuanVideoStream] Dropping H.265 P-frame without VCL (only param sets): types=${types.join(",")}`
4281
+ );
4282
+ }
4283
+ return Buffer.alloc(0);
4284
+ }
4285
+ if (types.includes(32) && types.includes(33) && types.includes(34))
4286
+ return annexB;
4225
4287
  if (!hasVcl) return annexB;
4226
4288
  if (this.lastPrependedParamSetsH265) return annexB;
4227
4289
  if (!this.lastVps || !this.lastSpsH265 || !this.lastPpsH265)
@@ -4403,7 +4465,7 @@ var BaichuanVideoStream = class _BaichuanVideoStream extends EventEmitter {
4403
4465
  }
4404
4466
  for (const p of parts) {
4405
4467
  maybeCacheParamSets(p, "Pframe", videoType);
4406
- const outP0 = prependParamSetsIfNeeded(p, videoType);
4468
+ const outP0 = prependParamSetsIfNeeded(p, videoType, true);
4407
4469
  if (outP0.length === 0) continue;
4408
4470
  const outP = outP0;
4409
4471
  dumpNalSummary(outP, "Pframe", media.microseconds);
@@ -4916,7 +4978,7 @@ async function createDiagnosticsBundle(params) {
4916
4978
  }
4917
4979
  function sanitizeFfmpegError(error) {
4918
4980
  return error.replace(
4919
- /([a-z]+:\/\/)([^:@\/\s]+):([^@\/\s]+)@/gi,
4981
+ /([a-z]+:\/\/)([^:@/\s]+):([^@/\s]+)@/gi,
4920
4982
  (match, protocol, username, password) => {
4921
4983
  return `${protocol}***:***@`;
4922
4984
  }
@@ -7235,4 +7297,4 @@ export {
7235
7297
  parseRecordingFileName,
7236
7298
  ReolinkCgiApi
7237
7299
  };
7238
- //# sourceMappingURL=chunk-MC2BRLLE.js.map
7300
+ //# sourceMappingURL=chunk-TZFZ5WJX.js.map