dịch chuyển documents
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
# IPCam Orange Pi Dashboard - FastAPI Backend Skeleton
|
||||
|
||||
## 1. Project Structure
|
||||
|
||||
```
|
||||
ipcam-dashboard/
|
||||
├── main.py
|
||||
├── mediamtx.py
|
||||
├── scheduler.py
|
||||
├── models.py
|
||||
├── config.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. requirements.txt
|
||||
|
||||
```
|
||||
fastapi
|
||||
uvicorn
|
||||
httpx
|
||||
pydantic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. config.py
|
||||
|
||||
```python
|
||||
MEDIAMTX_API = "http://127.0.0.1:9997"
|
||||
|
||||
CAMERAS = ["cam1", "cam2", "cam3", "cam4"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. mediamtx.py (MediaMTX API wrapper)
|
||||
|
||||
```python
|
||||
import httpx
|
||||
from config import MEDIAMTX_API, CAMERAS
|
||||
|
||||
|
||||
async def set_recording(enabled: bool):
|
||||
payload = {
|
||||
"paths": {
|
||||
cam: {"record": enabled}
|
||||
for cam in CAMERAS
|
||||
}
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
f"{MEDIAMTX_API}/v3/config/paths/patch",
|
||||
json=payload,
|
||||
timeout=5
|
||||
)
|
||||
return r.json()
|
||||
|
||||
|
||||
async def get_paths():
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(f"{MEDIAMTX_API}/v3/paths/list")
|
||||
return r.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. scheduler.py
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from mediamtx import set_recording
|
||||
|
||||
_last_state = None
|
||||
|
||||
|
||||
def is_record_time(now: datetime) -> bool:
|
||||
day = now.weekday() # 0=Mon
|
||||
hour = now.hour
|
||||
|
||||
# Weekend: always record
|
||||
if day in (5, 6):
|
||||
return True
|
||||
|
||||
# Weekdays: 18:00 → 08:00
|
||||
return hour >= 18 or hour < 8
|
||||
|
||||
|
||||
async def scheduler_loop():
|
||||
global _last_state
|
||||
|
||||
while True:
|
||||
now = datetime.now()
|
||||
should_record = is_record_time(now)
|
||||
|
||||
if _last_state != should_record:
|
||||
print(f"[Scheduler] Set recording = {should_record}")
|
||||
await set_recording(should_record)
|
||||
_last_state = should_record
|
||||
|
||||
import asyncio
|
||||
await asyncio.sleep(60)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. models.py
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RecordingToggle(BaseModel):
|
||||
enabled: bool
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. main.py
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
import asyncio
|
||||
|
||||
from mediamtx import set_recording, get_paths
|
||||
from scheduler import scheduler_loop
|
||||
from models import RecordingToggle
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
asyncio.create_task(scheduler_loop())
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/paths")
|
||||
async def list_paths():
|
||||
return await get_paths()
|
||||
|
||||
|
||||
@app.post("/recording")
|
||||
async def toggle_recording(data: RecordingToggle):
|
||||
return await set_recording(data.enabled)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Run Server
|
||||
|
||||
```
|
||||
uvicorn main:app --host 0.0.0.0 --port 8008
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. API Endpoints
|
||||
|
||||
### Get stream status
|
||||
```
|
||||
GET /paths
|
||||
```
|
||||
|
||||
### Enable recording
|
||||
```
|
||||
POST /recording
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
### Disable recording
|
||||
```
|
||||
POST /recording
|
||||
{
|
||||
"enabled": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Next Steps
|
||||
|
||||
- Add recordings API (list files)
|
||||
- Serve video files (StaticFiles)
|
||||
- Add camera config management (DB or JSON)
|
||||
- Build frontend dashboard (WebRTC grid)
|
||||
Reference in New Issue
Block a user