@apocaliss92/scrypted-reolink-native 0.5.42 → 0.5.44
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/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +2 -2
- package/src/camera.ts +236 -5
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apocaliss92/scrypted-reolink-native",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.44",
|
|
4
4
|
"description": "Use any reolink camera with Scrypted, even older/unsupported models without HTTP protocol support",
|
|
5
5
|
"author": "@apocaliss92",
|
|
6
6
|
"license": "Apache",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@apocaliss92/nodelink-js": "^0.
|
|
47
|
+
"@apocaliss92/nodelink-js": "^0.6.0",
|
|
48
48
|
"@scrypted/common": "file:../../scrypted/common",
|
|
49
49
|
"@scrypted/rtsp": "file:../../scrypted/plugins/rtsp",
|
|
50
50
|
"@scrypted/sdk": "^0.3.118"
|
package/src/camera.ts
CHANGED
|
@@ -43,7 +43,43 @@ import sdk, {
|
|
|
43
43
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
44
44
|
import crypto from "crypto";
|
|
45
45
|
import fs from "fs";
|
|
46
|
+
import os from "os";
|
|
46
47
|
import path from "path";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Per-platform list of ffmpeg H.264 encoders we offer in the composite
|
|
51
|
+
* settings dropdown. Mirrors `@scrypted/common/src/ffmpeg-hardware-
|
|
52
|
+
* acceleration.ts`'s `getH264EncoderArgs()` but replicated here so we
|
|
53
|
+
* don't take a build dependency on Scrypted's internal common package
|
|
54
|
+
* (the subpath import doesn't resolve cleanly through the file-symlink
|
|
55
|
+
* Scrypted plugins use). The shape mirrors the upstream helper exactly
|
|
56
|
+
* so we can swap to the real one as soon as the resolution issue is
|
|
57
|
+
* addressed upstream.
|
|
58
|
+
*
|
|
59
|
+
* Each value is the full `-c:v <encoder> [extra-args...]` arg list. The
|
|
60
|
+
* code below pulls element [1] for the `-c:v` identifier and propagates
|
|
61
|
+
* the rest through `extraOutputArgs`.
|
|
62
|
+
*/
|
|
63
|
+
function getCompositeH264EncoderArgs(): { [label: string]: string[] } {
|
|
64
|
+
const args: { [label: string]: string[] } = {};
|
|
65
|
+
const platform = os.platform();
|
|
66
|
+
if (platform === "darwin") {
|
|
67
|
+
args["VideoToolbox"] = ["-c:v", "h264_videotoolbox"];
|
|
68
|
+
} else if (platform === "win32") {
|
|
69
|
+
args["Intel QuickSync"] = ["-c:v", "h264_qsv"];
|
|
70
|
+
args["AMD"] = ["-c:v", "h264_amf"];
|
|
71
|
+
args["Nvidia"] = ["-c:v", "h264_nvenc"];
|
|
72
|
+
} else if (platform === "linux") {
|
|
73
|
+
args["V4L2 (Raspberry Pi)"] = [
|
|
74
|
+
"-pix_fmt", "yuv420p",
|
|
75
|
+
"-c:v", "h264_v4l2m2m",
|
|
76
|
+
];
|
|
77
|
+
args["VAAPI"] = ["-c:v", "h264_vaapi"];
|
|
78
|
+
args["Nvidia"] = ["-c:v", "h264_nvenc"];
|
|
79
|
+
}
|
|
80
|
+
args["libx264 (Software)"] = ["-c:v", "libx264"];
|
|
81
|
+
return args;
|
|
82
|
+
}
|
|
47
83
|
import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
48
84
|
import {
|
|
49
85
|
ReolinkCameraAutotracking,
|
|
@@ -683,6 +719,109 @@ export class ReolinkCamera
|
|
|
683
719
|
this.scheduleStreamManagerRestart("compositeDisableTranscode changed");
|
|
684
720
|
},
|
|
685
721
|
},
|
|
722
|
+
// ─── Composite ffmpeg tuning ─────────────────────────────────
|
|
723
|
+
// Encoder + quality knobs. Defaults preserve the previous behavior
|
|
724
|
+
// (libx264 / ultrafast / crf 23 / 1s GOP). Switching the encoder
|
|
725
|
+
// away from libx264 is the single biggest perf win — try the HW
|
|
726
|
+
// encoder for your host: h264_videotoolbox (macOS), h264_qsv or
|
|
727
|
+
// h264_vaapi (Intel/AMD), h264_nvenc (NVIDIA), h264_v4l2m2m
|
|
728
|
+
// (Raspberry Pi). When you change the encoder the libx264-specific
|
|
729
|
+
// preset/CRF knobs are silently dropped — use "Composite: extra
|
|
730
|
+
// output args" to pass the encoder's own options.
|
|
731
|
+
// Encoder choices come straight from Scrypted's per-platform
|
|
732
|
+
// hardware-acceleration helper, so the dropdown only ever offers
|
|
733
|
+
// encoders that ffmpeg on this host actually supports — Mac sees
|
|
734
|
+
// VideoToolbox, Windows sees QuickSync/AMD/Nvidia, Linux sees
|
|
735
|
+
// V4L2/VAAPI/Nvidia, Raspberry Pi sees its dedicated V4L2 path.
|
|
736
|
+
// The displayed label is human-readable ("VideoToolbox",
|
|
737
|
+
// "Intel QuickSync", "libx264 (Software)") and the actual ffmpeg
|
|
738
|
+
// `-c:v` identifier is resolved at use time via
|
|
739
|
+
// `resolveCompositeVideoEncoder()` below.
|
|
740
|
+
compositeVideoEncoder: {
|
|
741
|
+
title: "Composite: ffmpeg encoder",
|
|
742
|
+
description:
|
|
743
|
+
"Output video encoder. Choices are limited to what ffmpeg supports on this host. Hardware encoders typically reduce CPU 5-10× vs libx264.",
|
|
744
|
+
type: "string",
|
|
745
|
+
defaultValue: "libx264 (Software)",
|
|
746
|
+
group: "Composite stream",
|
|
747
|
+
hide: true,
|
|
748
|
+
choices: Object.keys(getCompositeH264EncoderArgs()),
|
|
749
|
+
onPut: async () => {
|
|
750
|
+
this.scheduleStreamManagerRestart("compositeVideoEncoder changed");
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
compositeEncoderPreset: {
|
|
754
|
+
title: "Composite: libx264 preset",
|
|
755
|
+
description:
|
|
756
|
+
"Only used when encoder is libx264. ultrafast = least CPU / worst compression; placebo = the opposite. Default ultrafast.",
|
|
757
|
+
type: "string",
|
|
758
|
+
defaultValue: "ultrafast",
|
|
759
|
+
group: "Composite stream",
|
|
760
|
+
hide: true,
|
|
761
|
+
choices: [
|
|
762
|
+
"ultrafast",
|
|
763
|
+
"superfast",
|
|
764
|
+
"veryfast",
|
|
765
|
+
"faster",
|
|
766
|
+
"fast",
|
|
767
|
+
"medium",
|
|
768
|
+
"slow",
|
|
769
|
+
"slower",
|
|
770
|
+
"veryslow",
|
|
771
|
+
"placebo",
|
|
772
|
+
],
|
|
773
|
+
onPut: async () => {
|
|
774
|
+
this.scheduleStreamManagerRestart("compositeEncoderPreset changed");
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
compositeCrf: {
|
|
778
|
+
title: "Composite: libx264 CRF (quality)",
|
|
779
|
+
description:
|
|
780
|
+
"Only used when encoder is libx264. 0 = lossless (huge), 23 = visually lossless, 51 = worst. Drop by 2 to roughly halve bitrate at the same quality.",
|
|
781
|
+
type: "number",
|
|
782
|
+
defaultValue: 23,
|
|
783
|
+
group: "Composite stream",
|
|
784
|
+
hide: true,
|
|
785
|
+
onPut: async () => {
|
|
786
|
+
this.scheduleStreamManagerRestart("compositeCrf changed");
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
compositeGopSeconds: {
|
|
790
|
+
title: "Composite: keyframe interval (seconds)",
|
|
791
|
+
description:
|
|
792
|
+
"Lower = faster mid-stream join + larger stream; higher = better compression + slower join. Default 1s.",
|
|
793
|
+
type: "number",
|
|
794
|
+
defaultValue: 1,
|
|
795
|
+
group: "Composite stream",
|
|
796
|
+
hide: true,
|
|
797
|
+
onPut: async () => {
|
|
798
|
+
this.scheduleStreamManagerRestart("compositeGopSeconds changed");
|
|
799
|
+
},
|
|
800
|
+
},
|
|
801
|
+
compositeExtraGlobalArgs: {
|
|
802
|
+
title: "Composite: extra global args",
|
|
803
|
+
description:
|
|
804
|
+
"Free-form ffmpeg args inserted BEFORE the inputs. Use for hardware decode hints, e.g. \"-hwaccel videotoolbox\" or \"-hwaccel qsv -qsv_device /dev/dri/renderD128\". Whitespace-separated. Invalid args WILL crash ffmpeg.",
|
|
805
|
+
type: "string",
|
|
806
|
+
defaultValue: "",
|
|
807
|
+
group: "Composite stream",
|
|
808
|
+
hide: true,
|
|
809
|
+
onPut: async () => {
|
|
810
|
+
this.scheduleStreamManagerRestart("compositeExtraGlobalArgs changed");
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
compositeExtraOutputArgs: {
|
|
814
|
+
title: "Composite: extra output args",
|
|
815
|
+
description:
|
|
816
|
+
"Free-form ffmpeg args inserted JUST BEFORE the output. Use for encoder-specific options when encoder ≠ libx264, e.g. \"-q:v 23\" (qsv) or \"-preset fast -rc cbr\" (nvenc). Whitespace-separated. Invalid args WILL crash ffmpeg.",
|
|
817
|
+
type: "string",
|
|
818
|
+
defaultValue: "",
|
|
819
|
+
group: "Composite stream",
|
|
820
|
+
hide: true,
|
|
821
|
+
onPut: async () => {
|
|
822
|
+
this.scheduleStreamManagerRestart("compositeExtraOutputArgs changed");
|
|
823
|
+
},
|
|
824
|
+
},
|
|
686
825
|
// ─── E-mail Push ─────────────────────────────────────────────
|
|
687
826
|
// Per-camera knobs that pair with the singleton
|
|
688
827
|
// `Reolink E-mail Push Server` device. Hidden by default and
|
|
@@ -1889,10 +2028,10 @@ export class ReolinkCamera
|
|
|
1889
2028
|
/**
|
|
1890
2029
|
* Initialize or recreate the StreamManager, taking into account multifocal composite options.
|
|
1891
2030
|
*/
|
|
1892
|
-
protected initStreamManager(
|
|
2031
|
+
protected async initStreamManager(
|
|
1893
2032
|
logger?: Console,
|
|
1894
2033
|
forceRecreate: boolean = false,
|
|
1895
|
-
): void {
|
|
2034
|
+
): Promise<void> {
|
|
1896
2035
|
const { username, password } = this.storageSettings.values;
|
|
1897
2036
|
// Ensure logger is always valid - use provided logger or get from device, fallback to console
|
|
1898
2037
|
const validLogger = logger || this.getBaichuanLogger() || console;
|
|
@@ -1917,7 +2056,73 @@ export class ReolinkCamera
|
|
|
1917
2056
|
rtspChannel,
|
|
1918
2057
|
compositeAssumeH264,
|
|
1919
2058
|
compositeDisableTranscode,
|
|
2059
|
+
compositeVideoEncoder,
|
|
2060
|
+
compositeEncoderPreset,
|
|
2061
|
+
compositeCrf,
|
|
2062
|
+
compositeGopSeconds,
|
|
2063
|
+
compositeExtraGlobalArgs,
|
|
2064
|
+
compositeExtraOutputArgs,
|
|
1920
2065
|
} = this.storageSettings.values;
|
|
2066
|
+
// Get the path to the ffmpeg binary Scrypted ships with. The
|
|
2067
|
+
// composite stream spawns ffmpeg directly; on Windows / Electron
|
|
2068
|
+
// the host has a stripped PATH so a bare `ffmpeg` ENOENTs — we
|
|
2069
|
+
// MUST pass the absolute path the SDK knows about.
|
|
2070
|
+
let ffmpegPath: string | undefined;
|
|
2071
|
+
try {
|
|
2072
|
+
ffmpegPath = await sdk.mediaManager.getFFmpegPath();
|
|
2073
|
+
} catch {
|
|
2074
|
+
// Older Scrypted runtimes / weird environments may not expose
|
|
2075
|
+
// this. Fall through to the library's default ("ffmpeg" via PATH);
|
|
2076
|
+
// worst case the composite fails to spawn and the user sees a
|
|
2077
|
+
// friendly error.
|
|
2078
|
+
ffmpegPath = undefined;
|
|
2079
|
+
}
|
|
2080
|
+
// Whitespace-split the free-form extra args. Empty string -> [].
|
|
2081
|
+
const splitArgs = (s: string | undefined): string[] | undefined => {
|
|
2082
|
+
if (typeof s !== "string") return undefined;
|
|
2083
|
+
const arr = s
|
|
2084
|
+
.trim()
|
|
2085
|
+
.split(/\s+/)
|
|
2086
|
+
.filter((a) => a.length > 0);
|
|
2087
|
+
return arr.length > 0 ? arr : undefined;
|
|
2088
|
+
};
|
|
2089
|
+
const extraGlobalArgs = splitArgs(compositeExtraGlobalArgs);
|
|
2090
|
+
const extraOutputArgs = splitArgs(compositeExtraOutputArgs);
|
|
2091
|
+
|
|
2092
|
+
// Resolve the human-readable encoder label the user picked back
|
|
2093
|
+
// into the actual ffmpeg `-c:v` identifier. The helper returns
|
|
2094
|
+
// arrays shaped like ['-c:v', 'h264_videotoolbox', ...extras] —
|
|
2095
|
+
// we pull element [1] for the encoder name and propagate any
|
|
2096
|
+
// additional pix-fmt / pix-encoder args via extraOutputArgs so
|
|
2097
|
+
// they end up just before the output pipe.
|
|
2098
|
+
let resolvedVideoEncoder: string | undefined;
|
|
2099
|
+
let encoderImpliedOutputArgs: string[] = [];
|
|
2100
|
+
if (compositeVideoEncoder) {
|
|
2101
|
+
try {
|
|
2102
|
+
const encoderArgs = getCompositeH264EncoderArgs()[compositeVideoEncoder];
|
|
2103
|
+
// Some entries (V4L2/Raspberry Pi) carry pre-`-c:v` pixel-format
|
|
2104
|
+
// args. Find where `-c:v` is in the array — everything before
|
|
2105
|
+
// becomes encoderImpliedOutputArgs (pixfmt), the element after
|
|
2106
|
+
// `-c:v` is the encoder identifier, and anything after that is
|
|
2107
|
+
// also part of encoderImpliedOutputArgs.
|
|
2108
|
+
const cvIdx = encoderArgs?.indexOf("-c:v") ?? -1;
|
|
2109
|
+
if (encoderArgs && cvIdx >= 0 && encoderArgs[cvIdx + 1]) {
|
|
2110
|
+
resolvedVideoEncoder = encoderArgs[cvIdx + 1]!;
|
|
2111
|
+
// Everything except the `-c:v <encoder>` pair becomes extra
|
|
2112
|
+
// output args, preserving any pixel-format hints the upstream
|
|
2113
|
+
// helper baked in (e.g. yuv420p for V4L2).
|
|
2114
|
+
encoderImpliedOutputArgs = [
|
|
2115
|
+
...encoderArgs.slice(0, cvIdx),
|
|
2116
|
+
...encoderArgs.slice(cvIdx + 2),
|
|
2117
|
+
];
|
|
2118
|
+
}
|
|
2119
|
+
} catch {
|
|
2120
|
+
// ignore; fall back to library default below
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
const finalExtraOutputArgs = encoderImpliedOutputArgs.length || extraOutputArgs
|
|
2124
|
+
? [...encoderImpliedOutputArgs, ...(extraOutputArgs ?? [])]
|
|
2125
|
+
: undefined;
|
|
1921
2126
|
|
|
1922
2127
|
// On NVR/Hub, TrackMix lenses are selected via stream variant, not via a separate channel.
|
|
1923
2128
|
// Use rtspChannel for BOTH wide and tele so the library can request tele via streamType/variant.
|
|
@@ -1967,6 +2172,20 @@ export class ReolinkCamera
|
|
|
1967
2172
|
forceH264: true,
|
|
1968
2173
|
assumeH264Inputs: compositeAssumeH264 ?? true,
|
|
1969
2174
|
disableTranscode: compositeDisableTranscode ?? false,
|
|
2175
|
+
// ffmpeg knobs — only include when set so the library defaults stay live.
|
|
2176
|
+
...(ffmpegPath ? { ffmpegPath } : {}),
|
|
2177
|
+
...(resolvedVideoEncoder
|
|
2178
|
+
? { videoEncoder: resolvedVideoEncoder }
|
|
2179
|
+
: {}),
|
|
2180
|
+
...(compositeEncoderPreset
|
|
2181
|
+
? { encoderPreset: compositeEncoderPreset }
|
|
2182
|
+
: {}),
|
|
2183
|
+
...(typeof compositeCrf === "number" ? { crf: compositeCrf } : {}),
|
|
2184
|
+
...(typeof compositeGopSeconds === "number"
|
|
2185
|
+
? { gopSeconds: compositeGopSeconds }
|
|
2186
|
+
: {}),
|
|
2187
|
+
...(extraGlobalArgs ? { extraGlobalArgs } : {}),
|
|
2188
|
+
...(finalExtraOutputArgs ? { extraOutputArgs: finalExtraOutputArgs } : {}),
|
|
1970
2189
|
};
|
|
1971
2190
|
}
|
|
1972
2191
|
|
|
@@ -1995,7 +2214,7 @@ export class ReolinkCamera
|
|
|
1995
2214
|
logger.log(
|
|
1996
2215
|
"Restarting StreamManager due to PIP/composite settings change",
|
|
1997
2216
|
);
|
|
1998
|
-
this.initStreamManager(logger, true);
|
|
2217
|
+
await this.initStreamManager(logger, true);
|
|
1999
2218
|
|
|
2000
2219
|
// Invalidate snapshot cache for battery/multifocal-battery so that
|
|
2001
2220
|
// the next snapshot reflects the new PIP/composite configuration.
|
|
@@ -3560,7 +3779,7 @@ export class ReolinkCamera
|
|
|
3560
3779
|
const logger = this.getBaichuanLogger();
|
|
3561
3780
|
logger.warn("StreamManager not initialized, initializing now...");
|
|
3562
3781
|
try {
|
|
3563
|
-
this.initStreamManager(logger);
|
|
3782
|
+
await this.initStreamManager(logger);
|
|
3564
3783
|
} catch (e) {
|
|
3565
3784
|
logger.error(
|
|
3566
3785
|
"Failed to initialize StreamManager in getVideoStream",
|
|
@@ -3788,6 +4007,18 @@ export class ReolinkCamera
|
|
|
3788
4007
|
this.storageSettings.settings.pipPosition.hide = !this.isMultiFocal;
|
|
3789
4008
|
this.storageSettings.settings.pipSize.hide = !this.isMultiFocal;
|
|
3790
4009
|
this.storageSettings.settings.pipMargin.hide = !this.isMultiFocal;
|
|
4010
|
+
this.storageSettings.settings.compositeAssumeH264.hide = !this.isMultiFocal;
|
|
4011
|
+
this.storageSettings.settings.compositeDisableTranscode.hide =
|
|
4012
|
+
!this.isMultiFocal;
|
|
4013
|
+
this.storageSettings.settings.compositeVideoEncoder.hide = !this.isMultiFocal;
|
|
4014
|
+
this.storageSettings.settings.compositeEncoderPreset.hide =
|
|
4015
|
+
!this.isMultiFocal;
|
|
4016
|
+
this.storageSettings.settings.compositeCrf.hide = !this.isMultiFocal;
|
|
4017
|
+
this.storageSettings.settings.compositeGopSeconds.hide = !this.isMultiFocal;
|
|
4018
|
+
this.storageSettings.settings.compositeExtraGlobalArgs.hide =
|
|
4019
|
+
!this.isMultiFocal;
|
|
4020
|
+
this.storageSettings.settings.compositeExtraOutputArgs.hide =
|
|
4021
|
+
!this.isMultiFocal;
|
|
3791
4022
|
|
|
3792
4023
|
const hideUid = !this.isBattery || this.isOnNvr || !!this.multiFocalDevice;
|
|
3793
4024
|
this.storageSettings.settings.uid.hide = hideUid;
|
|
@@ -3844,7 +4075,7 @@ export class ReolinkCamera
|
|
|
3844
4075
|
}
|
|
3845
4076
|
|
|
3846
4077
|
try {
|
|
3847
|
-
this.initStreamManager();
|
|
4078
|
+
await this.initStreamManager();
|
|
3848
4079
|
} catch (e) {
|
|
3849
4080
|
logger.error(
|
|
3850
4081
|
"Failed to initialize StreamManager in init",
|