Create daily historical AI videos with Gemini, fal.ai, Telegram and YouTube
## Who’s it for This workflow is ideal for: - Content creators producing daily historical or educational videos - YouTube automation enthusiasts building AI-driven channels - Educators sharing engaging historical facts in short-form video format - Anyone creating an automated AI video pipeline with human approval ## How it works This workflow automates the full pipeline of generating and publishing historical videos: 1. Triggers daily at 1 AM and initializes retry tracking (maximum 3 attempts) 2. Fetches historical events for the current date and selects one randomly 3. Uses Google Gemini to generate a cinematic text-to-video script 4. Sends the prompt to fal.ai (Hunyuan LoRA) to generate a short video 5. Polls the generation status every 30 seconds until the video is ready 6. Downloads the generated video and sends it to Telegram with context 7. Waits for manual approval via Telegram 8. If approved → uploads the video to YouTube and sends a confirmation message 9. If declined → retries with a new event (up to 3 attempts total) ## How to set up 1. Import the workflow into n8n 2. Configure your Telegram credentials 3. Set your Telegram Chat ID using a variable or Set node (avoid hardcoding) 4. Configure HTTP Header Auth credentials for fal.ai (API key required) 5. Set up Google Gemini API credentials 6. Connect your YouTube account using OAuth2 7. (Optional) Adjust the schedule time in the trigger node 8. Activate the workflow ## Requirements - n8n (cloud or self-hosted) - fal.ai account and API key (for video generation) - Google Gemini API access - YouTube account with upload permissions - Telegram account for approval notifications ## How to customize the workflow - Adjust retry limits in the retry logic node - Modify video parameters (resolution, frames, aspect ratio) in the fal.ai request - Change the script style by editing the Gemini prompt - Replace the historical events API with another content s
Workflow JSON
{"meta":{"instanceId":"984de408a5cad88d157768c66d3889d41083cb00b76637dbc690936e2e4bdbf0","templateCredsSetupCompleted":true},"nodes":[{"id":"66521938-f8bd-4a68-8159-0b5bd054f49d","name":"Check Video Generation Status","type":"n8n-nodes-base.httpRequest","position":[10704,15760],"parameters":{"url":"=https://queue.fal.run/fal-ai/hunyuan-video-lora/requests/{{ $json.request_id }}/status","options":{},"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"credential-id","name":"Header Auth account 2"}},"typeVersion":4.4},{"id":"98749ae7-eb3f-489b-8533-a3a2821e00b7","name":"Is Generation Complete?","type":"n8n-nodes-base.if","position":[10944,15760],"parameters":{"options":{},"conditions":{"options":{"version":3,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.status }}","rightValue":"=COMPLETED"}]}},"typeVersion":2.3},{"id":"e6e4f735-fe2d-46a5-8523-3733e855ddb5","name":"Wait 30 Seconds Before Retry","type":"n8n-nodes-base.wait","position":[11184,15968],"webhookId":"3d64ae5a-1fd4-496c-ba80-bf211fbd409b","parameters":{"amount":30},"typeVersion":1.1},{"id":"31420155-9de5-4138-a24a-ec18380fa01d","name":"Get Generated Video","type":"n8n-nodes-base.httpRequest","position":[11152,15744],"parameters":{"url":"=https://queue.fal.run/fal-ai/hunyuan-video-lora/requests/{{ $json.request_id }}","options":{},"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"credential-id","name":"Header Auth account 2"}},"typeVersion":4.4},{"id":"2631b63c-f928-422e-a9b7-39f519a66bcf","name":"Send Video for Approval","type":"n8n-nodes-base.telegram","position":[12320,15744],"webhookId":"03d3e840-6aae-4e4b-a4af-92400898694b","parameters":{"chatId":"123456789","operation":"sendVideo","additionalFields":{}},"credentials":{"telegramApi":{"id":"credential-id","name":"Telegram account 4"}},"typeVersion":1.2},{"id":"1d8fef8c-f4e0-41f6-96a4-d3b1db0ebc9d","name":"Prepare Approval Message","type":"n8n-nodes-base.code","position":[12048,15744],"parameters":{"jsCode":"// Prepare message with video info and approval buttons\nconst info = $node[\"Extract Video Metadata\"].json;\nconst maxRetries = 3;\n\nconst retryInfo = info.attemptNumber > 1 \n ? `\\n🔄 *Attempt ${info.attemptNumber}/${maxRetries}* (previous declined)`\n : '';\n\nconst caption = `🎬 *Historical Video*${retryInfo}\n\n📅 *Event:* ${info.eventDescription.substring(0, 150)}\n🗓️ *Year:* ${info.eventYear}\n\n📝 *Script:*\n${info.script.substring(0, 200)}${info.script.length > 200 ? '...' : ''}\n\n❓ *Upload to YouTube?*\n/yes - Upload this video\n/no - Try another event\n/stop - Stop workflow`;\n\nreturn { \n caption: caption,\n videoUrl: info.videoUrl,\n eventYear: info.eventYear,\n eventDescription: info.eventDescription,\n script: info.script,\n attemptNumber: info.attemptNumber\n};"},"typeVersion":2},{"id":"4727dcd0-5412-4aaa-b1fb-98f7e32a2e3d","name":"Wait for User Approval","type":"n8n-nodes-base.telegram","position":[12544,15744],"webhookId":"e89251c2-970d-44c5-8dbd-f06815e74f35","parameters":{"chatId":"123456789","message":"Please approve or reject this video:","options":{},"operation":"sendAndWait","approvalOptions":{"values":{"approvalType":"double"}}},"credentials":{"telegramApi":{"id":"credential-id","name":"Telegram account 4"}},"typeVersion":1.2},{"id":"fe5539e0-82a7-44ef-ac9c-3ae33b9431cb","name":"Increment Retry Counter","type":"n8n-nodes-base.code","position":[13072,16160],"parameters":{"jsCode":"const maxRetries = 3;\n\n// Get current retry count from input data\nconst current = $input.first().json.retryCount || 0;\n\n// Increment retry\nconst next = current + 1;\n\n// Check if limit reached (>= means stop when attempts exceed maxRetries)\nif (next >= maxRetries) {\n return {\n stopWorkflow: true,\n message: `⛔ Max retries (${maxRetries}) reached. Workflow stopped after ${next} attempts.`,\n retryCount: next,\n attemptNumber: next\n };\n}\n\nreturn {\n stopWorkflow: false,\n retryCount: next,\n attemptNumber: next,\n message: `🔄 Retry ${next}/${maxRetries} - selecting a new historical event`\n};"},"typeVersion":2},{"id":"85e57273-15e1-4cd8-8ffd-af1defa89bdb","name":"Upload Confirmation Message","type":"n8n-nodes-base.code","position":[13616,15728],"parameters":{"jsCode":"// Send final confirmation after successful YouTube upload\nconst uploadResult = $input.first().json;\nconst videoInfo = $node[\"Extract Video Metadata\"].json;\n\nconst message = `✅ *Video uploaded to YouTube!*\n\n📹 *Watch:* https://www.youtube.com/watch?v=${uploadResult.id}\n📅 *Event:* ${videoInfo.eventDescription.substring(0, 100)}\n🗓️ *Year:* ${videoInfo.eventYear}`;\n\nreturn { message: message };"},"typeVersion":2},{"id":"454ac1f6-1a70-4c29-9451-86fc340963a4","name":"Send Upload Confirmation","type":"n8n-nodes-base.telegram","position":[13888,15728],"webhookId":"6fb75372-a91f-4bc9-93c9-940cc17f67eb","parameters":{"text":"={{ $json.message }}","chatId":"123456789","additionalFields":{}},"credentials":{"telegramApi":{"id":"credential-id","name":"Telegram account 4"}},"typeVersion":1.2},{"id":"bb2d112d-0312-4cb5-b387-cd3883bdbf72","name":"Select Random Historical Event","type":"n8n-nodes-base.code","position":[9488,15792],"parameters":{"jsCode":"// Pick a random historical event from the fetched data\nconst data = $input.first().json.data;\nconst events = data.Events;\n\n// Random selection - each run picks a different event\nconst selectedEvent = events[Math.floor(Math.random() * events.length)];\n\n// Clean HTML entities from event description\nconst cleanText = selectedEvent.text\n .replace(/ /g, ' ')\n .replace(/&[a-z]+;/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n// Get retry count from input if available\nconst retryData = $input.first().json;\nconst retryCount = retryData.retryCount || 0;\nconst attemptNumber = retryCount + 1;\n\nreturn {\n year: selectedEvent.year,\n description: cleanText,\n retryCount: retryCount,\n attemptNumber: attemptNumber\n};"},"typeVersion":2},{"id":"953fafa0-23f6-4baf-8027-982e3e4cd1ca","name":"Generate Cinematic Script with AI","type":"@n8n/n8n-nodes-langchain.agent","position":[9840,15792],"parameters":{"text":"=You are a professional scriptwriter specializing in cinematic historical documentaries.\n\nCreate a SHORT cinematic video script based on this historical event:\n{{ $json.description }} ({{ $json.year }})\n\nReturn ONLY a single clean paragraph (no markdown, no **bold**, no --- separators, no \"SCENE 1\" labels) that includes:\n- scene description\n- mood\n- camera movement\n- visual style\n\nMake it ready to be used directly as a Text-to-Video prompt. Keep it under 400 words.","options":{"systemMessage":"You are a professional scriptwriter specializing in cinematic historical documentaries."},"promptType":"define"},"typeVersion":3.1},{"id":"be82b1f5-aaaa-439f-a5fe-f5ae919fb584","name":"Extract Video Metadata","type":"n8n-nodes-base.code","position":[11456,15744],"parameters":{"jsCode":"// Extract video URL and metadata from fal.ai response\nconst response = $input.first().json;\nconst videoFile = response.video;\nconst eventInfo = $node[\"Select Random Historical Event\"].json;\n\nif (!videoFile || !videoFile.url) {\n throw new Error('No video URL found from fal.ai');\n}\n\nreturn {\n videoUrl: videoFile.url,\n eventYear: eventInfo.year,\n eventDescription: eventInfo.description,\n script: $node[\"Generate Cinematic Script with AI\"].json.output,\n retryCount: eventInfo.retryCount,\n attemptNumber: eventInfo.attemptNumber\n};"},"typeVersion":2},{"id":"3044c4e5-1289-42a0-8ab1-49830391df2f","name":"Download Video File","type":"n8n-nodes-base.httpRequest","position":[11712,15744],"parameters":{"url":"={{ $json.videoUrl }}","options":{"timeout":300000,"response":{"response":{"responseFormat":"file"}}}},"typeVersion":4.4},{"id":"fc353991-a943-4951-8d48-e9a5c82e5a08","name":"Upload to YouTube","type":"n8n-nodes-base.youTube","position":[13280,15728],"parameters":{"title":"={{ $node['Extract Video Metadata'].json.eventDescription.substring(0, 100) }} ({{ $node['Extract Video Metadata'].json.eventYear }})","options":{},"resource":"video","operation":"upload","regionCode":"US"},"credentials":{"youTubeOAuth2Api":{"id":"credential-id","name":"YouTube account"}},"typeVersion":1},{"id":"3f79dcb7-887b-495d-abba-f7501fb35a09","name":"Check User Approval","type":"n8n-nodes-base.if","position":[12848,15744],"parameters":{"options":{},"conditions":{"options":{"version":3,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"8ef3580a-a5ce-43e5-962e-7a5e3c7e79f9","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.data.approved }}","rightValue":"=true"}]}},"typeVersion":2.2},{"id":"92259068-c1d3-4370-bbfe-35a7edd2ffcc","name":"Initialize Workflow State","type":"n8n-nodes-base.code","position":[8256,15760],"parameters":{"jsCode":"// Initialize workflow state for retry tracking\n// Using a robust approach that passes data through the flow\n\n// Check if we have incoming retry data\nconst input = $input.first();\nlet retryCount = 0;\n\nif (input && input.json) {\n // If this is a retry, preserve the retry count\n if (input.json.retryCount !== undefined) {\n retryCount = input.json.retryCount;\n }\n}\n\n// For fresh starts, retryCount remains 0\nreturn {\n retryCount: retryCount,\n attemptNumber: retryCount + 1,\n stopWorkflow: false,\n initialized: true,\n message: retryCount > 0 ? `Continuing with retry attempt ${retryCount + 1}` : \"Workflow initialized\"\n};"},"typeVersion":2},{"id":"7c5cc181-aabf-4bd6-9957-8f3b90fb8837","name":"Fetch Historical Events","type":"n8n-nodes-base.httpRequest","position":[9232,15792],"parameters":{"url":"=https://history.muffinlabs.com/date/{{ new Date().getMonth() + 1 }}/{{ new Date().getDate() }}","options":{"timeout":30000}},"typeVersion":4.4},{"id":"b1198fbb-f109-46da-8dfe-50c1fd5387bf","name":"Send Stop Message","type":"n8n-nodes-base.telegram","position":[8800,15632],"webhookId":"d7c6b4c8-bbb6-4e60-8246-de0bb3d97d71","parameters":{"text":"={{ $json.message }}","chatId":"123456789","additionalFields":{}},"credentials":{"telegramApi":{"id":"credential-id","name":"Telegram account 4"}},"typeVersion":1.2},{"id":"7c5d8f43-1341-4bc6-8f78-b60b14d341eb","name":"Check Retry Limit","type":"n8n-nodes-base.if","position":[8608,15760],"parameters":{"options":{},"conditions":{"options":{"version":3,"leftValue":"","caseSensitive":true,"typeValidation":"loose"},"combinator":"and","conditions":[{"id":"fa22adc5-4304-4cd7-a4c6-92abfd39d62f","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.stopWorkflow }}","rightValue":"=true"}]},"looseTypeValidation":true},"typeVersion":2.3},{"id":"ba210861-ef8d-4365-b479-3a3264f231ca","name":"Schedule (Daily at 1 AM)","type":"n8n-nodes-base.scheduleTrigger","position":[8000,15760],"parameters":{"rule":{"interval":[{"triggerAtHour":1}]}},"typeVersion":1.3},{"id":"09a713ae-dc16-42c6-b974-d59f870b3dbf","name":"Gemini Language Model","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[9808,16032],"parameters":{"options":{}},"credentials":{"googlePalmApi":{"id":"credential-id","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"c76633c3-8d4f-4cea-a75b-c9f529ad2e0f","name":"Submit to fal.ai","type":"n8n-nodes-base.httpRequest","position":[10336,15792],"parameters":{"url":"https://queue.fal.run/fal-ai/hunyuan-video-lora","method":"POST","options":{},"jsonBody":"={\"prompt\": \"{{ $json.output.replace(/\\\"/g, '\\\\\"') }}\", \"aspect_ratio\": \"16:9\", \"resolution\": \"720p\", \"num_frames\": 129, \"enable_safety_checker\": true}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"credential-id","name":"Header Auth account 2"}},"typeVersion":4.4},{"id":"2527fde4-db08-4cf1-b49c-c461bf75ef31","name":"Event Selection","type":"n8n-nodes-base.stickyNote","width":380,"height":120,"position":[9152,15648],"parameters":{"color":6,"width":1056,"height":544,"content":"**Fetch and process historical event**\nRetrieves daily history and generates a cinematic script via AI."},"typeVersion":1},{"id":"3bd98221-d891-4beb-83cd-6a02c67887cd","name":"Video Generation","type":"n8n-nodes-base.stickyNote","width":420,"height":140,"position":[10288,15648],"parameters":{"color":5,"width":1600,"height":544,"content":"**Generate and download AI video **\nSubmits script to fal.ai and monitors generation progress."},"typeVersion":1},{"id":"bc215578-d34b-406c-b6db-fd1624bbb956","name":"User Approval","type":"n8n-nodes-base.stickyNote","width":420,"height":160,"position":[11968,15584],"parameters":{"width":1024,"height":384,"content":"**Retrieve and prepare video file**\n\nFetches the final file and extracts metadata for the approval message."},"typeVersion":1},{"id":"cb1cb366-7f61-4aaa-8a2a-f9e69e635f28","name":"Retry on Decline","type":"n8n-nodes-base.stickyNote","width":320,"height":120,"position":[13008,15984],"parameters":{"width":384,"height":368,"content":"**Retry Handling on Decline**\n\nIncrements retry counter (max 3) and loops back to select a new event."},"typeVersion":1},{"id":"90021626-a94a-4f79-9698-7074162ec719","name":"YouTube Upload","type":"n8n-nodes-base.stickyNote","width":400,"height":140,"position":[13232,15584],"parameters":{"color":4,"width":832,"height":336,"content":"**YouTube Upload & Confirmation**\n\nUploads approved video to YouTube and sends final confirmation with link via Telegram."},"typeVersion":1},{"id":"6e8d4318-c6f4-475d-9fb8-feed182b8410","name":"Daily Schedule & Init","type":"n8n-nodes-base.stickyNote","width":520,"height":140,"position":[7920,15600],"parameters":{"color":4,"width":1168,"height":416,"content":"**Schedule and state initialization**\nHandles the daily trigger, retry logic, and workflow termination."},"typeVersion":1},{"id":"ecd4c7ed-5cbe-4616-8048-199368391481","name":"Main Workflow Overview","type":"n8n-nodes-base.stickyNote","style":{"fontSize":14,"borderColor":"#F4D03F","backgroundColor":"#FFF8C4"},"width":820,"height":320,"position":[7072,15440],"parameters":{"width":688,"height":1200,"content":"## Generate AI historical videos and upload to YouTube with Telegram approval\n\n## Who’s it for\nThis workflow is ideal for:\n- Content creators producing daily historical or educational videos \n- YouTube automation enthusiasts building AI-driven channels \n- Educators sharing engaging historical facts in short-form video format \n- Anyone creating an automated AI video pipeline with human approval \n\n## How it works\nThis workflow automates the full pipeline of generating and publishing historical videos:\n\n1. Triggers daily at 1 AM and initializes retry tracking (maximum 3 attempts) \n2. Fetches historical events for the current date and selects one randomly \n3. Uses Google Gemini to generate a cinematic text-to-video script \n4. Sends the prompt to fal.ai (Hunyuan LoRA) to generate a short video \n5. Polls the generation status every 30 seconds until the video is ready \n6. Downloads the generated video and sends it to Telegram with context \n7. Waits for manual approval via Telegram \n8. If approved → uploads the video to YouTube and sends a confirmation message \n9. If declined → retries with a new event (up to 3 attempts total) \n\n## How to set up\n1. Import the workflow into n8n \n2. Configure your Telegram credentials \n3. Set your Telegram Chat ID using a variable or Set node (avoid hardcoding) \n4. Configure HTTP Header Auth credentials for fal.ai (API key required) \n5. Set up Google Gemini API credentials \n6. Connect your YouTube account using OAuth2 \n7. (Optional) Adjust the schedule time in the trigger node \n8. Activate the workflow \n\n## Requirements\n- n8n (cloud or self-hosted) \n- fal.ai account and API key (for video generation) \n- Google Gemini API access \n- YouTube account with upload permissions \n- Telegram account for approval notifications \n\n## How to customize the workflow\n- Adjust retry limits in the retry logic node \n- Modify video parameters (resolution, frames, aspect ratio) in the fal.ai request \n- Change the script style by editing the Gemini prompt \n- Replace the historical events API with another content source \n- Customize Telegram messages or approval flow "},"typeVersion":1}],"pinData":{},"connections":{"Submit to fal.ai":{"main":[[{"node":"Check Video Generation Status","type":"main","index":0}]]},"Check Retry Limit":{"main":[[{"node":"Send Stop Message","type":"main","index":0}],[{"node":"Fetch Historical Events","type":"main","index":0}]]},"Upload to YouTube":{"main":[[{"node":"Upload Confirmation Message","type":"main","index":0}]]},"Check User Approval":{"main":[[{"node":"Upload to YouTube","type":"main","index":0}],[{"node":"Increment Retry Counter","type":"main","index":0}]]},"Download Video File":{"main":[[{"node":"Prepare Approval Message","type":"main","index":0}]]},"Get Generated Video":{"main":[[{"node":"Extract Video Metadata","type":"main","index":0}]]},"Gemini Language Model":{"ai_languageModel":[[{"node":"Generate Cinematic Script with AI","type":"ai_languageModel","index":0}]]},"Extract Video Metadata":{"main":[[{"node":"Download Video File","type":"main","index":0}]]},"Wait for User Approval":{"main":[[{"node":"Check User Approval","type":"main","index":0}]]},"Fetch Historical Events":{"main":[[{"node":"Select Random Historical Event","type":"main","index":0}]]},"Increment Retry Counter":{"main":[[{"node":"Check Retry Limit","type":"main","index":0}]]},"Is Generation Complete?":{"main":[[{"node":"Get Generated Video","type":"main","index":0}],[{"node":"Wait 30 Seconds Before Retry","type":"main","index":0}]]},"Send Video for Approval":{"main":[[{"node":"Wait for User Approval","type":"main","index":0}]]},"Prepare Approval Message":{"main":[[{"node":"Send Video for Approval","type":"main","index":0}]]},"Schedule (Daily at 1 AM)":{"main":[[{"node":"Initialize Workflow State","type":"main","index":0}]]},"Initialize Workflow State":{"main":[[{"node":"Check Retry Limit","type":"main","index":0}]]},"Upload Confirmation Message":{"main":[[{"node":"Send Upload Confirmation","type":"main","index":0}]]},"Wait 30 Seconds Before Retry":{"main":[[{"node":"Check Video Generation Status","type":"main","index":0}]]},"Check Video Generation Status":{"main":[[{"node":"Is Generation Complete?","type":"main","index":0}]]},"Select Random Historical Event":{"main":[[{"node":"Generate Cinematic Script with AI","type":"main","index":0}]]},"Generate Cinematic Script with AI":{"main":[[{"node":"Submit to fal.ai","type":"main","index":0}]]}}}How to Import This Workflow
- 1Copy the workflow JSON above using the Copy Workflow JSON button.
- 2Open your n8n instance and go to Workflows.
- 3Click Import from JSON and paste the copied workflow.
Don't have an n8n instance? Start your free trial at n8nautomation.cloud
Related Templates
AI Agent for project management and meetings with Airtable and Fireflies
Ready-to-use n8n workflow template for productivity. This automation connects Airtable, Gmail, Google Calendar, OpenAI with 18 nodes. Import directly into your n8n instance and customize for your needs.
Handling Job Application Submissions with AI and n8n Forms
Ready-to-use n8n workflow template for productivity. This automation connects Airtable, OpenAI with 23 nodes. Import directly into your n8n instance and customize for your needs.
AI Agent to chat with Airtable and analyze data
Ready-to-use n8n workflow template for productivity. This automation connects Airtable, OpenAI with 41 nodes. Import directly into your n8n instance and customize for your needs.