AI upscaling is one of the most exciting applications of deep learning in video processing. Using FFmpeg's DNN (Deep Neural Network) filters, you can upscale low-resolution videos to 2x, 3x, or 4x their original resolution with remarkable quality — restoring detail, reducing artifacts, and breathing new life into old footage.
This guide shows you how to build a fully automated AI upscaling pipeline using a declarative YAML template and a transpiler that generates the complete SQL migration. The pipeline turns a low‑resolution video into a high‑quality upscaled masterpiece — all driven by PostgreSQL triggers and pgmq.
Key takeaways
- One YAML file — define your entire pipeline in a single, version‑controlled file.
- Automatic SQL generation — the transpiler produces the exact PostgreSQL migration.
- Zero manual intervention — upload a low-res video, get a high-res upscaled version.
- AI-powered upscaling — uses SRCNN and other DNN models for super‑resolution.
- Scalable and reliable — pgmq provides durable, transaction‑safe job queuing.
- Configurable — choose between 2x, 3x, and 4x upscaling factors.
- Multiple backends — supports TensorFlow, OpenVINO, Torch, and Native backends.
- Visual graph — understand your pipeline at a glance with an SVG diagram.
- Per‑run grouping – all outputs for a single upload are stored under a unique
runIdfolder.
The Gap: From Low Resolution to High Quality
Low‑resolution footage is everywhere: old home videos, legacy content, game captures, and low‑bitrate streams. Traditional upscaling (bicubic, lanczos) simply stretches pixels, creating blurry, artifact‑ridden results. AI upscaling uses deep learning to actually reconstruct missing detail, producing sharp, natural-looking results.
But AI upscaling is computationally intensive and time‑consuming. Doing it manually for every video is impractical. What if the pipeline could be fully automated — triggered by the upload itself, processing in the background, and delivering a high‑quality upscaled video when complete? And what if you could define that pipeline in a declarative YAML file that you can version, share, and reuse?
This guide shows you exactly how to build that pipeline.
Architecture Overview
Upscaled Video → Public Folder → pg_notify → User Notified
The pipeline consists of:
- Supabase Storage — two buckets:
upscale-uploads(per‑user) andpublic-processed(per‑user). - RLS policies — restrict access to each user's own folders.
- PostgreSQL triggers — one trigger per processing step. Each fires on
INSERTintostorage.objectswhen a file appears in a specific bucket. - pgmq — message queue for job processing (uses the existing
renderqueue). - ffmpeglab-runner — executes FFmpeg commands with DNN upscaling.
- pg_notify — real‑time status updates.
- YAML transpiler — reads the pipeline definition and generates the SQL migration + SVG graph.
Important: This pipeline uses the existing render and logpiece tables from the FFmpegLab server. It does not create new tables — it only adds the pipeline components.
What the Pipeline Delivers
| Output | Format | Location |
|---|---|---|
| Upscaled Video (AI) | MP4 (H.264) with SRCNN upscaling | public-processed/{userId}/{pipelineId}/{runId}/upscaled/{{baseFilename}}_AI_2x.mp4 |
| Upscaled Video (Bicubic) | MP4 (H.264) with lanczos upscaling + sharpening | public-processed/{userId}/{pipelineId}/{runId}/upscaled/{{baseFilename}}_bicubic_2x.mp4 |
| Real‑time notifications | pg_notify channels | N/A |
| Job tracking | render table | Existing FFmpegLab table |
| Logs | logpiece table | Existing FFmpegLab table |
Prerequisites
- A Supabase project (cloud or self‑hosted).
- ffmpeglab-server and ffmpeglab-runner deployed.
- FFmpeg compiled with DNN support (
--enable-libopenvinoor--enable-libtensorflow). - DNN model files downloaded (see Downloading DNN Models).
- The
renderandlogpiecetables must already exist (created by the FFmpegLab server migrations). - Access to your Supabase database (psql or the Supabase SQL Editor).
- Deno installed to run the transpiler.
The YAML‑Driven Approach
While you can write the SQL directly, the recommended way is to use the YAML transpiler. This gives you:
- Declarative pipeline definition — define steps, triggers, and buckets in clean YAML.
- Automatic SQL generation — the transpiler produces the exact PostgreSQL migration.
- Visual pipeline graph — generate an SVG diagram of your pipeline with
--svg. - Reusable templates — share and version your pipeline definitions.
The transpiler is a single TypeScript file that reads your YAML and generates the SQL migration. It runs with Deno and has zero external dependencies (except yaml for parsing).
The YAML Template
Create a file called ai-upscaling-pipeline.yaml with the following content. It defines the buckets, RLS policies, and each processing step. The runId section configures how the per‑run ID is generated — in this case, deterministically from the input file name.
name: "AI Upscaling Pipeline" pipelineId: "ai-upscaling" runId: mode: "deterministic" template: "{baseFilename}" description: "Automated AI upscaling using DNN models (SRCNN) and bicubic fallback" version: "1.0.0" editor: compressionLevel: 23 preset: "medium" aspectRatio: "16:9" framerate: 30 opacity: 1.0 output: "mp4" storage: output_bucket: "public-processed" buckets: - name: "upscale-uploads" public: false allowed_mime_types: - "video/mp4" - "video/quicktime" - "video/x-msvideo" - "video/webm" - "video/mpeg" - name: "public-processed" public: true allowed_mime_types: - "video/mp4" rls_policies: - name: "Users can upload to their own folder" operation: "INSERT" role: "authenticated" condition: | bucket_id = 'upscale-uploads' AND (storage.foldername(name))[1] = auth.uid()::text - name: "Users can download from their own folder" operation: "SELECT" role: "authenticated" condition: | bucket_id = 'upscale-uploads' AND (storage.foldername(name))[1] = auth.uid()::text - name: "Public read access to processed media" operation: "SELECT" role: "anon" condition: | bucket_id = 'public-processed' - name: "Service role can manage processed media" operation: "ALL" role: "service_role" condition: | bucket_id = 'public-processed' - name: "Users can read their own processed media" operation: "SELECT" role: "authenticated" condition: | bucket_id = 'public-processed' AND (storage.foldername(name))[1] = auth.uid()::text steps: # Step 1: AI Upscaling with DNN (SRCNN) - id: "ai_upscale" trigger: name: "handle_ai_upscale" event: "INSERT" table: "storage.objects" condition: | NEW.bucket_id = 'upscale-uploads' AND NEW.metadata->>'mimetype' LIKE 'video/%' command: -i $MEDIA_1 -vf "format=rgb24,dnn_processing=model=$DNN_MODEL_PATH:input=x:output=y:dnn_backend=$DNN_BACKEND,scale=iw*$UPSCALE_FACTOR:ih*$UPSCALE_FACTOR" -c:v libx264 -crf 18 -pix_fmt yuv420p -y $OUTPUT_PATH inputs: ["INPUT_FILE"] outputs: ["OUTPUT_FILE"] output_path: "{{userId}}/{{pipelineId}}/{{runId}}/upscaled/{{baseFilename}}_AI_${UPSCALE_FACTOR}x.mp4" editor: output: "mp4" preset: "slow" selectedCode: "custom" width: 0 height: 0 compressionLevel: 18 keep: true # Step 2: Bicubic Upscaling (Fallback) - id: "bicubic_upscale" trigger: name: "handle_bicubic_upscale" event: "INSERT" table: "storage.objects" condition: | NEW.bucket_id = 'upscale-uploads' AND NEW.metadata->>'mimetype' LIKE 'video/%' command: -i $MEDIA_1 -vf "scale=iw*$UPSCALE_FACTOR:ih*$UPSCALE_FACTOR:flags=lanczos,unsharp=5:5:1.5:5:5:0.5" -c:v libx264 -crf 18 -pix_fmt yuv420p -y $OUTPUT_PATH inputs: ["INPUT_FILE"] outputs: ["OUTPUT_FILE"] output_path: "{{userId}}/{{pipelineId}}/{{runId}}/upscaled/{{baseFilename}}_bicubic_${UPSCALE_FACTOR}x.mp4" editor: output: "mp4" preset: "slow" selectedCode: "custom" width: 0 height: 0 compressionLevel: 18 keep: true render: project_name: "upscaling" status: "queued" public: false
The keep: true flag on both steps tells the transpiler to send the output directly to the final bucket (public-processed). The runId is computed deterministically from the input file name (using mode: "deterministic" and template: "{baseFilename}"). This ensures all steps in the parallel pipeline compute the same run ID, grouping all outputs for a single upload under one folder.
Running the Transpiler
Download the transpiler and the SVG generator:
Run the transpiler to generate the migration files:
Add the --svg flag to also generate a visual graph of your pipeline:
The output will be:
Apply the migration to your Supabase database:
Visualising the Pipeline
The generated SVG gives you a clear overview of your pipeline. Steps marked with KEEP are green – their outputs are permanently stored in the final bucket. Edges are labelled with the bucket they use for data flow.
In the graph above, both steps trigger on the same upscale-uploads bucket and output directly to public-processed. This parallel execution allows both AI and bicubic upscaling to run simultaneously, giving you a comparison of results.
FFmpeg Commands
The YAML steps define the following FFmpeg commands using placeholders:
$MEDIA_1— The path to the downloaded input file (resolved by the runner).$OUTPUT_PATH— The temporary path for the output file (resolved by the runner).$DNN_MODEL_PATH,$DNN_BACKEND,$UPSCALE_FACTOR— Environment variables for model configuration.
1. AI Upscaling with DNN (SRCNN)
format=rgb24— DNN models typically expect RGB24 format.dnn_processing— FFmpeg's DNN processing filter.model=$DNN_MODEL_PATH— Path to the SRCNN model (OpenVINO format).input=x:output=y— Input and output tensor names for the model.dnn_backend=$DNN_BACKEND— DNN backend (openvino, tensorflow, native, torch).scale=iw*$UPSCALE_FACTOR:ih*$UPSCALE_FACTOR— Upscale the DNN output to the target size.-crf 18— High quality encoding for the upscaled result.-y— Overwrite output file without prompting.
2. Bicubic Upscaling (Fallback)
scale=iw*$UPSCALE_FACTOR:ih*$UPSCALE_FACTOR:flags=lanczos— Lanczos scaling (highest quality).unsharp=5:5:1.5:5:5:0.5— Unsharp mask to restore sharpness after scaling.-crf 18— High quality encoding.-y— Overwrite output file without prompting.
FFmpeg Command Table (Quick Reference)
| Operation | FFmpeg Command |
|---|---|
| 2x AI Upscaling (SRCNN) | ffmpeg -i input.mp4 -vf "format=rgb24,dnn_processing=model=srcnn.xml:input=x:output=y:dnn_backend=openvino,scale=iw*2:ih*2" -c:v libx264 -crf 18 output.mp4 |
| 3x AI Upscaling (SRCNN) | ffmpeg -i input.mp4 -vf "format=rgb24,dnn_processing=model=srcnn.xml:input=x:output=y:dnn_backend=openvino,scale=iw*3:ih*3" -c:v libx264 -crf 18 output.mp4 |
| 4x AI Upscaling (SRCNN) | ffmpeg -i input.mp4 -vf "format=rgb24,dnn_processing=model=srcnn.xml:input=x:output=y:dnn_backend=openvino,scale=iw*4:ih*4" -c:v libx264 -crf 18 output.mp4 |
| Bicubic Upscaling (Fallback) | ffmpeg -i input.mp4 -vf "scale=iw*2:ih*2:flags=lanczos,unsharp=5:5:1.5:5:5:0.5" -c:v libx264 -crf 18 output.mp4 |
Downloading DNN Models
The pipeline uses SRCNN (Super-Resolution Convolutional Neural Network) models. You can download them from the FFmpeg DNN model repository.
# TensorFlow format wget -O srcnn.pb https://github.com/guoyejun/ffmpeg_dnn/raw/main/models/tensorflow/srcnn.pb
# Copy models (adjust paths as needed) cp srcnn.xml /app/models/sr/ cp srcnn.bin /app/models/sr/
Configure ffmpeglab-runner
The runner needs to be configured to poll the render queue (used by the transpiler) and execute the provided FFmpeg commands.
.env file or Docker Compose configuration.# 2. For each job:
# a. Download the input file from upscale-uploads
# b. Parse the 'commands' array from the job payload
# c. For each command:
# - Replace 'INPUT_FILE' with the local input path
# - Replace 'OUTPUT_FILE' with a temporary local path
# - Execute the FFmpeg command
# - Upload the output file to public-processed/{output_path}
# - Update the render table with progress and logs
# d. Mark the job as complete in the render table
# e. Delete the job from the queue
# For TensorFlow: ./configure --enable-libtensorflow make && make install
Monitor the Pipeline
You can monitor the pipeline using SQL queries and notifications.
render table for job status.Customising the Pipeline
Change Upscaling Factor
Update the UPSCALE_FACTOR environment variable in your runner:
The YAML command uses $UPSCALE_FACTOR so it will automatically apply the new value.
Use a Different DNN Model
Replace the model path and tensor names:
Update the YAML command accordingly:
Add a Third Upscaling Method
Duplicate an existing step and modify the command:
Frequently Asked Questions (FAQ)
What models are used for AI upscaling?
The pipeline uses FFmpeg's dnn_processing filter with SRCNN (Super-Resolution Convolutional Neural Network) models. SRCNN is a lightweight model that works well for 2x upscaling. For higher upscaling factors, you can use ESPCN or other models.
What upscaling factors are supported?
The pipeline supports 2x, 3x, and 4x upscaling. The default is 2x upscaling using the SRCNN model. You can configure the scale factor in the runner's environment variable (UPSCALE_FACTOR).
What DNN backends are supported?
FFmpeg supports TensorFlow, OpenVINO, Torch, and Native DNN backends. The pipeline uses OpenVINO by default as it provides the best performance for Intel CPUs and GPUs. You can change the backend in the dnn_processing filter.
Is this pipeline suitable for real-time upscaling?
No. DNN-based upscaling is computationally intensive. A 5-minute video can take 1-2 hours to upscale, depending on the resolution and hardware. This pipeline is designed for batch processing where time is not critical.
How do I improve upscaling quality?
To improve quality, you can: (1) Use a larger model like EDSR or Real-ESRGAN (requires custom model conversion), (2) Increase the bitrate (-b:v) or use -crf 14, (3) Use the unsharp filter after upscaling to restore sharpness.
Does this pipeline create new tables?
No. The pipeline uses the existing render and logpiece tables from the FFmpegLab server. It only adds storage buckets, RLS policies, and the trigger function — no table conflicts.
Final Word
You now have a fully automated AI upscaling pipeline defined in YAML and generated via a transpiler. With PostgreSQL triggers, pgmq, and Supabase Storage, you get:
- AI-powered upscaling — SRCNN models for super‑resolution
- Multiple upscaling factors — 2x, 3x, or 4x
- Multiple DNN backends — TensorFlow, OpenVINO, Torch, Native
- Real‑time notifications — know when processing is complete
- Full observability — monitoring views and logs
- Declarative YAML — version‑controlled, reusable pipeline definitions
- Per‑run grouping via deterministic
runId– all outputs for one upload stay together.
The pipeline is production‑ready, scalable, and configurable. It uses the existing render and logpiece tables from the FFmpegLab server, so there are no table conflicts — just pure, AI-powered upscaling.
Important: DNN upscaling is computationally intensive. Start with small test clips and monitor your runner's CPU usage before scaling up to larger videos.