8
n8n 中文网amn8n.com

每日体育摘要

高级

这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 34 个节点。主要使用 If, Set, Code, Merge, Filter 等节点。 使用Google Gemini、Kokoro TTS和FFmpeg将RSS源转换为播客

前置要求
  • Telegram Bot Token
  • 可能需要目标 API 的认证凭证
  • Google Gemini API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "4zCVyhXKummU8dD4",
  "meta": {
    "instanceId": "8683f1aa9dbb94f1118c6d4f62e60551722c177df8a0b76c24809a905727cb8d",
    "templateCredsSetupCompleted": true
  },
  "name": "每日体育摘要",
  "tags": [],
  "nodes": [
    {
      "id": "e01655d1-9722-4630-a7d6-4425282ace11",
      "name": "Google Gemini聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -2500,
        -280
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "1uJGKonJ3ZbmhxG0",
          "name": "Google Gemini(PaLM) Api account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3271e00c-bf97-4d63-b1ec-8e33c63eaf29",
      "name": "Google Gemini 聊天模型1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -2320,
        620
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "1uJGKonJ3ZbmhxG0",
          "name": "Google Gemini(PaLM) Api account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e34ac6ba-25c6-4941-9e53-31ff3bb00801",
      "name": "便签 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4740,
        -600
      ],
      "parameters": {
        "width": 420,
        "height": 1240,
        "content": "## 🎧 每日 RSS 摘要与播客生成"
      },
      "typeVersion": 1
    },
    {
      "id": "10c71894-9b5c-4bd0-a618-57ca8161275b",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4300,
        -600
      ],
      "parameters": {
        "color": 7,
        "width": 1352,
        "height": 1244,
        "content": "## 📰 步骤 1:获取并筛选每日新闻"
      },
      "typeVersion": 1
    },
    {
      "id": "a9078490-2bd4-4b5e-ada1-ee67e147d139",
      "name": "每日触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -4240,
        -100
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "0af5a33b-e2e4-4fc3-95c4-a5c24c6b2ab5",
      "name": "获取 RSS 1:Folha de SP",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -3900,
        -260
      ],
      "parameters": {
        "url": "https://feeds.folha.uol.com.br/esporte/rss091.xml",
        "options": {}
      },
      "typeVersion": 1.1
    },
    {
      "id": "84cc82a2-937f-4b7f-801c-045fb7e164cf",
      "name": "获取 RSS 2:GE",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -3900,
        40
      ],
      "parameters": {
        "url": "https://ge.globo.com/rss/ge/",
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "5b1afc5f-829b-4ee2-866e-4bf045631f43",
      "name": "清理字段",
      "type": "n8n-nodes-base.set",
      "position": [
        -3640,
        -260
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a0229879-764d-455d-9064-2d939d7e5701",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title.replace(/\\[PACK\\].*/, \"\").replace(/\\[.*?\\]/g, \"\").trim() }}"
            },
            {
              "id": "2da9330c-e39f-4515-b737-d14f3c4aeb8b",
              "name": "pubDate",
              "type": "string",
              "value": "={{ $json.pubDate }}"
            },
            {
              "id": "c7b0f3d6-e2bb-48a7-9911-edcd44700868",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link.replace(/\\/torrent\\/download\\/(\\d+)\\..*/, \"/torrents/$1\") }}"
            },
            {
              "id": "05d172b5-3201-450d-b02f-fc9b649664f0",
              "name": "content",
              "type": "string",
              "value": "={{ $json.content }}"
            },
            {
              "id": "db80b578-d9b4-40fd-bbe1-6e7615a27ce3",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0d76fc33-7930-4ab8-b363-b86e1e5f6889",
      "name": "清理字段1",
      "type": "n8n-nodes-base.set",
      "position": [
        -3640,
        40
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a0229879-764d-455d-9064-2d939d7e5701",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title.replace(/\\[PACK\\].*/, \"\").replace(/\\[.*?\\]/g, \"\").trim() }}"
            },
            {
              "id": "2da9330c-e39f-4515-b737-d14f3c4aeb8b",
              "name": "pubDate",
              "type": "string",
              "value": "={{ $json.pubDate }}"
            },
            {
              "id": "c7b0f3d6-e2bb-48a7-9911-edcd44700868",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link.replace(/\\/torrent\\/download\\/(\\d+)\\..*/, \"/torrents/$1\") }}"
            },
            {
              "id": "05d172b5-3201-450d-b02f-fc9b649664f0",
              "name": "content",
              "type": "string",
              "value": "={{ $json.content }}"
            },
            {
              "id": "db80b578-d9b4-40fd-bbe1-6e7615a27ce3",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "69db334b-7681-4174-abaf-f745306d28f4",
      "name": "合并新闻源",
      "type": "n8n-nodes-base.merge",
      "position": [
        -3380,
        -100
      ],
      "parameters": {},
      "typeVersion": 3
    },
    {
      "id": "56d67b29-8950-49f8-bdea-d8cd5461c8da",
      "name": "筛选过去24小时新闻",
      "type": "n8n-nodes-base.filter",
      "position": [
        -3100,
        -100
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c590146a-caae-495c-a933-37864e921876",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ (new Date()).setHours(0, 0, 0, 0) - 24 * 60 * 60 * 1000 }}"
            },
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ (new Date()).setHours(0, 0, 0, 0) }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f597998a-7621-49ce-9933-6db94c53325e",
      "name": "为 AI 准备数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -2820,
        -100
      ],
      "parameters": {
        "jsCode": "// This code gets all the news items that passed the filter.\nconst allItems = $input.all();\n\n// We will format each news item with its title, content, and a link.\nconst formattedNews = allItems.map(item => {\n  // Get the title, content, and link from the JSON data of each item.\n  const title = item.json.title;\n  const content = item.json.content;\n  const link = item.json.link;\n\n  // Return a clean, formatted string for each article.\n  return `\n---\nTitle: ${title}\nContent: ${content}\nLink: ${link}\n---\n  `;\n});\n\n// --- NEW CODE ADDED BELOW ---\n// Get today's date and format it beautifully.\n// It will look like: \"July 31, 2025\"\nconst digestDate = $now.setZone('America/Sao_Paulo').toFormat('MMMM d, yyyy');\n\n// Join all the formatted news items into a single block of text\n// and return it as an object along with our new date string.\nreturn {\n  allNews: formattedNews.join('\\n'),\n  digestDate: digestDate\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "db56296f-138c-4441-9593-a2392efef7fd",
      "name": "创建临时目录",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        -2540,
        380
      ],
      "parameters": {
        "command": "mkdir -p /tmp/dailydigest"
      },
      "typeVersion": 1
    },
    {
      "id": "a0290570-125b-4e68-a06c-f89cc83efd10",
      "name": "生成文本摘要",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -2500,
        -520
      ],
      "parameters": {
        "text": "=**ROLE & GOAL:**\nYou are a witty and engaging sports journalist. Your goal is to create a \"Daily Sports Digest\" in English that is informative and fun to read, summarizing sports news from Brazil.\n\n**CONTEXT:**\nYou will receive a block of text with news articles written in Portuguese and a formatted string for today's date.\n\n**CRITICAL RULES:**\n1.  **Language:** Your entire output MUST be in English.\n2.  **Source Integrity:** Use ONLY the information from the provided text. DO NOT add external information or speculate.\n3.  **Markdown:** Your entire output must use MarkdownV2 for formatting.\n\n**OUTPUT STRUCTURE:**\n\n**1. Title:**\n- Use the provided date string to create a top-level heading like this: `# Daily Sports Digest: {{ $('Prepare Data for AI').item.json.digestDate }}`\n\n**2. Introduction:**\n- On the next line, start with a single, engaging paragraph (2-3 sentences) that gives a \"big picture\" summary of the day's main news or overall theme. Use a \"newspaper\" emoji 📰 at the beginning.\n\n**3. Football Section:**\n- Create a main heading: `## ⚽ Football Focus`\n- If there is enough variety, categorize the football news into subheadings like `### 🇧🇷 Brazilian Clubs`, `### 🌎 International`, or `### 🏆 Tournaments`. Use your best judgment based on the articles provided.\n\n**4. Other Sports Section:**\n- If there are articles on other sports (Motorsport, Basketball, etc.), group them all under a single heading: `## ⚡ Around the Horn`.\n\n**5. News Item Format (for every single article):**\n- Start the line with a single, relevant emoji (e.g.,  investigatory nature of a story).\n- Translate the original title into a concise and accurate English headline. Display this new English headline as a clickable link to its URL.\n- On the **next line**, write your concise 1-2 sentence summary in English.\n\n**EXAMPLE of the required News Item format:**\nIf you receive: `Title: AFA e River Plate criticam aumento de imposto a clubes na Argentina\\nLink: http://example.com/news-link`\nYou must format it like this: `[AFA and River Plate Criticize Tax Increase on Clubs in Argentina](http://example.com/news-link)\\nThe Argentine Football Association and River Plate have criticized a government measure to increase taxes on football clubs.`\n\n---\n**Here is the block of text with today's articles:**\n{{ $('Prepare Data for AI').item.json.allNews }}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2
    },
    {
      "id": "08b47cef-36e7-4491-8c9d-8c1bf674f474",
      "name": "发送文本摘要",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -2040,
        -520
      ],
      "webhookId": "ca51bbe7-102b-4b7f-8442-560a0ccc7628",
      "parameters": {
        "text": "={{ $json.output }}",
        "chatId": "[YOUR_TELEGRAM_CHAT_ID]",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "kuP8lCkbwbeD61gU",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "cf8d7651-4b2b-4ede-9fff-c7ca9fa6b248",
      "name": "生成播客脚本",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -2320,
        380
      ],
      "parameters": {
        "text": "=You are the scriptwriter of a podcast that transforms dense written content into a lively, natural conversation between two AI speakers, `voice1` and `voice2`.\n\nYour task is to turn the following newsletter content into a **realistic audio dialogue**. The conversation should be fluid, informal, and engaging — similar in tone and structure to how NotebookLM rewrites long documents as discussions. It must sound like two well-informed people exchanging ideas, not like a text being read aloud.\n\n### Roles\n\n- **voice1**: Curious, expressive, casual, often injects humor or everyday references. Tends to ask questions, react with surprise or amusement, and bring lightness to the discussion.\n- **voice2**: Analytical, composed, insightful. Adds perspective, context, and a slightly ironic or dry sense of humor. Offers clarity without sounding robotic.\n\nUse realistic, human-like phrasing with brief interjections (`\"Right?\"`, `\"Let me stop you there\"`, `\"That's exactly it\"`). Use `<break time=\"1.5s\" />` tags occasionally to simulate natural pauses.\n\n### Structure\n\n1. **Introduction**: Set the scene naturally. Briefly introduce what the episode is about based on the content, without listing or labeling sections. Present `voice1` and `voice2` through dialogue, not narration.\n2. **Content Breakdown**: For each key idea or section from the newsletter:\n   - Paraphrase the content in spoken language.\n   - Embed the headline or theme organically in the conversation.\n   - Include personal reactions, examples, and small tangents to make it relatable.\n   - Open loops by teasing questions or ideas that will be answered later in the conversation.\n   - Maintain curiosity and variety in tone and rhythm.\n3. **Closing**: End warmly and casually, with a brief comment on what stood out or what’s coming next (no need for formal farewells).\n\n### Requirements\n\n- The script must be at least **ten thousand characters** (about 15 minutes of speech).\n- Use **commas** to separate items in a list, not periods.\n- Format the output as a single uninterrupted block of text with clear speaker tags:\n  \nvoice1: …\nvoice2: …\n\nYou will be given a newsletter input under this key:\n\n{{ $('Prepare Data for AI').item.json.allNews }}\n\nGenerate only the final dialogue script — no explanations, bullet points, or headings. Just the conversation in English.\n\n\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2
    },
    {
      "id": "04689446-91a9-4ba2-b76f-db126e150a47",
      "name": "便签 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2900,
        -600
      ],
      "parameters": {
        "color": 7,
        "width": 1112,
        "height": 1604,
        "content": "## ✍️ 步骤 2:生成 AI 内容(摘要与脚本)"
      },
      "typeVersion": 1
    },
    {
      "id": "2a450954-0691-423a-b5c7-c8b9aac595ca",
      "name": "按发言人分割脚本",
      "type": "n8n-nodes-base.code",
      "position": [
        -1720,
        380
      ],
      "parameters": {
        "jsCode": "/**\n * This Function node takes the script from the previous node\n * and splits it using \"voice1:\" and \"voice2:\" as delimiters.\n * Each resulting segment retains the respective identifier.\n */\n\nconst script = $input.first().json.output|| \"\";\n\n// Ensure consistent line breaks\nconst normalizedScript = script.replace(/\\r\\n/g, \"\\n\");\n\n// Split the script while keeping \"voice1:\" and \"voice2:\" in the result\nconst segments = normalizedScript.split(/(?=(?:voice1:|voice2:))/g).map(s => s.trim()).filter(Boolean);\n\n// Return one item per segment\nreturn segments.map(segment => {\n  return {\n    json: {\n      segment\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "467b302f-98fd-4c81-9dbd-8a467469d057",
      "name": "遍历片段",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1500,
        380
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "ff9fa0c1-cc63-4fac-8001-e095f8b6d9ae",
      "name": "清理对话片段",
      "type": "n8n-nodes-base.code",
      "position": [
        -1320,
        560
      ],
      "parameters": {
        "jsCode": "const paragraph = $input.first().json.segment; \nif (!paragraph) {\n    throw new Error(\"No se encontró contenido de texto en el correo.\");\n}\n\nlet cleanedText = paragraph\n  .replace(/\"/g, \"\")\n  .replace(/“/g, \"\")\n  .replace(/”/g, \"\");\n\ncleanedText = cleanedText.replace(/\\n/g, \"\");\n\nconsole.log(\"Texto limpio sin comillas ni saltos de línea:\", cleanedText);\n\nreturn [{ json: { cleanedText } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "228ee637-49f9-4ecd-ac6d-f532a2aef401",
      "name": "路由到正确语音",
      "type": "n8n-nodes-base.if",
      "position": [
        -1100,
        560
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "93609a28-55f5-439e-8238-a48375255f4f",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.cleanedText }}",
              "rightValue": "voice1:"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4857627b-027d-4270-8a48-3f2c4461d3a7",
      "name": "准备 TTS 文本",
      "type": "n8n-nodes-base.code",
      "position": [
        -880,
        560
      ],
      "parameters": {
        "jsCode": "// Get the text from the previous node.\nconst cleanedText = $input.first().json.cleanedText;\n\n// Safety check: Ensure the input is a string.\nif (typeof cleanedText !== \"string\") {\n    throw new Error(\"Input 'cleanedText' must be a string.\");\n}\n\n// Debugging: Log the original text received by the node.\nconsole.log(\"Original text:\", JSON.stringify(cleanedText, null, 2));\n\n// Debugging: Check if the speaker tag exists before removal.\nif (cleanedText.includes(\"voice1:\")) {\n    console.log(\"✅ 'voice1:' detected in original text.\");\n} else {\n    console.log(\"❌ 'voice1:' NOT found in original text. Check input!\");\n}\n\n// This is the main action: Remove the speaker tag (e.g., \"voice1: \") and trim whitespace.\nconst modifiedString = cleanedText.replace(/\\bvoice1:\\s*/gi, \"\").trim();\n\n// Debugging: Log the text *after* modification to confirm it was removed.\nconsole.log(\"Modified text:\", JSON.stringify(modifiedString, null, 2));\n\n// Debugging: Final check to ensure the tag is gone.\nif (modifiedString.includes(\"voice1:\")) {\n    console.log(\"❌ 'voice1:' is still present in the modified text. The regex needs adjustment!\");\n} else {\n    console.log(\"✅ 'voice1:' removed successfully.\");\n}\n\n// Return the final, cleaned string for the TTS API.\nreturn [\n    {\n        json: {\n            modifiedString\n        }\n    }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e43ecaff-904d-4039-b8f3-21680080ed99",
      "name": "准备 TTS 文本1",
      "type": "n8n-nodes-base.code",
      "position": [
        -860,
        800
      ],
      "parameters": {
        "jsCode": "const cleanedText = $input.first().json.cleanedText;\n\nif (typeof cleanedText !== \"string\") {\n    throw new Error(\"cleanedText debe ser un string.\");\n}\n\nconsole.log(\"Texto original:\", JSON.stringify(cleanedText, null, 2));\n\nif (cleanedText.includes(\"voice2:\")) {\n    console.log(\"✅ 'voice2:' detectado en el texto original.\");\n} else {\n    console.log(\"❌ 'voice2:' NO encontrado en el texto original. ¡Revisar input!\");\n}\n\nconst modifiedString = cleanedText.replace(/\\bvoice2:\\s*/gi, \"\").trim();\n\nconsole.log(\"Texto modificado:\", JSON.stringify(modifiedString, null, 2));\n\nif (modifiedString.includes(\"voice2:\")) {\n    console.log(\"❌ 'voice2:' sigue presente en el texto modificado. ¡El regex debe ajustarse!\");\n} else {\n    console.log(\"✅ 'voice2:' eliminado correctamente.\");\n}\n\nreturn [\n    {\n        json: {\n            modifiedString\n        }\n    }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "66c339ee-6893-4f1e-ac56-f76a77d02591",
      "name": "生成音频(语音 1)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -600,
        560
      ],
      "parameters": {
        "url": "https://tts-kokoro.mfxikq.easypanel.host/api/v1/audio/speech",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "model_q8f16"
            },
            {
              "name": "voice",
              "value": "am_liam"
            },
            {
              "name": "speed",
              "value": "={{ 1 }}"
            },
            {
              "name": "response_format",
              "value": "mp3"
            },
            {
              "name": "input",
              "value": "={{ $json.modifiedString }}"
            }
          ]
        }
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "d725b21a-4b3e-444e-baf4-a08cd3a3d663",
      "name": "生成音频(语音 2)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -600,
        800
      ],
      "parameters": {
        "url": "https://tts-kokoro.mfxikq.easypanel.host/api/v1/audio/speech",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "model_q8f16"
            },
            {
              "name": "voice",
              "value": "af_heart"
            },
            {
              "name": "speed",
              "value": "={{ 1 }}"
            },
            {
              "name": "response_format",
              "value": "mp3"
            },
            {
              "name": "input",
              "value": "={{ $json.modifiedString }}"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "cfd32a14-4359-4025-9ed6-6b41bfd48432",
      "name": "便签 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1760,
        -600
      ],
      "parameters": {
        "color": 7,
        "width": 1372,
        "height": 1604,
        "content": "## ✍️ 步骤 3:分割脚本并生成音频块"
      },
      "typeVersion": 1
    },
    {
      "id": "877dd4d2-3096-448f-bb36-759ee1abb352",
      "name": "保存音频块到磁盘",
      "type": "n8n-nodes-base.readWriteFile",
      "position": [
        -260,
        360
      ],
      "parameters": {
        "options": {},
        "fileName": "=/tmp/dailydigest_{{$itemIndex}}.mp3",
        "operation": "write"
      },
      "typeVersion": 1
    },
    {
      "id": "5bf9e496-fa0d-4d4c-8908-de01512fe4d8",
      "name": "生成 FFmpeg 合并列表",
      "type": "n8n-nodes-base.code",
      "position": [
        -40,
        360
      ],
      "parameters": {
        "jsCode": "/**\n * This Code node will:\n * 1. Gather all file paths from the incoming items (assuming each item has `item.json.filePath`).\n * 2. Build a single text string, each line in FFmpeg concat format: `file '/path/to/audio.mp3'`\n * 3. Convert that text to binary (Base64) so the next node (\"Write Binary File\") can save it as `concat_list.txt`.\n */\n\nconst items = $input.all();\n\n// Build the concat list\nlet concatListText = '';\n\nitems.forEach(item => {\n  // The 'Save File' node outputs the path in item.json.filePath\n  const filePath = item.json.filePath;\n  if (filePath) {\n    concatListText += `file '${filePath}'\\n`;\n  }\n});\n\n// The 'Save concat_list' node expects binary data\nconst buffer = Buffer.from(concatListText, 'utf-8');\nconst base64Data = buffer.toString('base64');\n\nreturn [\n  {\n    json: {},\n    binary: {\n      data: {\n        data: base64Data,\n        mimeType: 'text/plain',\n        fileName: 'concat_list.txt'\n      }\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "73680e23-7e03-4125-a5f9-91defe784743",
      "name": "保存合并列表到磁盘",
      "type": "n8n-nodes-base.readWriteFile",
      "position": [
        180,
        360
      ],
      "parameters": {
        "options": {},
        "fileName": "/tmp/dailydigest/concat_list.txt",
        "operation": "write"
      },
      "typeVersion": 1
    },
    {
      "id": "9b64cb66-f9be-449e-a12a-3e3e5504cadb",
      "name": "合并音频并清理",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        400,
        360
      ],
      "parameters": {
        "command": "ffmpeg -y -f concat -safe 0 -i /tmp/dailydigest/concat_list.txt -c copy /tmp/dailydigest/final_merged.mp3\n\nfind /tmp/dailydigest/ -type f ! -name \"final_merged.mp3\" -delete\n"
      },
      "typeVersion": 1
    },
    {
      "id": "91281464-dc37-4ae0-94f2-bea3d1054fdf",
      "name": "便签 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -360,
        -600
      ],
      "parameters": {
        "color": 7,
        "width": 932,
        "height": 1264,
        "content": "## 🎛️ 步骤 4:保存、准备和合并音频"
      },
      "typeVersion": 1
    },
    {
      "id": "ad20440c-57dd-4e26-8505-13aa166cd274",
      "name": "便签 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        600,
        -600
      ],
      "parameters": {
        "color": 7,
        "width": 632,
        "height": 1264,
        "content": "## 📤 步骤 5:读取合并音频并发送最终播客"
      },
      "typeVersion": 1
    },
    {
      "id": "aed39482-9644-4f38-93e6-4df6bb2d1742",
      "name": "读取最终合并 MP3",
      "type": "n8n-nodes-base.readWriteFile",
      "position": [
        720,
        360
      ],
      "parameters": {
        "options": {},
        "fileSelector": "/tmp/dailydigest/final_merged.mp3"
      },
      "typeVersion": 1
    },
    {
      "id": "4069618f-f4e0-484b-92bc-39907a876896",
      "name": "发送播客到 Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1000,
        360
      ],
      "webhookId": "d25eb6ba-9cd2-4d43-80c4-a46cb68c1d56",
      "parameters": {
        "chatId": "[YOUR_TELEGRAM_CHAT_ID]",
        "operation": "sendAudio",
        "binaryData": true,
        "additionalFields": {
          "fileName": "=Daily Digest - {{ $now.setZone('America/Sao_Paulo').toFormat('dd/LL/yyyy') }}.mp3"
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "kuP8lCkbwbeD61gU",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "59a82666-de28-4e02-bf8e-936f8b628674",
  "connections": {
    "Daily Trigger": {
      "main": [
        [
          {
            "node": "Fetch RSS 1: Folha de SP",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch RSS 2: GE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Up Fields": {
      "main": [
        [
          {
            "node": "Merge News Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch RSS 2: GE": {
      "main": [
        [
          {
            "node": "Clean Up Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Up Fields1": {
      "main": [
        [
          {
            "node": "Merge News Sources",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge News Sources": {
      "main": [
        [
          {
            "node": "Filter for Last 24hrs' News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Data for AI": {
      "main": [
        [
          {
            "node": "Create Temp Directory",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Text Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Text Digest": {
      "main": [
        [
          {
            "node": "Send Text Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Text for TTS": {
      "main": [
        [
          {
            "node": "Generate Audio (Voice 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Temp Directory": {
      "main": [
        [
          {
            "node": "Generate Podcast Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Through Segments": {
      "main": [
        [
          {
            "node": "Save Audio Chunk to Disk",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Clean Dialogue Segment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Text for TTS1": {
      "main": [
        [
          {
            "node": "Generate Audio (Voice 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Final Merged MP3": {
      "main": [
        [
          {
            "node": "Send Podcast to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Dialogue Segment": {
      "main": [
        [
          {
            "node": "Route to Correct Voice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Audio & Clean Up": {
      "main": [
        [
          {
            "node": "Read Final Merged MP3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route to Correct Voice": {
      "main": [
        [
          {
            "node": "Prepare Text for TTS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Text for TTS1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Podcast Script": {
      "main": [
        [
          {
            "node": "Split Script by Speaker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Script by Speaker": {
      "main": [
        [
          {
            "node": "Loop Through Segments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch RSS 1: Folha de SP": {
      "main": [
        [
          {
            "node": "Clean Up Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Audio (Voice 1)": {
      "main": [
        [
          {
            "node": "Loop Through Segments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Audio (Voice 2)": {
      "main": [
        [
          {
            "node": "Loop Through Segments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Text Digest",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Save Audio Chunk to Disk": {
      "main": [
        [
          {
            "node": "Generate FFmpeg Concat List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Concat List to Disk": {
      "main": [
        [
          {
            "node": "Merge Audio & Clean Up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Podcast Script",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Filter for Last 24hrs' News": {
      "main": [
        [
          {
            "node": "Prepare Data for AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate FFmpeg Concat List": {
      "main": [
        [
          {
            "node": "Save Concat List to Disk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 内容创作, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量34
分类2
节点类型15
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流