UNPKG

homebridge-eufy-security-mikebcbc

Version:
190 lines 9.65 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FfmpegCodecs = void 0; const node_child_process_1 = require("node:child_process"); const node_os_1 = __importDefault(require("node:os")); const node_process_1 = __importDefault(require("node:process")); const node_util_1 = __importDefault(require("node:util")); class FfmpegCodecs { constructor(platform) { this.platform = platform; this.log = this.platform.log; this.videoProcessor = this.platform.videoProcessor; this._gpuMem = 0; this._ffmpegVersion = ''; this.videoProcessorCodecs = {}; this.videoProcessorHwAccels = {}; } // Launch our configured controllers once all accessories have been loaded. Once we do, they will sustain themselves. async probe() { // Let's conduct our system-specific capability probes. switch (this.platform.hostSystem) { case 'raspbian': // If we're on a Raspberry Pi, let's verify that we have enough GPU memory for hardware-based decoding and encoding. await this.probeRpiGpuMem(); break; default: break; } // Capture the version information of FFmpeg. if (!(await this.probeFfmpegVersion())) { return false; } // Ensure we've got a working video processor before we do anything else. if (!(await this.probeVideoProcessorCodecs()) || !(await this.probeVideoProcessorHwAccel())) { return false; } return true; } // Utility to determine whether or not a specific decoder is available to the video processor for a given format. hasDecoder(codec, decoder) { var _a; // Normalize our lookups. codec = codec.toLowerCase(); decoder = decoder.toLowerCase(); return (_a = this.videoProcessorCodecs[codec]) === null || _a === void 0 ? void 0 : _a.decoders.some(x => x === decoder); } // Utility to determine whether or not a specific encoder is available to the video processor for a given format. hasEncoder(codec, encoder) { var _a; // Normalize our lookups. codec = codec.toLowerCase(); encoder = encoder.toLowerCase(); return (_a = this.videoProcessorCodecs[codec]) === null || _a === void 0 ? void 0 : _a.encoders.some(x => x === encoder); } // Utility to determine whether or not a specific decoder is available to the video processor for a given format. hasHwAccel(accel) { return this.videoProcessorHwAccels[accel.toLowerCase()] ? true : false; } // Utility that returns the amount of GPU memory available to us. get gpuMem() { return this._gpuMem; } get ffmpegVersion() { return this._ffmpegVersion; } async probeFfmpegVersion() { return this.probeCmd(this.videoProcessor, ['-hide_banner', '-version'], (stdout) => { // A regular expression to parse out the version. const versionRegex = /^ffmpeg version (.*) Copyright.*$/m; // Parse out the version string. const versionMatch = versionRegex.exec(stdout); // If we have a version string, let's save it. Otherwise, we're blind. this._ffmpegVersion = versionMatch ? versionMatch[1] : 'unknown'; this.log.debug(`Using FFmpeg version: ${this.ffmpegVersion}`); }); } // Probe our video processor's hardware acceleration capabilities. async probeVideoProcessorHwAccel() { if (!(await this.probeCmd(this.videoProcessor, ['-hide_banner', '-hwaccels'], (stdout) => { // Iterate through each line, and a build a list of encoders. for (const accel of stdout.split(node_os_1.default.EOL)) { // Skip blank lines. if (!accel.length) { continue; } // Skip the first line. if (accel === 'Hardware acceleration methods:') { continue; } // We've found a hardware acceleration method, let's add it. this.videoProcessorHwAccels[accel.toLowerCase()] = true; } }))) { return false; } // Let's test to ensure that just because we have a codec or capability available to us, it doesn't necessarily mean that the user has the hardware capabilities // needed to use it, resulting in an FFmpeg error. We catch that here and prevent those capabilities from being exposed to HBUP unless both software and hardware // capabilities enable it. This simple test, generates a one-second video that is processed by the requested codec. If it fails, we discard the codec. for (const accel of Object.keys(this.videoProcessorHwAccels)) { // eslint-disable-next-line no-await-in-loop if (!(await this.probeCmd(this.videoProcessor, [ '-hide_banner', '-hwaccel', accel, '-v', 'quiet', '-t', '1', '-f', 'lavfi', '-i', 'color=black:1920x1080', '-c:v', 'libx264', '-f', 'null', '-', ], () => { }, true))) { delete this.videoProcessorHwAccels[accel]; this.log.error(`Hardware-accelerated decoding and encoding using ${accel} will be unavailable: unable to successfully validate capabilities.`); } } return true; } // Probe our video processor's encoding and decoding capabilities. async probeVideoProcessorCodecs() { return this.probeCmd(this.videoProcessor, ['-hide_banner', '-codecs'], (stdout) => { // A regular expression to parse out the codec and it's supported decoders. const decodersRegex = /\S+\s+(\S+).+\(decoders: (.*?)\s*\)/; // A regular expression to parse out the codec and it's supported encoders. const encodersRegex = /\S+\s+(\S+).+\(encoders: (.*?)\s*\)/; // Iterate through each line, and a build a list of encoders. for (const codecLine of stdout.split(node_os_1.default.EOL)) { // Let's see if we have decoders. const decodersMatch = decodersRegex.exec(codecLine); // Let's see if we have encoders. const encodersMatch = encodersRegex.exec(codecLine); // If we found decoders, add them to our list of supported decoders for this format. if (decodersMatch) { this.videoProcessorCodecs[decodersMatch[1]] = { decoders: [], encoders: [] }; this.videoProcessorCodecs[decodersMatch[1]].decoders = decodersMatch[2].split(' ').map(x => x.toLowerCase()); } // If we found decoders, add them to our list of supported decoders for this format. if (encodersMatch) { if (!this.videoProcessorCodecs[encodersMatch[1]]) { this.videoProcessorCodecs[encodersMatch[1]] = { decoders: [], encoders: [] }; } this.videoProcessorCodecs[encodersMatch[1]].encoders = encodersMatch[2].split(' ').map(x => x.toLowerCase()); } } }); } // Probe Raspberry Pi GPU. async probeRpiGpuMem() { return this.probeCmd('vcgencmd', ['get_mem', 'gpu'], (stdout) => { // A regular expression to parse out the configured GPU memory on the Raspberry Pi. const gpuRegex = /^gpu=(.*)M\n$/; // Let's see what we've got. const gpuMatch = gpuRegex.exec(stdout); // We matched what we're looking for. if (gpuMatch) { // Parse the result and retrieve our allocated GPU memory. this._gpuMem = parseInt(gpuMatch[1]); // Something went wrong. if (isNaN(this._gpuMem)) { this._gpuMem = 0; } } }); } // Utility to probe the capabilities of FFmpeg and the host platform. async probeCmd(command, commandLineArgs, processOutput, quietRunErrors = false) { try { // Promisify exec to allow us to wait for it asynchronously. const execAsync = node_util_1.default.promisify(node_child_process_1.execFile); // Check for the codecs in our video processor. const { stdout } = await execAsync(command, commandLineArgs); processOutput(stdout); return true; } catch (error) { // It's really a SystemError, but Node hides that type from us for esoteric reasons. if (error instanceof Error) { const execError = error; if (execError.code === 'ENOENT') { this.log.error(`Unable to find '${command}' in path: '${node_process_1.default.env['PATH']}'.`); } else if (quietRunErrors) { return false; } else { this.log.error(`Error running ${command}: ${error.message}`); } } this.log.error(`Unable to probe the capabilities of your Homebridge host without access to '${command}'. Ensure that it is available in your path and correctly working.`); return false; } } } exports.FfmpegCodecs = FfmpegCodecs; //# sourceMappingURL=ffmpeg-codecs.js.map