Spaces:
Sleeping
Sleeping
updated dockerfile & api
Browse files- .DS_Store +0 -0
- API_DOCS.md +98 -0
- Dockerfile +3 -0
- server.py +123 -0
.DS_Store
CHANGED
|
Binary files a/.DS_Store and b/.DS_Store differ
|
|
|
API_DOCS.md
CHANGED
|
@@ -179,6 +179,80 @@ or
|
|
| 179 |
}
|
| 180 |
```
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
## File Storage
|
| 183 |
|
| 184 |
All uploaded files and processed data are stored in the `/Model/` directory:
|
|
@@ -240,4 +314,28 @@ async def stream_audio():
|
|
| 240 |
await websocket.send(json.dumps({"command": "stop"}))
|
| 241 |
|
| 242 |
asyncio.run(stream_audio())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
```
|
|
|
|
| 179 |
}
|
| 180 |
```
|
| 181 |
|
| 182 |
+
### 5. Get Angle Metadata for Session
|
| 183 |
+
**Endpoint:** `GET /api/audio/angles/{session_id}`
|
| 184 |
+
|
| 185 |
+
**Description:** Retrieve angle data collected during an audio streaming session.
|
| 186 |
+
|
| 187 |
+
**Authentication:** Required (JWT token)
|
| 188 |
+
|
| 189 |
+
**Parameters:**
|
| 190 |
+
- `session_id` (path parameter): The UUID of the audio session
|
| 191 |
+
|
| 192 |
+
**Response:**
|
| 193 |
+
```json
|
| 194 |
+
{
|
| 195 |
+
"ok": true,
|
| 196 |
+
"session_id": "uuid-here",
|
| 197 |
+
"angles": [
|
| 198 |
+
{"timestamp": 0.000, "angle": 45.50},
|
| 199 |
+
{"timestamp": 0.064, "angle": 46.20},
|
| 200 |
+
{"timestamp": 0.128, "angle": 47.00}
|
| 201 |
+
],
|
| 202 |
+
"count": 3
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### 6. Download Audio File
|
| 207 |
+
**Endpoint:** `GET /api/audio/download/{session_id}`
|
| 208 |
+
|
| 209 |
+
**Description:** Download the recorded audio file (.wav) for a specific session.
|
| 210 |
+
|
| 211 |
+
**Authentication:** Required (JWT token)
|
| 212 |
+
|
| 213 |
+
**Parameters:**
|
| 214 |
+
- `session_id` (path parameter): The UUID of the audio session
|
| 215 |
+
|
| 216 |
+
**Response:**
|
| 217 |
+
- Binary WAV file with `Content-Type: audio/wav`
|
| 218 |
+
- File download with appropriate filename header
|
| 219 |
+
|
| 220 |
+
### 7. Set Desired Angle
|
| 221 |
+
**Endpoint:** `POST /api/audio/set-angle/{session_id}`
|
| 222 |
+
|
| 223 |
+
**Description:** Send a desired/target angle to the audio processing backend for a session.
|
| 224 |
+
|
| 225 |
+
**Authentication:** Required (JWT token)
|
| 226 |
+
|
| 227 |
+
**Parameters:**
|
| 228 |
+
- `session_id` (path parameter): The UUID of the audio session
|
| 229 |
+
- `angle` (form parameter, required): Desired angle in degrees (0-360)
|
| 230 |
+
|
| 231 |
+
**Request:**
|
| 232 |
+
```
|
| 233 |
+
POST /api/audio/set-angle/session-uuid-here
|
| 234 |
+
Content-Type: multipart/form-data
|
| 235 |
+
|
| 236 |
+
angle=45.5
|
| 237 |
+
```
|
| 238 |
+
|
| 239 |
+
**Response:**
|
| 240 |
+
```json
|
| 241 |
+
{
|
| 242 |
+
"ok": true,
|
| 243 |
+
"message": "Desired angle set to 45.5°",
|
| 244 |
+
"session_id": "uuid-here",
|
| 245 |
+
"angle": 45.5
|
| 246 |
+
}
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
**Error Response (Invalid Angle):**
|
| 250 |
+
```json
|
| 251 |
+
{
|
| 252 |
+
"detail": "Angle must be between 0 and 360 degrees"
|
| 253 |
+
}
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
## File Storage
|
| 257 |
|
| 258 |
All uploaded files and processed data are stored in the `/Model/` directory:
|
|
|
|
| 314 |
await websocket.send(json.dumps({"command": "stop"}))
|
| 315 |
|
| 316 |
asyncio.run(stream_audio())
|
| 317 |
+
|
| 318 |
+
# 4. Get angle data for a session
|
| 319 |
+
response = requests.get(
|
| 320 |
+
f"http://localhost:8000/api/audio/angles/{session_id}",
|
| 321 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 322 |
+
)
|
| 323 |
+
angles = response.json()["angles"]
|
| 324 |
+
print(f"Recorded {len(angles)} angle measurements")
|
| 325 |
+
|
| 326 |
+
# 5. Download recorded audio
|
| 327 |
+
response = requests.get(
|
| 328 |
+
f"http://localhost:8000/api/audio/download/{session_id}",
|
| 329 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 330 |
+
)
|
| 331 |
+
with open("downloaded_audio.wav", "wb") as f:
|
| 332 |
+
f.write(response.content)
|
| 333 |
+
|
| 334 |
+
# 6. Send desired angle to backend
|
| 335 |
+
response = requests.post(
|
| 336 |
+
f"http://localhost:8000/api/audio/set-angle/{session_id}",
|
| 337 |
+
data={"angle": 90.0},
|
| 338 |
+
headers={"Authorization": f"Bearer {token}"}
|
| 339 |
+
)
|
| 340 |
+
print(response.json())
|
| 341 |
```
|
Dockerfile
CHANGED
|
@@ -3,6 +3,9 @@ FROM python:3.11-slim
|
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
RUN apt-get update && apt-get install -y \
|
|
|
|
|
|
|
|
|
|
| 6 |
libgl1-mesa-glx \
|
| 7 |
libglib2.0-0 \
|
| 8 |
libsm6 \
|
|
|
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
RUN apt-get update && apt-get install -y \
|
| 6 |
+
gcc \
|
| 7 |
+
gfortran \
|
| 8 |
+
libopenblas-dev \
|
| 9 |
libgl1-mesa-glx \
|
| 10 |
libglib2.0-0 \
|
| 11 |
libsm6 \
|
server.py
CHANGED
|
@@ -710,5 +710,128 @@ async def list_audio_recordings(current_user: UserPublic = Depends(get_current_u
|
|
| 710 |
logger.error(f"Error listing recordings: {e}")
|
| 711 |
raise HTTPException(status_code=500, detail=str(e))
|
| 712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
if __name__ == "__main__":
|
| 714 |
uvicorn.run("server:app", host="0.0.0.0", port=8000, reload=True)
|
|
|
|
| 710 |
logger.error(f"Error listing recordings: {e}")
|
| 711 |
raise HTTPException(status_code=500, detail=str(e))
|
| 712 |
|
| 713 |
+
@app.get("/api/audio/angles/{session_id}")
|
| 714 |
+
async def get_audio_angles(
|
| 715 |
+
session_id: str
|
| 716 |
+
):
|
| 717 |
+
"""Get angle metadata for a specific audio session."""
|
| 718 |
+
try:
|
| 719 |
+
metadata_file = MODEL_DIR / "audio_recordings" / f"audio_{session_id}_metadata.txt"
|
| 720 |
+
|
| 721 |
+
if not metadata_file.exists():
|
| 722 |
+
raise HTTPException(
|
| 723 |
+
status_code=404,
|
| 724 |
+
detail=f"No metadata found for session {session_id}"
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
angles_data = []
|
| 728 |
+
with open(metadata_file, 'r') as f:
|
| 729 |
+
lines = f.readlines()
|
| 730 |
+
# Skip header if present
|
| 731 |
+
start_idx = 1 if lines and 'timestamp' in lines[0] else 0
|
| 732 |
+
for line in lines[start_idx:]:
|
| 733 |
+
if line.strip():
|
| 734 |
+
parts = line.strip().split(',')
|
| 735 |
+
if len(parts) >= 2:
|
| 736 |
+
try:
|
| 737 |
+
timestamp = float(parts[0])
|
| 738 |
+
angle = float(parts[1])
|
| 739 |
+
angles_data.append({"timestamp": timestamp, "angle": angle})
|
| 740 |
+
except ValueError:
|
| 741 |
+
continue
|
| 742 |
+
|
| 743 |
+
return {
|
| 744 |
+
"ok": True,
|
| 745 |
+
"session_id": session_id,
|
| 746 |
+
"angles": angles_data,
|
| 747 |
+
"count": len(angles_data)
|
| 748 |
+
}
|
| 749 |
+
except HTTPException:
|
| 750 |
+
raise
|
| 751 |
+
except Exception as e:
|
| 752 |
+
logger.error(f"Error retrieving angles for session {session_id}: {e}")
|
| 753 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 754 |
+
|
| 755 |
+
@app.get("/api/audio/download/{session_id}")
|
| 756 |
+
async def download_audio_file(
|
| 757 |
+
session_id: str
|
| 758 |
+
):
|
| 759 |
+
"""Download recorded audio file for a specific session."""
|
| 760 |
+
try:
|
| 761 |
+
# Find the audio file matching this session_id
|
| 762 |
+
audio_dir = MODEL_DIR / "audio_recordings"
|
| 763 |
+
|
| 764 |
+
if not audio_dir.exists():
|
| 765 |
+
raise HTTPException(status_code=404, detail="No audio recordings found")
|
| 766 |
+
|
| 767 |
+
# Search for the file with this session_id
|
| 768 |
+
audio_file = None
|
| 769 |
+
for file in audio_dir.glob(f"audio_{session_id}_*.wav"):
|
| 770 |
+
# Skip metadata files
|
| 771 |
+
if not str(file).endswith("_metadata.txt"):
|
| 772 |
+
audio_file = file
|
| 773 |
+
break
|
| 774 |
+
|
| 775 |
+
if not audio_file or not audio_file.exists():
|
| 776 |
+
raise HTTPException(
|
| 777 |
+
status_code=404,
|
| 778 |
+
detail=f"Audio file not found for session {session_id}"
|
| 779 |
+
)
|
| 780 |
+
|
| 781 |
+
return StreamingResponse(
|
| 782 |
+
iter([await asyncio.get_event_loop().run_in_executor(None, audio_file.read_bytes)]),
|
| 783 |
+
media_type="audio/wav",
|
| 784 |
+
headers={
|
| 785 |
+
"Content-Disposition": f"attachment; filename={audio_file.name}"
|
| 786 |
+
}
|
| 787 |
+
)
|
| 788 |
+
except HTTPException:
|
| 789 |
+
raise
|
| 790 |
+
except Exception as e:
|
| 791 |
+
logger.error(f"Error downloading audio for session {session_id}: {e}")
|
| 792 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 793 |
+
|
| 794 |
+
@app.post("/api/audio/set-angle/{session_id}")
|
| 795 |
+
async def set_desired_angle(
|
| 796 |
+
session_id: str,
|
| 797 |
+
angle: float = Form(...)
|
| 798 |
+
):
|
| 799 |
+
"""Send a desired angle to the audio processing system."""
|
| 800 |
+
try:
|
| 801 |
+
if not (0 <= angle <= 360):
|
| 802 |
+
raise HTTPException(
|
| 803 |
+
status_code=400,
|
| 804 |
+
detail="Angle must be between 0 and 360 degrees"
|
| 805 |
+
)
|
| 806 |
+
|
| 807 |
+
# Check if session exists
|
| 808 |
+
audio_dir = MODEL_DIR / "audio_recordings"
|
| 809 |
+
session_exists = any(audio_dir.glob(f"audio_{session_id}_*.wav"))
|
| 810 |
+
|
| 811 |
+
if not session_exists:
|
| 812 |
+
raise HTTPException(
|
| 813 |
+
status_code=404,
|
| 814 |
+
detail=f"Session {session_id} not found"
|
| 815 |
+
)
|
| 816 |
+
|
| 817 |
+
# Store desired angle (append to metadata or create angle request file)
|
| 818 |
+
desired_angle_file = audio_dir / f"audio_{session_id}_desired_angle.txt"
|
| 819 |
+
with open(desired_angle_file, 'w') as f:
|
| 820 |
+
f.write(f"{angle}\n")
|
| 821 |
+
|
| 822 |
+
logger.info(f"Set desired angle {angle}° for session {session_id}")
|
| 823 |
+
|
| 824 |
+
return {
|
| 825 |
+
"ok": True,
|
| 826 |
+
"message": f"Desired angle set to {angle}°",
|
| 827 |
+
"session_id": session_id,
|
| 828 |
+
"angle": angle
|
| 829 |
+
}
|
| 830 |
+
except HTTPException:
|
| 831 |
+
raise
|
| 832 |
+
except Exception as e:
|
| 833 |
+
logger.error(f"Error setting angle for session {session_id}: {e}")
|
| 834 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 835 |
+
|
| 836 |
if __name__ == "__main__":
|
| 837 |
uvicorn.run("server:app", host="0.0.0.0", port=8000, reload=True)
|