controller: switch wire format JSON → key=val URL-encoded (matches filter)
Filter использует sscanf("%s") который stops on whitespace — нужно
URL-encode string values (text="hello world" → text=hello%20world).
Filter inline decode'ит %xx.
Также:
tools/smoke_test_overlays.sh — integration test script (manual run
утром когда GPU свободна; сейчас прод-сервисы заняли всю VRAM)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -25,14 +25,28 @@ log = structlog.get_logger()
|
||||
def _serialize_overlay_to_zmq(overlay: Overlay) -> str:
|
||||
"""Сериализовать overlay в одну строку для FFmpeg process_command.
|
||||
|
||||
Формат: `add_overlay <id> <type> <json-base64-payload>`
|
||||
Filter-side (Phase 4b) парсит JSON и применяет.
|
||||
|
||||
JSON используем потому что overlay имеет вложенные поля (style для graph
|
||||
и т.п.); проще чем positional args.
|
||||
Формат: `<id> <type> <key>=<val> <key>=<val> ...`
|
||||
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:
|
||||
|
||||
Executable
+86
@@ -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" <<EOF
|
||||
echo "Parsed_cuda_grid_0 add_overlay r1 rect cell=0 x=0.05 y=0.05 w=0.9 h=0.9 r=255 g=0 b=0 thickness=8 opacity=255" | \
|
||||
docker exec -i cuda-grid-smoke-ffmpeg /opt/ffmpeg/bin/zmqsend -b tcp://127.0.0.1:${ZMQ_PORT}
|
||||
EOF
|
||||
chmod +x "$TEST_DIR/cmd_rect.sh"
|
||||
bash "$TEST_DIR/cmd_rect.sh" || echo " warn: zmqsend failed (or no zmqsend tool)"
|
||||
|
||||
echo ""
|
||||
echo "=== 3. Test add_overlay text ==="
|
||||
cat > "$TEST_DIR/cmd_text.sh" <<EOF
|
||||
echo "Parsed_cuda_grid_0 add_overlay t1 text cell=1 x=0.1 y=0.4 text=Hello%20World font_size=48 r=255 g=255 b=255 opacity=255" | \
|
||||
docker exec -i cuda-grid-smoke-ffmpeg /opt/ffmpeg/bin/zmqsend -b tcp://127.0.0.1:${ZMQ_PORT}
|
||||
EOF
|
||||
chmod +x "$TEST_DIR/cmd_text.sh"
|
||||
bash "$TEST_DIR/cmd_text.sh" || echo " warn: zmqsend failed"
|
||||
|
||||
echo ""
|
||||
echo "=== 4. Inspect ffmpeg logs (last 40 lines) ==="
|
||||
docker logs cuda-grid-smoke-ffmpeg 2>&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
|
||||
Reference in New Issue
Block a user