mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-12 01:57:36 +03:00
recording for GStreamer
This commit is contained in:
parent
d5dd82fa47
commit
1aa6ccaa6d
@ -12,7 +12,7 @@ import yaml
|
|||||||
from pydantic import BaseModel, Extra, Field, validator, root_validator
|
from pydantic import BaseModel, Extra, Field, validator, root_validator
|
||||||
from pydantic.fields import PrivateAttr
|
from pydantic.fields import PrivateAttr
|
||||||
|
|
||||||
from frigate.const import BASE_DIR, CACHE_DIR, YAML_EXT
|
from frigate.const import BASE_DIR, CACHE_DIR, YAML_EXT, RECORD_SEGMENT_TIME_SECONDS
|
||||||
from frigate.util import (
|
from frigate.util import (
|
||||||
create_mask,
|
create_mask,
|
||||||
deep_merge,
|
deep_merge,
|
||||||
@ -315,7 +315,7 @@ RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT = [
|
|||||||
"-f",
|
"-f",
|
||||||
"segment",
|
"segment",
|
||||||
"-segment_time",
|
"-segment_time",
|
||||||
"10",
|
str(RECORD_SEGMENT_TIME_SECONDS),
|
||||||
"-segment_format",
|
"-segment_format",
|
||||||
"mp4",
|
"mp4",
|
||||||
"-reset_timestamps",
|
"-reset_timestamps",
|
||||||
@ -616,7 +616,7 @@ class CameraConfig(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
builder = GstreamerBuilder(
|
builder = GstreamerBuilder(
|
||||||
gstreamer_input.path, self.detect.width, self.detect.height
|
gstreamer_input.path, self.detect.width, self.detect.height, self.name
|
||||||
)
|
)
|
||||||
if caps is None or len(caps) == 0:
|
if caps is None or len(caps) == 0:
|
||||||
logger.warn("gsreamer was not able to detect the input stream format")
|
logger.warn("gsreamer was not able to detect the input stream format")
|
||||||
|
|||||||
@ -3,3 +3,5 @@ CLIPS_DIR = f"{BASE_DIR}/clips"
|
|||||||
RECORD_DIR = f"{BASE_DIR}/recordings"
|
RECORD_DIR = f"{BASE_DIR}/recordings"
|
||||||
CACHE_DIR = "/tmp/cache"
|
CACHE_DIR = "/tmp/cache"
|
||||||
YAML_EXT = (".yaml", ".yml")
|
YAML_EXT = (".yaml", ".yml")
|
||||||
|
RECORD_SEGMENT_TIME_SECONDS = 10
|
||||||
|
GSTREAMER_RECORD_SUFFIX = "-gstsplitmuxchunk"
|
||||||
|
|||||||
@ -3,7 +3,11 @@ import logging
|
|||||||
import traceback
|
import traceback
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
from frigate.const import CACHE_DIR
|
from frigate.const import (
|
||||||
|
CACHE_DIR,
|
||||||
|
GSTREAMER_RECORD_SUFFIX,
|
||||||
|
RECORD_SEGMENT_TIME_SECONDS,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -62,6 +66,7 @@ def gst_inspect_find_codec(codec: str) -> List[str]:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def autodetect_decoder_pipeline(
|
def autodetect_decoder_pipeline(
|
||||||
codec: Optional[str],
|
codec: Optional[str],
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
@ -118,10 +123,11 @@ CODECS = {
|
|||||||
|
|
||||||
|
|
||||||
class GstreamerBuilder:
|
class GstreamerBuilder:
|
||||||
def __init__(self, uri, width, height, format="I420"):
|
def __init__(self, uri, width, height, name, format="I420"):
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.name = name
|
||||||
self.video_format = f"video/x-raw,width=(int){width},height=(int){height},format=(string){format}"
|
self.video_format = f"video/x-raw,width=(int){width},height=(int){height},format=(string){format}"
|
||||||
self.input_pipeline = [f'rtspsrc location="{uri}" latency=0']
|
self.input_pipeline = [f'rtspsrc location="{uri}" latency=0']
|
||||||
self.destination_format_pipeline = [self.video_format, "videoconvert"]
|
self.destination_format_pipeline = [self.video_format, "videoconvert"]
|
||||||
@ -141,7 +147,7 @@ class GstreamerBuilder:
|
|||||||
|
|
||||||
self.decoder_pipeline = autodetect_decoder_pipeline(codec)
|
self.decoder_pipeline = autodetect_decoder_pipeline(codec)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_source_format_pipeline(self, source_format_pipeline):
|
def with_source_format_pipeline(self, source_format_pipeline):
|
||||||
source_format_pipeline = (
|
source_format_pipeline = (
|
||||||
source_format_pipeline
|
source_format_pipeline
|
||||||
@ -176,7 +182,7 @@ class GstreamerBuilder:
|
|||||||
"queue2",
|
"queue2",
|
||||||
"x264enc key-int-max=10",
|
"x264enc key-int-max=10",
|
||||||
"h264parse",
|
"h264parse",
|
||||||
f"splitmuxsink async-handling=true location={os.path.join(CACHE_DIR, self.name)}-gst-%05d.mp4 max-size-time=10000000000",
|
f"splitmuxsink async-handling=true location={os.path.join(CACHE_DIR, self.name)}{GSTREAMER_RECORD_SUFFIX}-%05d.mp4 max-size-time={RECORD_SEGMENT_TIME_SECONDS*1000000000}",
|
||||||
]
|
]
|
||||||
if use_record
|
if use_record
|
||||||
else []
|
else []
|
||||||
@ -188,4 +194,3 @@ class GstreamerBuilder:
|
|||||||
]
|
]
|
||||||
pipeline_args = [item for sublist in pipeline_args for item in sublist]
|
pipeline_args = [item for sublist in pipeline_args for item in sublist]
|
||||||
return ["gst-launch-1.0", "-q", *pipeline_args][:-1]
|
return ["gst-launch-1.0", "-q", *pipeline_args][:-1]
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
from dateutil import tz
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
@ -17,7 +18,12 @@ import psutil
|
|||||||
from peewee import JOIN, DoesNotExist
|
from peewee import JOIN, DoesNotExist
|
||||||
|
|
||||||
from frigate.config import RetainModeEnum, FrigateConfig
|
from frigate.config import RetainModeEnum, FrigateConfig
|
||||||
from frigate.const import CACHE_DIR, RECORD_DIR
|
from frigate.const import (
|
||||||
|
CACHE_DIR,
|
||||||
|
RECORD_DIR,
|
||||||
|
GSTREAMER_RECORD_SUFFIX,
|
||||||
|
RECORD_SEGMENT_TIME_SECONDS,
|
||||||
|
)
|
||||||
from frigate.models import Event, Recordings
|
from frigate.models import Event, Recordings
|
||||||
from frigate.util import area
|
from frigate.util import area
|
||||||
|
|
||||||
@ -69,7 +75,7 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
files_in_use = []
|
files_in_use = []
|
||||||
for process in psutil.process_iter():
|
for process in psutil.process_iter():
|
||||||
try:
|
try:
|
||||||
if process.name() != "ffmpeg":
|
if process.name() not in ["ffmpeg", "gst-launch-1.0"]:
|
||||||
continue
|
continue
|
||||||
flist = process.open_files()
|
flist = process.open_files()
|
||||||
if flist:
|
if flist:
|
||||||
@ -89,7 +95,14 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
cache_path = os.path.join(CACHE_DIR, f)
|
cache_path = os.path.join(CACHE_DIR, f)
|
||||||
basename = os.path.splitext(f)[0]
|
basename = os.path.splitext(f)[0]
|
||||||
camera, date = basename.rsplit("-", maxsplit=1)
|
camera, date = basename.rsplit("-", maxsplit=1)
|
||||||
start_time = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
|
if camera.endswith(GSTREAMER_RECORD_SUFFIX):
|
||||||
|
camera = camera.split(GSTREAMER_RECORD_SUFFIX)[0]
|
||||||
|
creation_time = (
|
||||||
|
os.path.getmtime(cache_path) - RECORD_SEGMENT_TIME_SECONDS
|
||||||
|
)
|
||||||
|
start_time = datetime.datetime.utcfromtimestamp(creation_time)
|
||||||
|
else:
|
||||||
|
start_time = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
|
||||||
|
|
||||||
grouped_recordings[camera].append(
|
grouped_recordings[camera].append(
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user