{
  "name": "Fix captions and re-burn (Video Subtitle & Transcript API)",
  "nodes": [
    {
      "parameters": {
        "content": "## ✏️ Fix transcription mistakes, then re-burn for free\n\nSpeech recognition sometimes gets names, product terms and jargon wrong. This workflow transcribes a video, fixes known mistakes with your find-and-replace list and burns the corrected captions into the same video. **The re-burn is free**: up to 3 per job within 7 days, as long as you send the same video.\n\n### How it works\n1. Transcribe without burning (billed per minute of video; failed jobs are free)\n2. Apply your corrections to the transcript segments\n3. If anything changed, re-burn with `reburn_of` and the corrected segments\n4. Download the captioned MP4\n\n### Setup\n1. Subscribe to the [Video Subtitle & Transcript API](https://rapidapi.com/yosuga-yosuga-default/api/video-subtitle-transcript-api1) on RapidAPI (free BASIC plan: 30 min/month)\n2. Create a **Header Auth** credential: name `X-RapidAPI-Key`, value your RapidAPI key\n3. Select it in **Transcribe**, **Get transcript**, **Re-burn with fixes** and **Get re-burn**\n4. Edit **Settings** and click **Test workflow**\n\nThe default video is a 30-second Japanese sample where the engine hears 字幕API instead of キャプションAPI; the example corrections fix exactly that.\n\n### Customization\n- Set `preset` to any of the 12 caption styles, or tune one in [Caption Studio](https://yosuga.dev/app/caption/)\n- Connect Google Drive, Dropbox or S3 after the download to keep the file\n",
        "height": 580,
        "width": 600,
        "color": 1
      },
      "id": "6a5f9fdb-29b5-59d9-aaaf-0ca2349a621d",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -320
      ]
    },
    {
      "parameters": {
        "content": "## 1. Settings\nSet the video URL, language, caption `preset` and your `corrections` list (`find` / `replace` pairs).",
        "height": 400,
        "width": 480,
        "color": 7
      },
      "id": "0a18414c-1386-5b27-aea9-1a48e5f40dfa",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -180
      ]
    },
    {
      "parameters": {
        "content": "## 2. Transcribe\nCreates a job with `burn_in: false` and checks it every 20 seconds. Stops with the error code if it fails.",
        "height": 780,
        "width": 900,
        "color": 7
      },
      "id": "5ca5a82e-1c6d-5a18-ba28-ebf003595e75",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        440,
        -180
      ]
    },
    {
      "parameters": {
        "content": "## 3. Apply corrections\nRuns your find-and-replace list on every segment. If nothing changed, the workflow ends here.",
        "height": 560,
        "width": 660,
        "color": 7
      },
      "id": "c415de78-76c3-55ee-99db-a7ebcd3807f2",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1400,
        -180
      ]
    },
    {
      "parameters": {
        "content": "## 4. Re-burn for free\nSends the same video with `reburn_of` and the corrected segments, waits, then downloads the captioned MP4.",
        "height": 780,
        "width": 1180,
        "color": 7
      },
      "id": "3f9a512c-26f1-5e13-9e5d-688f1cdf4520",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2100,
        -180
      ]
    },
    {
      "parameters": {},
      "id": "b2d1e0a0-0000-4000-8000-000000000001",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "video_url",
              "value": "https://caption.yosuga.dev/samples/video/sample-30s.mp4",
              "type": "string"
            },
            {
              "id": "s2",
              "name": "language",
              "value": "ja",
              "type": "string"
            },
            {
              "id": "s3",
              "name": "preset",
              "value": "karaoke_fill",
              "type": "string"
            },
            {
              "id": "s4",
              "name": "corrections",
              "value": "={{ [ { find: '字幕API', replace: 'キャプションAPI' }, { find: 'ユースガ', replace: 'YOSUGA' } ] }}",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000002",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ]
    },
    {
      "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"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { source_url: $json.video_url, language: $json.language, mode: 'clean', outputs: ['json', 'srt'], burn_in: false } }}",
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000003",
      "name": "Transcribe",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        520,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "RapidAPI key (X-RapidAPI-Key)"
        }
      }
    },
    {
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000004",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        740,
        0
      ],
      "webhookId": "b2d1e0a0-0000-4000-8000-0000000wait1"
    },
    {
      "parameters": {
        "url": "=https://video-subtitle-transcript-api1.p.rapidapi.com/v1/jobs/{{ $('Transcribe').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": "b2d1e0a0-0000-4000-8000-000000000005",
      "name": "Get transcript",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "RapidAPI key (X-RapidAPI-Key)"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000006",
      "name": "Transcript ready?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1180,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c2",
              "leftValue": "={{ $json.status }}",
              "rightValue": "failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000007",
      "name": "Transcription failed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1180,
        260
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ $json.error.code }}: {{ $json.error.message }}"
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000008",
      "name": "Stop: transcription failed",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        1180,
        460
      ]
    },
    {
      "parameters": {
        "jsCode": "// Apply the corrections from Settings to every transcript segment.\n// Unchanged segments keep their word timings (karaoke presets stay in sync);\n// the API re-times the words of segments you changed.\nconst job = $input.first().json;\nconst corrections = $('Settings').first().json.corrections || [];\nlet changed = 0;\nconst segments = (job.transcript?.segments || []).map((s) => {\n  let text = s.text;\n  for (const c of corrections) {\n    if (c && c.find) text = text.split(c.find).join(c.replace ?? '');\n  }\n  const seg = { start: s.start, end: s.end, text };\n  if (text === s.text) {\n    if (Array.isArray(s.words) && s.words.length) seg.words = s.words.map((w) => ({ word: w.word, start: w.start, end: w.end }));\n  } else {\n    changed++;\n  }\n  return seg;\n}).filter((s) => s.text.trim() !== '');\nreturn [{ json: { job_id: job.id, language: job.meta?.language_detected || null, changed, segments } }];"
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000009",
      "name": "Apply corrections",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1460,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c3",
              "leftValue": "={{ $json.changed }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000010",
      "name": "Anything fixed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1680,
        0
      ]
    },
    {
      "parameters": {},
      "id": "b2d1e0a0-0000-4000-8000-000000000011",
      "name": "Nothing to fix",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1900,
        220
      ]
    },
    {
      "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"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { source_url: $('Settings').first().json.video_url, reburn_of: $json.job_id, language: $json.language || $('Settings').first().json.language, burn_in: true, caption_style: { preset: $('Settings').first().json.preset }, outputs: ['srt'], captions: { segments: $json.segments } } }}",
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000012",
      "name": "Re-burn with fixes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2160,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "RapidAPI key (X-RapidAPI-Key)"
        }
      }
    },
    {
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000013",
      "name": "Wait for re-burn",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2380,
        0
      ],
      "webhookId": "b2d1e0a0-0000-4000-8000-0000000wait2"
    },
    {
      "parameters": {
        "url": "=https://video-subtitle-transcript-api1.p.rapidapi.com/v1/jobs/{{ $('Re-burn with fixes').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": "b2d1e0a0-0000-4000-8000-000000000014",
      "name": "Get re-burn",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2600,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "RapidAPI key (X-RapidAPI-Key)"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c4",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000015",
      "name": "Re-burn ready?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2820,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c5",
              "leftValue": "={{ $json.status }}",
              "rightValue": "failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000016",
      "name": "Re-burn failed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2820,
        260
      ]
    },
    {
      "parameters": {
        "errorMessage": "={{ $json.error.code }}: {{ $json.error.message }}"
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000017",
      "name": "Stop: re-burn failed",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2820,
        460
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.files.burned_video }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "id": "b2d1e0a0-0000-4000-8000-000000000018",
      "name": "Download burned video",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3060,
        0
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Transcribe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "Get transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get transcript": {
      "main": [
        [
          {
            "node": "Transcript ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcript ready?": {
      "main": [
        [
          {
            "node": "Apply corrections",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Transcription failed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcription failed?": {
      "main": [
        [
          {
            "node": "Stop: transcription failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply corrections": {
      "main": [
        [
          {
            "node": "Anything fixed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anything fixed?": {
      "main": [
        [
          {
            "node": "Re-burn with fixes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Nothing to fix",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Re-burn with fixes": {
      "main": [
        [
          {
            "node": "Wait for re-burn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for re-burn": {
      "main": [
        [
          {
            "node": "Get re-burn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get re-burn": {
      "main": [
        [
          {
            "node": "Re-burn ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Re-burn ready?": {
      "main": [
        [
          {
            "node": "Download burned video",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Re-burn failed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Re-burn failed?": {
      "main": [
        [
          {
            "node": "Stop: re-burn failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait for re-burn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
