meme / app.py
donotthink's picture
Upload app.py with huggingface_hub
66bf9b9 verified
Raw History Blame Contribute Delete
5.11 kB
import io
import os
import shutil
import subprocess
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
# ---- 自动拉取 Xet 指针文件 ----
def _is_valid_image(fpath):
try:
with open(fpath, "rb") as f:
head = f.read(16)
if len(head) < 4:
return False
if head[:8] == b"\x89PNG\r\n\x1a\n":
return True
if head[:3] == b"\xff\xd8\xff":
return True
if head[:6] in (b"GIF87a", b"GIF89a"):
return True
if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
return True
if head[:2] == b"BM":
return True
return False
except Exception:
return False
def _is_valid_image_bytes(data):
if len(data) < 4:
return False
if data[:8] == b"\x89PNG\r\n\x1a\n":
return True
if data[:3] == b"\xff\xd8\xff":
return True
if data[:6] in (b"GIF87a", b"GIF89a"):
return True
if data[:4] == b"RIFF" and data[8:12] == b"WEBP":
return True
if data[:2] == b"BM":
return True
return False
def _download_one(urls, fpath):
"""尝试从多个源下载,成功一个即返回。"""
if isinstance(urls, str):
urls = [urls]
for url in urls:
for attempt in range(3):
try:
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=120) as resp:
data = resp.read()
if data and _is_valid_image_bytes(data):
with open(fpath, "wb") as f:
f.write(data)
return True
except Exception:
if attempt < 2:
import time
time.sleep(1)
return False
def _materialize_xet():
repo_id = "donotthink/meme"
hf_base = f"https://huggingface.co/spaces/{repo_id}/resolve/main"
gh_base = "https://raw.githubusercontent.com/Memes02/meme-generator/main"
exts = (".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp")
need_download = []
total = 0
for root, _dirs, files in os.walk("meme_generator"):
for fname in files:
if not fname.lower().endswith(exts):
continue
total += 1
fpath = os.path.join(root, fname)
if not _is_valid_image(fpath):
need_download.append(fpath)
print(f"[xet] scanned {total} images, {len(need_download)} need download", flush=True)
if not need_download:
return
count = 0
failed_files = []
with ThreadPoolExecutor(max_workers=6) as executor:
futures = {}
for fpath in need_download:
rel = os.path.relpath(fpath, ".").replace(os.sep, "/")
# 先试 HF,失败再试 GitHub
urls = [f"{hf_base}/{rel}", f"{gh_base}/{rel}"]
futures[executor.submit(_download_one, urls, fpath)] = fpath
for fut in as_completed(futures):
fpath = futures[fut]
if fut.result():
count += 1
else:
failed_files.append(fpath)
if (count + len(failed_files)) % 100 == 0:
print(f"[xet] progress {count + len(failed_files)}/{len(need_download)}", flush=True)
print(f"[xet] materialized {count} files, {len(failed_files)} failures", flush=True)
if failed_files:
print(f"[xet] failed files ({len(failed_files)}):", flush=True)
for f in failed_files[:30]:
print(f" - {f}", flush=True)
if len(failed_files) > 30:
print(f" ... and {len(failed_files) - 30} more", flush=True)
_materialize_xet()
# ---- 注册字体 ----
_font_src = Path("resources/fonts")
_font_dst = Path.home() / ".fonts"
if _font_src.exists():
_font_dst.mkdir(parents=True, exist_ok=True)
for _f in _font_src.iterdir():
if _f.is_file():
shutil.copy2(_f, _font_dst / _f.name)
subprocess.run(["fc-cache", "-fv"], capture_output=True)
# ---- ZeroGPU 兼容层:必须有 @spaces.GPU 装饰的函数并实际调用,否则 ZeroGPU 会停止应用 ----
import spaces
import asyncio
@spaces.GPU
def _zero_gpu_warmup():
"""占位函数,让 ZeroGPU 检测到 GPU 函数调用。实际不使用 GPU。"""
return "warmup-ok"
# 启动前实际调用一次,触发 spaces 库的 GPU 初始化
try:
_r = _zero_gpu_warmup()
if asyncio.iscoroutine(_r):
asyncio.run(_r)
print("[zero-gpu] warmup done", flush=True)
except Exception as _e:
print(f"[zero-gpu] warmup skipped: {_e}", flush=True)
# ---- 启动 meme_generator 原生 FastAPI(无 Gradio,无 CSRF)----
import uvicorn
from meme_generator.app import app, register_routers
register_routers()
print("[meme] API server starting on 0.0.0.0:7860", flush=True)
print("[meme] endpoints: /memes/keys, /memes/{key}/, /memes/{key}/info, /docs", flush=True)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)