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`.
 | 
					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]`
 | 
					### `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.
 | 
					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.
 | 
					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`
 | 
					### `GET /api/events/<id>/clip.mp4`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Returns the clip for the event id. Works after the event has ended.
 | 
					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)                 |
 | 
					| `crop`      | int  | Crop the snapshot to the (0 or 1)                 |
 | 
				
			||||||
| `quality`   | int  | Jpeg encoding quality (0-100). Defaults to 70.    |
 | 
					| `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`
 | 
					### `GET /clips/<camera>-<id>.jpg`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
JPG snapshot for the given camera and event id.
 | 
					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"
 | 
					        response.headers["Cache-Control"] = "private, max-age=31536000"
 | 
				
			||||||
    return response
 | 
					    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")
 | 
					@bp.route("/events/<id>/snapshot.jpg")
 | 
				
			||||||
def event_snapshot(id):
 | 
					def event_snapshot(id):
 | 
				
			||||||
@ -271,6 +304,37 @@ def event_snapshot(id):
 | 
				
			|||||||
        ] = f"attachment; filename=snapshot-{id}.jpg"
 | 
					        ] = f"attachment; filename=snapshot-{id}.jpg"
 | 
				
			||||||
    return response
 | 
					    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")
 | 
					@bp.route("/events/<id>/clip.mp4")
 | 
				
			||||||
def event_clip(id):
 | 
					def event_clip(id):
 | 
				
			||||||
@ -391,48 +455,6 @@ def stats():
 | 
				
			|||||||
    return jsonify(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>")
 | 
					@bp.route("/<camera_name>")
 | 
				
			||||||
def mjpeg_feed(camera_name):
 | 
					def mjpeg_feed(camera_name):
 | 
				
			||||||
    fps = int(request.args.get("fps", "3"))
 | 
					    fps = int(request.args.get("fps", "3"))
 | 
				
			||||||
 | 
				
			|||||||
@ -134,7 +134,7 @@ export default function Camera({ camera }) {
 | 
				
			|||||||
              key={objectType}
 | 
					              key={objectType}
 | 
				
			||||||
              header={objectType}
 | 
					              header={objectType}
 | 
				
			||||||
              href={`/events?camera=${camera}&label=${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>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user