{
  "nodes": [
    {
      "id": "91f5daad-fcfd-47b8-9503-ac3f07aee1d4",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2080,
        -256
      ],
      "parameters": {
        "color": 4,
        "width": 556,
        "height": 1108,
        "content": "## YouTube Tutorial to Auto Timestamp Generator + Community Post \u2014 WayinVideo Find Moments + GPT-4o-mini + Google Sheets\n\nFor YouTube creators, educators, and content teams who publish long tutorial or how-to videos and want to automatically generate chapter timestamps for their video description plus a Community post to promote it. Submit any YouTube tutorial URL via the form with your section search query. WayinVideo Find Moments API scans the full video and returns up to 15 matching section moments. The code node collects all moments, sorts them chronologically by timestamp, and builds a single formatted string for GPT. GPT-4o-mini completes three tasks in one call: generates clean chapter timestamps (with 0:00 Introduction first), writes a 3\u20134 sentence video description block, and writes a 100\u2013150 word YouTube Community post with the full timestamp list embedded. Everything is saved as one row in Google Sheets \u2014 ready to copy and paste.\n\n## How it works\n- **1. Form \u2014 Video URL + Details** collects the video URL, title, channel name, category, target audience, and section search query\n- **2. WayinVideo \u2014 Submit Find Moments** submits the URL and query to Find Moments API \u2014 limit 15, sorted chronologically\n- **3. Wait \u2014 90 Seconds** gives the API initial processing time\n- **4. WayinVideo \u2014 Get Moments Results** polls the find-moments results endpoint\n- **5. IF \u2014 Find Moments Complete?** checks for SUCCEEDED \u2014 if not, retries via 30-second wait\n- **7. Code \u2014 Collect + Sort All Moments** sorts all clips by start time ascending, builds a single formatted moments string, and aggregates into one item for GPT\n- **8. AI Agent \u2014 Generate Timestamps + Post** uses GPT-4o-mini to complete three tasks: formatted chapter list, video description block, and Community post\n- **10. Code \u2014 Parse Timestamps Output** extracts all 5 labeled sections via regex\n- **11. Google Sheets \u2014 Save Timestamp Library** appends one row per video with all timestamps, description, community post, and hashtags\n\n## Set up steps\n1. In **2. WayinVideo \u2014 Submit Find Moments** and **4. WayinVideo \u2014 Get Moments Results** \u2014 replace `YOUR_WAYINVIDEO_API_KEY`\n2. In **9. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n3. In **11. Google Sheets \u2014 Save Timestamp Library** \u2014 connect your Google Sheets OAuth2 credential and replace `YOUR_GOOGLE_SHEET_ID`\n4. Create a Google Sheet tab named Timestamp Library with columns: Video URL, Video Title, Channel Name, Video Category, Total Chapters, Total Moments Found, Video Duration (min), Formatted Timestamps, Video Description Block, Community Post, Hashtags, Generated On, Status"
      },
      "typeVersion": 1
    },
    {
      "id": "94a356a1-0f63-47fe-8731-6caab7facd5d",
      "name": "Section \u2014 Form Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1472,
        -128
      ],
      "parameters": {
        "color": 5,
        "width": 308,
        "height": 452,
        "content": "## Form Input\nUser submits video URL, title, channel name, category, target audience, and section search query. The search query directly drives which moments WayinVideo finds \u2014 the form includes example queries for different video types."
      },
      "typeVersion": 1
    },
    {
      "id": "dcc6f665-631a-4480-b751-bf3363442ebd",
      "name": "Section \u2014 Find Moments Submit and Poll",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1136,
        -32
      ],
      "parameters": {
        "color": 6,
        "width": 708,
        "height": 308,
        "content": "## WayinVideo Find Moments Submit and Poll\nSubmits the video URL and section query to Find Moments API \u2014 limit 15, the highest in the series. Waits 90 seconds then polls until SUCCEEDED."
      },
      "typeVersion": 1
    },
    {
      "id": "ff9911a0-f008-4149-b8e6-ee61a361b07a",
      "name": "Section \u2014 Status Check and Retry Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        -240
      ],
      "parameters": {
        "color": 6,
        "width": 324,
        "height": 708,
        "content": "## Find Moments Status Check and Retry Loop\nIF checks for SUCCEEDED status. TRUE proceeds to moment collection. FALSE waits 30 seconds and polls again. Loop continues until results are ready."
      },
      "typeVersion": 1
    },
    {
      "id": "bee0bb76-ed5c-4980-a906-85169e37686f",
      "name": "Section \u2014 Moment Collection, Sort, and AI Timestamp Writing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -240
      ],
      "parameters": {
        "color": 6,
        "width": 516,
        "height": 708,
        "content": "## Moment Collection, Sort, and AI Timestamp Writing\nSorts all moments chronologically by start time and aggregates into one item. GPT-4o-mini completes three tasks in one call: chapter timestamps, video description block, and Community post with timestamps embedded."
      },
      "typeVersion": 1
    },
    {
      "id": "6227720b-f50f-4510-a281-d149ac586945",
      "name": "Section \u2014 Timestamp Parse and Google Sheets Save",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        -144
      ],
      "parameters": {
        "color": 4,
        "width": 452,
        "height": 452,
        "content": "## Timestamp Parse and Google Sheets Save\nRegex extracts all 5 labeled sections. Google Sheets appends one row per video with formatted timestamps, description block, Community post, hashtags, and Ready status."
      },
      "typeVersion": 1
    },
    {
      "id": "7eedc8a0-2107-4b49-abad-372a5da8565a",
      "name": "Note \u2014 Known Risks",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        512
      ],
      "parameters": {
        "color": 3,
        "width": 1272,
        "height": 176,
        "content": "## \u26a0\ufe0f Two Known Risks\n1. Infinite loop \u2014 no poll counter. If WayinVideo returns FAILED or the URL is private, the workflow loops forever. Add a pollCount variable and stop after 15 retries.\n2. Query quality \u2014 be specific. 'step by step section transition' works better than just 'steps'. You can run multiple queries by duplicating nodes 2\u20135."
      },
      "typeVersion": 1
    },
    {
      "id": "f8656f75-121f-4b0a-baa9-acc9f1586ad0",
      "name": "1. Form \u2014 Video URL + Details",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1392,
        80
      ],
      "parameters": {
        "options": {},
        "formTitle": "YouTube Timestamp Generator + Community Post",
        "formFields": {
          "values": [
            {
              "fieldLabel": "YouTube Video URL",
              "placeholder": "https://www.youtube.com/watch?v=xxxxxxx",
              "requiredField": true
            },
            {
              "fieldLabel": "Video Title",
              "placeholder": "e.g. Complete SEO Tutorial for Beginners 2025 \u2014 Step by Step",
              "requiredField": true
            },
            {
              "fieldLabel": "Channel Name",
              "placeholder": "e.g. Incrementors, TechWithTim, Ali Abdaal",
              "requiredField": true
            },
            {
              "fieldLabel": "Video Category",
              "placeholder": "e.g. SEO Tutorial, Python Course, Marketing Guide, Cooking Class",
              "requiredField": true
            },
            {
              "fieldLabel": "Target Audience",
              "placeholder": "e.g. Beginners, digital marketers, developers, small business owners",
              "requiredField": true
            },
            {
              "fieldLabel": "Section Search Query",
              "placeholder": "e.g. step by step tutorial section / topic introduction and explanation / code example and demo",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Paste your YouTube tutorial or how-to video URL. AI will find all the key sections, generate formatted chapter timestamps, and write a YouTube Community post ready to publish."
      },
      "typeVersion": 2.2
    },
    {
      "id": "43c5b26c-0cce-40e5-a695-e2835f68f03d",
      "name": "2. WayinVideo \u2014 Submit Find Moments",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1056,
        80
      ],
      "parameters": {
        "url": "https://wayinvideo-api.wayin.ai/api/v2/clips/find-moments",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"video_url\": \"{{ $json['YouTube Video URL'] }}\",\n  \"query\": \"{{ $json['Section Search Query'] }}\",\n  \"project_name\": \"Timestamps \u2014 {{ $json['Video Title'] }}\",\n  \"limit\": 15,\n  \"enable_export\": false,\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": "27df905a-6627-4154-8504-337f5024c124",
      "name": "3. Wait \u2014 90 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        -816,
        80
      ],
      "parameters": {
        "amount": 90
      },
      "typeVersion": 1.1
    },
    {
      "id": "3349f918-cb9c-4eb3-9f86-a60c8eec7190",
      "name": "4. WayinVideo \u2014 Get Moments Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -576,
        80
      ],
      "parameters": {
        "url": "=https://wayinvideo-api.wayin.ai/api/v2/clips/find-moments/results/{{ $('2. WayinVideo \u2014 Submit Find Moments').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": "7a925bd1-faa1-452e-9c0f-9d6ef2f840de",
      "name": "5. IF \u2014 Find Moments Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        -304,
        80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "status-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "047d427f-5291-4066-bc71-7c4f3328035b",
      "name": "6. Wait \u2014 30 Seconds Retry",
      "type": "n8n-nodes-base.wait",
      "position": [
        -304,
        272
      ],
      "parameters": {
        "amount": 30
      },
      "typeVersion": 1.1
    },
    {
      "id": "f80f98b5-8ec4-4afa-aa5b-859227d250ce",
      "name": "7. Code \u2014 Collect + Sort All Moments",
      "type": "n8n-nodes-base.code",
      "position": [
        48,
        64
      ],
      "parameters": {
        "jsCode": "// Extract all moments and build a single structured item for GPT\nconst clips = $('4. WayinVideo \u2014 Get Moments Results').item.json.data?.clips || [];\n\nif (clips.length === 0) {\n  throw new Error('No moments found \u2014 try a different Section Search Query or verify the video is publicly accessible.');\n}\n\n// Sort clips by start time \u2014 chronological order for timestamps\nconst sortedClips = clips.sort((a, b) => (a.begin_ms || 0) - (b.begin_ms || 0));\n\n// Build formatted moments list for GPT\nconst momentsForGPT = sortedClips.map((clip, index) => {\n  const startMs = clip.begin_ms || 0;\n  const minutes = Math.floor(startMs / 60000);\n  const seconds = String(Math.floor((startMs % 60000) / 1000)).padStart(2, '0');\n  const timestamp = `${minutes}:${seconds}`;\n  return `Moment ${index + 1}: [${timestamp}] ${clip.title || 'Section'} \u2014 ${clip.desc || ''}`;\n}).join('\\n');\n\n// Get form data\nconst videoTitle = $('1. Form \u2014 Video URL + Details').item.json['Video Title'];\nconst channelName = $('1. Form \u2014 Video URL + Details').item.json['Channel Name'];\nconst videoCategory = $('1. Form \u2014 Video URL + Details').item.json['Video Category'];\nconst targetAudience = $('1. Form \u2014 Video URL + Details').item.json['Target Audience'];\nconst videoUrl = $('1. Form \u2014 Video URL + Details').item.json['YouTube Video URL'];\nconst totalMoments = sortedClips.length;\n\n// Calculate total video duration estimate\nconst lastClip = sortedClips[sortedClips.length - 1];\nconst totalMs = lastClip?.end_ms || lastClip?.begin_ms || 0;\nconst totalMinutes = Math.round(totalMs / 60000);\n\nreturn [{\n  json: {\n    momentsForGPT,\n    totalMoments,\n    totalMinutes,\n    videoTitle,\n    channelName,\n    videoCategory,\n    targetAudience,\n    videoUrl\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "41722fcf-2256-4714-9d0f-edd0ddb506b7",
      "name": "8. AI Agent \u2014 Generate Timestamps + Post",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        256,
        64
      ],
      "parameters": {
        "text": "={{ $json.momentsForGPT }}",
        "options": {
          "systemMessage": "=You are an expert YouTube content strategist who specializes in creating chapter timestamps and Community posts for tutorial videos.\n\nWayinVideo AI has identified the key moments in a YouTube tutorial video. Your job is to:\n1. Generate a clean formatted YouTube chapter timestamp list\n2. Write a Video Description block with timestamps embedded\n3. Write a YouTube Community post that promotes the video\n\n---\n\n## VIDEO DETAILS\n- Video Title: {{ $json.videoTitle }}\n- Channel: {{ $json.channelName }}\n- Category: {{ $json.videoCategory }}\n- Target Audience: {{ $json.targetAudience }}\n- Video URL: {{ $json.videoUrl }}\n- Total Moments Found: {{ $json.totalMoments }}\n- Estimated Duration: {{ $json.totalMinutes }} minutes\n\n---\n\n## MOMENTS FOUND BY WAYINVIDEO (in chronological order)\n{{ $json.momentsForGPT }}\n\n---\n\n## YOUR TASKS\n\n### Task 1 \u2014 YouTube Chapter Timestamps\n- Use the timestamps exactly as provided \u2014 do not change the MM:SS values\n- Write clean, descriptive chapter titles \u2014 3-6 words each\n- First timestamp MUST be 0:00 \u2014 write 'Introduction' if there is no earlier moment\n- Format: MM:SS Chapter Title (one per line)\n- Sort chronologically\n\n### Task 2 \u2014 Video Description Block\n- Write 3-4 sentences introducing the video\n- Include what the viewer will learn\n- Mention the chapter timestamps are below\n- Include the video URL at the end\n\n### Task 3 \u2014 YouTube Community Post\n- 100-150 words \u2014 engaging and friendly\n- Start with a hook that makes people want to watch\n- Mention 2-3 specific things they will learn from the video\n- Include the full timestamp list inside the post\n- End with a CTA to watch and comment\n- 3-5 relevant hashtags\n\n---\n\n## OUTPUT FORMAT\nReturn in this exact structure:\n\nFORMATTED_TIMESTAMPS:\n0:00 Introduction\n[MM:SS Chapter Title]\n[MM:SS Chapter Title]\n[Continue for all moments]\n\nVIDEO_DESCRIPTION_BLOCK:\n[Write 3-4 sentence description block here]\n\nCOMMUNITY_POST:\n[Write full YouTube Community post here with timestamps embedded and hashtags at end]\n\nHASHTAGS:\n[5-8 relevant hashtags \u2014 lowercase \u2014 space separated]\n\nTOTAL_CHAPTERS:\n[Number only]\n\n---\n\nNow generate the timestamps, description block, and Community post:"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "d55838b8-40c9-4a4e-a048-990a8fbb00e3",
      "name": "9. OpenAI \u2014 GPT-4o-mini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        256,
        256
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "3573296b-b4c0-425e-80b4-92758ceedfb6",
      "name": "10. Code \u2014 Parse Timestamps Output",
      "type": "n8n-nodes-base.code",
      "position": [
        624,
        64
      ],
      "parameters": {
        "jsCode": "// Parse AI output into structured fields\nconst output = $input.first().json.output || '';\n\n// Extract each section\nconst timestampsMatch = output.match(/FORMATTED_TIMESTAMPS:\\s*([\\s\\S]*?)(?=\\nVIDEO_DESCRIPTION_BLOCK:|$)/);\nconst descBlockMatch = output.match(/VIDEO_DESCRIPTION_BLOCK:\\s*([\\s\\S]*?)(?=\\nCOMMUNITY_POST:|$)/);\nconst communityPostMatch = output.match(/COMMUNITY_POST:\\s*([\\s\\S]*?)(?=\\nHASHTAGS:|$)/);\nconst hashtagsMatch = output.match(/HASHTAGS:\\s*([\\s\\S]*?)(?=\\nTOTAL_CHAPTERS:|$)/);\nconst totalChaptersMatch = output.match(/TOTAL_CHAPTERS:\\s*([\\d]+)/);\n\nconst formattedTimestamps = timestampsMatch ? timestampsMatch[1].trim() : '';\nconst videoDescriptionBlock = descBlockMatch ? descBlockMatch[1].trim() : '';\nconst communityPost = communityPostMatch ? communityPostMatch[1].trim() : '';\nconst hashtags = hashtagsMatch ? hashtagsMatch[1].trim() : '';\nconst totalChapters = totalChaptersMatch ? parseInt(totalChaptersMatch[1]) : 0;\n\nif (!formattedTimestamps) {\n  throw new Error('Could not parse timestamps from AI output. Check the AI prompt format.');\n}\n\n// Get video data from step 7\nconst videoData = $('7. Code \u2014 Collect + Sort All Moments').item.json;\n\nreturn [{\n  json: {\n    formattedTimestamps,\n    videoDescriptionBlock,\n    communityPost,\n    hashtags,\n    totalChapters,\n    videoTitle: videoData.videoTitle,\n    channelName: videoData.channelName,\n    videoCategory: videoData.videoCategory,\n    videoUrl: videoData.videoUrl,\n    totalMoments: videoData.totalMoments,\n    totalMinutes: videoData.totalMinutes\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "df3fbbcd-92a4-4faa-b964-ee862cf8d04d",
      "name": "11. Google Sheets \u2014 Save Timestamp Library",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        864,
        64
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Ready",
            "Hashtags": "={{ $json.hashtags }}",
            "Video URL": "={{ $json.videoUrl }}",
            "Video Title": "={{ $json.videoTitle }}",
            "Channel Name": "={{ $json.channelName }}",
            "Generated On": "={{ $now.toFormat('dd MMMM yyyy HH:mm') }}",
            "Community Post": "={{ $json.communityPost }}",
            "Total Chapters": "={{ $json.totalChapters }}",
            "Video Category": "={{ $json.videoCategory }}",
            "Total Moments Found": "={{ $json.totalMoments }}",
            "Formatted Timestamps": "={{ $json.formattedTimestamps }}",
            "Video Duration (min)": "={{ $json.totalMinutes }}",
            "Video Description Block": "={{ $json.videoDescriptionBlock }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Timestamp Library"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    }
  ],
  "connections": {
    "3. Wait \u2014 90 Seconds": {
      "main": [
        [
          {
            "node": "4. WayinVideo \u2014 Get Moments Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Wait \u2014 30 Seconds Retry": {
      "main": [
        [
          {
            "node": "4. WayinVideo \u2014 Get Moments Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Form \u2014 Video URL + Details": {
      "main": [
        [
          {
            "node": "2. WayinVideo \u2014 Submit Find Moments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. OpenAI \u2014 GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "8. AI Agent \u2014 Generate Timestamps + Post",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "5. IF \u2014 Find Moments Complete?": {
      "main": [
        [
          {
            "node": "7. Code \u2014 Collect + Sort All Moments",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "6. Wait \u2014 30 Seconds Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. Code \u2014 Parse Timestamps Output": {
      "main": [
        [
          {
            "node": "11. Google Sheets \u2014 Save Timestamp Library",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. WayinVideo \u2014 Submit Find Moments": {
      "main": [
        [
          {
            "node": "3. Wait \u2014 90 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. WayinVideo \u2014 Get Moments Results": {
      "main": [
        [
          {
            "node": "5. IF \u2014 Find Moments Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Code \u2014 Collect + Sort All Moments": {
      "main": [
        [
          {
            "node": "8. AI Agent \u2014 Generate Timestamps + Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. AI Agent \u2014 Generate Timestamps + Post": {
      "main": [
        [
          {
            "node": "10. Code \u2014 Parse Timestamps Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}