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)
if export.thumb_path:
Path(export.thumb_path).unlink(missing_ok=True)
export.delete_instance()
return make_response(
jsonify(
@ -152,4 +155,3 @@ def export_delete(id: str):
),
200,
)

View File

@ -56,6 +56,7 @@ from frigate.plus import PlusApi
from frigate.ptz.autotrack import PtzAutoTrackerThread
from frigate.ptz.onvif import OnvifController
from frigate.record.cleanup import RecordingCleanup
from frigate.record.export import migrate_exports
from frigate.record.record import manage_recordings
from frigate.review.review import manage_review_segments
from frigate.stats.emitter import StatsEmitter
@ -331,6 +332,17 @@ class FrigateApp:
]
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:
self.external_event_processor = ExternalEventProcessor(self.config)
@ -631,6 +643,7 @@ class FrigateApp:
self.init_review_segment_manager()
self.init_go2rtc()
self.bind_database()
self.check_db_data_migrations()
self.init_inter_process_communicator()
self.init_dispatcher()
except Exception as e:

View File

@ -249,3 +249,61 @@ class RecordingExporter(threading.Thread):
).execute()
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,
}: ExportProps) {
const [hovered, setHovered] = useState(false);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(
exportedRecording.thumb_path.length > 0,
);
// editing name
@ -129,7 +131,7 @@ export default function ExportCard({
</div>
<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"
onClick={() => {
onSelect(exportedRecording);
@ -142,11 +144,17 @@ export default function ExportCard({
{exportedRecording.in_progress ? (
<ActivityIndicator />
) : (
<>
{exportedRecording.thumb_path.length > 0 ? (
<img
className="absolute inset-0 object-contain aspect-video rounded-2xl"
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 && (
<Skeleton className="absolute inset-0 aspect-video rounded-2xl" />