From c8a6806f371cfa1cd963e55acab1258f5bed439e Mon Sep 17 00:00:00 2001 From: nusquama Date: Sun, 15 Mar 2026 12:01:54 +0800 Subject: [PATCH] creation --- ...te_360_product_videos_from_photos_with_veo_3_and_telegram.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 workflows/Generate 360° product videos from photos with Veo 3 and Telegram-13928/generate_360_product_videos_from_photos_with_veo_3_and_telegram.json diff --git a/workflows/Generate 360° product videos from photos with Veo 3 and Telegram-13928/generate_360_product_videos_from_photos_with_veo_3_and_telegram.json b/workflows/Generate 360° product videos from photos with Veo 3 and Telegram-13928/generate_360_product_videos_from_photos_with_veo_3_and_telegram.json new file mode 100644 index 000000000..840ddc508 --- /dev/null +++ b/workflows/Generate 360° product videos from photos with Veo 3 and Telegram-13928/generate_360_product_videos_from_photos_with_veo_3_and_telegram.json @@ -0,0 +1 @@ +{"meta":{"instanceId":"d8b6cd0a2c2e6ba23a5da9f16cd13931d99066fe95baf9d490aa54324f0e20c5","templateCredsSetupCompleted":true},"nodes":[{"id":"b2b02fc9-ea89-4d96-893f-f9e530876ea8","name":"šŸ“‹ Overview","type":"n8n-nodes-base.stickyNote","position":[-9264,-336],"parameters":{"width":620,"height":584,"content":"## šŸŽ¬ 360° Product Video Generator\n\nTurn a single product photo into a cinematic 360° video using Google Veo 3 — delivered straight to Telegram.\n\n## How it works\n1. User sends a product photo to your Telegram bot\n2. The workflow authenticates with Google Cloud using a Service Account stored in Google Sheets\n3. The image is sent to Vertex AI (Veo 3) with a 360° orbit camera prompt\n4. The workflow polls every 2 minutes until the video is ready (up to 10 min)\n5. The finished video is sent back to the user in Telegram\n\n## Setup steps\n1. Create a Telegram bot via @BotFather and add the credentials in n8n\n2. Enable the **Vertex AI API** in your Google Cloud project and request Veo 3 preview access\n3. Create a Service Account with `roles/aiplatform.user` and download the JSON key\n4. Paste the key fields into your Google Sheet — columns needed: `client_email`, `private_key`, `project_id`, `scope`\n5. Update the **1. Get Service Account Details** node with your Sheet ID\n6. Connect your Google and Telegram credentials, then activate the workflow"},"typeVersion":1},{"id":"70a23ef7-b728-4388-b4e4-afe39ef8b657","name":"Group 1","type":"n8n-nodes-base.stickyNote","position":[-8528,-208],"parameters":{"color":7,"width":428,"height":348,"content":"## Receive & Validate\nListens for Telegram messages and checks that a photo was sent and is at least 480px. Rejects documents or text-only messages and replies with a clear error."},"typeVersion":1},{"id":"0cf4efdf-44ac-4673-a88c-cb25452ca036","name":"Group 2","type":"n8n-nodes-base.stickyNote","position":[-8064,-176],"parameters":{"color":7,"width":840,"height":332,"content":"## Google Cloud Auth\nReads Service Account credentials from Google Sheets, signs a JWT locally, and exchanges it for a short-lived OAuth access token. Runs fresh on every request."},"typeVersion":1},{"id":"db7f81ba-92c6-4677-a921-93e879d0771b","name":"Group 3","type":"n8n-nodes-base.stickyNote","position":[-7168,-288],"parameters":{"color":7,"width":840,"height":480,"content":"## Download & Convert\nDownloads the highest-resolution version of the photo from Telegram and converts it to Base64. Any conversion failure is caught and reported back to the user."},"typeVersion":1},{"id":"a8977c41-5e0f-4e02-91ce-3fef4a6ca60b","name":"Group 4","type":"n8n-nodes-base.stickyNote","position":[-6272,-384],"parameters":{"color":7,"width":620,"height":508,"content":"## Submit to Veo 3\nBuilds the API payload with a cinematic 360° orbit prompt and submits a long-running job to Vertex AI Veo 3. Gets back an operation ID used for polling."},"typeVersion":1},{"id":"19948635-28b8-4c13-8ede-1a7bee91c42e","name":"Group 5","type":"n8n-nodes-base.stickyNote","position":[-5616,-528],"parameters":{"color":7,"width":1108,"height":872,"content":"## Poll for Result\nChecks every 2 minutes whether the video is ready. Times out after 40 attempts (~10 min) and notifies the user with a friendly error if generation takes too long."},"typeVersion":1},{"id":"be8ea122-84d1-4264-a646-be1010aa4bb2","name":"Group 6","type":"n8n-nodes-base.stickyNote","position":[-4496,-432],"parameters":{"color":7,"width":660,"height":276,"content":"## Deliver Video\nConverts the Base64 video bytes to a file and sends it directly to the user in Telegram."},"typeVersion":1},{"id":"ee81c12f-2c54-441b-90a1-f5482c40b97e","name":"Wait 2 Minutes","type":"n8n-nodes-base.wait","position":[-5552,-240],"webhookId":"d3800f2b-17a5-4490-b79a-1d8ce76e3a88","parameters":{"unit":"minutes","amount":2},"typeVersion":1.1},{"id":"99b5b684-d316-4c79-9f58-d190be2e85df","name":"Send Timeout Error","type":"n8n-nodes-base.telegram","position":[-4784,144],"webhookId":"timeout-error-webhook","parameters":{"text":"=āŒ Video generation failed after {{ $json.poll_count }} attempts.\n\nPlease try again with a different image or contact support.","chatId":"={{ $json.chatId }}","additionalFields":{"reply_to_message_id":"={{ $json.messageId }}"}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.1},{"id":"e545feb6-efa8-4bf0-9bf0-0bb5d3fae486","name":"Check Timeout","type":"n8n-nodes-base.if","position":[-5184,32],"parameters":{"options":{},"conditions":{"string":[{"value1":"={{ $json.status }}","value2":"timeout"}]}},"typeVersion":2},{"id":"7d7f7f2c-0e25-473b-bf87-7339957295d7","name":"Download Image","type":"n8n-nodes-base.telegram","position":[-6896,-160],"webhookId":"75958cb9-50b4-43c4-8764-d6d99607f122","parameters":{"fileId":"={{ $json.result.reply_to_message.photo[3].file_id }}","resource":"file","additionalFields":{}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.1},{"id":"83d5cd34-c7a8-454d-bea7-22854b9fea78","name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","position":[-8480,-48],"webhookId":"4883299d-cfbb-4008-9d5d-9e7ef74689d5","parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.2},{"id":"1ed72edc-642e-4d2b-a33a-39eac6401c2c","name":"2. Validate Input","type":"n8n-nodes-base.code","position":[-8256,-48],"parameters":{"jsCode":"const inputData = $input.item.json;\nconst message = inputData.message;\nconst output = { ...inputData };\n\nif (!message) {\n output.error = true;\n output.errorMessage = 'āŒ No message found.';\n return [{ json: output }];\n}\n\nif (!message.photo || !Array.isArray(message.photo) || message.photo.length === 0) {\n output.error = true;\n output.errorMessage = 'āŒ No photo found. Please send an image of your product.';\n return [{ json: output }];\n}\n\nconst photo = message.photo[message.photo.length - 1];\nif (Math.min(photo.width, photo.height) < 480) {\n output.error = true;\n output.errorMessage = `āŒ Image too small (${photo.width}x${photo.height}).\\n\\nMinimum: 480px — Recommended: 1024x1024 or larger`;\n return [{ json: output }];\n}\n\noutput.chatId = message.chat?.id;\noutput.messageId = message.message_id;\noutput.caption = message.caption || '';\noutput.file_id = photo.file_id;\noutput.error = false;\n\nreturn [{ json: output }];"},"typeVersion":2},{"id":"1f36ad53-6c90-471e-b8a3-6686b96de5de","name":"Check Auth Token Valid","type":"n8n-nodes-base.if","position":[-7344,-48],"parameters":{"options":{},"conditions":{"options":{"version":1,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"9e617a27-7ee6-44ba-a7cb-02b3e5738b08","operator":{"type":"boolean","operation":"false","singleValue":true},"leftValue":"={{ $json.access_token.isEmpty() }}","rightValue":""}]}},"typeVersion":2},{"id":"3153c612-b11e-40ad-a24c-06b48b24f2eb","name":"Send Validation Error","type":"n8n-nodes-base.telegram","position":[-7120,32],"webhookId":"fcd46af2-c85c-4146-8d0c-bc0e80798ac9","parameters":{"text":"={{ $json.errorMessage }}","chatId":"={{ $json.chatId }}","additionalFields":{"reply_to_message_id":"={{ $json.messageId }}"}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.1},{"id":"526add44-409e-41e6-b327-05c91a427708","name":"Send Processing Message","type":"n8n-nodes-base.telegram","position":[-7120,-160],"webhookId":"0a22f792-6405-4b9b-9fb7-d6ef02c6df9c","parameters":{"text":"šŸŽ¬ Creating your 360° product video...\n\nā±ļø This takes around 3–4 minutes\nšŸ”„ Processing with Google Veo 3...","chatId":"={{ $json.chatId }}","additionalFields":{"reply_to_message_id":"={{ $json.messageId }}"}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.1},{"id":"238d0cb6-789f-4cbd-b894-38525e8b18e8","name":"Convert Image to Base64","type":"n8n-nodes-base.code","position":[-6672,-160],"parameters":{"jsCode":"const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n if (!item.binary || !item.binary.data) {\n throw new Error('No binary data found');\n }\n\n let imageBuffer;\n let mimeType = 'image/jpeg';\n\n if (item.binary.data.data) {\n imageBuffer = Buffer.from(item.binary.data.data, 'base64');\n mimeType = item.binary.data.mimeType || mimeType;\n } else if (Buffer.isBuffer(item.binary.data)) {\n imageBuffer = item.binary.data;\n mimeType = item.binary.mimeType || mimeType;\n } else if (typeof item.binary.data === 'string') {\n imageBuffer = Buffer.from(item.binary.data, 'base64');\n mimeType = item.binary.mimeType || mimeType;\n } else {\n const binaryKeys = Object.keys(item.binary).filter(k =>\n !['mimeType', 'fileType', 'fileName', 'fileExtension'].includes(k)\n );\n if (binaryKeys.length > 0) {\n const binaryData = item.binary[binaryKeys[0]];\n imageBuffer = Buffer.isBuffer(binaryData) ? binaryData\n : binaryData.data ? Buffer.from(binaryData.data)\n : Buffer.from(binaryData);\n mimeType = item.binary.mimeType || mimeType;\n } else {\n throw new Error('Could not locate binary data');\n }\n }\n\n if (!imageBuffer || imageBuffer.length === 0) throw new Error('Empty image buffer');\n\n results.push({\n json: {\n ...item.json,\n imageBase64: imageBuffer.toString('base64'),\n imageMimeType: mimeType,\n imageSize: imageBuffer.length,\n conversionStatus: 'success'\n }\n });\n\n } catch (error) {\n results.push({\n json: {\n ...item.json,\n error: true,\n conversionStatus: 'failed',\n conversionError: error.message\n }\n });\n }\n}\n\nreturn results;"},"typeVersion":2},{"id":"5696a1e9-ef12-4d7e-bc24-5cfcec661fec","name":"Conversion OK?","type":"n8n-nodes-base.if","position":[-6448,-160],"parameters":{"conditions":{"string":[{"value1":"={{ $json.conversionStatus }}","value2":"success"}]}},"typeVersion":1},{"id":"f4ac9dea-d831-413b-9ecc-1181a8ee0185","name":"Send Conversion Error","type":"n8n-nodes-base.telegram","position":[-6016,-48],"webhookId":"08210d1c-81de-4ee6-b6d8-c46c756eaa29","parameters":{"text":"=āŒ Failed to process your image: {{ $json.conversionError }}\n\nPlease try sending the photo again.","chatId":"={{ $json.chatId }}","additionalFields":{"reply_to_message_id":"={{ $json.messageId }}"}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.1},{"id":"06685eae-cf1a-4469-84de-f2d06079121f","name":"5. Prepare Veo Request","type":"n8n-nodes-base.code","position":[-6224,-240],"parameters":{"jsCode":"const item = $input.item.json;\n\nconst basePrompt = `Create a professional 360-degree product showcase video. The camera smoothly orbits the product in a full 360-degree rotation, maintaining consistent framing. Use studio lighting with a clean white background. The product stays centered throughout. Smooth, cinematic movement — no jitter or cuts.`;\n\nconst userCaption = item.caption || '';\nconst finalPrompt = userCaption.trim().length > 0\n ? basePrompt + ` Product context: ${userCaption.trim()}`\n : basePrompt;\n\nconst apiPayload = {\n instances: [{\n prompt: finalPrompt,\n image: {\n bytesBase64Encoded: item.imageBase64,\n mimeType: item.imageMimeType\n }\n }],\n parameters: {\n aspectRatio: '16:9',\n sampleCount: 1,\n durationSeconds: 8,\n personGeneration: 'allow_all',\n addWatermark: true,\n includeRaiReason: true,\n generateAudio: true\n }\n};\n\nreturn {\n api_payload: JSON.stringify(apiPayload),\n chatId: item.chatId,\n messageId: item.messageId,\n caption: item.caption\n};"},"typeVersion":2},{"id":"010b2b1c-fdde-454d-851d-e8d1aa5b7bfb","name":"6. Call Vertex AI Veo 3","type":"n8n-nodes-base.httpRequest","position":[-6000,-240],"parameters":{"url":"=https://us-central1-aiplatform.googleapis.com/v1/projects/{{ $('3. Get Access Token').item.json.project_id }}/locations/us-central1/publishers/google/models/veo-3.0-generate-preview:predictLongRunning","method":"POST","options":{},"jsonBody":"={{ $json.api_payload }}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"Authorization","value":"=Bearer {{ $('3. Get Access Token').item.json.access_token }}"},{"name":"Content-Type","value":"application/json"}]}},"typeVersion":4.2},{"id":"1343ec01-9309-40c6-b287-df7d3af9941b","name":"Extract Operation Name","type":"n8n-nodes-base.code","position":[-5776,-240],"parameters":{"jsCode":"const response = $input.item.json;\n\nif (response.name) {\n return [{\n json: {\n operation_name: response.name,\n status: 'polling',\n poll_count: 0,\n chatId: response.chatId,\n messageId: response.messageId,\n caption: response.caption\n }\n }];\n} else {\n return [{\n json: {\n ...response,\n error: 'No operation name returned from Vertex AI',\n status: 'failed'\n }\n }];\n}"},"typeVersion":2},{"id":"13ef8fde-d595-4fec-b48b-66e479cdd700","name":"Poll Video Status","type":"n8n-nodes-base.httpRequest","position":[-5328,-320],"parameters":{"url":"=https://us-central1-aiplatform.googleapis.com/v1/projects/{{ $('3. Get Access Token').item.json.project_id }}/locations/us-central1/publishers/google/models/veo-3.0-generate-preview:fetchPredictOperation","method":"POST","options":{},"jsonBody":"={\n \"operationName\": \"{{ $json.operation_name }}\"\n}","sendBody":true,"sendHeaders":true,"specifyBody":"json"},"typeVersion":4.2},{"id":"041a8843-5462-4ff0-ae89-d7985463618a","name":"Is Video Ready?","type":"n8n-nodes-base.if","position":[-5104,-320],"parameters":{"options":{},"conditions":{"options":{"version":1,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"5b5ff11d-4bb6-4955-9d99-b1e9e27666ea","operator":{"type":"boolean","operation":"true","singleValue":true},"leftValue":"={{ $json.done }}","rightValue":""}]}},"typeVersion":2},{"id":"4127d7d4-340d-4228-9ace-b287b6bb0526","name":"Convert Video to File","type":"n8n-nodes-base.convertToFile","position":[-4880,-416],"parameters":{"options":{},"operation":"toBinary","sourceProperty":"response.videos[0].bytesBase64Encoded"},"typeVersion":1.1},{"id":"82f5dcb0-62e4-43b7-9148-7bce5074361e","name":"Continue Polling","type":"n8n-nodes-base.code","position":[-4880,-224],"parameters":{"jsCode":"const item = $input.item.json;\nconst pollCount = (item.poll_count || 0) + 1;\n\nif (pollCount > 40) {\n return {\n error: 'Video generation timeout',\n status: 'timeout',\n poll_count: pollCount,\n chatId: item.chatId,\n messageId: item.messageId\n };\n}\n\nreturn {\n operation_name: item.name || item.operation_name,\n poll_count: pollCount,\n status: 'polling',\n chatId: item.chatId,\n messageId: item.messageId,\n caption: item.caption\n};"},"typeVersion":2},{"id":"6b7f1886-adda-4211-a4ff-a0b16e4bb131","name":"Send Video to User","type":"n8n-nodes-base.telegram","position":[-4224,-320],"webhookId":"74e11481-5b2a-4977-83dc-b438449eb577","parameters":{"chatId":"={{ $('Telegram Trigger').item.json.message.chat.id }}","operation":"sendVideo","binaryData":true,"additionalFields":{}},"credentials":{"telegramApi":{"id":"MZYDXGRSvEPr584A","name":"BytezTechSopingBot"}},"typeVersion":1.1},{"id":"8fb2a208-a649-4182-83d1-87d715e946a2","name":"2. Build JWT from Sheet","type":"n8n-nodes-base.code","position":[-7792,-48],"parameters":{"jsCode":"const item = $input.item.json;\nconst crypto = require('crypto');\n\nconst SERVICE_ACCOUNT_EMAIL = item.client_email;\nconst PRIVATE_KEY = item.private_key.replace(/\\\\n/g, '\\n');\nconst PROJECT_ID = item.project_id;\nconst SCOPE = item.scope || 'https://www.googleapis.com/auth/cloud-platform';\nconst TOKEN_URI = 'https://oauth2.googleapis.com/token';\nconst now = Math.floor(Date.now() / 1000);\n\nfunction base64url(input) {\n const buf = Buffer.isBuffer(input) ? input : Buffer.from(JSON.stringify(input));\n return buf.toString('base64').replace(/=/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n}\n\ntry {\n const header = base64url({ alg: 'RS256', typ: 'JWT' });\n const claims = base64url({ iss: SERVICE_ACCOUNT_EMAIL, scope: SCOPE, aud: TOKEN_URI, iat: now, exp: now + 3600 });\n const signingInput = `${header}.${claims}`;\n const sign = crypto.createSign('RSA-SHA256');\n sign.update(signingInput);\n sign.end();\n const signature = sign.sign(PRIVATE_KEY).toString('base64').replace(/=/g, '').replace(/\\+/g, '-').replace(/\\//g, '_');\n\n return [{ json: { ...item, jwt: `${signingInput}.${signature}`, project_id: PROJECT_ID, client_email: SERVICE_ACCOUNT_EMAIL, error: false } }];\n} catch (err) {\n return [{ json: { ...item, error: true, errorMessage: `āŒ JWT Build Failed: ${err.message}` } }];\n}"},"typeVersion":2},{"id":"faa1f906-1f3a-4b08-8fc1-fe117ea31a51","name":"3. Get Access Token","type":"n8n-nodes-base.httpRequest","position":[-7568,-48],"parameters":{"url":"https://oauth2.googleapis.com/token","method":"POST","options":{},"sendBody":true,"contentType":"form-urlencoded","bodyParameters":{"parameters":[{"name":"grant_type","value":"urn:ietf:params:oauth:grant-type:jwt-bearer"},{"name":"assertion","value":"={{ $json.jwt }}"}]}},"typeVersion":4.2},{"id":"563b037e-91b8-4348-a994-abfa27fba9ec","name":"1. Get Service Account Details","type":"n8n-nodes-base.googleSheets","position":[-8016,-48],"parameters":{"options":{},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=0","cachedResultName":"Sheet1"},"documentId":{"__rl":true,"mode":"list","value":"YOUR_GOOGLE_SHEET_ID_HERE","cachedResultUrl":"https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit","cachedResultName":"Service Account Credentials"}},"credentials":{"googleSheetsOAuth2Api":{"id":"c9xJX6hTzeLptu1d","name":"Jignesh Sheet"}},"typeVersion":4.7}],"pinData":{},"connections":{"Check Timeout":{"main":[[{"node":"Send Timeout Error","type":"main","index":0}],[{"node":"Wait 2 Minutes","type":"main","index":0}]]},"Conversion OK?":{"main":[[{"node":"5. Prepare Veo Request","type":"main","index":0}],[{"node":"Send Conversion Error","type":"main","index":0}]]},"Download Image":{"main":[[{"node":"Convert Image to Base64","type":"main","index":0}]]},"Wait 2 Minutes":{"main":[[{"node":"Poll Video Status","type":"main","index":0}]]},"Is Video Ready?":{"main":[[{"node":"Convert Video to File","type":"main","index":0}],[{"node":"Continue Polling","type":"main","index":0}]]},"Continue Polling":{"main":[[{"node":"Check Timeout","type":"main","index":0}]]},"Telegram Trigger":{"main":[[{"node":"2. Validate Input","type":"main","index":0}]]},"2. Validate Input":{"main":[[{"node":"1. Get Service Account Details","type":"main","index":0}]]},"Poll Video Status":{"main":[[{"node":"Is Video Ready?","type":"main","index":0}]]},"3. Get Access Token":{"main":[[{"node":"Check Auth Token Valid","type":"main","index":0}]]},"Convert Video to File":{"main":[[{"node":"Send Video to User","type":"main","index":0}]]},"5. Prepare Veo Request":{"main":[[{"node":"6. Call Vertex AI Veo 3","type":"main","index":0}]]},"Check Auth Token Valid":{"main":[[{"node":"Send Processing Message","type":"main","index":0}],[{"node":"Send Validation Error","type":"main","index":0}]]},"Extract Operation Name":{"main":[[{"node":"Wait 2 Minutes","type":"main","index":0}]]},"2. Build JWT from Sheet":{"main":[[{"node":"3. Get Access Token","type":"main","index":0}]]},"6. Call Vertex AI Veo 3":{"main":[[{"node":"Extract Operation Name","type":"main","index":0}]]},"Convert Image to Base64":{"main":[[{"node":"Conversion OK?","type":"main","index":0}]]},"Send Processing Message":{"main":[[{"node":"Download Image","type":"main","index":0}]]},"1. Get Service Account Details":{"main":[[{"node":"2. Build JWT from Sheet","type":"main","index":0}]]}}} \ No newline at end of file