UGC Video Processing for Brands: Automate at Scale

March 18, 2026 ยท RenderIO

UGC processing is a scaling nightmare

Your brand receives 200 UGC videos per month from creators. Each video is different: shot on different phones, at different resolutions, in different aspect ratios, with different audio levels.

Before these videos can go on your social accounts, each one needs:

  1. Resolution normalized to your standard

  2. Aspect ratio adjusted for the target platform

  3. Audio levels normalized

  4. Brand intro/outro added

  5. Watermark/logo overlay

  6. Compressed for upload

Your social media manager processes 5 per day manually in Premiere. That's 100 per month. The other 100 sit in a Google Drive folder, unused.

The content exists. The bottleneck is processing.

The processing requirements

Input variety

Real UGC comes in every format:

SourceResolutionAspect RatioAudio
iPhone 15 Pro4K (3840x2160)9:16AAC, variable levels
Samsung Galaxy1080p or 4K9:16 or 16:9Various
Webcam720p-1080p16:9Often poor
Screen recordingVariable16:9System audio
DSLR/mirrorless4K16:9External mic or none

Output requirements

Every output should be:

  • 1080x1920 (9:16) for TikTok/Reels/Shorts

  • 1920x1080 (16:9) for YouTube/LinkedIn

  • Audio at -14 LUFS

  • Brand intro (2 seconds)

  • Logo watermark (top-right)

  • H.264, CRF 20, faststart

FFmpeg processing pipeline

Step 1: Normalize resolution and aspect ratio

For 9:16 output:

ffmpeg -i ugc-input.mp4 \
  -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[v]" \
  -map "[v]" -map 0:a \
  -c:v libx264 -crf 20 -c:a aac \
  normalized.mp4

This handles any input aspect ratio: landscape videos get center-cropped, portrait videos get scaled. The output is always exactly 1080x1920.

Step 2: Normalize audio

ffmpeg -i normalized.mp4 \
  -af "loudnorm=I=-14:TP=-2:LRA=7" \
  -c:v copy -c:a aac -b:a 128k \
  audio-fixed.mp4

Step 3: Add brand intro

ffmpeg -i brand-intro.mp4 -i audio-fixed.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 20 -c:a aac -b:a 128k \
  with-intro.mp4

Step 4: Add brand outro

ffmpeg -i with-intro.mp4 -i brand-outro.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 20 -c:a aac -b:a 128k \
  with-outro.mp4

Step 5: Add logo watermark

ffmpeg -i with-outro.mp4 -i brand-logo.png \
  -filter_complex "[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.4[logo];[0:v][logo]overlay=W-w-20:20[v]" \
  -map "[v]" -map 0:a \
  -c:v libx264 -crf 20 -c:a copy \
  branded.mp4

Step 6: Optimize for upload

ffmpeg -i branded.mp4 \
  -movflags +faststart \
  -c copy \
  final.mp4

Combined pipeline command

All steps (without intro/outro concatenation) in one command:

ffmpeg -i ugc-input.mp4 -i brand-logo.png \
  -filter_complex "\
    [0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[scaled];\
    [1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.4[logo];\
    [scaled][logo]overlay=W-w-20:20[v]" \
  -map "[v]" -map 0:a \
  -af "loudnorm=I=-14:TP=-2:LRA=7" \
  -c:v libx264 -crf 20 -preset medium \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  processed.mp4

Automate with RenderIO API

Process a single UGC video

curl -X POST https://renderio.dev/api/v1/run-ffmpeg-command \
  -H "X-API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "ffmpeg_command": "-i {in_video} -i {in_logo} -filter_complex \"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[scaled];[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.4[logo];[scaled][logo]overlay=W-w-20:20[v]\" -map \"[v]\" -map 0:a -af \"loudnorm=I=-14:TP=-2:LRA=7\" -c:v libx264 -crf 20 -c:a aac -b:a 128k -movflags +faststart {out_video}",
    "input_files": {
      "in_video": "https://storage.example.com/ugc/creator-video-001.mp4",
      "in_logo": "https://storage.example.com/brand/logo.png"
    },
    "output_files": { "out_video": "ugc-001-processed.mp4" }
  }'

Batch process all incoming UGC

async function processUGCBatch(ugcVideos) {
  const BRAND_LOGO = "https://storage.example.com/brand/logo.png";

  const UGC_PIPELINE = `-i {in_video} -i {in_logo} -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[scaled];[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.4[logo];[scaled][logo]overlay=W-w-20:20[v]" -map "[v]" -map 0:a -af "loudnorm=I=-14:TP=-2:LRA=7" -c:v libx264 -crf 20 -c:a aac -b:a 128k -movflags +faststart {out_video}`;

  const jobs = ugcVideos.map((video, i) =>
    fetch("https://renderio.dev/api/v1/run-ffmpeg-command", {
      method: "POST",
      headers: {
        "X-API-KEY": process.env.RENDERIO_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ffmpeg_command: UGC_PIPELINE,
        input_files: {
          in_video: video.url,
          in_logo: BRAND_LOGO,
        },
        output_files: { out_video: `ugc-${video.creatorId}-processed.mp4` },
      }),
    }).then(r => r.json())
  );

  return Promise.all(jobs);
}

// Process 50 UGC videos at once
const videos = await getUnprocessedUGCVideos();
const results = await processUGCBatch(videos);

50 videos process in parallel. Total time: roughly the same as processing one.

Multi-platform output

Each UGC video needs multiple platform versions:

async function processForAllPlatforms(videoUrl, creatorId) {
  const LOGO = "https://storage.example.com/brand/logo.png";

  const platforms = [
    {
      name: "tiktok",
      command: `-i {in_video} -i {in_logo} -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[s];[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.4[l];[s][l]overlay=W-w-20:20[v]" -map "[v]" -map 0:a -af "loudnorm=I=-14" -c:v libx264 -crf 22 -c:a aac -movflags +faststart {out_video}`,
    },
    {
      name: "youtube",
      command: `-i {in_video} -i {in_logo} -filter_complex "[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black[s];[1:v]scale=100:-1,format=rgba,colorchannelmixer=aa=0.3[l];[s][l]overlay=W-w-20:20[v]" -map "[v]" -map 0:a -af "loudnorm=I=-14" -c:v libx264 -crf 20 -c:a aac -movflags +faststart {out_video}`,
    },
    {
      name: "instagram",
      command: `-i {in_video} -i {in_logo} -filter_complex "[0:v]scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080[s];[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.4[l];[s][l]overlay=W-w-15:15[v]" -map "[v]" -map 0:a -af "loudnorm=I=-14" -c:v libx264 -crf 22 -c:a aac -movflags +faststart {out_video}`,
    },
  ];

  const jobs = platforms.map(p =>
    fetch("https://renderio.dev/api/v1/run-ffmpeg-command", {
      method: "POST",
      headers: {
        "X-API-KEY": process.env.RENDERIO_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ffmpeg_command: p.command,
        input_files: { in_video: videoUrl, in_logo: LOGO },
        output_files: { out_video: `${creatorId}-${p.name}.mp4` },
      }),
    }).then(r => r.json())
  );

  return Promise.all(jobs);
}

Automation workflow

Trigger on Google Drive upload

When creators upload UGC to a shared Google Drive folder:

  1. n8n watches the Google Drive folder for new files

  2. New file detected: Get the public URL

  3. HTTP Request: POST to RenderIO with the UGC pipeline

  4. Wait: Poll for completion

  5. Download: Get processed video URLs

  6. Upload: Move to "Processed" folder or upload to scheduling tool

Webhook-based processing

Configure RenderIO webhooks for completion notifications:

app.post("/webhook/ugc-processed", async (req, res) => {
  const { command_id, status, output_files } = req.body;

  if (status === "completed") {
    // Move to content library
    await addToContentLibrary({
      commandId: command_id,
      outputUrl: output_files["processed.mp4"],
      processedAt: new Date(),
    });

    // Notify social media manager
    await sendSlackNotification(
      `New UGC video processed: ${output_files["processed.mp4"]}`
    );
  }

  res.sendStatus(200);
});

Cost comparison

Approach200 UGC videos/monthCost
Manual editing (Premiere)40 hours @ $50/hr$2,000
Freelance editor200 videos @ $10/each$2,000
RenderIO API (3 platforms each)600 API calls$29

The API approach costs 98.5% less than manual editing. It also processes faster: 200 videos in minutes instead of a month of editing time.

Every UGC video your brand receives can be processed and ready for posting within minutes. No editors. No backlog. No wasted content.