Handle in progress previews export and fix time check bug (#14930)

* Handle in progress previews and fix time check bug

* Formatting
This commit is contained in:
Nicolas Mowen 2024-11-11 08:30:55 -07:00 committed by GitHub
parent 6c86827d3a
commit 9c20cd5f7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 2 deletions

View File

@ -19,6 +19,7 @@ from frigate.record.export import (
PlaybackSourceEnum, PlaybackSourceEnum,
RecordingExporter, RecordingExporter,
) )
from frigate.util.builtin import is_current_hour
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -86,7 +87,7 @@ def export_recording(
.count() .count()
) )
if previews_count <= 0: if not is_current_hour(start_time) and previews_count <= 0:
return JSONResponse( return JSONResponse(
content=( content=(
{"success": False, "message": "No previews found for time range"} {"success": False, "message": "No previews found for time range"}

View File

@ -144,6 +144,9 @@ def output_frames(
# check for any cameras that are currently offline # check for any cameras that are currently offline
# and need to generate a preview # and need to generate a preview
if generated_preview: if generated_preview:
logger.debug(
"Checking for offline cameras because another camera generated a preview."
)
for camera, time in preview_write_times.copy().items(): for camera, time in preview_write_times.copy().items():
if time != 0 and frame_time - time > 10: if time != 0 and frame_time - time > 10:
preview_recorders[camera].flag_offline(frame_time) preview_recorders[camera].flag_offline(frame_time)

View File

@ -27,6 +27,7 @@ from frigate.ffmpeg_presets import (
parse_preset_hardware_acceleration_encode, parse_preset_hardware_acceleration_encode,
) )
from frigate.models import Export, Previews, Recordings from frigate.models import Export, Previews, Recordings
from frigate.util.builtin import is_current_hour
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -235,6 +236,32 @@ class RecordingExporter(threading.Thread):
def get_preview_export_command(self, video_path: str) -> list[str]: def get_preview_export_command(self, video_path: str) -> list[str]:
playlist_lines = [] playlist_lines = []
codec = "-c copy"
if is_current_hour(self.start_time):
# get list of current preview frames
preview_dir = os.path.join(CACHE_DIR, "preview_frames")
file_start = f"preview_{self.camera}"
start_file = f"{file_start}-{self.start_time}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}-{self.end_time}.{PREVIEW_FRAME_TYPE}"
for file in sorted(os.listdir(preview_dir)):
if not file.startswith(file_start):
continue
if file < start_file:
continue
if file > end_file:
break
playlist_lines.append(f"file '{os.path.join(preview_dir, file)}'")
playlist_lines.append("duration 0.12")
if playlist_lines:
last_file = playlist_lines[-2]
playlist_lines.append(last_file)
codec = "-c:v libx264"
# get full set of previews # get full set of previews
export_previews = ( export_previews = (
@ -277,7 +304,7 @@ class RecordingExporter(threading.Thread):
if self.playback_factor == PlaybackFactorEnum.realtime: if self.playback_factor == PlaybackFactorEnum.realtime:
ffmpeg_cmd = ( ffmpeg_cmd = (
f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} -c copy -movflags +faststart {video_path}" f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} {codec} -movflags +faststart {video_path}"
).split(" ") ).split(" ")
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x: elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
ffmpeg_cmd = ( ffmpeg_cmd = (

View File

@ -282,6 +282,17 @@ def get_tomorrow_at_time(hour: int) -> datetime.datetime:
) )
def is_current_hour(timestamp: int) -> bool:
"""Returns if timestamp is in the current UTC hour."""
start_of_next_hour = (
datetime.datetime.now(datetime.timezone.utc).replace(
minute=0, second=0, microsecond=0
)
+ datetime.timedelta(hours=1)
).timestamp()
return timestamp < start_of_next_hour
def clear_and_unlink(file: Path, missing_ok: bool = True) -> None: def clear_and_unlink(file: Path, missing_ok: bool = True) -> None:
"""clear file then unlink to avoid space retained by file descriptors.""" """clear file then unlink to avoid space retained by file descriptors."""
if not missing_ok and not file.exists(): if not missing_ok and not file.exists():