AutomationFlowsData & Sheets › Kz 03 Edit Render

Kz 03 Edit Render

KZ-03-Edit-Render. Uses supabase, executeCommand, airtable, telegram. Webhook trigger; 12 nodes.

Webhook trigger★★★★☆ complexity12 nodesSupabaseExecute CommandAirtableTelegram
Data & Sheets Trigger: Webhook Nodes: 12 Complexity: ★★★★☆ Added:

This workflow follows the Supabase → Telegram recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "KZ-03-Edit-Render",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "events/render",
        "responseMode": "responseNode"
      },
      "name": "Webhook /events/render",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "tableId": "candidates",
        "filterType": "manual",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "candidate_id",
              "condition": "eq",
              "keyValue": "={{$json.body.candidate_id}}"
            }
          ]
        }
      },
      "name": "Load Candidate",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        460,
        300
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "// Build per-platform variant specs from one candidate.\n// Default: 2 variants per platform (different hook + slight pacing tweak).\nconst c = $input.first().json[0];\nconst platforms = ['TT','IG','YT','FB'];\nconst hooks = (c.hooks || []).slice(0, 4); // ensure at least 4\nconst specs = [];\nfor (const p of platforms) {\n  for (let v = 1; v <= 2; v++) {\n    const hook = hooks[(v - 1) % hooks.length];\n    specs.push({\n      candidate_id: c.candidate_id,\n      clip_id:      c.clip_id,\n      template:     c.template,\n      start_s:      c.start_s,\n      end_s:        c.end_s,\n      platform:     p,\n      variant:      `v${v}`,\n      hook:         hook,\n      caption_pack: (c.captions || {})[p.toLowerCase()],\n      viral_score:  c.viral_score\n    });\n  }\n}\nreturn specs.map(s => ({ json: s }));"
      },
      "name": "Pick Variants",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "name": "Loop Variants",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "command": "=set -e;\nKEY=$(echo '{{JSON.stringify($json)}}' | jq -r '.clip_id');\nrclone copy r2:${R2_BUCKET}/raw/.../${KEY}.mp4 /tmp/;"
      },
      "name": "Download Source",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "// Generate subs.ass from Whisper SRT chunks + hook overlay.\n// (Reference impl - real code lives on the render VPS as build_ass.py)\nconst { hook, subtitles_chunks } = $input.first().json;\nconst events = [];\nevents.push(`Dialogue: 0,0:00:00.00,0:00:01.40,Hook,,0,0,0,,{\\\\fad(40,40)}${hook.text.toUpperCase()}`);\nfor (const c of subtitles_chunks || []) {\n  events.push(`Dialogue: 0,${secToAss(c.start)},${secToAss(c.end)},Default,,0,0,0,,{\\\\fad(40,40)}${c.text}`);\n}\nfunction secToAss(s){const h=Math.floor(s/3600),m=Math.floor(s/60)%60,ss=(s%60).toFixed(2).padStart(5,'0');return `${h}:${m.toString().padStart(2,'0')}:${ss}`;}\nreturn [{ json: { ...$input.first().json, ass: events.join('\\n') } }];"
      },
      "name": "Build ASS",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {
        "command": "=set -e;\nSPEC='{{JSON.stringify($json)}}';\nCLIP=$(jq -r '.clip_id' <<<$SPEC);\nSTART=$(jq -r '.start_s' <<<$SPEC);\nEND=$(jq -r '.end_s' <<<$SPEC);\nPLATFORM=$(jq -r '.platform' <<<$SPEC);\nVARIANT=$(jq -r '.variant' <<<$SPEC);\nMOOD=mood\nOUT=/tmp/${CLIP}-${PLATFORM}-${VARIANT}.mp4\necho $(jq -r '.ass' <<<$SPEC) > /tmp/${CLIP}-${PLATFORM}-${VARIANT}.ass\nffmpeg -y -ss $START -to $END -i /tmp/${CLIP}.mp4 -i /opt/music/${MOOD}.mp3 \\\n  -filter_complex \"[0:v]scale=1080:-2,crop=1080:1920,setpts=PTS-STARTPTS,eq=contrast=1.08:saturation=1.15,subtitles=/tmp/${CLIP}-${PLATFORM}-${VARIANT}.ass[v];[0:a]volume=0.9,afade=t=in:d=0.3[a1];[1:a]volume=0.6,afade=t=in:d=0.3[a2];[a1][a2]amix=inputs=2:duration=longest[a]\" \\\n  -map \"[v]\" -map \"[a]\" -c:v libx264 -preset veryfast -crf 19 -profile:v high -pix_fmt yuv420p -c:a aac -b:a 192k -ar 48000 -movflags +faststart $OUT\nls -la $OUT"
      },
      "name": "FFmpeg Render",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        1560,
        300
      ]
    },
    {
      "parameters": {
        "command": "=ffprobe -v error -show_streams -show_format -of json /tmp/{{$json.clip_id}}-{{$json.platform}}-{{$json.variant}}.mp4"
      },
      "name": "QA Check",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        1780,
        300
      ]
    },
    {
      "parameters": {
        "command": "=rclone copy /tmp/{{$json.clip_id}}-{{$json.platform}}-{{$json.variant}}.mp4 r2:${R2_BUCKET}/processed/{{$now.year}}/{{$now.month.padStart(2,'0')}}/{{$now.day.padStart(2,'0')}}/"
      },
      "name": "R2 Upload Rendered",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        2000,
        300
      ]
    },
    {
      "parameters": {
        "tableId": "posts",
        "dataMode": "autoMapInputData",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "post_id",
              "fieldValue": "={{$json.clip_id}}-{{$json.platform}}-{{$json.variant}}"
            },
            {
              "fieldId": "candidate_id",
              "fieldValue": "={{$json.candidate_id}}"
            },
            {
              "fieldId": "clip_id",
              "fieldValue": "={{$json.clip_id}}"
            },
            {
              "fieldId": "platform",
              "fieldValue": "={{$json.platform}}"
            },
            {
              "fieldId": "variant",
              "fieldValue": "={{$json.variant}}"
            },
            {
              "fieldId": "asset_url",
              "fieldValue": "=https://...r2.../processed/.../{{$json.clip_id}}-{{$json.platform}}-{{$json.variant}}.mp4"
            },
            {
              "fieldId": "caption",
              "fieldValue": "={{$json.caption_pack.caption}}"
            },
            {
              "fieldId": "hashtags",
              "fieldValue": "={{JSON.stringify($json.caption_pack.hashtags)}}"
            },
            {
              "fieldId": "hook_text",
              "fieldValue": "={{$json.hook.text}}"
            },
            {
              "fieldId": "hook_id",
              "fieldValue": "={{$json.hook.hook_id}}"
            },
            {
              "fieldId": "viral_score_pre",
              "fieldValue": "={{$json.viral_score}}"
            },
            {
              "fieldId": "template",
              "fieldValue": "={{$json.template}}"
            },
            {
              "fieldId": "status",
              "fieldValue": "={{ ($json.viral_score >= 80) ? 'auto_ok' : 'pending' }}"
            }
          ]
        }
      },
      "name": "Insert Post",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        2220,
        300
      ],
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "create",
        "base": "={{$env[\"AIRTABLE_BASE_ID\"]}}",
        "table": "Queue",
        "fieldsUi": {
          "fieldsValues": [
            {
              "fieldName": "Post ID",
              "fieldValue": "={{$json.post_id}}"
            },
            {
              "fieldName": "Hook",
              "fieldValue": "={{$json.hook.text}}"
            },
            {
              "fieldName": "Caption",
              "fieldValue": "={{$json.caption_pack.caption}}"
            },
            {
              "fieldName": "Platform",
              "fieldValue": "={{$json.platform}}"
            },
            {
              "fieldName": "Variant",
              "fieldValue": "={{$json.variant}}"
            },
            {
              "fieldName": "Viral score",
              "fieldValue": "={{$json.viral_score}}"
            }
          ]
        }
      },
      "name": "Airtable Queue",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2,
      "position": [
        2440,
        300
      ],
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendMessage",
        "chatId": "={{$env[\"TELEGRAM_OPS_CHAT_ID\"]}}",
        "text": "=\u2702\ufe0f Variant ready: {{$json.post_id}} \u2014 score {{$json.viral_score}}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "name": "Telegram Notify",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2660,
        300
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook /events/render": {
      "main": [
        [
          {
            "node": "Load Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Candidate": {
      "main": [
        [
          {
            "node": "Pick Variants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick Variants": {
      "main": [
        [
          {
            "node": "Loop Variants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Variants": {
      "main": [
        [
          {
            "node": "Download Source",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Source": {
      "main": [
        [
          {
            "node": "Build ASS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build ASS": {
      "main": [
        [
          {
            "node": "FFmpeg Render",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FFmpeg Render": {
      "main": [
        [
          {
            "node": "QA Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QA Check": {
      "main": [
        [
          {
            "node": "R2 Upload Rendered",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "R2 Upload Rendered": {
      "main": [
        [
          {
            "node": "Insert Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Post": {
      "main": [
        [
          {
            "node": "Airtable Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable Queue": {
      "main": [
        [
          {
            "node": "Telegram Notify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    "kontent-zavod",
    "render",
    "ffmpeg"
  ]
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

KZ-03-Edit-Render. Uses supabase, executeCommand, airtable, telegram. Webhook trigger; 12 nodes.

Source: https://github.com/alexdmitrievi/Kontent_zavod_podryadpro/blob/claude/ai-content-factory-design-hzhVn/workflows/03-edit-render.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Buildnbloom - Typeform to Tier 1 Call. Uses supabase, airtable, httpRequest. Webhook trigger; 17 nodes.

Supabase, Airtable, HTTP Request
Data & Sheets

KZ-02-Classification. Uses supabase, executeCommand, httpRequest. Webhook trigger; 14 nodes.

Supabase, Execute Command, HTTP Request
Data & Sheets

This workflow automatically monitors a Facebook post, extracts comments, enforces a "past winner" blocklist, analyzes sentiment using AI to find positive entries, randomly selects a winner, stores the

Airtable, OpenAI Chat, Telegram +3
Data & Sheets

webhook - simulador PDV (fluxo). Uses supabase. Webhook trigger; 55 nodes.

Supabase
Data & Sheets

Reddit Monitor Master v3. Uses airtable, supabase, slack, httpRequest. Event-driven trigger; 52 nodes.

Airtable, Supabase, Slack +2