basaHoushanApi/src/main.py

253 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import FastAPI, Form # <-- Form is imported here
from fastapi.staticfiles import StaticFiles
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from tortoise.contrib.fastapi import register_tortoise
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from .config import settings
from .tasks.user import free_credit
from .services import storage as storage_service, tool as tool_service
from .routes import register_routers
from .models import SunoTask
from pytz import timezone
from typing import Annotated # We keep the import but adjust usage to fix the NameError
import fal_client
import os
import httpx
import asyncio
import json
# مدل مورد نظر Fal AI برای تولید ویدئو از متن
FAL_VIDEO_MODEL = "fal-ai/pixverse/v3.5/text-to-video"
FAL_API_KEY = os.getenv("FAL_KEY") # کلید Fal API از محیط
FAL_BASE_URL = "https://queue.fal.run/fal-ai/pixverse"
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
allow_credentials=True,
)
# Create scheduler
scheduler = AsyncIOScheduler()
# Add a job to the scheduler
tehran_tz = timezone('Asia/Tehran')
# scheduler.add_job(
# free_credit,
# CronTrigger(hour=0, minute=0, second=0, timezone=tehran_tz)
# )
# Start the scheduler
scheduler.start()
register_tortoise(
app,
db_url=settings.database_url,
modules={"models": ["src.models"]},
generate_schemas=False,
add_exception_handlers=True,
)
app.mount(
"/.well-known",
StaticFiles(directory="static/.well-known"),
name=".well-known",
)
register_routers(app)
@app.get("/", tags=["home"])
async def info():
return {
"image": "https://www.instagram.com/houshan.ai/",
"audio": "https://www.instagram.com/houshan.ai/",
"video": "https://www.instagram.com/houshan.ai/",
}
@app.get("/apk", tags=["app"])
async def apk():
file_size = await storage_service.get_file_size("apk/app-release.apk")
headers = {
"Content-Disposition": 'attachment; filename="houshan.apk"',
"Content-Length": str(file_size),
}
return StreamingResponse(
storage_service.get("apk/app-release.apk"),
media_type="application/octet-stream",
headers=headers,
)
# ------------------------------------
## 🎥 ر: تولید ویدئو از متن (با استفاده مستقیم از fal_client)
# ----------------------------------------------------------------------
@app.post("/public/generate-video", tags=["public"])
async def generate_public_video(
prompt: str = Form(..., description="The text prompt to generate the video from."),
length: int = Form(5),
negative_prompt: str = Form("blurry, low quality"),
):
if not FAL_API_KEY:
return JSONResponse({
"status": "error",
"message": "FAL_KEY environment variable is not set."
}, 500)
headers = {
"Authorization": f"Key {FAL_API_KEY}",
"Content-Type": "application/json",
}
# مرحله ۱: ارسال درخواست تولید ویدیو
async with httpx.AsyncClient(timeout=60.0) as client:
try:
submit_url = f"{FAL_BASE_URL}/v3.5/text-to-video"
response = await client.post(
submit_url,
headers=headers,
json={
"prompt": prompt,
"duration": "5",
"resolution":"540p",
"negative_prompt": negative_prompt
}
)
if response.status_code != 200:
return JSONResponse({
"status": "error",
"message": "Failed to submit video generation request.",
"details": response.text
}, response.status_code)
data = response.json()
request_id = data.get("request_id")
if not request_id:
return JSONResponse({
"status": "error",
"message": "No request_id returned from Fal API.",
"details": data
}, 500)
print(f"✅ Fal request submitted. Request ID: {request_id}")
except Exception as e:
return JSONResponse({
"status": "error",
"message": "Failed to connect to Fal API.",
"details": str(e)
}, 500)
# مرحله ۲: بررسی وضعیت تا زمان تکمیل
status_url = f"{FAL_BASE_URL}/requests/{request_id}/status"
for attempt in range(30):
await asyncio.sleep(3)
async with httpx.AsyncClient(timeout=60.0) as client:
status_response = await client.get(status_url, headers=headers)
status_data = status_response.json()
status = status_data.get("status")
print(f"🔄 Fal Status Check {attempt+1}: {status}")
if status == "COMPLETED":
break
elif status in ["FAILED", "ERROR"]:
return JSONResponse({
"status": "error",
"message": "Video generation failed.",
"details": status_data
}, 500)
else:
return JSONResponse({
"status": "error",
"message": "Timeout: Video not completed in expected time."
}, 504)
# مرحله ۳: دریافت نتیجه نهایی
result_url = f"{FAL_BASE_URL}/requests/{request_id}"
async with httpx.AsyncClient(timeout=60.0) as client:
final_response = await client.get(result_url, headers=headers)
if final_response.status_code != 200:
return JSONResponse({
"status": "error",
"message": "Failed to retrieve final video result.",
"details": final_response.text
}, final_response.status_code)
final_data = final_response.json()
return JSONResponse({
"status": "success",
"message": "Video generated successfully.",
"video_result": final_data
})
@app.post("/suno/callback")
async def suno_callback(payload: dict):
print("=== Suno Callback Received ===")
print("Payload:", json.dumps(payload, ensure_ascii=False, indent=2))
task_id = payload.get("data", {}).get("task_id")
callback_type = payload.get("data", {}).get("callbackType")
if not task_id:
print("WARNING: task_id missing in callback")
return {"ok": True}
suno_task = await SunoTask.get_or_none(task_id=task_id)
if not suno_task:
print(f"WARNING: Task {task_id} not found in DB")
return {"ok": True}
suno_task.raw_callback = payload
if callback_type in ("text", "first"):
suno_task.status = "PROCESSING"
print(f"Task {task_id} status updated to PROCESSING")
elif callback_type == "complete":
items = payload.get("data", {}).get("data", [])
if items:
suno_task.audio_url = items[0].get("audio_url")
suno_task.image_url = items[0].get("image_url")
print(f"Task {task_id} audio_url: {suno_task.audio_url}")
print(f"Task {task_id} image_url: {suno_task.image_url}")
suno_task.status = "COMPLETE"
print(f"Task {task_id} status updated to COMPLETE")
await suno_task.save()
print(f"Task {task_id} saved to DB")
return {"ok": True}
@app.get("/api/suno/tasks/{task_id}")
async def get_suno_task(task_id: str):
suno_task = await SunoTask.get_or_none(task_id=task_id)
if not suno_task:
print(f"Task {task_id} not found")
raise HTTPException(404, "Task not found")
result = {
"task_id": suno_task.task_id,
"status": suno_task.status,
"audio_url": suno_task.audio_url,
"image_url": suno_task.image_url,
}
print(f"Returning task {task_id}:", json.dumps(result, ensure_ascii=False))
return result