import os import shutil import subprocess import json import time import requests from fastapi import FastAPI, BackgroundTasks, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from playwright.sync_api import sync_playwright app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # --- KONFIGURASI --- UNITY_PATH = "/opt/unity/Editor/Unity" PROJECT_PATH = "/app/UnityProject" TEMP_DIR = "/tmp/zepeto_work" os.makedirs(TEMP_DIR, exist_ok=True) # --- MODEL DATA (Sesuai dengan kiriman Cloudflare + Cloudinary) --- class ConversionRequest(BaseModel): file_url: str # URL dari Cloudinary filename: str # Nama file (contoh: baju_keren.fbx) zepeto_id: str zepeto_password: str # --- FUNGSI: LOGIN & UPLOAD KE ZEPETO STUDIO --- def upload_to_zepeto(file_path, z_id, z_pw): print(f"🔐 [Zepeto] Mencoba login untuk: {z_id}") try: with sync_playwright() as p: browser = p.chromium.launch(headless=True, args=['--no-sandbox']) context = browser.new_context(user_agent="Mozilla/5.0...") page = context.new_page() # Login Process page.goto("https://account.zepeto.me/signin?sm=1") page.fill('input[name="userId"]', z_id) page.fill('input[type="password"]', z_pw) page.click('button[type="submit"]') # Tunggu Token/Cookie page.wait_for_url(lambda u: "signin" not in u, timeout=15000) cookies = context.cookies() jwt_token = next((c['value'] for c in cookies if c['name'] == 'token'), None) browser.close() if not jwt_token: return False, "Gagal mendapatkan JWT Token" # API Upload Zepeto Studio print("🚀 [Zepeto] Mengirim file ke Studio API...") upload_url = "https://cf-api-studio.zepeto.me/api/items" headers = {"Authorization": f"Bearer {jwt_token}"} with open(file_path, "rb") as f: files = {"file": (os.path.basename(file_path), f, "application/octet-stream")} res = requests.post(upload_url, headers=headers, files=files) if res.status_code in [200, 201]: return True, "Sukses Upload" else: return False, f"API Error: {res.text}" except Exception as e: return False, str(e) # --- FUNGSI UTAMA: DOWNLOAD -> UNITY -> UPLOAD --- def process_pipeline(req: ConversionRequest): local_fbx = os.path.join(PROJECT_PATH, "Assets", req.filename) output_filename = req.filename.replace(".fbx", ".zepeto") local_zepeto = os.path.join(PROJECT_PATH, "output", output_filename) try: # 1. DOWNLOAD DARI CLOUDINARY print(f"📥 [Cloudinary] Downloading: {req.filename}") r = requests.get(req.file_url, stream=True) with open(local_fbx, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) # 2. JALANKAN UNITY (Konversi & Optimasi Poligon) print("⚙️ [Unity] Memulai konversi & optimasi...") # Perhatikan: Method C# harus menangani decimation/polygon reduction cmd = [ UNITY_PATH, "-batchmode", "-nographics", "-projectPath", PROJECT_PATH, "-executeMethod", "TestBuilder.ManualConvert", "-inputFile", req.filename, "-quit", "-logFile", "-" ] result = subprocess.run(cmd, capture_output=True, text=True) if not os.path.exists(local_zepeto): print(f"❌ [Unity] Gagal. Log: {result.stdout}") return # 3. UPLOAD KE ZEPETO STUDIO success, msg = upload_to_zepeto(local_zepeto, req.z_id, req.z_pw) if success: print(f"✅ [FINAL] {req.filename} Berhasil dikirim ke Zepeto!") else: print(f"❌ [FINAL] Gagal upload ke Zepeto Studio: {msg}") except Exception as e: print(f"❌ [CRITICAL] Pipeline Error: {e}") finally: # Cleanup if os.path.exists(local_fbx): os.remove(local_fbx) @app.post("/convert") async def api_convert(req: ConversionRequest, background_tasks: BackgroundTasks): background_tasks.add_task(process_pipeline, req) return {"status": "success", "message": "Proses konversi dan upload Zepeto dimulai di background"}