Spaces:
Paused
Paused
| 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 | |
| # ========================= | |
| 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) | |
| # ========================= | |
| 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) | |
| # ========================= | |
| 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") |