mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-05 18:55:23 +03:00
Add http endpoint
This commit is contained in:
parent
63d95fa1e2
commit
3649dd3d59
@ -24,6 +24,7 @@ from frigate.const import (
|
|||||||
CLIPS_DIR,
|
CLIPS_DIR,
|
||||||
CONFIG_DIR,
|
CONFIG_DIR,
|
||||||
DEFAULT_DB_PATH,
|
DEFAULT_DB_PATH,
|
||||||
|
EXPORT_DIR,
|
||||||
MODEL_CACHE_DIR,
|
MODEL_CACHE_DIR,
|
||||||
RECORD_DIR,
|
RECORD_DIR,
|
||||||
)
|
)
|
||||||
@ -68,7 +69,7 @@ class FrigateApp:
|
|||||||
os.environ[key] = value
|
os.environ[key] = value
|
||||||
|
|
||||||
def ensure_dirs(self) -> None:
|
def ensure_dirs(self) -> None:
|
||||||
for d in [CONFIG_DIR, RECORD_DIR, CLIPS_DIR, CACHE_DIR, MODEL_CACHE_DIR]:
|
for d in [CONFIG_DIR, RECORD_DIR, CLIPS_DIR, CACHE_DIR, MODEL_CACHE_DIR, EXPORT_DIR]:
|
||||||
if not os.path.exists(d) and not os.path.islink(d):
|
if not os.path.exists(d) and not os.path.islink(d):
|
||||||
logger.info(f"Creating directory: {d}")
|
logger.info(f"Creating directory: {d}")
|
||||||
os.makedirs(d)
|
os.makedirs(d)
|
||||||
|
|||||||
@ -35,6 +35,7 @@ from frigate.models import Event, Recordings, Timeline
|
|||||||
from frigate.object_processing import TrackedObject
|
from frigate.object_processing import TrackedObject
|
||||||
from frigate.plus import PlusApi
|
from frigate.plus import PlusApi
|
||||||
from frigate.ptz import OnvifController
|
from frigate.ptz import OnvifController
|
||||||
|
from frigate.record.export import PlaybackFactorEnum, RecordingExporter
|
||||||
from frigate.stats import stats_snapshot
|
from frigate.stats import stats_snapshot
|
||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
from frigate.util import (
|
from frigate.util import (
|
||||||
@ -1504,6 +1505,21 @@ def vod_event(id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/export/<camera_name>/start/<start_time>/end/<end_time>")
|
||||||
|
def export_recording(camera_name: str, start_time: int, end_time: int):
|
||||||
|
playback_factor = request.args.get("playback", type=str, default="realtime")
|
||||||
|
exporter = RecordingExporter(
|
||||||
|
camera_name,
|
||||||
|
int(start_time),
|
||||||
|
int(end_time),
|
||||||
|
PlaybackFactorEnum[playback_factor]
|
||||||
|
if playback_factor in PlaybackFactorEnum.__members__.values()
|
||||||
|
else PlaybackFactorEnum.real_time,
|
||||||
|
)
|
||||||
|
exporter.start()
|
||||||
|
return "Starting export of recording", 200
|
||||||
|
|
||||||
|
|
||||||
def imagestream(detected_frames_processor, camera_name, fps, height, draw_options):
|
def imagestream(detected_frames_processor, camera_name, fps, height, draw_options):
|
||||||
while True:
|
while True:
|
||||||
# max out at specified FPS
|
# max out at specified FPS
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
end_time: int,
|
end_time: int,
|
||||||
playback_factor: PlaybackFactorEnum,
|
playback_factor: PlaybackFactorEnum,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
threading.Thread.__init__(self)
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
self.end_time = end_time
|
self.end_time = end_time
|
||||||
@ -35,21 +36,25 @@ class RecordingExporter(threading.Thread):
|
|||||||
|
|
||||||
def get_datetime_from_timestamp(self, timestamp: int) -> str:
|
def get_datetime_from_timestamp(self, timestamp: int) -> str:
|
||||||
"""Convenience fun to get a simple date time from timestamp."""
|
"""Convenience fun to get a simple date time from timestamp."""
|
||||||
return datetime.datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d %I:%M")
|
return datetime.datetime.fromtimestamp(timestamp).strftime("%Y_%m_%d_%I:%M")
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Beginning export for {self.camera} from {self.start_time} to {self.end_time}"
|
f"Beginning export for {self.camera} from {self.start_time} to {self.end_time}"
|
||||||
)
|
)
|
||||||
file_name = f"{EXPORT_DIR}/in_progress.{self.camera}_{self.get_datetime_from_timestamp(self.start_time)}-{self.get_datetime_from_timestamp(self.end_time)}.mp4"
|
file_name = f"{EXPORT_DIR}/in_progress.{self.camera}@{self.get_datetime_from_timestamp(self.start_time)}__{self.get_datetime_from_timestamp(self.end_time)}.mp4"
|
||||||
final_file_name = f"{EXPORT_DIR}/{self.camera}_{self.get_datetime_from_timestamp(self.start_time)}-{self.get_datetime_from_timestamp(self.end_time)}.mp4"
|
final_file_name = f"{EXPORT_DIR}/{self.camera}_{self.get_datetime_from_timestamp(self.start_time)}__{self.get_datetime_from_timestamp(self.end_time)}.mp4"
|
||||||
|
|
||||||
if (self.end_time - self.start_time) <= MAX_PLAYLIST_SECONDS:
|
if (self.end_time - self.start_time) <= MAX_PLAYLIST_SECONDS:
|
||||||
playlist_lines = f"http://127.0.0.1:5000/vod/start/{self.start_time}/end/{self.end_time}/index.m3u8"
|
playlist_lines = f"http://127.0.0.1:5000/vod/{self.camera}/start/{self.start_time}/end/{self.end_time}/index.m3u8"
|
||||||
ffmpeg_cmd = [
|
ffmpeg_cmd = [
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
|
"-hide_banner",
|
||||||
|
"-y",
|
||||||
|
"-protocol_whitelist",
|
||||||
|
"pipe,file,http,tcp",
|
||||||
"-i",
|
"-i",
|
||||||
"/dev/stdin",
|
playlist_lines,
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
playlist_lines = []
|
playlist_lines = []
|
||||||
@ -57,15 +62,16 @@ class RecordingExporter(threading.Thread):
|
|||||||
|
|
||||||
while playlist_start < self.end_time:
|
while playlist_start < self.end_time:
|
||||||
playlist_lines.append(
|
playlist_lines.append(
|
||||||
f"file http://127.0.0.1:5000/vod/start/{playlist_start}/end/{min(playlist_start + MAX_PLAYLIST_SECONDS, self.end_time)}/index.m3u8"
|
f"file http://127.0.0.1:5000/vod/{self.camera}/start/{playlist_start}/end/{min(playlist_start + MAX_PLAYLIST_SECONDS, self.end_time)}/index.m3u8"
|
||||||
)
|
)
|
||||||
playlist_start += MAX_PLAYLIST_SECONDS
|
playlist_start += MAX_PLAYLIST_SECONDS
|
||||||
|
|
||||||
ffmpeg_cmd = [
|
ffmpeg_cmd = [
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
|
"-hide_banner",
|
||||||
"-y",
|
"-y",
|
||||||
"-protocol_whitelist",
|
"-protocol_whitelist",
|
||||||
"pipe,file",
|
"pipe,file,http,tcp",
|
||||||
"-f",
|
"-f",
|
||||||
"concat",
|
"concat",
|
||||||
"-safe",
|
"-safe",
|
||||||
@ -92,5 +98,6 @@ class RecordingExporter(threading.Thread):
|
|||||||
logger.error(p.stderr)
|
logger.error(p.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.error(f"Updating finalized export {file_name}")
|
||||||
os.rename(file_name, final_file_name)
|
os.rename(file_name, final_file_name)
|
||||||
logger.debug("Finished exporting")
|
logger.error(f"Finished exporting {file_name}")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user