mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 21:25:24 +03:00
Import existing exports to DB
This commit is contained in:
parent
e6f00c4b4f
commit
2e36f6d257
@ -141,7 +141,10 @@ def export_delete(id: str):
|
||||
)
|
||||
|
||||
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()
|
||||
return make_response(
|
||||
jsonify(
|
||||
@ -152,4 +155,3 @@ def export_delete(id: str):
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 />
|
||||
) : (
|
||||
<img
|
||||
className="absolute inset-0 object-contain aspect-video rounded-2xl"
|
||||
src={exportedRecording.thumb_path.replace("/media/frigate", "")}
|
||||
onLoad={() => setLoading(false)}
|
||||
/>
|
||||
<>
|
||||
{exportedRecording.thumb_path.length > 0 ? (
|
||||
<img
|
||||
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" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user