{
 "name": "RecipeAgents: YouTube video to blog draft with Gemini",
 "nodes": [
  {
   "parameters": {
    "formTitle": "YouTube video to blog draft",
    "formDescription": "Paste the link to a public YouTube video.",
    "formFields": {
     "values": [
      {
       "fieldLabel": "YouTube URL",
       "placeholder": "https://www.youtube.com/watch?v=...",
       "requiredField": true
      },
      {
       "fieldLabel": "Notes for the draft",
       "fieldType": "textarea",
       "placeholder": "Audience, keyword, length (optional)"
      }
     ]
    },
    "options": {}
   },
   "id": "5593c14e-f199-5c6b-af41-12105484b9be",
   "name": "Form Trigger",
   "type": "n8n-nodes-base.formTrigger",
   "typeVersion": 2.2,
   "position": [
    240,
    300
   ]
  },
  {
   "parameters": {
    "jsCode": "// Accepts youtube.com/watch?v=, youtu.be/, /shorts/, /live/ and /embed/ links and returns one clean watch URL.\nconst f = $input.first().json;\nconst raw = String(f['YouTube URL'] || '').trim();\nconst m = raw.match(/(?:youtube\\.com\\/(?:watch\\?(?:.*&)?v=|shorts\\/|live\\/|embed\\/)|youtu\\.be\\/)([A-Za-z0-9_-]{11})/);\nif (!m) throw new Error('This is not a YouTube video link: ' + raw.slice(0, 100));\nreturn [{ json: { video_url: 'https://www.youtube.com/watch?v=' + m[1], notes: String(f['Notes for the draft'] || '').trim() } }];"
   },
   "id": "448fe963-a685-55ae-8cee-367c9b3a34be",
   "name": "Check URL",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    500,
    300
   ]
  },
  {
   "parameters": {
    "method": "POST",
    "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash-lite:generateContent",
    "authentication": "predefinedCredentialType",
    "nodeCredentialType": "googlePalmApi",
    "sendBody": true,
    "specifyBody": "json",
    "jsonBody": "={{ JSON.stringify({ contents: [{ role: 'user', parts: [{ fileData: { fileUri: $json.video_url } }, { text: \"You are an editor turning a YouTube video into a first blog draft for the video's own creator. Watch the whole video, picture and sound.\\nNotes from the creator (may be empty): [NOTES]\\nReturn JSON only, with these keys:\\ntitle: a blog title under 60 characters that says what the reader gets\\nmeta_description: 120 to 155 characters\\ndraft: the post in plain text, 600 to 1,000 words. Start with a 2-sentence answer to the video's main question. Put each section heading on its own line in Title Case, with no # or * symbols. Use the speaker's own points and examples, in your own words: do not copy long passages word for word. Mention things shown on screen (slides, code, products) only when they matter.\\ncheck_these: a list of objects with timestamp (MM:SS) and claim, for every number, price, date, name and factual claim in the draft, so a person can check them against the video.\\nUse only what is in the video and the notes. If something is unclear (a name you couldn't hear, a number that went by fast), say so in check_these instead of guessing.\".replace('[NOTES]', $json.notes || 'none') }] }], generationConfig: { responseMimeType: 'application/json', maxOutputTokens: 8192 } }) }}",
    "options": {
     "timeout": 300000
    }
   },
   "id": "881c2c3e-fb3c-5442-a401-17804961e6dd",
   "name": "Gemini: watch and draft",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    760,
    300
   ]
  },
  {
   "parameters": {
    "jsCode": "// Stops with the reason if Gemini cut the answer short or blocked it, then lays out the Doc text.\nconst res = $input.first().json;\nconst cand = (res.candidates || [])[0];\nif (!cand) throw new Error('Gemini returned no answer: ' + JSON.stringify(res.promptFeedback || res).slice(0, 300));\nif (cand.finishReason && cand.finishReason !== 'STOP') throw new Error('Gemini stopped early: ' + cand.finishReason + '. MAX_TOKENS: raise maxOutputTokens in the HTTP Request body. RECITATION: the answer followed the video too closely; ask for a shorter draft in new words.');\nconst text = (cand.content && cand.content.parts || []).filter(p => !p.thought).map(p => p.text || '').join('');\nlet d;\ntry { d = JSON.parse(text.replace(/^```(?:json)?\\s*|\\s*```$/g, '').trim()); } catch (e) { throw new Error('Could not read the JSON from Gemini: ' + text.slice(0, 200)); }\nconst url = $('Check URL').first().json.video_url;\nconst checks = (d.check_these || []).map(c => '- [' + (c.timestamp || '?') + '] ' + c.claim).join('\\n');\nconst used = res.usageMetadata ? res.usageMetadata.promptTokenCount + ' input tokens, ' + (res.usageMetadata.candidatesTokenCount || 0) + ' output tokens' : 'not reported';\nconst body = [d.title || '', '', 'Meta description: ' + (d.meta_description || ''), 'Source video: ' + url, '', d.draft || '', '',\n  'CHECK BEFORE PUBLISHING (numbers, names and claims, with the time they come up):', checks || '- nothing flagged', '', 'Tokens used: ' + used].join('\\n');\nreturn [{ json: { title: 'DRAFT: ' + (d.title || 'Untitled'), body, video_url: url } }];"
   },
   "id": "521b1e03-8bb8-5e17-9200-779dc02ee137",
   "name": "Read the answer",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    1020,
    300
   ]
  },
  {
   "parameters": {
    "driveId": "myDrive",
    "folderId": "default",
    "title": "={{ $json.title }}"
   },
   "id": "72868b77-dc5e-5105-a3e9-2c6ed8788259",
   "name": "Create doc",
   "type": "n8n-nodes-base.googleDocs",
   "typeVersion": 2,
   "position": [
    1280,
    300
   ]
  },
  {
   "parameters": {
    "operation": "update",
    "documentURL": "={{ $json.id }}",
    "actionsUi": {
     "actionFields": [
      {
       "object": "text",
       "action": "insert",
       "insertSegment": "body",
       "locationChoice": "endOfSegmentLocation",
       "text": "={{ $('Read the answer').item.json.body }}"
      }
     ]
    },
    "updateFields": {}
   },
   "id": "f87305e5-9a91-532a-b051-39b8e004f5ae",
   "name": "Add the draft",
   "type": "n8n-nodes-base.googleDocs",
   "typeVersion": 2,
   "position": [
    1540,
    300
   ]
  },
  {
   "parameters": {
    "content": "## YouTube video to blog draft with Gemini\nFrom recipeagents.usestarbase.com/recipes/youtube-to-blog-draft-gemini\n\n1. Connect credentials on each node (Google Gemini(PaLM) API on the HTTP Request node, Google Docs on both Docs nodes).\n2. Pick your sheet/folder/channel and model.\n3. Run once with test data and check the output before activating.\nKeep a human check on anything customer-facing.",
    "height": 260,
    "width": 420
   },
   "id": "4beeb733-36ac-5e79-9a86-0e4e3bd73fbb",
   "name": "Read me",
   "type": "n8n-nodes-base.stickyNote",
   "typeVersion": 1,
   "position": [
    200,
    20
   ]
  }
 ],
 "connections": {
  "Form Trigger": {
   "main": [
    [
     {
      "node": "Check URL",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Check URL": {
   "main": [
    [
     {
      "node": "Gemini: watch and draft",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Gemini: watch and draft": {
   "main": [
    [
     {
      "node": "Read the answer",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Read the answer": {
   "main": [
    [
     {
      "node": "Create doc",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Create doc": {
   "main": [
    [
     {
      "node": "Add the draft",
      "type": "main",
      "index": 0
     }
    ]
   ]
  }
 },
 "pinData": {},
 "active": false,
 "settings": {
  "executionOrder": "v1"
 },
 "meta": {
  "templateCredsSetupCompleted": false
 },
 "tags": []
}