{
  "name": "Clean Optimized Workflow",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        400,
        300
      ],
      "id": "manual-trigger",
      "name": "Manual Trigger"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "1kh0f3fhpCSvlv0jQrELaVHHSAByQikVLc08zwp7YBWM",
          "mode": "list"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "status",
              "lookupValue": "new"
            }
          ]
        },
        "options": {
          "returnFirstMatch": true
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.6,
      "position": [
        600,
        300
      ],
      "id": "get-row",
      "name": "Get Pending Row",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.idea }}",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "You are a professional motivational content creator. Your task is to generate a powerful, cinematic-style motivational script designed for a short-form video under 60 seconds. The final output should be split into 3 parts, each tied to a specific black & white image and audio segment.\n\nRequirements:\n- Each image must depict **only one person** in a wide or mid-shot (no close-up shots).\n- The image prompt should be **detailed**, cinematic, and emotionally expressive.\n- Every image description must end with: **\"Black & White\"**\n- Each part should include:\n  1. **Image Prompt** \u2013 A highly visual, detailed prompt for generating a black & white image.\n  2. **Script Text** \u2013 A motivational sentence (1\u20132 lines) that can be read in 5\u20137 seconds.\n\nConstraints:\n- Total combined audio must be under 60 seconds.\n- Keep language grounded, simple, and impactful.\n- No hashtags, bullet points, or emojis.\n- Topic: {{ $json.idea }}\n\nOutput Format (JSON):\n{\n  \"image_1\": \"....\",\n  \"image_2\": \"....\",\n  \"image_3\": \"....\",\n  \"audio_1\": \"....\",\n  \"audio_2\": \"....\",\n  \"audio_3\": \"....\"\n}"
            }
          ]
        }
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        800,
        300
      ],
      "id": "generate-script",
      "name": "Generate Script"
    },
    {
      "parameters": {
        "model": "llama-3.3-70b-versatile",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "typeVersion": 1,
      "position": [
        800,
        460
      ],
      "id": "groq",
      "name": "Groq Model",
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"image_1\": \"...\",\n  \"image_2\": \"...\",\n  \"image_3\": \"...\",\n  \"audio_1\": \"...\",\n  \"audio_2\": \"...\",\n  \"audio_3\": \"...\"\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        800,
        620
      ],
      "id": "parser",
      "name": "Output Parser"
    },
    {
      "parameters": {
        "jsCode": "// Simple hash function\nfunction simpleHash(str) {\n  let hash = 0;\n  for (let i = 0; i < str.length; i++) {\n    const char = str.charCodeAt(i);\n    hash = ((hash << 5) - hash) + char;\n    hash = hash & hash;\n  }\n  return Math.abs(hash).toString(36);\n}\n\nconst output = $input.first().json.output;\nconst items = [];\n\n// Process images\nfor (let i = 1; i <= 3; i++) {\n  const script = output[`image_${i}`];\n  if (script) {\n    items.push({\n      json: {\n        type: 'image',\n        index: i,\n        script: script,\n        script_hash: simpleHash(script)\n      }\n    });\n  }\n}\n\n// Process audio\nfor (let i = 1; i <= 3; i++) {\n  const script = output[`audio_${i}`];\n  if (script) {\n    items.push({\n      json: {\n        type: 'audio',\n        index: i,\n        script: script,\n        script_hash: simpleHash(script)\n      }\n    });\n  }\n}\n\nreturn items;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1000,
        300
      ],
      "id": "split-hash",
      "name": "Split & Hash"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "1kh0f3fhpCSvlv0jQrELaVHHSAByQikVLc08zwp7YBWM",
          "mode": "list"
        },
        "sheetName": {
          "__rl": true,
          "value": 1778583878,
          "mode": "list",
          "cachedResultName": "Generated_Assets"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "script_hash",
              "lookupValue": "={{ $json.script_hash }}"
            },
            {
              "lookupColumn": "type",
              "lookupValue": "={{ $json.type }}"
            }
          ]
        },
        "options": {
          "returnFirstMatch": true
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.6,
      "position": [
        1200,
        300
      ],
      "id": "check-cache",
      "name": "Check Cache",
      "continueOnFail": true,
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Merge original item with cache result\nconst originalItem = $('Split & Hash').item.json;\nconst cacheResult = $input.first().json;\n\n// Check if we found a cached asset\nconst cachedUrl = cacheResult.asset_url || null;\n\nreturn [{\n  json: {\n    ...originalItem,\n    cached_url: cachedUrl,\n    needs_generation: cachedUrl === null\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1400,
        300
      ],
      "id": "merge-cache-result",
      "name": "Merge Cache Result"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "needs-gen",
              "leftValue": "={{ $json.needs_generation }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1600,
        300
      ],
      "id": "needs-gen",
      "name": "Needs Generation?"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "asset_url",
              "value": "={{ $json.cached_url }}",
              "type": "string"
            },
            {
              "id": "2",
              "name": "type",
              "value": "={{ $json.type }}",
              "type": "string"
            },
            {
              "id": "3",
              "name": "index",
              "value": "={{ $json.index }}",
              "type": "number"
            },
            {
              "id": "4",
              "name": "script_hash",
              "value": "={{ $json.script_hash }}",
              "type": "string"
            },
            {
              "id": "5",
              "name": "from_cache",
              "value": true,
              "type": "boolean"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1800,
        200
      ],
      "id": "use-cached",
      "name": "Use Cached"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "is-image",
              "leftValue": "={{ $json.type }}",
              "rightValue": "image",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1800,
        400
      ],
      "id": "type-check",
      "name": "Type?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/flux-pro/v1.1-ultra",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"prompt\": \"{{ $json.script }}\",\n  \"num_images\": 1,\n  \"aspect_ratio\": \"9:16\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2000,
        320
      ],
      "id": "gen-image",
      "name": "Start Image Gen",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/elevenlabs/tts/multilingual-v2",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"{{ $json.script }}\",\n  \"voice\": \"Brian\",\n  \"stability\": 0.5,\n  \"similarity_boost\": 0.75,\n  \"speed\": 1\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2000,
        480
      ],
      "id": "gen-audio",
      "name": "Start Audio Gen",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "// Poll for image completion\nconst requestId = $input.first().json.request_id;\nconst originalItem = $('Type?').item.json;\nconst maxRetries = 40;\nconst delay = 5000;\n\nfor (let i = 0; i < maxRetries; i++) {\n  const result = await this.helpers.httpRequestWithAuthentication.call(\n    this,\n    'httpHeaderAuth',\n    {\n      method: 'GET',\n      url: `https://queue.fal.run/fal-ai/flux-pro/requests/${requestId}`\n    }\n  );\n\n  if (result.status === 'COMPLETED') {\n    return [{\n      json: {\n        asset_url: result.images[0].url,\n        type: 'image',\n        index: originalItem.index,\n        script_hash: originalItem.script_hash,\n        from_cache: false\n      }\n    }];\n  }\n\n  if (result.status === 'FAILED') {\n    throw new Error(`Image generation failed: ${JSON.stringify(result.error)}`);\n  }\n\n  await new Promise(resolve => setTimeout(resolve, delay));\n}\n\nthrow new Error('Image generation timeout');"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        320
      ],
      "id": "poll-image",
      "name": "Poll Image",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 10000
    },
    {
      "parameters": {
        "jsCode": "// Poll for audio completion\nconst requestId = $input.first().json.request_id;\nconst originalItem = $('Type?').item.json;\nconst maxRetries = 40;\nconst delay = 5000;\n\nfor (let i = 0; i < maxRetries; i++) {\n  const result = await this.helpers.httpRequestWithAuthentication.call(\n    this,\n    'httpHeaderAuth',\n    {\n      method: 'GET',\n      url: `https://queue.fal.run/fal-ai/elevenlabs/requests/${requestId}`\n    }\n  );\n\n  if (result.status === 'COMPLETED') {\n    return [{\n      json: {\n        asset_url: result.audio.url,\n        type: 'audio',\n        index: originalItem.index,\n        script_hash: originalItem.script_hash,\n        from_cache: false\n      }\n    }];\n  }\n\n  if (result.status === 'FAILED') {\n    throw new Error(`Audio generation failed: ${JSON.stringify(result.error)}`);\n  }\n\n  await new Promise(resolve => setTimeout(resolve, delay));\n}\n\nthrow new Error('Audio generation timeout');"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        480
      ],
      "id": "poll-audio",
      "name": "Poll Audio",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 10000
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "1kh0f3fhpCSvlv0jQrELaVHHSAByQikVLc08zwp7YBWM",
          "mode": "list"
        },
        "sheetName": {
          "__rl": true,
          "value": 1778583878,
          "mode": "list",
          "cachedResultName": "Generated_Assets"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "script_hash": "={{ $json.script_hash }}",
            "asset_url": "={{ $json.asset_url }}",
            "type": "={{ $json.type }}",
            "timestamp": "={{ $now.toISO() }}",
            "status": "completed"
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2200,
        400
      ],
      "id": "save-cache",
      "name": "Save To Cache",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll"
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2400,
        300
      ],
      "id": "merge-all",
      "name": "Merge All"
    },
    {
      "parameters": {
        "jsCode": "// Rebuild final structure\nconst items = $input.all();\nconst result = { json: {} };\n\nitems.forEach(item => {\n  const key = `${item.json.type}_${item.json.index}`;\n  result.json[key] = item.json.asset_url;\n});\n\n// Add stats\nconst cached = items.filter(i => i.json.from_cache).length;\nresult.json.stats = {\n  total: items.length,\n  cached: cached,\n  generated: items.length - cached\n};\n\nreturn [result];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2600,
        300
      ],
      "id": "rebuild",
      "name": "Rebuild Structure"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "Image 1",
              "value": "={{ $json.image_1 }}",
              "type": "string"
            },
            {
              "id": "2",
              "name": "Audio 1",
              "value": "={{ $json.audio_1 }}",
              "type": "string"
            },
            {
              "id": "3",
              "name": "Image 2",
              "value": "={{ $json.image_2 }}",
              "type": "string"
            },
            {
              "id": "4",
              "name": "Audio 2",
              "value": "={{ $json.audio_2 }}",
              "type": "string"
            },
            {
              "id": "5",
              "name": "Image 3",
              "value": "={{ $json.image_3 }}",
              "type": "string"
            },
            {
              "id": "6",
              "name": "Audio 3",
              "value": "={{ $json.audio_3 }}",
              "type": "string"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2800,
        300
      ],
      "id": "final-output",
      "name": "Final Output"
    },
    {
      "parameters": {
        "content": "## START\nGet pending row",
        "height": 200,
        "width": 300,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        380,
        200
      ],
      "id": "note1",
      "name": "Note 1"
    },
    {
      "parameters": {
        "content": "## SCRIPT GENERATION\nCreate motivational content",
        "height": 400,
        "width": 300,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        780,
        200
      ],
      "id": "note2",
      "name": "Note 2"
    },
    {
      "parameters": {
        "content": "## SPLIT & CACHE CHECK\nFor each item:\n1. Hash the script\n2. Check if cached\n3. Route based on cache",
        "height": 200,
        "width": 400,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        980,
        200
      ],
      "id": "note3",
      "name": "Note 3"
    },
    {
      "parameters": {
        "content": "## GENERATION\nGenerate only if not cached\nPoll until ready\nSave to cache",
        "height": 360,
        "width": 680,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1560,
        240
      ],
      "id": "note4",
      "name": "Note 4"
    },
    {
      "parameters": {
        "content": "## MERGE & OUTPUT\nCombine all assets\nPrepare for video processing",
        "height": 200,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2360,
        200
      ],
      "id": "note5",
      "name": "Note 5"
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Get Pending Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Pending Row": {
      "main": [
        [
          {
            "node": "Generate Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Script": {
      "main": [
        [
          {
            "node": "Split & Hash",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Script",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Script",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Split & Hash": {
      "main": [
        [
          {
            "node": "Check Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Cache": {
      "main": [
        [
          {
            "node": "Merge Cache Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Cache Result": {
      "main": [
        [
          {
            "node": "Needs Generation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Generation?": {
      "main": [
        [
          {
            "node": "Type?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Use Cached",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Use Cached": {
      "main": [
        [
          {
            "node": "Merge All",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Type?": {
      "main": [
        [
          {
            "node": "Start Image Gen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Start Audio Gen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Image Gen": {
      "main": [
        [
          {
            "node": "Poll Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Audio Gen": {
      "main": [
        [
          {
            "node": "Poll Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Image": {
      "main": [
        [
          {
            "node": "Save To Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Audio": {
      "main": [
        [
          {
            "node": "Save To Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save To Cache": {
      "main": [
        [
          {
            "node": "Merge All",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All": {
      "main": [
        [
          {
            "node": "Rebuild Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rebuild Structure": {
      "main": [
        [
          {
            "node": "Final Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "id": "CleanOptimizedWorkflow",
  "tags": []
}