@apocaliss92/nodelink-js 0.1.7 → 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 +9 -6
- package/dist/{DiagnosticsTools-MTXG65O3.js → DiagnosticsTools-EC7DADEQ.js} +2 -2
- package/dist/{chunk-MC2BRLLE.js → chunk-TZFZ5WJX.js} +71 -9
- package/dist/chunk-TZFZ5WJX.js.map +1 -0
- package/dist/{chunk-JMT75JNG.js → chunk-YUBYINJF.js} +674 -64
- package/dist/chunk-YUBYINJF.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +740 -68
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.d.cts +1 -0
- package/dist/cli/rtsp-server.d.ts +1 -0
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +3293 -248
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8187 -0
- package/dist/index.d.ts +761 -1
- package/dist/index.js +2359 -5
- package/dist/index.js.map +1 -1
- package/package.json +14 -3
- package/dist/chunk-JMT75JNG.js.map +0 -1
- package/dist/chunk-MC2BRLLE.js.map +0 -1
- /package/dist/{DiagnosticsTools-MTXG65O3.js.map → DiagnosticsTools-EC7DADEQ.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/icon.png" alt="nodelink.js" width="128" height="128">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
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-
|
|
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-
|
|
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]+:\/\/)([
|
|
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-
|
|
7300
|
+
//# sourceMappingURL=chunk-TZFZ5WJX.js.map
|