mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	FEAT: Replace best jpg endpoint (#2944)
* Added object thumbnail def and made camera tracked objects use it. * Add object snapshot def * Remove documentation for best.jpg * Update docs for label thumbnail and snapshot defs
This commit is contained in:
		
							parent
							
								
									dccfc3b84f
								
							
						
					
					
						commit
						0abd0627df
					
				| @ -24,16 +24,6 @@ Accepts the following query string parameters: | ||||
| 
 | ||||
| You can access a higher resolution mjpeg stream by appending `h=height-in-pixels` to the endpoint. For example `http://localhost:5000/api/back?h=1080`. You can also increase the FPS by appending `fps=frame-rate` to the URL such as `http://localhost:5000/api/back?fps=10` or both with `?fps=10&h=1000`. | ||||
| 
 | ||||
| ### `GET /api/<camera_name>/<object_name>/best.jpg[?h=300&crop=1&quality=70]` | ||||
| 
 | ||||
| The best snapshot for any object type. It is a full resolution image by default. | ||||
| 
 | ||||
| Example parameters: | ||||
| 
 | ||||
| - `h=300`: resizes the image to 300 pixes tall | ||||
| - `crop=1`: crops the image to the region of the detection rather than returning the entire image | ||||
| - `quality=70`: sets the jpeg encoding quality (0-100) | ||||
| 
 | ||||
| ### `GET /api/<camera_name>/latest.jpg[?h=300]` | ||||
| 
 | ||||
| The most recent frame that frigate has finished processing. It is a full resolution image by default. | ||||
| @ -200,6 +190,10 @@ Sets retain to false for the event id (event may be deleted quickly after removi | ||||
| 
 | ||||
| Returns a thumbnail for the event id optimized for notifications. Works while the event is in progress and after completion. Passing `?format=android` will convert the thumbnail to 2:1 aspect ratio. | ||||
| 
 | ||||
| ### `GET /api/<camera_name>/<label>/thumbnail.jpg` | ||||
| 
 | ||||
| Returns the thumbnail from the latest event for the given camera and label combo. Using `any` as the label will return the latest thumbnail regardless of type.  | ||||
| 
 | ||||
| ### `GET /api/events/<id>/clip.mp4` | ||||
| 
 | ||||
| Returns the clip for the event id. Works after the event has ended. | ||||
| @ -218,6 +212,10 @@ Accepts the following query string parameters, but they are only applied when an | ||||
| | `crop`      | int  | Crop the snapshot to the (0 or 1)                 | | ||||
| | `quality`   | int  | Jpeg encoding quality (0-100). Defaults to 70.    | | ||||
| 
 | ||||
| ### `GET /api/<camera_name>/<label>/snapshot.jpg` | ||||
| 
 | ||||
| Returns the snapshot image from the latest event for the given camera and label combo. Using `any` as the label will return the latest thumbnail regardless of type. | ||||
| 
 | ||||
| ### `GET /clips/<camera>-<id>.jpg` | ||||
| 
 | ||||
| JPG snapshot for the given camera and event id. | ||||
|  | ||||
							
								
								
									
										106
									
								
								frigate/http.py
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								frigate/http.py
									
									
									
									
									
								
							| @ -226,6 +226,39 @@ def event_thumbnail(id): | ||||
|         response.headers["Cache-Control"] = "private, max-age=31536000" | ||||
|     return response | ||||
| 
 | ||||
| @bp.route("/<camera_name>/<label>/best.jpg") | ||||
| @bp.route("/<camera_name>/<label>/thumbnail.jpg") | ||||
| def label_thumbnail(camera_name, label): | ||||
|     if label == "any": | ||||
|         event_query = ( | ||||
|             Event.select() | ||||
|             .where(Event.camera == camera_name) | ||||
|             .where(Event.has_snapshot == True) | ||||
|             .order_by(Event.start_time.desc()) | ||||
|         ) | ||||
|     else: | ||||
|         event_query = ( | ||||
|             Event.select() | ||||
|             .where(Event.camera == camera_name) | ||||
|             .where(Event.label == label) | ||||
|             .where(Event.has_snapshot == True) | ||||
|             .order_by(Event.start_time.desc()) | ||||
|         ) | ||||
| 
 | ||||
|     try: | ||||
|         event = event_query.get() | ||||
| 
 | ||||
|         return event_thumbnail(event.id) | ||||
|     except DoesNotExist: | ||||
|         frame = np.zeros((175, 175, 3), np.uint8) | ||||
|         ret, jpg = cv2.imencode( | ||||
|             ".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 70] | ||||
|         ) | ||||
| 
 | ||||
|         response = make_response(jpg.tobytes()) | ||||
|         response.headers["Content-Type"] = "image/jpeg" | ||||
|         return response | ||||
| 
 | ||||
| 
 | ||||
| @bp.route("/events/<id>/snapshot.jpg") | ||||
| def event_snapshot(id): | ||||
| @ -271,6 +304,37 @@ def event_snapshot(id): | ||||
|         ] = f"attachment; filename=snapshot-{id}.jpg" | ||||
|     return response | ||||
| 
 | ||||
| @bp.route("/<camera_name>/<label>/snapshot.jpg") | ||||
| def label_snapshot(camera_name, label): | ||||
|     if label == "any": | ||||
|         event_query = ( | ||||
|             Event.select() | ||||
|             .where(Event.camera == camera_name) | ||||
|             .where(Event.has_snapshot == True) | ||||
|             .order_by(Event.start_time.desc()) | ||||
|         ) | ||||
|     else: | ||||
|         event_query = ( | ||||
|             Event.select() | ||||
|             .where(Event.camera == camera_name) | ||||
|             .where(Event.label == label) | ||||
|             .where(Event.has_snapshot == True) | ||||
|             .order_by(Event.start_time.desc()) | ||||
|         ) | ||||
| 
 | ||||
|     try: | ||||
|         event = event_query.get() | ||||
|         return event_snapshot(event.id) | ||||
|     except DoesNotExist: | ||||
|         frame = np.zeros((720, 1280, 3), np.uint8) | ||||
|         ret, jpg = cv2.imencode( | ||||
|             ".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 70] | ||||
|         ) | ||||
| 
 | ||||
|         response = make_response(jpg.tobytes()) | ||||
|         response.headers["Content-Type"] = "image/jpeg" | ||||
|         return response | ||||
| 
 | ||||
| 
 | ||||
| @bp.route("/events/<id>/clip.mp4") | ||||
| def event_clip(id): | ||||
| @ -391,48 +455,6 @@ def stats(): | ||||
|     return jsonify(stats) | ||||
| 
 | ||||
| 
 | ||||
| @bp.route("/<camera_name>/<label>/best.jpg") | ||||
| def best(camera_name, label): | ||||
|     if camera_name in current_app.frigate_config.cameras: | ||||
|         best_object = current_app.detected_frames_processor.get_best(camera_name, label) | ||||
|         best_frame = best_object.get("frame") | ||||
|         if best_frame is None: | ||||
|             best_frame = np.zeros((720, 1280, 3), np.uint8) | ||||
|         else: | ||||
|             best_frame = cv2.cvtColor(best_frame, cv2.COLOR_YUV2BGR_I420) | ||||
| 
 | ||||
|         crop = bool(request.args.get("crop", 0, type=int)) | ||||
|         if crop: | ||||
|             box_size = 300 | ||||
|             box = best_object.get("box", (0, 0, box_size, box_size)) | ||||
|             region = calculate_region( | ||||
|                 best_frame.shape, | ||||
|                 box[0], | ||||
|                 box[1], | ||||
|                 box[2], | ||||
|                 box[3], | ||||
|                 box_size, | ||||
|                 multiplier=1.1, | ||||
|             ) | ||||
|             best_frame = best_frame[region[1] : region[3], region[0] : region[2]] | ||||
| 
 | ||||
|         height = int(request.args.get("h", str(best_frame.shape[0]))) | ||||
|         width = int(height * best_frame.shape[1] / best_frame.shape[0]) | ||||
|         resize_quality = request.args.get("quality", default=70, type=int) | ||||
| 
 | ||||
|         best_frame = cv2.resize( | ||||
|             best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA | ||||
|         ) | ||||
|         ret, jpg = cv2.imencode( | ||||
|             ".jpg", best_frame, [int(cv2.IMWRITE_JPEG_QUALITY), resize_quality] | ||||
|         ) | ||||
|         response = make_response(jpg.tobytes()) | ||||
|         response.headers["Content-Type"] = "image/jpeg" | ||||
|         return response | ||||
|     else: | ||||
|         return "Camera named {} not found".format(camera_name), 404 | ||||
| 
 | ||||
| 
 | ||||
| @bp.route("/<camera_name>") | ||||
| def mjpeg_feed(camera_name): | ||||
|     fps = int(request.args.get("fps", "3")) | ||||
|  | ||||
| @ -134,7 +134,7 @@ export default function Camera({ camera }) { | ||||
|               key={objectType} | ||||
|               header={objectType} | ||||
|               href={`/events?camera=${camera}&label=${objectType}`} | ||||
|               media={<img src={`${apiHost}/api/${camera}/${objectType}/best.jpg?crop=1&h=150`} />} | ||||
|               media={<img src={`${apiHost}/api/${camera}/${objectType}/thumbnail.jpg`} />} | ||||
|             /> | ||||
|           ))} | ||||
|         </div> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user