7fd1d78ffa
Features: - Optional sub-stream selection from already discovered streams - No additional scanning required - reuse existing results - UI: "Add Sub Stream" button to select secondary stream - UI: "Remove Sub Stream" button to clear selection - Smart stream routing in Frigate configs - Go2RTC: generates _main and _sub stream names - Frigate: detect on sub (CPU efficient), record on main (quality) - Frigate: auto-detection of stream resolution - Object detection: person, car, cat, dog - Motion-based recording by default - Live view streams configuration - Support for any resolution: HD, 4K, 8K+ - Comprehensive documentation with examples
180 lines
6.6 KiB
JavaScript
180 lines
6.6 KiB
JavaScript
/**
|
|
* Frigate NVR Configuration Generator
|
|
* Generates unified Frigate + Go2RTC YAML configs
|
|
* All cameras are routed through Frigate's built-in go2rtc for optimal performance
|
|
*/
|
|
export class FrigateGenerator {
|
|
/**
|
|
* Generate complete Frigate config with embedded Go2RTC
|
|
* @param {Object} mainStream - Main stream object (used for recording)
|
|
* @param {Object} subStream - Optional sub stream object (used for detection if provided)
|
|
* @returns {string} YAML configuration string
|
|
*/
|
|
static generate(mainStream, subStream = null) {
|
|
const cameraName = this.generateCameraName(mainStream);
|
|
const config = [];
|
|
|
|
// MQTT Configuration
|
|
config.push('mqtt:');
|
|
config.push(' enabled: false');
|
|
config.push('');
|
|
|
|
// Global Record Configuration
|
|
config.push('# Global Recording Settings');
|
|
config.push('record:');
|
|
config.push(' enabled: true');
|
|
config.push(' retain:');
|
|
config.push(' days: 7');
|
|
config.push(' mode: motion # Record only on motion detection');
|
|
config.push('');
|
|
|
|
// Generate Go2RTC section
|
|
config.push('# Go2RTC Configuration (Frigate built-in)');
|
|
config.push('go2rtc:');
|
|
config.push(' streams:');
|
|
|
|
// Main stream configuration
|
|
const mainStreamName = this.generateStreamName(mainStream, 'main');
|
|
const mainSource = this.generateGo2RTCSource(mainStream);
|
|
config.push(` '${mainStreamName}':`);
|
|
config.push(` - ${mainSource}`);
|
|
|
|
// Sub stream configuration if provided
|
|
if (subStream) {
|
|
config.push('');
|
|
const subStreamName = this.generateStreamName(subStream, 'sub');
|
|
const subSource = this.generateGo2RTCSource(subStream);
|
|
config.push(` '${subStreamName}':`);
|
|
config.push(` - ${subSource}`);
|
|
}
|
|
|
|
config.push('');
|
|
|
|
// Generate Frigate cameras section
|
|
config.push('# Frigate Camera Configuration');
|
|
config.push('cameras:');
|
|
config.push(` ${cameraName}:`);
|
|
config.push(' ffmpeg:');
|
|
config.push(' inputs:');
|
|
|
|
if (subStream) {
|
|
// If sub stream exists: use it for detection, main for recording
|
|
const subStreamName = this.generateStreamName(subStream, 'sub');
|
|
config.push(` - path: rtsp://127.0.0.1:8554/${subStreamName}`);
|
|
config.push(' input_args: preset-rtsp-restream');
|
|
config.push(' roles:');
|
|
config.push(' - detect');
|
|
config.push(` - path: rtsp://127.0.0.1:8554/${mainStreamName}`);
|
|
config.push(' input_args: preset-rtsp-restream');
|
|
config.push(' roles:');
|
|
config.push(' - record');
|
|
} else {
|
|
// No sub stream: use main for both detection and recording
|
|
config.push(` - path: rtsp://127.0.0.1:8554/${mainStreamName}`);
|
|
config.push(' input_args: preset-rtsp-restream');
|
|
config.push(' roles:');
|
|
config.push(' - detect');
|
|
config.push(' - record');
|
|
}
|
|
|
|
// Live view configuration
|
|
if (subStream) {
|
|
config.push(' live:');
|
|
config.push(' streams:');
|
|
config.push(` Main Stream: ${mainStreamName} # HD для просмотра`);
|
|
config.push(` Sub Stream: ${this.generateStreamName(subStream, 'sub')} # Низкое разрешение (опционально)`);
|
|
}
|
|
|
|
// Object detection configuration
|
|
config.push(' objects:');
|
|
config.push(' track:');
|
|
config.push(' - person');
|
|
config.push(' - car');
|
|
config.push(' - cat');
|
|
config.push(' - dog');
|
|
|
|
// Recording configuration
|
|
config.push(' record:');
|
|
config.push(' enabled: true');
|
|
|
|
config.push('');
|
|
config.push('version: 0.16-0');
|
|
|
|
return config.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Generate Go2RTC source configuration based on stream type
|
|
* Returns the source string for go2rtc streams section
|
|
*/
|
|
static generateGo2RTCSource(stream) {
|
|
// Handle JPEG snapshots with exec:ffmpeg conversion
|
|
// Uses full path to ffmpeg and {{output}} for Frigate template escaping
|
|
if (stream.type === 'JPEG') {
|
|
return [
|
|
'exec:/usr/lib/ffmpeg/7.0/bin/ffmpeg',
|
|
'-loglevel quiet',
|
|
'-f image2',
|
|
'-loop 1',
|
|
'-framerate 10',
|
|
`-i ${stream.url}`,
|
|
'-c:v libx264',
|
|
'-preset ultrafast',
|
|
'-tune zerolatency',
|
|
'-g 20',
|
|
'-f rtsp {{output}}' // Double braces for Frigate template escaping
|
|
].join(' ');
|
|
}
|
|
|
|
// Handle ONVIF - convert to onvif:// format if needed
|
|
if (stream.type === 'ONVIF') {
|
|
try {
|
|
const urlObj = new URL(stream.url);
|
|
// Extract credentials and host from HTTP URL
|
|
const username = urlObj.username || 'admin';
|
|
const password = urlObj.password || '';
|
|
const host = urlObj.hostname;
|
|
const port = urlObj.port || '80';
|
|
|
|
// Generate onvif:// URL
|
|
return `onvif://${username}:${password}@${host}:${port}`;
|
|
} catch (e) {
|
|
// If URL parsing fails, return as-is
|
|
return stream.url;
|
|
}
|
|
}
|
|
|
|
// For all other types (RTSP, MJPEG, HLS, HTTP-FLV, RTMP, etc.): use direct URL
|
|
// Go2RTC handles these formats natively
|
|
return stream.url;
|
|
}
|
|
|
|
/**
|
|
* Generate camera name from IP address
|
|
* Format: "camera_192_168_1_100"
|
|
*/
|
|
static generateCameraName(stream) {
|
|
try {
|
|
const urlObj = new URL(stream.url);
|
|
const ip = urlObj.hostname.replace(/\./g, '_').replace(/:/g, '_');
|
|
return `camera_${ip}`;
|
|
} catch (e) {
|
|
return 'camera';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate stream name for Go2RTC reference
|
|
* Format: "192_168_1_100_main" or "192_168_1_100_sub"
|
|
*/
|
|
static generateStreamName(stream, suffix) {
|
|
try {
|
|
const urlObj = new URL(stream.url);
|
|
const ip = urlObj.hostname.replace(/\./g, '_').replace(/:/g, '_');
|
|
return `${ip}_${suffix}`;
|
|
} catch (e) {
|
|
return `camera_stream_${suffix}`;
|
|
}
|
|
}
|
|
}
|