mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
Save exports that are in progress
This commit is contained in:
parent
bfdcd3bb48
commit
b5d00efa5b
@ -80,9 +80,11 @@ class Recordings(Model): # type: ignore[misc]
|
|||||||
class Export(Model): # type: ignore[misc]
|
class Export(Model): # type: ignore[misc]
|
||||||
id = CharField(null=False, primary_key=True, max_length=30)
|
id = CharField(null=False, primary_key=True, max_length=30)
|
||||||
camera = CharField(index=True, max_length=20)
|
camera = CharField(index=True, max_length=20)
|
||||||
|
name = CharField(index=True, max_length=100)
|
||||||
date = DateTimeField()
|
date = DateTimeField()
|
||||||
video_path = CharField(unique=True)
|
video_path = CharField(unique=True)
|
||||||
thumb_path = CharField(unique=True)
|
thumb_path = CharField(unique=True)
|
||||||
|
in_progress = BooleanField()
|
||||||
|
|
||||||
|
|
||||||
class ReviewSegment(Model): # type: ignore[misc]
|
class ReviewSegment(Model): # type: ignore[misc]
|
||||||
|
|||||||
@ -65,12 +65,11 @@ 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_%H_%M")
|
return datetime.datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d %H:%M")
|
||||||
|
|
||||||
def save_thumbnail(self, id: str) -> str:
|
def save_thumbnail(self, id: str) -> str:
|
||||||
thumb_path = os.path.join(CLIPS_DIR, f"export/{id}.webp")
|
thumb_path = os.path.join(CLIPS_DIR, f"export/{id}.webp")
|
||||||
|
|
||||||
|
|
||||||
if datetime.datetime.fromtimestamp(
|
if datetime.datetime.fromtimestamp(
|
||||||
self.start_time
|
self.start_time
|
||||||
) < datetime.datetime.now().replace(minute=0, second=0):
|
) < datetime.datetime.now().replace(minute=0, second=0):
|
||||||
@ -157,12 +156,26 @@ class RecordingExporter(threading.Thread):
|
|||||||
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 = (
|
export_id = f"{self.camera}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
|
||||||
|
export_name = (
|
||||||
self.user_provided_name
|
self.user_provided_name
|
||||||
or f"{self.camera}_{self.get_datetime_from_timestamp(self.start_time)}__{self.get_datetime_from_timestamp(self.end_time)}"
|
or f"{self.camera} {self.get_datetime_from_timestamp(self.start_time)} {self.get_datetime_from_timestamp(self.end_time)}"
|
||||||
)
|
)
|
||||||
file_path = f"{EXPORT_DIR}/in_progress.{file_name}.mp4"
|
video_path = f"{EXPORT_DIR}/{export_id}.mp4"
|
||||||
final_file_path = f"{EXPORT_DIR}/{file_name}.mp4"
|
|
||||||
|
thumb_path = self.save_thumbnail(export_id)
|
||||||
|
|
||||||
|
Export.insert(
|
||||||
|
{
|
||||||
|
Export.id: export_id,
|
||||||
|
Export.camera: self.camera,
|
||||||
|
Export.name: export_name,
|
||||||
|
Export.date: self.start_time,
|
||||||
|
Export.video_path: video_path,
|
||||||
|
Export.thumb_path: thumb_path,
|
||||||
|
Export.in_progress: True,
|
||||||
|
}
|
||||||
|
).execute()
|
||||||
|
|
||||||
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/{self.camera}/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"
|
||||||
@ -201,14 +214,14 @@ class RecordingExporter(threading.Thread):
|
|||||||
|
|
||||||
if self.playback_factor == PlaybackFactorEnum.realtime:
|
if self.playback_factor == PlaybackFactorEnum.realtime:
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
f"ffmpeg -hide_banner {ffmpeg_input} -c copy -movflags +faststart {file_path}"
|
f"ffmpeg -hide_banner {ffmpeg_input} -c copy -movflags +faststart {video_path}"
|
||||||
).split(" ")
|
).split(" ")
|
||||||
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
parse_preset_hardware_acceleration_encode(
|
parse_preset_hardware_acceleration_encode(
|
||||||
self.config.ffmpeg.hwaccel_args,
|
self.config.ffmpeg.hwaccel_args,
|
||||||
f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}",
|
f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}",
|
||||||
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart {file_path}",
|
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart {video_path}",
|
||||||
EncodeTypeEnum.timelapse,
|
EncodeTypeEnum.timelapse,
|
||||||
)
|
)
|
||||||
).split(" ")
|
).split(" ")
|
||||||
@ -226,23 +239,13 @@ class RecordingExporter(threading.Thread):
|
|||||||
f"Failed to export recording for command {' '.join(ffmpeg_cmd)}"
|
f"Failed to export recording for command {' '.join(ffmpeg_cmd)}"
|
||||||
)
|
)
|
||||||
logger.error(p.stderr)
|
logger.error(p.stderr)
|
||||||
Path(file_path).unlink(missing_ok=True)
|
Path(video_path).unlink(missing_ok=True)
|
||||||
|
Export.delete().where(Export.id == export_id).execute()
|
||||||
|
Path(thumb_path).unlink(missing_ok=True)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
Export.update({Export.in_progress: False}).where(
|
||||||
|
Export.id == export_id
|
||||||
|
).execute()
|
||||||
|
|
||||||
logger.debug(f"Updating finalized export {file_path}")
|
logger.debug(f"Finished exporting {video_path}")
|
||||||
os.rename(file_path, final_file_path)
|
|
||||||
export_id = f"{self.camera}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
|
|
||||||
|
|
||||||
thumb_path = self.save_thumbnail(export_id)
|
|
||||||
|
|
||||||
Export.insert(
|
|
||||||
{
|
|
||||||
Export.id: export_id,
|
|
||||||
Export.camera: self.camera,
|
|
||||||
Export.date: self.start_time,
|
|
||||||
Export.video_path: final_file_path,
|
|
||||||
Export.thumb_path: thumb_path,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"Finished exporting {file_path}")
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ SQL = pw.SQL
|
|||||||
|
|
||||||
def migrate(migrator, database, fake=False, **kwargs):
|
def migrate(migrator, database, fake=False, **kwargs):
|
||||||
migrator.sql(
|
migrator.sql(
|
||||||
'CREATE TABLE IF NOT EXISTS "export" ("id" VARCHAR(30) NOT NULL PRIMARY KEY, "camera" VARCHAR(20) NOT NULL, "date" DATETIME NOT NULL, "video_path" VARCHAR(255) NOT NULL, "thumb_path" VARCHAR(255) NOT NULL)'
|
'CREATE TABLE IF NOT EXISTS "export" ("id" VARCHAR(30) NOT NULL PRIMARY KEY, "camera" VARCHAR(20) NOT NULL, "name" VARCHAR(100) NOT NULL, "date" DATETIME NOT NULL, "video_path" VARCHAR(255) NOT NULL, "thumb_path" VARCHAR(255) NOT NULL, "in_progress" INTEGER NOT NULL)'
|
||||||
)
|
)
|
||||||
migrator.sql(
|
migrator.sql(
|
||||||
'CREATE INDEX IF NOT EXISTS "export_camera" ON "export" ("camera")'
|
'CREATE INDEX IF NOT EXISTS "export_camera" ON "export" ("camera")'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user