from fastapi import FastAPI, Query from fastapi.responses import FileResponse, JSONResponse from playwright.async_api import async_playwright import os, uuid, asyncio, shutil, stat app = FastAPI( title="Brat-Api Farel", description="Free BRAT Image & Video Generator API", version="1.0.0", ) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def ensure_dir(path: str): os.makedirs(path, exist_ok=True) os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) return path OUTPUT_DIR = ensure_dir(os.path.join(BASE_DIR, "output")) TMP_DIR = ensure_dir(os.path.join(BASE_DIR, "tmp")) async def delete_later(path: str, delay: int = 300): await asyncio.sleep(delay) if os.path.exists(path): os.remove(path) # ========================= # ROOT → index.html # ========================= @app.get("/") async def root(): html = os.path.join(BASE_DIR, "index.html") if not os.path.exists(html): return JSONResponse(status_code=404, content={"error": "index.html not found"}) return FileResponse(html, media_type="text/html") # ========================= # BRAT IMAGE (INLINE VIEW) # ========================= @app.get("/api/brat") async def brat_image(text: str = Query(...)): text = text.strip() if not text: return JSONResponse(status_code=400, content={"error": "text is required"}) filepath = os.path.join(OUTPUT_DIR, f"brat-{uuid.uuid4().hex}.png") async with async_playwright() as p: browser = await p.chromium.launch(args=["--no-sandbox"]) page = await (await browser.new_context( viewport={"width": 1536, "height": 695} )).new_page() await page.goto("https://www.bratgenerator.com/", wait_until="domcontentloaded") try: await page.click("text=Accept", timeout=3000) except: pass await page.click("#toggleButtonWhite") await page.click("#textOverlay") await page.fill("#textInput", text) await asyncio.sleep(0.4) el = await page.query_selector("#textOverlay") box = await el.bounding_box() img = await page.screenshot( clip={"x": box["x"], "y": box["y"], "width": 500, "height": 440} ) with open(filepath, "wb") as f: f.write(img) await browser.close() asyncio.create_task(delete_later(filepath)) # ⬅️ INLINE (preview di browser) return FileResponse(filepath, media_type="image/png") # ========================= # BRAT VIDEO (INLINE VIEW) # ========================= @app.get("/api/bratvid") async def brat_video(text: str = Query(...)): text = text.strip() if not text: return JSONResponse(status_code=400, content={"error": "text is required"}) words = text.split() temp_dir = ensure_dir(os.path.join(TMP_DIR, uuid.uuid4().hex)) async with async_playwright() as p: browser = await p.chromium.launch(args=["--no-sandbox"]) page = await (await browser.new_context( viewport={"width": 1536, "height": 695} )).new_page() await page.goto("https://www.bratgenerator.com/", wait_until="domcontentloaded") try: await page.click("text=Accept", timeout=3000) except: pass await page.click("#toggleButtonWhite") await page.click("#textOverlay") for i in range(len(words)): await page.fill("#textInput", " ".join(words[:i+1])) await asyncio.sleep(0.2) el = await page.query_selector("#textOverlay") box = await el.bounding_box() img = await page.screenshot( clip={"x": box["x"], "y": box["y"], "width": 500, "height": 440} ) with open(os.path.join(temp_dir, f"frame{i:03d}.png"), "wb") as f: f.write(img) await browser.close() output = os.path.join(OUTPUT_DIR, f"bratvid-{uuid.uuid4().hex}.mp4") await asyncio.create_subprocess_exec( "ffmpeg", "-y", "-framerate", "1.4", "-i", os.path.join(temp_dir, "frame%03d.png"), "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2,fps=30", "-pix_fmt", "yuv420p", output, stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL ) shutil.rmtree(temp_dir, ignore_errors=True) asyncio.create_task(delete_later(output)) # ⬅️ INLINE VIDEO (play langsung) return FileResponse(output, media_type="video/mp4")