Run ffmpeg sub process & video_properties as async

This commit is contained in:
Nick Mowen 2023-07-24 08:23:36 -06:00
parent 680198148b
commit a325ed40dc
3 changed files with 26 additions and 22 deletions

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import json import json
import logging import logging
import os import os
@ -1049,7 +1050,7 @@ class FrigateConfig(FrigateBaseModel):
if "detect" in input.roles: if "detect" in input.roles:
stream_info = {"width": 0, "height": 0} stream_info = {"width": 0, "height": 0}
try: try:
stream_info = get_video_properties(input.path) stream_info = asyncio.run(get_video_properties(input.path))
except Exception: except Exception:
logger.warn( logger.warn(
f"Error detecting stream resolution automatically for {input.path} Applying default values." f"Error detecting stream resolution automatically for {input.path} Applying default values."

View File

@ -8,7 +8,6 @@ import os
import queue import queue
import random import random
import string import string
import subprocess as sp
import threading import threading
from collections import defaultdict from collections import defaultdict
from multiprocessing.synchronize import Event as MpEvent from multiprocessing.synchronize import Event as MpEvent
@ -183,7 +182,7 @@ class RecordingMaintainer(threading.Thread):
if cache_path in self.end_time_cache: if cache_path in self.end_time_cache:
end_time, duration = self.end_time_cache[cache_path] end_time, duration = self.end_time_cache[cache_path]
else: 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"]: if segment_info["duration"]:
duration = float(segment_info["duration"]) duration = float(segment_info["duration"])
@ -231,7 +230,7 @@ class RecordingMaintainer(threading.Thread):
if overlaps: if overlaps:
record_mode = self.config.cameras[camera].record.events.retain.mode record_mode = self.config.cameras[camera].record.events.retain.mode
# move from cache to recordings immediately # move from cache to recordings immediately
return self.move_segment( return await self.move_segment(
camera, camera,
start_time, start_time,
end_time, end_time,
@ -253,7 +252,7 @@ class RecordingMaintainer(threading.Thread):
# else retain days includes this segment # else retain days includes this segment
else: else:
record_mode = self.config.cameras[camera].record.retain.mode 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 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)) return SegmentInfo(motion_count, active_count, round(average_dBFS))
def move_segment( async def move_segment(
self, self,
camera: str, camera: str,
start_time: datetime.datetime, start_time: datetime.datetime,
@ -332,7 +331,7 @@ class RecordingMaintainer(threading.Thread):
start_frame = datetime.datetime.now().timestamp() start_frame = datetime.datetime.now().timestamp()
# add faststart to kept segments to improve metadata reading # add faststart to kept segments to improve metadata reading
ffmpeg_cmd = [ p = await asyncio.create_subprocess_exec(
"ffmpeg", "ffmpeg",
"-hide_banner", "-hide_banner",
"-y", "-y",
@ -343,17 +342,13 @@ class RecordingMaintainer(threading.Thread):
"-movflags", "-movflags",
"+faststart", "+faststart",
file_path, file_path,
] stderr=asyncio.subprocess.PIPE,
p = sp.run(
ffmpeg_cmd,
encoding="ascii",
capture_output=True,
) )
await p.wait()
if p.returncode != 0: if p.returncode != 0:
logger.error(f"Unable to convert {cache_path} to {file_path}") 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 return None
else: else:
logger.debug( logger.debug(

View File

@ -1,5 +1,6 @@
"""Utilities for services.""" """Utilities for services."""
import asyncio
import json import json
import logging import logging
import os import os
@ -337,8 +338,8 @@ def vainfo_hwaccel(device_name: Optional[str] = None) -> sp.CompletedProcess:
return sp.run(ffprobe_cmd, capture_output=True) return sp.run(ffprobe_cmd, capture_output=True)
def get_video_properties(url, get_duration=False): async def get_video_properties(url, get_duration=False):
def calculate_duration(video: Optional[any]) -> float: async def calculate_duration(video: Optional[any]) -> float:
duration = None duration = None
if video is not 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 cv2 failed need to use ffprobe
if duration is None: if duration is None:
ffprobe_cmd = [ p = await asyncio.create_subprocess_exec(
"ffprobe", "ffprobe",
"-v", "-v",
"error", "error",
@ -360,11 +361,18 @@ def get_video_properties(url, get_duration=False):
"-of", "-of",
"default=noprint_wrappers=1:nokey=1", "default=noprint_wrappers=1:nokey=1",
f"{url}", f"{url}",
] stdout=asyncio.subprocess.PIPE,
p = sp.run(ffprobe_cmd, capture_output=True) stderr=asyncio.subprocess.PIPE,
)
await p.wait()
if p.returncode == 0 and p.stdout.decode(): if p.returncode == 0:
duration = float(p.stdout.decode().strip()) result = (await p.stdout.read()).decode()
else:
result = None
if result:
duration = float(result.strip())
else: else:
duration = -1 duration = -1
@ -385,7 +393,7 @@ def get_video_properties(url, get_duration=False):
result = {} result = {}
if get_duration: if get_duration:
result["duration"] = calculate_duration(video) result["duration"] = await calculate_duration(video)
if video is not None: if video is not None:
# Get the width of frames in the video stream # Get the width of frames in the video stream