In response to Upload Videos to Gemini to Analyze

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(‘:rocket: 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(‘:hourglass_not_done: 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(‘:microphone: 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(‘:clapper_board: 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 }];
});