{
  "id": "E7Npvta2ZCEtC6U7",
  "name": "Transcribe and Summarize Podcast Episodes to X (Twitter) with Slack Approval",
  "tags": [],
  "nodes": [
    {
      "id": "ad312cb5-6f9a-44bd-bca6-595eccd6b466",
      "name": "\u4ed8\u7b8b9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        -288
      ],
      "parameters": {
        "width": 644,
        "height": 640,
        "content": "# Podcast Summary & Social Post with Human-in-the-Loop\n\nThis workflow automatically fetches the latest podcast episode, transcribes the audio, generates a summary using AI, and posts it to X (Twitter) after your approval in Slack.\n\n## How it works\n1. **Trigger:** The workflow runs on a schedule (e.g., daily) to fetch the latest episode from a Podcast RSS feed.\n2. **Process:** It handles large audio files by downloading them in chunks. It then transcribes the audio using OpenAI Whisper and generates a bullet-point summary using GPT-4o-mini.\n3. **Approval:** The drafted post is sent to Slack. The workflow pauses and waits for you to approve.\n4. **Action:** Once approved in Slack, the content is automatically posted to X (Twitter).\n\n## Setup steps\n1. **Configure RSS:** Open the node `[Step 1] RSS` and replace the URL with your target Podcast RSS feed.\n2. **Credentials:** Connect your OpenAI, Slack, and Twitter (X) credentials in the respective nodes.\n3. **Slack Setup:** In the node `[Step 12] Slack`, select the Channel ID where you want to receive the approval request.\n4. **Customize:** You can adjust the system prompt in `[Step 11] LLM` to change the tone or format of the summary."
      },
      "typeVersion": 1
    },
    {
      "id": "e0655577-b632-4075-8720-de523c2d429a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 1072,
        "height": 384,
        "content": "## RSS Fetch & Date Formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "33590a87-32cc-46dc-874b-56ec01b886b5",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 384,
        "content": "## Audio Chunking\nHandles large file downloads by splitting them into chunks."
      },
      "typeVersion": 1
    },
    {
      "id": "9242f889-8bac-4285-a911-cb3097e9949a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 384,
        "content": "## Transcribe & Summarize (AI)\nUses Whisper for transcription and GPT-4o for summarization."
      },
      "typeVersion": 1
    },
    {
      "id": "4d004742-6874-442f-9667-6396f58279f4",
      "name": "[Step 0] Schedule \u2014 Daily 09:",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        16,
        32
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9a4b1f61-0446-4ea4-af42-7022469284d5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2752,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 384,
        "content": "## Approval & Posting\nWait for Slack approval before posting to X."
      },
      "typeVersion": 1
    },
    {
      "id": "4e445fa6-a33b-400d-a1f8-6264a5384036",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 352,
        "content": "## Prompt Customization\n\nYou can change the prompt language to match your podcast (e.g., Japanese, Spanish)."
      },
      "typeVersion": 1
    },
    {
      "id": "a22f2cbd-ae46-4936-86cb-81cf2555b1bf",
      "name": "[Step 11] LLM \u2014 Summarize as News Bullets",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2480,
        32
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are an expert copywriter. Please summarize the podcast transcript below, strictly following the summary rules. Output in the specified format.\n\n# Podcast Transcript\n{{ $json.fullText }}\n\n# Summary Rules\n1. Summarize each news item in 1-2 concise sentences.\n2. Include key facts (dates, places, people, numbers).\n3. Prioritize news based on its importance in the episode.\n4. Exclude intros, weather forecasts, and ads.\n5. Format today's date as \"YYYY-MM-DD\".\n\n# Output Format\n{{ $json.date }} Podcast Summary\ud83d\udc47\n\n-  Content 1 \n-  Content 2\n-  Content 3 "
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "4a0b4604-15fa-4f5f-ab1a-9d5297be36a4",
      "name": "[Step 12] Slack \u2014 Review & Approve",
      "type": "n8n-nodes-base.slack",
      "position": [
        2832,
        32
      ],
      "parameters": {
        "select": "channel",
        "message": "=The podcast summary is ready. Here is the drafted tweet content:\n\n---\n\n{{ $json.message.content }}",
        "options": {},
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#example"
        },
        "operation": "sendAndWait"
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "7abdbb60-1889-4ae8-940f-a8ac8465d0de",
      "name": "[Step 13] X (Twitter) \u2014 Post Tweet",
      "type": "n8n-nodes-base.twitter",
      "position": [
        3056,
        32
      ],
      "parameters": {
        "text": "={{ $('[Step 11] LLM \u2014 Summarize as News Bullets').item.json.message.content }}",
        "additionalFields": {}
      },
      "credentials": {
        "twitterOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "0b26a5a8-28a8-4e73-ba7e-954b2444c108",
      "name": "[Step 10] Text \u2014 Merge Transcript & Pass Date",
      "type": "n8n-nodes-base.code",
      "position": [
        2256,
        32
      ],
      "parameters": {
        "jsCode": "// Sort by part number if available\nitems.sort((a, b) => (a.json.part || 0) - (b.json.part || 0));\n\n// Join transcript texts with newlines\nconst fullText = items.map(item => item.json.text).join(\"\\n\");\nconst date = $('[Step 4] Item \u2014 Select Enclosure URL & Date').first().json.date;\n\nreturn [{ json: { fullText, date } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "d7c60c9a-6bf6-4eec-8c71-8b1f76a6cd26",
      "name": "[Step 9] Text \u2014 Attach Part Number",
      "type": "n8n-nodes-base.set",
      "position": [
        2032,
        32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5e8e0908-df6a-404f-bf9b-38170c7a2f3a",
              "name": "text",
              "type": "string",
              "value": "={{ $json.text }}"
            },
            {
              "id": "b56e6282-4b36-48d9-af4c-aef6d5c201dd",
              "name": "part",
              "type": "number",
              "value": "={{ $('[Step 6] Chunk \u2014 Plan Byte Ranges').item.json.part }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "9a0fd855-8305-43da-b633-8468436ba500",
      "name": "[Step 8] Speech-to-Text \u2014 Transcribe Chunks",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1808,
        32
      ],
      "parameters": {
        "options": {},
        "resource": "audio",
        "operation": "transcribe"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "ab38e80b-96ec-4416-916d-038b09398797",
      "name": "[Step 7] HTTP \u2014 Download Chunk",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1584,
        32
      ],
      "parameters": {
        "url": "={{ $('[Step 4] Item \u2014 Select Enclosure URL & Date').item.json.enclosure.url }}",
        "options": {
          "timeout": 30000,
          "redirect": {
            "redirect": {
              "maxRedirects": 10
            }
          },
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0"
            },
            {
              "name": "Accept",
              "value": "audio/mpeg"
            },
            {
              "name": "Connection",
              "value": "close"
            },
            {
              "name": "Range",
              "value": "={{ $json.end !== '' && $json.end !== null && $json.end !== undefined   ? 'bytes=' + $json.start + '-' + $json.end   : 'bytes=' + $json.start + '-' }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "6e506503-d297-43f6-b072-f8fd2c6e75e4",
      "name": "[Step 6] Chunk \u2014 Plan Byte Ranges",
      "type": "n8n-nodes-base.code",
      "position": [
        1360,
        32
      ],
      "parameters": {
        "jsCode": "const url = $('[Step 4] Item \u2014 Select Enclosure URL & Date').first().json.enclosure.url; // \u2190 Always present\nconst cr  = $json.headers?.['content-range'];\n\nif (!cr) throw new Error('No content-range header found. Check if \"Return Full Response\" is ON in the HTTP(0-0) node or if the status is 206.');\n\nconst total = Number(cr.split('/')[1]);\nconst CHUNK = 24 * 1024 * 1024; // 24MB chunks\nconst out = [];\nlet start = 0, part = 1;\n\nwhile (start < total) {\n  const end = Math.min(start + CHUNK - 1, total - 1);\n  out.push({ json: { url, part, start, end } });\n  start = end + 1; part++;\n}\nreturn out;"
      },
      "typeVersion": 2
    },
    {
      "id": "f864aaf4-a53b-467b-98e4-f483d47f5d0d",
      "name": "[Step 5] HTTP \u2014 Probe Content-Range (0\u20130)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1136,
        32
      ],
      "parameters": {
        "url": "={{ $json.enclosure.url }}",
        "options": {
          "timeout": 30000,
          "redirect": {
            "redirect": {
              "maxRedirects": 10
            }
          },
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Range",
              "value": "=bytes=0-0"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0db83e42-83c7-44da-89a9-79ddbba85f45",
      "name": "[Step 4] Item \u2014 Select Enclosure URL & Date",
      "type": "n8n-nodes-base.set",
      "position": [
        912,
        32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "28b25795-0f2a-4bcf-bf31-b4e6242ecabe",
              "name": "enclosure.url",
              "type": "string",
              "value": "={{ $('[Step 2] RSS \u2014 Take Latest Item').item.json.enclosure.url }}"
            },
            {
              "id": "bd3f2f40-2868-47ca-8874-ab18943025ab",
              "name": "date",
              "type": "string",
              "value": "={{ $json.formatted }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5367e60e-b3e6-4165-9254-248783aec81b",
      "name": "[Step 3] Date \u2014 Format as YYYY-MM-DD",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        32
      ],
      "parameters": {
        "jsCode": "// Get the date string from the RSS node\nconst dateStr = $('[Step 1] RSS \u2014 Fetch Feed').first().json.pubDate; \n\n// Convert to Date object\nconst date = new Date(dateStr);\n\n// Get year, month, and day\nconst year = date.getFullYear();\nconst month = date.getMonth() + 1; // Month is 0-indexed\nconst day = date.getDate();\n\n// Return formatted string (YYYY-MM-DD)\nreturn [\n  {    json: {\n      original: dateStr,\n      formatted: `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "da3005f1-35b0-4e57-a8ea-19b10ff514e7",
      "name": "[Step 2] RSS \u2014 Take Latest Item",
      "type": "n8n-nodes-base.limit",
      "position": [
        464,
        32
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1bf40255-13f7-45e7-8e4a-9e848f893a31",
      "name": "[Step 1] RSS \u2014 Fetch Feed",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        240,
        32
      ],
      "parameters": {
        "url": "https://feeds.feedburner.com/tedtalks_audio",
        "options": {}
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "68e862cf-1513-4a35-83eb-cb419c8c773d",
  "connections": {
    "[Step 1] RSS \u2014 Fetch Feed": {
      "main": [
        [
          {
            "node": "[Step 2] RSS \u2014 Take Latest Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 0] Schedule \u2014 Daily 09:": {
      "main": [
        [
          {
            "node": "[Step 1] RSS \u2014 Fetch Feed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 7] HTTP \u2014 Download Chunk": {
      "main": [
        [
          {
            "node": "[Step 8] Speech-to-Text \u2014 Transcribe Chunks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 2] RSS \u2014 Take Latest Item": {
      "main": [
        [
          {
            "node": "[Step 3] Date \u2014 Format as YYYY-MM-DD",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 6] Chunk \u2014 Plan Byte Ranges": {
      "main": [
        [
          {
            "node": "[Step 7] HTTP \u2014 Download Chunk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 12] Slack \u2014 Review & Approve": {
      "main": [
        [
          {
            "node": "[Step 13] X (Twitter) \u2014 Post Tweet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 9] Text \u2014 Attach Part Number": {
      "main": [
        [
          {
            "node": "[Step 10] Text \u2014 Merge Transcript & Pass Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 3] Date \u2014 Format as YYYY-MM-DD": {
      "main": [
        [
          {
            "node": "[Step 4] Item \u2014 Select Enclosure URL & Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 11] LLM \u2014 Summarize as News Bullets": {
      "main": [
        [
          {
            "node": "[Step 12] Slack \u2014 Review & Approve",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 4] Item \u2014 Select Enclosure URL & Date": {
      "main": [
        [
          {
            "node": "[Step 5] HTTP \u2014 Probe Content-Range (0\u20130)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 5] HTTP \u2014 Probe Content-Range (0\u20130)": {
      "main": [
        [
          {
            "node": "[Step 6] Chunk \u2014 Plan Byte Ranges",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 8] Speech-to-Text \u2014 Transcribe Chunks": {
      "main": [
        [
          {
            "node": "[Step 9] Text \u2014 Attach Part Number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "[Step 10] Text \u2014 Merge Transcript & Pass Date": {
      "main": [
        [
          {
            "node": "[Step 11] LLM \u2014 Summarize as News Bullets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}