ChatCraft / backend /scripts /upscale_cover.py
gabraken's picture
feat: add new units/buildings/map assets, admin routes, and frontend build
dd96d2f
#!/usr/bin/env python3
"""
Upscale l'image cover (lobby) via Vertex AI Imagen 4.0 upscale.
Prérequis : GCP_PROJECT_ID et GCP_LOCATION dans .env, et `gcloud auth application-default login`
(ou GOOGLE_APPLICATION_CREDENTIALS).
Usage :
cd backend && python -m scripts.upscale_cover
cd backend && python -m scripts.upscale_cover --factor x2 --input ../frontend/src/cover.jpg --output ../frontend/src/cover.jpg
"""
from __future__ import annotations
import argparse
import base64
import subprocess
import sys
from pathlib import Path
_backend = Path(__file__).resolve().parent.parent
if str(_backend) not in sys.path:
sys.path.insert(0, str(_backend))
from config import GCP_PROJECT_ID, GCP_LOCATION
def get_access_token() -> str:
"""Token via gcloud (ADC)."""
out = subprocess.run(
["gcloud", "auth", "print-access-token"],
capture_output=True,
text=True,
check=False,
)
if out.returncode != 0:
raise RuntimeError(
"Échec gcloud auth. Lancez: gcloud auth application-default login"
)
return out.stdout.strip()
def upscale_image(
image_path: Path,
out_path: Path,
factor: str = "x4",
region: str | None = None,
project_id: str | None = None,
) -> None:
region = region or GCP_LOCATION
project_id = project_id or GCP_PROJECT_ID
if not project_id:
raise SystemExit("Définir GCP_PROJECT_ID dans .env")
data = image_path.read_bytes()
if len(data) > 10 * 1024 * 1024:
raise SystemExit("Image trop lourde (max 10 Mo)")
b64 = base64.standard_b64encode(data).decode("ascii")
token = get_access_token()
url = (
f"https://{region}-aiplatform.googleapis.com/v1/projects/{project_id}"
f"/locations/{region}/publishers/google/models/imagen-4.0-upscale-preview:predict"
)
import httpx
payload = {
"instances": [
{"prompt": "Upscale the image", "image": {"bytesBase64Encoded": b64}}
],
"parameters": {
"mode": "upscale",
"upscaleConfig": {"upscaleFactor": factor},
"outputOptions": {"mimeType": "image/png"},
},
}
resp = httpx.post(
url,
json=payload,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
timeout=120.0,
)
resp.raise_for_status()
body = resp.json()
preds = body.get("predictions") or []
if not preds or "bytesBase64Encoded" not in preds[0]:
raise SystemExit("Réponse Vertex sans image: " + str(body)[:500])
out_b64 = preds[0]["bytesBase64Encoded"]
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(base64.standard_b64decode(out_b64))
print(f"Upscale {factor} OK → {out_path} ({out_path.stat().st_size / 1024:.1f} Ko)")
def main() -> None:
parser = argparse.ArgumentParser(description="Upscale cover image via Vertex AI Imagen")
parser.add_argument(
"--input",
type=Path,
default=_backend.parent / "frontend" / "src" / "cover.jpg",
help="Image source",
)
parser.add_argument(
"--output",
type=Path,
default=None,
help="Image de sortie (défaut: --input)",
)
parser.add_argument(
"--factor",
choices=("x2", "x3", "x4"),
default="x4",
help="Facteur d'upscale (défaut: x4)",
)
args = parser.parse_args()
out = args.output or args.input
if not args.input.is_file():
raise SystemExit(f"Fichier introuvable: {args.input}")
upscale_image(args.input, out, factor=args.factor)
if __name__ == "__main__":
main()