{
  "name": "Add burned-in captions to any video (12 styles, karaoke fill, natural line breaks)",
  "nodes": [
    {
      "parameters": {
        "content": "## 🎬 Add burned-in captions to any video\n\n![Karaoke fill captions on a streamer video](https://caption.yosuga.dev/samples/video/demo2-karaoke_fill.gif)\n\nPaste a video URL into a form and get the video back **with captions burned in**: transcribed, split at natural phrase breaks and styled with one of **12 caption styles**, such as karaoke fill and word pop.\n\n### How it works\n1. The form collects a video URL, a caption style, a language and optional keywords\n2. The [Video Subtitle & Transcript API](https://rapidapi.com/yosuga-yosuga-default/api/video-subtitle-transcript-api1) transcribes the audio with word timestamps, breaks lines where a reader expects them and burns the captions in\n3. The workflow checks the job every 15 seconds\n4. The form page shows download links for the MP4 and the SRT\n\n### Setup\n1. Subscribe to the API on RapidAPI (the free BASIC plan includes 30 min/month)\n2. Create a **Header Auth** credential: name `X-RapidAPI-Key`, value your RapidAPI key\n3. Select it in **Create captioning job** and **Get job**\n4. Click **Test workflow** and submit the form. Leave the URL empty to try the 8-second demo clip\n\n### Customization\n- Compare the 12 styles in the [style gallery](https://yosuga.dev/n8n/caption-styles-demo.jpg) or tune one in [Caption Studio](https://yosuga.dev/app/caption/), then paste its `caption_style` into **Create captioning job**\n- **Keywords** (names, product terms) improve recognition, and the `keyword_color` style colors them\n- Burn-in languages: English, Japanese, Spanish, Portuguese, French, German, Turkish, Indonesian, Hindi\n- Billed per minute only when the job succeeds. BASIC adds a small watermark\n",
        "height": 880,
        "width": 600,
        "color": 1
      },
      "id": "896dd517-d7f2-58a8-a25c-e986d10f08c1",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -820,
        -280
      ]
    },
    {
      "parameters": {
        "content": "## ▶️ 2-minute walkthrough\n@[youtube](07_snRH1oGg)",
        "height": 420,
        "width": 600,
        "color": 7
      },
      "id": "a909800b-6be9-5618-96b6-7cb2cb63a8e2",
      "name": "Video",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -820,
        620
      ]
    },
    {
      "parameters": {
        "content": "## 1. Collect the video and style\nThe form asks for a video URL, a caption style, a language and optional keywords. Keep **Settings** if you swap the form for another trigger.",
        "height": 400,
        "width": 520,
        "color": 7
      },
      "id": "c39c6686-cc0a-5ff6-ad18-ed502929cb63",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        -180
      ]
    },
    {
      "parameters": {
        "formTitle": "Add burned-in captions to a video",
        "formDescription": "Paste a public video URL (MP4, MOV, WebM, M4A...), choose a caption style and submit. The captioned video is ready in about a minute for a short clip.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Video URL",
              "placeholder": "https://caption.yosuga.dev/samples/video/demo-streamer.mp4",
              "requiredField": false
            },
            {
              "fieldLabel": "Caption style",
              "fieldType": "dropdown",
              "requiredField": true,
              "fieldOptions": {
                "values": [
                  {
                    "option": "karaoke_fill - Karaoke Fill"
                  },
                  {
                    "option": "word_pop - Word Pop"
                  },
                  {
                    "option": "karaoke_highlight - Karaoke Highlight"
                  },
                  {
                    "option": "bold_center - Bold Center"
                  },
                  {
                    "option": "clean_outline - Clean Outline"
                  },
                  {
                    "option": "box_white - White Box"
                  },
                  {
                    "option": "band_dark - Dark Band"
                  },
                  {
                    "option": "keyword_color - Keyword Color"
                  },
                  {
                    "option": "double_outline - Double Outline"
                  },
                  {
                    "option": "clean_shadow - Clean Shadow"
                  },
                  {
                    "option": "manual_ud - Manual UD"
                  },
                  {
                    "option": "title_top - Title Top"
                  }
                ]
              }
            },
            {
              "fieldLabel": "Language",
              "fieldType": "dropdown",
              "requiredField": true,
              "fieldOptions": {
                "values": [
                  {
                    "option": "auto - Detect automatically"
                  },
                  {
                    "option": "en - English"
                  },
                  {
                    "option": "ja - Japanese"
                  },
                  {
                    "option": "es - Spanish"
                  },
                  {
                    "option": "pt - Portuguese"
                  },
                  {
                    "option": "fr - French"
                  },
                  {
                    "option": "de - German"
                  },
                  {
                    "option": "tr - Turkish"
                  },
                  {
                    "option": "id - Indonesian"
                  },
                  {
                    "option": "hi - Hindi"
                  }
                ]
              }
            },
            {
              "fieldLabel": "Keywords",
              "placeholder": "Names or terms, comma-separated (keyword_color colors these)",
              "requiredField": false
            }
          ]
        },
        "options": {
          "buttonLabel": "Caption my video",
          "appendAttribution": false
        }
      },
      "id": "a6d7c408-50f1-58ce-9417-35d25d00be2a",
      "name": "Video form",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "position": [
        -60,
        0
      ],
      "webhookId": "9d5b996e-7f6f-57f8-93f4-58d851213af7"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ac2b8fa9-30f9-5b55-8412-bd948efc0dfd",
              "name": "video_url",
              "type": "string",
              "value": "={{ $json['Video URL'] || 'https://caption.yosuga.dev/samples/video/demo-streamer.mp4' }}"
            },
            {
              "id": "93ceacd4-49ea-50de-9437-451554e74cd8",
              "name": "preset",
              "type": "string",
              "value": "={{ ($json['Caption style'] || 'karaoke_fill').split(' ')[0] }}"
            },
            {
              "id": "9fc17e28-3119-56c3-8283-d2b6713aa9a1",
              "name": "language",
              "type": "string",
              "value": "={{ ($json['Language'] || 'auto').split(' ')[0] }}"
            },
            {
              "id": "37d62fab-d148-59ab-9e19-ab405da646da",
              "name": "orientation",
              "type": "string",
              "value": "auto"
            },
            {
              "id": "b3d19187-142a-5dbc-b4a0-a555b995154a",
              "name": "vocabulary",
              "type": "array",
              "value": "={{ ($json['Keywords'] || '').split(',').map(s => s.trim()).filter(s => s) }}"
            }
          ]
        },
        "options": {}
      },
      "id": "7f79b79d-1179-5ad7-ab9f-1b46a8c76c0d",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        180,
        0
      ]
    },
    {
      "parameters": {
        "content": "## 2. Create the job and wait\nOne API call transcribes, wraps and burns in the captions. The loop checks the status every 15 seconds until the job finishes or fails.",
        "height": 560,
        "width": 1060,
        "color": 7
      },
      "id": "f226785d-2412-532f-8e0f-0787ac5069fc",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        440,
        -180
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://video-subtitle-transcript-api1.p.rapidapi.com/v1/jobs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Host",
              "value": "video-subtitle-transcript-api1.p.rapidapi.com"
            }
          ]
        },
        "options": {},
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { source_url: $json.video_url, language: $json.language, orientation: $json.orientation, vocabulary: $json.vocabulary, caption_style: { preset: $json.preset }, burn_in: true, outputs: ['srt'] } }}"
      },
      "id": "f515bf45-c195-5abd-95b8-e2712c4aee37",
      "name": "Create captioning job",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        540,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "RapidAPI - Video Subtitle API"
        }
      }
    },
    {
      "parameters": {
        "amount": 15,
        "unit": "seconds"
      },
      "id": "b2c84855-ec2d-5ab7-babe-5776684de72b",
      "name": "Wait 15 s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        900,
        0
      ],
      "webhookId": "4b14fc7c-f0e2-5f39-bfab-3c91024edd17"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://video-subtitle-transcript-api1.p.rapidapi.com/v1/jobs/{{ $('Create captioning job').item.json.id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Host",
              "value": "video-subtitle-transcript-api1.p.rapidapi.com"
            }
          ]
        },
        "options": {}
      },
      "id": "fe05cc28-a611-5cc6-8db1-697c80ba0a17",
      "name": "Get job",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "RapidAPI - Video Subtitle API"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "f1ccf5a8-474e-5f5c-acff-98167325fc54",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "37322c2a-b975-5430-a9e9-00ff2ae02386",
      "name": "Finished?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "6931f1aa-9dad-5f75-91c1-8725c3848eb6",
              "leftValue": "={{ $json.status }}",
              "rightValue": "failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "6999e904-c375-53a0-a7ab-065028cfd43e",
      "name": "Failed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        200
      ]
    },
    {
      "parameters": {
        "content": "## 3. Return the result\nThe form page shows the MP4 and SRT links, or why the job failed (failed jobs are free). Enable **Download video** to save the file.",
        "height": 680,
        "width": 620,
        "color": 7
      },
      "id": "16d06e59-a04e-5373-a998-65954c2706c8",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1560,
        -180
      ]
    },
    {
      "parameters": {
        "operation": "completion",
        "respondWith": "showText",
        "responseText": "={{ '<p><a href=\"' + $json.files.burned_video + '\" target=\"_blank\">▶ Download the captioned video (MP4)</a></p>' + '<p><a href=\"' + $json.files.srt + '\" target=\"_blank\">Download the SRT</a></p>' + '<p>Style: ' + $('Settings').item.json.preset + ' · language: ' + $json.meta.language_detected + ' · ' + $json.billing.minutes + ' min billed</p>' }}"
      },
      "id": "cedad57e-daa0-594b-8db4-45fc2f69ebd0",
      "name": "Show download links",
      "type": "n8n-nodes-base.form",
      "typeVersion": 1,
      "position": [
        1660,
        -40
      ],
      "webhookId": "1d1f44f7-e230-52e5-96de-b5ceec538173"
    },
    {
      "parameters": {
        "url": "={{ $json.files.burned_video }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "af6f27b3-d091-5cf4-849a-9a1cd9b5a412",
      "name": "Download video",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1920,
        -40
      ],
      "disabled": true
    },
    {
      "parameters": {
        "operation": "completion",
        "respondWith": "showText",
        "responseText": "={{ '<p><b>' + $json.error.code + '</b>: ' + $json.error.message + '</p>' + ($json.error.hint ? '<p>' + $json.error.hint + '</p>' : '') + '<p>Failed jobs are not billed.</p>' }}"
      },
      "id": "2ed793d2-bb7f-53bd-a735-e59a4ca96cb0",
      "name": "Show error",
      "type": "n8n-nodes-base.form",
      "typeVersion": 1,
      "position": [
        1660,
        340
      ],
      "webhookId": "486a2e4f-088f-584a-b49c-6c8207f9d0b8"
    }
  ],
  "connections": {
    "Video form": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Create captioning job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create captioning job": {
      "main": [
        [
          {
            "node": "Wait 15 s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 15 s": {
      "main": [
        [
          {
            "node": "Get job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get job": {
      "main": [
        [
          {
            "node": "Finished?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finished?": {
      "main": [
        [
          {
            "node": "Show download links",
            "type": "main",
            "index": 0
          },
          {
            "node": "Download video",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Failed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Failed?": {
      "main": [
        [
          {
            "node": "Show error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 15 s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
