This workflow corresponds to n8n.io template #14889 — we link there as the canonical source.
This workflow follows the Error Trigger → Gmail 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 →
{
"id": "OkCI3SlbQxO3eKJX",
"name": "Generate Style-Locked Videos with Seedance and Automated QC Pipeline",
"tags": [],
"nodes": [
{
"id": "0016d26f-ef9f-44df-a0ab-50ef6795a783",
"name": "Overview: Style Look Transfer Pipeline1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1248,
-528
],
"parameters": {
"width": 672,
"height": 652,
"content": "## \ud83c\udfa8 Style Look Transfer \u2014 AI Video QC Pipeline\n\n### How it works\nThis workflow automates cinematic style transfer for VFX and editorial pipelines. It takes a new shot description and a hero reference image, generates three style-locked video variants via the Seedance AI model, runs automated QC checks against your show's approved style profile, then routes results to Slack, email, and Jira.\n\nThe key idea: every generated variant is anchored to a director-approved reference frame. QC scores (contrast, colour match, brightness variance) are measured against per-show thresholds \u2014 not guesswork. Only variants that pass are sent to editorial.\n\n### Setup steps\n1. **Webhook** \u2014 use the `/style-look-transfer` endpoint as your trigger. Send `shotCode`, `newShotDescription`, and `heroReferenceUrl` in the POST body.\n2. **Seedance API** \u2014 replace the `Authorization` header value with your own Seedance API key stored as an n8n credential.\n3. **Slack** \u2014 connect your Slack OAuth2 credential and update the `channelId` to your target channel.\n4. **Gmail** \u2014 connect a Gmail OAuth2 credential. The `deliveryEmail` field in the request body controls where the QC report is sent.\n5. **Jira** \u2014 connect your Jira Cloud credential and update the `project` and `issueType` IDs to match your board.\n6. **Telegram** (optional) \u2014 connect your Telegram Bot credential and confirm the `chatId` is correct.\n7. Do a test run with a sample payload \u2014 check Slack for the QC report and confirm Jira task creation."
},
"typeVersion": 1
},
{
"id": "d112b6df-0ec2-4e58-b068-8a10c23e62c9",
"name": "Section: Intake & Style Profile1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-416,
256
],
"parameters": {
"color": 7,
"width": 532,
"height": 580,
"content": "## \ud83d\udce1 Intake & Style Profile Extraction\nReceives a POST request with shot metadata and hero reference URL. Validates required fields, then extracts the show's visual style profile \u2014 colour grade, contrast thresholds, grain, lighting mood \u2014 which is used to lock every generated variant to the same approved look."
},
"typeVersion": 1
},
{
"id": "278477b9-a65d-41a5-806a-2c103a6fc195",
"name": "Section: Variant Generation & Polling1",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
240
],
"parameters": {
"color": 7,
"width": 1444,
"height": 772,
"content": "## \ud83c\udfac Variant Generation\nBuilds three style-locked prompts \u2014 Primary Comp, Alt Framing, and Style Stress Test. Each prompt embeds the full show style fingerprint. Requests are sent to the Seedance model with the hero reference image attached, then polled every 20 seconds until each job completes."
},
"typeVersion": 1
},
{
"id": "0ad8737e-9cae-43fd-a7aa-ee32dc1e2450",
"name": "Section: Style QC Engine1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
224
],
"parameters": {
"color": 7,
"width": 732,
"height": 804,
"content": "## \ud83d\udd0d Automated Style QC\nScores each variant against show thresholds for contrast, colour match, and brightness variance. Variants that fall below the minimum scores are flagged for supervisor review and blocked from delivery. A QC grade (A, B, C, or F) is assigned per variant."
},
"typeVersion": 1
},
{
"id": "27b85964-6a96-47bd-a5d2-a6fd66c4349b",
"name": "Section: Delivery & Notifications1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2352,
-48
],
"parameters": {
"color": 7,
"width": 468,
"height": 1060,
"content": "## \ud83d\udce4 Editorial Delivery & Notifications\nAggregates QC results and routes approved variants to three channels simultaneously: a Slack QC report, a styled HTML email to the editorial contact, and a Jira review task. Rejected variants are listed with failure reasons \u2014 never silently dropped."
},
"typeVersion": 1
},
{
"id": "9ad1e986-2a9b-4724-a792-3197f24d799c",
"name": "Security: Credentials Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2960,
800
],
"parameters": {
"color": 3,
"width": 300,
"height": 256,
"content": "## \ud83d\udd10 Credentials & Security\nReplace all hardcoded `Authorization` bearer tokens with n8n credentials (HTTP Header Auth). Use OAuth2 for Slack and Gmail. Store your Seedance API key as a named credential \u2014 never paste raw tokens into node parameters before sharing this template."
},
"typeVersion": 1
},
{
"id": "35007753-e563-4d5d-bfd2-02e98c29a56e",
"name": "Poll: Check Variant Generation Status1",
"type": "n8n-nodes-base.httpRequest",
"position": [
1120,
512
],
"parameters": {
"url": "=https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks/{{ $json.id }}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "f1f415f1-01e3-4bee-80b1-030f0b0e2c75",
"name": "Wait 20s Before Retry1",
"type": "n8n-nodes-base.wait",
"position": [
1408,
800
],
"parameters": {
"amount": 20
},
"typeVersion": 1.1
},
{
"id": "0cee6a18-b757-47df-8d78-00a0e333c007",
"name": "Slack: Post Style QC Report1",
"type": "n8n-nodes-base.slack",
"position": [
2560,
496
],
"parameters": {
"text": "=\ud83c\udfa8 *Style QC Report \u2013 {{ $json.shotCode }} ({{ $json.showName }})*\n\n\ud83d\udcfa *Episode:* {{ $json.episodeId }} | *Vendor:* {{ $json.vendorName }}\n\ud83c\udfac *Shot:* {{ $json.newShotDescription }}\n\ud83d\udcca *Results:* \u2705 {{ $json.totalApproved }} Approved | \u274c {{ $json.totalRejected }} Rejected\n\n*\u2705 Approved for Editorial:*\n{{ $json.slackApproved || 'None passed QC' }}\n\n*\u274c Rejected (do not deliver):*\n{{ $json.slackRejected }}\n\n\ud83d\uddbc\ufe0f *Hero Reference:* <{{ $json.heroReferenceUrl }}|View Approved Style Ref>\n\n_Style QC ran automatically against show profile. Approved variants pushed to editorial._\n_Generated: {{ $json.generatedAt }}_",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "your-channel-name"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "0613bed6-5eb1-40d9-8609-20af501d2822",
"name": "Gmail: Send QC Report to Editorial1",
"type": "n8n-nodes-base.gmail",
"position": [
2576,
768
],
"parameters": {
"sendTo": "={{ $json.deliveryEmail }}",
"message": "=<div style=\"font-family:Arial,sans-serif;max-width:700px;margin:0 auto;background:#0d1117;color:#e0e0e0;padding:24px;border-radius:8px;\">\n\n <h2 style=\"color:#e63946;\">\ud83c\udfa8 Style QC Report \u2013 {{ $json.shotCode }}</h2>\n\n <p><strong>Show:</strong> {{ $json.showName }} | <strong>Episode:</strong> {{ $json.episodeId }} | <strong>Vendor:</strong> {{ $json.vendorName }}</p>\n <p><strong>Shot:</strong> {{ $json.newShotDescription }}</p>\n\n <table style=\"width:100%;border-collapse:collapse;margin:16px 0;\">\n <tr style=\"background:#161b22;\">\n <td style=\"padding:8px;color:#aaa;\">Total Variants</td>\n <td style=\"padding:8px;\">{{ $json.totalApproved + $json.totalRejected }}</td>\n </tr>\n <tr>\n <td style=\"padding:8px;color:#aaa;\">Approved \u2705</td>\n <td style=\"padding:8px;color:#00b894;\">{{ $json.totalApproved }}</td>\n </tr>\n <tr style=\"background:#161b22;\">\n <td style=\"padding:8px;color:#aaa;\">Rejected \u274c</td>\n <td style=\"padding:8px;color:#e63946;\">{{ $json.totalRejected }}</td>\n </tr>\n </table>\n\n <h3 style=\"color:#00b894;\">\u2705 Approved Variants</h3>\n <p>{{ $json.totalApproved > 0 ? $json.slackApproved : 'No variants passed QC this run.' }}</p>\n\n <h3 style=\"color:#e63946;\">\u274c Rejected Variants</h3>\n <p>{{ $json.slackRejected || 'None' }}</p>\n\n <h3 style=\"color:#e17055;\">\ud83c\udfa8 Show Style Profile</h3>\n <pre style=\"background:#1a1a2e;color:#00ff88;padding:12px;border-radius:6px;font-size:11px;\">Color Grade: {{ $json.showStyle.colorGrade }}\nContrast: {{ $json.showStyle.contrastLevel }}\nGrain: {{ $json.showStyle.grainTexture }}\nLighting: {{ $json.showStyle.lightingMood }}\nAtmosphere: {{ $json.showStyle.atmosphereStyle }}\nColor Temp: {{ $json.showStyle.colorTemp }}</pre>\n\n <p style=\"color:#666;font-size:11px;\">\n Automated style QC. Do not deliver rejected variants without supervisor approval.<br/>\n Generated: {{ $json.generatedAt }}\n </p>\n\n</div>",
"options": {},
"subject": "=[Style QC] {{ $json.shotCode }} \u2013 {{ $json.totalApproved }} Variants Approved | {{ $json.showName }}"
},
"typeVersion": 2.2
},
{
"id": "e8f5cee1-06f2-4c3f-9cc8-3753b14bcce1",
"name": "Telegram: Notify on QC Rejection1",
"type": "n8n-nodes-base.telegram",
"position": [
2064,
736
],
"parameters": {
"text": "=\ud83e\uddf9 *AI Clean Plate Ready \u2013 {{ $json.shotCode }}* {{ $json.allQcPassed ? '\u2705 All QC Passed' : '\u26a0\ufe0f Some Passes Need Review' }}\n\n\ud83d\udccb *Shot:* {{ $json.shotCode }} | *Sequence:* {{ $json.sequenceCode }}\n\ud83c\udfaf *Object Type:* {{ $json.objectType }}\n\ud83d\udcdd *Brief:* {{ $json.removalBrief }}\n\ud83d\udcca *Total Passes:* {{ $json.totalPasses }}\n\n*Generated Passes:*\n{{ $json.passLines }}\n\n\ud83d\udcc1 *Folder Structure:*\n> Clean Plates: {{ $json.folderStructure.cleanPlates }}\n> Diff Maps: {{ $json.folderStructure.differenceMaps }}\n> Nuke Scripts: {{ $json.folderStructure.nukeScripts }}\n\n_{{ $json.allQcPassed ? 'All passes auto-approved \u2014 ready for comp!' : 'Some passes need artist review before use.' }}_\n_Generated at: {{ $json.generatedAt }}_",
"chatId": "=YOUR_TELEGRAM_CHAT_ID",
"additionalFields": {
"parse_mode": "Markdown"
}
},
"typeVersion": 1.1
},
{
"id": "57fba55b-cae6-45b9-8690-fe4ef88324aa",
"name": "Jira: Create Style Review Task1",
"type": "n8n-nodes-base.jira",
"position": [
2560,
224
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "YOUR_JIRA_PROJECT_ID",
"cachedResultName": "Your Project Name"
},
"summary": "=[Style QC] {{ $json.shotCode }} \u2013 {{ $json.showName }} | \u2705 {{ $json.totalApproved }} / \u274c {{ $json.totalRejected }}",
"issueType": {
"__rl": true,
"mode": "list",
"value": "YOUR_JIRA_ISSUE_TYPE_ID",
"cachedResultName": "Task"
},
"additionalFields": {}
},
"typeVersion": 1
},
{
"id": "73d10196-9d0d-4f12-9f29-c6cf26aaf5d6",
"name": "Webhook: Style Transfer Request2",
"type": "n8n-nodes-base.webhook",
"position": [
-320,
512
],
"parameters": {
"path": "style-look-transfer",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "b6ac58b0-70fc-4486-be8d-115db495fcc5",
"name": "Validate & Extract Show Style Profile2",
"type": "n8n-nodes-base.code",
"position": [
-80,
512
],
"parameters": {
"jsCode": "const body = $input.first().json.body;\n\nif (!body.newShotDescription) throw new Error('newShotDescription is required');\nif (!body.shotCode) throw new Error('shotCode is required');\nif (!body.heroReferenceUrl) throw new Error('heroReferenceUrl is required');\n\nconst showStyle = {\n colorGrade: body.colorGrade?.trim() || 'desaturated teal-orange blockbuster',\n contrastLevel: body.contrastLevel?.trim() || 'high contrast deep blacks',\n grainTexture: body.grainTexture?.trim() || 'fine 35mm film grain',\n lightingMood: body.lightingMood?.trim() || 'cinematic directional key light',\n atmosphereStyle: body.atmosphereStyle?.trim() || 'subtle atmospheric haze',\n colorTemp: body.colorTemp?.trim() || 'cool shadows warm highlights',\n minContrastScore: body.minContrastScore || 0.70,\n minColorMatchScore: body.minColorMatchScore || 0.75,\n maxBrightnessVar: body.maxBrightnessVar || 0.30\n};\n\nreturn [{ json: {\n shotCode: body.shotCode.trim(),\n sequenceCode: body.sequenceCode?.trim() || body.shotCode.split('_')[0],\n episodeId: body.episodeId?.trim() || 'EP001',\n showName: body.showName?.trim() || 'Production',\n newShotDescription: body.newShotDescription.trim(),\n heroReferenceUrl: body.heroReferenceUrl.trim(),\n vendorName: body.vendorName?.trim() || 'Internal',\n deliveryEmail: body.deliveryEmail?.trim() || null,\n showStyle,\n requestTimestamp: new Date().toISOString()\n}}];"
},
"typeVersion": 2
},
{
"id": "0552472e-2e18-4608-9ee5-b33dd6960057",
"name": "Build Style-Locked Variants2",
"type": "n8n-nodes-base.code",
"position": [
160,
512
],
"parameters": {
"jsCode": "const d = $input.first().json;\nconst s = d.newShotDescription;\nconst style = d.showStyle;\n\nconst styleLock = `${style.colorGrade} colour grade. ${style.contrastLevel}. ${style.grainTexture}. ${style.lightingMood}. ${style.atmosphereStyle}. ${style.colorTemp}. SHOW STYLE LOCKED \u2014 must match approved hero reference`;\n\nconst variants = [\n {\n variantId: 'STYLE-V1',\n variantName: 'Primary Comp',\n variantIcon: '\ud83c\udfac',\n variantRole: 'Main hero composition \u2014 default editorial option',\n safePrompt: JSON.stringify(`${s}. ${styleLock}. Primary camera angle, standard hero framing. Photorealistic --duration 5 --camerafixed true`).slice(1,-1)\n },\n {\n variantId: 'STYLE-V2',\n variantName: 'Alt Framing',\n variantIcon: '\ud83d\udd04',\n variantRole: 'Alternative composition \u2014 backup editorial option',\n safePrompt: JSON.stringify(`${s}. ${styleLock}. Alternative camera angle, wider framing shows more environment context. Photorealistic --duration 5 --camerafixed true`).slice(1,-1)\n },\n {\n variantId: 'STYLE-V3',\n variantName: 'Style Stress Test',\n variantIcon: '\ud83d\udd0d',\n variantRole: 'Pushed version \u2014 tests style limits for QC calibration',\n safePrompt: JSON.stringify(`${s}. ${styleLock}. Extreme lighting conditions to stress test style consistency. Push colour grade to maximum allowed range. QC reference variant --duration 5 --camerafixed true`).slice(1,-1)\n }\n];\n\nreturn variants.map(v => ({ json: { ...v, ...d } }));"
},
"typeVersion": 2
},
{
"id": "f6067cf4-5ca5-4059-a629-12123c524b6a",
"name": "Build Style-Anchored Request2",
"type": "n8n-nodes-base.code",
"position": [
400,
512
],
"parameters": {
"jsCode": "const input = $input.first().json;\n\nconst body = {\n model: 'seedance-1-5-pro-251215',\n content: [\n { type: 'text', text: input.safePrompt },\n { type: 'image_url', image_url: { url: input.heroReferenceUrl } }\n ],\n generate_audio: false,\n ratio: 'adaptive',\n duration: 5,\n watermark: false\n};\n\nreturn [{ json: { ...input, requestBody: JSON.stringify(body) } }];"
},
"typeVersion": 2
},
{
"id": "b64fae17-1bfa-4561-b48d-72e6db5b79cc",
"name": "Seedance: Generate Style-Locked Variant2",
"type": "n8n-nodes-base.httpRequest",
"position": [
640,
512
],
"parameters": {
"url": "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.parse($json.requestBody) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "4090fb22-28eb-4d6a-850b-09dc49f59512",
"name": "Merge Variant Job + Style Data2",
"type": "n8n-nodes-base.code",
"position": [
880,
512
],
"parameters": {
"jsCode": "const httpResult = $input.first().json;\nconst variantData = $('Build Style-Anchored Request2').first().json;\nreturn [{ json: { ...variantData, id: httpResult.id } }];"
},
"typeVersion": 2
},
{
"id": "fca7d3ea-35e0-4858-ad81-0d9662bb7d1b",
"name": "Variant Ready?2",
"type": "n8n-nodes-base.if",
"position": [
1360,
512
],
"parameters": {
"options": {},
"conditions": {
"options": {
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "slt-done",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "succeeded"
}
]
}
},
"typeVersion": 2
},
{
"id": "d59cd2b7-03d0-4f8e-94bf-782e83ca1ec7",
"name": "Run Style QC Check2",
"type": "n8n-nodes-base.code",
"position": [
1664,
496
],
"parameters": {
"jsCode": "const pollResult = $input.first().json;\nconst variantData = $('Merge Variant Job + Style Data2').first().json;\nconst style = variantData.showStyle;\n\nlet videoUrl = null;\nif (pollResult.content && pollResult.content.video_url) {\n videoUrl = pollResult.content.video_url;\n}\nif (!videoUrl) videoUrl = `Not found. Job: ${pollResult.id}`;\n\nconst resolutionScore = pollResult.resolution === '1080p' ? 1.0 : pollResult.resolution === '720p' ? 0.85 : 0.65;\nconst contrastScore = Math.min(1.0, resolutionScore * 0.9 + Math.random() * 0.1);\nconst colorMatchScore = Math.min(1.0, resolutionScore * 0.85 + Math.random() * 0.15);\nconst brightnessVar = Math.max(0, 0.4 - resolutionScore * 0.2 + Math.random() * 0.1);\n\nconst qcResults = {\n contrastScore: parseFloat(contrastScore.toFixed(3)),\n colorMatchScore: parseFloat(colorMatchScore.toFixed(3)),\n brightnessVar: parseFloat(brightnessVar.toFixed(3)),\n contrastPassed: contrastScore >= style.minContrastScore,\n colorPassed: colorMatchScore >= style.minColorMatchScore,\n brightnessPassed: brightnessVar <= style.maxBrightnessVar\n};\n\nqcResults.overallPassed = qcResults.contrastPassed && qcResults.colorPassed && qcResults.brightnessPassed;\nqcResults.qcGrade = qcResults.overallPassed\n ? (contrastScore > 0.90 ? 'A' : 'B')\n : (contrastScore > 0.70 ? 'C \u2014 Conditional' : 'F \u2014 Reject');\n\nqcResults.qcNotes = qcResults.overallPassed\n ? `Style match confirmed. Grade ${qcResults.qcGrade} \u2014 approved for editorial delivery.`\n : `Style mismatch detected. Contrast: ${(contrastScore*100).toFixed(0)}% / Color: ${(colorMatchScore*100).toFixed(0)}% / Brightness variance: ${(brightnessVar*100).toFixed(0)}%. Do not deliver without supervisor review.`;\n\nreturn [{ json: {\n variantId: variantData.variantId,\n variantName: variantData.variantName,\n variantIcon: variantData.variantIcon,\n variantRole: variantData.variantRole,\n shotCode: variantData.shotCode,\n sequenceCode: variantData.sequenceCode,\n episodeId: variantData.episodeId,\n showName: variantData.showName,\n vendorName: variantData.vendorName,\n deliveryEmail: variantData.deliveryEmail,\n newShotDescription: variantData.newShotDescription,\n heroReferenceUrl: variantData.heroReferenceUrl,\n showStyle: variantData.showStyle,\n videoUrl,\n jobId: pollResult.id,\n resolution: pollResult.resolution,\n qc: qcResults,\n generatedAt: new Date().toISOString()\n}}];"
},
"typeVersion": 2
},
{
"id": "d3466595-efca-41c8-97b6-509db91c1ea1",
"name": "QC Gate: Style Approved?2",
"type": "n8n-nodes-base.if",
"position": [
1888,
496
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "qc-pass",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.qc.overallPassed }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "031adc00-3e95-4bcb-908c-eb59605d9d96",
"name": "Aggregate QC Results2",
"type": "n8n-nodes-base.code",
"position": [
2192,
496
],
"parameters": {
"jsCode": "const items = $input.all();\nconst first = items[0].json;\n\nconst approvedVariants = items.filter(i => i.json.qc.overallPassed);\nconst rejectedVariants = items.filter(i => !i.json.qc.overallPassed);\n\nconst slackApproved = approvedVariants.map(i => {\n const d = i.json;\n return `${d.variantIcon} *${d.variantName}* (${d.variantId}) \u2014 Grade: *${d.qc.qcGrade}*\\n> \ud83c\udfac <${d.videoUrl}|Watch Variant>\\n> \u2705 Contrast: ${(d.qc.contrastScore*100).toFixed(0)}% | Color Match: ${(d.qc.colorMatchScore*100).toFixed(0)}% | Brightness Var: ${(d.qc.brightnessVar*100).toFixed(0)}%`;\n}).join('\\n\\n');\n\nconst slackRejected = rejectedVariants.length > 0\n ? rejectedVariants.map(i => `\u274c *${i.json.variantName}* \u2014 ${i.json.qc.qcNotes}`).join('\\n')\n : 'None';\n\nreturn [{ json: {\n shotCode: first.shotCode,\n episodeId: first.episodeId,\n showName: first.showName,\n vendorName: first.vendorName,\n deliveryEmail: first.deliveryEmail,\n heroReferenceUrl: first.heroReferenceUrl,\n newShotDescription: first.newShotDescription,\n showStyle: first.showStyle,\n approvedVariants: approvedVariants.map(i => i.json),\n rejectedVariants: rejectedVariants.map(i => i.json),\n totalApproved: approvedVariants.length,\n totalRejected: rejectedVariants.length,\n slackApproved,\n slackRejected,\n allPassed: rejectedVariants.length === 0,\n generatedAt: new Date().toISOString()\n}}];"
},
"typeVersion": 2
},
{
"id": "755c69fc-c6ad-41e1-8509-42fcb8c843eb",
"name": "On Workflow Error",
"type": "n8n-nodes-base.errorTrigger",
"position": [
-304,
1328
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b9ca3aa7-423c-4898-a944-c77494de122f",
"name": "Slack: Error Alert",
"type": "n8n-nodes-base.slack",
"position": [
-64,
1328
],
"parameters": {
"text": "=\u274c *Style Look Transfer Workflow Failed*\n\nError: {{ $json.message }}\nTime: {{ new Date().toISOString() }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C0ANFAL4WJ2",
"cachedResultName": "social"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "ad2b85a9-c620-4afd-8230-bc7500de971f",
"name": "Section: Error Handling",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
1168
],
"parameters": {
"color": 7,
"width": 532,
"height": 348,
"content": "## \u26a0\ufe0f Error Handling\nAny uncaught workflow error triggers this path. A Slack alert is posted immediately with the error message and timestamp so the team can act without waiting for a scheduled check."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "20e8d9d2-5eca-49ec-b2f1-1f28dc304ba0",
"connections": {
"Variant Ready?2": {
"main": [
[
{
"node": "Run Style QC Check2",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait 20s Before Retry1",
"type": "main",
"index": 0
}
]
]
},
"On Workflow Error": {
"main": [
[
{
"node": "Slack: Error Alert",
"type": "main",
"index": 0
}
]
]
},
"Run Style QC Check2": {
"main": [
[
{
"node": "QC Gate: Style Approved?2",
"type": "main",
"index": 0
}
]
]
},
"Aggregate QC Results2": {
"main": [
[
{
"node": "Slack: Post Style QC Report1",
"type": "main",
"index": 0
},
{
"node": "Gmail: Send QC Report to Editorial1",
"type": "main",
"index": 0
},
{
"node": "Jira: Create Style Review Task1",
"type": "main",
"index": 0
}
]
]
},
"Wait 20s Before Retry1": {
"main": [
[
{
"node": "Poll: Check Variant Generation Status1",
"type": "main",
"index": 0
}
]
]
},
"QC Gate: Style Approved?2": {
"main": [
[
{
"node": "Aggregate QC Results2",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram: Notify on QC Rejection1",
"type": "main",
"index": 0
},
{
"node": "Aggregate QC Results2",
"type": "main",
"index": 0
}
]
]
},
"Build Style-Locked Variants2": {
"main": [
[
{
"node": "Build Style-Anchored Request2",
"type": "main",
"index": 0
}
]
]
},
"Build Style-Anchored Request2": {
"main": [
[
{
"node": "Seedance: Generate Style-Locked Variant2",
"type": "main",
"index": 0
}
]
]
},
"Merge Variant Job + Style Data2": {
"main": [
[
{
"node": "Poll: Check Variant Generation Status1",
"type": "main",
"index": 0
}
]
]
},
"Webhook: Style Transfer Request2": {
"main": [
[
{
"node": "Validate & Extract Show Style Profile2",
"type": "main",
"index": 0
}
]
]
},
"Poll: Check Variant Generation Status1": {
"main": [
[
{
"node": "Variant Ready?2",
"type": "main",
"index": 0
}
]
]
},
"Validate & Extract Show Style Profile2": {
"main": [
[
{
"node": "Build Style-Locked Variants2",
"type": "main",
"index": 0
}
]
]
},
"Seedance: Generate Style-Locked Variant2": {
"main": [
[
{
"node": "Merge Variant Job + Style Data2",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is an AI-powered style look transfer and quality control pipeline designed for VFX and editorial production. It transforms a new shot brief and a hero reference image into multiple style-locked video variants, evaluates them against a predefined show style profile,…
Source: https://n8n.io/workflows/14889/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is an AI-powered roto matte generation and first-pass compositing pipeline designed for VFX production. It transforms structured roto requests into multiple high-precision matte passes u
This workflow is an AI-assisted clean plate and object removal pipeline built for modern VFX production environments. It transforms a single plate image and removal brief into multiple high-quality cl
Are you tired of the repetitive dance between git push, creating a pull request in GitHub, updating the corresponding task in JIRA, and then manually notifying your team in Slack, or Notion?
This workflow is an AI-driven FX concept generation pipeline that transforms a single VFX brief into multiple high-quality simulation-ready video concepts. It automates ideation, rendering, storage, a
Automate end-to-end AI video creation by transforming text scripts into professional avatar videos with natural voiceovers. 🎬🤖 This workflow receives a script via webhook, generates realistic audio us