Compare commits

..

31 Commits

Author SHA1 Message Date
Koushik Dutta
5eab99866f server: Force ipv4 for npm usage 2023-09-19 13:39:18 -07:00
Koushik Dutta
e10a4f3c58 client: abnormal login results of any type on the alternate urls should fail 2023-09-19 11:30:13 -07:00
Koushik Dutta
2585b1832e docker: add node 20 base 2023-09-19 10:49:23 -07:00
Koushik Dutta
5e8e0d7773 client: validate results 2023-09-19 10:30:19 -07:00
Koushik Dutta
7c17b478d7 cloud: add cors options 2023-09-19 10:29:44 -07:00
Koushik Dutta
9f5dd55c73 h264: ignore nal delimiter 2023-09-19 10:11:44 -07:00
Koushik Dutta
b6f400382d client: support local checks 2023-09-19 09:49:33 -07:00
Koushik Dutta
024b2166b8 snapshot: publish 2023-09-18 08:25:11 -07:00
Koushik Dutta
b49771840e amcrest: httpsAgent usage fixes 2023-09-17 20:34:48 -07:00
Koushik Dutta
4001fc996f amcrest: publish 2023-09-17 17:59:56 -07:00
Koushik Dutta
0d97010ca8 amcrest: fix audiocodec detection nre 2023-09-17 17:59:22 -07:00
Koushik Dutta
e243d99d12 sdk: unprivatize settings method 2023-09-15 14:59:01 -07:00
Koushik Dutta
86a91dfbe4 webrtc: update from upstream 2023-09-15 09:09:46 -07:00
Koushik Dutta
c86ae752e8 videoanalysis: fixup spurious motion triggering object detection on a lot of cams 2023-09-15 09:02:32 -07:00
Koushik Dutta
b7ca477b98 cloud: show tunnel url 2023-09-14 08:30:57 -07:00
Koushik Dutta
c37f8926b8 onvif: fix 2 way audio logging 2023-09-14 08:15:37 -07:00
Koushik Dutta
4b181a8ac9 videoanalysis: fix migration bug by reenabling mixins 2023-09-14 08:15:18 -07:00
Koushik Dutta
b8439aaec3 server: add axios post shim 2023-09-13 16:17:16 -07:00
Koushik Dutta
77d0c33657 videoanalysis: move object detectors behind developer mode flag to prevent footgunning 2023-09-13 10:45:12 -07:00
Koushik Dutta
0b6d61a801 sdk: fix python generation 2023-09-09 20:45:31 -07:00
Koushik Dutta
71a2d27cbd detect: add ObjectDetection filtering interfaces to prevent footgunning 2023-09-09 20:40:56 -07:00
Koushik Dutta
f8f79f5cc2 sdk: add ObjectDetectionPreview 2023-09-09 20:34:57 -07:00
Koushik Dutta
988f297e32 sdk: add ObjectDetectionGenerator 2023-09-09 20:33:09 -07:00
Koushik Dutta
6e109d89e0 Merge branch 'main' of github.com:koush/scrypted 2023-09-09 13:45:24 -07:00
Koushik Dutta
6ada4854bc python-codecs: reduce jpeg quality for better file sizes 2023-09-09 13:45:20 -07:00
Koushik Dutta
bc5e89668f ha: publish 2023-09-09 10:04:27 -07:00
Brett Jia
4c11def52b core: use webpack bundled map marker (#1049)
* core: use webpack bundled map marker

* document source of marker icon workaround

* disable touch zoom
2023-09-09 09:57:15 -07:00
Koushik Dutta
8890d307f4 docker: add builder secrets 2023-09-09 09:39:03 -07:00
Koushik Dutta
9f8f562dcc docker: fixup template path 2023-09-08 21:34:13 -07:00
Koushik Dutta
2ce798c8c2 server: postrelease 2023-09-08 20:12:08 -07:00
Koushik Dutta
4271ef321f postrelease 2023-09-08 20:11:59 -07:00
51 changed files with 525 additions and 175 deletions

View File

@@ -10,7 +10,7 @@ jobs:
# runs-on: ubuntu-latest
strategy:
matrix:
NODE_VERSION: ["18"]
NODE_VERSION: ["18", "20"]
BASE: ["jammy"]
FLAVOR: ["full", "lite", "thin"]
steps:
@@ -23,13 +23,13 @@ jobs:
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: Koushik-MacStudio
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: raspberrypi
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
@@ -37,9 +37,9 @@ jobs:
with:
platforms: linux/arm64,linux/armhf
append: |
- endpoint: ssh://koush@Koushik-MacStudio
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM64 }}
platforms: linux/arm64
- endpoint: ssh://koush@raspberrypi
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM7 }}
platforms: linux/armhf
- name: Login to Docker Hub

View File

@@ -19,7 +19,7 @@ jobs:
# runs-on: ubuntu-latest
strategy:
matrix:
BASE: ["18-jammy-full", "18-jammy-lite", "18-jammy-thin"]
BASE: ["18-jammy-full", "18-jammy-lite", "18-jammy-thin", "20-jammy-full", "20-jammy-lite", "20-jammy-thin"]
SUPERVISOR: ["", ".s6"]
steps:
- name: Check out the repo
@@ -42,13 +42,13 @@ jobs:
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: Koushik-MacStudio
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: raspberrypi
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
@@ -56,12 +56,11 @@ jobs:
with:
platforms: linux/arm64,linux/armhf
append: |
- endpoint: ssh://koush@Koushik-MacStudio
# platforms: linux/arm64
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM64 }}
platforms: linux/arm64
- endpoint: ssh://koush@raspberrypi
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM7 }}
platforms: linux/armhf
- name: Login to Docker Hub
uses: docker/login-action@v2
with:

View File

@@ -3,14 +3,13 @@ import sdk from "@scrypted/sdk";
const { systemManager } = sdk;
const autoIncludeToken = 'v4';
export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
hasEnabledMixin: { [id: string]: string } = {};
pluginsComponent: Promise<any>;
unshiftMixin = false;
constructor(nativeId?: string) {
constructor(nativeId?: string, public autoIncludeToken = 'v4') {
super(nativeId);
try {
@@ -30,10 +29,12 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
this.maybeEnableMixin(eventSource);
});
for (const id of Object.keys(systemManager.getSystemState())) {
const device = systemManager.getDeviceById(id);
this.maybeEnableMixin(device);
}
process.nextTick(() => {
for (const id of Object.keys(systemManager.getSystemState())) {
const device = systemManager.getDeviceById(id);
this.maybeEnableMixin(device);
}
});
}
async shouldEnableMixin(device: ScryptedDevice) {
@@ -44,7 +45,7 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
if (!device || device.mixins?.includes(this.id))
return;
if (this.hasEnabledMixin[device.id] === autoIncludeToken)
if (this.hasEnabledMixin[device.id] === this.autoIncludeToken)
return;
const match = await this.canMixin(device.type, device.interfaces);
@@ -66,9 +67,9 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
}
setHasEnabledMixin(id: string) {
if (this.hasEnabledMixin[id] === autoIncludeToken)
if (this.hasEnabledMixin[id] === this.autoIncludeToken)
return;
this.hasEnabledMixin[id] = autoIncludeToken;
this.hasEnabledMixin[id] = this.autoIncludeToken;
this.storage.setItem('hasEnabledMixin', JSON.stringify(this.hasEnabledMixin));
}

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "18-jammy-full.s6-v0.41.0"
version: "18-jammy-full.s6-v0.50.0"
slug: scrypted
description: Scrypted is a high performance home video integration and automation platform
url: "https://github.com/koush/scrypted"

View File

@@ -50,7 +50,7 @@ services:
# Modify to add the additional volume for Scrypted NVR.
# The following example would mount the /mnt/sda/video path on the host
# to the /nvr path inside the docker container.
# - /mnt/sda/video:/nvr
# - /mnt/media/video:/nvr
# Or use a network mount from one of the CIFS/NFS examples at the top of this file.
# - type: volume

View File

@@ -1,5 +1,5 @@
import { MediaObjectOptions, RTCConnectionManagement, RTCSignalingSession, ScryptedStatic } from "@scrypted/types";
import axios, { AxiosRequestConfig } from 'axios';
import axios, { AxiosRequestConfig, AxiosRequestHeaders } from 'axios';
import * as eio from 'engine.io-client';
import { SocketOptions } from 'engine.io-client';
import { Deferred } from "../../../common/src/deferred";
@@ -8,7 +8,6 @@ import { BrowserSignalingSession, waitPeerConnectionIceConnected, waitPeerIceCon
import { DataChannelDebouncer } from "../../../plugins/webrtc/src/datachannel-debouncer";
import type { IOSocket } from '../../../server/src/io';
import { MediaObject } from '../../../server/src/plugin/mediaobject';
import type { MediaObjectRemote } from '../../../server/src/plugin/plugin-api';
import { attachPluginRemote } from '../../../server/src/plugin/plugin-remote';
import { RpcPeer } from '../../../server/src/rpc';
import { createRpcDuplexSerializer, createRpcSerializer } from '../../../server/src/rpc-serializer';
@@ -48,9 +47,8 @@ export interface ScryptedClientStatic extends ScryptedStatic {
browserSignalingSession?: BrowserSignalingSession;
address?: string;
connectionType: ScryptedClientConnectionType;
authorization?: string;
queryToken?: { [parameter: string]: string };
rpcPeer: RpcPeer,
rpcPeer: RpcPeer;
loginResult: ScryptedClientLoginResult;
}
export interface ScryptedConnectionOptions {
@@ -59,6 +57,7 @@ export interface ScryptedConnectionOptions {
webrtc?: boolean;
baseUrl?: string;
axiosConfig?: AxiosRequestConfig;
previousLoginResult?: ScryptedClientLoginResult;
}
export interface ScryptedLoginOptions extends ScryptedConnectionOptions {
@@ -155,8 +154,12 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
export async function checkScryptedClientLogin(options?: ScryptedConnectionOptions) {
let { baseUrl } = options || {};
const url = combineBaseUrl(baseUrl, 'login');
const headers: AxiosRequestHeaders = {};
if (options?.previousLoginResult?.authorization)
headers.Authorization = options?.previousLoginResult?.authorization;
const response = await axios.get(url, {
withCredentials: true,
headers,
...options?.axiosConfig,
});
const scryptedCloud = response.headers['x-scrypted-cloud'] === 'true';
@@ -164,6 +167,7 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
const cloudAddress = response.headers['x-scrypted-cloud-address'];
return {
baseUrl,
hostname: response.data.hostname as string,
redirect: response.data.redirect as string,
username: response.data.username as string,
@@ -180,6 +184,15 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
};
}
export interface ScryptedClientLoginResult {
authorization: string;
queryToken: { [parameter: string]: string };
localAddresses: string[];
scryptedCloud: boolean;
directAddress: string;
cloudAddress: string;
}
export class ScryptedClientLoginError extends Error {
constructor(public result: Awaited<ReturnType<typeof checkScryptedClientLogin>>) {
super(result.error);
@@ -215,10 +228,9 @@ export async function redirectScryptedLogout(baseUrl?: string) {
export async function connectScryptedClient(options: ScryptedClientOptions): Promise<ScryptedClientStatic> {
const start = Date.now();
let { baseUrl, pluginId, clientName, username, password } = options;
let authorization: string;
let queryToken: any;
const extraHeaders: { [header: string]: string } = {};
let localAddresses: string[];
let scryptedCloud: boolean;
let directAddress: string;
@@ -226,6 +238,8 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
console.log('@scrypted/client', packageJson.version);
const extraHeaders: { [header: string]: string } = {};
if (username && password) {
const loginResult = await loginScryptedClient(options as ScryptedLoginOptions);
if (loginResult.authorization)
@@ -239,9 +253,47 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
console.log('login result', Date.now() - start, loginResult);
}
else {
const loginCheck = await checkScryptedClientLogin({
const urlsToCheck = new Set<string>();
for (const u of [
...options?.previousLoginResult?.localAddresses || [],
options?.previousLoginResult?.directAddress,
options?.previousLoginResult?.cloudAddress,
]) {
if (u)
urlsToCheck.add(u);
}
// the alternate urls must have a valid response.
const loginCheckPromises = [...urlsToCheck].map(async baseUrl => {
const loginCheck = await checkScryptedClientLogin({
baseUrl,
previousLoginResult: options?.previousLoginResult,
});
if (loginCheck.error || loginCheck.redirect)
throw new Error('login error');
if (!loginCheck.authorization || !loginCheck.username || !loginCheck.queryToken) {
console.error(loginCheck);
throw new Error('malformed login result');
}
return loginCheck;
});
const baseUrlCheck = checkScryptedClientLogin({
baseUrl,
});
loginCheckPromises.push(baseUrlCheck);
let loginCheck: Awaited<ReturnType<typeof checkScryptedClientLogin>>;
try {
loginCheck = await Promise.any(loginCheckPromises);
}
catch (e) {
loginCheck = await baseUrlCheck;
}
if (loginCheck.error || loginCheck.redirect)
throw new ScryptedClientLoginError(loginCheck);
localAddresses = loginCheck.addresses;
@@ -632,9 +684,15 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
pluginHostAPI: undefined,
rtcConnectionManagement,
browserSignalingSession,
authorization,
queryToken,
rpcPeer,
loginResult: {
directAddress,
localAddresses,
scryptedCloud,
queryToken,
authorization,
cloudAddress,
}
}
socket.on('close', () => {

View File

@@ -0,0 +1,23 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ts-node",
"type": "node",
"request": "launch",
"args": [
"${workspaceFolder}/test/test.ts"
],
"runtimeArgs": [
"-r",
"ts-node/register"
],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
}
]
}

View File

@@ -0,0 +1,93 @@
import { H264Repacketizer, depacketizeStapA } from '../src/index';
import { H264_NAL_TYPE_IDR, H264_NAL_TYPE_PPS, H264_NAL_TYPE_SEI, H264_NAL_TYPE_SPS, H264_NAL_TYPE_STAP_A, RtspServer, getNaluTypesInNalu } from '../../../common/src/rtsp-server';
import fs from 'fs';
import { getNvrSessionStream } from '../../../../nvr/nvr-plugin/src/session-stream';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { RtpPacket } from '../../../external/werift/packages/rtp/src/rtp/rtp';
function parse(parameters: string) {
const spspps = parameters.split(',');
// empty sprop-parameter-sets is apparently a thing:
// a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=
if (spspps?.length !== 2) {
return {
sps: undefined,
pps: undefined,
};
}
const [sps, pps] = spspps;
return {
sps: Buffer.from(sps, 'base64'),
pps: Buffer.from(pps, 'base64'),
}
}
async function main() {
const spspps = parse('Z2QAM6wVFKAoALWQ,aO48sA==');
// Z2QAM6wVFKAoALWQ
// Z00AMpY1QEABg03BQEFQAAADABAAAAMDKEA=
const repacketizer = new H264Repacketizer(console, 1300, undefined);
const stream = fs.createReadStream('/Users/koush/Downloads/rtsp/1692537093973.rtsp', {
start: 0,
highWaterMark: 800000,
});
let rtspParser = new RtspServer(stream as any, '');
rtspParser.setupTracks = {
'0': {
codec: '0',
protocol: 'tcp',
control: '',
destination: 0,
},
'2': {
codec: '2',
protocol: 'tcp',
control: '',
destination: 2,
},
}
for await (const rtspSample of rtspParser.handleRecord()) {
if (rtspSample.type !== '0')
continue;
const rtp = RtpPacket.deSerialize(rtspSample.packet);
const nalus = getNaluTypesInNalu(rtp.payload);
if (nalus.has(H264_NAL_TYPE_SEI)) {
console.warn('SEI', rtp.payload)
}
if (nalus.has(H264_NAL_TYPE_SPS)) {
console.warn('SPS', rtp.payload, spspps.sps)
}
if (nalus.has(H264_NAL_TYPE_PPS)) {
console.warn('PPS', rtp.payload, spspps.sps)
}
if (nalus.has(H264_NAL_TYPE_STAP_A)) {
const parts = depacketizeStapA(rtp.payload);
console.log('stapa', parts);
for (const part of parts) {
}
}
if (nalus.has(H264_NAL_TYPE_IDR)) {
const h264Packetizer = new H264Repacketizer(console, 65535, spspps as any);
// offset the stapa packet by -1 so the sequence numbers can be reused.
h264Packetizer.extraPackets = -1;
const stapas: RtpPacket[] = [];
const idr = RtpPacket.deSerialize(rtspSample.packet);
h264Packetizer.maybeSendStapACodecInfo(idr, stapas);
if (stapas.length === 1) {
const stapa = stapas[0].serialize();
// console.log(stapa);
}
}
}
}
main();

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.124",
"version": "0.0.127",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.124",
"version": "0.0.127",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.124",
"version": "0.0.127",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -95,6 +95,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
for (const element of deviceParameters) {
try {
const response = await this.getClient().digestAuth.request({
httpsAgent: amcrestHttpsAgent,
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=${element.action}`
});
@@ -147,6 +148,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
return;
const response = await this.getClient().digestAuth.request({
httpsAgent: amcrestHttpsAgent,
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${params}`
});
this.console.log('reconfigure result', response.data);
@@ -190,14 +192,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|| event === AmcrestEvent.PhoneCallDetectStart
|| event === AmcrestEvent.AlarmIPCStart
|| event === AmcrestEvent.DahuaTalkInvite) {
if (event === AmcrestEvent.DahuaTalkInvite && payload && multipleCallIds)
{
if (payload.includes(callerId))
{
if (event === AmcrestEvent.DahuaTalkInvite && payload && multipleCallIds) {
if (payload.includes(callerId)) {
this.binaryState = true;
}
} else
{
} else {
this.binaryState = true;
}
}
@@ -259,25 +258,23 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
if (!twoWayAudio)
twoWayAudio = isDoorbell ? 'Amcrest' : 'None';
if (doorbellType == DAHUA_DOORBELL_TYPE)
{
if (doorbellType == DAHUA_DOORBELL_TYPE) {
ret.push(
{
title: 'Multiple Call Buttons',
key: 'multipleCallIds',
description: 'Some Dahua Doorbells integrate multiple Call Buttons for apartment buildings.',
type: 'boolean',
value: (this.storage.getItem('multipleCallIds') === 'true').toString(),
}
{
title: 'Multiple Call Buttons',
key: 'multipleCallIds',
description: 'Some Dahua Doorbells integrate multiple Call Buttons for apartment buildings.',
type: 'boolean',
value: (this.storage.getItem('multipleCallIds') === 'true').toString(),
}
);
}
const multipleCallIds = this.storage.getItem('multipleCallIds');
if (multipleCallIds)
{
if (multipleCallIds) {
ret.push(
{
title: 'Caller ID',
@@ -288,7 +285,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
}
)
}
ret.push(
{
@@ -309,11 +306,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
);
return ret;
}
async takeSmartCameraPicture(option?: PictureOptions): Promise<MediaObject> {
return this.createMediaObject(await this.getClient().jpegSnapshot(), 'image/jpeg');
@@ -401,11 +398,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
?.replace('.', '')?.toLowerCase()?.trim();
if (audioCodec?.includes('aac'))
audioCodec = 'aac';
else if (audioCodec.includes('g711a'))
else if (audioCodec?.includes('g711a'))
audioCodec = 'pcm_alaw';
else if (audioCodec.includes('g711u'))
else if (audioCodec?.includes('g711u'))
audioCodec = 'pcm_ulaw';
else if (audioCodec.includes('g711'))
else if (audioCodec?.includes('g711'))
audioCodec = 'pcm';
if (vso.audio)
@@ -490,7 +487,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
this.videoStreamOptions = undefined;
super.putSetting(key, value);
this.updateDevice();
this.updateDeviceInfo();
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/cloud",
"version": "0.1.40",
"version": "0.1.41",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/cloud",
"version": "0.1.40",
"version": "0.1.41",
"dependencies": {
"@eneris/push-receiver": "^3.1.4",
"@scrypted/common": "file:../../common",

View File

@@ -54,5 +54,5 @@
"@types/nat-upnp": "^1.1.2",
"@types/node": "^20.4.5"
},
"version": "0.1.40"
"version": "0.1.41"
}

View File

@@ -159,11 +159,20 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.cloudflared?.child.kill();
},
},
cloudflaredTunnelUrl: {
group: 'Advanced',
title: 'Cloudflare Tunnel URL',
description: 'Cloudflare Tunnel URL is a randomized cloud connection, unless a Cloudflare Tunnel Token is provided.',
readonly: true,
mapGet: () => this.cloudflareTunnel || 'Unavailable',
},
register: {
group: 'Advanced',
title: 'Register',
type: 'button',
onPut: () => this.manager.registrationId.then(r => this.sendRegistrationId(r)),
onPut: () => {
this.manager.registrationId.then(r => this.sendRegistrationId(r))
},
description: 'Register server with Scrypted Cloud.',
},
testPortForward: {
@@ -173,6 +182,14 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
onPut: () => this.testPortForward(),
description: 'Test the port forward connection from Scrypted Cloud.',
},
additionalCorsOrigins: {
title: "Additional CORS Origins",
description: "Debugging purposes only. DO NOT EDIT.",
group: 'CORS',
multiple: true,
combobox: true,
defaultValue: [],
}
});
upnpInterval: NodeJS.Timeout;
upnpClient = upnp.createClient();
@@ -474,6 +491,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
`https://${SCRYPTED_SERVER}`,
// chromecast receiver. move this into google home and chromecast plugins?
'https://koush.github.io',
...this.storageSettings.values.additionalCorsOrigins,
],
});
}

View File

@@ -10,20 +10,29 @@
doubleClickZoom: false,
boxZoom: false,
scrollWheelZoom: false,
touchZoom: false,
}"
>
<l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
<l-marker :lat-lng="position" :icon="icon"></l-marker>
<l-marker :lat-lng="position"></l-marker>
<l-control-attribution position="bottomright" :prefix="prefix"></l-control-attribution>
</l-map>
</template>
<script>
import { latLng, icon } from "leaflet";
import { latLng, Icon } from "leaflet";
import { LMap, LTileLayer, LMarker, LControlAttribution } from "vue2-leaflet";
import 'leaflet/dist/leaflet.css';
import RPCInterface from "../RPCInterface.vue";
// https://vue2-leaflet.netlify.app/quickstart/#marker-icons-are-missing
delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
export default {
mixins: [RPCInterface],
components: {
@@ -37,10 +46,6 @@ export default {
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
prefix: '<a target="blank" href="https://leafletjs.com/">Leaflet</a>',
attribution: '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
icon: icon({
iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png",
shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png",
}),
};
},
computed: {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/coreml",
"version": "0.1.26",
"version": "0.1.28",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/coreml",
"version": "0.1.26",
"version": "0.1.28",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -34,11 +34,12 @@
"type": "API",
"interfaces": [
"Settings",
"ObjectDetection"
"ObjectDetection",
"ObjectDetectionPreview"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.26"
"version": "0.1.28"
}

View File

@@ -498,6 +498,9 @@ export class H264Repacketizer {
// after the codec information. so codec information can be changed between
// idr and non-idr? maybe it is not applied until next idr?
}
else if (nalType === 0) {
// nal delimiter or something. usually empty.
}
else {
this.console.warn('Skipped a stapa type. Please report this to @koush on Discord.', nalType)
}

View File

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

View File

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

View File

@@ -161,7 +161,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
maybeStartDetection() {
if (!this.hasMotionType) {
// object detection may be restarted if there are slots available.
if (this.cameraDevice.motionDetected && this.plugin.canStartObjectDetection())
if (this.cameraDevice.motionDetected && this.plugin.canStartObjectDetection(this))
this.startPipelineAnalysis();
return;
}
@@ -1022,6 +1022,12 @@ class ObjectDetectionPlugin extends AutoenableMixinProvider implements Settings,
return value;
},
},
developerMode: {
group: 'Advanced',
title: 'Developer Mode',
description: 'Developer mode enables usage of the raw detector object detectors. Using raw object detectors (ie, outside of Scrypted NVR) can cause severe performance degradation.',
type: 'boolean',
}
});
shouldUseSnapshotPipeline() {
@@ -1068,15 +1074,27 @@ class ObjectDetectionPlugin extends AutoenableMixinProvider implements Settings,
return maxConcurrent;
}
canStartObjectDetection() {
canStartObjectDetection(mixin: ObjectDetectionMixin) {
const maxConcurrent = this.maxConcurrent;
const objectDetections = [...this.currentMixins.values()]
const runningDetections = [...this.currentMixins.values()]
.map(d => [...d.currentMixins.values()].filter(dd => !dd.hasMotionType)).flat()
.filter(c => c.detectorRunning)
.sort((a, b) => a.detectionStartTime - b.detectionStartTime);
return objectDetections.length < maxConcurrent;
// already running
if (runningDetections.find(o => o.id === mixin.id))
return false;
if (runningDetections.length < maxConcurrent)
return true;
const [first] = runningDetections;
if (Date.now() - first.detectionStartTime > 30000)
return true;
mixin.console.log(`Not starting object detection to continue processing recent activity on ${first.name}.`);
return false;
}
objectDetectionStarted(name: string, console: Console) {
@@ -1143,7 +1161,7 @@ class ObjectDetectionPlugin extends AutoenableMixinProvider implements Settings,
}
constructor(nativeId?: ScryptedNativeId) {
super(nativeId);
super(nativeId, 'v5');
process.nextTick(() => {
sdk.deviceManager.onDevicesChanged({
@@ -1180,6 +1198,8 @@ class ObjectDetectionPlugin extends AutoenableMixinProvider implements Settings,
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
if (!interfaces.includes(ScryptedInterface.ObjectDetection))
return;
if (!this.storageSettings.values.developerMode && !interfaces.includes(ScryptedInterface.ObjectDetectionGenerator))
return;
return [ScryptedInterface.MixinProvider];
}

View File

@@ -187,7 +187,7 @@ export class OnvifIntercom implements Intercom {
let pending: RtpPacket;
let seqNumber = 0;
const forwarder = await startRtpForwarderProcess(console, ffmpegInput, {
const forwarder = await startRtpForwarderProcess(this.camera.console, ffmpegInput, {
audio: {
onRtp: (rtp) => {
// if (true) {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/opencv",
"version": "0.0.87",
"version": "0.0.89",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/opencv",
"version": "0.0.87",
"version": "0.0.89",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -26,7 +26,8 @@
"runtime": "python",
"type": "API",
"interfaces": [
"ObjectDetection"
"ObjectDetection",
"ObjectDetectionGenerator"
],
"pluginDependencies": [
"@scrypted/objectdetector",
@@ -36,5 +37,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.0.87"
"version": "0.0.89"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/openvino",
"version": "0.1.40",
"version": "0.1.42",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/openvino",
"version": "0.1.40",
"version": "0.1.42",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -34,12 +34,13 @@
"runtime": "python",
"type": "API",
"interfaces": [
"Settings",
"ObjectDetection",
"Settings"
"ObjectDetectionPreview"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.40"
"version": "0.1.42"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/pam-diff",
"version": "0.0.23",
"version": "0.0.24",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/pam-diff",
"version": "0.0.23",
"version": "0.0.24",
"dependencies": {
"@types/node": "^16.6.1",
"pipe2pam": "^0.6.2"

View File

@@ -27,7 +27,8 @@
"name": "PAM Diff Motion Detection",
"type": "API",
"interfaces": [
"ObjectDetection"
"ObjectDetection",
"ObjectDetectionGenerator"
],
"pluginDependencies": [
"@scrypted/objectdetector"
@@ -43,5 +44,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.0.23"
"version": "0.0.24"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/python-codecs",
"version": "0.1.84",
"version": "0.1.85",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/python-codecs",
"version": "0.1.84",
"version": "0.1.85",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/python-codecs",
"version": "0.1.84",
"version": "0.1.85",
"description": "Python Codecs for Scrypted",
"keywords": [
"scrypted",

View File

@@ -60,7 +60,7 @@ class VipsImage(scrypted_sdk.Image):
return memoryview(gray.write_to_memory())
return await to_thread(format)
return await to_thread(lambda: vipsImage.vipsImage.write_to_buffer('.' + options['format']))
return await to_thread(lambda: vipsImage.vipsImage.write_to_buffer(f'.{options["format"]}[Q=80]'))
async def toImageInternal(self, options: scrypted_sdk.ImageOptions = None):
return await to_thread(lambda: toVipsImage(self, options))

View File

@@ -10,7 +10,7 @@
"port": 10081,
"request": "attach",
"skipFiles": [
"**/plugin-remote-worker.*",
"**/plugin-console.*",
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",

View File

@@ -3,3 +3,12 @@
The Snapshot Plugin is a core plugin that provides video to image conversion and image manipulation functionality.
This plugin can be enabled on cameras to add custom snapshot URLs, crop and scale the snapshots, and improves camera snapshot responsiveness.
## Core Features
* Shrink images for better transfer rate on mobile networks.
* Custom Snapshot URLs for RTSP cameras.
* Cache snapshots for better responsiveness.
* Snapshot error recovery.
* Convert video streams to images for cameras without snapshot URLs.
* Resize image service for other plugins.

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/snapshot",
"version": "0.0.60",
"version": "0.0.62",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/snapshot",
"version": "0.0.60",
"version": "0.0.62",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",
"@types/node": "^18.16.18",

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/tensorflow-lite",
"version": "0.1.42",
"version": "0.1.44",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/tensorflow-lite",
"version": "0.1.42",
"version": "0.1.44",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -47,11 +47,12 @@
"type": "API",
"interfaces": [
"Settings",
"ObjectDetection"
"ObjectDetection",
"ObjectDetectionPreview"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.42"
"version": "0.1.44"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/tensorflow-lite",
"version": "0.1.16",
"version": "0.1.18",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/tensorflow-lite",
"version": "0.1.16",
"version": "0.1.18",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -35,11 +35,12 @@
"type": "API",
"interfaces": [
"Settings",
"ObjectDetection"
"ObjectDetection",
"ObjectDetectionPreview"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.16"
"version": "0.1.18"
}

View File

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

View File

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

View File

@@ -161,7 +161,7 @@ export class StorageSettings<T extends string> implements Settings {
this.device.onDeviceEvent(ScryptedInterface.Settings, undefined);
}
private getItemInternal(key: T, setting: StorageSetting, rawDevice?: boolean): any {
getItemInternal(key: T, setting: StorageSetting, rawDevice?: boolean): any {
if (!setting)
return this.device.storage.getItem(key);
const readDefaultValue = () => {

View File

@@ -146,6 +146,8 @@ class ScryptedInterface(str, Enum):
Notifier = "Notifier"
OauthClient = "OauthClient"
ObjectDetection = "ObjectDetection"
ObjectDetectionGenerator = "ObjectDetectionGenerator"
ObjectDetectionPreview = "ObjectDetectionPreview"
ObjectDetector = "ObjectDetector"
ObjectTracker = "ObjectTracker"
OccupancySensor = "OccupancySensor"
@@ -1109,6 +1111,16 @@ class ObjectDetection:
pass
class ObjectDetectionGenerator:
pass
class ObjectDetectionPreview:
pass
class ObjectDetector:
"""ObjectDetector is found on Cameras that have smart detection capabilities."""
@@ -2913,6 +2925,16 @@ ScryptedInterfaceDescriptors = {
],
"properties": []
},
"ObjectDetectionPreview": {
"name": "ObjectDetectionPreview",
"methods": [],
"properties": []
},
"ObjectDetectionGenerator": {
"name": "ObjectDetectionGenerator",
"methods": [],
"properties": []
},
"HumiditySetting": {
"name": "HumiditySetting",
"methods": [

View File

@@ -21,15 +21,16 @@ function toTypescriptType(type: any): string {
for (const name of Object.values(ScryptedInterface)) {
const td = schema.children.find((child: any) => child.name === name);
const properties = td.children.filter((child: any) => child.kindString === 'Property').map((child: any) => child.name);
const methods = td.children.filter((child: any) => child.kindString === 'Method').map((child: any) => child.name);
const children = td.children || [];
const properties = children.filter((child: any) => child.kindString === 'Property').map((child: any) => child.name);
const methods = children.filter((child: any) => child.kindString === 'Method').map((child: any) => child.name);
ScryptedInterfaceDescriptors[name] = {
name,
methods,
properties,
};
for (const p of td.children.filter((child: any) => child.kindString === 'Property')) {
for (const p of children.filter((child: any) => child.kindString === 'Property')) {
allProperties[p.name] = p.type;
}
}
@@ -196,8 +197,9 @@ class ${td.name}:
${toDocstring(td)}
`;
const properties = td.children.filter((child: any) => child.kindString === 'Property');
const methods = td.children.filter((child: any) => child.kindString === 'Method');
const children = td.children || [];
const properties = children.filter((child: any) => child.kindString === 'Property');
const methods = children.filter((child: any) => child.kindString === 'Method');
for (const property of properties) {
python += ` ${property.name}: ${toPythonType(property.type)}${toComment(property)}
`
@@ -208,6 +210,10 @@ ${toDocstring(td)}
`
}
if (!td.children)
python += `
pass
`
}
for (const td of interfaces) {

View File

@@ -1383,6 +1383,10 @@ export interface ObjectDetection {
detectObjects(mediaObject: MediaObject, session?: ObjectDetectionSession): Promise<ObjectsDetected>;
getDetectionModel(settings?: { [key: string]: any }): Promise<ObjectDetectionModel>;
}
export interface ObjectDetectionPreview {
}
export interface ObjectDetectionGenerator {
}
export type ImageFormat = 'gray' | 'rgba' | 'rgb' | 'jpg';
export interface ImageOptions {
crop?: {
@@ -2006,6 +2010,8 @@ export enum ScryptedInterface {
ObjectTracker = "ObjectTracker",
ObjectDetector = "ObjectDetector",
ObjectDetection = "ObjectDetection",
ObjectDetectionPreview = "ObjectDetectionPreview",
ObjectDetectionGenerator = "ObjectDetectionGenerator",
HumiditySetting = "HumiditySetting",
Fan = "Fan",
RTCSignalingChannel = "RTCSignalingChannel",

View File

@@ -15,7 +15,6 @@
"preLaunchTask": "npm: build",
"program": "${workspaceFolder}/bin/scrypted-serve",
"runtimeArgs": [
"--dns-result-order=ipv4first",
"--trace-warnings",
"--nolazy",
],

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/server",
"version": "0.50.0",
"version": "0.51.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.50.0",
"version": "0.51.0",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
@@ -18,14 +18,15 @@
"engine.io": "^6.5.2",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.3",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
"linkfs": "^2.1.0",
"lodash": "^4.17.21",
"memfs": "^4.2.1",
"memfs": "^4.3.0",
"mime": "^3.0.0",
"nan": "^2.17.0",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^9.4.0",
@@ -36,26 +37,27 @@
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"whatwg-mimetype": "^3.0.0",
"ws": "^8.14.1"
"ws": "^8.14.2"
},
"bin": {
"scrypted-serve": "bin/scrypted-serve"
},
"devDependencies": {
"@types/adm-zip": "^0.5.0",
"@types/adm-zip": "^0.5.1",
"@types/cookie-parser": "^1.4.4",
"@types/debug": "^4.1.8",
"@types/express": "^4.17.17",
"@types/http-auth": "^4.1.1",
"@types/follow-redirects": "^1.14.2",
"@types/http-auth": "^4.1.2",
"@types/ip": "^1.1.0",
"@types/lodash": "^4.14.198",
"@types/mime": "^3.0.1",
"@types/node-dijkstra": "^2.5.3",
"@types/node-forge": "^1.3.5",
"@types/pem": "^1.14.0",
"@types/semver": "^7.5.1",
"@types/pem": "^1.14.1",
"@types/semver": "^7.5.2",
"@types/source-map-support": "^0.5.7",
"@types/tar": "^6.1.5",
"@types/tar": "^6.1.6",
"@types/whatwg-mimetype": "^3.0.0",
"@types/ws": "^8.5.5"
},
@@ -192,9 +194,9 @@
}
},
"node_modules/@types/adm-zip": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.0.tgz",
"integrity": "sha512-FCJBJq9ODsQZUNURo5ILAQueuA8WJhRvuihS3ke2iI25mJlfV2LK8jG2Qj2z2AWg8U0FtWWqBHVRetceLskSaw==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.1.tgz",
"integrity": "sha512-3+psmbh60N5JXM2LMkujFqnjMf3KB0LZoIQO73NJvkv57q+384nK/A7pP0v+ZkB/Zrfqn+5xtAyt5OsY+GiYLQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -274,10 +276,19 @@
"@types/send": "*"
}
},
"node_modules/@types/follow-redirects": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/@types/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-NfvpNEAyiTvM+Wq4iTrSCyuryLG+ZIrnhUbvgakBxMiTY4Q959jX9GHGXm0p/jQgry1hjd7c2eRjT2NJX1m5uQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/http-auth": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/http-auth/-/http-auth-4.1.1.tgz",
"integrity": "sha512-oBOiSe402K7P2yoCDLctjFOoUfiBBbvoZ3RVRr3vZg7R/RW2U+BbYR2MYFNrQ6S3Kwtvt893DowfNe9g5LyIXQ==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@types/http-auth/-/http-auth-4.1.2.tgz",
"integrity": "sha512-MBN/yFiZ3dofn3YO4gX8boXEpD1KDgcYDkr7BHIBIUhABFMlpeAro08a2l2rO7BBxJmo2dW8Tp9Zu9ISLJlp0w==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -331,9 +342,9 @@
}
},
"node_modules/@types/pem": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@types/pem/-/pem-1.14.0.tgz",
"integrity": "sha512-Jaq0lQWgz2jVmQ+smGDbPnLye6DOLojykwmnGgveoYb3sjZfrxO+2ssbccbekKKCTFxoLSEGCP/48z9FPjAbJg==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@types/pem/-/pem-1.14.1.tgz",
"integrity": "sha512-blRIGpofJKMV7v6UnS/R75AUAqLfla212lN/7TXYtTcGSqvMv84Aiwl/xg8MirahHTvDABRV8PdnCT1fVZW09g==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -352,9 +363,9 @@
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz",
"integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==",
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==",
"dev": true
},
"node_modules/@types/send": {
@@ -393,9 +404,9 @@
}
},
"node_modules/@types/tar": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.5.tgz",
"integrity": "sha512-qm2I/RlZij5RofuY7vohTpYNaYcrSQlN2MyjucQc7ZweDwaEWkdN/EeNh6e9zjK6uEm6PwjdMXkcj05BxZdX1Q==",
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.6.tgz",
"integrity": "sha512-HQ06kiiDXz9uqtmE9ksQUn1ovcPr1gGV9EgaCWo6FGYKD0onNBCetBzL0kfcS8Kbj1EFxJWY9jL2W4ZvvtGI8Q==",
"dev": true,
"dependencies": {
"@types/node": "*",
@@ -1355,6 +1366,25 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -1925,9 +1955,9 @@
}
},
"node_modules/memfs": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.2.1.tgz",
"integrity": "sha512-CINEB6cNAAhLUfRGrB4lj2Pj47ygerEmw3jxPb6R1gkD6Jfp484gJLteQ6MzqIjGWtFWuVzDl+KN7HiipMuKSw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.3.0.tgz",
"integrity": "sha512-bOMyYalKCLeEkd5l3sYv0XIsVPQW+2oGhYm8LwD1htXS637VmIdoXVrWPxZdbJlEogDIrTnm6wqqZBrYb7ZFPw==",
"dependencies": {
"json-joy": "^9.2.0",
"thingies": "^1.11.1"
@@ -2193,9 +2223,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nan": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
"integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ=="
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
"integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w=="
},
"node_modules/napi-build-utils": {
"version": "1.0.2",
@@ -3589,9 +3619,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz",
"integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==",
"version": "8.14.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
"engines": {
"node": ">=10.0.0"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/server",
"version": "0.50.0",
"version": "0.51.0",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
@@ -12,14 +12,15 @@
"engine.io": "^6.5.2",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.3",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
"linkfs": "^2.1.0",
"lodash": "^4.17.21",
"memfs": "^4.2.1",
"memfs": "^4.3.0",
"mime": "^3.0.0",
"nan": "^2.17.0",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^9.4.0",
@@ -30,23 +31,24 @@
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"whatwg-mimetype": "^3.0.0",
"ws": "^8.14.1"
"ws": "^8.14.2"
},
"devDependencies": {
"@types/adm-zip": "^0.5.0",
"@types/adm-zip": "^0.5.1",
"@types/cookie-parser": "^1.4.4",
"@types/debug": "^4.1.8",
"@types/express": "^4.17.17",
"@types/http-auth": "^4.1.1",
"@types/follow-redirects": "^1.14.2",
"@types/http-auth": "^4.1.2",
"@types/ip": "^1.1.0",
"@types/lodash": "^4.14.198",
"@types/mime": "^3.0.1",
"@types/node-dijkstra": "^2.5.3",
"@types/node-forge": "^1.3.5",
"@types/pem": "^1.14.0",
"@types/semver": "^7.5.1",
"@types/pem": "^1.14.1",
"@types/semver": "^7.5.2",
"@types/source-map-support": "^0.5.7",
"@types/tar": "^6.1.5",
"@types/tar": "^6.1.6",
"@types/whatwg-mimetype": "^3.0.0",
"@types/ws": "^8.5.5"
},

View File

@@ -1,11 +1,61 @@
import { once } from 'events';
import { http, https } from 'follow-redirects';
import { RequestOptions, IncomingMessage } from 'http';
export async function getNpmPackageInfo(pkg: string) {
return fetchJSON(`https://registry.npmjs.org/${pkg}`);
const { json } = await fetchJSON(`https://registry.npmjs.org/${pkg}`, {
// force ipv4 in case of busted ipv6.
family: 4,
});
return json;
}
export async function fetchJSON(url: string, init?: RequestInit) {
return await (await fetch(url, init)).json();
export async function fetchJSON(url: string, init?: RequestOptions) {
init ||= {};
init.headers = {
...init.headers,
Accept: 'application/json',
};
const { body, headers } = await fetchBuffer(url, init);
return {
json: JSON.parse(body.toString()),
headers,
}
}
export async function fetchBuffer(url: string, init?: RequestInit) {
return Buffer.from(await (await fetch(url, init)).arrayBuffer());
export async function fetchPostJSON(url: string, postBody: any, init?: RequestOptions) {
init ||= {};
init.method = 'POST';
init.headers = {
...init.headers,
Accept: 'application/json',
'Content-Type': 'application/json',
};
const { body, headers } = await fetchBuffer(url, init, Buffer.from(JSON.stringify(postBody)));
return {
json: JSON.parse(body.toString()),
headers,
}
}
export async function fetchBuffer(url: string, init?: RequestOptions, body?: Buffer) {
const proto = url.startsWith('https:') ? https : http;
const request = proto.request(url, {
...init,
});
if (body)
request.write(body);
request.end();
const [response] = await once(request, 'response') as IncomingMessage[];
const buffers: Buffer[] = [];
response.on('data', buffer => buffers.push(buffer));
await once(response, 'end');
return {
headers: response.headers,
body: Buffer.concat(buffers),
};
}

View File

@@ -510,7 +510,10 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
}
console.log('installing package', pkg, version);
const tarball = await fetchBuffer(`${registry.versions[version].dist.tarball}`);
const { body: tarball } = await fetchBuffer(`${registry.versions[version].dist.tarball}`, {
// force ipv4 in case of busted ipv6.
family: 4,
});
console.log('downloaded tarball', tarball?.length);
const parse = new (tar.Parse as any)();
const files: { [name: string]: Buffer } = {};