◆ ForgeVis
Skip to content

Event Hooks ​

ForgeVis поддерживает выполнение произвольных команд при определённых событиях записи сегментов.

Доступные хуки ​

runOnSegmentCreate ​

Выполняется когда начинается запись нового сегмента (создаётся новый файл).

Переменные окружения:

  • SF_PATH - идентификатор камеры
  • SF_SEGMENT_PATH - полный путь к файлу сегмента

Пример использования:

yaml
record:
  runOnSegmentCreate: "./hooks/on-create.sh"

runOnSegmentComplete ​

Выполняется когда сегмент полностью записан и закрыт.

Переменные окружения:

  • SF_PATH - идентификатор камеры
  • SF_SEGMENT_PATH - полный путь к файлу сегмента
  • SF_SEGMENT_DURATION - длительность сегмента в секундах

Пример использования:

yaml
record:
  runOnSegmentComplete: "./hooks/on-complete.sh"

runPeriodically ​

Запускается с фиксированным интервалом для каждой камеры — независимо от границ сегментов. Основной use case — генерация live-превью.

Параметры конфигурации:

  • intervalSec — частота запуска для каждой камеры (по умолчанию 60)
  • command — путь к скрипту
  • maxConcurrent — глобальный лимит одновременно работающих хуков по всем камерам (по умолчанию 50). Превышающие запуски ожидают в FIFO-очереди.

Переменные окружения:

  • SF_PATH — идентификатор камеры
  • SF_CAMERA_DIR — корневой каталог записей этой камеры. Скрипт сам находит последний сегмент (имена файлов содержат zero-padded timestamp, поэтому лексическая сортировка совпадает с хронологической).

Пример использования:

yaml
record:
  runPeriodically:
    intervalSec: 60
    command: "./hooks/preview.sh"
    maxConcurrent: 50

При первом запуске каждая камера получает рандомный stagger в [0, intervalSec) чтобы 1000 камер не били одновременно. Эффективный лаг превью растёт как (всего_камер × среднее_время_ffmpeg) / maxConcurrent.

Примеры скриптов ​

Уведомление при создании сегмента ​

bash
#!/bin/bash
# on-create.sh

echo "New segment started: $SF_SEGMENT_PATH for camera $SF_PATH"

# Отправить уведомление через webhook
curl -X POST "https://example.com/webhook" \
  -H "Content-Type: application/json" \
  -d '{"event": "segment_created", "camera": "'"$SF_PATH"'", "path": "'"$SF_SEGMENT_PATH"'"}'

Обработка завершённого сегмента ​

bash
#!/bin/bash
# on-complete.sh

echo "Segment completed: $SF_SEGMENT_PATH (${SF_SEGMENT_DURATION}s)"

# Создать резервную копию
rsync -av "$SF_SEGMENT_PATH" /mnt/backup/recordings/

# Отправить уведомление
curl -X POST "https://example.com/webhook" \
  -H "Content-Type: application/json" \
  -d '{"event": "segment_completed", "camera": "'"$SF_PATH"'", "path": "'"$SF_SEGMENT_PATH"'", "duration": '"$SF_SEGMENT_DURATION"'}'

Проверка размера файла ​

bash
#!/bin/bash
# check-size.sh

FILE_SIZE=$(stat -f%z "$SF_SEGMENT_PATH" 2>/dev/null || stat -c%s "$SF_SEGMENT_PATH" 2>/dev/null)
FILE_SIZE_MB=$((FILE_SIZE / 1024 / 1024))

echo "Segment size: ${FILE_SIZE_MB}MB"

if [ $FILE_SIZE_MB -lt 1 ]; then
  echo "WARNING: Segment is too small!"
  # Отправить алерт
fi

Генерация thumbnail ​

bash
#!/bin/bash
# generate-thumbnail.sh

THUMBNAIL="${SF_SEGMENT_PATH%.mp4}.jpg"

# Создать превью из середины видео
ffmpeg -i "$SF_SEGMENT_PATH" -ss 00:00:30 -vframes 1 -q:v 2 "$THUMBNAIL" 2>/dev/null

echo "Thumbnail created: $THUMBNAIL"

Условное выполнение ​

bash
#!/bin/bash
# conditional-upload.sh

# Загружать только длинные сегменты
if [ "$SF_SEGMENT_DURATION" -gt 600 ]; then
  echo "Segment is long enough, uploading..."
  # Выполнить загрузку
fi

Rate limiting ​

bash
#!/bin/bash
# rate-limited-notify.sh

LOCK_FILE="/tmp/forgevis-notify.lock"
LOCK_TIMEOUT=60

# Отправлять уведомления не чаще раза в минуту
if [ ! -f "$LOCK_FILE" ] || [ $(($(date +%s) - $(stat -f%m "$LOCK_FILE" 2>/dev/null || stat -c%Y "$LOCK_FILE"))) -gt $LOCK_TIMEOUT ]; then
  touch "$LOCK_FILE"
  # Отправить уведомление
  curl -X POST "https://example.com/webhook" -d '{"event": "recording"}'
fi

Live-превью через periodic-hook ​

Генерирует JPEG-превью для каждой камеры раз в 60 секунд. ffmpeg читает последние секунды самого свежего сегмента, поэтому превью устаревает максимум на segmentDuration + intervalSec.

bash
#!/bin/bash
# preview.sh — извлечение превью-кадра из последнего сегмента

set -eu
[ -z "${SF_CAMERA_DIR:-}" ] && exit 1
[ ! -d "$SF_CAMERA_DIR" ] && exit 0

# Имена файлов содержат zero-padded timestamp, лексическая сортировка = хронологическая.
LATEST=$(find "$SF_CAMERA_DIR" -name '*.mp4' -type f 2>/dev/null | sort | tail -1)
[ -z "$LATEST" ] && exit 0

ffmpeg -hide_banner -loglevel error -y \
    -sseof -3 -i "$LATEST" \
    -update 1 -frames:v 1 -q:v 5 \
    "$SF_CAMERA_DIR/preview.jpg"
yaml
record:
  runPeriodically:
    intervalSec: 60
    command: "/etc/forgevis/hooks/preview.sh"
    maxConcurrent: 50

Превью доступно через существующий маршрут archive: GET /api/archive/file/<camera_id>/preview.jpg.

Webhook с повторными попытками ​

Перезапрашивает webhook при сетевой ошибке, ограничивая число попыток:

bash
#!/bin/bash
# webhook-with-retry.sh
MAX_RETRIES=3
RETRY_DELAY=5

for i in $(seq 1 $MAX_RETRIES); do
    if curl -X POST "https://api.example.com/webhook" \
        -d "{\"path\": \"$SF_SEGMENT_PATH\"}" \
        --max-time 10; then
        exit 0
    fi
    sleep $RETRY_DELAY
done

echo "Webhook failed after $MAX_RETRIES attempts" >&2
exit 1

Интеграции ​

Запуск обработки видео ​

bash
#!/bin/bash
# process-video.sh

# Добавить задачу в очередь обработки
echo "$SF_SEGMENT_PATH" >> /var/queue/video-processing.txt

# Или вызвать API сервиса обработки
curl -X POST http://video-processor:8080/process \
  -H "Content-Type: application/json" \
  -d '{
    "camera": "'"$SF_PATH"'",
    "file": "'"$SF_SEGMENT_PATH"'",
    "duration": '"$SF_SEGMENT_DURATION"'
  }'

Применение ​

  • Мониторинг и алерты - уведомления о событиях записи, аномальная длительность сегментов, сбои записи
  • Пост-обработка - транскодирование, генерация превью, извлечение метаданных
  • Интеграция с внешними системами - webhooks, очереди задач, системы хранения
  • Очистка и архивирование - удаление старых файлов, загрузка в облако, сжатие

Лучшие практики ​

1. Сделать скрипт исполняемым ​

bash
chmod +x /path/to/your-hook.sh

2. Обрабатывать ошибки ​

bash
#!/bin/bash
set -e  # Выйти при первой ошибке

# Логика хука

exit 0  # Возвращать успех, чтобы ForgeVis не считал хук упавшим

3. Запускать долгие задачи в фоне ​

Не блокируйте процесс записи — хук должен завершаться быстро:

bash
#!/bin/bash
{
    process_video "$SF_SEGMENT_PATH"
} &

exit 0

4. Логировать вывод хука ​

bash
#!/bin/bash
LOG_FILE="/var/log/forgevis-hooks.log"

{
    echo "Hook triggered: $(date)"
    echo "Camera: $SF_PATH"
    # Логика хука
} >> "$LOG_FILE" 2>&1

5. Тестировать хуки до подключения ​

Запустите хук вручную с подставленными переменными окружения:

bash
export SF_PATH="camera_001"
export SF_SEGMENT_PATH="./test.mp4"
export SF_SEGMENT_DURATION="60"

./your-hook.sh

Помимо ручного запуска, удобен универсальный тестовый скрипт:

bash
#!/bin/bash
# test-hook.sh — вывести всё, что ForgeVis передал хуку
echo "=========================="
echo "ForgeVis Hook Test"
echo "=========================="
echo "Time: $(date)"
echo "Camera: $SF_PATH"
echo "Segment: $SF_SEGMENT_PATH"
[ -n "$SF_SEGMENT_DURATION" ] && echo "Duration: $SF_SEGMENT_DURATION seconds"
echo "=========================="

Устранение проблем ​

Хук не запускается ​

  1. Проверьте права на исполнение: ls -l your-hook.sh
  2. Убедитесь, что путь в config.yaml правильный (предпочтительно абсолютный)
  3. Поищите ошибки в логах ForgeVis
  4. Запустите скрипт вручную с тестовыми переменными окружения

Логи выполнения хуков можно найти в выводе ForgeVis:

INFO  Hook executed successfully: ./test-hook.sh
ERROR Hook failed with status exit status: 1: ./failed-hook.sh

Хук молча падает ​

Добавьте подробное логирование в сам скрипт:

bash
#!/bin/bash
exec 2>> /tmp/hook-errors.log
set -x  # Печатать все команды

# Логика хука

Хук замедляет запись ​

Если запись начинает «отставать»:

  1. Запускайте долгие задачи в фоне (& или nohup ... &)
  2. Выносите обработку в отдельные процессы/воркеры
  3. Накапливайте задачи в очередь и обрабатывайте пачкой
  4. Замеряйте время выполнения хука

Безопасность ​

Хуки выполняются с правами процесса ForgeVis — относитесь к ним как к доверенному коду. Рекомендации:

  • Никогда не логируйте чувствительные данные (пароли, токены, ключи).
  • Валидируйте все входные пути перед использованием в файловых операциях.
  • Используйте абсолютные пути в скриптах — никаких $PATH-зависимостей.
  • Минимизируйте права процесса ForgeVis: запись только в нужные каталоги.
  • Санитизируйте переменные окружения перед подстановкой в шелл-команды. Переменные SF_PATH и SF_SEGMENT_PATH приходят из конфигурации и от файловой системы — в шелл-командах используйте двойные кавычки и избегайте eval/конкатенации.

Пример небезопасного и безопасного использования:

bash
# ❌ Небезопасно: $SF_PATH может содержать пробелы или специальные символы
cp /var/recordings/$SF_PATH/* /backup/

# ✅ Безопасно: кавычки + проверка
if [[ "$SF_PATH" =~ ^[a-zA-Z0-9_-]+$ ]]; then
    cp -r "/var/recordings/$SF_PATH" "/backup/"
fi

Запуск в фоне ​

Тяжёлые операции (транскодирование, загрузка в облако) стоит запускать в фоне, чтобы хук возвращал управление быстро и не накапливал отставание:

bash
#!/bin/bash
# Запустить транскодирование в фоне и отвязать от основного процесса
nohup ffmpeg -i "$SF_SEGMENT_PATH" \
    -c:v libx264 -preset fast \
    "/transcoded/${SF_PATH}/$(basename "$SF_SEGMENT_PATH")" \
    > /dev/null 2>&1 &
disown

См. также ​

Proprietary software.