feat: bổ sung MIDI
This commit is contained in:
+146
-6
@@ -1,14 +1,119 @@
|
||||
import time
|
||||
import json
|
||||
import uuid
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException, Depends, Header
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Any, Dict
|
||||
from jsonschema import validate, ValidationError
|
||||
from app.models.user import get_db_connection
|
||||
from app.api.v1.auth import get_current_user, decode_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
SCHEMA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "models", "project_schema.json")
|
||||
|
||||
def upgrade_project_json_if_needed(project_data: dict) -> dict:
|
||||
if "main_session" in project_data:
|
||||
return project_data
|
||||
|
||||
tracks = project_data.get("tracks", [])
|
||||
upgraded_tracks = []
|
||||
for t in tracks:
|
||||
track_id = str(t.get("id", ""))
|
||||
track_name = t.get("name", "Track")
|
||||
vol = t.get("volumeDb", 0.0)
|
||||
pan = t.get("pan", 0.0)
|
||||
muted = t.get("muted", False)
|
||||
solo = t.get("solo", False)
|
||||
|
||||
items = []
|
||||
for c in t.get("clips", []):
|
||||
items.append({
|
||||
"id": c.get("id"),
|
||||
"name": c.get("name", "Audio Clip"),
|
||||
"type": "AUDIO_ITEM",
|
||||
"start_bar": c.get("startTime", 0.0) / 4.0,
|
||||
"duration_bars": 4.0,
|
||||
"clip_start_offset_bars": 0.0,
|
||||
"source_data": {
|
||||
"audio_file_url": f"/static/audio/uploads/{t.get('serverFileId')}" if t.get("serverFileId") else "",
|
||||
"sample_rate": 44100,
|
||||
"channels": 2,
|
||||
"gain": 1.0
|
||||
}
|
||||
})
|
||||
for m in t.get("midiItems", []):
|
||||
items.append({
|
||||
"id": m.get("id"),
|
||||
"name": m.get("name", "MIDI Item"),
|
||||
"type": "MIDI_ITEM",
|
||||
"start_bar": m.get("startTime", 0.0) / 4.0,
|
||||
"duration_bars": m.get("duration", 4.0),
|
||||
"clip_start_offset_bars": 0.0,
|
||||
"source_data": {
|
||||
"total_buffer_bars": m.get("duration", 8.0),
|
||||
"notes": m.get("notes", [])
|
||||
}
|
||||
})
|
||||
|
||||
upgraded_tracks.append({
|
||||
"id": track_id,
|
||||
"name": track_name,
|
||||
"type": "MIDI" if t.get("midiItems") else "AUDIO",
|
||||
"volume_db": vol,
|
||||
"pan": pan,
|
||||
"mute": muted,
|
||||
"solo": solo,
|
||||
"fx_chain": [],
|
||||
"synth_engine": {
|
||||
"plugin_id": "synth",
|
||||
"preset_id": "default",
|
||||
"parameters": {}
|
||||
},
|
||||
"items": items
|
||||
})
|
||||
|
||||
return {
|
||||
"project_id": project_data.get("id", "temp_project"),
|
||||
"metadata": {
|
||||
"title": project_data.get("name", "Dự án mới"),
|
||||
"bpm": 120.0,
|
||||
"time_signature_numerator": 4,
|
||||
"time_signature_denominator": 4,
|
||||
"sample_rate": 44100
|
||||
},
|
||||
"main_session": {
|
||||
"id": "main",
|
||||
"name": "MAIN SESSION",
|
||||
"is_root": True,
|
||||
"length_bars": 16.0,
|
||||
"auto_compute_length": True,
|
||||
"tracks": upgraded_tracks
|
||||
},
|
||||
"section_store": {}
|
||||
}
|
||||
|
||||
def validate_project_data(data_json: str) -> str:
|
||||
try:
|
||||
data = json.loads(data_json)
|
||||
if "main_session" not in data:
|
||||
data = upgrade_project_json_if_needed(data)
|
||||
data_json = json.dumps(data)
|
||||
|
||||
if os.path.exists(SCHEMA_PATH):
|
||||
with open(SCHEMA_PATH, "r") as f:
|
||||
schema = json.load(f)
|
||||
validate(instance=data, schema=schema)
|
||||
return data_json
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Cấu trúc JSON không hợp lệ: {str(e)}")
|
||||
except ValidationError as e:
|
||||
path = " -> ".join(str(p) for p in e.path)
|
||||
raise HTTPException(status_code=400, detail=f"Lỗi xác thực project schema tại [{path}]: {e.message}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Lỗi xác thực dự án: {str(e)}")
|
||||
|
||||
class SaveProjectRequest(BaseModel):
|
||||
name: str
|
||||
data_json: str
|
||||
@@ -24,11 +129,12 @@ def get_optional_user(authorization: Optional[str] = Header(None)) -> Optional[d
|
||||
|
||||
@router.post("/temp")
|
||||
async def save_temp_project(req: SaveTempProjectRequest, current_user: Optional[dict] = Depends(get_optional_user)):
|
||||
validated_data_json = validate_project_data(req.data_json)
|
||||
user_id = current_user["user_id"] if current_user else "anonymous"
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
size_bytes = len(req.data_json.encode("utf-8"))
|
||||
size_bytes = len(validated_data_json.encode("utf-8"))
|
||||
now = time.time()
|
||||
temp_id = f"temp_{user_id}"
|
||||
|
||||
@@ -36,7 +142,7 @@ async def save_temp_project(req: SaveTempProjectRequest, current_user: Optional[
|
||||
INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at)
|
||||
VALUES (?, ?, 'Dự án tạm chưa lưu', ?, 1, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET data_json = excluded.data_json, size_bytes = excluded.size_bytes, updated_at = excluded.updated_at
|
||||
""", (temp_id, user_id, req.data_json, size_bytes, now))
|
||||
""", (temp_id, user_id, validated_data_json, size_bytes, now))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -64,6 +170,7 @@ async def get_temp_project(current_user: Optional[dict] = Depends(get_optional_u
|
||||
|
||||
@router.post("/cloud")
|
||||
async def save_cloud_project(req: SaveProjectRequest, current_user: dict = Depends(get_current_user)):
|
||||
validated_data_json = validate_project_data(req.data_json)
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
@@ -76,7 +183,7 @@ async def save_cloud_project(req: SaveProjectRequest, current_user: dict = Depen
|
||||
used_row = cursor.fetchone()
|
||||
used_bytes = used_row["total_used"] if used_row and used_row["total_used"] else 0
|
||||
|
||||
new_size_bytes = len(req.data_json.encode("utf-8"))
|
||||
new_size_bytes = len(validated_data_json.encode("utf-8"))
|
||||
max_bytes = storage_limit_mb * 1024 * 1024
|
||||
|
||||
if used_bytes + new_size_bytes > max_bytes:
|
||||
@@ -92,7 +199,7 @@ async def save_cloud_project(req: SaveProjectRequest, current_user: dict = Depen
|
||||
cursor.execute("""
|
||||
INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at)
|
||||
VALUES (?, ?, ?, ?, 0, ?, ?)
|
||||
""", (project_id, user_id, req.name, req.data_json, new_size_bytes, now))
|
||||
""", (project_id, user_id, req.name, validated_data_json, new_size_bytes, now))
|
||||
|
||||
temp_id = f"temp_{user_id}"
|
||||
cursor.execute("DELETE FROM projects WHERE id = ? AND is_temp = 1", (temp_id,))
|
||||
@@ -157,6 +264,7 @@ async def delete_cloud_project(project_id: str, current_user: dict = Depends(get
|
||||
|
||||
@router.put("/cloud/{project_id}")
|
||||
async def update_cloud_project(project_id: str, req: SaveProjectRequest, current_user: dict = Depends(get_current_user)):
|
||||
validated_data_json = validate_project_data(req.data_json)
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
@@ -167,15 +275,47 @@ async def update_cloud_project(project_id: str, req: SaveProjectRequest, current
|
||||
conn.close()
|
||||
raise HTTPException(status_code=404, detail="Không tìm thấy dự án để cập nhật")
|
||||
|
||||
new_size_bytes = len(req.data_json.encode("utf-8"))
|
||||
new_size_bytes = len(validated_data_json.encode("utf-8"))
|
||||
now = time.time()
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE projects
|
||||
SET name = ?, data_json = ?, size_bytes = ?, updated_at = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
""", (req.name, req.data_json, new_size_bytes, now, project_id, user_id))
|
||||
""", (req.name, validated_data_json, new_size_bytes, now, project_id, user_id))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"success": True, "message": "Đã cập nhật dự án thành công"}
|
||||
|
||||
class RenderProjectRequest(BaseModel):
|
||||
sample_rate: Optional[int] = 44100
|
||||
|
||||
@router.post("/cloud/{project_id}/render")
|
||||
async def render_project_endpoint(project_id: str, req: RenderProjectRequest, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name, data_json FROM projects WHERE id = ? AND user_id = ? AND is_temp = 0", (project_id, user_id))
|
||||
row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Không tìm thấy dự án để kết xuất")
|
||||
|
||||
# Validate schema
|
||||
validate_project_data(row["data_json"])
|
||||
|
||||
# Trigger Celery task
|
||||
from app.tasks.worker import render_project_task
|
||||
task = render_project_task.delay(
|
||||
project_id=project_id,
|
||||
project_name=row["name"],
|
||||
project_json_str=row["data_json"],
|
||||
sample_rate=req.sample_rate or 44100
|
||||
)
|
||||
|
||||
return {
|
||||
"task_id": task.id,
|
||||
"status": "processing"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user