AutomationFlowsWeb Scraping › Zero-cost Social Pipeline (m5)

Zero-cost Social Pipeline (m5)

Zero-Cost Social Pipeline (M5). Uses executeCommand, httpRequest. Webhook trigger; 15 nodes.

Webhook trigger★★★★☆ complexity15 nodesExecute CommandHTTP Request
Web Scraping Trigger: Webhook Nodes: 15 Complexity: ★★★★☆ Added:

This workflow follows the Executecommand → HTTP Request 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": "Zero-Cost Social Pipeline (M5)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "social-pipeline",
        "responseMode": "onReceived",
        "responseData": "noData",
        "options": {}
      },
      "id": "node-webhook-01",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "notes": "Receives POST { videoPath, prompt, context?, platform? }. platform defaults to telegram. Responds immediately."
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst body = item.body && typeof item.body === 'object' ? item.body : item;\nconst videoPath = body.videoPath || item.videoPath;\nconst prompt = body.prompt || item.prompt;\nif (!videoPath || typeof videoPath !== 'string') {\n  throw new Error('Missing or empty videoPath in webhook payload');\n}\nif (!prompt || typeof prompt !== 'string') {\n  throw new Error('Missing or empty prompt in webhook payload');\n}\nconst denoiseRaw = body.denoise ?? item.denoise ?? 'off';\nconst denoise = ['off', 'light', 'strong'].indexOf(denoiseRaw) !== -1 ? denoiseRaw : 'off';\nconst subtitleYRaw = body.subtitleY ?? item.subtitleY ?? 0.70;\nconst subtitleY = typeof subtitleYRaw === 'number' && subtitleYRaw >= 0 && subtitleYRaw <= 1 ? subtitleYRaw : 0.70;\nconst subtitleSizeRaw = body.subtitleSize ?? item.subtitleSize ?? 'medium';\nconst subtitleSize = ['small', 'medium', 'large'].indexOf(subtitleSizeRaw) !== -1 ? subtitleSizeRaw : 'medium';\nconst caption = body.caption || '';\nconst hashtags = Array.isArray(body.hashtags) ? body.hashtags : [];\nreturn [{\n  json: {\n    ...item,\n    id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),\n    videoPath,\n    prompt,\n    context: body.context ?? item.context ?? null,\n    platform: body.platform || item.platform || 'telegram',\n    denoise,\n    subtitleY,\n    subtitleSize,\n    caption,\n    hashtags\n  }\n}];"
      },
      "id": "node-preflight-02",
      "name": "Preflight",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        0
      ],
      "notes": "Validates videoPath and prompt, defaults platform to telegram, generates the run id used in filenames, passes the payload through."
    },
    {
      "parameters": {
        "command": "={{ 'D:/Projects/social-media-automation/.venv/Scripts/python.exe D:/Projects/social-media-automation/scripts/video_pipeline.py --transcribe-only --input \"' + $json.videoPath + '\" --transcript-output \"' + 'D:/Projects/social-media-automation/output/transcript_' + $json.id + '.json\"' }}"
      },
      "id": "node-transcribe-12",
      "name": "Transcribe",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        800,
        0
      ],
      "notes": "Runs faster-whisper tiny/int8 via video_pipeline --transcribe-only. Transcript text is printed between TRANSCRIPT_TEXT_BEGIN/END markers on stdout."
    },
    {
      "parameters": {
        "jsCode": "const exec = $input.first().json;\nconst stdout = exec.stdout || '';\nconst pre = $('Preflight').first().json;\nlet transcript = '';\nconst m = stdout.match(/TRANSCRIPT_TEXT_BEGIN\\n([\\s\\S]*?)TRANSCRIPT_TEXT_END/);\nif (m) transcript = m[1].trim();\nconst isAuto = !pre.prompt || pre.prompt.trim() === '' || pre.prompt.trim().toLowerCase() === 'auto';\nlet prompt = pre.prompt;\nlet context = pre.context;\nif (isAuto) {\n  prompt = transcript\n    ? 'Write an engaging short-form social media post based on the spoken content of this video clip. Use the actual speech as the source material.'\n    : 'Write an engaging short-form social media post about this video clip. Make it energetic and scroll-stopping.';\n  context = transcript || null;\n}\nreturn [{\n  json: {\n    id: pre.id,\n    videoPath: pre.videoPath,\n    prompt,\n    context,\n    platform: pre.platform,\n    denoise: pre.denoise,\n    subtitleY: pre.subtitleY,\n    subtitleSize: pre.subtitleSize,\n    transcriptPath: 'D:/Projects/social-media-automation/output/transcript_' + pre.id + '.json'\n  }\n}];"
      },
      "id": "node-prepare-gemini-13",
      "name": "PrepareGemini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        0
      ],
      "notes": "When the prompt is empty/'auto', switches to a subtitle-based prompt and feeds the whisper transcript as context so Gemini writes caption/description/tags from the actual speech."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash-lite:generateContent?key=' + $env.GEMINI_API_KEY }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ systemInstruction: { parts: [{ text: 'You are an expert social media content strategist. You write captions that stop the scroll. Respond with a single JSON object and nothing else. Use exactly this shape: {\"caption\": \"...\", \"description\": \"...\", \"hashtags\": [\"...\"], \"hook\": \"...\"}. Rules: caption is the main post caption, at most 220 characters. description is a longer description or alt text for the video, at most 1000 characters. hashtags is an array of 2 to 8 hashtags as plain words, omit the # symbol, all lowercase, each at most 25 characters, no spaces. hook is a short opening line that grabs attention, at most 60 characters. Write engaging, human, energetic copy. Do not wrap the JSON in markdown fences.' }] }, contents: [{ parts: [{ text: 'Write a social media post about: ' + $json.prompt + ($json.context ? '\\nBackground context:\\n' + $json.context : '') }] }], generationConfig: { responseMimeType: 'application/json', temperature: 0.8, maxOutputTokens: 1024 } }) }}"
      },
      "id": "node-gemini-03",
      "name": "Gemini",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1600,
        0
      ],
      "notes": "Calls Gemini free tier generateContent. Requires GEMINI_API_KEY to be available to n8n as an environment variable."
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst candidates = item.candidates || [];\nconst parts = (candidates[0] && candidates[0].content && candidates[0].content.parts) || [];\nconst text = parts.map(function (p) { return p.text ? p.text : ''; }).join('').trim();\nconst cleaned = text.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '').trim();\nlet parsed;\ntry {\n  parsed = JSON.parse(cleaned);\n} catch (e) {\n  throw new Error('Gemini response was not valid JSON: ' + text);\n}\nconst required = ['caption', 'description', 'hashtags', 'hook'];\nconst missing = required.filter(function (k) { return parsed[k] === undefined || parsed[k] === null || parsed[k] === ''; });\nif (missing.length > 0) {\n  throw new Error('Gemini response is missing required field(s): ' + missing.join(', '));\n}\nconst meta = $('PrepareGemini').first().json;\nreturn [{\n  json: {\n    ...meta,\n    caption: parsed.caption,\n    description: parsed.description,\n    hashtags: parsed.hashtags,\n    hook: parsed.hook\n  }\n}];"
      },
      "id": "node-parse-gemini-04",
      "name": "ParseGemini",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        0
      ],
      "notes": "Extracts the JSON object from the Gemini response parts, validates the four required fields, and passes through the run metadata from PrepareGemini."
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst pre = $('Preflight').first().json;\nconst meta = {\n  caption: pre.caption || item.caption || '',\n  description: pre.description || item.description || '',\n  hashtags: pre.hashtags && pre.hashtags.length ? pre.hashtags : (item.hashtags || []),\n  hook: pre.hook || item.hook || '',\n  platform: item.platform || 'telegram'\n};\nconst metaB64 = Buffer.from(JSON.stringify(meta), 'utf8').toString('base64');\nreturn [{ json: { ...item, meta, metaB64 } }];"
      },
      "id": "node-encode-meta-14",
      "name": "EncodeMeta",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2400,
        0
      ],
      "notes": "Packs caption/description/hashtags/hook/platform into base64 so it survives the command line on its way to disk."
    },
    {
      "parameters": {
        "command": "={{ 'D:/Projects/social-media-automation/.venv/Scripts/python.exe D:/Projects/social-media-automation/scripts/store_meta.py \"' + $json.id + '\" \"' + $json.metaB64 + '\"' }}"
      },
      "id": "node-store-meta-15",
      "name": "StoreMeta",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        2800,
        0
      ],
      "notes": "Writes output/meta_<id>.json so the bot can publish after the user approves the preview."
    },
    {
      "parameters": {
        "command": "={{ 'D:/Projects/social-media-automation/.venv/Scripts/python.exe D:/Projects/social-media-automation/scripts/video_pipeline.py --input \"' + $('ParseGemini').first().json.videoPath + '\" --no-caption --output \"' + 'D:/Projects/social-media-automation/output/final_' + $('ParseGemini').first().json.id + '.mp4\" --denoise \"' + ($('ParseGemini').first().json.denoise || 'off') + '\" --subtitles --transcript \"' + $('ParseGemini').first().json.transcriptPath + '\" --subtitle-y \"' + $('ParseGemini').first().json.subtitleY + '\" --subtitle-size \"' + $('ParseGemini').first().json.subtitleSize + '\"' }}"
      },
      "id": "node-run-video-05",
      "name": "RunVideoPipeline",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        3200,
        0
      ],
      "notes": "Runs the MoviePy/FFmpeg pipeline: 9:16 crop, denoise, and burned-in subtitles reused from the transcript. Caption text is NOT burned on the video (--no-caption); caption/description/tags are published as post text."
    },
    {
      "parameters": {
        "jsCode": "const cmd = $input.first().json;\nconst stdout = cmd.stdout || '';\nconst match = stdout.match(/OUTPUT_VIDEO=(\\S+)/);\nconst outputFile = match ? match[1] : '';\nconst meta = $('ParseGemini').first().json;\nreturn [{\n  json: {\n    ...meta,\n    exitCode: cmd.exitCode,\n    stderr: cmd.stderr,\n    stdout: cmd.stdout,\n    outputFile,\n    fileExists: outputFile.length > 0 && cmd.exitCode === 0,\n    outputSize: outputFile.length > 0 ? 1 : 0\n  }\n}];\n"
      },
      "id": "node-check-output-06",
      "name": "CheckOutputFile",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        0
      ],
      "notes": "Verifies the generated mp4 exists on disk and is non-empty."
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-output-exists-01",
              "leftValue": "={{ $json.fileExists }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-output-exists-07",
      "name": "OutputExists",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        4000,
        0
      ],
      "notes": "true -> ReadBinaryFile/SendPreview; false -> RaiseError (feeds the configured error workflow)."
    },
    {
      "parameters": {
        "jsCode": "const fs = require('fs');\nconst filePath = 'D:/Projects/social-media-automation/output/final_' + $json.id + '.mp4';\nconst stat = fs.statSync(filePath);\nconst buffer = fs.readFileSync(filePath);\nconst binaryData = await this.helpers.prepareBinaryData(buffer, 'video.mp4', 'video/mp4');\nreturn [{ json: $json, binary: { data: binaryData } }];"
      },
      "id": "node-read-binary-16",
      "name": "ReadBinaryFile",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4400,
        0
      ],
      "notes": "Loads the rendered mp4 into n8n binary data (property 'data') for the Telegram upload. Uses raw fs.readFileSync to bypass n8n's readBinaryFile path restriction."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendVideo' }}",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "chat_id",
              "value": "={{ Number($env.TELEGRAM_CHAT_ID) || $env.TELEGRAM_CHAT_ID }}",
              "parameterType": "formData"
            },
            {
              "name": "caption",
              "value": "={{ ($json.caption || '') + ($json.hashtags && $json.hashtags.length ? '\\n\\n' + $json.hashtags.map(function (h) { return '#' + h; }).join(' ') : '') + '\\n\\n\ud83d\udc40 Preview for ' + $json.platform + ' \u2014 approve to publish?' }}",
              "parameterType": "formData"
            },
            {
              "name": "reply_markup",
              "value": "={{ JSON.stringify({ inline_keyboard: [[{ text: '\u2705 Publish', callback_data: 'pv_yes_' + $json.id }, { text: '\u274c Discard', callback_data: 'pv_no_' + $json.id }]] }) }}",
              "parameterType": "formData"
            },
            {
              "name": "video",
              "value": "",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            }
          ]
        },
        "options": {}
      },
      "id": "node-send-preview-17",
      "name": "SendPreview",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        4800,
        0
      ],
      "notes": "Sends the full rendered video to the user's Telegram chat with \u2705 Publish / \u274c Discard buttons. Nothing is posted yet; the bot publishes on approval."
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nthrow new Error('Video pipeline failed: output file missing or empty (' + item.outputFile + '). exitCode=' + item.exitCode + ' stderr=' + (item.stderr || ''));"
      },
      "id": "node-raise-error-10",
      "name": "RaiseError",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4400,
        200
      ],
      "notes": "Throws so the execution fails and the configured error workflow (social_pipeline_error) fires."
    },
    {
      "parameters": {},
      "id": "node-end-11",
      "name": "End",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        5200,
        0
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Preflight",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preflight": {
      "main": [
        [
          {
            "node": "Transcribe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe": {
      "main": [
        [
          {
            "node": "PrepareGemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PrepareGemini": {
      "main": [
        [
          {
            "node": "Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini": {
      "main": [
        [
          {
            "node": "ParseGemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ParseGemini": {
      "main": [
        [
          {
            "node": "EncodeMeta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "EncodeMeta": {
      "main": [
        [
          {
            "node": "StoreMeta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "StoreMeta": {
      "main": [
        [
          {
            "node": "RunVideoPipeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RunVideoPipeline": {
      "main": [
        [
          {
            "node": "CheckOutputFile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckOutputFile": {
      "main": [
        [
          {
            "node": "OutputExists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OutputExists": {
      "main": [
        [
          {
            "node": "ReadBinaryFile",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "RaiseError",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ReadBinaryFile": {
      "main": [
        [
          {
            "node": "SendPreview",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SendPreview": {
      "main": [
        [
          {
            "node": "End",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "errorWorkflow": "2"
  },
  "active": true,
  "versionId": "2c4d08a3-4b54-43ec-847a-44f545e7c6a1",
  "tags": [],
  "id": 1
}
Pro

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

About this workflow

Zero-Cost Social Pipeline (M5). Uses executeCommand, httpRequest. Webhook trigger; 15 nodes.

Source: https://github.com/tamimlabs/social-media-automation/blob/main/workflows/social_pipeline.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1
Web Scraping

MLOps Pipeline EN-PT. Uses executeCommand, httpRequest, errorTrigger. Webhook trigger; 18 nodes.

Execute Command, HTTP Request, Error Trigger
Web Scraping

AI Product Video Generator (Windows). Uses httpRequest, writeBinaryFile, executeCommand, readBinaryFile. Webhook trigger; 16 nodes.

HTTP Request, Write Binary File, Execute Command +1
Web Scraping

MLOps Pipeline - Hand Talk. Uses executeCommand, httpRequest. Webhook trigger; 15 nodes.

Execute Command, HTTP Request
Web Scraping

AIDP - Main Workflow v2. Uses executeCommand, httpRequest. Webhook trigger; 13 nodes.

Execute Command, HTTP Request