Import existing exports to DB

This commit is contained in:
Nicolas Mowen 2024-04-19 10:06:08 -06:00
parent e6f00c4b4f
commit 2e36f6d257
4 changed files with 90 additions and 9 deletions

View File

@ -141,7 +141,10 @@ def export_delete(id: str):
) )
Path(export.video_path).unlink(missing_ok=True) Path(export.video_path).unlink(missing_ok=True)
Path(export.thumb_path).unlink(missing_ok=True)
if export.thumb_path:
Path(export.thumb_path).unlink(missing_ok=True)
export.delete_instance() export.delete_instance()
return make_response( return make_response(
jsonify( jsonify(
@ -152,4 +155,3 @@ def export_delete(id: str):
), ),
200, 200,
) )

View File

@ -56,6 +56,7 @@ from frigate.plus import PlusApi
from frigate.ptz.autotrack import PtzAutoTrackerThread from frigate.ptz.autotrack import PtzAutoTrackerThread
from frigate.ptz.onvif import OnvifController from frigate.ptz.onvif import OnvifController
from frigate.record.cleanup import RecordingCleanup from frigate.record.cleanup import RecordingCleanup
from frigate.record.export import migrate_exports
from frigate.record.record import manage_recordings from frigate.record.record import manage_recordings
from frigate.review.review import manage_review_segments from frigate.review.review import manage_review_segments
from frigate.stats.emitter import StatsEmitter from frigate.stats.emitter import StatsEmitter
@ -331,6 +332,17 @@ class FrigateApp:
] ]
self.db.bind(models) self.db.bind(models)
def check_db_data_migrations(self) -> None:
# check if vacuum needs to be run
if not os.path.exists(f"{CONFIG_DIR}/.exports"):
try:
with open(f"{CONFIG_DIR}/.exports", "w") as f:
f.write(str(datetime.datetime.now().timestamp()))
except PermissionError:
logger.error("Unable to write to /config to save export state")
migrate_exports(self.config.cameras.keys())
def init_external_event_processor(self) -> None: def init_external_event_processor(self) -> None:
self.external_event_processor = ExternalEventProcessor(self.config) self.external_event_processor = ExternalEventProcessor(self.config)
@ -631,6 +643,7 @@ class FrigateApp:
self.init_review_segment_manager() self.init_review_segment_manager()
self.init_go2rtc() self.init_go2rtc()
self.bind_database() self.bind_database()
self.check_db_data_migrations()
self.init_inter_process_communicator() self.init_inter_process_communicator()
self.init_dispatcher() self.init_dispatcher()
except Exception as e: except Exception as e:

View File

@ -249,3 +249,61 @@ class RecordingExporter(threading.Thread):
).execute() ).execute()
logger.debug(f"Finished exporting {video_path}") logger.debug(f"Finished exporting {video_path}")
def migrate_exports(camera_names: list[str]):
Path(os.path.join(CLIPS_DIR, "export")).mkdir(exist_ok=True)
exports = []
for export_file in os.listdir(EXPORT_DIR):
camera = "unknown"
for cam_name in camera_names:
if cam_name in export_file:
camera = cam_name
break
id = f"{camera}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
video_path = os.path.join(EXPORT_DIR, export_file)
thumb_path = os.path.join(
CLIPS_DIR, f"export/{id}.jpg"
) # use jpg because webp encoder can't get quality low enough
ffmpeg_cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel",
"warning",
"-i",
video_path,
"-vf",
"scale=-1:180",
"-frames",
"1",
"-q:v",
"8",
thumb_path,
]
process = sp.run(
ffmpeg_cmd,
capture_output=True,
)
if process.returncode != 0:
logger.error(process.stderr)
continue
exports.append(
{
Export.id: id,
Export.camera: camera,
Export.name: export_file.replace(".mp4", ""),
Export.date: os.path.getctime(video_path),
Export.video_path: video_path,
Export.thumb_path: thumb_path,
Export.in_progress: False,
}
)
Export.insert_many(exports).execute()

View File

@ -27,7 +27,9 @@ export default function ExportCard({
onDelete, onDelete,
}: ExportProps) { }: ExportProps) {
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(
exportedRecording.thumb_path.length > 0,
);
// editing name // editing name
@ -129,7 +131,7 @@ export default function ExportCard({
</div> </div>
<Button <Button
className="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 w-20 h-20 z-20 text-white hover:text-white hover:bg-transparent" className="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 w-20 h-20 z-20 text-white hover:text-white hover:bg-transparent cursor-pointer"
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
onSelect(exportedRecording); onSelect(exportedRecording);
@ -142,11 +144,17 @@ export default function ExportCard({
{exportedRecording.in_progress ? ( {exportedRecording.in_progress ? (
<ActivityIndicator /> <ActivityIndicator />
) : ( ) : (
<img <>
className="absolute inset-0 object-contain aspect-video rounded-2xl" {exportedRecording.thumb_path.length > 0 ? (
src={exportedRecording.thumb_path.replace("/media/frigate", "")} <img
onLoad={() => setLoading(false)} className="size-full absolute inset-0 object-contain aspect-video rounded-2xl"
/> src={exportedRecording.thumb_path.replace("/media/frigate", "")}
onLoad={() => setLoading(false)}
/>
) : (
<div className="absolute inset-0 bg-secondary rounded-2xl" />
)}
</>
)} )}
{loading && ( {loading && (
<Skeleton className="absolute inset-0 aspect-video rounded-2xl" /> <Skeleton className="absolute inset-0 aspect-video rounded-2xl" />