mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 05:35:25 +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.video_path).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
if export.thumb_path:
|
||||||
Path(export.thumb_path).unlink(missing_ok=True)
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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 />
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
{exportedRecording.thumb_path.length > 0 ? (
|
||||||
<img
|
<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", "")}
|
src={exportedRecording.thumb_path.replace("/media/frigate", "")}
|
||||||
onLoad={() => setLoading(false)}
|
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" />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user