From a325ed40dc0746a1c2bb0cf05d0d3e66126daf1e Mon Sep 17 00:00:00 2001 From: Nick Mowen Date: Mon, 24 Jul 2023 08:23:36 -0600 Subject: [PATCH] Run ffmpeg sub process & video_properties as async --- frigate/config.py | 3 ++- frigate/record/maintainer.py | 21 ++++++++------------- frigate/util/services.py | 24 ++++++++++++++++-------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/frigate/config.py b/frigate/config.py index 59e086989..e17aeea3a 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import json import logging import os @@ -1049,7 +1050,7 @@ class FrigateConfig(FrigateBaseModel): if "detect" in input.roles: stream_info = {"width": 0, "height": 0} try: - stream_info = get_video_properties(input.path) + stream_info = asyncio.run(get_video_properties(input.path)) except Exception: logger.warn( f"Error detecting stream resolution automatically for {input.path} Applying default values." diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 372e6a4fd..8f89c0c08 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -8,7 +8,6 @@ import os import queue import random import string -import subprocess as sp import threading from collections import defaultdict from multiprocessing.synchronize import Event as MpEvent @@ -183,7 +182,7 @@ class RecordingMaintainer(threading.Thread): if cache_path in self.end_time_cache: end_time, duration = self.end_time_cache[cache_path] else: - segment_info = get_video_properties(cache_path, get_duration=True) + segment_info = await get_video_properties(cache_path, get_duration=True) if segment_info["duration"]: duration = float(segment_info["duration"]) @@ -231,7 +230,7 @@ class RecordingMaintainer(threading.Thread): if overlaps: record_mode = self.config.cameras[camera].record.events.retain.mode # move from cache to recordings immediately - return self.move_segment( + return await self.move_segment( camera, start_time, end_time, @@ -253,7 +252,7 @@ class RecordingMaintainer(threading.Thread): # else retain days includes this segment else: record_mode = self.config.cameras[camera].record.retain.mode - return self.move_segment( + return await self.move_segment( camera, start_time, end_time, duration, cache_path, record_mode ) @@ -296,7 +295,7 @@ class RecordingMaintainer(threading.Thread): return SegmentInfo(motion_count, active_count, round(average_dBFS)) - def move_segment( + async def move_segment( self, camera: str, start_time: datetime.datetime, @@ -332,7 +331,7 @@ class RecordingMaintainer(threading.Thread): start_frame = datetime.datetime.now().timestamp() # add faststart to kept segments to improve metadata reading - ffmpeg_cmd = [ + p = await asyncio.create_subprocess_exec( "ffmpeg", "-hide_banner", "-y", @@ -343,17 +342,13 @@ class RecordingMaintainer(threading.Thread): "-movflags", "+faststart", file_path, - ] - - p = sp.run( - ffmpeg_cmd, - encoding="ascii", - capture_output=True, + stderr=asyncio.subprocess.PIPE, ) + await p.wait() if p.returncode != 0: logger.error(f"Unable to convert {cache_path} to {file_path}") - logger.error(p.stderr) + logger.error((await p.stderr.read()).decode("ascii")) return None else: logger.debug( diff --git a/frigate/util/services.py b/frigate/util/services.py index 507ee76ea..14344f312 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -1,5 +1,6 @@ """Utilities for services.""" +import asyncio import json import logging import os @@ -337,8 +338,8 @@ def vainfo_hwaccel(device_name: Optional[str] = None) -> sp.CompletedProcess: return sp.run(ffprobe_cmd, capture_output=True) -def get_video_properties(url, get_duration=False): - def calculate_duration(video: Optional[any]) -> float: +async def get_video_properties(url, get_duration=False): + async def calculate_duration(video: Optional[any]) -> float: duration = None if video is not None: @@ -351,7 +352,7 @@ def get_video_properties(url, get_duration=False): # if cv2 failed need to use ffprobe if duration is None: - ffprobe_cmd = [ + p = await asyncio.create_subprocess_exec( "ffprobe", "-v", "error", @@ -360,11 +361,18 @@ def get_video_properties(url, get_duration=False): "-of", "default=noprint_wrappers=1:nokey=1", f"{url}", - ] - p = sp.run(ffprobe_cmd, capture_output=True) + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + await p.wait() - if p.returncode == 0 and p.stdout.decode(): - duration = float(p.stdout.decode().strip()) + if p.returncode == 0: + result = (await p.stdout.read()).decode() + else: + result = None + + if result: + duration = float(result.strip()) else: duration = -1 @@ -385,7 +393,7 @@ def get_video_properties(url, get_duration=False): result = {} if get_duration: - result["duration"] = calculate_duration(video) + result["duration"] = await calculate_duration(video) if video is not None: # Get the width of frames in the video stream