import os import shutil import subprocess import requests import zipfile import uuid import glob import datetime from fastapi import FastAPI, BackgroundTasks from fastapi.responses import FileResponse, JSONResponse from pydantic import BaseModel # --- KONFIGURASI PATH --- os.chdir("/tmp") os.environ["HOME"] = "/tmp" os.environ["UNITY_PATH"] = "/opt/unity/Editor/Unity" BASE_PROJECT_PATH = "/app/UnityProject" LICENSE_SOURCE = "/app/Unity_lic.ulf" app = FastAPI() class ConversionRequest(BaseModel): transaction_id: str file_url: str filename: str z_id: str z_pw: str # --- FUNGSI PEMBANTU --- def setup_license(): """Menyiapkan lisensi dan mengecek waktu server untuk debug.""" # 1. Cek Waktu Server (Penting untuk mengatasi 'Time validation failed') now = datetime.datetime.now() print(f"โฐ [DEBUG] Waktu Server Sekarang: {now.strftime('%Y-%m-%d %H:%M:%S')}", flush=True) target_dir = "/tmp/.local/share/unity3d/Unity" os.makedirs(target_dir, exist_ok=True) if os.path.exists(LICENSE_SOURCE): # Bersihkan lisensi lama di folder internal jika ada internal_license = os.path.join(target_dir, "Unity_lic.ulf") if os.path.exists(internal_license): os.remove(internal_license) shutil.copy2(LICENSE_SOURCE, internal_license) print("โœ… [SYSTEM] Lisensi disalin ke folder internal Unity.", flush=True) else: print("โš ๏ธ [WARNING] File Unity_lic.ulf tidak ditemukan di /app/", flush=True) def run_unity_command(project_path, method, extra_args=None): """Menjalankan Unity dengan flag headless dan anti-stuck.""" cmd = [ os.environ["UNITY_PATH"], "-batchmode", "-nographics", "-quit", "-no-webservices", # Matikan pencarian layanan cloud "-no-graphics", # Proteksi tambahan mode headless "-silent-crashes", # Jangan munculkan popup error "-manualLicenseFile", LICENSE_SOURCE, # Paksa baca file lisensi fisik "-projectPath", project_path, "-executeMethod", method, "-logFile", "-" # Log keluar secara real-time ke console ] if extra_args: cmd.extend(extra_args) # Jalankan perintah dan tunggu sampai selesai return subprocess.run(cmd, capture_output=False) # --- ENDPOINTS --- @app.get("/lisensi") async def generate_new_alf(): """Endpoint untuk generate file .alf baru jika lisensi lama Expired.""" # Bersihkan sisa file .alf sebelumnya for f in glob.glob("/tmp/*.alf"): try: os.remove(f) except: pass print("๐Ÿ› ๏ธ [LICENSE] Meminta file aktivasi (.alf) baru dari Unity...", flush=True) cmd = [ os.environ["UNITY_PATH"], "-batchmode", "-nographics", "-createManualActivationFile", "-logfile", "/dev/stdout" ] subprocess.run(cmd, cwd="/tmp", timeout=60) found_alf = glob.glob("/tmp/*.alf") if found_alf: print(f"โœ… [LICENSE] File .alf berhasil dibuat: {found_alf[0]}", flush=True) return FileResponse(path=found_alf[0], filename=os.path.basename(found_alf[0])) return JSONResponse(status_code=500, content={"error": "Gagal generate .alf. Cek tab Logs."}) @app.post("/convert") async def api_convert(req: ConversionRequest, background_tasks: BackgroundTasks): """Endpoint utama untuk memicu pipeline convert dan upload.""" background_tasks.add_task(process_pipeline, req) return {"status": "processing", "transaction_id": req.transaction_id} # --- INTI PROSES (PIPELINE) --- def process_pipeline(req: ConversionRequest): setup_license() # Folder unik untuk setiap transaksi SESSION_PATH = f"/tmp/Project_{req.transaction_id}" extract_tmp = f"/tmp/extract_{req.transaction_id}" zip_tmp = f"/tmp/{req.transaction_id}.zip" try: # 1. DOWNLOAD & PREPARE print(f"๐Ÿ“ฅ [START] Mengambil aset: {req.transaction_id}", flush=True) r = requests.get(req.file_url, timeout=30) with open(zip_tmp, 'wb') as f: f.write(r.content) os.makedirs(extract_tmp, exist_ok=True) with zipfile.ZipFile(zip_tmp, 'r') as z: z.extractall(extract_tmp) # 2. SETUP PROJECT WORKSPACE # Salin UnityProject dasar ke folder temporer agar project asli tetap bersih shutil.copytree(BASE_PROJECT_PATH, SESSION_PATH, ignore=shutil.ignore_patterns("Library", "Temp")) target_in = os.path.join(SESSION_PATH, "Assets/InputRaw") os.makedirs(target_in, exist_ok=True) # Pindahkan file hasil ekstrak ke folder input project for item in os.listdir(extract_tmp): shutil.move(os.path.join(extract_tmp, item), os.path.join(target_in, item)) # 3. TAHAP 1: PENYEDERHANAAN MESH print(f"๐Ÿ› ๏ธ [STEP 1] Menjalankan TestBuilder.SimplifyOnly...", flush=True) step1 = run_unity_command(SESSION_PATH, "TestBuilder.SimplifyOnly") if step1.returncode != 0: print(f"โŒ [FAILED] Step 1 Gagal (Exit Code: {step1.returncode}). Cek Logs di atas.", flush=True) return # 4. TAHAP 2: UPLOAD KE ZEPETO print(f"๐Ÿš€ [STEP 2] Menjalankan Zepeto CLI Upload...", flush=True) upload_args = [ "-id", req.z_id, "-password", req.z_pw, "-upload" ] step2 = run_unity_command(SESSION_PATH, "ZepetoPackageCliBuilder.BuildWithArgs", upload_args) if step2.returncode == 0: print(f"โœ… [SUCCESS] Item {req.transaction_id} telah terunggah ke ZEPETO Studio.", flush=True) else: print(f"โŒ [FAILED] Step 2 Gagal (Exit Code: {step2.returncode}).", flush=True) except Exception as e: print(f"๐Ÿ”ฅ [CRITICAL ERROR] {str(e)}", flush=True) finally: # PEMBERSIHAN (Wajib agar disk tidak penuh) print(f"๐Ÿงน [CLEANUP] Menghapus folder sesi {req.transaction_id}...", flush=True) for p in [SESSION_PATH, extract_tmp, zip_tmp]: if os.path.exists(p): if os.path.isdir(p): shutil.rmtree(p) else: os.remove(p) if __name__ == "__main__": import uvicorn # Jalankan server FastAPI uvicorn.run(app, host="0.0.0.0", port=7860)