Since the topic is closed. I will respond here.
To upload videos to Gemini, I ingest the video from a https request and pass the binary file to this script using a code node, you have to allow the following modules externally - Axios, Form-Data and @google/generative-ai.
Also add your Google API Key.
// n8n Code Node for Google Gemini Video Transcription
// Using correct GoogleGenerativeAI package structure
const { GoogleGenerativeAI } = require(‘@google/generative-ai’);
const fs = require(‘fs’);
const path = require(‘path’);
const os = require(‘os’);
const FormData = require(‘form-data’);
const axios = require(‘axios’);
// Configuration
const API_KEY = 'ADD API KEY;
const MAX_PROCESSING_ATTEMPTS = 60; // 2 minutes max wait
const PROCESSING_CHECK_INTERVAL = 2000; // 2 seconds
const SUPPORTED_MIME_TYPES = [
‘video/mp4’, ‘video/mpeg’, ‘video/mov’, ‘video/avi’,
‘video/x-flv’, ‘video/mpg’, ‘video/webm’, ‘video/wmv’,
‘video/3gpp’, ‘video/quicktime’
];
// Initialize Gemini AI
const genAI = new GoogleGenerativeAI(API_KEY);
class VideoTranscriptionError extends Error {
constructor(message, code = ‘TRANSCRIPTION_ERROR’, details = {}) {
super(message);
this.name = ‘VideoTranscriptionError’;
this.code = code;
this.details = details;
}
}
// Function to validate input
async function validateInput() {
const inputData = $input.first();
if (!inputData) {
throw new VideoTranscriptionError(‘No input data received’, ‘NO_INPUT’);
}
const binaryData = inputData.binary;
if (!binaryData || Object.keys(binaryData).length === 0) {
throw new VideoTranscriptionError(‘No binary data found in input’, ‘NO_BINARY_DATA’);
}
return binaryData;
}
function getBinaryFile(binaryData) {
// Get the first binary file or look for video-like files
const fileKeys = Object.keys(binaryData);
let selectedKey = fileKeys[0];
// Prefer video files if multiple files exist
for (const key of fileKeys) {
const file = binaryData[key];
if (file.mimeType && file.mimeType.startsWith(‘video/’)) {
selectedKey = key;
break;
}
}
const videoFile = binaryData[selectedKey];
if (!videoFile) {
throw new VideoTranscriptionError(‘Video file not found in binary data’, ‘NO_VIDEO_FILE’);
}
// Validate MIME type
if (!videoFile.mimeType) {
console.warn(‘No MIME type specified, assuming video/mp4’);
videoFile.mimeType = ‘video/mp4’;
}
if (!SUPPORTED_MIME_TYPES.includes(videoFile.mimeType)) {
console.warn(MIME type ${videoFile.mimeType} may not be supported. Supported types: ${SUPPORTED_MIME_TYPES.join(', ')}
);
}
return { file: videoFile, key: selectedKey };
}
function getFileExtension(mimeType) {
const mimeToExt = {
‘video/mp4’: ‘mp4’,
‘video/mpeg’: ‘mpeg’,
‘video/mov’: ‘mov’,
‘video/avi’: ‘avi’,
‘video/x-flv’: ‘flv’,
‘video/mpg’: ‘mpg’,
‘video/webm’: ‘webm’,
‘video/wmv’: ‘wmv’,
‘video/3gpp’: ‘3gp’,
‘video/quicktime’: ‘mov’
};
return mimeToExt[mimeType] || ‘mp4’;
}
async function createTempFile(videoFile) {
const tempDir = os.tmpdir();
const timestamp = Date.now();
const extension = getFileExtension(videoFile.mimeType);
const originalName = videoFile.fileName || video_${timestamp}
;
const safeName = originalName.replace(/[^a-zA-Z0-9.-]/g, ‘_’);
const tempFileName = n8n_gemini_${timestamp}_${safeName}.${extension}
;
const tempFilePath = path.join(tempDir, tempFileName);
try {
// Convert base64 to buffer and write to file
const buffer = Buffer.from(videoFile.data, ‘base64’);
fs.writeFileSync(tempFilePath, buffer);
// Verify file was written correctly
const stats = fs.statSync(tempFilePath);
if (stats.size === 0) {
throw new Error('Written file is empty');
}
console.log(`✓ Created temporary file: ${tempFileName}`);
console.log(`✓ File size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
return {
path: tempFilePath,
name: tempFileName,
size: stats.size
};
} catch (error) {
// Clean up if file creation failed
try {
if (fs.existsSync(tempFilePath)) {
fs.unlinkSync(tempFilePath);
}
} catch (cleanupError) {
console.warn(‘Failed to cleanup temporary file:’, cleanupError.message);
}
throw new VideoTranscriptionError(
`Failed to create temporary file: ${error.message}`,
'TEMP_FILE_ERROR',
{ originalError: error.message }
);
}
}
async function uploadFileToGemini(tempFilePath, videoFile) {
try {
console.log(‘ Uploading file to Gemini using Files API…’);
// Get file stats
const stats = fs.statSync(tempFilePath);
console.log(`📁 File size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
// Create form data
const form = new FormData();
// Add metadata
const metadata = {
file: {
displayName: videoFile.fileName || path.basename(tempFilePath)
}
};
form.append('metadata', JSON.stringify(metadata), {
contentType: 'application/json'
});
// Add file
form.append('file', fs.createReadStream(tempFilePath), {
filename: videoFile.fileName || 'video.mp4',
contentType: videoFile.mimeType
});
console.log(`📤 Uploading with axios and form-data...`);
// Upload using axios
const uploadUrl = `https://generativelanguage.googleapis.com/upload/v1beta/files?key=${API_KEY}`;
const response = await axios.post(uploadUrl, form, {
headers: {
'X-Goog-Upload-Protocol': 'multipart',
...form.getHeaders()
},
maxContentLength: Infinity,
maxBodyLength: Infinity
});
console.log(`✓ File uploaded successfully`);
console.log(`✓ File URI: ${response.data.file.uri}`);
console.log(`✓ File Name: ${response.data.file.name}`);
return response.data.file;
} catch (error) {
const errorMessage = error.response
? Upload failed: ${error.response.status} - ${JSON.stringify(error.response.data)}
: Upload failed: ${error.message}
;
throw new VideoTranscriptionError(
errorMessage,
'UPLOAD_FAILED',
{
originalError: error.message,
status: error.response?.status,
data: error.response?.data
}
);
}
}
async function waitForFileProcessing(fileName) {
console.log(‘ Waiting for file processing…’);
let attempts = 0;
while (attempts < MAX_PROCESSING_ATTEMPTS) {
try {
// Get file status using axios
const statusUrl = https://generativelanguage.googleapis.com/v1beta/${fileName}?key=${API_KEY}
;
const response = await axios.get(statusUrl, {
headers: {
'Content-Type': 'application/json'
}
});
const file = response.data;
console.log(`📊 Processing status: ${file.state} (attempt ${attempts + 1}/${MAX_PROCESSING_ATTEMPTS})`);
if (file.state === 'ACTIVE') {
console.log('✓ File processing completed successfully');
return file;
}
if (file.state === 'FAILED') {
throw new VideoTranscriptionError(
'File processing failed on Gemini servers',
'PROCESSING_FAILED',
{ fileState: file.state }
);
}
if (file.state === 'PROCESSING') {
await new Promise(resolve => setTimeout(resolve, PROCESSING_CHECK_INTERVAL));
attempts++;
} else {
throw new VideoTranscriptionError(
`Unexpected file state: ${file.state}`,
'UNEXPECTED_STATE',
{ fileState: file.state }
);
}
} catch (error) {
if (error instanceof VideoTranscriptionError) {
throw error;
}
const errorMessage = error.response
? `Status check failed: ${error.response.status} - ${JSON.stringify(error.response.data)}`
: `Error checking file status: ${error.message}`;
throw new VideoTranscriptionError(
errorMessage,
'STATUS_CHECK_FAILED',
{
originalError: error.message,
attempt: attempts + 1,
status: error.response?.status,
data: error.response?.data
}
);
}
}
throw new VideoTranscriptionError(
File processing timeout after ${MAX_PROCESSING_ATTEMPTS * PROCESSING_CHECK_INTERVAL / 1000} seconds
,
‘PROCESSING_TIMEOUT’
);
}
async function generateTranscription(file) {
try {
console.log(‘ Generating transcription…’);
const model = genAI.getGenerativeModel({ model: 'gemini-2.5-flash-preview-05-20' });
const prompt = `Please provide a detailed and accurate transcription of this video.
Instructions:
- Include all spoken words and dialogue
- Format the output clearly and readably
- If there are multiple languages, identify them
- If audio quality is poor, note any uncertainties with [unclear] or [inaudible]
Please provide the transcription in a clean, organized format.`;
const result = await model.generateContent([
{
fileData: {
mimeType: file.mimeType,
fileUri: file.uri
}
},
{ text: prompt }
]);
const response = await result.response;
const transcription = response.text();
if (!transcription || transcription.trim().length === 0) {
throw new Error('Empty transcription returned');
}
console.log(`✓ Transcription generated successfully (${transcription.length} characters)`);
return transcription;
} catch (error) {
throw new VideoTranscriptionError(
Transcription generation failed: ${error.message}
,
‘TRANSCRIPTION_FAILED’,
{ originalError: error.message }
);
}
}
function cleanupTempFile(tempFilePath) {
try {
if (fs.existsSync(tempFilePath)) {
fs.unlinkSync(tempFilePath);
console.log(‘✓ Temporary file cleaned up’);
}
} catch (error) {
console.warn(⚠️ Failed to cleanup temporary file: ${error.message}
);
}
}
async function deleteGeminiFile(fileName) {
try {
const deleteUrl = https://generativelanguage.googleapis.com/v1beta/${fileName}?key=${API_KEY}
;
const response = await axios.delete(deleteUrl);
if (response.status >= 200 && response.status < 300) {
console.log('✓ Gemini file cleaned up');
} else {
console.warn(`⚠️ Failed to cleanup Gemini file: ${response.status}`);
}
} catch (error) {
console.warn(⚠️ Failed to cleanup Gemini file: ${error.message}
);
}
}
// Main execution function
async function processVideoTranscription() {
const startTime = Date.now();
let tempFile = null;
let uploadedFile = null;
try {
console.log(‘ Starting video transcription process…’);
// Step 1: Validate input and get binary data
const binaryData = await validateInput();
const { file: videoFile, key: fileKey } = getBinaryFile(binaryData);
console.log(`📹 Processing video: ${videoFile.fileName || 'unnamed'}`);
console.log(`📄 MIME type: ${videoFile.mimeType}`);
console.log(`🔑 Binary key: ${fileKey}`);
// Step 2: Create temporary file
tempFile = await createTempFile(videoFile);
// Step 3: Upload to Gemini
uploadedFile = await uploadFileToGemini(tempFile.path, videoFile);
// Step 4: Wait for processing
const processedFile = await waitForFileProcessing(uploadedFile.name);
// Step 5: Generate transcription
const transcription = await generateTranscription(processedFile);
const processingTime = Date.now() - startTime;
console.log(`✅ Process completed in ${(processingTime / 1000).toFixed(2)} seconds`);
return {
success: true,
transcription: transcription,
metadata: {
originalFileName: videoFile.fileName,
mimeType: videoFile.mimeType,
fileSize: tempFile.size,
processingTimeMs: processingTime,
processingTimeSeconds: Math.round(processingTime / 1000),
geminiFileInfo: {
name: processedFile.name,
uri: processedFile.uri,
displayName: processedFile.displayName,
sizeBytes: processedFile.sizeBytes
}
},
timestamp: new Date().toISOString()
};
} catch (error) {
const processingTime = Date.now() - startTime;
console.error('❌ Transcription process failed:', error.message);
return {
success: false,
error: {
message: error.message,
code: error.code || 'UNKNOWN_ERROR',
details: error.details || {},
processingTimeMs: processingTime
},
timestamp: new Date().toISOString()
};
} finally {
// Cleanup temporary file
if (tempFile) {
cleanupTempFile(tempFile.path);
}
// Optionally cleanup Gemini file (uncomment if desired)
// if (uploadedFile) {
// await deleteGeminiFile(uploadedFile.name);
// }
}
}
// Execute the main process
return processVideoTranscription().then(result => {
// Return result to n8n
return [{ json: result }];
});