>FFmpegLab Sign In
FFmpegLab Guide

AI Upscaling Pipeline – YAML & SQL Triggers, DNN & Supabase Storage

Declarative YAML pipeline for automated AI upscaling using DNN models (SRCNN) — upscale videos 2x, 3x, or 4x with SQL triggers and Supabase Storage.

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

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

User Uploads Low-Res Video → Supabase Storage → PostgreSQL Triggers → pgmq Queue → ffmpeglab-runner
Upscaled Video → Public Folder → pg_notify → User Notified

The pipeline consists of:

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

OutputFormatLocation
Upscaled Video (AI)MP4 (H.264) with SRCNN upscalingpublic-processed/{userId}/{pipelineId}/{runId}/upscaled/{{baseFilename}}_AI_2x.mp4
Upscaled Video (Bicubic)MP4 (H.264) with lanczos upscaling + sharpeningpublic-processed/{userId}/{pipelineId}/{runId}/upscaled/{{baseFilename}}_bicubic_2x.mp4
Real‑time notificationspg_notify channelsN/A
Job trackingrender tableExisting FFmpegLab table
Logslogpiece tableExisting FFmpegLab table

Prerequisites

The YAML‑Driven Approach

While you can write the SQL directly, the recommended way is to use the YAML transpiler. This gives you:

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.

ai-upscaling-pipeline.yaml
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:

# Download transpiler and SVG generator curl -O https://raw.githubusercontent.com/ffmpeglab/server/main/sdk/yaml/transpiler.ts curl -O https://raw.githubusercontent.com/ffmpeglab/server/main/sdk/yaml/svg.ts

Run the transpiler to generate the migration files:

# Generate the migration files deno run --allow-read --allow-write transpiler.ts ai-upscaling-pipeline.yaml ./supabase/migrations

Add the --svg flag to also generate a visual graph of your pipeline:

# Generate migration files + SVG graph deno run --allow-read --allow-write transpiler.ts ai-upscaling-pipeline.yaml ./supabase/migrations --svg

The output will be:

✅ Migration files created: UP: ./supabase/migrations/20260807120000_ai-upscaling.sql DOWN: ./supabase/migrations/20260807120000_ai-upscaling_down.sql SVG: ./supabase/migrations/20260807120000_ai-upscaling.svg

Apply the migration to your Supabase database:

# Via psql psql -U postgres -d your_database -f ./supabase/migrations/20260807120000_ai-upscaling.sql

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.

→ public-processed → public-processed 📤 upscale-uploads ai_upscale KEEP 📁 {userId}/ai-upscaling/{filename}/upsca… (slow) bicubic_upscale KEEP 📁 {userId}/ai-upscaling/{filename}/upsca… (slow)

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:

1. AI Upscaling with DNN (SRCNN)

ffmpeg -i $MEDIA_1 -vf "format=rgb24,dnn_processing=model=/app/models/sr/srcnn.xml:input=x:output=y:dnn_backend=openvino,scale=iw*2:ih*2" -c:v libx264 -crf 18 -pix_fmt yuv420p -y $OUTPUT_PATH
💡
DNN Upscaling Parameters Explained
  • 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)

ffmpeg -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
💡
Bicubic Upscaling Parameters Explained
  • 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)

OperationFFmpeg 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.

Step 1
Download SRCNN models
Download the model files for your chosen backend.
# OpenVINO format (recommended for Intel CPUs) wget -O srcnn.xml https://github.com/guoyejun/ffmpeg_dnn/raw/main/models/openvino/2021.1/super_resolution.xml wget -O srcnn.bin https://github.com/guoyejun/ffmpeg_dnn/raw/main/models/openvino/2021.1/super_resolution.bin

# TensorFlow format wget -O srcnn.pb https://github.com/guoyejun/ffmpeg_dnn/raw/main/models/tensorflow/srcnn.pb
Step 2
Place models in the runner
Copy the models to the runner's model directory.
# Create models directory in the runner mkdir -p /app/models/sr

# Copy models (adjust paths as needed) cp srcnn.xml /app/models/sr/ cp srcnn.bin /app/models/sr/
Step 3
Verify model files
List the model files to confirm they are present.
ls -la /app/models/sr/ # Should show srcnn.xml and srcnn.bin

Configure ffmpeglab-runner

The runner needs to be configured to poll the render queue (used by the transpiler) and execute the provided FFmpeg commands.

Step 1
Add the queue to your environment
Add the following to your .env file or Docker Compose configuration.
# Render queue (already used by FFmpegLab server) RENDER_QUEUE_NAME=render # DNN model path DNN_MODEL_PATH=/app/models/sr/srcnn.xml # DNN backend DNN_BACKEND=openvino # Upscaling factor (2, 3, 4) UPSCALE_FACTOR=2
Step 2
Mount models in Docker Compose
Add a volume mount for the models directory.
# In docker-compose.yml services: ffmpeglab-runner: volumes: - ./models:/app/models
Step 3
Implement the processing loop
The runner should execute the following steps for each job:
# 1. Connect to Supabase and listen to the queue

# 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
Step 4
Install FFmpeg with DNN support
FFmpeg must be compiled with DNN support for your chosen backend.
# For OpenVINO: ./configure --enable-libopenvino make && make install

# For TensorFlow: ./configure --enable-libtensorflow make && make install
Step 5
Restart the runner
After updating the environment, restart the runner service.
docker compose restart ffmpeglab-runner

Monitor the Pipeline

You can monitor the pipeline using SQL queries and notifications.

Step 1
Check queued jobs
Query the render table to see queued jobs.
SELECT * FROM "render" WHERE status = 'queued' ORDER BY created_at DESC;
Step 2
Check render status
Query the existing render table for job status.
SELECT id, title, status, progress, data FROM "render" WHERE project = 'ai-upscaling' ORDER BY created_at DESC;
Step 3
Listen to notifications
In your application, listen for real‑time updates.
-- In your PostgreSQL client: LISTEN render_status_channel; LISTEN log_channel;
Step 4
Check processed files
List all processed files in the public bucket.
SELECT name, metadata, created_at FROM storage.objects WHERE bucket_id = 'public-processed' ORDER BY created_at DESC;

Customising the Pipeline

Change Upscaling Factor

Update the UPSCALE_FACTOR environment variable in your runner:

# In your .env or docker-compose.yml UPSCALE_FACTOR=3

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:

# In your environment DNN_MODEL_PATH=/app/models/sr/espcn.xml DNN_INPUT=image DNN_OUTPUT=detection_out

Update the YAML command accordingly:

command: -i $MEDIA_1 -vf "format=rgb24,dnn_processing=model=$DNN_MODEL_PATH:input=$DNN_INPUT:output=$DNN_OUTPUT:dnn_backend=$DNN_BACKEND,scale=iw*$UPSCALE_FACTOR:ih*$UPSCALE_FACTOR" -c:v libx264 -crf 18 -pix_fmt yuv420p -y $OUTPUT_PATH

Add a Third Upscaling Method

Duplicate an existing step and modify the command:

- id: "neural_upscale" trigger: name: "handle_neural_upscale" event: "INSERT" table: "storage.objects" condition: | NEW.bucket_id = 'upscale-uploads' AND NEW.metadata->>'mimetype' LIKE 'video/%' command: -i $MEDIA_1 -vf "dnn_processing=model=neural_net.xml:input=x:output=y:dnn_backend=openvino,scale=iw*2:ih*2" -c:v libx264 -crf 18 -y $OUTPUT_PATH inputs: ["INPUT_FILE"] outputs: ["OUTPUT_FILE"] output_path: "{{userId}}/{{pipelineId}}/{{runId}}/upscaled/{{baseFilename}}_neural_2x.mp4" editor: output: "mp4" preset: "slow" selectedCode: "custom" keep: true

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:

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.