diff --git a/controller/cuda_grid_controller/dispatch.py b/controller/cuda_grid_controller/dispatch.py index 9409fa2..fe9a663 100644 --- a/controller/cuda_grid_controller/dispatch.py +++ b/controller/cuda_grid_controller/dispatch.py @@ -25,14 +25,28 @@ log = structlog.get_logger() def _serialize_overlay_to_zmq(overlay: Overlay) -> str: """Сериализовать overlay в одну строку для FFmpeg process_command. - Формат: `add_overlay ` - Filter-side (Phase 4b) парсит JSON и применяет. - - JSON используем потому что overlay имеет вложенные поля (style для graph - и т.п.); проще чем positional args. + Формат: ` = = ...` + String values URL-encoded (spaces → %20), filter-side decode'ит inline + в parse_overlay_args. Nested values (style и т.п.) skip'аются — Phase 4b + их не поддерживает. """ - payload = overlay.model_dump_json() - return f"{overlay.id} {overlay.type} {payload}" + from urllib.parse import quote + + parts = [overlay.id, overlay.type] + data = overlay.model_dump() + for key, value in data.items(): + if key in {"id", "type"}: + continue + if value is None: + continue + if isinstance(value, (dict, list)): + continue # Phase 4b skipped — Phase 5 для nested style + if isinstance(value, bool): + value = 1 if value else 0 + if isinstance(value, str): + value = quote(value, safe="") # encode spaces + всё кроме alnum + parts.append(f"{key}={value}") + return " ".join(parts) class CommandDispatcher: diff --git a/tools/smoke_test_overlays.sh b/tools/smoke_test_overlays.sh new file mode 100755 index 0000000..68688ea --- /dev/null +++ b/tools/smoke_test_overlays.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Phase 4b-5 — end-to-end smoke test для vf_cuda_grid overlays. +# +# Что делает: +# 1. Запускает FFmpeg pipeline с 4 lavfi (test pattern) → cuda_grid → null sink + zmq +# 2. Запускает cuda-grid-controller (если не запущен) +# 3. Через REST API создаёт rect/text/icon overlays +# 4. Проверяет логи ffmpeg + controller +# +# Требует: +# - ffmpeg binary с --enable-libcuframes --enable-libzmq --enable-libfreetype +# - GPU свободная (~2 GB VRAM свободно) +# - Controller deployed в /home/claude/projects/vf-cuda-grid/controller/ +# +# Использование: +# ./tools/smoke_test_overlays.sh + +set -euo pipefail + +FFMPEG_IMG="${FFMPEG_IMG:-ffmpeg-vf-cuda-grid:phase4b-icon}" +ZMQ_PORT="${ZMQ_PORT:-5555}" +HTTP_PORT="${HTTP_PORT:-8080}" +TEST_DIR="$(mktemp -d /tmp/cuda-grid-smoke-XXXX)" +LOG_FFMPEG="$TEST_DIR/ffmpeg.log" +LOG_CTRL="$TEST_DIR/controller.log" + +cleanup() { + echo "=== Cleanup ===" + [[ -n "${FFMPEG_PID:-}" ]] && kill -INT "$FFMPEG_PID" 2>/dev/null || true + [[ -n "${CTRL_PID:-}" ]] && kill -INT "$CTRL_PID" 2>/dev/null || true + wait 2>/dev/null || true + echo "logs: $TEST_DIR" +} +trap cleanup EXIT + +echo "=== 1. Запуск FFmpeg (4 colour inputs → cuda_grid → null) ===" +docker run --rm --gpus all --net=host -d \ + -e LD_LIBRARY_PATH=/usr/local/cuda/lib64:/opt/cuframes/lib \ + --name cuda-grid-smoke-ffmpeg \ + "$FFMPEG_IMG" \ + /opt/ffmpeg/bin/ffmpeg -hide_banner -loglevel info \ + -f lavfi -i "color=#404060:size=960x540:rate=10" \ + -f lavfi -i "color=#604040:size=960x540:rate=10" \ + -f lavfi -i "color=#406040:size=960x540:rate=10" \ + -f lavfi -i "color=#606040:size=960x540:rate=10" \ + -filter_complex "[0]format=nv12,hwupload_cuda[h0]; \ + [1]format=nv12,hwupload_cuda[h1]; \ + [2]format=nv12,hwupload_cuda[h2]; \ + [3]format=nv12,hwupload_cuda[h3]; \ + [h0][h1][h2][h3]cuda_grid=layout=quad:out_w=1920:out_h=1080@cg, \ + zmq=bind_address=tcp\\\\://127.0.0.1\\\\:${ZMQ_PORT}[v]" \ + -map "[v]" -c:v h264_nvenc -t 30 -f null - > "$LOG_FFMPEG" 2>&1 & +FFMPEG_PID=$! +sleep 3 +docker logs cuda-grid-smoke-ffmpeg | head -20 || true + +echo "" +echo "=== 2. Test add_overlay rect (red border) ===" +cat > "$TEST_DIR/cmd_rect.sh" < "$TEST_DIR/cmd_text.sh" <&1 | tail -40 || true + +echo "" +echo "Smoke test artifacts saved в $TEST_DIR" +echo "Manually inspect через:" +echo " docker logs cuda-grid-smoke-ffmpeg" +echo " docker exec -it cuda-grid-smoke-ffmpeg sh" +echo "" +echo "Press Ctrl-C to stop ffmpeg + cleanup..." +wait $FFMPEG_PID || true