Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,24 +1,24 @@
|
|
| 1 |
-
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from fastapi import FastAPI, BackgroundTasks
|
| 3 |
from fastapi.responses import FileResponse, JSONResponse
|
| 4 |
from pydantic import BaseModel
|
| 5 |
-
from datetime import datetime
|
| 6 |
|
|
|
|
| 7 |
os.chdir("/tmp")
|
| 8 |
os.environ["HOME"] = "/tmp"
|
| 9 |
os.environ["UNITY_PATH"] = "/opt/unity/Editor/Unity"
|
| 10 |
BASE_PROJECT_PATH = "/app/UnityProject"
|
| 11 |
-
|
| 12 |
-
R2_CONFIG = {
|
| 13 |
-
"account_id": "31f2970a3442da4e6f17f79064897226",
|
| 14 |
-
"access_key": "44613d17ec50941707a7ddd9b17944bc",
|
| 15 |
-
"secret_key": "50185c21ff5e4f53e412e2013305b6ab24b7eda16cc0d3dc66593eed64aa99a2",
|
| 16 |
-
"bucket_name": "zepeto-builds"
|
| 17 |
-
}
|
| 18 |
|
| 19 |
app = FastAPI()
|
| 20 |
|
| 21 |
-
# Model disesuaikan dengan payload Worker yang kamu kirim tadi
|
| 22 |
class ConversionRequest(BaseModel):
|
| 23 |
transaction_id: str
|
| 24 |
file_url: str
|
|
@@ -26,82 +26,146 @@ class ConversionRequest(BaseModel):
|
|
| 26 |
z_id: str
|
| 27 |
z_pw: str
|
| 28 |
|
| 29 |
-
|
| 30 |
-
# Logika zipping asli yang kamu berikan (Berhasil)
|
| 31 |
-
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as z:
|
| 32 |
-
for root, _, files in os.walk(source_dir):
|
| 33 |
-
for file in files:
|
| 34 |
-
if file.endswith(".manifest"): continue
|
| 35 |
-
full_path = os.path.join(root, file)
|
| 36 |
-
rel_path = os.path.relpath(full_path, source_dir)
|
| 37 |
-
z.write(full_path, rel_path)
|
| 38 |
|
| 39 |
def setup_license():
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
target_dir = "/tmp/.local/share/unity3d/Unity"
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
@app.get("/lisensi")
|
| 48 |
-
async def
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
def process_pipeline(req: ConversionRequest):
|
| 56 |
setup_license()
|
| 57 |
|
| 58 |
-
#
|
| 59 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 60 |
-
unique_filename = f"{req.filename}_{timestamp}"
|
| 61 |
-
|
| 62 |
-
# Gunakan transaction_id untuk folder sesi agar tidak tabrakan
|
| 63 |
SESSION_PATH = f"/tmp/Project_{req.transaction_id}"
|
| 64 |
extract_tmp = f"/tmp/extract_{req.transaction_id}"
|
| 65 |
zip_tmp = f"/tmp/{req.transaction_id}.zip"
|
| 66 |
-
final_zepeto = f"/tmp/{unique_filename}.zepeto"
|
| 67 |
|
| 68 |
try:
|
| 69 |
-
#
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
| 73 |
os.makedirs(extract_tmp, exist_ok=True)
|
| 74 |
-
with zipfile.ZipFile(zip_tmp, 'r') as z:
|
|
|
|
| 75 |
|
| 76 |
-
#
|
|
|
|
| 77 |
shutil.copytree(BASE_PROJECT_PATH, SESSION_PATH, ignore=shutil.ignore_patterns("Library", "Temp"))
|
| 78 |
target_in = os.path.join(SESSION_PATH, "Assets/InputRaw")
|
| 79 |
os.makedirs(target_in, exist_ok=True)
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
|
|
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
|
|
|
|
|
|
|
|
|
| 94 |
finally:
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
| 97 |
if os.path.isdir(p): shutil.rmtree(p)
|
| 98 |
else: os.remove(p)
|
| 99 |
|
| 100 |
-
@app.post("/convert")
|
| 101 |
-
async def api_convert(req: ConversionRequest, background_tasks: BackgroundTasks):
|
| 102 |
-
background_tasks.add_task(process_pipeline, req)
|
| 103 |
-
return {"status": "processing", "transaction_id": req.transaction_id}
|
| 104 |
-
|
| 105 |
if __name__ == "__main__":
|
| 106 |
import uvicorn
|
|
|
|
| 107 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import subprocess
|
| 4 |
+
import requests
|
| 5 |
+
import zipfile
|
| 6 |
+
import uuid
|
| 7 |
+
import glob
|
| 8 |
+
import datetime
|
| 9 |
from fastapi import FastAPI, BackgroundTasks
|
| 10 |
from fastapi.responses import FileResponse, JSONResponse
|
| 11 |
from pydantic import BaseModel
|
|
|
|
| 12 |
|
| 13 |
+
# --- KONFIGURASI PATH ---
|
| 14 |
os.chdir("/tmp")
|
| 15 |
os.environ["HOME"] = "/tmp"
|
| 16 |
os.environ["UNITY_PATH"] = "/opt/unity/Editor/Unity"
|
| 17 |
BASE_PROJECT_PATH = "/app/UnityProject"
|
| 18 |
+
LICENSE_SOURCE = "/app/Unity_lic.ulf"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
app = FastAPI()
|
| 21 |
|
|
|
|
| 22 |
class ConversionRequest(BaseModel):
|
| 23 |
transaction_id: str
|
| 24 |
file_url: str
|
|
|
|
| 26 |
z_id: str
|
| 27 |
z_pw: str
|
| 28 |
|
| 29 |
+
# --- FUNGSI PEMBANTU ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
def setup_license():
|
| 32 |
+
"""Menyiapkan lisensi dan mengecek waktu server untuk debug."""
|
| 33 |
+
# 1. Cek Waktu Server (Penting untuk mengatasi 'Time validation failed')
|
| 34 |
+
now = datetime.datetime.now()
|
| 35 |
+
print(f"⏰ [DEBUG] Waktu Server Sekarang: {now.strftime('%Y-%m-%d %H:%M:%S')}", flush=True)
|
| 36 |
+
|
| 37 |
target_dir = "/tmp/.local/share/unity3d/Unity"
|
| 38 |
+
os.makedirs(target_dir, exist_ok=True)
|
| 39 |
+
|
| 40 |
+
if os.path.exists(LICENSE_SOURCE):
|
| 41 |
+
# Bersihkan lisensi lama di folder internal jika ada
|
| 42 |
+
internal_license = os.path.join(target_dir, "Unity_lic.ulf")
|
| 43 |
+
if os.path.exists(internal_license):
|
| 44 |
+
os.remove(internal_license)
|
| 45 |
+
|
| 46 |
+
shutil.copy2(LICENSE_SOURCE, internal_license)
|
| 47 |
+
print("✅ [SYSTEM] Lisensi disalin ke folder internal Unity.", flush=True)
|
| 48 |
+
else:
|
| 49 |
+
print("⚠️ [WARNING] File Unity_lic.ulf tidak ditemukan di /app/", flush=True)
|
| 50 |
+
|
| 51 |
+
def run_unity_command(project_path, method, extra_args=None):
|
| 52 |
+
"""Menjalankan Unity dengan flag headless dan anti-stuck."""
|
| 53 |
+
cmd = [
|
| 54 |
+
os.environ["UNITY_PATH"],
|
| 55 |
+
"-batchmode",
|
| 56 |
+
"-nographics",
|
| 57 |
+
"-quit",
|
| 58 |
+
"-no-webservices", # Matikan pencarian layanan cloud
|
| 59 |
+
"-no-graphics", # Proteksi tambahan mode headless
|
| 60 |
+
"-silent-crashes", # Jangan munculkan popup error
|
| 61 |
+
"-manualLicenseFile", LICENSE_SOURCE, # Paksa baca file lisensi fisik
|
| 62 |
+
"-projectPath", project_path,
|
| 63 |
+
"-executeMethod", method,
|
| 64 |
+
"-logFile", "-" # Log keluar secara real-time ke console
|
| 65 |
+
]
|
| 66 |
+
if extra_args:
|
| 67 |
+
cmd.extend(extra_args)
|
| 68 |
+
|
| 69 |
+
# Jalankan perintah dan tunggu sampai selesai
|
| 70 |
+
return subprocess.run(cmd, capture_output=False)
|
| 71 |
+
|
| 72 |
+
# --- ENDPOINTS ---
|
| 73 |
|
| 74 |
@app.get("/lisensi")
|
| 75 |
+
async def generate_new_alf():
|
| 76 |
+
"""Endpoint untuk generate file .alf baru jika lisensi lama Expired."""
|
| 77 |
+
# Bersihkan sisa file .alf sebelumnya
|
| 78 |
+
for f in glob.glob("/tmp/*.alf"):
|
| 79 |
+
try: os.remove(f)
|
| 80 |
+
except: pass
|
| 81 |
+
|
| 82 |
+
print("🛠️ [LICENSE] Meminta file aktivasi (.alf) baru dari Unity...", flush=True)
|
| 83 |
+
cmd = [
|
| 84 |
+
os.environ["UNITY_PATH"],
|
| 85 |
+
"-batchmode", "-nographics",
|
| 86 |
+
"-createManualActivationFile",
|
| 87 |
+
"-logfile", "/dev/stdout"
|
| 88 |
+
]
|
| 89 |
+
subprocess.run(cmd, cwd="/tmp", timeout=60)
|
| 90 |
+
|
| 91 |
+
found_alf = glob.glob("/tmp/*.alf")
|
| 92 |
+
if found_alf:
|
| 93 |
+
print(f"✅ [LICENSE] File .alf berhasil dibuat: {found_alf[0]}", flush=True)
|
| 94 |
+
return FileResponse(path=found_alf[0], filename=os.path.basename(found_alf[0]))
|
| 95 |
+
|
| 96 |
+
return JSONResponse(status_code=500, content={"error": "Gagal generate .alf. Cek tab Logs."})
|
| 97 |
+
|
| 98 |
+
@app.post("/convert")
|
| 99 |
+
async def api_convert(req: ConversionRequest, background_tasks: BackgroundTasks):
|
| 100 |
+
"""Endpoint utama untuk memicu pipeline convert dan upload."""
|
| 101 |
+
background_tasks.add_task(process_pipeline, req)
|
| 102 |
+
return {"status": "processing", "transaction_id": req.transaction_id}
|
| 103 |
+
|
| 104 |
+
# --- INTI PROSES (PIPELINE) ---
|
| 105 |
|
| 106 |
def process_pipeline(req: ConversionRequest):
|
| 107 |
setup_license()
|
| 108 |
|
| 109 |
+
# Folder unik untuk setiap transaksi
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
SESSION_PATH = f"/tmp/Project_{req.transaction_id}"
|
| 111 |
extract_tmp = f"/tmp/extract_{req.transaction_id}"
|
| 112 |
zip_tmp = f"/tmp/{req.transaction_id}.zip"
|
|
|
|
| 113 |
|
| 114 |
try:
|
| 115 |
+
# 1. DOWNLOAD & PREPARE
|
| 116 |
+
print(f"📥 [START] Mengambil aset: {req.transaction_id}", flush=True)
|
| 117 |
+
r = requests.get(req.file_url, timeout=30)
|
| 118 |
+
with open(zip_tmp, 'wb') as f:
|
| 119 |
+
f.write(r.content)
|
| 120 |
|
| 121 |
os.makedirs(extract_tmp, exist_ok=True)
|
| 122 |
+
with zipfile.ZipFile(zip_tmp, 'r') as z:
|
| 123 |
+
z.extractall(extract_tmp)
|
| 124 |
|
| 125 |
+
# 2. SETUP PROJECT WORKSPACE
|
| 126 |
+
# Salin UnityProject dasar ke folder temporer agar project asli tetap bersih
|
| 127 |
shutil.copytree(BASE_PROJECT_PATH, SESSION_PATH, ignore=shutil.ignore_patterns("Library", "Temp"))
|
| 128 |
target_in = os.path.join(SESSION_PATH, "Assets/InputRaw")
|
| 129 |
os.makedirs(target_in, exist_ok=True)
|
| 130 |
+
|
| 131 |
+
# Pindahkan file hasil ekstrak ke folder input project
|
| 132 |
+
for item in os.listdir(extract_tmp):
|
| 133 |
+
shutil.move(os.path.join(extract_tmp, item), os.path.join(target_in, item))
|
| 134 |
|
| 135 |
+
# 3. TAHAP 1: PENYEDERHANAAN MESH
|
| 136 |
+
print(f"🛠️ [STEP 1] Menjalankan TestBuilder.SimplifyOnly...", flush=True)
|
| 137 |
+
step1 = run_unity_command(SESSION_PATH, "TestBuilder.SimplifyOnly")
|
| 138 |
|
| 139 |
+
if step1.returncode != 0:
|
| 140 |
+
print(f"❌ [FAILED] Step 1 Gagal (Exit Code: {step1.returncode}). Cek Logs di atas.", flush=True)
|
| 141 |
+
return
|
| 142 |
+
|
| 143 |
+
# 4. TAHAP 2: UPLOAD KE ZEPETO
|
| 144 |
+
print(f"🚀 [STEP 2] Menjalankan Zepeto CLI Upload...", flush=True)
|
| 145 |
+
upload_args = [
|
| 146 |
+
"-id", req.z_id,
|
| 147 |
+
"-password", req.z_pw,
|
| 148 |
+
"-upload"
|
| 149 |
+
]
|
| 150 |
+
step2 = run_unity_command(SESSION_PATH, "ZepetoPackageCliBuilder.BuildWithArgs", upload_args)
|
| 151 |
+
|
| 152 |
+
if step2.returncode == 0:
|
| 153 |
+
print(f"✅ [SUCCESS] Item {req.transaction_id} telah terunggah ke ZEPETO Studio.", flush=True)
|
| 154 |
+
else:
|
| 155 |
+
print(f"❌ [FAILED] Step 2 Gagal (Exit Code: {step2.returncode}).", flush=True)
|
| 156 |
|
| 157 |
+
except Exception as e:
|
| 158 |
+
print(f"🔥 [CRITICAL ERROR] {str(e)}", flush=True)
|
| 159 |
+
|
| 160 |
finally:
|
| 161 |
+
# PEMBERSIHAN (Wajib agar disk tidak penuh)
|
| 162 |
+
print(f"🧹 [CLEANUP] Menghapus folder sesi {req.transaction_id}...", flush=True)
|
| 163 |
+
for p in [SESSION_PATH, extract_tmp, zip_tmp]:
|
| 164 |
+
if os.path.exists(p):
|
| 165 |
if os.path.isdir(p): shutil.rmtree(p)
|
| 166 |
else: os.remove(p)
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
if __name__ == "__main__":
|
| 169 |
import uvicorn
|
| 170 |
+
# Jalankan server FastAPI
|
| 171 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|