UNPKG

camstreamerlib

Version:

Helper library for CamStreamer ACAP applications.

480 lines (479 loc) 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VapixAPI = void 0; const prettifyXml = require("prettify-xml"); const utils_1 = require("./internal/utils"); const VapixAPI_1 = require("./types/VapixAPI"); const errors_1 = require("./errors/errors"); const ProxyClient_1 = require("./internal/ProxyClient"); const zod_1 = require("zod"); const fast_xml_parser_1 = require("fast-xml-parser"); class VapixAPI { client; constructor(client, getProxyUrl) { this.client = new ProxyClient_1.ProxyClient(client, getProxyUrl); } async getUrlEncoded(proxy, path, parameters, headers = {}) { const data = (0, utils_1.paramToUrl)(parameters); const head = { ...headers, 'Content-Type': 'application/x-www-form-urlencoded' }; const res = await this.client.post(proxy, path, data, {}, head); if (!res.ok) { throw new Error(await (0, utils_1.responseStringify)(res)); } return res; } async postJson(proxy, path, jsonData, headers = {}) { const data = JSON.stringify(jsonData); const head = { ...headers, 'Content-Type': 'application/json' }; const res = await this.client.post(proxy, path, data, {}, head); if (!res.ok) { throw new Error(await (0, utils_1.responseStringify)(res)); } return res; } async getCameraImage(params, proxy = null) { return await this.client.get(proxy, '/axis-cgi/jpg/image.cgi', params); } async getEventDeclarations(proxy = null) { const data = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' + '<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' + 'xmlns:xsd="http://www.w3.org/2001/XMLSchema">' + '<GetEventInstances xmlns="http://www.axis.com/vapix/ws/event1"/>' + '</s:Body>' + '</s:Envelope>'; const res = await this.client.post(proxy, '/vapix/services', data, { 'Content-Type': 'application/soap+xml' }); if (!res.ok) { throw new Error(await (0, utils_1.responseStringify)(res)); } const declarations = await res.text(); return prettifyXml(declarations); } async getSupportedAudioSampleRate(proxy = null) { const url = '/axis-cgi/audio/streamingcapabilities.cgi'; const jsonData = { apiVersion: '1.0', method: 'list' }; const res = await this.postJson(proxy, url, jsonData); const encoders = VapixAPI_1.audioSampleRatesResponseSchema.parse(await res.json()).data.encoders; const data = encoders.aac ?? encoders.AAC ?? []; return data.map((item) => { return { sampleRate: item.sample_rate, bitRates: item.bit_rates, }; }); } async performAutofocus(proxy = null) { try { const data = { apiVersion: '1', method: 'performAutofocus', params: { optics: [ { opticsId: '0', }, ], }, }; await this.postJson(proxy, '/axis-cgi/opticscontrol.cgi', data); } catch (err) { await this.getUrlEncoded(proxy, '/axis-cgi/opticssetup.cgi', { autofocus: 'perform', source: '1', }); } } async checkSDCard(proxy = null) { const res = await this.getUrlEncoded(proxy, '/axis-cgi/disks/list.cgi', { diskid: 'SD_DISK', }); const xmlText = await res.text(); const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', allowBooleanAttributes: true, }); const result = parser.parse(xmlText); const data = result.root.disks.disk; return { totalSize: parseInt(data.totalsize), freeSize: parseInt(data.freesize), status: VapixAPI_1.sdCardWatchedStatuses.includes(data.status) ? data.status : 'disconnected', }; } mountSDCard(proxy = null) { return this._doSDCardMountAction('MOUNT', proxy); } unmountSDCard(proxy = null) { return this._doSDCardMountAction('UNMOUNT', proxy); } async _doSDCardMountAction(action, proxy = null) { const res = await this.getUrlEncoded(proxy, '/axis-cgi/disks/mount.cgi', { action: action, diskid: 'SD_DISK', }); const textXml = await res.text(); const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', allowBooleanAttributes: true, }); const result = parser.parse(textXml); const job = result.root.job; if (job.result !== 'OK') { throw new errors_1.SDCardActionError(action, await (0, utils_1.responseStringify)(res)); } return Number(job.jobid); } async fetchSDCardJobProgress(jobId, proxy = null) { const res = await this.getUrlEncoded(proxy, '/disks/job.cgi', { jobid: String(jobId), diskid: 'SD_DISK', }); const textXml = await res.text(); const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', allowBooleanAttributes: true, }); const job = parser.parse(textXml).root.job; if (job.result !== 'OK') { throw new errors_1.SDCardJobError(); } return Number(job.progress); } downloadCameraReport(proxy = null) { return this.getUrlEncoded(proxy, '/axis-cgi/serverreport.cgi', { mode: 'text' }); } getSystemLog(proxy = null) { return this.getUrlEncoded(proxy, '/axis-cgi/admin/systemlog.cgi'); } async getMaxFps(channel, proxy = null) { const data = { apiVersion: '1.0', method: 'getCaptureModes' }; const res = await this.postJson(proxy, '/axis-cgi/capturemode.cgi', data); const response = VapixAPI_1.maxFpsResponseSchema.parse(await res.json()); const channels = response.data; if (channels === undefined) { throw new errors_1.MaxFPSError('MALFORMED_REPLY'); } const channelData = channels.find((x) => x.channel === channel); if (channelData === undefined) { throw new errors_1.MaxFPSError('CHANNEL_NOT_FOUND'); } const captureModes = channelData.captureMode; const captureMode = captureModes.find((x) => x.enabled === true); if (captureMode === undefined) { throw new errors_1.MaxFPSError('CAPTURE_MODE_NOT_FOUND'); } if ((0, utils_1.isNullish)(captureMode.maxFPS)) { throw new errors_1.MaxFPSError('FPS_NOT_SPECIFIED'); } return captureMode.maxFPS; } async getTimezone(proxy = null) { const data = { apiVersion: '1.0', method: 'getDateTimeInfo' }; const res = await this.postJson(proxy, '/axis-cgi/time.cgi', data); return (await res.json())?.timeZone ?? 'Europe/Prague'; } async getDateTimeInfo(proxy = null) { const data = { apiVersion: '1.0', method: 'getDateTimeInfo' }; const res = await this.postJson(proxy, '/axis-cgi/time.cgi', data); return VapixAPI_1.dateTimeinfoSchema.parse(await res.json()); } async getDevicesSettings(proxy = null) { const data = { apiVersion: '1.0', method: 'getDevicesSettings' }; const res = await this.postJson(proxy, '/axis-cgi/audiodevicecontrol.cgi', data); const result = VapixAPI_1.audioDeviceRequestSchema.parse(await res.json()); return result.data.devices.map((device) => ({ ...device, inputs: (device.inputs || []).sort((a, b) => a.id.localeCompare(b.id)), outputs: (device.outputs || []).sort((a, b) => a.id.localeCompare(b.id)), })); } async fetchRemoteDeviceInfo(payload, proxy = null) { const res = await this.postJson(proxy, '/axis-cgi/basicdeviceinfo.cgi', payload); const textXml = await res.text(); const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', allowBooleanAttributes: true, }); const result = parser.parse(textXml); if ((0, utils_1.isNullish)(result.body.data)) { throw new errors_1.NoDeviceInfoError(); } return result.data; } async getHeaders(proxy = null) { const data = { apiVersion: '1.0', method: 'list' }; const res = await this.postJson(proxy, '/axis-cgi/customhttpheader.cgi', data); return zod_1.z.object({ data: zod_1.z.record(zod_1.z.string()) }).parse(await res.json()).data; } async setHeaders(headers, proxy = null) { const data = { apiVersion: '1.0', method: 'set', params: headers }; return this.postJson(proxy, '/axis-cgi/customhttpheader.cgi', data); } async getParameter(paramNames, proxy = null) { const response = await this.getUrlEncoded(proxy, '/axis-cgi/param.cgi', { action: 'list', group: (0, utils_1.arrayToUrl)(paramNames), }); return parseParameters(await response.text()); } async setParameter(params, proxy = null) { const res = await this.getUrlEncoded(proxy, '/axis-cgi/param.cgi', { ...params, action: 'update', }); const responseText = await res.text(); if (responseText.startsWith('# Error')) { throw new Error(responseText); } return true; } async getGuardTourList(proxy = null) { const gTourList = new Array(); const response = await this.getParameter('GuardTour', proxy); for (let i = 0; i < 20; i++) { const gTourBaseName = 'root.GuardTour.G' + i; if (gTourBaseName + '.CamNbr' in response) { const gTour = { id: gTourBaseName, camNbr: response[gTourBaseName + '.CamNbr'], name: response[gTourBaseName + '.Name'] ?? 'Guard Tour ' + (i + 1), randomEnabled: response[gTourBaseName + '.RandomEnabled'], running: response[gTourBaseName + '.Running'] ?? 'no', timeBetweenSequences: response[gTourBaseName + '.TimeBetweenSequences'], tour: [], }; for (let j = 0; j < 100; j++) { const tourBaseName = 'root.GuardTour.G' + i + '.Tour.T' + j; if (tourBaseName + '.MoveSpeed' in response) { const tour = { moveSpeed: response[tourBaseName + '.MoveSpeed'], position: response[tourBaseName + '.Position'], presetNbr: response[tourBaseName + '.PresetNbr'], waitTime: response[tourBaseName + '.WaitTime'], waitTimeViewType: response[tourBaseName + '.WaitTimeViewType'], }; gTour.tour.push(tour); } } gTourList.push(gTour); } else { break; } } return gTourList; } setGuardTourEnabled(guardTourID, enable, proxy = null) { const options = {}; options[guardTourID + '.Running'] = enable ? 'yes' : 'no'; return this.setParameter(options, proxy); } async getPTZPresetList(channel, proxy = null) { const res = await this.getUrlEncoded(proxy, '/axis-cgi/com/ptz.cgi', { query: 'presetposcam', camera: channel.toString(), }); const text = await res.text(); const lines = text.split(/[\r\n]/); const positions = []; for (const line of lines) { if (line.indexOf('presetposno') !== -1) { const delimiterPos = line.indexOf('='); if (delimiterPos !== -1) { const value = line.substring(delimiterPos + 1); positions.push(value); } } } return positions; } async listPTZ(camera, proxy = null) { const url = `/axis-cgi/com/ptz.cgi`; const response = await this.getUrlEncoded(proxy, url, { camera, query: 'presetposcamdata', format: 'json', }); const text = await response.text(); if (text === '') { throw new errors_1.PtzNotSupportedError(); } return parseCameraPtzResponse(text)[camera] ?? []; } async listPtzVideoSourceOverview(proxy = null) { const response = await this.getUrlEncoded(proxy, '/axis-cgi/com/ptz.cgi', { query: 'presetposall', format: 'json', }); const text = await response.text(); if (text === '') { throw new errors_1.PtzNotSupportedError(); } const data = parseCameraPtzResponse(text); const res = {}; Object.keys(data) .map(Number) .forEach((camera) => { if (data[camera] !== undefined) { res[camera - 1] = data[camera]?.map(({ data: itemData, ...d }) => d); } }); return res; } goToPreset(channel, presetName, proxy = null) { return this.getUrlEncoded(proxy, '/axis-cgi/com/ptz.cgi', { camera: channel.toString(), gotoserverpresetname: presetName, }); } async getPtzPosition(camera, proxy = null) { const res = await this.getUrlEncoded(proxy, '/axis-cgi/com/ptz.cgi', { query: 'position', camera: camera.toString(), }); const params = parseParameters(await res.text()); return { pan: Number(params.pan), tilt: Number(params.tilt), zoom: Number(params.zoom), }; } async getInputState(port, proxy = null) { const response = await (await this.getUrlEncoded(proxy, '/axis-cgi/io/port.cgi', { checkactive: port.toString() })).text(); return response.split('=')[1]?.indexOf('active') === 0; } async setOutputState(port, active, proxy = null) { return this.getUrlEncoded(proxy, '/axis-cgi/io/port.cgi', { action: active ? `${port}:/` : `${port}:\\` }); } async getApplicationList(proxy = null) { const res = await this.client.get(proxy, '/axis-cgi/applications/list.cgi'); const xml = await res.text(); const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', allowBooleanAttributes: true, }); const result = parser.parse(xml); return result.reply.application.map((app) => { return { ...app, appId: VapixAPI_1.APP_IDS.find((id) => id.toLowerCase() === app.Name.toLowerCase()) ?? null, }; }); } async startApplication(applicationID, proxy = null) { const res = await this.client.get(proxy, '/axis-cgi/applications/control.cgi', { package: applicationID.toLowerCase(), action: 'start', }); const text = (await res.text()).trim().toLowerCase(); if (text !== 'ok' && !(text.startsWith('error:') && text.substring(7) === '6')) { throw new errors_1.ApplicationAPIError('START', await (0, utils_1.responseStringify)(res)); } } async restartApplication(applicationID, proxy = null) { const res = await this.client.get(proxy, '/axis-cgi/applications/control.cgi', { package: applicationID.toLowerCase(), action: 'restart', }); const text = (await res.text()).trim().toLowerCase(); if (text !== 'ok') { throw new errors_1.ApplicationAPIError('RESTART', await (0, utils_1.responseStringify)(res)); } } async stopApplication(applicationID, proxy = null) { const res = await this.client.get(proxy, '/axis-cgi/applications/control.cgi', { package: applicationID.toLowerCase(), action: 'stop', }); const text = (await res.text()).trim().toLowerCase(); if (text !== 'ok' && !(text.startsWith('error:') && text.substring(7) === '6')) { throw new errors_1.ApplicationAPIError('STOP', await (0, utils_1.responseStringify)(res)); } } async installApplication(data, fileName) { const formData = new FormData(); formData.append('packfil', data, fileName); const res = await this.client.post(null, '/axis-cgi/applications/upload.cgi', formData, {}, { contentType: 'application/octet-stream', }); if (!res.ok) { throw new Error(await (0, utils_1.responseStringify)(res)); } const text = await res.text(); if (text.length > 5) { throw new Error('installing error: ' + text); } } } exports.VapixAPI = VapixAPI; const parseParameters = (response) => { const params = {}; const lines = response.split(/[\r\n]/); for (const line of lines) { if (line.length === 0 || line.substring(0, 7) === '# Error') { continue; } const delimiterPos = line.indexOf('='); if (delimiterPos !== -1) { const paramName = line.substring(0, delimiterPos).replace('root.', ''); const paramValue = line.substring(delimiterPos + 1); params[paramName] = paramValue; } } return params; }; const parseCameraPtzResponse = (response) => { const json = JSON.parse(response); const parsed = {}; Object.keys(json).forEach((key) => { if (!key.startsWith('Camera ')) { return; } const camera = Number(key.replace('Camera ', '')); if (json[key].presets !== undefined) { parsed[camera] = parsePtz(json[key].presets); } }); return parsed; }; const parsePtz = (parsed) => { const res = []; parsed.forEach((value) => { const delimiterPos = value.indexOf('='); if (delimiterPos === -1) { return; } if (!value.startsWith('presetposno')) { return; } const id = Number(value.substring(11, delimiterPos)); if (Number.isNaN(id)) { return; } const data = value.substring(delimiterPos + 1).split(':'); const getValue = (valueName) => { for (const d of data) { const p = d.split('='); if (p[0] === valueName) { return Number(p[1]); } } return 0; }; res.push({ id, name: data[0] ?? 'Preset ' + id, data: { pan: getValue('pan'), tilt: getValue('tilt'), zoom: getValue('zoom'), }, }); }); return res; };