import os import shutil import subprocess import requests import zipfile import glob import uuid import boto3 from fastapi import FastAPI, BackgroundTasks from pydantic import BaseModel # --- CONFIG --- os.chdir("/tmp") os.environ["HOME"] = "/tmp" os.environ["UNITY_PATH"] = "/opt/unity/Editor/Unity" BASE_PROJECT_PATH = "/app/UnityProject" R2_CONFIG = { "account_id": "31f2970a3442da4e6f17f79064897226", "access_key": "44613d17ec50941707a7ddd9b17944bc", "secret_key": "50185c21ff5e4f53e412e2013305b6ab24b7eda16cc0d3dc66593eed64aa99a2", "bucket_name": "zepeto-builds" } app = FastAPI() class ConversionRequest(BaseModel): file_url: str filename: str def upload_to_r2(local_path, r2_filename): try: s3 = boto3.client('s3', endpoint_url=f"https://{R2_CONFIG['account_id']}.r2.cloudflarestorage.com", aws_access_key_id=R2_CONFIG['access_key'], aws_secret_access_key=R2_CONFIG['secret_key']) s3.upload_file(local_path, R2_CONFIG['bucket_name'], f"packed/{r2_filename}") print(f"✅ [R2] Upload Sukses: packed/{r2_filename}", flush=True) except Exception as e: print(f"❌ [R2] Gagal: {e}", flush=True) def make_zepeto_archive(source_dir, output_file): """Membungkus folder InputRaw menjadi file .zepeto (ZIP)""" with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as z: for root, dirs, files in os.walk(source_dir): for file in files: # Jangan masukkan file manifest root Unity atau file log if file.endswith((".manifest", ".txt")): continue file_path = os.path.join(root, file) z.write(file_path, os.path.relpath(file_path, source_dir)) def process_pipeline(req: ConversionRequest): session_id = str(uuid.uuid4())[:8] SESSION_PATH = f"/tmp/Project_{session_id}" extract_tmp = f"/tmp/extract_{session_id}" final_zepeto = f"/tmp/{req.filename}.zepeto" try: # 1. Download & Extract Repository r = requests.get(req.file_url) zip_tmp = f"/tmp/{session_id}.zip" with open(zip_tmp, 'wb') as f: f.write(r.content) os.makedirs(extract_tmp) with zipfile.ZipFile(zip_tmp, 'r') as z: z.extractall(extract_tmp) # 2. Setup Unity Project & Assets shutil.copytree(BASE_PROJECT_PATH, SESSION_PATH, ignore=shutil.ignore_patterns("Library", "Temp")) target_input = os.path.join(SESSION_PATH, "Assets", "InputRaw") os.makedirs(target_input, exist_ok=True) # Pindahkan SEMUA file asli (PNG, Meta, Prefab) ke InputRaw for item in os.listdir(extract_tmp): s = os.path.join(extract_tmp, item) d = os.path.join(target_input, item) if os.path.isdir(s): shutil.copytree(s, d) else: shutil.copy2(s, d) # 3. Jalankan Unity (Rimpang Poligon & Build Binary Platform) print(f"🚀 [UNITY] Merampingkan Mesh & Membangun Platform...", flush=True) subprocess.run([os.environ["UNITY_PATH"], "-batchmode", "-nographics", "-quit", "-projectPath", SESSION_PATH, "-executeMethod", "TestBuilder.ManualConvert", "-logFile", "/dev/stdout"], check=False) # 4. PACKING (Membungkus folder InputRaw yang sudah lengkap menjadi .zepeto) print(f"📦 [PACKING] Membuat archive {req.filename}.zepeto...", flush=True) make_zepeto_archive(target_input, final_zepeto) # 5. UPLOAD HASIL KE R2 if os.path.exists(final_zepeto): upload_to_r2(final_zepeto, f"{req.filename}.zepeto") else: print("❌ [FAIL] Gagal membuat file .zepeto", flush=True) finally: for p in [SESSION_PATH, extract_tmp, zip_tmp, final_zepeto]: if os.path.exists(p): shutil.rmtree(p) if os.path.isdir(p) else os.remove(p) @app.post("/convert") async def api_convert(req: ConversionRequest, background_tasks: BackgroundTasks): background_tasks.add_task(process_pipeline, req) return {"status": "success"}