mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 15:15:22 +03:00
Convert all media endpoints to FastAPI. Added /media prefix (/media/camera && media/events && /media/preview)
This commit is contained in:
parent
b83f9532ab
commit
012c953dfb
@ -23,7 +23,6 @@ from frigate.api.auth import AuthBp, get_jwt_secret, limiter
|
|||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.api.event import EventBp
|
from frigate.api.event import EventBp
|
||||||
from frigate.api.export import ExportBp
|
from frigate.api.export import ExportBp
|
||||||
from frigate.api.media import MediaBp
|
|
||||||
from frigate.api.notification import NotificationBp
|
from frigate.api.notification import NotificationBp
|
||||||
from frigate.api.review import ReviewBp
|
from frigate.api.review import ReviewBp
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
@ -49,7 +48,6 @@ logger = logging.getLogger(__name__)
|
|||||||
bp = Blueprint("frigate", __name__)
|
bp = Blueprint("frigate", __name__)
|
||||||
bp.register_blueprint(EventBp)
|
bp.register_blueprint(EventBp)
|
||||||
bp.register_blueprint(ExportBp)
|
bp.register_blueprint(ExportBp)
|
||||||
bp.register_blueprint(MediaBp)
|
|
||||||
bp.register_blueprint(ReviewBp)
|
bp.register_blueprint(ReviewBp)
|
||||||
bp.register_blueprint(AuthBp)
|
bp.register_blueprint(AuthBp)
|
||||||
bp.register_blueprint(NotificationBp)
|
bp.register_blueprint(NotificationBp)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import logging
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from frigate.api import app as main_app
|
from frigate.api import app as main_app
|
||||||
from frigate.api import preview
|
from frigate.api import media, preview
|
||||||
from frigate.plus import PlusApi
|
from frigate.plus import PlusApi
|
||||||
from frigate.ptz.onvif import OnvifController
|
from frigate.ptz.onvif import OnvifController
|
||||||
from frigate.stats.emitter import StatsEmitter
|
from frigate.stats.emitter import StatsEmitter
|
||||||
@ -21,9 +21,13 @@ def create_fastapi_app(
|
|||||||
stats_emitter: StatsEmitter,
|
stats_emitter: StatsEmitter,
|
||||||
):
|
):
|
||||||
logger.info("Starting FastAPI app")
|
logger.info("Starting FastAPI app")
|
||||||
app = FastAPI(debug=False)
|
app = FastAPI(
|
||||||
|
debug=False,
|
||||||
|
swagger_ui_parameters={"apisSorter": "alpha", "operationsSorter": "alpha"},
|
||||||
|
)
|
||||||
# Routes
|
# Routes
|
||||||
app.include_router(main_app.router)
|
app.include_router(main_app.router)
|
||||||
|
app.include_router(media.router)
|
||||||
app.include_router(preview.router)
|
app.include_router(preview.router)
|
||||||
# App Properties
|
# App Properties
|
||||||
app.frigate_config = frigate_config
|
app.frigate_config = frigate_config
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -5,12 +5,14 @@ import os
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
from peewee_migrate import Router
|
from peewee_migrate import Router
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
from playhouse.sqlite_ext import SqliteExtDatabase
|
from playhouse.sqlite_ext import SqliteExtDatabase
|
||||||
from playhouse.sqliteq import SqliteQueueDatabase
|
from playhouse.sqliteq import SqliteQueueDatabase
|
||||||
|
|
||||||
from frigate.api.app import create_app
|
from frigate.api.app import create_app
|
||||||
|
from frigate.api.fastapi_app import create_fastapi_app
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.models import Event, Recordings
|
from frigate.models import Event, Recordings
|
||||||
from frigate.plus import PlusApi
|
from frigate.plus import PlusApi
|
||||||
@ -362,24 +364,23 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert config["cameras"]["front_door"]
|
assert config["cameras"]["front_door"]
|
||||||
|
|
||||||
def test_recordings(self):
|
def test_recordings(self):
|
||||||
app = create_app(
|
app_fastapi = create_fastapi_app(
|
||||||
FrigateConfig(**self.minimal_config).runtime_config(),
|
FrigateConfig(**self.minimal_config).runtime_config(),
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
PlusApi(),
|
PlusApi(),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
client = TestClient(app_fastapi)
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
with app.test_client() as client:
|
_insert_mock_recording(id)
|
||||||
_insert_mock_recording(id)
|
response = client.get("/media/camera/front_door/recordings")
|
||||||
recording = client.get("/front_door/recordings").json
|
assert response.status_code == 200
|
||||||
assert recording
|
recording = response.json()
|
||||||
assert recording[0]["id"] == id
|
assert recording
|
||||||
|
assert recording[0]["id"] == id
|
||||||
|
|
||||||
def test_stats(self):
|
def test_stats(self):
|
||||||
stats = Mock(spec=StatsEmitter)
|
stats = Mock(spec=StatsEmitter)
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export default function CameraImage({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSrc = `${apiHost}api/${name}/latest.webp?h=${requestHeight}${
|
const newSrc = `${apiHost}api/media/camera/${name}/frame/latest?extension=webp&?height=${requestHeight}${
|
||||||
searchParams ? `&${searchParams}` : ""
|
searchParams ? `&${searchParams}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
|||||||
@ -89,7 +89,7 @@ export default function CameraImage({
|
|||||||
if (!config || scaledHeight === 0 || !canvasRef.current) {
|
if (!config || scaledHeight === 0 || !canvasRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
img.src = `${apiHost}api/${name}/latest.webp?h=${scaledHeight}${
|
img.src = `${apiHost}api/media/camera/${name}/frame/latest?extension=webp&height=${scaledHeight}${
|
||||||
searchParams ? `&${searchParams}` : ""
|
searchParams ? `&${searchParams}` : ""
|
||||||
}`;
|
}`;
|
||||||
}, [apiHost, canvasRef, name, img, searchParams, scaledHeight, config]);
|
}, [apiHost, canvasRef, name, img, searchParams, scaledHeight, config]);
|
||||||
|
|||||||
@ -173,7 +173,7 @@ export function AnimatedEventCard({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<source
|
<source
|
||||||
src={`${baseUrl}api/review/${event.id}/preview?format=mp4`}
|
src={`${baseUrl}api/media/review/${event.id}/preview?format=mp4`}
|
||||||
type="video/mp4"
|
type="video/mp4"
|
||||||
/>
|
/>
|
||||||
</video>
|
</video>
|
||||||
|
|||||||
@ -160,13 +160,13 @@ export default function ObjectLifecycle({
|
|||||||
// image
|
// image
|
||||||
|
|
||||||
const [src, setSrc] = useState(
|
const [src, setSrc] = useState(
|
||||||
`${apiHost}api/${event.camera}/recordings/${event.start_time + annotationOffset / 1000}/snapshot.jpg?height=500`,
|
`${apiHost}api/media/camera/${event.camera}/recordings/${event.start_time + annotationOffset / 1000}/snapshot.jpg?height=500`,
|
||||||
);
|
);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timeIndex) {
|
if (timeIndex) {
|
||||||
const newSrc = `${apiHost}api/${event.camera}/recordings/${timeIndex + annotationOffset / 1000}/snapshot.jpg?height=500`;
|
const newSrc = `${apiHost}api/media/camera/${event.camera}/recordings/${timeIndex + annotationOffset / 1000}/snapshot.jpg?height=500`;
|
||||||
setSrc(newSrc);
|
setSrc(newSrc);
|
||||||
}
|
}
|
||||||
setImgLoaded(false);
|
setImgLoaded(false);
|
||||||
|
|||||||
@ -248,8 +248,8 @@ function EventItem({
|
|||||||
draggable={false}
|
draggable={false}
|
||||||
src={
|
src={
|
||||||
event.has_snapshot
|
event.has_snapshot
|
||||||
? `${apiHost}api/events/${event.id}/snapshot.jpg`
|
? `${apiHost}api/media/events/${event.id}/snapshot.jpg`
|
||||||
: `${apiHost}api/events/${event.id}/thumbnail.jpg`
|
: `${apiHost}api/media/events/${event.id}/thumbnail.jpg`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{hovered && (
|
{hovered && (
|
||||||
@ -263,8 +263,8 @@ function EventItem({
|
|||||||
download
|
download
|
||||||
href={
|
href={
|
||||||
event.has_snapshot
|
event.has_snapshot
|
||||||
? `${apiHost}api/events/${event.id}/snapshot.jpg`
|
? `${apiHost}api/media/events/${event.id}/snapshot.jpg`
|
||||||
: `${apiHost}api/events/${event.id}/thumbnail.jpg`
|
: `${apiHost}api/media/events/${event.id}/thumbnail.jpg`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500">
|
<Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500">
|
||||||
|
|||||||
@ -95,7 +95,7 @@ export function FrigatePlusDialog({
|
|||||||
{upload?.id && (
|
{upload?.id && (
|
||||||
<img
|
<img
|
||||||
className={`w-full ${grow} bg-black`}
|
className={`w-full ${grow} bg-black`}
|
||||||
src={`${baseUrl}api/events/${upload?.id}/snapshot.jpg`}
|
src={`${baseUrl}api/media/events/${upload?.id}/snapshot.jpg`}
|
||||||
alt={`${upload?.label}`}
|
alt={`${upload?.label}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -586,7 +586,7 @@ class PreviewFramesController extends PreviewController {
|
|||||||
if (this.seeking) {
|
if (this.seeking) {
|
||||||
this.timeToSeek = frame;
|
this.timeToSeek = frame;
|
||||||
} else {
|
} else {
|
||||||
const newSrc = `${baseUrl}api/preview/preview_${this.camera}-${frame}.webp/thumbnail.webp`;
|
const newSrc = `${baseUrl}api/media/preview/preview_${this.camera}-${frame}.webp/thumbnail.webp`;
|
||||||
|
|
||||||
if (this.imgController.current.src != newSrc) {
|
if (this.imgController.current.src != newSrc) {
|
||||||
this.imgController.current.src = newSrc;
|
this.imgController.current.src = newSrc;
|
||||||
@ -603,7 +603,7 @@ class PreviewFramesController extends PreviewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.timeToSeek) {
|
if (this.timeToSeek) {
|
||||||
const newSrc = `${baseUrl}api/preview/preview_${this.camera}-${this.timeToSeek}.webp/thumbnail.webp`;
|
const newSrc = `${baseUrl}api/media/preview/preview_${this.camera}-${this.timeToSeek}.webp/thumbnail.webp`;
|
||||||
|
|
||||||
if (this.imgController.current.src != newSrc) {
|
if (this.imgController.current.src != newSrc) {
|
||||||
this.imgController.current.src = newSrc;
|
this.imgController.current.src = newSrc;
|
||||||
|
|||||||
@ -149,7 +149,7 @@ export default function DynamicVideoPlayer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const time = controller.getProgress(playTime);
|
const time = controller.getProgress(playTime);
|
||||||
return axios.post(`/${camera}/plus/${time}`);
|
return axios.post(`/media/camera/${camera}/plus/${time}`);
|
||||||
},
|
},
|
||||||
[camera, controller],
|
[camera, controller],
|
||||||
);
|
);
|
||||||
@ -164,7 +164,7 @@ export default function DynamicVideoPlayer({
|
|||||||
[timeRange],
|
[timeRange],
|
||||||
);
|
);
|
||||||
const { data: recordings } = useSWR<Recording[]>(
|
const { data: recordings } = useSWR<Recording[]>(
|
||||||
[`${camera}/recordings`, recordingParams],
|
[`media/camera/${camera}/recordings`, recordingParams],
|
||||||
{ revalidateOnFocus: false },
|
{ revalidateOnFocus: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -433,7 +433,7 @@ export function InProgressPreview({
|
|||||||
<div className="relative flex size-full items-center bg-black">
|
<div className="relative flex size-full items-center bg-black">
|
||||||
<img
|
<img
|
||||||
className="pointer-events-none size-full object-contain"
|
className="pointer-events-none size-full object-contain"
|
||||||
src={`${apiHost}api/preview/${previewFrames[key]}/thumbnail.webp`}
|
src={`${apiHost}api/media/preview/${previewFrames[key]}/thumbnail.webp`}
|
||||||
onLoad={handleLoad}
|
onLoad={handleLoad}
|
||||||
/>
|
/>
|
||||||
{showProgress && (
|
{showProgress && (
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export function PolygonCanvas({
|
|||||||
const element = new window.Image();
|
const element = new window.Image();
|
||||||
element.width = width;
|
element.width = width;
|
||||||
element.height = height;
|
element.height = height;
|
||||||
element.src = `${apiHost}api/${camera}/latest.webp?cache=${Date.now()}`;
|
element.src = `${apiHost}api/media/camera/${camera}/frame/latest?extension=webp&?cache=${Date.now()}`;
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
// we know that these deps are correct
|
// we know that these deps are correct
|
||||||
|
|||||||
@ -259,7 +259,7 @@ export default function SubmitPlus() {
|
|||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
className="aspect-video h-full rounded-lg object-contain md:rounded-2xl"
|
className="aspect-video h-full rounded-lg object-contain md:rounded-2xl"
|
||||||
src={`${baseUrl}api/events/${event.id}/snapshot.jpg`}
|
src={`${baseUrl}api/media/events/${event.id}/snapshot.jpg`}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -499,7 +499,9 @@ function PtzControlPanel({
|
|||||||
clickOverlay: boolean;
|
clickOverlay: boolean;
|
||||||
setClickOverlay: React.Dispatch<React.SetStateAction<boolean>>;
|
setClickOverlay: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}) {
|
}) {
|
||||||
const { data: ptz } = useSWR<CameraPtzInfo>(`${camera}/ptz/info`);
|
const { data: ptz } = useSWR<CameraPtzInfo>(
|
||||||
|
`/media/camera/${camera}/ptz/info`,
|
||||||
|
);
|
||||||
|
|
||||||
const { send: sendPtz } = usePtzCommand(camera);
|
const { send: sendPtz } = usePtzCommand(camera);
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ type StorageMetricsProps = {
|
|||||||
export default function StorageMetrics({
|
export default function StorageMetrics({
|
||||||
setLastUpdated,
|
setLastUpdated,
|
||||||
}: StorageMetricsProps) {
|
}: StorageMetricsProps) {
|
||||||
const { data: cameraStorage } = useSWR<CameraStorage>("recordings/storage");
|
const { data: cameraStorage } = useSWR<CameraStorage>("media/recordings/storage");
|
||||||
const { data: stats } = useSWR<FrigateStats>("stats");
|
const { data: stats } = useSWR<FrigateStats>("stats");
|
||||||
|
|
||||||
const totalStorage = useMemo(() => {
|
const totalStorage = useMemo(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user