Compare commits

...

27 Commits

Author SHA1 Message Date
Koushik Dutta
ab00ade016 install: bump node 2025-05-07 09:32:35 -07:00
Koushik Dutta
6cfc3db05c server: fix package lock 2025-04-29 11:43:02 -07:00
Koushik Dutta
95aa58ce38 postbeta 2025-04-28 20:30:02 -07:00
Koushik Dutta
0d88b4746b ncnn: publish face/text 2025-04-28 12:54:15 -07:00
Koushik Dutta
8c4beeb3a0 Merge branch 'main' of github.com:koush/scrypted 2025-04-28 12:09:32 -07:00
Koushik Dutta
4846cfaddf ncnn: face recognition support 2025-04-28 12:09:26 -07:00
Koushik Dutta
4e14f7fd6f common: rtsp server basic auth fix 2025-04-28 12:09:13 -07:00
Roman Sokolov
266be72606 Fixed an issue for some devices. They send screen width as not even value. (#1797) 2025-04-27 14:04:00 -07:00
Koushik Dutta
6a1970c075 ncnn: update model list 2025-04-27 10:15:54 -07:00
Koushik Dutta
0575d98424 ncnn: publish 2025-04-26 21:31:06 -07:00
Koushik Dutta
cdf42fc1a2 rebroadcast: fix url escaping for basic auth 2025-04-24 19:23:23 -07:00
Koushik Dutta
fc1fabc49e common/webrtc: expand h265 keyframe types 2025-04-22 22:20:24 -07:00
Koushik Dutta
4e08daecb2 Merge branch 'main' of github.com:koush/scrypted 2025-04-21 09:02:30 -07:00
Koushik Dutta
58b27805ba common: fix sdp default rtpmap props 2025-04-21 09:02:25 -07:00
Koushik Dutta
b37c6bbd06 postbeta 2025-04-19 12:07:22 -07:00
Koushik Dutta
8eca02d819 server: move cluster fork timeout to prior to fork 2025-04-19 12:07:07 -07:00
Koushik Dutta
0efdb34114 postbeta 2025-04-19 10:53:40 -07:00
Koushik Dutta
1a25100de2 server: replace mime with mime-type which isnt esmodule 2025-04-19 10:53:30 -07:00
Koushik Dutta
51e0a8836d videoanalysis: fix occupancy sensor picking 2025-04-19 08:11:43 -07:00
Koushik Dutta
562d0839b7 videoanalysis: fix smart sensor picking 2025-04-19 08:10:37 -07:00
Koushik Dutta
e3df6accea videoanalysis: make sure duplciate nvr vs camera detections dont cause ui weirdness 2025-04-18 12:43:26 -07:00
Koushik Dutta
03d159a89c server: remove debug code 2025-04-18 11:51:00 -07:00
Koushik Dutta
4ead4726a9 postbeta 2025-04-18 11:49:45 -07:00
Koushik Dutta
b06ef623b3 server: fix potential socket leak if cluster server is down 2025-04-18 11:49:36 -07:00
Koushik Dutta
8edb157e2a snapshot: fix crop and scale 2025-04-17 16:03:03 -07:00
Koushik Dutta
155a1ceb38 rpc: publish 2025-04-15 15:10:30 -07:00
Koushik Dutta
1cb6212fc6 webrtc: implement default clocks for assigned payload types 2025-04-15 07:53:28 -07:00
33 changed files with 554 additions and 303 deletions

View File

@@ -93,8 +93,12 @@ export const H265_NAL_TYPE_AGG = 48;
export const H265_NAL_TYPE_VPS = 32;
export const H265_NAL_TYPE_SPS = 33;
export const H265_NAL_TYPE_PPS = 34;
export const H265_NAL_TYPE_IDR_N = 19;
export const H265_NAL_TYPE_IDR_W = 20;
export const H265_NAL_TYPE_BLA_W_LP = 16;
export const H265_NAL_TYPE_BLA_W_RADL = 17;
export const H265_NAL_TYPE_BLA_N_LP = 18;
export const H265_NAL_TYPE_IDR_W_RADL = 19;
export const H265_NAL_TYPE_IDR_N_LP = 20;
export const H265_NAL_TYPE_CRA_NUT = 21;
export const H265_NAL_TYPE_FU = 49;
export const H265_NAL_TYPE_SEI_PREFIX = 39;
export const H265_NAL_TYPE_SEI_SUFFIX = 40;
@@ -252,6 +256,26 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
return ret;
}
export function isH265KeyFrameRelatedInSet(naluTypes: Set<number>, allowCodecInfo = true) {
if (naluTypes.has(H265_NAL_TYPE_IDR_N_LP)
|| naluTypes.has(H265_NAL_TYPE_IDR_W_RADL)
|| naluTypes.has(H265_NAL_TYPE_CRA_NUT)
|| naluTypes.has(H265_NAL_TYPE_BLA_N_LP)
|| naluTypes.has(H265_NAL_TYPE_BLA_W_LP)
|| naluTypes.has(H265_NAL_TYPE_BLA_W_RADL)) {
return true;
}
if (allowCodecInfo) {
if (naluTypes.has(H265_NAL_TYPE_VPS)
|| naluTypes.has(H265_NAL_TYPE_SPS)
|| naluTypes.has(H265_NAL_TYPE_PPS))
return true;
}
return false;
}
export function createRtspParser(options?: StreamParserOptions): RtspStreamParser {
let resolve: any;
@@ -283,12 +307,7 @@ export function createRtspParser(options?: StreamParserOptions): RtspStreamParse
else if (streamChunk.type === 'h265') {
const naluTypes = getStartedH265NaluTypes(streamChunk);
if (naluTypes.has(H265_NAL_TYPE_VPS)
|| naluTypes.has(H265_NAL_TYPE_SPS)
|| naluTypes.has(H265_NAL_TYPE_PPS)
|| naluTypes.has(H265_NAL_TYPE_IDR_N)
|| naluTypes.has(H265_NAL_TYPE_IDR_W)
) {
if (isH265KeyFrameRelatedInSet(naluTypes)) {
return streamChunks.slice(prebufferIndex);
}
}
@@ -670,9 +689,12 @@ export class RtspClient extends RtspBase {
// @ts-ignore
const { parseHTTPHeadersQuotedKeyValueSet } = await import('http-auth-utils/dist/utils');
const authedUrl = new URL(this.url);
const username = decodeURIComponent(authedUrl.username);
const password = decodeURIComponent(authedUrl.password);
if (this.wwwAuthenticate.includes('Basic')) {
const parsedUrl = new URL(this.url);
const hash = BASIC.computeHash({ username: parsedUrl.username, password: parsedUrl.password });
const hash = BASIC.computeHash({ username, password });
return `Basic ${hash}`;
}
@@ -692,10 +714,6 @@ export class RtspClient extends RtspBase {
REQUIRED_WWW_AUTHENTICATE_KEYS,
) as DigestWWWAuthenticateData;
const authedUrl = new URL(this.url);
const username = decodeURIComponent(authedUrl.username);
const password = decodeURIComponent(authedUrl.password);
const strippedUrl = new URL(url.toString());
strippedUrl.username = '';
strippedUrl.password = '';

View File

@@ -175,6 +175,8 @@ export type RTPMap = ReturnType<typeof parseRtpMap>;
export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string) {
const mlineType = mline.type;
const match = rtpmap?.match(/a=rtpmap:([\d]+) (.*?)\/([\d]+)(\/([\d]+))?/);
let channels = parseInt(match?.[5]) || undefined;
let payloadType = parseInt(match?.[1]);
rtpmap = rtpmap?.toLowerCase();
@@ -222,14 +224,20 @@ export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string
if (mline.payloadTypes?.includes(0)) {
codec = 'pcm_mulaw';
ffmpegEncoder = 'pcm_mulaw';
payloadType = 0;
channels = 1;
}
else if (mline.payloadTypes?.includes(8)) {
codec = 'pcm_alaw';
ffmpegEncoder = 'pcm_alaw';
payloadType = 8;
channels = 1;
}
else if (mline.payloadTypes?.includes(14)) {
codec = 'mp3';
ffmpegEncoder = 'mp3';
payloadType = 14;
channels = 2;
}
else {
// ffmpeg seems to omit the rtpmap type for pcm alaw when creating sdp?
@@ -239,17 +247,29 @@ export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string
// https://en.wikipedia.org/wiki/RTP_payload_formats
codec = 'pcm_alaw';
ffmpegEncoder = 'pcm_alaw';
payloadType = 8;
channels = 1;
}
}
// assigned payload types do not need to provide a clock, there is a default.
let clock = parseInt(match?.[3]);
if (!clock) {
clock = undefined;
if (codec === 'pcm_mulaw' || codec === 'pcm_alaw')
clock = 8000;
else if (codec === 'pcm_s16be')
clock = 16000;
}
return {
line: rtpmap,
codec,
ffmpegEncoder,
rawCodec: match?.[2],
clock: parseInt(match?.[3]),
channels: parseInt(match?.[5]) || undefined,
payloadType: parseInt(match?.[1]),
clock,
channels,
payloadType,
}
}

View File

@@ -42,9 +42,6 @@ RUN brew update
# in sequoia, brew node is unusable because it is not signed and can't access local network unless run as root.
# https://developer.apple.com/forums/thread/766270
# RUN_IGNORE brew install node@20
# NODE_PATH=$(brew --prefix node@20)
# NODE_BIN_PATH=$NODE_PATH/bin
RUN_IGNORE curl -L https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg -o /tmp/node.pkg
RUN_IGNORE sudo installer -pkg /tmp/node.pkg -target /
NODE_PATH=/usr/local # used to pass var test
@@ -88,13 +85,13 @@ RUN mkdir -p ~/Library/LaunchAgents
if [ ! -d "$NODE_PATH" ]
then
echo "Unable to determine node@20 path."
echo "Unable to determine node path."
exit 1
fi
if [ ! -d "$NODE_BIN_PATH" ]
then
echo "Unable to determine node@20 bin path."
echo "Unable to determine node bin path."
echo "$NODE_BIN_PATH does not exist."
exit 1
fi

View File

@@ -19,7 +19,7 @@ sc.exe stop scrypted.exe
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
# Install node.js
choco upgrade -y nodejs-lts --version=20.18.0
choco upgrade -y nodejs-lts --version=22.15.0
# Install VC Redist, which is necessary for portable python
choco install -y vcredist140
@@ -34,7 +34,7 @@ $SCRYPTED_WINDOWS_PYTHON_VERSION="-3.9"
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
# Workaround Windows Node no longer creating %APPDATA%\npm which causes npx to fail
# Fixed in newer versions of NPM but not the one bundled with Node 20
# Fixed in newer versions of NPM but not the one bundled with Node 2x
# https://github.com/nodejs/node/issues/53538
npm i -g npm

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/rpc",
"version": "0.0.7",
"version": "0.0.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/rpc",
"version": "0.0.7",
"version": "0.0.8",
"license": "ISC",
"devDependencies": {
"@types/node": "^18.11.18",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/rpc",
"version": "0.0.7",
"version": "0.0.8",
"description": "",
"main": "dist/index.js",
"scripts": {

View File

@@ -25,9 +25,6 @@ def cosine_similarity(vector_a, vector_b):
similarity = dot_product / (norm_a * norm_b)
return similarity
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "Vision-Predict")
class CoreMLFaceRecognition(FaceRecognizeDetection):
def __init__(self, plugin, nativeId: str):
super().__init__(plugin, nativeId)

View File

@@ -1,6 +1,6 @@
{
"scrypted.debugHost": "scrypted-nvr",
"scrypted.debugHost": "scrypted-amd",
"python.analysis.extraPaths": [
"./node_modules/@scrypted/sdk/types/scrypted_python"
]

View File

@@ -1,34 +1,41 @@
{
"name": "@scrypted/coreml",
"version": "0.1.77",
"name": "@scrypted/ncnn",
"version": "0.1.82",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/coreml",
"version": "0.1.77",
"name": "@scrypted/ncnn",
"version": "0.1.82",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.77",
"version": "0.5.12",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
@@ -41,11 +48,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10"
"typedoc": "^0.26.11"
}
},
"../sdk": {
@@ -61,22 +66,27 @@
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.26.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^22.10.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"stringify-object": "^3.3.0",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
}
}

View File

@@ -38,11 +38,15 @@
"ClusterForkInterface",
"ObjectDetection",
"ObjectDetectionPreview"
]
],
"labels": {
"require": [
"@scrypted/ncnn"
]
}
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.77"
"version": "0.1.82"
}

View File

@@ -3,9 +3,7 @@ from __future__ import annotations
import ast
import asyncio
import concurrent.futures
import os
import re
import threading
import traceback
from typing import Any, List, Tuple
@@ -18,11 +16,11 @@ import ncnn
from common import yolo
try:
from ncnn.face_recognition import NCNNFaceRecognition
from nc.face_recognition import NCNNFaceRecognition
except:
NCNNFaceRecognition = None
try:
from ncnn.text_recognition import NCNNTextRecognition
from nc.text_recognition import NCNNTextRecognition
except:
NCNNTextRecognition = None
from predict import Prediction, PredictPlugin
@@ -33,18 +31,14 @@ prepareExecutor = concurrent.futures.ThreadPoolExecutor(1, "NCNN-Prepare")
availableModels = [
"Default",
"scrypted_yolov10m_320",
"scrypted_yolov10n_320",
"scrypted_yolo_nas_s_320",
"scrypted_yolov9e_320",
"scrypted_yolov9c_relu_320",
"scrypted_yolov9m_relu_320",
"scrypted_yolov9s_relu_320",
"scrypted_yolov9t_relu_320",
"scrypted_yolov9c_320",
"scrypted_yolov9m_320",
"scrypted_yolov9s_320",
"scrypted_yolov9t_320",
"scrypted_yolov6n_320",
"scrypted_yolov6s_320",
"scrypted_yolov8n_320",
"ssdlite_mobilenet_v2",
"yolov4-tiny",
]
@@ -106,7 +100,7 @@ class NCNNPlugin(
}
files = [
f"{model}/best_converted.ncnn.bin",
f"{model}//best_converted.ncnn.param",
f"{model}/best_converted.ncnn.param",
]
for f in files:
@@ -123,14 +117,10 @@ class NCNNPlugin(
self.net = ncnn.Net()
# self.net.opt.use_vulkan_compute = True
# self.net.opt.use_winograd_convolution = False
# self.net.opt.use_sgemm_convolution = False
# self.net.opt.use_fp16_packed = False
# self.net.opt.use_fp16_storage = False
# self.net.opt.use_fp16_arithmetic = False
# self.net.opt.use_int8_storage = False
# self.net.opt.use_int8_arithmetic = False
self.net.opt.use_vulkan_compute = True
self.net.opt.use_fp16_packed = False
self.net.opt.use_fp16_storage = False
self.net.opt.use_fp16_arithmetic = False
self.net.load_param(paramFile)
self.net.load_model(binFile)
@@ -153,55 +143,55 @@ class NCNNPlugin(
# self.loop = asyncio.get_event_loop()
# self.minThreshold = 0.2
# self.faceDevice = None
# self.textDevice = None
self.faceDevice = None
self.textDevice = None
# if not self.forked:
# asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
if not self.forked:
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
# async def prepareRecognitionModels(self):
# try:
# devices = [
# {
# "nativeId": "facerecognition",
# "type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
# "interfaces": [
# scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
# scrypted_sdk.ScryptedInterface.ObjectDetection.value,
# ],
# "name": "NCNN Face Recognition",
# },
# ]
async def prepareRecognitionModels(self):
try:
devices = [
{
"nativeId": "facerecognition",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
"interfaces": [
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
],
"name": "NCNN Face Recognition",
},
]
# if NCNNTextRecognition:
# devices.append(
# {
# "nativeId": "textrecognition",
# "type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
# "interfaces": [
# scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
# scrypted_sdk.ScryptedInterface.ObjectDetection.value,
# ],
# "name": "NCNN Text Recognition",
# },
# )
if NCNNTextRecognition:
devices.append(
{
"nativeId": "textrecognition",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
"interfaces": [
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
],
"name": "NCNN Text Recognition",
},
)
# await scrypted_sdk.deviceManager.onDevicesChanged(
# {
# "devices": devices,
# }
# )
# except:
# pass
await scrypted_sdk.deviceManager.onDevicesChanged(
{
"devices": devices,
}
)
except:
pass
# async def getDevice(self, nativeId: str) -> Any:
# if nativeId == "facerecognition":
# self.faceDevice = self.faceDevice or NCNNFaceRecognition(self, nativeId)
# return self.faceDevice
# if nativeId == "textrecognition":
# self.textDevice = self.textDevice or NCNNTextRecognition(self, nativeId)
# return self.textDevice
# raise Exception("unknown device")
async def getDevice(self, nativeId: str) -> Any:
if nativeId == "facerecognition":
self.faceDevice = self.faceDevice or NCNNFaceRecognition(self, nativeId)
return self.faceDevice
if nativeId == "textrecognition":
self.textDevice = self.textDevice or NCNNTextRecognition(self, nativeId)
return self.textDevice
raise Exception("unknown device")
async def getSettings(self) -> list[Setting]:
model = self.storage.getItem("model") or "Default"
@@ -239,6 +229,8 @@ class NCNNPlugin(
im = np.expand_dims(input, axis=0)
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
im = im.astype(np.float32) / 255.0
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
im = im.reshape((1, 3, 320, 320)).squeeze(0)
im = np.ascontiguousarray(im) # contiguous
return im

View File

@@ -0,0 +1 @@
../../../openvino/src/ov/async_infer.py

View File

@@ -0,0 +1,111 @@
from __future__ import annotations
import asyncio
import numpy as np
from PIL import Image
import ncnn
from nc import async_infer
from predict.face_recognize import FaceRecognizeDetection
faceDetectPrepare, faceDetectPredict = async_infer.create_executors("FaceDetect")
faceRecognizePrepare, faceRecognizePredict = async_infer.create_executors(
"FaceRecognize"
)
class NCNNFaceRecognition(FaceRecognizeDetection):
def __init__(self, plugin, nativeId: str):
super().__init__(plugin=plugin, nativeId=nativeId)
self.prefer_relu = True
def downloadModel(self, model: str):
scrypted_yolov9 = "scrypted_yolov9" in model
ncnnmodel = "best_converted" if scrypted_yolov9 else model
model_version = "v1"
files = [
f"{model}/{ncnnmodel}.ncnn.bin",
f"{model}/{ncnnmodel}.ncnn.param",
]
for f in files:
p = self.downloadFile(
f"https://github.com/koush/ncnn-models/raw/main/{f}",
f"{model_version}/{f}",
)
if ".bin" in p:
binFile = p
if ".param" in p:
paramFile = p
net = ncnn.Net()
net.opt.use_vulkan_compute = True
net.opt.use_fp16_packed = False
net.opt.use_fp16_storage = False
net.opt.use_fp16_arithmetic = False
net.load_param(paramFile)
net.load_model(binFile)
input_name = net.input_names()[0]
return [net, input_name]
async def predictDetectModel(self, input: Image.Image):
def prepare():
im = np.array(input)
im = np.expand_dims(input, axis=0)
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
im = im.astype(np.float32) / 255.0
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
im = im.reshape((1, 3, 320, 320)).squeeze(0)
im = np.ascontiguousarray(im) # contiguous
return im
def predict(input_tensor):
net, input_name = self.detectModel
input_ncnn = ncnn.Mat(input_tensor)
ex = net.create_extractor()
ex.input(input_name, input_ncnn)
output_ncnn = ncnn.Mat()
ex.extract("out0", output_ncnn)
output_tensors = np.array(output_ncnn)
return output_tensors
input_tensor = await asyncio.get_event_loop().run_in_executor(
faceDetectPrepare, lambda: prepare()
)
return await asyncio.get_event_loop().run_in_executor(
faceDetectPredict, lambda: predict(input_tensor)
)
async def predictFaceModel(self, input: np.ndarray):
def prepare():
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
im = input.squeeze(0)
im = np.ascontiguousarray(im) # contiguous
return im
def predict(input_tensor):
net, input_name = self.faceModel
input_ncnn = ncnn.Mat(input_tensor)
ex = net.create_extractor()
ex.input(input_name, input_ncnn)
output_ncnn = ncnn.Mat()
ex.extract("out0", output_ncnn)
output_tensors = np.array(output_ncnn)
return output_tensors
input_tensor = await asyncio.get_event_loop().run_in_executor(
faceDetectPrepare, lambda: prepare()
)
return await asyncio.get_event_loop().run_in_executor(
faceDetectPredict, lambda: predict(input_tensor)
)

View File

@@ -0,0 +1,105 @@
from __future__ import annotations
import asyncio
import numpy as np
import ncnn
from nc import async_infer
from predict.text_recognize import TextRecognition
textDetectPrepare, textDetectPredict = async_infer.create_executors("TextDetect")
textRecognizePrepare, textRecognizePredict = async_infer.create_executors(
"TextRecognize"
)
class NCNNTextRecognition(TextRecognition):
def downloadModel(self, model: str):
model_version = "v1"
files = [
f"{model}/{model}.ncnn.bin",
f"{model}/{model}.ncnn.param",
]
for f in files:
p = self.downloadFile(
f"https://github.com/koush/ncnn-models/raw/main/{f}",
f"{model_version}/{f}",
)
if ".bin" in p:
binFile = p
if ".param" in p:
paramFile = p
net = ncnn.Net()
net.opt.use_vulkan_compute = True
net.opt.use_fp16_packed = False
net.opt.use_fp16_storage = False
net.opt.use_fp16_arithmetic = False
net.load_param(paramFile)
net.load_model(binFile)
input_name = net.input_names()[0]
return [net, input_name]
async def predictDetectModel(self, input: np.ndarray):
def prepare():
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
im = input.squeeze(0)
im = np.ascontiguousarray(im) # contiguous
return im
def predict(input_tensor):
net, input_name = self.detectModel
input_ncnn = ncnn.Mat(input_tensor)
ex = net.create_extractor()
ex.input(input_name, input_ncnn)
output_ncnn = ncnn.Mat()
ex.extract("out0", output_ncnn)
output_tensors = np.array(output_ncnn)
output_tensors = output_tensors.transpose((1, 2, 0))
# readd a batch dimension
output_tensors = np.expand_dims(output_tensors, axis=0)
return output_tensors
input_tensor = await asyncio.get_event_loop().run_in_executor(
textDetectPrepare, lambda: prepare()
)
return await asyncio.get_event_loop().run_in_executor(
textDetectPredict, lambda: predict(input_tensor)
)
async def predictTextModel(self, input: np.ndarray):
def prepare():
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
im = input.squeeze(0)
im = np.ascontiguousarray(im) # contiguous
return im
def predict(input_tensor):
net, input_name = self.textModel
input_ncnn = ncnn.Mat(input_tensor)
ex = net.create_extractor()
ex.input(input_name, input_ncnn)
output_ncnn = ncnn.Mat()
ex.extract("out0", output_ncnn)
output_tensors = np.array(output_ncnn)
# readd a batch dimension
output_tensors = np.expand_dims(output_tensors, axis=0)
return output_tensors
input_tensor = await asyncio.get_event_loop().run_in_executor(
textRecognizePrepare, lambda: prepare()
)
return await asyncio.get_event_loop().run_in_executor(
textRecognizePredict, lambda: predict(input_tensor)
)

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/objectdetector",
"version": "0.1.68",
"version": "0.1.72",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/objectdetector",
"version": "0.1.68",
"version": "0.1.72",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/objectdetector",
"version": "0.1.68",
"version": "0.1.72",
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -138,7 +138,6 @@ export class FFmpegVideoFrameGenerator extends ScryptedDeviceBase implements Vid
try {
reader();
const flush = async () => { };
while (!finished) {
frameDeferred = new Deferred();
@@ -151,9 +150,7 @@ export class FFmpegVideoFrameGenerator extends ScryptedDeviceBase implements Vid
yield {
__json_copy_serialize_children: true,
timestamp: 0,
queued: 0,
image,
flush,
};
}
finally {

View File

@@ -85,7 +85,8 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
this.storageSettings.settings.detections.onGet = async () => {
const objectDetector: ObjectDetector = this.storageSettings.values.objectDetector;
const choices = (await objectDetector?.getObjectTypes?.())?.classes || [];
const classes = (await objectDetector?.getObjectTypes?.())?.classes || [];
const choices = [...new Set(classes)];
return {
hide: !objectDetector,
choices,

View File

@@ -104,7 +104,8 @@ export class SmartOccupancySensor extends ScryptedDeviceBase implements Settings
this.storageSettings.settings.detections.onGet = async () => {
const objectDetection: ObjectDetection = this.storageSettings.values.objectDetection;
const choices = (await objectDetection?.getDetectionModel())?.classes || [];
const classes = (await objectDetection?.getDetectionModel())?.classes || []
const choices = [...new Set(classes)];
return {
hide: !objectDetection,
choices,

View File

@@ -2,6 +2,6 @@ import concurrent.futures
def create_executors(name: str):
prepare = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Prepare")
predict = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Predict")
prepare = concurrent.futures.ThreadPoolExecutor(1, f"{name}Prepare")
predict = concurrent.futures.ThreadPoolExecutor(1, f"{name}Predict")
return prepare, predict

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.57",
"version": "0.10.59",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.57",
"version": "0.10.59",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.57",
"version": "0.10.59",
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/snapshot",
"version": "0.2.58",
"version": "0.2.59",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/snapshot",
"version": "0.2.58",
"version": "0.2.59",
"dependencies": {
"@types/node": "^22.10.2",
"sharp": "^0.33.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/snapshot",
"version": "0.2.58",
"version": "0.2.59",
"description": "Snapshot Plugin for Scrypted",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",

View File

@@ -12,6 +12,7 @@ import { ffmpegFilterImage, ffmpegFilterImageBuffer } from './ffmpeg-image-filte
import { ImageConverter, ImageConverterNativeId } from './image-converter';
import { ImageReader, ImageReaderNativeId, loadSharp, loadVipsImage } from './image-reader';
import { ImageWriter, ImageWriterNativeId } from './image-writer';
import { fixLegacyClipPath, normalizeBox, polygonIntersectsBoundingBox } from '../../objectdetector/src/polygon';
const { mediaManager, systemManager } = sdk;
if (os.cpus().find(cpu => cpu.model?.toLowerCase().includes('qemu'))) {
@@ -433,10 +434,11 @@ class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
if (!this.storageSettings.values.snapshotCropScale?.length)
return picture;
const xmin = Math.min(...this.storageSettings.values.snapshotCropScale.map(([x, y]) => x)) / 100;
const ymin = Math.min(...this.storageSettings.values.snapshotCropScale.map(([x, y]) => y)) / 100;
const xmax = Math.max(...this.storageSettings.values.snapshotCropScale.map(([x, y]) => x)) / 100;
const ymax = Math.max(...this.storageSettings.values.snapshotCropScale.map(([x, y]) => y)) / 100;
const clipPath = fixLegacyClipPath(this.storageSettings.values.snapshotCropScale);
const xmin = Math.min(...clipPath.map(([x, y]) => x));
const ymin = Math.min(...clipPath.map(([x, y]) => y));
const xmax = Math.max(...clipPath.map(([x, y]) => x));
const ymax = Math.max(...clipPath.map(([x, y]) => y));
if (loadSharp()) {
const vips = await loadVipsImage(picture, this.id);

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/webrtc",
"version": "0.2.76",
"version": "0.2.77",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/webrtc",
"version": "0.2.76",
"version": "0.2.77",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/webrtc",
"version": "0.2.76",
"version": "0.2.78",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",

View File

@@ -458,7 +458,13 @@ export function parseOptions(options: RTCSignalingOptions) {
if (options?.userAgent?.includes('Firefox/'))
sessionSupportsH264High = true;
const transcodeWidth = Math.max(640, Math.min(options?.screen?.width || 960, 1280));
// Some devices return a `screen width` value that is not a multiple of 2, which is not allowed for the h264 codec.
// Convert to a smaller even value.
const screenWidthForTranscodeH264 = !options?.screen?.width
? 960
: Math.trunc(options?.screen?.width / 2) * 2;
const transcodeWidth = Math.max(640, Math.min(screenWidthForTranscodeH264, 1280));
const devicePixelRatio = options?.screen?.devicePixelRatio || 1;
const width = (options?.screen?.width * devicePixelRatio) || undefined;
const height = (options?.screen?.height * devicePixelRatio) || undefined;

View File

@@ -79,6 +79,16 @@ function getNalType(data: Buffer): number {
return (data[0] & 0x7E) >> 1; // 6 bits starting from bit 1
}
function isKeyFrame(nalType: number): boolean {
// For IDR frames, send codec info first
if (nalType === NAL_TYPE_IDR_W_RADL || nalType === NAL_TYPE_IDR_N_LP ||
nalType === NAL_TYPE_BLA_W_LP || nalType === NAL_TYPE_BLA_W_RADL ||
nalType === NAL_TYPE_BLA_N_LP || nalType === NAL_TYPE_CRA_NUT) {
return true;
}
return false;
}
// Function to depacketize Aggregation Packets (similar to STAP-A in H.264)
export function depacketizeAP(data: Buffer) {
const ret: Buffer[] = [];
@@ -385,7 +395,7 @@ export class H265Repacketizer {
}
else {
// For IDR frames, send codec info first
if (splitNaluType === NAL_TYPE_IDR_W_RADL || splitNaluType === NAL_TYPE_IDR_N_LP) {
if (isKeyFrame(splitNaluType)) {
this.maybeSendAPCodecInfo(first, ret);
}
@@ -689,9 +699,7 @@ export class H265Repacketizer {
}
// For IDR frames, send codec info first
if (nalType === NAL_TYPE_IDR_W_RADL || nalType === NAL_TYPE_IDR_N_LP ||
nalType === NAL_TYPE_BLA_W_LP || nalType === NAL_TYPE_BLA_W_RADL ||
nalType === NAL_TYPE_BLA_N_LP) {
if (isKeyFrame(nalType)) {
this.maybeSendAPCodecInfo(packet, ret);
}

262
server/package-lock.json generated
View File

@@ -1,29 +1,29 @@
{
"name": "@scrypted/server",
"version": "0.138.22",
"version": "0.138.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.138.22",
"version": "0.138.27",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@scrypted/ffmpeg-static": "^6.1.0-build3",
"@scrypted/node-pty": "^1.0.22",
"@scrypted/node-pty": "^1.0.24",
"@scrypted/types": "^0.5.12",
"adm-zip": "^0.5.16",
"body-parser": "^2.2.0",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"engine.io": "^6.6.4",
"express": "^5.1.0",
"follow-redirects": "^1.15.9",
"http-auth": "^4.2.0",
"level": "^9.0.0",
"level": "^10.0.0",
"lodash": "^4.17.21",
"mime": "^4.0.7",
"mime-types": "^3.0.1",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^11.2.0",
@@ -47,7 +47,8 @@
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/lodash": "^4.17.16",
"@types/node": "^22.14.0",
"@types/mime-types": "^2.1.4",
"@types/node": "^22.15.3",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.7.0",
@@ -564,12 +565,13 @@
}
},
"node_modules/@scrypted/node-pty": {
"version": "1.0.22",
"resolved": "https://registry.npmjs.org/@scrypted/node-pty/-/node-pty-1.0.22.tgz",
"integrity": "sha512-GXpxrtDkbEG9oFOqJ4kVNT8r0HBzSDzQyVAllAApHTec2NezgKU2wMDK668s0gPW7Q1mvF3g0EV4646cAA0hHg==",
"version": "1.0.24",
"resolved": "https://registry.npmjs.org/@scrypted/node-pty/-/node-pty-1.0.24.tgz",
"integrity": "sha512-UyrsMRsH0hRazVDUeA+oBrIPcoryDhXXD5fVS/z19q4zHube20Tj7xvP1AWte1NGGQ7AnIZiK8EPlQgweXC15g==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"prebuild-install": "npm:@scrypted/prebuild-install@^7.1.8"
"prebuild-install": "npm:@scrypted/prebuild-install@^7.1.10"
}
},
"node_modules/@scrypted/types": {
@@ -680,10 +682,17 @@
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
"dev": true
},
"node_modules/@types/mime-types": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
"integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
"version": "22.15.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
"integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -784,20 +793,20 @@
}
},
"node_modules/abstract-level": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-2.0.2.tgz",
"integrity": "sha512-pPJixmXk/kTKLB2sSue7o4Uj6TlLD2XfaP2gWZomHVCC6cuUGX/VslQqKG1yZHfXwBb/3lS6oSTMPGzh1P1iig==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-3.1.0.tgz",
"integrity": "sha512-j2e+TsAxy7Ri+0h7dJqwasymgt0zHBWX4+nMk3XatyuqgHfdstBJ9wsMfbiGwE1O+QovRyPcVAqcViMYdyPaaw==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"is-buffer": "^2.0.5",
"level-supports": "^6.0.0",
"level-supports": "^6.2.0",
"level-transcoder": "^1.0.1",
"maybe-combine-errors": "^1.0.0",
"module-error": "^1.0.1"
},
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/accepts": {
@@ -812,6 +821,27 @@
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/adm-zip": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
@@ -889,7 +919,8 @@
"type": "consulting",
"url": "https://feross.org/support"
}
]
],
"license": "MIT"
},
"node_modules/base64id": {
"version": "2.0.0",
@@ -908,6 +939,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@@ -932,6 +964,7 @@
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@@ -989,12 +1022,12 @@
}
},
"node_modules/browser-level": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/browser-level/-/browser-level-2.0.0.tgz",
"integrity": "sha512-RuYSCHG/jwFCrK+KWA3dLSUNLKHEgIYhO5ORPjJMjCt7T3e+RzpIDmYKWRHxq2pfKGXjlRuEff7y7RESAAgzew==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/browser-level/-/browser-level-3.0.0.tgz",
"integrity": "sha512-kGXtLh29jMwqKaskz5xeDLtCtN1KBz/DbQSqmvH7QdJiyGRC7RAM8PPg6gvUiNMa+wVnaxS9eSmEtP/f5ajOVw==",
"license": "MIT",
"dependencies": {
"abstract-level": "^2.0.1"
"abstract-level": "^3.1.0"
}
},
"node_modules/buffer": {
@@ -1131,13 +1164,13 @@
}
},
"node_modules/classic-level": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-2.0.0.tgz",
"integrity": "sha512-ftiMvKgCQK+OppXcvMieDoYlYLYWhScK6yZRFBrrlHQRbm4k6Gr+yDgu/wt3V0k1/jtNbuiXAsRmuAFcD0Tx5Q==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-3.0.0.tgz",
"integrity": "sha512-yGy8j8LjPbN0Bh3+ygmyYvrmskVita92pD/zCoalfcC9XxZj6iDtZTAnz+ot7GG8p9KLTG+MZ84tSA4AhkgVZQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"abstract-level": "^2.0.0",
"abstract-level": "^3.1.0",
"module-error": "^1.0.1",
"napi-macros": "^2.2.2",
"node-gyp-build": "^4.3.0"
@@ -1292,6 +1325,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
@@ -1313,9 +1347,9 @@
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -1376,6 +1410,7 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
@@ -1491,6 +1526,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
@@ -1581,27 +1617,6 @@
}
}
},
"node_modules/express/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -1736,7 +1751,8 @@
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-minipass": {
"version": "3.0.3",
@@ -1799,7 +1815,8 @@
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": {
"version": "11.0.0",
@@ -1998,7 +2015,8 @@
"type": "consulting",
"url": "https://feross.org/support"
}
]
],
"license": "BSD-3-Clause"
},
"node_modules/imurmurhash": {
"version": "0.1.4",
@@ -2017,7 +2035,8 @@
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/ip-address": {
"version": "9.0.5",
@@ -2113,14 +2132,14 @@
"license": "MIT"
},
"node_modules/level": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/level/-/level-9.0.0.tgz",
"integrity": "sha512-n+mVuf63mUEkd8NUx7gwxY+QF5vtkibv6fXTGUgtHWLPDaA5/XZjLcI/Q1nQ8k6OttHT6Ezt+7nSEXsRUfHtOQ==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/level/-/level-10.0.0.tgz",
"integrity": "sha512-aZJvdfRr/f0VBbSRF5C81FHON47ZsC2TkGxbBezXpGGXAUEL/s6+GP73nnhAYRSCIqUNsmJjfeOF4lzRDKbUig==",
"license": "MIT",
"dependencies": {
"abstract-level": "^2.0.1",
"browser-level": "^2.0.0",
"classic-level": "^2.0.0"
"abstract-level": "^3.1.0",
"browser-level": "^3.0.0",
"classic-level": "^3.0.0"
},
"engines": {
"node": ">=18"
@@ -2235,35 +2254,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
"integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==",
"funding": [
"https://github.com/sponsors/broofa"
],
"license": "MIT",
"bin": {
"mime": "bin/cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
@@ -2288,6 +2294,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -2430,7 +2437,8 @@
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/module-error": {
"version": "1.0.2",
@@ -2449,7 +2457,8 @@
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
"license": "MIT"
},
"node_modules/napi-macros": {
"version": "2.2.2",
@@ -2466,14 +2475,15 @@
}
},
"node_modules/node-abi": {
"version": "3.63.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.63.0.tgz",
"integrity": "sha512-vAszCsOUrUxjGAmdnM/pq7gUgie0IRteCQMX6d4A534fQCR93EJU5qgzBvU6EkFfK27s0T3HEV3BOyJIr7OMYw==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.4.0.tgz",
"integrity": "sha512-+sBEWs/HZ3ZDBtPSPKfYndkTF9ebr1BJm/z2TBDJj/upiOx9J6BeGXRtFyOXz1r6vUqzsCRM5pUr+K83i64agg==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
"semver": "^7.6.3"
},
"engines": {
"node": ">=10"
"node": ">=22.12.0"
}
},
"node_modules/node-dijkstra": {
@@ -2651,9 +2661,10 @@
},
"node_modules/prebuild-install": {
"name": "@scrypted/prebuild-install",
"version": "7.1.8",
"resolved": "https://registry.npmjs.org/@scrypted/prebuild-install/-/prebuild-install-7.1.8.tgz",
"integrity": "sha512-ghmvo7gRUUyTUtjBGEcZfL7fizBNdzFFLnMRiNh3+lL25t8Rjv4EDBEKmDT6C4/wKVfdtzAnITBBc9r9DhVqRg==",
"version": "7.1.10",
"resolved": "https://registry.npmjs.org/@scrypted/prebuild-install/-/prebuild-install-7.1.10.tgz",
"integrity": "sha512-DkDz+z6O4EKe17GJ3rs9hdyv9gYj3VI9GhO2ZzBhSmBNNYkYs5DULMczsxRpLH8eygJ0zzW8Arf7+hip/oUZwQ==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
@@ -2662,7 +2673,7 @@
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^3.3.0",
"node-abi": "^4.4.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"tar-fs": "^2.0.0",
@@ -2710,9 +2721,10 @@
}
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -2771,6 +2783,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -2785,6 +2798,7 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -2937,27 +2951,6 @@
}
}
},
"node_modules/send/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/send/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3208,6 +3201,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -3266,6 +3260,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -3301,12 +3296,14 @@
"node_modules/tar-fs/node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
@@ -3359,6 +3356,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
@@ -3380,27 +3378,6 @@
"node": ">= 0.6"
}
},
"node_modules/type-is/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/type-is/node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -3461,7 +3438,8 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/uuid": {
"version": "8.3.2",

View File

@@ -1,22 +1,22 @@
{
"name": "@scrypted/server",
"version": "0.138.23",
"version": "0.138.27",
"description": "",
"dependencies": {
"@scrypted/ffmpeg-static": "^6.1.0-build3",
"@scrypted/node-pty": "^1.0.22",
"@scrypted/node-pty": "^1.0.24",
"@scrypted/types": "^0.5.12",
"adm-zip": "^0.5.16",
"body-parser": "^2.2.0",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"engine.io": "^6.6.4",
"express": "^5.1.0",
"follow-redirects": "^1.15.9",
"http-auth": "^4.2.0",
"level": "^9.0.0",
"level": "^10.0.0",
"lodash": "^4.17.21",
"mime": "^4.0.7",
"mime-types": "^3.0.1",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^11.2.0",
@@ -37,7 +37,8 @@
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/lodash": "^4.17.16",
"@types/node": "^22.14.0",
"@types/mime-types": "^2.1.4",
"@types/node": "^22.15.3",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.7.0",

View File

@@ -1,7 +1,7 @@
import { BufferConverter, DeviceManager, FFmpegInput, MediaConverter, MediaManager, MediaObjectCreateOptions, MediaObject as MediaObjectInterface, MediaStreamUrl, ScryptedDevice, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, SystemDeviceState, SystemManager } from "@scrypted/types";
import fs from 'fs';
import https from 'https';
import mime from 'mime';
import mime from 'mime-types';
import Graph from 'node-dijkstra';
import path from 'path';
import MimeType from 'whatwg-mimetype';
@@ -77,8 +77,8 @@ export abstract class MediaManagerBase implements MediaManager {
}
const ab = await fs.promises.readFile(filename);
const mt = mime.getType(filename);
const mo = this.createMediaObject(ab, mt);
const mt = mime.lookup(filename);
const mo = this.createMediaObject(ab, mt || 'application/octet-stream');
return mo;
}
});
@@ -238,8 +238,8 @@ export abstract class MediaManagerBase implements MediaManager {
ensureMediaObjectRemote(mediaObject: string | MediaObjectInterface): MediaObjectRemote {
if (typeof mediaObject === 'string') {
const mt = mime.getType(mediaObject);
return this.createMediaObjectRemote(mediaObject, mt);
const mt = mime.lookup(mediaObject);
return this.createMediaObjectRemote(mediaObject, mt || 'application/octet-stream');
}
return mediaObject as MediaObjectRemote;
}

View File

@@ -172,6 +172,9 @@ function createClusterForkParam(mainFilename: string, clusterId: string, cluster
});
let getRemote: any;
let ping: any;
const timeout = setTimeout(() => {
threadPeer.kill('cluster fork timeout');
}, 10000);
try {
const initializeCluster: InitializeCluster = await threadPeer.getParam('initializeCluster');
await initializeCluster({ clusterId, clusterSecret, clusterWorkerId });
@@ -189,9 +192,6 @@ function createClusterForkParam(mainFilename: string, clusterId: string, cluster
}
}
const timeout = setTimeout(() => {
threadPeer.kill('cluster fork timeout');
}, 10000);
const clusterGetRemote = (...args: any[]) => {
clearTimeout(timeout);
return {
@@ -252,6 +252,7 @@ export function startClusterClient(mainFilename: string, options?: {
}
catch (e) {
console.warn('Cluster server not available.', host, port, e);
rawSocket.destroy();
continue;
}
@@ -264,6 +265,7 @@ export function startClusterClient(mainFilename: string, options?: {
await once(socket, 'secureConnect');
}
catch (e) {
socket.destroy();
console.warn('Cluster server tls failed.', host, port, e);
continue;
}