diff --git a/docs/static/frigate-analytics-schema.json b/docs/static/frigate-analytics-schema.json index d861a44c08..fcbbf93e0b 100644 --- a/docs/static/frigate-analytics-schema.json +++ b/docs/static/frigate-analytics-schema.json @@ -683,6 +683,15 @@ "title": "FeaturesSection", "type": "object" }, + "FfmpegSource": { + "enum": [ + "default", + "bundled", + "custom" + ], + "title": "FfmpegSource", + "type": "string" + }, "FpsBucket": { "enum": [ "le_5", @@ -1017,6 +1026,17 @@ "description": "CPU architecture", "x-public": true }, + "custom_go2rtc": { + "description": "Whether an executable /config/go2rtc build exists", + "title": "Custom Go2Rtc", + "type": "boolean", + "x-public": true + }, + "ffmpeg": { + "$ref": "#/$defs/FfmpegSource", + "description": "Configured global ffmpeg.path: the default build, another bundled version, or a custom path", + "x-public": true + }, "image_variant": { "$ref": "#/$defs/ImageVariant", "description": "Published image the install runs", @@ -1054,7 +1074,9 @@ "install_type", "arch", "kernel", - "run_as_root" + "run_as_root", + "custom_go2rtc", + "ffmpeg" ], "title": "InstallSection", "type": "object" diff --git a/frigate/analytics/collectors/install.py b/frigate/analytics/collectors/install.py index 45be1324b5..6bd9af40f7 100644 --- a/frigate/analytics/collectors/install.py +++ b/frigate/analytics/collectors/install.py @@ -6,7 +6,15 @@ import re from frigate.analytics.collectors.common import closed from frigate.analytics.context import ReportContext -from frigate.analytics.schema import Arch, ImageVariant, InstallSection, InstallType +from frigate.analytics.schema import ( + Arch, + FfmpegSource, + ImageVariant, + InstallSection, + InstallType, +) +from frigate.const import CONFIG_DIR, DEFAULT_FFMPEG_VERSION, INCLUDED_FFMPEG_VERSIONS +from frigate.util.config import is_valid_ffmpeg_path from frigate.version import VERSION KERNEL_PATTERN = re.compile(r"^(\d{1,3})\.(\d{1,3})") @@ -54,6 +62,17 @@ def kernel(release: str) -> str: return f"{match.group(1)}.{match.group(2)}" if match else "unknown" +def ffmpeg_source(path: str) -> FfmpegSource: + """Classify the configured ffmpeg.path, applying resolve_ffmpeg_path's alias fallbacks.""" + if path.startswith("/") and is_valid_ffmpeg_path(path): + return FfmpegSource.custom + + if path != DEFAULT_FFMPEG_VERSION and path in INCLUDED_FFMPEG_VERSIONS: + return FfmpegSource.bundled + + return FfmpegSource.default + + def collect(ctx: ReportContext) -> InstallSection: """Describe the Frigate version, image, install method, and platform.""" return InstallSection( @@ -63,4 +82,6 @@ def collect(ctx: ReportContext) -> InstallSection: arch=arch(platform.machine()), kernel=kernel(platform.release()), run_as_root=os.geteuid() == 0, + custom_go2rtc=os.access(os.path.join(CONFIG_DIR, "go2rtc"), os.X_OK), + ffmpeg=ffmpeg_source(ctx.config.ffmpeg.path), ) diff --git a/frigate/analytics/schema.py b/frigate/analytics/schema.py index 6ad59c2dbe..51ce1cdb59 100644 --- a/frigate/analytics/schema.py +++ b/frigate/analytics/schema.py @@ -47,6 +47,12 @@ class Arch(StrEnum): other = "other" +class FfmpegSource(StrEnum): + default = "default" + bundled = "bundled" + custom = "custom" + + class GpuVendor(StrEnum): intel = "intel" amd = "amd" @@ -201,6 +207,10 @@ class InstallSection(AnalyticsModel): "Host kernel as major.minor", pattern=r"^(\d{1,3}\.\d{1,3}|unknown)$" ) run_as_root: bool = metric("Whether the main process runs as root") + custom_go2rtc: bool = metric("Whether an executable /config/go2rtc build exists") + ffmpeg: FfmpegSource = metric( + "Configured global ffmpeg.path: the default build, another bundled version, or a custom path" + ) class GpuInfo(AnalyticsModel): diff --git a/frigate/test/test_analytics_collectors_system.py b/frigate/test/test_analytics_collectors_system.py index c87b402e52..29b2b7d68b 100644 --- a/frigate/test/test_analytics_collectors_system.py +++ b/frigate/test/test_analytics_collectors_system.py @@ -12,6 +12,7 @@ from frigate.analytics.collectors.common import closed, histogram, rate from frigate.analytics.schema import ( Arch, DecodeFamily, + FfmpegSource, GpuVendor, HardwareKey, ImageVariant, @@ -56,6 +57,36 @@ class TestInstallCollector(unittest.TestCase): self.assertEqual(section.arch, Arch.aarch64) self.assertEqual(section.kernel, "6.8") self.assertFalse(section.run_as_root) + self.assertEqual(section.ffmpeg, FfmpegSource.default) + + def test_ffmpeg_source_follows_path_resolution(self): + with ( + patch.object(install, "DEFAULT_FFMPEG_VERSION", "8.0"), + patch.object(install, "INCLUDED_FFMPEG_VERSIONS", ["8.0", "7.0"]), + ): + self.assertEqual(install.ffmpeg_source("default"), FfmpegSource.default) + self.assertEqual(install.ffmpeg_source("8.0"), FfmpegSource.default) + self.assertEqual(install.ffmpeg_source("4.4"), FfmpegSource.default) + self.assertEqual(install.ffmpeg_source("7.0"), FfmpegSource.bundled) + self.assertEqual( + install.ffmpeg_source("/config/custom-ffmpeg"), FfmpegSource.custom + ) + self.assertEqual( + install.ffmpeg_source("/config/a b; rm"), FfmpegSource.default + ) + + def test_custom_go2rtc_needs_an_executable_binary(self): + with tempfile.TemporaryDirectory() as config_dir: + binary = os.path.join(config_dir, "go2rtc") + + with patch.object(install, "CONFIG_DIR", config_dir): + self.assertFalse(install.collect(make_context()).custom_go2rtc) + + open(binary, "w").close() + self.assertFalse(install.collect(make_context()).custom_go2rtc) + + os.chmod(binary, 0o755) + self.assertTrue(install.collect(make_context()).custom_go2rtc) def test_image_variant_is_dev_when_unset_and_other_when_unknown(self): self.assertEqual(install.image_variant(None), ImageVariant.dev) diff --git a/frigate/test/test_analytics_report.py b/frigate/test/test_analytics_report.py index 33a359dbef..905e42639e 100644 --- a/frigate/test/test_analytics_report.py +++ b/frigate/test/test_analytics_report.py @@ -25,6 +25,8 @@ INSTALL = InstallSection( arch="x86_64", kernel="6.8", run_as_root=False, + custom_go2rtc=False, + ffmpeg="default", )