Files
SHiNE-server/shine-UI/generate_telegram_emoji_previews.py
PixelandClaude Opus 4.8 266a74ef79 WIP: эмодзи-пикер (Telegram-набор) — чекпоинт незавершённой работы
- Эмодзи-пикер: js/components/emoji-picker.js + каталог telegram-emoji-*.js
  (smileys/people/animals_nature/food_drink/travel_places/activity/objects/
  symbols/flags) + 631 ассет в assets/emoji (7.2 МБ).
- Интеграция пикера: chat-view.js, app.js, index.html, styles/components.css.
- Дев-скрипты генерации превью + THIRD_PARTY_NOTICES.
- Работа НЕ завершена (WIP), сохранено как чекпоинт. С origin (+56) пока НЕ
  мержится — большой мерж будет после завершения, отдельным шагом.
- VERSION: client 1.2.263.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:28:45 +03:00

91 lines
3.2 KiB
Python

"""Generate local first-frame PNG previews for the Telegram emoji catalog."""
from __future__ import annotations
import argparse
import re
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from io import BytesIO
from pathlib import Path
from urllib.parse import quote
from urllib.request import Request, urlopen
from PIL import Image
ROOT = Path(__file__).resolve().parent
CATALOG_DIR = ROOT / "js" / "components"
OUTPUT_DIR = ROOT / "assets" / "emoji" / "telegram-previews"
ASSET_BASE_URL = "https://raw.githubusercontent.com/Tarikul-Islam-Anik/Telegram-Animated-Emojis/main/"
FILE_PATTERN = re.compile(r'"file"\s*:\s*"([^"]+\.webp)"', re.IGNORECASE)
def catalog_files() -> list[str]:
files: set[str] = set()
for path in sorted(CATALOG_DIR.glob("telegram-emoji-*.js")):
files.update(FILE_PATTERN.findall(path.read_text(encoding="utf-8")))
return sorted(files)
def preview_path(source_file: str) -> Path:
return OUTPUT_DIR / Path(source_file).with_suffix(".png")
def generate_one(source_file: str, force: bool) -> tuple[str, str]:
output = preview_path(source_file)
if output.exists() and not force:
return source_file, "cached"
url = ASSET_BASE_URL + quote(source_file, safe="/")
request = Request(url, headers={"User-Agent": "SHiNE emoji preview generator"})
with urlopen(request, timeout=30) as response:
payload = response.read()
with Image.open(BytesIO(payload)) as source:
source.seek(0)
frame = source.convert("RGBA")
frame.thumbnail((96, 96), Image.Resampling.LANCZOS)
canvas = Image.new("RGBA", (96, 96), (0, 0, 0, 0))
offset = ((96 - frame.width) // 2, (96 - frame.height) // 2)
canvas.alpha_composite(frame, offset)
output.parent.mkdir(parents=True, exist_ok=True)
canvas.save(output, format="PNG", optimize=True)
return source_file, "generated"
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--force", action="store_true", help="Regenerate existing previews")
parser.add_argument("--workers", type=int, default=8)
args = parser.parse_args()
files = catalog_files()
if not files:
print("No catalog files found", file=sys.stderr)
return 1
print(f"Generating {len(files)} previews into {OUTPUT_DIR}")
failures: list[tuple[str, str]] = []
generated = cached = 0
with ThreadPoolExecutor(max_workers=max(1, args.workers)) as executor:
futures = {executor.submit(generate_one, source_file, args.force): source_file for source_file in files}
for future in as_completed(futures):
source_file = futures[future]
try:
_, status = future.result()
except Exception as error: # noqa: BLE001 - report all asset failures together
failures.append((source_file, str(error)))
continue
generated += status == "generated"
cached += status == "cached"
print(f"Generated: {generated}; cached: {cached}; failed: {len(failures)}")
for source_file, error in failures:
print(f"FAILED {source_file}: {error}", file=sys.stderr)
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())