diff --git a/frigate/app.py b/frigate/app.py index bcbd3ed2d..563f6f5ba 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -24,6 +24,7 @@ from frigate.const import ( CLIPS_DIR, CONFIG_DIR, DEFAULT_DB_PATH, + EXPORT_DIR, MODEL_CACHE_DIR, RECORD_DIR, ) @@ -68,7 +69,7 @@ class FrigateApp: os.environ[key] = value 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): logger.info(f"Creating directory: {d}") os.makedirs(d) diff --git a/frigate/http.py b/frigate/http.py index b94637976..6a768fae0 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -35,6 +35,7 @@ from frigate.models import Event, Recordings, Timeline from frigate.object_processing import TrackedObject from frigate.plus import PlusApi from frigate.ptz import OnvifController +from frigate.record.export import PlaybackFactorEnum, RecordingExporter from frigate.stats import stats_snapshot from frigate.storage import StorageMaintainer from frigate.util import ( @@ -1504,6 +1505,21 @@ def vod_event(id): ) +@bp.route("/export//start//end/") +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): while True: # max out at specified FPS diff --git a/frigate/record/export.py b/frigate/record/export.py index dcf5ac8f6..6713de55a 100644 --- a/frigate/record/export.py +++ b/frigate/record/export.py @@ -28,6 +28,7 @@ class RecordingExporter(threading.Thread): end_time: int, playback_factor: PlaybackFactorEnum, ) -> None: + threading.Thread.__init__(self) self.camera = camera self.start_time = start_time self.end_time = end_time @@ -35,21 +36,25 @@ class RecordingExporter(threading.Thread): def get_datetime_from_timestamp(self, timestamp: int) -> str: """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: logger.debug( 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" - 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" + 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" 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", + "-hide_banner", + "-y", + "-protocol_whitelist", + "pipe,file,http,tcp", "-i", - "/dev/stdin", + playlist_lines, ] else: playlist_lines = [] @@ -57,15 +62,16 @@ class RecordingExporter(threading.Thread): while playlist_start < self.end_time: 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 ffmpeg_cmd = [ "ffmpeg", + "-hide_banner", "-y", "-protocol_whitelist", - "pipe,file", + "pipe,file,http,tcp", "-f", "concat", "-safe", @@ -92,5 +98,6 @@ class RecordingExporter(threading.Thread): logger.error(p.stderr) return + logger.error(f"Updating finalized export {file_name}") os.rename(file_name, final_file_name) - logger.debug("Finished exporting") + logger.error(f"Finished exporting {file_name}")