◆ ForgeVis
Skip to content

Event Hooks ​

Event hooks allow you to execute custom scripts or commands when specific events occur during recording. This enables integration with external systems, automation, and custom processing workflows.

Overview ​

ForgeVis supports three types of event hooks:

  • runOnSegmentCreate - Triggered when a new recording segment starts
  • runOnSegmentComplete - Triggered when a recording segment finishes
  • runPeriodically - Fires at a fixed interval per camera (independent of segments). Designed for live-preview thumbnails. A global concurrency limit caps simultaneous executions.

Configuration ​

Add hooks to your config.yaml in the record section:

yaml
record:
  segmentDuration: 900
  runOnSegmentCreate: "/path/to/on-create.sh"
  runOnSegmentComplete: "/path/to/on-complete.sh"
  runPeriodically:
    intervalSec: 60                  # how often to fire (per camera)
    command: "/path/to/periodic.sh"
    maxConcurrent: 50                # global cap across all cameras

Environment Variables ​

Your hook scripts receive the following environment variables:

Common Variables (both hooks) ​

  • SF_PATH - Camera identifier (e.g., camera_001)
  • SF_SEGMENT_PATH - Full path to the segment file

Additional Variables (complete hook only) ​

  • SF_SEGMENT_DURATION - Segment duration in seconds

Periodic Hook Variables ​

  • SF_PATH - Camera identifier
  • SF_CAMERA_DIR - Per-camera recordings root. The script is expected to locate the latest segment itself (filenames are zero-padded timestamps, so a lexical sort matches chronological order).

Example Scripts ​

Basic Notification Hook ​

bash
#!/bin/bash
# on-complete.sh - Send notification when segment completes

echo "Segment complete: $SF_SEGMENT_PATH"
echo "Camera: $SF_PATH"
echo "Duration: $SF_SEGMENT_DURATION seconds"

# Send notification (example)
# curl -X POST "https://hooks.slack.com/..." \
#   -d "{\"text\": \"Recording complete: $SF_SEGMENT_PATH\"}"

Generate Thumbnail ​

bash
#!/bin/bash
# generate-thumbnail.sh - Create thumbnail from segment

if [ -n "$SF_SEGMENT_DURATION" ]; then
    THUMB_PATH="${SF_SEGMENT_PATH%.mp4}.jpg"
    
    ffmpeg -i "$SF_SEGMENT_PATH" \
        -ss 00:00:01 \
        -vframes 1 \
        -q:v 2 \
        "$THUMB_PATH" \
        -loglevel quiet
    
    echo "Thumbnail created: $THUMB_PATH"
fi

Live Preview via Periodic Hook ​

Generate a near-live JPEG preview for each camera every 60 seconds. ffmpeg reads the last few seconds of the most recent segment, so the snapshot is at most segmentDuration + intervalSec seconds old.

bash
#!/bin/bash
# preview.sh — extract a preview snapshot from the latest segment

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

# Filenames carry zero-padded timestamps; lexical sort = chronological.
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

The preview is then accessible through the existing archive file route: GET /api/archive/file/<camera_id>/preview.jpg.

The global maxConcurrent cap means that even with 1000 cameras only maxConcurrent ffmpeg processes run at any one time; the rest queue in FIFO order on the camera-side. Effective preview lag scales as (total_cameras × avg_ffmpeg_time) / maxConcurrent.

Webhook Notification ​

bash
#!/bin/bash
# webhook-notify.sh - Send notifications via HTTP webhook

WEBHOOK_URL="https://example.com/api/webhook"

if [ -n "$SF_SEGMENT_DURATION" ]; then
    EVENT_TYPE="segment_complete"
else
    EVENT_TYPE="segment_create"
fi

curl -X POST "$WEBHOOK_URL" \
    -H "Content-Type: application/json" \
    -d "{
        \"event\": \"$EVENT_TYPE\",
        \"camera\": \"$SF_PATH\",
        \"path\": \"$SF_SEGMENT_PATH\",
        \"duration\": \"$SF_SEGMENT_DURATION\"
    }" \
    -s > /dev/null

Cleanup Old Recordings ​

bash
#!/bin/bash
# cleanup-old.sh - Delete recordings older than 7 days

if [ -n "$SF_SEGMENT_DURATION" ]; then
    RECORDINGS_DIR="$(dirname "$SF_SEGMENT_PATH")"
    
    # Delete files older than 7 days
    find "$RECORDINGS_DIR" -name "*.mp4" -mtime +7 -delete
    
    echo "Cleaned up old recordings in $RECORDINGS_DIR"
fi

Integrations ​

Video Processing Pipeline ​

Push completed segments into an external processing queue or call a worker service directly:

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

# Append the segment path to a worker queue file
echo "$SF_SEGMENT_PATH" >> /var/queue/video-processing.txt

# Or call a processing service's API directly
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"'
  }'

Best Practices ​

1. Make Scripts Executable ​

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

2. Handle Errors Gracefully ​

bash
#!/bin/bash
set -e  # Exit on error

# Your hook logic here

exit 0  # Always exit successfully

3. Run Long Tasks in Background ​

bash
#!/bin/bash
# Don't block recording process
{
    # Long-running task
    process_video "$SF_SEGMENT_PATH"
} &

exit 0

4. Log Hook Output ​

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

{
    echo "Hook triggered: $(date)"
    echo "Camera: $SF_PATH"
    # Your logic here
} >> "$LOG_FILE" 2>&1

5. Test Your Hooks ​

Create a test script to verify hook functionality:

bash
# Set test environment variables
export SF_PATH="camera_001"
export SF_SEGMENT_PATH="./test.mp4"
export SF_SEGMENT_DURATION="60"

# Run your hook
./your-hook.sh

Use Cases ​

Common Use Cases ​

  • Monitoring & Alerting: Send notifications when recording starts/stops
  • Post-Processing: Generate thumbnails, extract metadata, transcode
  • Integration: Upload to cloud storage, update external systems via API
  • Cleanup: Delete old recordings automatically, archive to cold storage

Troubleshooting ​

Hook Not Executing ​

  1. Check script permissions: ls -l your-hook.sh
  2. Verify script path in config.yaml
  3. Check ForgeVis logs for errors
  4. Test script manually with environment variables

Hook Failing Silently ​

Add logging to your script:

bash
#!/bin/bash
exec 2>> /tmp/hook-errors.log
set -x  # Print all commands

# Your hook logic

Performance Issues ​

If hooks slow down recording:

  1. Run long tasks in background (&)
  2. Use separate worker processes
  3. Queue tasks for batch processing
  4. Monitor hook execution time

Security Considerations ​

  • Never log sensitive credentials
  • Validate all input paths
  • Use absolute paths in scripts
  • Limit file system access
  • Run hooks with minimal privileges
  • Sanitize environment variables before using in commands

Advanced Examples ​

Conditional Execution ​

bash
#!/bin/bash
# Only process specific cameras
if [ "$SF_PATH" == "camera_front" ]; then
    # Special processing for front camera
    process_important_camera "$SF_SEGMENT_PATH"
fi

Rate Limiting ​

bash
#!/bin/bash
# Prevent hook spam
LOCK_FILE="/tmp/hook-${SF_PATH}.lock"

if [ -f "$LOCK_FILE" ]; then
    exit 0  # Another hook is running
fi

touch "$LOCK_FILE"
trap "rm -f $LOCK_FILE" EXIT

# Your hook logic here

Webhook with Retry ​

bash
#!/bin/bash
# Retry webhook calls on failure
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

See Also ​

Proprietary software.