mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-08 14:25:41 +03:00
refactor get_video_properties and use json output from ffprobe
This commit is contained in:
parent
2f209b2cf4
commit
6ba318b297
@ -603,87 +603,87 @@ def auto_detect_hwaccel() -> str:
|
|||||||
async def get_video_properties(
|
async def get_video_properties(
|
||||||
ffmpeg, url: str, get_duration: bool = False
|
ffmpeg, url: str, get_duration: bool = False
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
async def calculate_duration(video: Optional[Any]) -> float:
|
async def probe_with_ffprobe(
|
||||||
duration = None
|
url: str,
|
||||||
|
) -> tuple[bool, int, int, Optional[str], float]:
|
||||||
if video is not None:
|
"""Fallback using ffprobe: returns (valid, width, height, codec, duration)."""
|
||||||
# Get the frames per second (fps) of the video stream
|
cmd = [
|
||||||
fps = video.get(cv2.CAP_PROP_FPS)
|
ffmpeg.ffprobe_path,
|
||||||
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
"-v",
|
||||||
|
"quiet",
|
||||||
if fps and total_frames:
|
"-print_format",
|
||||||
duration = total_frames / fps
|
"json",
|
||||||
|
"-show_format",
|
||||||
# if cv2 failed need to use ffprobe
|
"-show_streams",
|
||||||
if duration is None:
|
url,
|
||||||
p = await asyncio.create_subprocess_exec(
|
]
|
||||||
ffmpeg.ffprobe_path,
|
try:
|
||||||
"-v",
|
proc = await asyncio.create_subprocess_exec(
|
||||||
"error",
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
"-show_entries",
|
|
||||||
"format=duration",
|
|
||||||
"-of",
|
|
||||||
"default=noprint_wrappers=1:nokey=1",
|
|
||||||
f"{url}",
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE,
|
|
||||||
)
|
)
|
||||||
await p.wait()
|
stdout, _ = await proc.communicate()
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return False, 0, 0, None, -1
|
||||||
|
|
||||||
if p.returncode == 0:
|
data = json.loads(stdout.decode())
|
||||||
result = (await p.stdout.read()).decode()
|
video_streams = [
|
||||||
else:
|
s for s in data.get("streams", []) if s.get("codec_type") == "video"
|
||||||
result = None
|
]
|
||||||
|
if not video_streams:
|
||||||
|
return False, 0, 0, None, -1
|
||||||
|
|
||||||
if result:
|
v = video_streams[0]
|
||||||
try:
|
width = int(v.get("width", 0))
|
||||||
duration = float(result.strip())
|
height = int(v.get("height", 0))
|
||||||
except ValueError:
|
codec = v.get("codec_name")
|
||||||
duration = -1
|
|
||||||
else:
|
|
||||||
duration = -1
|
|
||||||
|
|
||||||
return duration
|
duration_str = data.get("format", {}).get("duration")
|
||||||
|
duration = float(duration_str) if duration_str else -1.0
|
||||||
|
|
||||||
width = height = 0
|
return True, width, height, codec, duration
|
||||||
|
except (json.JSONDecodeError, ValueError, KeyError, asyncio.SubprocessError):
|
||||||
|
return False, 0, 0, None, -1
|
||||||
|
|
||||||
try:
|
def probe_with_cv2(url: str) -> tuple[bool, int, int, Optional[str], float]:
|
||||||
# Open the video stream using OpenCV
|
"""Primary attempt using cv2: returns (valid, width, height, fourcc, duration)."""
|
||||||
video = cv2.VideoCapture(url)
|
cap = cv2.VideoCapture(url)
|
||||||
|
if not cap.isOpened():
|
||||||
|
cap.release()
|
||||||
|
return False, 0, 0, None, -1
|
||||||
|
|
||||||
# Check if the video stream was opened successfully
|
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
if not video.isOpened():
|
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
video = None
|
valid = width > 0 and height > 0
|
||||||
except Exception:
|
fourcc = None
|
||||||
video = None
|
duration = -1.0
|
||||||
|
|
||||||
result = {}
|
if valid:
|
||||||
|
fourcc_int = int(cap.get(cv2.CAP_PROP_FOURCC))
|
||||||
|
fourcc = fourcc_int.to_bytes(4, "little").decode("latin-1").strip()
|
||||||
|
|
||||||
|
if get_duration:
|
||||||
|
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||||
|
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
|
if fps > 0 and total_frames > 0:
|
||||||
|
duration = total_frames / fps
|
||||||
|
|
||||||
|
cap.release()
|
||||||
|
return valid, width, height, fourcc, duration
|
||||||
|
|
||||||
|
# try cv2 first
|
||||||
|
has_video, width, height, fourcc, duration = probe_with_cv2(url)
|
||||||
|
|
||||||
|
# fallback to ffprobe if needed
|
||||||
|
if not has_video or (get_duration and duration < 0):
|
||||||
|
has_video, width, height, fourcc, duration = await probe_with_ffprobe(url)
|
||||||
|
|
||||||
|
result: dict[str, Any] = {"has_valid_video": has_video}
|
||||||
|
if has_video:
|
||||||
|
result.update({"width": width, "height": height})
|
||||||
|
if fourcc:
|
||||||
|
result["fourcc"] = fourcc
|
||||||
if get_duration:
|
if get_duration:
|
||||||
result["duration"] = await calculate_duration(video)
|
result["duration"] = duration
|
||||||
|
|
||||||
if video is not None:
|
|
||||||
# Get the width of frames in the video stream
|
|
||||||
width = video.get(cv2.CAP_PROP_FRAME_WIDTH)
|
|
||||||
|
|
||||||
# Get the height of frames in the video stream
|
|
||||||
height = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
|
||||||
|
|
||||||
# Get the stream encoding
|
|
||||||
fourcc_int = int(video.get(cv2.CAP_PROP_FOURCC))
|
|
||||||
fourcc = (
|
|
||||||
chr((fourcc_int >> 0) & 255)
|
|
||||||
+ chr((fourcc_int >> 8) & 255)
|
|
||||||
+ chr((fourcc_int >> 16) & 255)
|
|
||||||
+ chr((fourcc_int >> 24) & 255)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Release the video stream
|
|
||||||
video.release()
|
|
||||||
|
|
||||||
result["width"] = round(width)
|
|
||||||
result["height"] = round(height)
|
|
||||||
result["fourcc"] = fourcc
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user