Have you ever wanted to encode many video or audio files at once, across multiple devices? FFNodes is a Rust-based tool designed to simplify this process by leveraging the power of ffmpeg and distributed computing.
- Distributed encoding across multiple devices
- GUID-based authentication for secure client connections
- Priority-based job queue (largest and most complex files first)
- Smart file re-probing (only probes when file modification time changes)
- Automatic file system watching with batch processing
- Client heartbeat monitoring with automatic job reassignment on timeout
- FFmpeg template system with hardware acceleration support
- Real-time WebSocket progress monitoring
- Automatic job recovery and retry with exponential backoff
- Support for 47+ video/audio formats
FFNodes uses a client-server architecture for distributed video encoding:
-
Server - Central coordinator that manages:
- Media file discovery and metadata extraction
- Job queue with priority-based assignment
- Client registration and heartbeat monitoring
- File system watching for automatic job creation
- REST API and WebSocket endpoints
-
Clients - Worker nodes that:
- Connect to the server via GUID authentication
- Request encoding jobs from the queue
- Execute FFmpeg with hardware acceleration
- Report progress and completion status
- Send periodic heartbeats
- Recursively scans configured directories for supported video/audio files
- Extracts metadata using FFprobe (duration, resolution, bitrate, codec, etc.)
- Calculates encoding complexity:
(width Ă— height) Ă— bitrate Ă— duration - Smart re-probing: only re-probes files when
last_modifiedtimestamp changes - Processes up to 10 files concurrently
- Monitors configured directories for file system changes using
notify - Batch processing every 30 seconds (configurable) to reduce overhead
- Automatically adds new files to database and creates encoding jobs
- Removes deleted files from database and cancels associated jobs
- Priority-based queue:
priority = file_size Ă— encoding_complexity / (retry_count + 1) - Largest and most complex files encode first
- Failed jobs automatically deprioritized based on retry count
- In-memory cache with SQLite persistence
- Job states:
pending→assigned→in_progress→completed/failed
- GUID-based authentication (server generates UUID on first launch)
- Tracks client metadata: display name, computer name, connection time
- Heartbeat monitoring with configurable timeout (default 300 seconds)
- Automatic disconnection detection and job reassignment
- Background task runs every 60 seconds
- Detects stale jobs (assigned but no progress for > timeout)
- Automatically requeues stale jobs when clients disconnect or timeout
- Increments retry count and recalculates priority
- Server provides FFmpeg command template to clients
- Template format:
-c:v h264{HWACCEL_CODE} -preset medium -crf 23 -i {INPUT} {OUTPUT} - Clients specify hardware acceleration:
_nvenc- NVIDIA NVENC_amf- AMD AMF_qsv- Intel Quick Sync- `` (empty) - Software encoding
- Template parser performs simple string replacement
-
Server Startup:
- Load configuration (or generate with new GUID)
- Initialize SQLite database with WAL mode
- Start job scheduler for stale job monitoring
- Start file watcher for automatic discovery
- Perform initial media file scan
- Start HTTP server and WebSocket endpoint
-
Client Connection:
- Client sends handshake with server GUID, display name, computer name
- Server validates GUID and registers client
- Server returns auth token (client ID) and FFmpeg template
-
Job Assignment:
- Client requests job with client ID
- Server retrieves highest priority pending job
- Server assigns job to client and updates state
- Server returns job details with input/output paths
-
Job Execution:
- Client starts job (updates state to
in_progress) - Client executes FFmpeg with template and hardware acceleration
- Client sends periodic progress updates (frame, fps, speed)
- Client sends heartbeats to maintain connection
- Client starts job (updates state to
-
Job Completion:
- Client reports completion with output metadata (size, bitrate)
- OR client reports failure with error message (increments retry count)
- Server updates job state and media file record
- Job removed from queue or requeued if failed
-
Monitoring:
- WebSocket connection provides real-time progress updates
- REST endpoints provide system status, client list, active jobs
media_files table:
- Stores file metadata and processing state
- Primary key:
path(file path) - Fields: size, bitrate, duration, resolution, frames, encoding_complexity, retry_count, processed, last_modified
encoding_jobs table:
- Stores job queue state
- Primary key:
id(UUID) - Fields: media_file_path, status, priority, assigned_client, timestamps, output details, error_message
- Foreign keys: media_files(path), clients(id)
clients table:
- Stores connected client information
- Primary key:
id(UUID) - Fields: display_name, computer_name, connected_at, last_heartbeat, disconnected_at
encoding_progress table:
- Stores real-time encoding progress
- Primary key:
job_id - Fields: frame, fps, bitrate, speed, updated_at
- Foreign key: encoding_jobs(id)
FFNodes is available on Windows, Linux, and MacOS, for both x86 and ARM architectures.
Note: FFNodes requires ffmpeg to be installed on your system and accessible from the command line. You can find ffmpeg here.
The server is a simple REST API that can be used to manage encoding jobs. You can Install the server in a few ways:
- Using Cargo Install:
cargo install ffnodes-server --git https://github.com/Drew-Chase/FFNodes.git - Using our installer script:
curl -fsSL https://raw.githubusercontent.com/Drew-Chase/FFNodes/main/install.sh | bash(linux only) - Using our MSI installer found in the releases page (windows only)
- Building from source:
git clone https://github.com/Drew-Chase/FFNodes.git && cd FFNodes && cargo build --release --bin ffnodes-server. - Using Docker:
docker run -p 8080:8080 ghcr.io/drew-chase/ffnodes-server:latest - Using Homebrew:
brew install drew-chase/tap/ffnodes-server(macos only) - Using winget:
winget install FFNodes.FFNodesServer(windows only)
The client is a GUI application that can be used to manage encoding jobs. You can install the client in a few ways:
- Using Cargo Install:
cargo install ffnodes-client --git https://github.com/Drew-Chase/FFNodes.git - Using our appimage file found in the releases page (linux only)
- Using our MSI installer found in the releases page (windows only)
- Building from source:
git clone https://github.com/Drew-Chase/FFNodes.git && cd FFNodes && cargo build --release --bin ffnodes-client. - Using Homebrew:
brew install drew-chase/tap/ffnodes-client(macos only) - Using winget:
winget install FFNodes.FFNodesClient(windows only)
The ffnodes-server can be deployed as a Docker container for easy deployment and management. Note: Only the server is containerized. The client is a desktop application that connects to the server.
# Pull latest image
docker pull ghcr.io/drew-chase/ffnodes/ffnodes-server:latest
# Run with default configuration
docker run -d \
--name ffnodes-server \
-p 8080:8080 \
-v $(pwd)/data:/app/data \
-v $(pwd)/logs:/app/logs \
ghcr.io/drew-chase/ffnodes/ffnodes-server:latestCreate docker-compose.yml:
version: '3.8'
services:
ffnodes-server:
image: ghcr.io/drew-chase/ffnodes/ffnodes-server:latest
container_name: ffnodes-server
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./data:/app/data
- ./logs:/app/logs
- /path/to/videos:/videos:ro # Mount video directories read-only
environment:
- RUST_LOG=info
- FFNODES_PORT=8080
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/api/status"]
interval: 30s
timeout: 10s
retries: 3Run:
docker-compose up -dThe container expects configuration at /app/data/config.json. On first run, a default configuration will be generated.
Extract the server GUID:
docker logs ffnodes-server | grep "Server GUID"Mount existing config:
docker run -d \
-p 8080:8080 \
-v $(pwd)/config.json:/app/data/config.json \
-v $(pwd)/data:/app/data \
ghcr.io/drew-chase/ffnodes/ffnodes-server:latestThe container uses the following volumes for persistent data:
/app/data- Configuration and SQLite database (app.db)/app/logs- Application logs- Video directories - Mount source directories as read-only with
:roflag
The Docker image supports multiple architectures:
linux/amd64- Intel/AMD x86_64linux/arm64- ARM 64-bit (Raspberry Pi 4, Apple Silicon, AWS Graviton)
Docker automatically pulls the correct architecture for your platform.
# Clone repository
git clone https://github.com/Drew-Chase/FFNodes.git
cd FFNodes
# Build for current architecture
docker build -t ffnodes-server .
# Build for specific platform
docker buildx build --platform linux/arm64 -t ffnodes-server:arm64 .The server uses a config.json file for configuration. On first launch, it will generate a default configuration with a new server GUID.
- Debug mode:
target/dev-env/server/config.json - Production:
./config.json(in the same directory as the executable)
{
"port": 8080,
"ffmpeg": "path/to/ffmpeg",
"ffprobe": "path/to/ffprobe",
"watch_directories": [
"/path/to/video/library1",
"/path/to/video/library2"
],
"server_guid": "550e8400-e29b-41d4-a716-446655440000",
"ffmpeg_template": "-c:v h264{HWACCEL_CODE} -preset medium -crf 23 -i {INPUT} {OUTPUT}",
"client_timeout_seconds": 300,
"notify_batch_interval_seconds": 30,
"max_concurrent_jobs_per_client": 4
}- port - HTTP server port (default: 8080)
- ffmpeg - Path to FFmpeg binary (auto-detected if in PATH)
- ffprobe - Path to FFprobe binary (auto-detected if in PATH)
- watch_directories - Array of directories to monitor for media files
- server_guid - Unique server identifier for client authentication (auto-generated)
- ffmpeg_template - FFmpeg command template with placeholders:
{HWACCEL_CODE}- Hardware acceleration suffix (e.g.,_nvenc,_amf,_qsv, or empty){INPUT}- Input file path{OUTPUT}- Output file path
- client_timeout_seconds - Time before reassigning jobs from unresponsive clients (default: 300)
- notify_batch_interval_seconds - File watcher batch processing interval (default: 30)
- max_concurrent_jobs_per_client - Maximum jobs per client (default: 4)
FFNodes automatically detects and processes these formats: webm, mkv, flv, vob, ogv, ogg, rrc, gifv, mng, mov, avi, qt, wmv, yuv, rm, asf, amv, mp4, m4p, m4v, mpg, mp2, mpeg, mpe, mpv, m4v, svi, 3gp, 3g2, mxf, roq, nsv, flv, f4v, f4p, f4a, f4b, mts, m2ts, ts
-
Launch the server:
ffnodes-server
-
On first launch, the server will:
- Generate
config.jsonwith a new GUID - Create
app.dbSQLite database - Display the server GUID in logs (needed for client connections)
- Start the HTTP server on configured port
- Generate
-
The server will log:
Server GUID: 550e8400-e29b-41d4-a716-446655440000 Starting production server at http://127.0.0.1:8080...
-
Handshake: Connect to the server with GUID
POST /api/handshake { "server_guid": "550e8400-e29b-41d4-a716-446655440000", "display_name": "My Workstation", "computer_name": "DESKTOP-ABC123" }Response includes auth token and FFmpeg template.
-
Request Job: Poll for available jobs
POST /api/jobs/request/{client_id}Returns highest priority job or 204 No Content if queue is empty.
-
Start Job: Mark job as in progress
POST /api/jobs/{job_id}/start -
Report Progress: Send periodic updates
POST /api/jobs/{job_id}/progress { "frame": 1250, "fps": 45.2, "bitrate": "2500kbits/s", "speed": "1.5x" } -
Complete Job: Report success or failure
POST /api/jobs/{job_id}/complete { "output_size": 125829120, "output_bitrate": 2500000 }or
POST /api/jobs/{job_id}/fail { "error": "FFmpeg process exited with code 1" } -
Heartbeat: Maintain connection
POST /api/heartbeat/{client_id}Send every 60 seconds to avoid timeout.
-
System Status:
GET /api/status{ "total_media_files": 1523, "pending_jobs": 45, "active_jobs": 8, "connected_clients": 3 } -
Connected Clients:
GET /api/clients[ { "id": "client-uuid", "display_name": "My Workstation", "computer_name": "DESKTOP-ABC123", "active_jobs": 2, "connected_at": "2025-01-15T10:30:00Z" } ] -
Active Jobs:
GET /api/jobs/active[ { "id": "job-uuid", "media_file_path": "/videos/movie.mp4", "status": "in_progress", "assigned_client": "client-uuid", "priority": 15000000, "started_at": "2025-01-15T10:35:00Z" } ] -
Real-time Progress: WebSocket at
ws://localhost:8080/api/ws/progress- Receives JSON events for job assignments, progress updates, completions, and client connections
For complete API specification with request/response schemas, see API.md.
Authentication
POST /api/handshake- Register client with server GUID
Job Management
POST /api/jobs/request/{client_id}- Request next jobPOST /api/jobs/{job_id}/start- Mark job as startedPOST /api/jobs/{job_id}/progress- Update progressPOST /api/jobs/{job_id}/complete- Mark job completePOST /api/jobs/{job_id}/fail- Mark job failedGET /api/jobs/active- Get all active jobs
Monitoring
GET /api/status- Get system statusGET /api/clients- Get connected clientsPOST /api/heartbeat/{client_id}- Send heartbeat
WebSocket
GET /api/ws/progress- Real-time progress updates
FFNodes is built using Rust for performance and safety.
The server uses the following technologies:
- Actix Web - For the REST API
- SQLX with SQLite - For storing job metadata and found media files
- Tokio - For asynchronous tasks
- Notify - For watching for media files
The client uses the following technologies: