{
  "name": "PDF to AI Podcast in 9 Indian Languages with Smallest AI",
  "tags": [],
  "nodes": [
    {
      "id": "bb460b22-5b3d-4de3-9f0c-0d082bcf86fd",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7424,
        384
      ],
      "parameters": {
        "width": 480,
        "height": 1000,
        "content": "## \ud83c\udf99\ufe0f PDF to Podcast \u2014 Powered by Smallest AI\n\nConvert any PDF into a NotebookLM-style two-host podcast, delivered to your inbox. Generate audio in 19+ languages including 9 Indian tongues \u2014 Hindi, Tamil, Telugu, Kannada, Malayalam, Bengali, Marathi, Gujarati, Punjabi.\n\n### Required credentials (~5 min setup)\n\n- **Smallest AI** \u2014 get yours [API Key](https://app.smallest.ai/dashboard/api-keys) (free tier available)\n- **OpenAI** API key \u2014 GPT-5 or GPT-4o-mini\n- **Gmail** OAuth2 for delivery\n\n### Required community node\n\nThis template uses `n8n-nodes-smallestai`. Install via **Settings \u2192 Community Nodes** before importing.\n\n### How it works\n\n1. Form trigger captures the PDF\n2. GPT-5 generates a structured 2-host JSON script\n3. Switch routes each turn to the correct voice\n4. Smallest AI Lightning synthesizes each turn (~100ms TTFB)\n5. Code node concatenates WAV chunks in order\n6. Gmail emails the finished podcast\n\nPure JavaScript audio concat \u2014 no FFmpeg, works on n8n Cloud.\n\n### Customise\n\n- **Voices:** Edit `voiceId` in the two Synthesize Host nodes. Defaults: `avery` (Host A) + `devansh` (Host B). Browse all the [voices](https://docs.smallest.ai/waves/v-4-0-0/api-reference/api-reference/voices/get-waves-voices).\n- **Language:** Change `english` in the system prompt. Both voices must match.\n  - Hindi: `arjun` + `aanya` \u00b7 Tamil: `arvind` + `niharika` \u00b7 Bengali: `arnab` + `ishita`\n- **Length & format:** Edit the \"4-minute / 25-35 turns\" rule or rewrite cast personas (interview, debate, kids' story).\n- **Recipient:** Update the email in \"Email Podcast to User\".\n- **Delivery:** Swap Gmail for Slack, Telegram, or Drive Upload.\n\n### WAV format consistency\n\nDon't override `sample_rate` per call in the Smallest AI nodes \u2014 keep both Synthesize Host nodes on identical config or the concat will produce noise."
      },
      "typeVersion": 1
    },
    {
      "id": "cd5b08a4-5e6c-4729-9c2b-5825055f1d20",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7984,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 304,
        "content": "## 1. Upload PDF\n\nSubmits a PDF via the form. Extract from File pulls plain text and hands it to the script generator."
      },
      "typeVersion": 1
    },
    {
      "id": "2c23908e-dad3-494d-82b9-9e122aaa81b7",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8448,
        656
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 352,
        "content": "## 2. Generate the Podcast Script\n\nGPT-5 turns the PDF text into a structured 2-host JSON script. Schema is enforced via the OpenAI node's built-in JSON Schema mode.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3ab6b596-2dde-44f6-aaa2-173539eadd3d",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9296,
        656
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 568,
        "content": "## 3. Synthesize With the Right Voice\n\nSwitch routes each turn by `host` (A or B). Each branch calls Smallest AI Lightning with a different voice ID. Edit Fields preserve `order` and `host` for re-sorting downstream."
      },
      "typeVersion": 1
    },
    {
      "id": "6507a5a4-dddb-44c4-9dd5-d8806db9a1fb",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9872,
        864
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 320,
        "content": "## 4. Stitch Audio in Order\n\nMerge collects both branches, Sort restores conversational order via the `order` field, and the Code node concatenates all WAV chunks into a single audio file.\n\n**Pure JavaScript** \u2014 no FFmpeg required, works on n8n Cloud."
      },
      "typeVersion": 1
    },
    {
      "id": "3b1af034-f64a-4373-9abf-7f119e63225a",
      "name": "Section 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10608,
        784
      ],
      "parameters": {
        "color": 7,
        "height": 400,
        "content": "## 5. Deliver via Email\n\nGmail attaches the WAV and emails it to the recipient.\n\n**Update the `To` field** \u2014 currently set to a placeholder.\n\nSwap Gmail for Slack, Telegram, or Drive Upload as needed."
      },
      "typeVersion": 1
    },
    {
      "id": "50376525-17f4-40c6-ac88-bc1bda37ef2c",
      "name": "When Form Submitted",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        8032,
        816
      ],
      "parameters": {
        "options": {},
        "formTitle": "PDF to Podcast Generator",
        "formFields": {
          "values": [
            {
              "fieldType": "file",
              "fieldLabel": "pdf_podcast"
            }
          ]
        },
        "formDescription": "Upload a PDF and receive a two-host AI podcast by email."
      },
      "typeVersion": 2.5
    },
    {
      "id": "b764bc04-6750-490d-90bd-205c91f45e08",
      "name": "Extract Data from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        8208,
        816
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "pdf_podcast"
      },
      "typeVersion": 1.1
    },
    {
      "id": "e4f611b0-d2e8-4538-8aa5-10f160b16049",
      "name": "Generate Podcast Script (GPT-5)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        8496,
        816
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {
          "textFormat": {
            "textOptions": {
              "type": "json_schema",
              "schema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"script\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"order\": { \"type\": \"integer\" },\n          \"host\": { \"type\": \"string\", \"enum\": [\"A\", \"B\"] },\n          \"text\": { \"type\": \"string\" }\n        },\n        \"required\": [\"order\", \"host\", \"text\"],\n        \"additionalProperties\": false\n      }\n    }\n  },\n  \"required\": [\"script\"],\n  \"additionalProperties\": false\n}"
            }
          }
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "=You are scripting a 4-minute podcast in english. Aim for 25-35 short conversational turns total.\n\nCAST:\n- Host A: Avery, a young female American host. Curious, asks probing\n  questions, plays the \"interested generalist.\"\n- Host B: Devansh, a young male Indian host. The \"expert\" who explains\n  concepts, reacts to A's questions, occasionally challenges A.\n\nDIALOGUE RULES:\n1. Hosts MUST react to each other. B never just delivers a paragraph \u2014\n   B responds to what A just said, then adds new information.\n2. Average turn length: 1-3 sentences. Long monologues are forbidden.\n3. Use connective tissue: \"Right, so...\", \"Wait, but...\", \"Hmm, push back\n   on that...\", \"That's a great point \u2014 and actually...\"\n4. Include 3+ moments of interruption, qualification, or clarifying questions.\n5. Open: A welcomes listeners, introduces B + topic. Close: B summarizes,\n   A signs off.\n6. Snappy back-and-forth is the goal.\n\nNumber each turn sequentially in the `order` field. Output JSON matching\nthe schema."
            },
            {
              "content": "={{ $json.text }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "6f099ed0-ae55-474e-934d-93ef870c54a9",
      "name": "Split Script Into Turns",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        8784,
        816
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "output[0].content[0].text.script"
      },
      "typeVersion": 1
    },
    {
      "id": "4249a143-52a7-4eaa-9884-f3bcd39d9eb2",
      "name": "Route by Host",
      "type": "n8n-nodes-base.switch",
      "position": [
        8992,
        816
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "hostA",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "85998933-8933-4cea-89d0-8ad266985f46",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.host }}",
                    "rightValue": "A"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "hostB",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "e3a90670-b317-4f10-86aa-f94e8adc29df",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.host}}",
                    "rightValue": "B"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "aad2c4ef-ca7e-445f-96f2-cc9b5d1b0664",
      "name": "Synthesize Host A Audio",
      "type": "n8n-nodes-smallestai.smallestai",
      "position": [
        9360,
        800
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "voiceId": "avery",
        "additionalOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "33798e91-a00b-4475-8064-f010fc13f877",
      "name": "Synthesize Host B Audio",
      "type": "n8n-nodes-smallestai.smallestai",
      "position": [
        9376,
        1024
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "voiceId": "devansh",
        "additionalOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "94643a79-f43e-4f57-9b37-76d4ab7c0821",
      "name": "Preserve Host A Order",
      "type": "n8n-nodes-base.set",
      "position": [
        9584,
        800
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e2f8a2b1-b402-4344-a53a-2f6b726b2488",
              "name": "order",
              "type": "string",
              "value": "={{ $('Route by Host').item.json.order }}"
            },
            {
              "id": "4989e8e4-c429-4c61-9705-8193b4be85f2",
              "name": "host",
              "type": "string",
              "value": "={{ $('Route by Host').item.json.host }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "f14a3026-f707-4d72-8e5c-2ed6da414d9f",
      "name": "Preserve Host B Order",
      "type": "n8n-nodes-base.set",
      "position": [
        9600,
        1024
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "88d4bddb-d7ef-44ce-9d6f-c5ce95b84e49",
              "name": "order",
              "type": "string",
              "value": "={{ $('Route by Host').item.json.order }}"
            },
            {
              "id": "5bcb040a-ca64-4eb9-a5e9-d690a481910e",
              "name": "host",
              "type": "string",
              "value": "={{ $('Route by Host').item.json.host }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "e56ef207-0cf0-4d61-8244-c52b37ee27f7",
      "name": "Merge Both Hosts",
      "type": "n8n-nodes-base.merge",
      "position": [
        9920,
        1008
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "db2ad06c-aa24-4cd1-9240-8268e807df76",
      "name": "Sort by Conversational Order",
      "type": "n8n-nodes-base.sort",
      "position": [
        10128,
        1008
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "order"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d4b967c6-27cd-4eab-ba2e-83af67917abe",
      "name": "Concatenate WAV Audio",
      "type": "n8n-nodes-base.code",
      "position": [
        10352,
        1008
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\n\nfunction parseWav(buffer) {\n  const sampleRate = buffer.readUInt32LE(24);\n  const numChannels = buffer.readUInt16LE(22);\n  const bitsPerSample = buffer.readUInt16LE(34);\n  const dataSize = buffer.readUInt32LE(40);\n  const pcmData = buffer.slice(44, 44 + dataSize);\n  return { sampleRate, numChannels, bitsPerSample, pcmData };\n}\n\nfunction buildWavHeader(totalDataSize, sampleRate, numChannels, bitsPerSample) {\n  const header = Buffer.alloc(44);\n  const byteRate = sampleRate * numChannels * bitsPerSample / 8;\n  const blockAlign = numChannels * bitsPerSample / 8;\n  header.write('RIFF', 0);\n  header.writeUInt32LE(36 + totalDataSize, 4);\n  header.write('WAVE', 8);\n  header.write('fmt ', 12);\n  header.writeUInt32LE(16, 16);\n  header.writeUInt16LE(1, 20);\n  header.writeUInt16LE(numChannels, 22);\n  header.writeUInt32LE(sampleRate, 24);\n  header.writeUInt32LE(byteRate, 28);\n  header.writeUInt16LE(blockAlign, 32);\n  header.writeUInt16LE(bitsPerSample, 34);\n  header.write('data', 36);\n  header.writeUInt32LE(totalDataSize, 40);\n  return header;\n}\n\nconst pcmChunks = [];\nlet format = null;\n\nfor (let i = 0; i < items.length; i++) {\n  const buffer = await this.helpers.getBinaryDataBuffer(i, 'data');\n  const parsed = parseWav(buffer);\n\n  if (!format) {\n    format = {\n      sampleRate: parsed.sampleRate,\n      numChannels: parsed.numChannels,\n      bitsPerSample: parsed.bitsPerSample\n    };\n  }\n  pcmChunks.push(parsed.pcmData);\n}\n\nconst totalPcm = Buffer.concat(pcmChunks);\nconst header = buildWavHeader(\n  totalPcm.length,\n  format.sampleRate,\n  format.numChannels,\n  format.bitsPerSample\n);\nconst finalWav = Buffer.concat([header, totalPcm]);\n\nreturn [{\n  json: { fileName: 'podcast.wav', sizeBytes: finalWav.length },\n  binary: {\n    data: await this.helpers.prepareBinaryData(finalWav, 'podcast.wav', 'audio/wav')\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f73e4d22-9382-4d04-a63c-fc8547b06a80",
      "name": "Email Podcast to User",
      "type": "n8n-nodes-base.gmail",
      "position": [
        10672,
        1008
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "Your generated podcast is attached. Powered by Smallest AI.",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {}
            ]
          }
        },
        "subject": "Your AI Podcast is Ready \ud83c\udf99\ufe0f"
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "connections": {
    "Route by Host": {
      "main": [
        [
          {
            "node": "Synthesize Host A Audio",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Synthesize Host B Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Both Hosts": {
      "main": [
        [
          {
            "node": "Sort by Conversational Order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Form Submitted": {
      "main": [
        [
          {
            "node": "Extract Data from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Concatenate WAV Audio": {
      "main": [
        [
          {
            "node": "Email Podcast to User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preserve Host A Order": {
      "main": [
        [
          {
            "node": "Merge Both Hosts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preserve Host B Order": {
      "main": [
        [
          {
            "node": "Merge Both Hosts",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract Data from File": {
      "main": [
        [
          {
            "node": "Generate Podcast Script (GPT-5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Script Into Turns": {
      "main": [
        [
          {
            "node": "Route by Host",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synthesize Host A Audio": {
      "main": [
        [
          {
            "node": "Preserve Host A Order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synthesize Host B Audio": {
      "main": [
        [
          {
            "node": "Preserve Host B Order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by Conversational Order": {
      "main": [
        [
          {
            "node": "Concatenate WAV Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Podcast Script (GPT-5)": {
      "main": [
        [
          {
            "node": "Split Script Into Turns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}