{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "b5df56e5-9bcb-47ec-9b1c-2035c1b9d875",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3744,
        -400
      ],
      "parameters": {
        "color": 4,
        "width": 524,
        "height": 932,
        "content": "## Podcast Episode to LinkedIn Newsletter Auto-Publisher \u2014 WayinVideo + GPT-4o-mini + Sheets\n\nFor podcast hosts, content marketers, and personal brand builders who want to repurpose podcast episodes into professional LinkedIn newsletter articles without writing from scratch. Submit a podcast or video URL via the form. WayinVideo transcribes the full episode with speaker labels and timestamps. GPT-4o-mini reads the transcript and writes a complete LinkedIn article \u2014 headline, hook, full body, five key takeaways, and hashtags \u2014 following 7 writing rules for LinkedIn readability. The final article is saved to Google Sheets as a draft with Status set to Draft, ready for you to review and publish.\n\n## How it works\n- **1. Form \u2014 Podcast URL + Details** collects the video URL, episode title, host and guest names, target audience, and LinkedIn tone\n- **2. WayinVideo \u2014 Submit Transcription** submits the URL to the WayinVideo API for speaker-labeled transcription\n- **3. Wait \u2014 60 Seconds** gives the API time to start processing before the first poll\n- **4. WayinVideo \u2014 Get Transcript Results** polls the API for the task result\n- **5. IF \u2014 Transcription Complete?** checks if status equals SUCCEEDED \u2014 if not, loops back via a 30-second retry wait\n- **7. Code \u2014 Format Transcript** formats each segment as speaker label with timestamp and builds the text for GPT\n- **8. AI Agent \u2014 Write LinkedIn Newsletter** uses GPT-4o-mini with a structured system prompt to write the full article in labeled sections\n- **10. Code \u2014 Parse AI Output** extracts headline, article, key takeaways, and hashtags from the labeled output using regex\n- **11. Google Sheets \u2014 Save LinkedIn Draft** appends one row with all fields and sets status to Draft\n\n## Set up steps\n1. In **2. WayinVideo \u2014 Submit Transcription** and **4. WayinVideo \u2014 Get Transcript Results** \u2014 replace YOUR_WAYINVIDEO_API_KEY with your key from wayin.ai Account Settings\n2. In **9. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n3. In **11. Google Sheets \u2014 Save LinkedIn Draft** \u2014 connect your Google Sheets OAuth2 credential and replace YOUR_GOOGLE_SHEET_ID\n4. Create a Google Sheet tab named LinkedIn Drafts with columns: Podcast Title, Video URL, Episode Duration (min), LinkedIn Headline, LinkedIn Article, Key Takeaways, Hashtags, Word Count, Generated On, Status"
      },
      "typeVersion": 1
    },
    {
      "id": "78f888d2-c997-49fe-be38-11b7f215425d",
      "name": "Section \u2014 Form Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3136,
        -176
      ],
      "parameters": {
        "color": 5,
        "width": 308,
        "height": 372,
        "content": "## Form Input\nUser submits podcast URL, episode title, host and guest name, target audience, and LinkedIn tone. All five fields feed directly into the transcript formatter and AI prompt."
      },
      "typeVersion": 1
    },
    {
      "id": "f2d95f61-a150-466a-844b-c7a0a1166e20",
      "name": "Section \u2014 Transcription Submit and Poll",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2800,
        -176
      ],
      "parameters": {
        "color": 6,
        "width": 724,
        "height": 388,
        "content": "## WayinVideo Transcription Submit and Poll\nSubmits the podcast URL to WayinVideo for speaker-labeled transcription. Waits 60 seconds before the first poll. Polls the results endpoint and checks for SUCCEEDED status."
      },
      "typeVersion": 1
    },
    {
      "id": "1fb5726c-6c53-40e4-abe4-c85bb29a06ff",
      "name": "Section \u2014 Status Check and Retry Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2032,
        -320
      ],
      "parameters": {
        "color": 6,
        "width": 340,
        "height": 724,
        "content": "## Transcription Status Check and Retry Loop\nIF node checks if status equals SUCCEEDED. TRUE proceeds to transcript formatting. FALSE waits 30 seconds and polls again. Loop continues until SUCCEEDED."
      },
      "typeVersion": 1
    },
    {
      "id": "94fdf2ab-222e-4d50-95df-8899b0f3432a",
      "name": "Section \u2014 Transcript Formatting and AI Writing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1648,
        -432
      ],
      "parameters": {
        "color": 6,
        "width": 548,
        "height": 900,
        "content": "## Transcript Formatting and AI Newsletter Writing\nFormats each transcript segment as speaker label with timestamp. GPT-4o-mini reads the formatted transcript and writes a full LinkedIn article with headline, body (400\u2013600 words), five key takeaways, and hashtags using 7 LinkedIn readability rules."
      },
      "typeVersion": 1
    },
    {
      "id": "e1c8942f-8930-4060-b616-145849069bf6",
      "name": "Section \u2014 Parse Output and Save Draft",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -240
      ],
      "parameters": {
        "color": 4,
        "width": 484,
        "height": 580,
        "content": "## Parse Output and Save Draft\nRegex extracts HEADLINE, ARTICLE, KEY TAKEAWAYS, and HASHTAGS from the AI output. Google Sheets appends one row with all fields and Status set to Draft \u2014 ready for review before publishing."
      },
      "typeVersion": 1
    },
    {
      "id": "a35ae5db-7547-48a9-bcf7-7648098fe93c",
      "name": "1. Form \u2014 Podcast URL + Details",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -3056,
        -32
      ],
      "parameters": {
        "options": {},
        "formTitle": "Podcast to LinkedIn Newsletter Generator",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Podcast / Episode Video URL",
              "placeholder": "https://www.youtube.com/watch?v=xxxxxxx or Zoom/Vimeo link",
              "requiredField": true
            },
            {
              "fieldLabel": "Episode Title",
              "placeholder": "e.g. How AI is Changing Digital Marketing in 2025",
              "requiredField": true
            },
            {
              "fieldLabel": "Host / Guest Name",
              "placeholder": "e.g. John Smith, Jane Doe",
              "requiredField": true
            },
            {
              "fieldLabel": "Target Audience",
              "placeholder": "e.g. Marketing professionals, startup founders, HR leaders",
              "requiredField": true
            },
            {
              "fieldLabel": "Your LinkedIn Tone",
              "placeholder": "e.g. Professional and insightful / Casual and conversational / Data-driven"
            }
          ]
        },
        "formDescription": "Paste your podcast episode URL. AI will transcribe the full episode and write a professional LinkedIn newsletter article ready to publish."
      },
      "typeVersion": 2.2
    },
    {
      "id": "680dfb29-1c6f-4319-90d0-5597f895156c",
      "name": "2. WayinVideo \u2014 Submit Transcription",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2720,
        -32
      ],
      "parameters": {
        "url": "https://wayinvideo-api.wayin.ai/api/v2/transcripts",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"video_url\": \"{{ $json['Podcast / Episode Video URL'] }}\",\n  \"target_lang\": \"en\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "x-wayinvideo-api-version",
              "value": "v2"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0aee8ca1-178d-4a7e-a628-8187cbf8c5a8",
      "name": "3. Wait \u2014 60 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        -2480,
        -32
      ],
      "parameters": {
        "amount": 60
      },
      "typeVersion": 1.1
    },
    {
      "id": "15fe233d-cf66-4732-9972-067e3ebbb862",
      "name": "4. WayinVideo \u2014 Get Transcript Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2240,
        -32
      ],
      "parameters": {
        "url": "=https://wayinvideo-api.wayin.ai/api/v2/transcripts/results/{{ $('2. WayinVideo \u2014 Submit Transcription').item.json.data.id }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "x-wayinvideo-api-version",
              "value": "v2"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "012355fe-0493-4577-a57e-7c25f884f42a",
      "name": "5. IF \u2014 Transcription Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1920,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "status-check-succeeded",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "eb789bbc-48e7-4e52-bb1f-e266351f22cc",
      "name": "6. Wait \u2014 30 Seconds Retry",
      "type": "n8n-nodes-base.wait",
      "position": [
        -1920,
        192
      ],
      "parameters": {
        "amount": 45
      },
      "typeVersion": 1.1
    },
    {
      "id": "15d29406-9aa3-4e7a-a383-386e2406580c",
      "name": "7. Code \u2014 Format Transcript",
      "type": "n8n-nodes-base.code",
      "position": [
        -1584,
        -48
      ],
      "parameters": {
        "jsCode": "// Get transcript segments from WayinVideo response\nconst segments = $('4. WayinVideo \u2014 Get Transcript Results').item.json.data.transcript || [];\n\nif (segments.length === 0) {\n  throw new Error('Transcript is empty \u2014 check if the video URL is valid and publicly accessible.');\n}\n\n// Format transcript with speaker labels and timestamps\nconst formattedTranscript = segments\n  .map(s => {\n    const timeInSeconds = Math.floor(s.start / 1000);\n    const minutes = Math.floor(timeInSeconds / 60);\n    const seconds = timeInSeconds % 60;\n    const timestamp = `${minutes}:${seconds.toString().padStart(2, '0')}`;\n    return `[${s.speaker} | ${timestamp}] ${s.text.trim()}`;\n  })\n  .join('\\n');\n\n// Calculate total duration\nconst lastSegment = segments[segments.length - 1];\nconst totalDurationMs = lastSegment?.end || 0;\nconst totalMinutes = Math.round(totalDurationMs / 60000);\n\n// Count unique speakers\nconst speakers = [...new Set(segments.map(s => s.speaker))];\n\n// Word count\nconst wordCount = segments.map(s => s.text).join(' ').split(/\\s+/).length;\n\nreturn [{\n  json: {\n    formattedTranscript,\n    wordCount,\n    totalMinutes,\n    speakerCount: speakers.length,\n    speakers: speakers.join(', '),\n    episodeTitle: $('1. Form \u2014 Podcast URL + Details').item.json['Episode Title'],\n    hostGuestName: $('1. Form \u2014 Podcast URL + Details').item.json['Host / Guest Name'],\n    targetAudience: $('1. Form \u2014 Podcast URL + Details').item.json['Target Audience'],\n    linkedinTone: $('1. Form \u2014 Podcast URL + Details').item.json['Your LinkedIn Tone'] || 'Professional and insightful',\n    videoUrl: $('1. Form \u2014 Podcast URL + Details').item.json['Podcast / Episode Video URL']\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "63bbcd05-c083-42a4-a166-83d280358bad",
      "name": "8. AI Agent \u2014 Write LinkedIn Newsletter",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1424,
        -48
      ],
      "parameters": {
        "text": "={{ $json.formattedTranscript }}",
        "options": {
          "systemMessage": "=You are an expert LinkedIn content writer who specializes in turning podcast transcripts into high-performing LinkedIn newsletter articles.\n\nYour job is to read the podcast transcript below and write a professional, engaging LinkedIn newsletter article that will get high engagement from the target audience.\n\n---\n\n## EPISODE DETAILS\n- Episode Title: {{ $json.episodeTitle }}\n- Host / Guest: {{ $json.hostGuestName }}\n- Target Audience: {{ $json.targetAudience }}\n- Tone: {{ $json.linkedinTone }}\n- Episode Duration: {{ $json.totalMinutes }} minutes\n- Speakers: {{ $json.speakers }}\n\n---\n\n## WRITING RULES\n1. Write ONLY from what is said in the transcript \u2014 no made-up facts or general knowledge\n2. The article must feel human and conversational \u2014 not robotic or AI-generated\n3. Use short paragraphs \u2014 maximum 3 lines each \u2014 LinkedIn readers skim content\n4. Start with a strong hook in the first line \u2014 make it a bold statement, a surprising fact, or a question\n5. Use the tone specified above consistently throughout\n6. No unnecessary filler words \u2014 every sentence must add value\n7. End with a clear CTA that encourages comments or shares\n\n---\n\n## OUTPUT FORMAT\nReturn your response in this exact structure \u2014 use these exact labels:\n\nHEADLINE:\n[Write a compelling LinkedIn article headline \u2014 8 to 12 words \u2014 no clickbait]\n\nARTICLE:\n[Write the full LinkedIn newsletter article \u2014 400 to 600 words \u2014 formatted with short paragraphs and occasional line breaks for readability]\n\nKEY TAKEAWAYS:\n[List exactly 5 key takeaways from the episode \u2014 each one a single sentence starting with an action verb]\n\nHASHTAGS:\n[List 5 to 8 relevant LinkedIn hashtags \u2014 lowercase \u2014 separated by spaces]\n\n---\n\nNow read the transcript below and write the LinkedIn newsletter article:"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "0ca2bb68-8075-4bce-bdfd-156efb6679f2",
      "name": "9. OpenAI \u2014 GPT-4o-mini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -1424,
        272
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "736cb079-6dfb-4a3c-aaec-097b2e7042ed",
      "name": "10. Code \u2014 Parse AI Output",
      "type": "n8n-nodes-base.code",
      "position": [
        -992,
        -48
      ],
      "parameters": {
        "jsCode": "// Parse the AI output into structured fields\nconst output = $input.first().json.output || '';\n\n// Extract HEADLINE\nconst headlineMatch = output.match(/HEADLINE:\\s*([\\s\\S]*?)(?=\\nARTICLE:|$)/);\nconst headline = headlineMatch ? headlineMatch[1].trim() : 'LinkedIn Newsletter Draft';\n\n// Extract ARTICLE\nconst articleMatch = output.match(/ARTICLE:\\s*([\\s\\S]*?)(?=\\nKEY TAKEAWAYS:|$)/);\nconst article = articleMatch ? articleMatch[1].trim() : output;\n\n// Extract KEY TAKEAWAYS\nconst takeawaysMatch = output.match(/KEY TAKEAWAYS:\\s*([\\s\\S]*?)(?=\\nHASHTAGS:|$)/);\nconst keyTakeaways = takeawaysMatch ? takeawaysMatch[1].trim() : '';\n\n// Extract HASHTAGS\nconst hashtagsMatch = output.match(/HASHTAGS:\\s*([\\s\\S]*)$/);\nconst hashtags = hashtagsMatch ? hashtagsMatch[1].trim() : '';\n\n// Word count of final article\nconst wordCount = article.split(/\\s+/).length;\n\nreturn [{\n  json: {\n    headline,\n    article,\n    keyTakeaways,\n    hashtags,\n    wordCount,\n    episodeTitle: $('7. Code \u2014 Format Transcript').item.json.episodeTitle,\n    videoUrl: $('7. Code \u2014 Format Transcript').item.json.videoUrl,\n    totalMinutes: $('7. Code \u2014 Format Transcript').item.json.totalMinutes\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ebc11dea-6673-41a5-94bb-1a4a713e640e",
      "name": "11. Google Sheets \u2014 Save LinkedIn Draft",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -752,
        -48
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Draft",
            "Hashtags": "={{ $json.hashtags }}",
            "Video URL": "={{ $json.videoUrl }}",
            "Word Count": "={{ $json.wordCount }}",
            "Generated On": "={{ $now.toFormat('dd MMMM yyyy HH:mm') }}",
            "Key Takeaways": "={{ $json.keyTakeaways }}",
            "Podcast Title": "={{ $json.episodeTitle }}",
            "LinkedIn Article": "={{ $json.article }}",
            "LinkedIn Headline": "={{ $json.headline }}",
            "Episode Duration (min)": "={{ $json.totalMinutes }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "LinkedIn Drafts"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    }
  ],
  "connections": {
    "3. Wait \u2014 60 Seconds": {
      "main": [
        [
          {
            "node": "4. WayinVideo \u2014 Get Transcript Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. Code \u2014 Parse AI Output": {
      "main": [
        [
          {
            "node": "11. Google Sheets \u2014 Save LinkedIn Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Wait \u2014 30 Seconds Retry": {
      "main": [
        [
          {
            "node": "4. WayinVideo \u2014 Get Transcript Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Code \u2014 Format Transcript": {
      "main": [
        [
          {
            "node": "8. AI Agent \u2014 Write LinkedIn Newsletter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. OpenAI \u2014 GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "8. AI Agent \u2014 Write LinkedIn Newsletter",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "1. Form \u2014 Podcast URL + Details": {
      "main": [
        [
          {
            "node": "2. WayinVideo \u2014 Submit Transcription",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. IF \u2014 Transcription Complete?": {
      "main": [
        [
          {
            "node": "7. Code \u2014 Format Transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "6. Wait \u2014 30 Seconds Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. WayinVideo \u2014 Submit Transcription": {
      "main": [
        [
          {
            "node": "3. Wait \u2014 60 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. WayinVideo \u2014 Get Transcript Results": {
      "main": [
        [
          {
            "node": "5. IF \u2014 Transcription Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. AI Agent \u2014 Write LinkedIn Newsletter": {
      "main": [
        [
          {
            "node": "10. Code \u2014 Parse AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}