Sign inSign up

alexbic/video-processor-api

By alexbic

Updated 6 months ago

Buildkit cache
Image
0

8.4K

alexbic/video-processor-api repository overview

Video Processor API

Open Source REST API for video processing with FFmpeg. Create vertical Shorts, add subtitles, cut videos, extract audio.

Docker Hub GitHub License: MIT Version

English | Русский


✨ Features

  • 🎬 Pipeline Processing - chain multiple operations sequentially (cut → make_short → extract_audio)
  • 📦 Letterbox Mode - convert horizontal videos to vertical format (1080x1920) with blurred background
  • 📝 Universal Text Items System - flexible text overlays with individual timing, positioning, and styling
  • 🎨 Dynamic Subtitles - word-level timing with custom fonts, colors, background boxes, and positioning
  • 🖼️ Auto Thumbnails - automatic JPEG thumbnail generation from processed videos (perfect for YouTube/TikTok)
  • ✂️ Video Cutting - precise cutting by timecodes with automatic Shorts conversion
  • 🎵 Audio Extraction - with automatic chunking for Whisper API (max 24MB per chunk)
  • 📡 Webhooks - completion notifications with exponential backoff retry, orchestrator-managed resend
  • 🎯 Custom Webhook Headers - per-request authentication headers for webhook security
  • 🔄 Webhook State Tracking - unified webhook state in metadata.json with delivery status
  • File Expiration Tracking - expires_at field shows exact deletion time (ISO 8601)
  • Async Queue Processing - Redis queue + subprocess execution with real-time status tracking
  • 🔄 Orchestrator Process - separate supervisor for recovery, webhook retry, cleanup
  • 🔠 10 Tested Fonts - built-in fonts with full Cyrillic support (public version)
  • 🐳 Built-in Redis - embedded Redis server (256MB, localhost:6379) for task management
  • 🛡️ Input Validation - automatic media file validation before processing (Content-Type, signatures, size)
  • 🔗 Smart URL Generation - absolute URLs in all responses (public/internal modes)
  • 🧹 Smart Cleanup - orphaned tasks deleted after 1h, expired tasks after 3 days (hardcoded)
  • 🔄 Automatic Recovery - orchestrator scans and retries stuck tasks on startup (max 3 retries)
  • 📦 Client Metadata - pass-through custom JSON data (max 16KB) for platform-specific content

🚀 Quick Start

Docker Run (Production Ready)
docker pull alexbic/video-processor-api:latest
docker run -d -p 5001:5001 \
  -v $(pwd)/tasks:/app/tasks \
  --name video-processor \
  alexbic/video-processor-api:latest

Public version includes:

  • ✅ Built-in Redis (256MB, localhost:6379)
  • ✅ Orchestrator process (recovery, webhook retry, cleanup)
  • ✅ 2 Gunicorn workers (hardcoded)
  • ✅ Automatic task cleanup (every hour)
  • ✅ 3-day file retention (hardcoded)
Docker Compose (Optional)
docker-compose up -d

Configuration:

  • Port: 5001:5001
  • Volume: ./tasks:/app/tasks (task storage)

📚 API Reference

🔐 Authentication

API supports smart dual-mode operation with Bearer token authentication:

🔑 Two Operation Modes:

1️⃣ Public API Mode (when BOTH API_KEY AND PUBLIC_BASE_URL are set):

  • Protected endpoints require Bearer token authentication
  • Download URLs use public domain from PUBLIC_BASE_URL
  • Recommended for production with reverse proxy/CDN
  • Both parameters must be configured together

2️⃣ Internal Docker Network Mode (when API_KEY or PUBLIC_BASE_URL is NOT set):

  • All endpoints work without authentication
  • API operates within Docker network (e.g., with n8n)
  • Download URLs use internal Docker hostnames (http://video-processor:5001)
  • Ideal for trusted internal services
  • Works when: neither parameter set, only API_KEY set, or only PUBLIC_BASE_URL set

Setup:

# Generate secure API key
openssl rand -hex 32

# Public API mode (requires authentication) - BOTH parameters required
export API_KEY="your-generated-key-here"
export PUBLIC_BASE_URL="https://your-domain.com/video-api"

# Internal Docker mode (no authentication) - unset both or either
unset API_KEY
unset PUBLIC_BASE_URL

Usage with API Key:

curl -H "Authorization: Bearer your-api-key" \
  -X POST http://localhost:5001/process_video \
  -H "Content-Type: application/json" \
  -d '{"video_url": "...", "operations": [...]}'

Endpoint Protection:

  • Always public: /health, /fonts, /task_status/{task_id}, /download/{task_id}/...
  • 🔒 Protected in Public mode (when both API_KEY and PUBLIC_BASE_URL are set): /process_video, /tasks
  • 🔐 Task access: task_id acts as temporary access token (UUID, 72h TTL)

Client Metadata (Pass-through)

Add a client_meta field (any JSON object) to your request, and it will be:

  • saved in the task's metadata.json,
  • included in /process_video (sync) and /task_status/{task_id} (async) responses,
  • sent in webhook payloads (task_completed/task_failed).

This is useful for titles/captions for different social networks, campaign IDs, trace-ids, etc.

Example request with client_meta:

{
  "video_url": "https://example.com/video.mp4",
  "execution": "async",
  "operations": [{"type": "make_short", "crop_mode": "letterbox"}],
  "client_meta": {
    "titles": {
      "tiktok": "Cool AI Video",
      "youtube": "Amazing AI Demo",
      "instagram": "AI in Action"
    },
    "campaign_id": "cmp-2025-11-13"
  }
}

In responses, the field will be available as client_meta unchanged.

Limits (to protect the service):

  • Max size: 16 KB (UTF‑8 JSON)
  • Max depth: 5 levels
  • Max total keys: 200
  • Max list length: 200
  • Max string length: 1000 chars
  • Allowed types: objects, arrays, strings, numbers, booleans, null

Compatibility: client_meta may also be sent as a JSON string (it will be parsed server-side). Prefer sending an object directly.

n8n tip: if you have a nested object available only via string, you can send it using toJsonString() and the API will parse nested JSON strings too. Example:

{
  "client_meta": {
    "metadata": {{ $json.metadata.toJsonString() }}
  }
}

The server will convert metadata from a JSON string to an object before validation and saving.

Immediate echo:

  • Sync mode: client_meta is included in the final response.
  • Async mode: client_meta is included immediately in the 202 response (along with task_id and check_status_url).

Endpoints Overview
  • GET /health — service status (versions, storage_mode, Redis availability) [no authorization]
  • GET /fonts — list of available fonts (10 fonts in public version) [no authorization]
  • POST /process_video — make_short, cut_video, extract_audio (sync/async, webhooks) [requires API key in Public mode only]
  • GET /task_status/{task_id} — task status (queued/processing/completed/error) [no authorization]
  • GET /tasks — recent tasks (for debugging) [requires API key in Public mode only]
  • GET /download/{task_id}/{filename} — download completed file [no authorization]
  • GET /download/{task_id}/metadata.json — result metadata [no authorization]
Health Check
curl http://localhost:5001/health

Response:

{
  "status": "healthy",
  "service": "video-processor-api",
  "storage_mode": "redis",
  "redis_available": true,
  "api_key_enabled": true,
  "timestamp": "2025-01-08T10:00:00"
}

Available Fonts
curl http://localhost:5001/fonts

Response:

{
  "status": "success",
  "total_fonts": 10,
  "fonts": [
    {
      "name": "Charter",
      "filename": "Charter.ttc",
      "file": "/app/fonts/Charter.ttc",
      "type": "ttc"
    },
    {
      "name": "Copperplate",
      "filename": "Copperplate.ttc",
      "file": "/app/fonts/Copperplate.ttc",
      "type": "ttc"
    },
    ...
  ],
  "note": "These are the custom fonts available for video generation in /app/fonts/"
}

Available Fonts (Public Version):

  • 10 built-in fonts with full Cyrillic support
  • Use GET /fonts to see the complete list
  • Custom fonts available in Pro version only

See FONTS.md for font details and examples.


Video Processing

POST /process_video

Request structure:

{
  "video_url": "https://example.com/video.mp4",
  "execution": "sync|async",
  "operations": [{"type": "make_short|cut_video|extract_audio", ...}],
  "webhook": {"url": "...", "headers": {...}},
  "client_meta": {...}
}

Available operations:

  • cut_video - cut video by timecodes
  • make_short - convert to Shorts format with text overlays (max 2 text items in public version)
  • extract_audio - extract audio track with automatic chunking for Whisper API

See 📖 Examples section below for detailed usage examples.


Response Format

Unified format - all operations return the same structure:

{
  "task_id": "abc123",
  "status": "completed",
  "created_at": "2025-01-08T10:05:18",
  "completed_at": "2025-01-08T10:05:23",
  "input": {
    "video_url": "https://example.com/video.mp4",
    "operations": [
      {
        "operation": "make_short",
        "title": "Amazing Video",
        "font": "Montserrat-Bold.ttf"
      }
    ],
    "operations_count": 1
  },
  "output": {
    "output_files": [
      {
        "filename": "short_20251116_210049.mp4",
        "file_size": 16040960,
        "file_size_mb": 15.3,
        "download_url": "http://video-processor:5001/download/abc123/short_20251116_210049.mp4",
        "download_path": "/download/abc123/short_20251116_210049.mp4"
      },
      {
        "filename": "short_20251116_210049_thumbnail.jpg",
        "file_size": 211762,
        "file_size_mb": 0.2,
        "download_url": "http://video-processor:5001/download/abc123/short_20251116_210049_thumbnail.jpg",
        "download_path": "/download/abc123/short_20251116_210049_thumbnail.jpg"
      }
    ],
    "total_files": 2,
    "total_size": 16252722,
    "total_size_mb": 15.5,
    "is_chunked": false,
    "metadata_url": "/download/abc123/metadata.json",
    "ttl_seconds": 259200,
    "ttl_human": "3 days",
    "expires_at": "2025-01-11T10:05:23"
  }
}

Response structure:

  • Top level: task_id, status, timestamps
  • input: Original request data (video_url, operations)
  • output: Processing results (output_files, total_files, metadata URL, TTL info)
  • output_files is always an array (even if 1 file)
  • is_chunked: true if files are split into chunks (for Whisper API)
Error Responses

All errors are returned with an HTTP code, status: "error" field and message in error.

  • 400 Bad Request (validation):
    { "status": "error", "error": "video_url is required" }
    
  • 404 Not Found (task status):
    { "status": "error", "error": "Task not found" }
    
  • 403 Forbidden (downloading file outside task directory):
    { "status": "error", "error": "Invalid file path" }
    
  • 404 Not Found (file not found during download):
    { "status": "error", "error": "File not found" }
    
  • 500 Internal Server Error (execution error):
    { "status": "error", "error": "FFmpeg error: ..." }
    

In webhooks on error, event remains event: "task_failed", and status is status: "error".

For chunked files (extract_audio with splitting):

{
  "output_files": [
    {"filename": "audio_chunk_000.mp3", "chunk": "1:7", ...},
    {"filename": "audio_chunk_001.mp3", "chunk": "2:7", ...}
  ],
  "is_chunked": true
}

Execution Modes
Sync (synchronous) — default
{
  "execution": "sync"
}

Response (immediately):

{
  "task_id": "abc123",
  "status": "completed",
  "created_at": "2025-01-08T10:05:18",
  "completed_at": "2025-01-08T10:05:23",
  "input": {
    "video_url": "https://example.com/video.mp4",
    "operations": [{"operation": "cut_video", "start": 10, "end": 30}],
    "operations_count": 1
  },
  "output": {
    "output_files": [
      {
        "filename": "output_20250108_100523.mp4",
        "file_size": 16040960,
        "file_size_mb": 15.3,
        "download_url": "http://video-processor:5001/download/abc123/output_20250108_100523.mp4",
        "download_path": "/download/abc123/output_20250108_100523.mp4"
      }
    ],
    "total_files": 1,
    "is_chunked": false,
    "metadata_url": "/download/abc123/metadata.json",
    "ttl_seconds": 259200,
    "ttl_human": "3 days",
    "expires_at": "2025-01-11T10:05:23"
  }
}
Async (asynchronous)
{
  "execution": "async"
}

Response (immediately, HTTP 202):

{
  "task_id": "abc123",
  "status": "queued",
  "message": "Task created and processing in background",
  "check_status_url": "http://video-processor:5001/task_status/abc123"
}

Check status:

curl http://localhost:5001/task_status/abc123

Response:

{
  "task_id": "abc123",
  "status": "completed",
  "created_at": "2025-01-08T10:05:18",
  "completed_at": "2025-01-08T10:05:23",
  "input": {
    "video_url": "https://example.com/video.mp4",
    "operations": [{"operation": "cut_video", "start": 10, "end": 30}]
  },
  "output": {
    "output_files": [
      {
        "filename": "output.mp4",
        "file_size": 16040960,
        "file_size_mb": 15.3,
        "download_url": "http://video-processor:5001/download/abc123/output.mp4",
        "download_path": "/download/abc123/output.mp4"
      }
    ],
    "total_files": 1,
    "total_size": 16040960,
    "total_size_mb": 15.3,
    "is_chunked": false,
    "metadata_url": "http://video-processor:5001/download/abc123/metadata.json",
    "ttl_seconds": 259200,
    "ttl_human": "3 days",
    "expires_at": "2025-01-11T10:05:23"
  }
}

Webhooks

Add webhook object to receive notifications on task completion:

{
  "webhook": {
    "url": "https://n8n.example.com/webhook/video-completed"
  }
}

Custom Webhook Headers (Optional):

You can add custom headers for webhook authentication via webhook.headers:

{
  "webhook": {
    "url": "https://n8n.example.com/webhook/video-completed",
    "headers": {
      "X-API-Key": "your-secret-key",
      "Authorization": "Bearer token-123"
    }
  }
}

Use Cases:

  • 🔑 Different API keys for different webhooks
  • 🎫 Request-specific authorization tokens
  • 🏷️ Custom tracing/correlation IDs
  • 👤 Client identification headers

Validation:

  • Must be JSON object with string keys/values
  • Header name max: 256 chars
  • Header value max: 2048 chars
  • Content-Type cannot be overridden
  • Specify webhook.headers in each request (global headers not supported in public version)

Webhook Payload (success):

{
  "task_id": "abc123",
  "event": "task_completed",
  "status": "completed",
  "created_at": "2025-01-08T10:05:18",
  "completed_at": "2025-01-08T10:05:23",
  "input": {
    "video_url": "https://example.com/video.mp4",
    "operations": [{"operation": "cut_video", "start": 10, "end": 30}],
    "operations_count": 1
  },
  "output": {
    "output_files": [
      {
        "filename": "output.mp4",
        "file_size": 16040960,
        "file_size_mb": 15.3,
        "download_url": "http://video-processor:5001/download/abc123/output.mp4",
        "download_path": "/download/abc123/output.mp4"
      }
    ],
    "total_files": 1,
    "total_size": 16040960,
    "total_size_mb": 15.3,
    "is_chunked": false,
    "metadata_url": "http://video-processor:5001/download/abc123/metadata.json",
    "ttl_seconds": 259200,
    "ttl_human": "3 days",
    "expires_at": "2025-01-11T10:05:23"
  }
}

Webhook Payload (error):

{
  "task_id": "abc123",
  "event": "task_failed",
  "status": "error",
  "error": "FFmpeg error: ...",
  "failed_at": "2025-01-08T10:05:23"
}

Webhook State Tracking:

Webhook delivery state is saved in metadata.json under webhook field:

{
  "webhook": {
    "url": "https://n8n.example.com/webhook/video-completed",
    "headers": {"X-API-Key": "***"},
    "status": "delivered",
    "attempts": 1,
    "last_attempt": "2025-01-08T10:05:23",
    "last_status": 200,
    "last_error": null,
    "next_retry": null
  }
}

Retry Logic:

  • Initial attempts: 3 tries with exponential backoff (5s, 15s, 1m)
  • Background Resender: Automatically retries failed webhooks every 15 minutes
  • Retry delays: 5min → 15min → 1h → 4h → 12h → 24h (max)
  • Webhook status: pendingdelivered / failed
  • Failed webhooks continue retrying until delivered or task TTL expires (3 days)

Status Lifecycle

Task statuses and transitions:

  • queued → task created and queued (async)
  • processing → operations executing (progress 5–95%)
  • completed → finished; output_files, is_chunked, metadata_url, video_url available
  • error → execution error; error — description, failed_at — timestamp

Key status fields:

  • task_id: task identifier
  • status: queued | processing | completed | error
  • progress: 0–100 (for async)
  • created_at / completed_at / failed_at: timestamps
  • output_files: always an array; when chunked contains chunk: "i:n"
  • is_chunked: true if output_files has chunk field

Polling recommendations:

  • Poll GET /task_status/{task_id} every 2–3 seconds
  • Stop polling when status is {completed, error}

⚙️ Configuration

Environment Variables
VariableDefaultDescription
Authentication & URLs
API_KEYEnables public mode (Bearer required). When unset, internal mode (no auth).
PUBLIC_BASE_URLExternal base for absolute URLs (https://host/app). Used only if API_KEY is set.
INTERNAL_BASE_URLhttp://video-processor:5001Base for background URL generation (webhooks, logs).
LOG_LEVELINFOLogging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).

Notes:

  • With API_KEY set + PUBLIC_BASE_URL defined → service exposes absolute URLs and requires Bearer token.
  • Without API_KEY → internal mode suitable for Docker network usage (no auth).
  • check_status_url is always absolute in async responses.
Manual Recovery (optional)
  • Endpoint: GET/POST /recover/{task_id}
  • Enable via RECOVERY_PUBLIC_ENABLED=true (use only in trusted network)
  • Optional query: force=1 to ignore expired TTL

Response:

{ "task_id": "...", "ok": true, "message": "Recovery started", "status": "processing", "retry_count": 1 }

📖 Examples

Example 1: Shorts with automatic cutting and text overlays

What it does:

  • Cuts video from 10.5 to 70 seconds (59.5 sec total)
  • Converts horizontal to vertical (1080x1920) with blurred background
  • Adds title text (stays 60 sec) with semi-transparent black box
  • Adds call-to-action text (shows first 3 sec only)
  • Auto-generates JPEG thumbnail
{
  "video_url": "https://example.com/long-video.mp4",
  "execution": "sync",
  "operations": [
    {
      "type": "make_short",
      "start_time": 10.5,
      "end_time": 70.0,
      "crop_mode": "letterbox",
      "text_items": [
        {
          "text": "My First Shorts",
          "fontfile": "HelveticaNeue.ttc",
          "fontsize": 70,
          "fontcolor": "white",
          "x": "(w-text_w)/2",
          "y": 100,
          "start": 0,
          "end": 60,
          "box": 1,
          "boxcolor": "[email protected]"
        },
        {
          "text": "Subscribe for more!",
          "fontfile": "PTSans.ttc",
          "fontsize": 48,
          "fontcolor": "yellow",
          "x": "(w-text_w)/2",
          "y": "h-200",
          "start": 0,
          "end": 3
        }
      ],
      "generate_thumbnail": true
    }
  ]
}

Note: start_time/end_time accept numbers (seconds) or strings ("00:01:30"). Time in text_items is relative to the cropped video.

Example 2: Simple Shorts conversion (letterbox only, no text)

What it does:

  • Converts horizontal video to vertical format (letterbox mode)
  • No text overlays - clean conversion only
  • Generates thumbnail from frame at 0.5 seconds
{
  "video_url": "https://example.com/landscape.mp4",
  "execution": "sync",
  "operations": [
    {
      "type": "make_short",
      "crop_mode": "letterbox",
      "generate_thumbnail": true,
      "thumbnail_timestamp": 0.5
    }
  ]
}
Example 3: Dynamic subtitles with word-level timing

What it does:

  • Static title at top (stays entire duration) with background box
  • Dynamic subtitles at bottom with word-level timing
  • Each word appears/disappears at specific time (karaoke-style)
  • Text outline (borderw: 3px black) + background box with 8px border
  • Inherits style from container: all words use same font, size, colors
{
  "video_url": "https://example.com/video.mp4",
  "execution": "sync",
  "operations": [
    {
      "type": "make_short",
      "crop_mode": "letterbox",
      "text_items": [
        {
          "text": "Title",
          "fontfile": "HelveticaNeue.ttc",
          "fontsize": 80,
          "fontcolor": "white",
          "x": "(w-text_w)/2",
          "y": 100,
          "start": 0,
          "end": 60,
          "box": 1,
          "boxcolor": "[email protected]"
        },
        {
          "text": "",
          "fontfile": "PTSans.ttc",
          "fontsize": 60,
          "fontcolor": "yellow",
          "borderw": 3,
          "bordercolor": "black",
          "box": 1,
          "boxcolor": "[email protected]",
          "boxborderw": 8,
          "x": "(w-text_w)/2",
          "y": "h-200",
          "subtitles": {
            "items": [
              {"text": "First word", "start": 0, "end": 1.5},
              {"text": "Second word", "start": 1.5, "end": 3},
              {"text": "Third word", "start": 3, "end": 4.5}
            ]
          }
        }
      ]
    }
  ]
}
Example 4: Video cutting

What it does:

  • Cuts video from 1:30 to 2:00 (30 seconds total)
  • No format conversion - preserves original aspect ratio
  • Supports both formats: numbers (seconds) or strings ("HH:MM:SS")
{
  "video_url": "https://example.com/long-video.mp4",
  "execution": "sync",
  "operations": [
    {
      "type": "cut_video",
      "start_time": "00:01:30",
      "end_time": "00:02:00"
    }
  ]
}
Example 5: Pipeline - multiple operations

What it does:

  • Step 1: Cut video from 10 sec to 1 min (50 sec total)
  • Step 2: Convert cut video to Shorts with title
  • Operations execute sequentially - output of step 1 feeds into step 2
  • Async mode - returns immediately with task_id for status checking
{
  "video_url": "https://example.com/video.mp4",
  "execution": "async",
  "operations": [
    {
      "type": "cut_video",
      "start_time": "00:00:10",
      "end_time": "00:01:00"
    },
    {
      "type": "make_short",
      "letterbox_config": {"width": 1080, "height": 1920},
      "title": {"text": "Episode 1", "fontsize": 70}
    }
  ]
}
Example 6: Audio extraction (sync mode)
curl -X POST http://localhost:5001/process_video \
  -H "Content-Type: application/json" \
  -d '{
    "video_url": "https://example.com/video.mp4",
    "execution": "sync",
    "operations": [
      {
        "type": "extract_audio",
        "format": "mp3",
        "bitrate": "192k"
      }
    ]
  }'

Response (sync - returned immediately after completion):

{
  "task_id": "abc123-def456",
  "status": "completed",
  "output_files": [
    {
      "filename": "audio_20251112_194523.mp3",
      "file_size": 5048576,
      "file_size_mb": 4.8,
      "download_url": "http://localhost:5001/download/abc123-def456/audio_20251112_194523.mp3",
      "download_path": "/download/abc123-def456/audio_20251112_194523.mp3"
    }
  ],
  "total_files": 1,
  "is_chunked": false,
  "metadata_url": "http://localhost:5001/download/abc123-def456/metadata.json",
  "note": "Files will auto-delete after 3 days.",
  "completed_at": "2025-11-12T19:45:23"
}
Example 7: Audio extraction (async mode with webhook)
{
  "video_url": "https://example.com/video.mp4",
  "execution": "async",
  "operations": [
    {
      "type": "extract_audio",
      "format": "mp3",
      "bitrate": "320k"
    }
  ],
  "webhook": {
      "url": "https://n8n.example.com/webhook/audio-ready"
}

Response (async - returned immediately):

Tag summary

Content type

Image

Digest

sha256:7d195f66a

Size

307.3 MB

Last updated

6 months ago

docker pull alexbic/video-processor-api