@apocaliss92/scrypted-reolink-native 0.5.42 → 0.5.43

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/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.42",
3
+ "version": "0.5.43",
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.5.2",
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
@@ -683,6 +683,107 @@ export class ReolinkCamera
683
683
  this.scheduleStreamManagerRestart("compositeDisableTranscode changed");
684
684
  },
685
685
  },
686
+ // ─── Composite ffmpeg tuning ─────────────────────────────────
687
+ // Encoder + quality knobs. Defaults preserve the previous behavior
688
+ // (libx264 / ultrafast / crf 23 / 1s GOP). Switching the encoder
689
+ // away from libx264 is the single biggest perf win — try the HW
690
+ // encoder for your host: h264_videotoolbox (macOS), h264_qsv or
691
+ // h264_vaapi (Intel/AMD), h264_nvenc (NVIDIA), h264_v4l2m2m
692
+ // (Raspberry Pi). When you change the encoder the libx264-specific
693
+ // preset/CRF knobs are silently dropped — use "Composite: extra
694
+ // output args" to pass the encoder's own options.
695
+ compositeVideoEncoder: {
696
+ title: "Composite: ffmpeg encoder",
697
+ description:
698
+ "Output video encoder. Default libx264 (software). Change to the right hardware encoder for a 5-10× CPU win.",
699
+ type: "string",
700
+ defaultValue: "libx264",
701
+ group: "Composite stream",
702
+ hide: true,
703
+ choices: [
704
+ "libx264",
705
+ "h264_videotoolbox",
706
+ "h264_qsv",
707
+ "h264_vaapi",
708
+ "h264_nvenc",
709
+ "h264_v4l2m2m",
710
+ ],
711
+ onPut: async () => {
712
+ this.scheduleStreamManagerRestart("compositeVideoEncoder changed");
713
+ },
714
+ },
715
+ compositeEncoderPreset: {
716
+ title: "Composite: libx264 preset",
717
+ description:
718
+ "Only used when encoder is libx264. ultrafast = least CPU / worst compression; placebo = the opposite. Default ultrafast.",
719
+ type: "string",
720
+ defaultValue: "ultrafast",
721
+ group: "Composite stream",
722
+ hide: true,
723
+ choices: [
724
+ "ultrafast",
725
+ "superfast",
726
+ "veryfast",
727
+ "faster",
728
+ "fast",
729
+ "medium",
730
+ "slow",
731
+ "slower",
732
+ "veryslow",
733
+ "placebo",
734
+ ],
735
+ onPut: async () => {
736
+ this.scheduleStreamManagerRestart("compositeEncoderPreset changed");
737
+ },
738
+ },
739
+ compositeCrf: {
740
+ title: "Composite: libx264 CRF (quality)",
741
+ description:
742
+ "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.",
743
+ type: "number",
744
+ defaultValue: 23,
745
+ group: "Composite stream",
746
+ hide: true,
747
+ onPut: async () => {
748
+ this.scheduleStreamManagerRestart("compositeCrf changed");
749
+ },
750
+ },
751
+ compositeGopSeconds: {
752
+ title: "Composite: keyframe interval (seconds)",
753
+ description:
754
+ "Lower = faster mid-stream join + larger stream; higher = better compression + slower join. Default 1s.",
755
+ type: "number",
756
+ defaultValue: 1,
757
+ group: "Composite stream",
758
+ hide: true,
759
+ onPut: async () => {
760
+ this.scheduleStreamManagerRestart("compositeGopSeconds changed");
761
+ },
762
+ },
763
+ compositeExtraGlobalArgs: {
764
+ title: "Composite: extra global args",
765
+ description:
766
+ "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.",
767
+ type: "string",
768
+ defaultValue: "",
769
+ group: "Composite stream",
770
+ hide: true,
771
+ onPut: async () => {
772
+ this.scheduleStreamManagerRestart("compositeExtraGlobalArgs changed");
773
+ },
774
+ },
775
+ compositeExtraOutputArgs: {
776
+ title: "Composite: extra output args",
777
+ description:
778
+ "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.",
779
+ type: "string",
780
+ defaultValue: "",
781
+ group: "Composite stream",
782
+ hide: true,
783
+ onPut: async () => {
784
+ this.scheduleStreamManagerRestart("compositeExtraOutputArgs changed");
785
+ },
786
+ },
686
787
  // ─── E-mail Push ─────────────────────────────────────────────
687
788
  // Per-camera knobs that pair with the singleton
688
789
  // `Reolink E-mail Push Server` device. Hidden by default and
@@ -1889,10 +1990,10 @@ export class ReolinkCamera
1889
1990
  /**
1890
1991
  * Initialize or recreate the StreamManager, taking into account multifocal composite options.
1891
1992
  */
1892
- protected initStreamManager(
1993
+ protected async initStreamManager(
1893
1994
  logger?: Console,
1894
1995
  forceRecreate: boolean = false,
1895
- ): void {
1996
+ ): Promise<void> {
1896
1997
  const { username, password } = this.storageSettings.values;
1897
1998
  // Ensure logger is always valid - use provided logger or get from device, fallback to console
1898
1999
  const validLogger = logger || this.getBaichuanLogger() || console;
@@ -1917,7 +2018,38 @@ export class ReolinkCamera
1917
2018
  rtspChannel,
1918
2019
  compositeAssumeH264,
1919
2020
  compositeDisableTranscode,
2021
+ compositeVideoEncoder,
2022
+ compositeEncoderPreset,
2023
+ compositeCrf,
2024
+ compositeGopSeconds,
2025
+ compositeExtraGlobalArgs,
2026
+ compositeExtraOutputArgs,
1920
2027
  } = this.storageSettings.values;
2028
+ // Get the path to the ffmpeg binary Scrypted ships with. The
2029
+ // composite stream spawns ffmpeg directly; on Windows / Electron
2030
+ // the host has a stripped PATH so a bare `ffmpeg` ENOENTs — we
2031
+ // MUST pass the absolute path the SDK knows about.
2032
+ let ffmpegPath: string | undefined;
2033
+ try {
2034
+ ffmpegPath = await sdk.mediaManager.getFFmpegPath();
2035
+ } catch {
2036
+ // Older Scrypted runtimes / weird environments may not expose
2037
+ // this. Fall through to the library's default ("ffmpeg" via PATH);
2038
+ // worst case the composite fails to spawn and the user sees a
2039
+ // friendly error.
2040
+ ffmpegPath = undefined;
2041
+ }
2042
+ // Whitespace-split the free-form extra args. Empty string -> [].
2043
+ const splitArgs = (s: string | undefined): string[] | undefined => {
2044
+ if (typeof s !== "string") return undefined;
2045
+ const arr = s
2046
+ .trim()
2047
+ .split(/\s+/)
2048
+ .filter((a) => a.length > 0);
2049
+ return arr.length > 0 ? arr : undefined;
2050
+ };
2051
+ const extraGlobalArgs = splitArgs(compositeExtraGlobalArgs);
2052
+ const extraOutputArgs = splitArgs(compositeExtraOutputArgs);
1921
2053
 
1922
2054
  // On NVR/Hub, TrackMix lenses are selected via stream variant, not via a separate channel.
1923
2055
  // Use rtspChannel for BOTH wide and tele so the library can request tele via streamType/variant.
@@ -1967,6 +2099,20 @@ export class ReolinkCamera
1967
2099
  forceH264: true,
1968
2100
  assumeH264Inputs: compositeAssumeH264 ?? true,
1969
2101
  disableTranscode: compositeDisableTranscode ?? false,
2102
+ // ffmpeg knobs — only include when set so the library defaults stay live.
2103
+ ...(ffmpegPath ? { ffmpegPath } : {}),
2104
+ ...(compositeVideoEncoder
2105
+ ? { videoEncoder: compositeVideoEncoder }
2106
+ : {}),
2107
+ ...(compositeEncoderPreset
2108
+ ? { encoderPreset: compositeEncoderPreset }
2109
+ : {}),
2110
+ ...(typeof compositeCrf === "number" ? { crf: compositeCrf } : {}),
2111
+ ...(typeof compositeGopSeconds === "number"
2112
+ ? { gopSeconds: compositeGopSeconds }
2113
+ : {}),
2114
+ ...(extraGlobalArgs ? { extraGlobalArgs } : {}),
2115
+ ...(extraOutputArgs ? { extraOutputArgs } : {}),
1970
2116
  };
1971
2117
  }
1972
2118
 
@@ -1995,7 +2141,7 @@ export class ReolinkCamera
1995
2141
  logger.log(
1996
2142
  "Restarting StreamManager due to PIP/composite settings change",
1997
2143
  );
1998
- this.initStreamManager(logger, true);
2144
+ await this.initStreamManager(logger, true);
1999
2145
 
2000
2146
  // Invalidate snapshot cache for battery/multifocal-battery so that
2001
2147
  // the next snapshot reflects the new PIP/composite configuration.
@@ -3560,7 +3706,7 @@ export class ReolinkCamera
3560
3706
  const logger = this.getBaichuanLogger();
3561
3707
  logger.warn("StreamManager not initialized, initializing now...");
3562
3708
  try {
3563
- this.initStreamManager(logger);
3709
+ await this.initStreamManager(logger);
3564
3710
  } catch (e) {
3565
3711
  logger.error(
3566
3712
  "Failed to initialize StreamManager in getVideoStream",
@@ -3788,6 +3934,18 @@ export class ReolinkCamera
3788
3934
  this.storageSettings.settings.pipPosition.hide = !this.isMultiFocal;
3789
3935
  this.storageSettings.settings.pipSize.hide = !this.isMultiFocal;
3790
3936
  this.storageSettings.settings.pipMargin.hide = !this.isMultiFocal;
3937
+ this.storageSettings.settings.compositeAssumeH264.hide = !this.isMultiFocal;
3938
+ this.storageSettings.settings.compositeDisableTranscode.hide =
3939
+ !this.isMultiFocal;
3940
+ this.storageSettings.settings.compositeVideoEncoder.hide = !this.isMultiFocal;
3941
+ this.storageSettings.settings.compositeEncoderPreset.hide =
3942
+ !this.isMultiFocal;
3943
+ this.storageSettings.settings.compositeCrf.hide = !this.isMultiFocal;
3944
+ this.storageSettings.settings.compositeGopSeconds.hide = !this.isMultiFocal;
3945
+ this.storageSettings.settings.compositeExtraGlobalArgs.hide =
3946
+ !this.isMultiFocal;
3947
+ this.storageSettings.settings.compositeExtraOutputArgs.hide =
3948
+ !this.isMultiFocal;
3791
3949
 
3792
3950
  const hideUid = !this.isBattery || this.isOnNvr || !!this.multiFocalDevice;
3793
3951
  this.storageSettings.settings.uid.hide = hideUid;
@@ -3844,7 +4002,7 @@ export class ReolinkCamera
3844
4002
  }
3845
4003
 
3846
4004
  try {
3847
- this.initStreamManager();
4005
+ await this.initStreamManager();
3848
4006
  } catch (e) {
3849
4007
  logger.error(
3850
4008
  "Failed to initialize StreamManager in init",