Spaces:
Sleeping
Sleeping
| 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 --- | |
| 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."}) | |
| 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) |