mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			217 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Export apis."""
 | |
| 
 | |
| import logging
 | |
| import random
 | |
| import string
 | |
| from pathlib import Path
 | |
| from typing import Optional
 | |
| 
 | |
| import psutil
 | |
| from fastapi import APIRouter, Request
 | |
| from fastapi.responses import JSONResponse
 | |
| from peewee import DoesNotExist
 | |
| 
 | |
| from frigate.api.defs.tags import Tags
 | |
| from frigate.const import EXPORT_DIR
 | |
| from frigate.models import Export, Previews, Recordings
 | |
| from frigate.record.export import (
 | |
|     PlaybackFactorEnum,
 | |
|     PlaybackSourceEnum,
 | |
|     RecordingExporter,
 | |
| )
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| router = APIRouter(tags=[Tags.export])
 | |
| 
 | |
| 
 | |
| @router.get("/exports")
 | |
| def get_exports():
 | |
|     exports = Export.select().order_by(Export.date.desc()).dicts().iterator()
 | |
|     return JSONResponse(content=[e for e in exports])
 | |
| 
 | |
| 
 | |
| @router.post("/export/{camera_name}/start/{start_time}/end/{end_time}")
 | |
| def export_recording(
 | |
|     request: Request,
 | |
|     camera_name: str,
 | |
|     start_time: float,
 | |
|     end_time: float,
 | |
|     body: dict = None,
 | |
| ):
 | |
|     if not camera_name or not request.app.frigate_config.cameras.get(camera_name):
 | |
|         return JSONResponse(
 | |
|             content=(
 | |
|                 {"success": False, "message": f"{camera_name} is not a valid camera."}
 | |
|             ),
 | |
|             status_code=404,
 | |
|         )
 | |
| 
 | |
|     json: dict[str, any] = body or {}
 | |
|     playback_factor = json.get("playback", "realtime")
 | |
|     playback_source = json.get("source", "recordings")
 | |
|     friendly_name: Optional[str] = json.get("name")
 | |
| 
 | |
|     if len(friendly_name or "") > 256:
 | |
|         return JSONResponse(
 | |
|             content=({"success": False, "message": "File name is too long."}),
 | |
|             status_code=401,
 | |
|         )
 | |
| 
 | |
|     existing_image = json.get("image_path")
 | |
| 
 | |
|     if playback_source == "recordings":
 | |
|         recordings_count = (
 | |
|             Recordings.select()
 | |
|             .where(
 | |
|                 Recordings.start_time.between(start_time, end_time)
 | |
|                 | Recordings.end_time.between(start_time, end_time)
 | |
|                 | (
 | |
|                     (start_time > Recordings.start_time)
 | |
|                     & (end_time < Recordings.end_time)
 | |
|                 )
 | |
|             )
 | |
|             .where(Recordings.camera == camera_name)
 | |
|             .count()
 | |
|         )
 | |
| 
 | |
|         if recordings_count <= 0:
 | |
|             return JSONResponse(
 | |
|                 content=(
 | |
|                     {"success": False, "message": "No recordings found for time range"}
 | |
|                 ),
 | |
|                 status_code=400,
 | |
|             )
 | |
|     else:
 | |
|         previews_count = (
 | |
|             Previews.select()
 | |
|             .where(
 | |
|                 Previews.start_time.between(start_time, end_time)
 | |
|                 | Previews.end_time.between(start_time, end_time)
 | |
|                 | ((start_time > Previews.start_time) & (end_time < Previews.end_time))
 | |
|             )
 | |
|             .where(Previews.camera == camera_name)
 | |
|             .count()
 | |
|         )
 | |
| 
 | |
|         if previews_count <= 0:
 | |
|             return JSONResponse(
 | |
|                 content=(
 | |
|                     {"success": False, "message": "No previews found for time range"}
 | |
|                 ),
 | |
|                 status_code=400,
 | |
|             )
 | |
| 
 | |
|     export_id = f"{camera_name}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
 | |
|     exporter = RecordingExporter(
 | |
|         request.app.frigate_config,
 | |
|         export_id,
 | |
|         camera_name,
 | |
|         friendly_name,
 | |
|         existing_image,
 | |
|         int(start_time),
 | |
|         int(end_time),
 | |
|         (
 | |
|             PlaybackFactorEnum[playback_factor]
 | |
|             if playback_factor in PlaybackFactorEnum.__members__.values()
 | |
|             else PlaybackFactorEnum.realtime
 | |
|         ),
 | |
|         (
 | |
|             PlaybackSourceEnum[playback_source]
 | |
|             if playback_source in PlaybackSourceEnum.__members__.values()
 | |
|             else PlaybackSourceEnum.recordings
 | |
|         ),
 | |
|     )
 | |
|     exporter.start()
 | |
|     return JSONResponse(
 | |
|         content=(
 | |
|             {
 | |
|                 "success": True,
 | |
|                 "message": "Starting export of recording.",
 | |
|                 "export_id": export_id,
 | |
|             }
 | |
|         ),
 | |
|         status_code=200,
 | |
|     )
 | |
| 
 | |
| 
 | |
| @router.patch("/export/{event_id}/{new_name}")
 | |
| def export_rename(event_id: str, new_name: str):
 | |
|     try:
 | |
|         export: Export = Export.get(Export.id == event_id)
 | |
|     except DoesNotExist:
 | |
|         return JSONResponse(
 | |
|             content=(
 | |
|                 {
 | |
|                     "success": False,
 | |
|                     "message": "Export not found.",
 | |
|                 }
 | |
|             ),
 | |
|             status_code=404,
 | |
|         )
 | |
| 
 | |
|     export.name = new_name
 | |
|     export.save()
 | |
|     return JSONResponse(
 | |
|         content=(
 | |
|             {
 | |
|                 "success": True,
 | |
|                 "message": "Successfully renamed export.",
 | |
|             }
 | |
|         ),
 | |
|         status_code=200,
 | |
|     )
 | |
| 
 | |
| 
 | |
| @router.delete("/export/{event_id}")
 | |
| def export_delete(event_id: str):
 | |
|     try:
 | |
|         export: Export = Export.get(Export.id == event_id)
 | |
|     except DoesNotExist:
 | |
|         return JSONResponse(
 | |
|             content=(
 | |
|                 {
 | |
|                     "success": False,
 | |
|                     "message": "Export not found.",
 | |
|                 }
 | |
|             ),
 | |
|             status_code=404,
 | |
|         )
 | |
| 
 | |
|     files_in_use = []
 | |
|     for process in psutil.process_iter():
 | |
|         try:
 | |
|             if process.name() != "ffmpeg":
 | |
|                 continue
 | |
|             file_list = process.open_files()
 | |
|             if file_list:
 | |
|                 for nt in file_list:
 | |
|                     if nt.path.startswith(EXPORT_DIR):
 | |
|                         files_in_use.append(nt.path.split("/")[-1])
 | |
|         except psutil.Error:
 | |
|             continue
 | |
| 
 | |
|     if export.video_path.split("/")[-1] in files_in_use:
 | |
|         return JSONResponse(
 | |
|             content=(
 | |
|                 {"success": False, "message": "Can not delete in progress export."}
 | |
|             ),
 | |
|             status_code=400,
 | |
|         )
 | |
| 
 | |
|     Path(export.video_path).unlink(missing_ok=True)
 | |
| 
 | |
|     if export.thumb_path:
 | |
|         Path(export.thumb_path).unlink(missing_ok=True)
 | |
| 
 | |
|     export.delete_instance()
 | |
|     return JSONResponse(
 | |
|         content=(
 | |
|             {
 | |
|                 "success": True,
 | |
|                 "message": "Successfully deleted export.",
 | |
|             }
 | |
|         ),
 | |
|         status_code=200,
 | |
|     )
 |