diff --git a/frigate/util/services.py b/frigate/util/services.py index f0bf2de1e..c20ab2765 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -807,10 +807,15 @@ async def get_video_properties( ) -> dict[str, Any]: async def probe_with_ffprobe( url: str, + rtsp_transport: Optional[str] = None, ) -> tuple[bool, int, int, Optional[str], float]: """Fallback using ffprobe: returns (valid, width, height, codec, duration).""" - cmd = [ - ffmpeg.ffprobe_path, + cmd = [ffmpeg.ffprobe_path] + if rtsp_transport: + cmd += ["-rtsp_transport", rtsp_transport] + cmd += [ + "-rw_timeout", + "5000000", "-v", "quiet", "-print_format", @@ -879,6 +884,12 @@ async def get_video_properties( if not has_video or (get_duration and duration < 0): has_video, width, height, fourcc, duration = await probe_with_ffprobe(url) + # last resort for RTSP: try TCP transport, since default UDP may be blocked + if (not has_video or (get_duration and duration < 0)) and url.startswith("rtsp://"): + has_video, width, height, fourcc, duration = await probe_with_ffprobe( + url, rtsp_transport="tcp" + ) + result: dict[str, Any] = {"has_valid_video": has_video} if has_video: result.update({"width": width, "height": height}) diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index a1e14452e..012023b37 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -415,6 +415,7 @@ "audioCodecGood": "Audio codec is {{codec}}.", "resolutionHigh": "A resolution of {{resolution}} may cause increased resource usage.", "resolutionLow": "A resolution of {{resolution}} may be too low for reliable detection of small objects.", + "resolutionUnknown": "The resolution of this stream could not be probed. This will cause issues on startup. You should manually set the detect resolution in Settings or your config.", "noAudioWarning": "No audio detected for this stream, recordings will not have audio.", "audioCodecRecordError": "The AAC audio codec is required to support audio in recordings.", "audioCodecRequired": "An audio stream is required to support audio detection.", diff --git a/web/src/components/settings/wizard/Step4Validation.tsx b/web/src/components/settings/wizard/Step4Validation.tsx index 8352a1c75..b739fe1ab 100644 --- a/web/src/components/settings/wizard/Step4Validation.tsx +++ b/web/src/components/settings/wizard/Step4Validation.tsx @@ -607,23 +607,38 @@ function StreamIssues({ } } - if (stream.roles.includes("detect") && stream.resolution) { - const [width, height] = stream.resolution.split("x").map(Number); - if (!isNaN(width) && !isNaN(height) && width > 0 && height > 0) { - const minDimension = Math.min(width, height); - const maxDimension = Math.max(width, height); + if (stream.roles.includes("detect") && stream.testResult) { + const probedResolution = stream.testResult.resolution; + let probedWidth = 0; + let probedHeight = 0; + if (probedResolution) { + const [w, h] = probedResolution.split("x").map(Number); + if (!isNaN(w) && !isNaN(h)) { + probedWidth = w; + probedHeight = h; + } + } + + if (probedWidth <= 0 || probedHeight <= 0) { + result.push({ + type: "error", + message: t("cameraWizard.step4.issues.resolutionUnknown"), + }); + } else { + const minDimension = Math.min(probedWidth, probedHeight); + const maxDimension = Math.max(probedWidth, probedHeight); if (minDimension > 1080) { result.push({ type: "warning", message: t("cameraWizard.step4.issues.resolutionHigh", { - resolution: stream.resolution, + resolution: probedResolution, }), }); } else if (maxDimension < 640) { result.push({ type: "error", message: t("cameraWizard.step4.issues.resolutionLow", { - resolution: stream.resolution, + resolution: probedResolution, }), }); }