Кнопка «Фон» в тулбаре комнаты и выбор фона в превью на входе: три готовые
сцены и свои картинки из профиля. Фон применяется процессором к самому
публикуемому треку (`LocalVideoTrack.setProcessor`), а НЕ пересозданием
`RoomOptions` — ссылка на них обязана оставаться стабильной, иначе
`LiveKitRoom` переподключается к комнате.
Фича только для ДЕСКТОПА, и «десктоп» определяется по возможностям устройства
(`pointer: fine` + `hover: hover` + `maxTouchPoints`), а НЕ по ширине окна:
узкое окно на десктопе — всё ещё десктоп, а широкий планшет — всё ещё планшет,
который сегментация греет. На мобильном кнопки нет вовсе, а не задизейбленной.
Ассеты сегментации отдаются СО СВОЕГО домена: библиотека по умолчанию тянет
wasm с jsdelivr, а модель с storage.googleapis.com, и в закрытом контуре фича
молча не работала бы. Модель (Apache 2.0, см. NOTICE.txt) лежит в репозитории,
wasm-рантайм (~19 МБ) копируется из node_modules плагином сборки. Сама
библиотека и модель грузятся ЛЕНИВО — только когда фон реально включают, вход
в конференцию не стал медленнее.
Три дефолтные сцены — собственные векторные рисунки (`design/backgrounds/`),
а не фотографии из интернета: у нарисованной сцены нет чужой лицензии, а
продукт расходится по инсталляциям, и проверять права на каждую копию некому.
Свои картинки — в профиле, до 10 штук, с уменьшением до 1280px и переводом в
WebP прямо в браузере перед отправкой. Удаление применённого сейчас фона
сбрасывает выбор на «без фона»: хранится ключ записи, а не URL картинки.
Смена камеры фон не теряет (`restartTrack` перезапускает процессор сам),
выключение и включение камеры — навешивает его на новый трек заново.
⚠️ Прокси dev-сервера для `/media/` — обязательно со слэшем: ключ `/media`
Vite матчит префиксом и перехватывает заодно `/mediapipe/...`, из-за чего
модель получала 404 и фон молча не включался.
74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
||
"""Запекает векторные сцены дефолтных фонов (`*.svg`) в WebP для фронтенда.
|
||
|
||
Исходники сцен — собственные рисунки VidConf (см. README рядом), поэтому
|
||
лежат в репозитории вместе с результатом: чужих фотографий с их лицензиями в
|
||
продукте нет.
|
||
|
||
Запуск (нужен установленный Google Chrome и Pillow):
|
||
|
||
python3 design/backgrounds/render.py
|
||
|
||
Результат — `frontend/public/backgrounds/*.webp`, 1280×720. Chrome нужен
|
||
только для растеризации SVG (rsvg/ImageMagick на машине может не быть),
|
||
Pillow — для перевода PNG в WebP: браузерный скриншот WebP не отдаёт.
|
||
"""
|
||
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
from pathlib import Path
|
||
|
||
from PIL import Image
|
||
|
||
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||
WIDTH, HEIGHT = 1280, 720
|
||
# 82 — визуально неотличимо от исходника на плавных градиентах сцены, а вес
|
||
# втрое меньше, чем при 95. Фон грузится по сети при каждом первом показе.
|
||
WEBP_QUALITY = 82
|
||
|
||
SOURCE_DIR = Path(__file__).parent
|
||
TARGET_DIR = SOURCE_DIR.parents[1] / "frontend" / "public" / "backgrounds"
|
||
|
||
|
||
def render(svg_path: Path, out_path: Path, workdir: Path) -> None:
|
||
"""Растеризовать одну сцену и сохранить её в WebP."""
|
||
png_path = workdir / f"{svg_path.stem}.png"
|
||
subprocess.run(
|
||
[
|
||
CHROME,
|
||
"--headless",
|
||
"--disable-gpu",
|
||
"--hide-scrollbars",
|
||
"--default-background-color=00000000",
|
||
f"--screenshot={png_path}",
|
||
f"--window-size={WIDTH},{HEIGHT}",
|
||
svg_path.resolve().as_uri(),
|
||
],
|
||
check=True,
|
||
capture_output=True,
|
||
)
|
||
with Image.open(png_path) as image:
|
||
image.convert("RGB").save(out_path, "WEBP", quality=WEBP_QUALITY, method=6)
|
||
|
||
|
||
def main() -> int:
|
||
if not Path(CHROME).exists():
|
||
print(f"не найден Chrome: {CHROME}", file=sys.stderr)
|
||
return 1
|
||
TARGET_DIR.mkdir(parents=True, exist_ok=True)
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
workdir = Path(tmp)
|
||
for svg_path in sorted(SOURCE_DIR.glob("*.svg")):
|
||
out_path = TARGET_DIR / f"{svg_path.stem}.webp"
|
||
render(svg_path, out_path, workdir)
|
||
print(
|
||
f"{svg_path.name} → {out_path.relative_to(SOURCE_DIR.parents[1])}"
|
||
f" ({out_path.stat().st_size // 1024} КБ)"
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|