
Ai Multimodal
- 19 installs
- 95 repo stars
- Updated July 7, 2026
- binjuhor/shadcn-lar
AI Multimodal is a Claude skill that processes and generates audio, image, video, and document content through the Google Gemini API.
About
This skill processes and generates multimedia content through the Google Gemini API. A developer uses it to transcribe audio, analyze images and screenshots, process videos and PDF documents, and generate images with Imagen 4 or videos with Veo 3 from text prompts. It bundles Python CLI scripts (gemini_batch_process.py, media_optimizer.py, document_converter.py) that resolve API keys and handle inline vs File API uploads.
- Analyzes audio, images, video, and PDFs with the Google Gemini API
- Generates images with Imagen 4 and 8-second video clips with Veo 3
- Bundled Python CLI scripts for batch media processing and setup checks
Ai Multimodal by the numbers
- 19 all-time installs (skills.sh)
- Ranked #1,003 of 1,337 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
ai-multimodal capabilities & compatibility
Requires a Gemini API key; usage is billed per Gemini/Imagen/Veo pricing.
- Capabilities
- image generation · video generation · transcription · pdf parsing
- Works with
- openai
- Use cases
- image generation · video generation · transcription · pdf parsing
- Pricing
- Bring your own API key
What ai-multimodal says it does
Process and generate multimedia content using Google Gemini API for better vision capabilities.
generate videos (text-to-video with Veo 3, 8-second clips with native audio)
Get from https://aistudio.google.com/apikey
npx skills add https://github.com/binjuhor/shadcn-lar --skill ai-multimodalAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 95 |
| Last updated | July 7, 2026 |
| Repository | binjuhor/shadcn-lar ↗ |
What it does
Analyze audio, images, video and PDFs with Gemini and generate images or videos from text prompts.
Who is it for?
Adding multimodal analysis and image or video generation to an app via the Gemini API.
Skip if: Text-only generation or working without a Gemini API key.
When should I use this skill?
Working with audio/video files, analyzing images or screenshots, processing PDFs, or generating images/videos from text.
What you get
Media is analyzed or generated through Gemini, Imagen 4, and Veo 3 with structured outputs saved.
- transcriptions
- structured media analysis
- generated images
By the numbers
- 4 bundled Python scripts
- audio up to 9.5 hours
- video up to 6 hours
Files
AI Multimodal
Process audio, images, videos, documents, and generate images/videos using Google Gemini's multimodal API.
Setup
export GEMINI_API_KEY="your-key" # Get from https://aistudio.google.com/apikey
pip install google-genai python-dotenv pillowQuick Start
Verify setup: python scripts/check_setup.py Analyze media: python scripts/gemini_batch_process.py --files <file> --task <analyze|transcribe|extract>
- TIP: When you're asked to analyze an image, check if
geminicommand is available, then use"<prompt to analyze image>" | gemini -y -m gemini-2.5-flashcommand. Ifgeminicommand is not available, usepython scripts/gemini_batch_process.py --files <file> --task analyzecommand.
Generate content: python scripts/gemini_batch_process.py --task <generate|generate-video> --prompt "description"
Stdin support: You can pipe files directly via stdin (auto-detects PNG/JPG/PDF/WAV/MP3).
- cat image.png | python scripts/gemini_batch_process.py --task analyze --prompt "Describe this"- python scripts/gemini_batch_process.py --files image.png --task analyze (traditional)Models
- Image generation:
imagen-4.0-generate-001(standard),imagen-4.0-ultra-generate-001(quality),imagen-4.0-fast-generate-001(speed) - Video generation:
veo-3.1-generate-preview(8s clips with audio) - Analysis:
gemini-2.5-flash(recommended),gemini-2.5-pro(advanced)
Scripts
- `gemini_batch_process.py`: CLI orchestrator for
transcribe|analyze|extract|generate|generate-videothat auto-resolves API keys, picks sensible default models per task, streams files inline vs File API, and saves structured outputs (text/JSON/CSV/markdown plus generated assets) for Imagen 4 + Veo workflows. - `media_optimizer.py`: ffmpeg/Pillow-based preflight tool that compresses/resizes/converts audio, image, and video inputs, enforces target sizes/bitrates, splits long clips into hour chunks, and batch-processes directories so media stays within Gemini limits.
- `document_converter.py`: Gemini-powered converter that uploads PDFs/images/Office docs, applies a markdown-preserving prompt, batches multiple files, auto-names outputs under
docs/assets, and exposes CLI flags for model, prompt, auto-file naming, and verbose logging. - `check_setup.py`: Interactive readiness checker that verifies directory layout, centralized env resolver, required Python deps, and GEMINI_API_KEY availability/format, then performs a live Gemini API call and prints remediation instructions if anything fails.
Use --help for options.
References
Load for detailed guidance:
| Topic | File | Description |
|---|---|---|
| Audio | references/audio-processing.md | Audio formats and limits, transcription (timestamps, speakers, segments), non-speech analysis, File API vs inline input, TTS models, best practices, cost and token math, and concrete meeting/podcast/interview recipes. |
| Images | references/vision-understanding.md | Vision capabilities overview, supported formats and models, captioning/classification/VQA, detection and segmentation, OCR and document reading, multi-image workflows, structured JSON output, token costs, best practices, and common product/screenshot/chart/scene use cases. |
| Image Gen | references/image-generation.md | Imagen 4 and Gemini image model overview, generate_images vs generate_content APIs, aspect ratios and costs, text/image/both modalities, editing and composition, style and quality control, safety settings, best practices, troubleshooting, and common marketing/concept-art/UI scenarios. |
| Video | references/video-analysis.md | Video analysis capabilities and supported formats, model/context choices, local/inline/YouTube inputs, clipping and FPS control, multi-video comparison, temporal Q&A and scene detection, transcription with visual context, token and cost guidance, and optimization/best-practice patterns. |
| Video Gen | references/video-generation.md | Veo model matrix, text-to-video and image-to-video quick start, multi-reference and extension flows, camera and timing control, configuration (resolution, aspect, audio, safety), prompt design patterns, performance tips, limitations, troubleshooting, and cost estimates. |
Limits
Formats: Audio (WAV/MP3/AAC, 9.5h), Images (PNG/JPEG/WEBP, 3.6k), Video (MP4/MOV, 6h), PDF (1k pages) Size: 20MB inline, 2GB File API
Resources
# Google Gemini API Configuration
# ============================================================================
# OPTION 1: Google AI Studio (Default - Recommended for most users)
# ============================================================================
# Get your API key: https://aistudio.google.com/apikey
GEMINI_API_KEY=your_api_key_here
# ============================================================================
# OPTION 2: Vertex AI (Google Cloud Platform)
# ============================================================================
# Uncomment these lines to use Vertex AI instead of Google AI Studio
# GEMINI_USE_VERTEX=true
# VERTEX_PROJECT_ID=your-gcp-project-id
# VERTEX_LOCATION=us-central1
# ============================================================================
# Model Selection (Optional)
# ============================================================================
# Override default models for specific capabilities
# If not set, intelligent defaults are used based on task type
# --- Image Generation ---
# Used by: --task generate (image)
# Default: imagen-4.0-generate-001 (auto-fallback to cheaper model if billing fails)
# NOTE: All image generation requires billing - no free tier available (limit: 0)
# Options:
# imagen-4.0-generate-001 - Standard quality, balanced (~$0.02/image, requires billing)
# imagen-4.0-ultra-generate-001 - Maximum quality (~$0.04/image, requires billing)
# imagen-4.0-fast-generate-001 - Fastest generation (~$0.01/image, requires billing)
# gemini-3-pro-image-preview - Pro quality with editing (requires billing)
# gemini-2.5-flash-image - Cheaper fallback (~$0.039/image, requires billing)
# gemini-2.5-flash-image-preview - Preview variant (requires billing)
# Fallback chain: Imagen 4 -> gemini-2.5-flash-image (if Imagen fails)
# IMAGE_GEN_MODEL=imagen-4.0-generate-001
# --- Video Generation ---
# Used by: --task generate-video (new capability)
# Default: veo-3.1-generate-preview
# NOTE: Video generation requires billing - no free tier fallback available
# Options:
# veo-3.1-generate-preview - Latest, native audio, frame control (requires billing)
# veo-3.1-fast-generate-preview - Speed-optimized for business (requires billing)
# veo-3.0-generate-001 - Stable, native audio, 8s videos (requires billing)
# veo-3.0-fast-generate-001 - Stable fast variant (requires billing)
# VIDEO_GEN_MODEL=veo-3.1-generate-preview
# --- Multimodal Analysis ---
# Used by: --task analyze, transcribe, extract
# Default: gemini-2.5-flash
# Options:
# gemini-3-pro-preview - Latest, agentic workflows, 1M context
# gemini-2.5-flash - Best price/performance (recommended)
# gemini-2.5-pro - Highest quality
# MULTIMODAL_MODEL=gemini-2.5-flash
# --- Legacy Compatibility ---
# Generic model override (use specific variables above instead)
# GEMINI_MODEL=gemini-2.5-flash
# GEMINI_IMAGE_GEN_MODEL=gemini-2.5-flash-image
# ============================================================================
# Rate Limiting Configuration (Optional)
# ============================================================================
# Requests per minute limit (adjust based on your tier)
# GEMINI_RPM_LIMIT=15
# Tokens per minute limit
# GEMINI_TPM_LIMIT=4000000
# Requests per day limit
# GEMINI_RPD_LIMIT=1500
# ============================================================================
# Video Generation Options (Optional)
# ============================================================================
# Video duration in seconds (8s only for now)
# VEO_DURATION=8
# Video resolution: 720p or 1080p
# VEO_RESOLUTION=1080p
# Aspect ratio: 16:9, 9:16, 1:1 (16:9 is default)
# VEO_ASPECT_RATIO=16:9
# Frame rate: 24fps (fixed for now)
# VEO_FPS=24
# Enable native audio generation
# VEO_AUDIO=true
# ============================================================================
# Image Generation Options (Optional)
# ============================================================================
# Number of images to generate (1-4)
# IMAGEN_NUM_IMAGES=1
# Image size: 1K or 2K (Ultra/Standard only)
# IMAGEN_SIZE=1K
# Aspect ratio: 1:1, 16:9, 9:16, 4:3, 3:4
# IMAGEN_ASPECT_RATIO=1:1
# Enable person generation (restricted in EEA, CH, UK)
# IMAGEN_PERSON_GENERATION=true
# Add SynthID watermark (always enabled by default)
# IMAGEN_WATERMARK=true
# ============================================================================
# Processing Options (Optional)
# ============================================================================
# Video resolution mode: default or low-res
# low-res uses ~100 tokens/second vs ~300 for default
# GEMINI_VIDEO_RESOLUTION=default
# Audio quality: default (16 Kbps mono, auto-downsampled)
# GEMINI_AUDIO_QUALITY=default
# PDF processing mode: inline (<20MB) or file-api (>20MB, automatic)
# GEMINI_PDF_MODE=auto
# ============================================================================
# Retry Configuration (Optional)
# ============================================================================
# Maximum retry attempts for failed requests
# GEMINI_MAX_RETRIES=3
# Initial retry delay in seconds (uses exponential backoff)
# GEMINI_RETRY_DELAY=1
# ============================================================================
# Output Configuration (Optional)
# ============================================================================
# Default output directory for generated images
# OUTPUT_DIR=./output
# Image output format (png or jpeg)
# IMAGE_FORMAT=png
# Image quality for JPEG (1-100)
# IMAGE_QUALITY=95
# ============================================================================
# Context Caching (Optional)
# ============================================================================
# Enable context caching for repeated queries on same file
# GEMINI_ENABLE_CACHING=true
# Cache TTL in seconds (default: 1800 = 30 minutes)
# GEMINI_CACHE_TTL=1800
# ============================================================================
# Logging (Optional)
# ============================================================================
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
# LOG_LEVEL=INFO
# Log file path
# LOG_FILE=./logs/gemini.log
# ============================================================================
# Pricing Reference (as of 2025-11)
# ============================================================================
# Gemini 2.5 Flash: $1.00/1M input, $0.10/1M output
# Gemini 2.5 Pro: $3.00/1M input, $12.00/1M output
# Gemini 3 Pro: $2.00/1M input (<200k), $4.00 (>200k), $12/$18 output
# Imagen 4: ~$0.01-$0.04 per image (varies by variant)
# Veo 3: TBD (preview pricing)
# Monitor: https://ai.google.dev/pricing
# ============================================================================
# Notes
# ============================================================================
# 1. Never commit API keys to version control
# 2. Add .env to .gitignore
# 3. API keys can be restricted in Google Cloud Console
# 4. Monitor usage at: https://aistudio.google.com/apikey
# 5. Free tier limits: 15 RPM, 1M-4M TPM, 1,500 RPD
# 6. Vertex AI requires GCP authentication via gcloud CLI
# 7. New model defaults (Nov 2025):
# - Image gen: Imagen 4 (was Flash Image)
# - Video gen: Veo 3.1 (new capability)
# - Analysis: Gemini 2.5 Flash (unchanged)
# 8. Preview models (veo-3.1, gemini-3) may have API changes
Audio Processing Reference
Comprehensive guide for audio analysis and speech generation using Gemini API.
Audio Understanding
Supported Formats
| Format | MIME Type | Best Use |
|---|---|---|
| WAV | audio/wav | Uncompressed, highest quality |
| MP3 | audio/mp3 | Compressed, widely compatible |
| AAC | audio/aac | Compressed, good quality |
| FLAC | audio/flac | Lossless compression |
| OGG Vorbis | audio/ogg | Open format |
| AIFF | audio/aiff | Apple format |
Specifications
- Maximum length: 9.5 hours per request
- Multiple files: Unlimited count, combined max 9.5 hours
- Token rate: 32 tokens/second (1 minute = 1,920 tokens)
- Processing: Auto-downsampled to 16 Kbps mono
- File size limits:
- Inline: 20 MB max total request
- File API: 2 GB per file, 20 GB project quota
- Retention: 48 hours auto-delete
Transcription
Basic Transcription
from google import genai
import os
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
# Upload audio
myfile = client.files.upload(file='meeting.mp3')
# Transcribe
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Generate a transcript of the speech.', myfile]
)
print(response.text)With Timestamps
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Generate transcript with timestamps in MM:SS format.', myfile]
)Multi-Speaker Identification
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Transcribe with speaker labels. Format: [Speaker 1], [Speaker 2], etc.', myfile]
)Segment-Specific Transcription
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Transcribe only the segment from 02:30 to 05:15.', myfile]
)Audio Analysis
Summarization
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Summarize key points in 5 bullets with timestamps.', myfile]
)Non-Speech Audio Analysis
# Music analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Identify the musical instruments and genre.', myfile]
)
# Environmental sounds
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Identify all sounds: voices, music, ambient noise.', myfile]
)
# Birdsong identification
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Identify bird species based on their calls.', myfile]
)Timestamp-Based Analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['What is discussed from 10:30 to 15:45? Provide key points.', myfile]
)Input Methods
File Upload (>20MB or Reuse)
# Upload once, use multiple times
myfile = client.files.upload(file='large-audio.mp3')
# First query
response1 = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Transcribe this', myfile]
)
# Second query (reuses same file)
response2 = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Summarize this', myfile]
)Inline Data (<20MB)
from google.genai import types
with open('small-audio.mp3', 'rb') as f:
audio_bytes = f.read()
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Describe this audio',
types.Part.from_bytes(data=audio_bytes, mime_type='audio/mp3')
]
)Speech Generation (TTS)
Available Models
| Model | Quality | Speed | Cost/1M tokens |
|---|---|---|---|
gemini-2.5-flash-native-audio-preview-09-2025 | High | Fast | $10 |
gemini-2.5-pro TTS mode | Premium | Slower | $20 |
Basic TTS
response = client.models.generate_content(
model='gemini-2.5-flash-native-audio-preview-09-2025',
contents='Generate audio: Welcome to today\'s episode.'
)
# Save audio
with open('output.wav', 'wb') as f:
f.write(response.audio_data)Controllable Voice Style
# Professional tone
response = client.models.generate_content(
model='gemini-2.5-flash-native-audio-preview-09-2025',
contents='Generate audio in a professional, clear tone: Welcome to our quarterly earnings call.'
)
# Casual and friendly
response = client.models.generate_content(
model='gemini-2.5-flash-native-audio-preview-09-2025',
contents='Generate audio in a friendly, conversational tone: Hey there! Let\'s dive into today\'s topic.'
)
# Narrative style
response = client.models.generate_content(
model='gemini-2.5-flash-native-audio-preview-09-2025',
contents='Generate audio in a narrative, storytelling tone: Once upon a time, in a land far away...'
)Voice Control Parameters
- Style: Professional, casual, narrative, conversational
- Pace: Slow, normal, fast
- Tone: Friendly, serious, enthusiastic
- Accent: Natural language control (e.g., "British accent", "Southern drawl")
Best Practices
File Management
1. Use File API for files >20MB 2. Use File API for repeated queries (saves tokens) 3. Files auto-delete after 48 hours 4. Clean up manually when done:
client.files.delete(name=myfile.name)Prompt Engineering
Effective prompts:
- "Transcribe from 02:30 to 03:29 in MM:SS format"
- "Identify speakers and extract dialogue with timestamps"
- "Summarize key points with relevant timestamps"
- "Transcribe and analyze sentiment for each speaker"
Context improves accuracy:
- "This is a medical interview - use appropriate terminology"
- "Transcribe this legal deposition with precise terminology"
- "This is a technical podcast about machine learning"
Combined tasks:
- "Transcribe and summarize in bullet points"
- "Extract key quotes with timestamps and speaker labels"
- "Transcribe and identify action items with timestamps"
Cost Optimization
Token calculation:
- 1 minute audio = 1,920 tokens
- 1 hour audio = 115,200 tokens
- 9.5 hours = 1,094,400 tokens
Model selection:
- Use
gemini-2.5-flash($1/1M tokens) for most tasks - Upgrade to
gemini-2.5-pro($3/1M tokens) for complex analysis - For high-volume:
gemini-1.5-flash($0.70/1M tokens)
Reduce costs:
- Process only relevant segments using timestamps
- Use lower-quality audio when possible
- Batch multiple short files in one request
- Cache context for repeated queries
Error Handling
import time
def transcribe_with_retry(file_path, max_retries=3):
"""Transcribe audio with exponential backoff retry"""
for attempt in range(max_retries):
try:
myfile = client.files.upload(file=file_path)
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Transcribe with timestamps', myfile]
)
return response.text
except Exception as e:
if attempt == max_retries - 1:
raise
wait_time = 2 ** attempt
print(f"Retry {attempt + 1} after {wait_time}s")
time.sleep(wait_time)Common Use Cases
1. Meeting Transcription
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Transcribe this meeting with:
1. Speaker labels
2. Timestamps for topic changes
3. Action items highlighted
''',
myfile
]
)2. Podcast Summary
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Create podcast summary with:
1. Main topics with timestamps
2. Key quotes from each speaker
3. Recommended episode highlights
''',
myfile
]
)3. Interview Analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Analyze interview:
1. Questions asked with timestamps
2. Key responses from interviewee
3. Overall sentiment and tone
''',
myfile
]
)4. Content Verification
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Verify audio content:
1. Check for specific keywords or phrases
2. Identify any compliance issues
3. Note any concerning statements with timestamps
''',
myfile
]
)5. Multilingual Transcription
# Gemini auto-detects language
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Transcribe this audio and translate to English if needed.', myfile]
)Token Costs
Audio Input (32 tokens/second):
- 1 minute = 1,920 tokens
- 10 minutes = 19,200 tokens
- 1 hour = 115,200 tokens
- 9.5 hours = 1,094,400 tokens
Example costs (Gemini 2.5 Flash at $1/1M):
- 1 hour audio: 115,200 tokens = $0.12
- Full day podcast (8 hours): 921,600 tokens = $0.92
Limitations
- Maximum 9.5 hours per request
- Auto-downsampled to 16 Kbps mono (quality loss)
- Files expire after 48 hours
- No real-time streaming support
- Non-speech audio less accurate than speech
---
Related References
Current: Audio Processing
Related Capabilities:
- Video Analysis - Extract audio from videos
- Video Generation - Generate videos with native audio
- Image Understanding - Analyze audio with visual context
Back to: AI Multimodal Skill
Image Generation Reference
Comprehensive guide for image creation, editing, and composition using Imagen 4 and Gemini models.
Core Capabilities
- Text-to-Image: Generate images from text prompts
- Image Editing: Modify existing images with text instructions
- Multi-Image Composition: Combine up to 3 images
- Iterative Refinement: Refine images conversationally
- Aspect Ratios: Multiple formats (1:1, 16:9, 9:16, 4:3, 3:4)
- Quality Variants: Standard/Ultra/Fast for different needs
- Text in Images: Limited text rendering (varies by model)
Models
Imagen 4 (Recommended)
imagen-4.0-generate-001 - Standard quality, balanced performance
- Best for: General use, prototyping, iterative workflows
- Quality: High
- Speed: Medium (~5-10s per image)
- Cost: ~$0.02/image (estimated)
- Output: 1-4 images per request
- Resolution: 1K or 2K
- Updated: June 2025
imagen-4.0-ultra-generate-001 - Maximum quality
- Best for: Final production, marketing assets, detailed artwork
- Quality: Ultra (highest available)
- Speed: Slow (~15-25s per image)
- Cost: ~$0.04/image (estimated)
- Output: 1-4 images per request
- Resolution: 2K preferred
- Updated: June 2025
imagen-4.0-fast-generate-001 - Fastest generation
- Best for: Rapid iteration, bulk generation, real-time use
- Quality: Good
- Speed: Fast (~2-5s per image)
- Cost: ~$0.01/image (estimated)
- Output: 1-4 images per request
- Resolution: 1K
- Updated: June 2025
Gemini 3 Pro Image (Alternative)
gemini-3-pro-image-preview - Conversational image generation
- Best for: Iterative refinement with natural language editing
- Quality: High
- Context: 65k input / 32k output tokens
- Cost: $2/1M text input, $0.134/image output (resolution-dependent)
- Unique: Native 4K text rendering, grounded generation
- Updated: January 2025
Legacy Models
gemini-2.5-flash-image - Legacy image generation
- Status: Deprecated (use Imagen 4 instead)
- Still functional for backward compatibility
- Input: 65,536 tokens
- Output: 32,768 tokens
- Cost: $1/1M input
Model Comparison
| Model | Quality | Speed | Cost | Best For |
|---|---|---|---|---|
| imagen-4.0-ultra | ⭐⭐⭐⭐⭐ | 🐢 Slow | 💰💰 High | Production assets |
| imagen-4.0-standard | ⭐⭐⭐⭐ | ⚡ Medium | 💰 Medium | General use |
| imagen-4.0-fast | ⭐⭐⭐ | 🚀 Fast | 💵 Low | Rapid iteration |
| gemini-3-pro-image | ⭐⭐⭐⭐ | ⚡ Medium | 💰 Medium | Text rendering |
| gemini-2.5-flash-image | ⭐⭐⭐ | ⚡ Medium | 💵 Low | Legacy (deprecated) |
Selection Guide:
- Marketing/Production: Use
imagen-4.0-ultrafor final deliverables - General Development: Use
imagen-4.0-generate-001for balanced workflow - Prototyping/Iteration: Use
imagen-4.0-fastfor quick feedback - Text-Heavy Images: Use
gemini-3-pro-imagefor 4K text rendering
Quick Start
Basic Generation (Imagen 4)
from google import genai
from google.genai import types
import os
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
# Standard quality (recommended)
response = client.models.generate_images(
model='imagen-4.0-generate-001',
prompt='A serene mountain landscape at sunset with snow-capped peaks',
config=types.GenerateImagesConfig(
numberOfImages=1,
aspectRatio='16:9',
imageSize='1K'
)
)
# Save images
for i, generated_image in enumerate(response.generated_images):
with open(f'output-{i}.png', 'wb') as f:
f.write(generated_image.image.image_bytes)Quality Variants
# Ultra quality (production)
response = client.models.generate_images(
model='imagen-4.0-ultra-generate-001',
prompt='Professional product photography of smartphone',
config=types.GenerateImagesConfig(
numberOfImages=1,
imageSize='2K' # Use 2K for ultra (Standard/Ultra only)
)
)
# Fast generation (iteration)
# Note: Fast model doesn't support imageSize parameter
response = client.models.generate_images(
model='imagen-4.0-fast-generate-001',
prompt='Quick concept sketch of robot character',
config=types.GenerateImagesConfig(
numberOfImages=4, # Generate multiple variants (default: 4)
aspectRatio='1:1'
)
)Legacy Flash Image (Backward Compatibility)
# Still works but deprecated
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='A futuristic cityscape',
config=types.GenerateContentConfig(
response_modalities=['Image'],
image_config=types.ImageConfig(
aspect_ratio='16:9'
)
)
)
# Save from content parts
for i, part in enumerate(response.candidates[0].content.parts):
if part.inline_data:
with open(f'output-{i}.png', 'wb') as f:
f.write(part.inline_data.data)API Differences
Imagen 4 vs Flash Image
Imagen 4 uses generate_images():
response = client.models.generate_images(
model='imagen-4.0-generate-001',
prompt='...',
config=types.GenerateImagesConfig(
numberOfImages=1,
aspectRatio='16:9',
imageSize='1K' # Standard/Ultra only
)
)
# Access: response.generated_images[0].image.image_bytesFlash Image uses generate_content():
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='...',
config=types.GenerateContentConfig(
response_modalities=['Image'],
image_config=types.ImageConfig(...)
)
)
# Access: response.candidates[0].content.parts[0].inline_data.dataKey Differences: 1. Different method: generate_images() vs generate_content() 2. Different config: GenerateImagesConfig (camelCase params) vs GenerateContentConfig 3. Parameter names: prompt vs contents, numberOfImages (camelCase) vs number_of_images (snake_case) 4. Response structure: response.generated_images[i].image.image_bytes vs response.candidates[0].content.parts 5. Fast model limitation: No imageSize parameter support
Aspect Ratios
| Ratio | Resolution | Use Case | Token Cost |
|---|---|---|---|
| 1:1 | 1024×1024 | Social media, avatars | 1290 |
| 16:9 | 1344×768 | Landscapes, banners | 1290 |
| 9:16 | 768×1344 | Mobile, portraits | 1290 |
| 4:3 | 1152×896 | Traditional media | 1290 |
| 3:4 | 896×1152 | Vertical posters | 1290 |
All ratios cost the same: 1,290 tokens per image.
Response Modalities
Image Only
config = types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='1:1'
)Text Only (No Image)
config = types.GenerateContentConfig(
response_modalities=['text']
)
# Returns text description instead of generating imageBoth Image and Text
config = types.GenerateContentConfig(
response_modalities=['image', 'text'],
aspect_ratio='16:9'
)
# Returns both generated image and descriptionImage Editing
Modify Existing Image
import PIL.Image
# Load original
img = PIL.Image.open('original.png')
# Edit with instructions
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=[
'Add a red balloon floating in the sky',
img
],
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='16:9'
)
)Style Transfer
img = PIL.Image.open('photo.jpg')
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=[
'Transform this into an oil painting style',
img
]
)Object Addition/Removal
# Add object
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=[
'Add a vintage car parked on the street',
img
]
)
# Remove object
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=[
'Remove the person on the left side',
img
]
)Multi-Image Composition
Combine Multiple Images
img1 = PIL.Image.open('background.png')
img2 = PIL.Image.open('foreground.png')
img3 = PIL.Image.open('overlay.png')
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=[
'Combine these images into a cohesive scene',
img1,
img2,
img3
],
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='16:9'
)
)Note: Recommended maximum 3 input images for best results.
Prompt Engineering
Effective Prompt Structure
Three key elements: 1. Subject: What to generate 2. Context: Environmental setting 3. Style: Artistic treatment
Example: "A robot [subject] in a futuristic city [context], cyberpunk style with neon lighting [style]"
Quality Modifiers
Technical terms:
- "4K", "8K", "high resolution"
- "HDR", "high dynamic range"
- "professional photography"
- "studio lighting"
- "ultra detailed"
Camera settings:
- "35mm lens", "50mm lens"
- "shallow depth of field"
- "wide angle shot"
- "macro photography"
- "golden hour lighting"
Style Keywords
Art styles:
- "oil painting", "watercolor", "sketch"
- "digital art", "concept art"
- "photorealistic", "hyperrealistic"
- "minimalist", "abstract"
- "cyberpunk", "steampunk", "fantasy"
Mood and atmosphere:
- "dramatic lighting", "soft lighting"
- "moody", "bright and cheerful"
- "mysterious", "whimsical"
- "dark and gritty", "pastel colors"
Subject Description
Be specific:
- ❌ "A cat"
- ✅ "A fluffy orange tabby cat with green eyes"
Add context:
- ❌ "A building"
- ✅ "A modern glass skyscraper reflecting sunset clouds"
Include details:
- ❌ "A person"
- ✅ "A young woman in a red dress holding an umbrella"
Composition and Framing
Camera angles:
- "bird's eye view", "aerial shot"
- "low angle", "high angle"
- "close-up", "wide shot"
- "centered composition"
- "rule of thirds"
Perspective:
- "first person view"
- "third person perspective"
- "isometric view"
- "forced perspective"
Text in Images
Limitations:
- Maximum 25 characters total
- Up to 3 distinct text phrases
- Works best with simple text
Best practices:
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='A vintage poster with bold text "EXPLORE" at the top, mountain landscape, retro 1950s style'
)Font control:
- "bold sans-serif title"
- "handwritten script"
- "vintage letterpress"
- "modern minimalist font"
Advanced Techniques
Iterative Refinement
# Initial generation
response1 = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='A futuristic city skyline'
)
# Save first version
with open('v1.png', 'wb') as f:
f.write(response1.candidates[0].content.parts[0].inline_data.data)
# Refine
img = PIL.Image.open('v1.png')
response2 = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=[
'Add flying vehicles and neon signs',
img
]
)Negative Prompts (Indirect)
# Instead of "no blur", be specific about what you want
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='A crystal clear, sharp photograph of a diamond ring with perfect focus and high detail'
)Consistent Style Across Images
base_prompt = "Digital art, vibrant colors, cel-shaded style, clean lines"
prompts = [
f"{base_prompt}, a warrior character",
f"{base_prompt}, a mage character",
f"{base_prompt}, a rogue character"
]
for i, prompt in enumerate(prompts):
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=prompt
)
# Save each characterSafety Settings
Configure Safety Filters
config = types.GenerateContentConfig(
response_modalities=['image'],
safety_settings=[
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold=types.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
),
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold=types.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
)
]
)Available Categories
HARM_CATEGORY_HATE_SPEECHHARM_CATEGORY_DANGEROUS_CONTENTHARM_CATEGORY_HARASSMENTHARM_CATEGORY_SEXUALLY_EXPLICIT
Thresholds
BLOCK_NONE: No blockingBLOCK_LOW_AND_ABOVE: Block low probability and aboveBLOCK_MEDIUM_AND_ABOVE: Block medium and above (default)BLOCK_ONLY_HIGH: Block only high probability
Common Use Cases
1. Marketing Assets
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='''Professional product photography:
- Sleek smartphone on minimalist white surface
- Dramatic side lighting creating subtle shadows
- Shallow depth of field, crisp focus
- Clean, modern aesthetic
- 4K quality
''',
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='4:3'
)
)2. Concept Art
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='''Fantasy concept art:
- Ancient floating islands connected by chains
- Waterfalls cascading into clouds below
- Magical crystals glowing on the islands
- Epic scale, dramatic lighting
- Detailed digital painting style
''',
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='16:9'
)
)3. Social Media Graphics
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='''Instagram post design:
- Pastel gradient background (pink to blue)
- Motivational quote layout
- Modern minimalist style
- Clean typography
- Mobile-friendly composition
''',
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='1:1'
)
)4. Illustration
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='''Children's book illustration:
- Friendly cartoon dragon reading a book
- Bright, cheerful colors
- Soft, rounded shapes
- Whimsical forest background
- Warm, inviting atmosphere
''',
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='4:3'
)
)5. UI/UX Mockups
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents='''Modern mobile app interface:
- Clean dashboard design
- Card-based layout
- Soft shadows and gradients
- Contemporary color scheme (blue and white)
- Professional fintech aesthetic
''',
config=types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='9:16'
)
)Best Practices
Prompt Quality
1. Be specific: More detail = better results 2. Order matters: Most important elements first 3. Use examples: Reference known styles or artists 4. Avoid contradictions: Don't ask for opposing styles 5. Test and iterate: Refine prompts based on results
File Management
# Save with descriptive names
timestamp = int(time.time())
filename = f'generated_{timestamp}_{aspect_ratio}.png'
with open(filename, 'wb') as f:
f.write(image_data)Cost Optimization
Token costs:
- 1 image: 1,290 tokens = $0.00129 (Flash Image at $1/1M)
- 10 images: 12,900 tokens = $0.0129
- 100 images: 129,000 tokens = $0.129
Strategies:
- Generate fewer iterations
- Use text modality first to validate concept
- Batch similar requests
- Cache prompts for consistent style
Error Handling
Safety Filter Blocking
try:
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=prompt
)
except Exception as e:
# Check block reason
if hasattr(e, 'prompt_feedback'):
print(f"Blocked: {e.prompt_feedback.block_reason}")
# Modify prompt and retryToken Limit Exceeded
# Keep prompts concise
if len(prompt) > 1000:
# Truncate or simplify
prompt = prompt[:1000]Limitations
Imagen 4 Constraints
- Language: English prompts only
- Prompt length: Maximum 480 tokens
- Output: 1-4 images per request
- Watermark: All images include SynthID watermark
- Fast model: No
imageSizeparameter support (fixed resolution) - Text rendering: Limited to ~25 characters for optimal results
- Regional restrictions: Child images restricted in EEA, CH, UK
- Cannot replicate: Specific people or copyrighted characters
General Limitations
- Maximum 3 input images for composition
- No video or animation generation
- No real-time generation
Troubleshooting
aspect_ratio Parameter Error
Error: Extra inputs are not permitted [type=extra_forbidden, input_value='1:1', input_type=str]
Cause: The aspect_ratio parameter must be nested inside an image_config object, not passed directly to GenerateContentConfig.
Incorrect Usage:
# ❌ This will fail
config = types.GenerateContentConfig(
response_modalities=['image'],
aspect_ratio='16:9' # Wrong - not a direct parameter
)Correct Usage:
# ✅ Correct implementation
config = types.GenerateContentConfig(
response_modalities=['Image'], # Note: Capital 'I'
image_config=types.ImageConfig(
aspect_ratio='16:9'
)
)Response Modality Case Sensitivity
The response_modalities parameter expects capital case values:
- ✅ Correct:
['Image'],['Text'],['Image', 'Text'] - ❌ Wrong:
['image'],['text']
---
Related References
Current: Image Generation
Related Capabilities:
- Image Understanding - Analyzing and editing reference images
- Video Generation - Creating animated video content
- Audio Processing - Text-to-speech for multimedia
Back to: AI Multimodal Skill
Video Analysis Reference
Comprehensive guide for video understanding, temporal analysis, and YouTube processing using Gemini API.
Note: This guide covers video analysis (understanding existing videos). For video generation (creating new videos), see Video Generation Reference.
Core Capabilities
- Video Summarization: Create concise summaries
- Question Answering: Answer specific questions about content
- Transcription: Audio transcription with visual descriptions
- Timestamp References: Query specific moments (MM:SS format)
- Video Clipping: Process specific segments
- Scene Detection: Identify scene changes and transitions
- Multiple Videos: Compare up to 10 videos (2.5+)
- YouTube Support: Analyze YouTube videos directly
- Custom Frame Rate: Adjust FPS sampling
Supported Formats
- MP4, MPEG, MOV, AVI, FLV, MPG, WebM, WMV, 3GPP
Model Selection
Gemini 3 Series (Latest)
- gemini-3-pro-preview: Latest, agentic workflows, 1M context, dynamic thinking
Gemini 2.5 Series (Recommended)
- gemini-2.5-pro: Best quality, 1M-2M context
- gemini-2.5-flash: Balanced, 1M-2M context (recommended)
Context Windows
- 2M token models: ~2 hours (default) or ~6 hours (low-res)
- 1M token models: ~1 hour (default) or ~3 hours (low-res)
Basic Video Analysis
Local Video
from google import genai
import os
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
# Upload video (File API for >20MB)
myfile = client.files.upload(file='video.mp4')
# Wait for processing
import time
while myfile.state.name == 'PROCESSING':
time.sleep(1)
myfile = client.files.get(name=myfile.name)
if myfile.state.name == 'FAILED':
raise ValueError('Video processing failed')
# Analyze
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Summarize this video in 3 key points', myfile]
)
print(response.text)YouTube Video
from google.genai import types
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Summarize the main topics discussed',
types.Part.from_uri(
uri='https://www.youtube.com/watch?v=VIDEO_ID',
mime_type='video/mp4'
)
]
)Inline Video (<20MB)
with open('short-clip.mp4', 'rb') as f:
video_bytes = f.read()
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'What happens in this video?',
types.Part.from_bytes(data=video_bytes, mime_type='video/mp4')
]
)Advanced Features
Video Clipping
# Analyze specific time range
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Summarize this segment',
types.Part.from_video_metadata(
file_uri=myfile.uri,
start_offset='40s',
end_offset='80s'
)
]
)Custom Frame Rate
# Lower FPS for static content (saves tokens)
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Analyze this presentation',
types.Part.from_video_metadata(
file_uri=myfile.uri,
fps=0.5 # Sample every 2 seconds
)
]
)
# Higher FPS for fast-moving content
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Analyze rapid movements in this sports video',
types.Part.from_video_metadata(
file_uri=myfile.uri,
fps=5 # Sample 5 times per second
)
]
)Multiple Videos (2.5+)
video1 = client.files.upload(file='demo1.mp4')
video2 = client.files.upload(file='demo2.mp4')
# Wait for processing
for video in [video1, video2]:
while video.state.name == 'PROCESSING':
time.sleep(1)
video = client.files.get(name=video.name)
response = client.models.generate_content(
model='gemini-2.5-pro',
contents=[
'Compare these two product demos. Which explains features better?',
video1,
video2
]
)Temporal Understanding
Timestamp-Based Questions
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'What happens at 01:15 and how does it relate to 02:30?',
myfile
]
)Timeline Creation
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Create a timeline with timestamps:
- Key events
- Scene changes
- Important moments
Format: MM:SS - Description
''',
myfile
]
)Scene Detection
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Identify all scene changes with timestamps and describe each scene',
myfile
]
)Transcription
Basic Transcription
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Transcribe the audio from this video',
myfile
]
)With Visual Descriptions
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Transcribe with visual context:
- Audio transcription
- Visual descriptions of important moments
- Timestamps for salient events
''',
myfile
]
)Speaker Identification
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Transcribe with speaker labels and timestamps',
myfile
]
)Common Use Cases
1. Video Summarization
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Summarize this video:
1. Main topic and purpose
2. Key points with timestamps
3. Conclusion or call-to-action
''',
myfile
]
)2. Educational Content
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Create educational materials:
1. List key concepts taught
2. Create 5 quiz questions with answers
3. Provide timestamp for each concept
''',
myfile
]
)3. Action Detection
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'List all actions performed in this tutorial with timestamps',
myfile
]
)4. Content Moderation
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Review video content:
1. Identify any problematic content
2. Note timestamps of concerns
3. Provide content rating recommendation
''',
myfile
]
)5. Interview Analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Analyze interview:
1. Questions asked (timestamps)
2. Key responses
3. Candidate body language and demeanor
4. Overall assessment
''',
myfile
]
)6. Sports Analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Analyze sports video:
1. Key plays with timestamps
2. Player movements and positioning
3. Game strategy observations
''',
types.Part.from_video_metadata(
file_uri=myfile.uri,
fps=5 # Higher FPS for fast action
)
]
)YouTube Specific Features
Public Video Requirements
- Video must be public (not private or unlisted)
- No age-restricted content
- Valid video ID required
Usage Example
# YouTube URL
youtube_uri = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Create chapter markers with timestamps',
types.Part.from_uri(uri=youtube_uri, mime_type='video/mp4')
]
)Rate Limits
- Free tier: 8 hours of YouTube video per day
- Paid tier: No length-based limits
- Public videos only
Token Calculation
Video tokens depend on resolution and FPS:
Default resolution (~300 tokens/second):
- 1 minute = 18,000 tokens
- 10 minutes = 180,000 tokens
- 1 hour = 1,080,000 tokens
Low resolution (~100 tokens/second):
- 1 minute = 6,000 tokens
- 10 minutes = 60,000 tokens
- 1 hour = 360,000 tokens
Context windows:
- 2M tokens ≈ 2 hours (default) or 6 hours (low-res)
- 1M tokens ≈ 1 hour (default) or 3 hours (low-res)
Best Practices
File Management
1. Use File API for videos >20MB (most videos) 2. Wait for ACTIVE state before analysis 3. Files auto-delete after 48 hours 4. Clean up manually:
client.files.delete(name=myfile.name)Optimization Strategies
Reduce token usage:
- Process specific segments using start/end offsets
- Use lower FPS for static content
- Use low-resolution mode for long videos
- Split very long videos into chunks
Improve accuracy:
- Provide context in prompts
- Use higher FPS for fast-moving content
- Use Pro model for complex analysis
- Be specific about what to extract
Prompt Engineering
Effective prompts:
- "Summarize key points with timestamps in MM:SS format"
- "Identify all scene changes and describe each scene"
- "Extract action items mentioned with timestamps"
- "Compare these two videos on: X, Y, Z criteria"
Structured output:
from pydantic import BaseModel
from typing import List
class VideoEvent(BaseModel):
timestamp: str # MM:SS format
description: str
category: str
class VideoAnalysis(BaseModel):
summary: str
events: List[VideoEvent]
duration: str
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Analyze this video', myfile],
config=genai.types.GenerateContentConfig(
response_mime_type='application/json',
response_schema=VideoAnalysis
)
)Error Handling
import time
def upload_and_process_video(file_path, max_wait=300):
"""Upload video and wait for processing"""
myfile = client.files.upload(file=file_path)
elapsed = 0
while myfile.state.name == 'PROCESSING' and elapsed < max_wait:
time.sleep(5)
myfile = client.files.get(name=myfile.name)
elapsed += 5
if myfile.state.name == 'FAILED':
raise ValueError(f'Video processing failed: {myfile.state.name}')
if myfile.state.name == 'PROCESSING':
raise TimeoutError(f'Processing timeout after {max_wait}s')
return myfileCost Optimization
Token costs (Gemini 2.5 Flash at $1/1M):
- 1 minute video (default): 18,000 tokens = $0.018
- 10 minute video: 180,000 tokens = $0.18
- 1 hour video: 1,080,000 tokens = $1.08
Strategies:
- Use video clipping for specific segments
- Lower FPS for static content
- Use low-resolution mode for long videos
- Batch related queries on same video
- Use context caching for repeated queries
Limitations
- Maximum 6 hours (low-res) or 2 hours (default)
- YouTube videos must be public
- No live streaming analysis
- Files expire after 48 hours
- Processing time varies by video length
- No real-time processing
- Limited to 10 videos per request (2.5+)
---
Related References
Current: Video Analysis
Related Capabilities:
- Video Generation - Creating videos from text/images
- Audio Processing - Extract and analyze audio tracks
- Image Understanding - Analyze individual frames
Back to: AI Multimodal Skill
Video Generation Reference
Comprehensive guide for video creation using Veo models via Gemini API.
Core Capabilities
- Text-to-Video: Generate 8-second videos from text prompts
- Image-to-Video: Animate images with text direction
- Video Extension: Continue previously generated videos
- Frame Control: Precise camera movements and effects
- Native Audio: Synchronized audio generation
- Multiple Resolutions: 720p and 1080p output
- Aspect Ratios: 16:9, 9:16, 1:1
Models
Veo 3.1 Preview (Latest)
veo-3.1-generate-preview - Latest with advanced controls
- Frame-specific generation
- Up to 3 reference images for image-to-video
- Video extension capability
- Native audio generation
- Resolution: 720p, 1080p
- Duration: 8 seconds at 24fps
- Status: Preview (API may change)
- Updated: September 2025
veo-3.1-fast-generate-preview - Speed-optimized
- Optimized for business use cases
- Programmatic ad creation
- Social media content
- Same features as standard but faster
- Status: Preview
- Updated: September 2025
Veo 3.0 Stable
veo-3.0-generate-001 - Production-ready
- Native audio generation
- Text-to-video and image-to-video
- 720p and 1080p (16:9 only)
- 8 seconds at 24fps
- Status: Stable
- Updated: July 2025
veo-3.0-fast-generate-001 - Stable fast variant
- Speed-optimized stable version
- Same reliability as 3.0
- Status: Stable
- Updated: July 2025
Model Comparison
| Model | Speed | Features | Audio | Status | Best For |
|---|---|---|---|---|---|
| veo-3.1-preview | Medium | All | ✓ | Preview | Latest features |
| veo-3.1-fast | Fast | All | ✓ | Preview | Business/speed |
| veo-3.0-001 | Medium | Standard | ✓ | Stable | Production |
| veo-3.0-fast | Fast | Standard | ✓ | Stable | Production/speed |
Quick Start
Text-to-Video
from google import genai
from google.genai import types
import os
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
# Basic generation
response = client.models.generate_video(
model='veo-3.1-generate-preview',
prompt='A serene beach at sunset with gentle waves rolling onto the shore',
config=types.VideoGenerationConfig(
resolution='1080p',
aspect_ratio='16:9'
)
)
# Save video
with open('output.mp4', 'wb') as f:
f.write(response.video.data)Image-to-Video
import PIL.Image
# Load reference image
ref_image = PIL.Image.open('beach.jpg')
# Animate the image
response = client.models.generate_video(
model='veo-3.1-generate-preview',
prompt='Camera slowly pans across the scene from left to right',
reference_images=[ref_image],
config=types.VideoGenerationConfig(
resolution='1080p'
)
)Multiple Reference Images
# Use up to 3 reference images for complex scenes
img1 = PIL.Image.open('foreground.jpg')
img2 = PIL.Image.open('background.jpg')
img3 = PIL.Image.open('subject.jpg')
response = client.models.generate_video(
model='veo-3.1-generate-preview',
prompt='Combine these elements into a cohesive animated scene',
reference_images=[img1, img2, img3],
config=types.VideoGenerationConfig(
resolution='1080p',
aspect_ratio='16:9'
)
)Advanced Features
Video Extension
# Continue from previously generated video
previous_video = open('part1.mp4', 'rb').read()
response = client.models.extend_video(
model='veo-3.1-generate-preview',
video=previous_video,
prompt='The scene transitions to nighttime with stars appearing'
)Frame Control
# Precise camera movements
response = client.models.generate_video(
model='veo-3.1-generate-preview',
prompt='A mountain landscape',
config=types.VideoGenerationConfig(
resolution='1080p',
camera_motion='zoom_in', # Options: zoom_in, zoom_out, pan_left, pan_right, tilt_up, tilt_down, static
motion_speed='slow' # Options: slow, medium, fast
)
)Prompt Engineering
Effective Video Prompts
Structure: 1. Subject: What's in the scene 2. Action: What's happening 3. Camera: How it's filmed 4. Style: Visual treatment 5. Timing: Pacing details
Example:
"A hummingbird [subject] hovers near a red flower, then flies away [action].
Slow-motion close-up shot [camera] with vibrant colors and soft focus background [style].
Gentle, peaceful pacing [timing]."Action Verbs
Movement:
- "walks", "runs", "flies", "swims", "dances"
- "rotates", "spins", "rolls", "bounces"
- "emerges", "disappears", "transforms"
Camera:
- "zoom in on", "pull back from", "follow"
- "orbit around", "track alongside"
- "tilt up to reveal", "pan across"
Transitions:
- "gradually changes from... to..."
- "morphs into", "dissolves into"
- "cuts to", "fades to"
Timing Control
# Explicit timing in prompt
prompt = '''
0-2s: Close-up of a seed in soil
2-4s: Time-lapse of sprout emerging
4-6s: Growing into a small plant
6-8s: Zoom out to show garden context
'''Configuration Options
Resolution
config = types.VideoGenerationConfig(
resolution='1080p' # Options: 720p, 1080p
)Considerations:
- 1080p: Higher quality, longer generation time, larger file
- 720p: Faster generation, smaller file, good for drafts
Aspect Ratios
config = types.VideoGenerationConfig(
aspect_ratio='16:9' # Options: 16:9, 9:16, 1:1
)Use Cases:
- 16:9: Landscape, YouTube, traditional video
- 9:16: Mobile, TikTok, Instagram Stories
- 1:1: Square, Instagram feed, versatile
Audio Control
config = types.VideoGenerationConfig(
include_audio=True # Default: True
)Native audio is generated automatically and synchronized with video content.
Best Practices
1. Prompt Quality
Be specific:
- ❌ "A person walking"
- ✅ "A young woman in a red coat walking through a park in autumn"
Include motion:
- ❌ "A city street"
- ✅ "A busy city street with cars passing and people crossing"
Specify camera:
- ❌ "A mountain"
- ✅ "Aerial drone shot slowly ascending over a snow-capped mountain"
2. Reference Images
Quality:
- Use high-resolution images (1080p+)
- Clear, well-lit subjects
- Minimal motion blur
Composition:
- Match desired final aspect ratio
- Leave room for motion/movement
- Consider camera angle in prompt
3. Performance Optimization
Generation Time:
- 720p: ~30-60 seconds
- 1080p: ~60-120 seconds
- Fast models: 30-50% faster
Strategies:
- Use 720p for iteration/drafts
- Use fast models for rapid feedback
- Batch multiple requests
- Use async processing for UI responsiveness
Common Use Cases
1. Product Demos
response = client.models.generate_video(
model='veo-3.0-fast-generate-001',
prompt='''
Professional product video:
- Sleek smartphone rotating on a pedestal
- Clean white background with soft shadows
- Slow 360-degree rotation
- Spotlight highlighting premium design
- Modern, minimalist aesthetic
''',
config=types.VideoGenerationConfig(
resolution='1080p',
aspect_ratio='1:1'
)
)2. Social Media Content
response = client.models.generate_video(
model='veo-3.1-fast-generate-preview',
prompt='''
Trendy social media clip:
- Text overlay "NEW ARRIVAL" appears
- Fashion product showcase
- Quick cuts and dynamic camera
- Vibrant colors, high energy
- Upbeat pacing
''',
config=types.VideoGenerationConfig(
resolution='1080p',
aspect_ratio='9:16' # Mobile
)
)3. Explainer Animations
response = client.models.generate_video(
model='veo-3.1-generate-preview',
prompt='''
Educational animation:
- Simple diagram illustrating data flow
- Arrows and icons animating in sequence
- Clean, clear visual hierarchy
- Smooth transitions between steps
- Professional corporate style
''',
config=types.VideoGenerationConfig(
resolution='720p',
aspect_ratio='16:9'
)
)Safety & Content Policy
Safety Settings
config = types.VideoGenerationConfig(
safety_settings=[
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
)
]
)Prohibited Content
- Violence, gore, harm
- Sexually explicit content
- Hate speech, harassment
- Copyrighted characters/brands
- Real people (without consent)
- Misleading/deceptive content
Limitations
- Duration: Fixed 8 seconds (as of Sept 2025)
- Frame Rate: 24fps only
- File Size: ~5-20MB per video
- Generation Time: 30s-2min depending on resolution
- Reference Images: Max 3 images
- Preview Status: API may change (3.1 models)
- Audio: Cannot upload custom audio (native only)
- No real-time: Pre-generation required
Troubleshooting
Long Generation Times
import time
# Track generation progress
start = time.time()
response = client.models.generate_video(...)
duration = time.time() - start
print(f"Generated in {duration:.1f}s")Expected times:
- Fast models + 720p: 30-45s
- Standard models + 720p: 45-90s
- Fast models + 1080p: 45-60s
- Standard models + 1080p: 60-120s
Safety Filter Blocking
try:
response = client.models.generate_video(...)
except Exception as e:
if 'safety' in str(e).lower():
print("Video blocked by safety filters")
# Modify prompt and retryQuota Exceeded
# Implement exponential backoff
import time
def generate_with_retry(model, prompt, max_retries=3):
for attempt in range(max_retries):
try:
return client.models.generate_video(model=model, prompt=prompt)
except Exception as e:
if '429' in str(e): # Rate limit
wait = 2 ** attempt
print(f"Rate limited, waiting {wait}s...")
time.sleep(wait)
else:
raise
raise Exception("Max retries exceeded")Cost Estimation
Pricing: TBD (preview models)
Estimated based on compute:
- Fast + 720p: ~$0.05-$0.10 per video
- Standard + 1080p: ~$0.15-$0.25 per video
Monitor: https://ai.google.dev/pricing
Resources
---
Related References
Current: Video Generation
Related Capabilities:
- Video Analysis - Understanding existing videos
- Image Generation - Creating static images
- Image Understanding - Analyzing reference images
Back to: AI Multimodal Skill
Vision Understanding Reference
Comprehensive guide for image analysis, object detection, and visual understanding using Gemini API.
Core Capabilities
- Captioning: Generate descriptive text for images
- Classification: Categorize and identify content
- Visual Q&A: Answer questions about images
- Object Detection: Locate objects with bounding boxes (2.0+)
- Segmentation: Create pixel-level masks (2.5+)
- Multi-image: Compare up to 3,600 images
- OCR: Extract text from images
- Document Understanding: Process PDFs with vision
Supported Formats
- Images: PNG, JPEG, WEBP, HEIC, HEIF
- Documents: PDF (up to 1,000 pages)
- Size Limits:
- Inline: 20MB max total request
- File API: 2GB per file
- Max images: 3,600 per request
Model Selection
Gemini 2.5 Series
- gemini-2.5-pro: Best quality, segmentation + detection
- gemini-2.5-flash: Fast, efficient, all features
- gemini-2.5-flash-lite: Lightweight, all features
Feature Requirements
- Segmentation: Requires 2.5+ models
- Object Detection: Requires 2.0+ models
- Multi-image: All models (up to 3,600 images)
Basic Image Analysis
Image Captioning
from google import genai
import os
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
# Local file
with open('image.jpg', 'rb') as f:
img_bytes = f.read()
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Describe this image in detail',
genai.types.Part.from_bytes(data=img_bytes, mime_type='image/jpeg')
]
)
print(response.text)Image Classification
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Classify this image. Provide category and confidence level.',
img_part
]
)Visual Question Answering
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'How many people are in this image and what are they doing?',
img_part
]
)Advanced Features
Object Detection (2.5+)
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Detect all objects in this image and provide bounding boxes',
img_part
]
)
# Returns bounding box coordinates: [ymin, xmin, ymax, xmax]
# Normalized to [0, 1000] rangeSegmentation (2.5+)
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Create a segmentation mask for all people in this image',
img_part
]
)
# Returns pixel-level masks for requested objectsMulti-Image Comparison
import PIL.Image
img1 = PIL.Image.open('photo1.jpg')
img2 = PIL.Image.open('photo2.jpg')
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Compare these two images. What are the differences?',
img1,
img2
]
)OCR and Text Extraction
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Extract all visible text from this image',
img_part
]
)Input Methods
Inline Data (<20MB)
from google.genai import types
# From file
with open('image.jpg', 'rb') as f:
img_bytes = f.read()
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Analyze this image',
types.Part.from_bytes(data=img_bytes, mime_type='image/jpeg')
]
)PIL Image
import PIL.Image
img = PIL.Image.open('photo.jpg')
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['What is in this image?', img]
)File API (>20MB or Reuse)
# Upload once
myfile = client.files.upload(file='large-image.jpg')
# Use multiple times
response1 = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Describe this image', myfile]
)
response2 = client.models.generate_content(
model='gemini-2.5-flash',
contents=['What colors dominate this image?', myfile]
)URL (Public Images)
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Analyze this image',
types.Part.from_uri(
uri='https://example.com/image.jpg',
mime_type='image/jpeg'
)
]
)Token Calculation
Images consume tokens based on size:
Small images (≤384px both dimensions): 258 tokens
Large images: Tiled into 768×768 chunks, 258 tokens each
Formula:
crop_unit = floor(min(width, height) / 1.5)
tiles = (width / crop_unit) × (height / crop_unit)
total_tokens = tiles × 258Examples:
- 256×256: 258 tokens (small)
- 512×512: 258 tokens (small)
- 960×540: 6 tiles = 1,548 tokens
- 1920×1080: 6 tiles = 1,548 tokens
- 3840×2160 (4K): 24 tiles = 6,192 tokens
Structured Output
JSON Schema Output
from pydantic import BaseModel
from typing import List
class ObjectDetection(BaseModel):
object_name: str
confidence: float
bounding_box: List[int] # [ymin, xmin, ymax, xmax]
class ImageAnalysis(BaseModel):
description: str
objects: List[ObjectDetection]
scene_type: str
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Analyze this image', img_part],
config=genai.types.GenerateContentConfig(
response_mime_type='application/json',
response_schema=ImageAnalysis
)
)
result = ImageAnalysis.model_validate_json(response.text)Multi-Image Analysis
Batch Processing
images = [
PIL.Image.open(f'image{i}.jpg')
for i in range(10)
]
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=['Analyze these images and find common themes'] + images
)Image Comparison
before = PIL.Image.open('before.jpg')
after = PIL.Image.open('after.jpg')
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Compare before and after. List all visible changes.',
before,
after
]
)Visual Search
reference = PIL.Image.open('target.jpg')
candidates = [PIL.Image.open(f'option{i}.jpg') for i in range(5)]
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Find which candidate images contain objects similar to the reference',
reference
] + candidates
)Best Practices
Image Quality
1. Resolution: Use clear, non-blurry images 2. Rotation: Verify correct orientation 3. Lighting: Ensure good contrast and lighting 4. Size optimization: Balance quality vs token cost 5. Format: JPEG for photos, PNG for graphics
Prompt Engineering
Specific instructions:
- "Identify all vehicles with their colors and positions"
- "Count people wearing blue shirts"
- "Extract text from the sign in the top-left corner"
Output format:
- "Return results as JSON with fields: category, count, description"
- "Format as markdown table"
- "List findings as numbered items"
Few-shot examples:
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Example: For an image of a cat on a sofa, respond: "Object: cat, Location: sofa"',
'Now analyze this image:',
img_part
]
)File Management
1. Use File API for images >20MB 2. Use File API for repeated queries (saves tokens) 3. Files auto-delete after 48 hours 4. Clean up manually:
client.files.delete(name=myfile.name)Cost Optimization
Token-efficient strategies:
- Resize large images before upload
- Use File API for repeated queries
- Batch multiple images when related
- Use appropriate model (Flash vs Pro)
Token costs (Gemini 2.5 Flash at $1/1M):
- Small image (258 tokens): $0.000258
- HD image (1,548 tokens): $0.001548
- 4K image (6,192 tokens): $0.006192
Common Use Cases
1. Product Analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Analyze this product image:
1. Identify the product
2. List visible features
3. Assess condition
4. Estimate value range
''',
img_part
]
)2. Screenshot Analysis
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Extract all text and UI elements from this screenshot',
img_part
]
)3. Medical Imaging (Informational Only)
response = client.models.generate_content(
model='gemini-2.5-pro',
contents=[
'Describe visible features in this medical image. Note: This is for informational purposes only.',
img_part
]
)4. Chart/Graph Reading
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'Extract data from this chart and format as JSON',
img_part
]
)5. Scene Understanding
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
'''Analyze this scene:
1. Location type
2. Time of day
3. Weather conditions
4. Activities happening
5. Mood/atmosphere
''',
img_part
]
)Error Handling
import time
def analyze_image_with_retry(image_path, prompt, max_retries=3):
"""Analyze image with exponential backoff retry"""
for attempt in range(max_retries):
try:
with open(image_path, 'rb') as f:
img_bytes = f.read()
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=[
prompt,
genai.types.Part.from_bytes(
data=img_bytes,
mime_type='image/jpeg'
)
]
)
return response.text
except Exception as e:
if attempt == max_retries - 1:
raise
wait_time = 2 ** attempt
print(f"Retry {attempt + 1} after {wait_time}s: {e}")
time.sleep(wait_time)Limitations
- Maximum 3,600 images per request
- OCR accuracy varies with text quality
- Object detection requires 2.0+ models
- Segmentation requires 2.5+ models
- No video frame extraction (use video API)
- Regional restrictions on child images (EEA, CH, UK)
---
Related References
Current: Image Understanding
Related Capabilities:
- Image Generation - Create and edit images
- Video Analysis - Analyze video frames
- Video Generation - Reference images for video generation
Back to: AI Multimodal Skill
SQLite format 3@
.�
V�^
O-�
&�m ��a c��Q�}tabletracertracer
CREATE TABLE tracer (
-- A row per file indicating the tracer used for that file.
file_id integer primary key,
tracer text,
foreign key (file_id) references file (id)
)�
�etablearcarcCREATE TABLE arc (
-- If recording branches, a row per context per from/to line transition executed.
file_id integer, -- foreign key to `file`.
context_id integer, -- foreign key to `context`.
fromno integer, -- line number jumped from.
tono integer, -- line number jumped to.
foreign key (file_id) references file (id),
foreign key (context_id) references context (id),
unique (file_id, context_id, fromno, tono)
)%9indexsqlite_autoindex_arc_1arc��qtableline_bitsline_bits CREATE TABLE line_bits (
-- If recording lines, a row per context per file executed.
-- All of the line numbers for that file/context are in one numbits.
file_id integer, -- foreign key to `file`.
context_id integer, -- foreign key to `context`.
numbits blob, -- see the numbits functions in coverage.numbits
foreign key (file_id) references file (id),
foreign key (context_id) references context (id),
unique (file_id, context_id)
)1 Eindexsqlite_autoindex_line_bits_1line_bits
�� tablecontextcontextCREATE TABLE context (
-- A row per context measured.
id integer primary key,
context text,
unique (context)
)-Aindexsqlite_autoindex_context_1context��qtablefilefileCREATE TABLE file (
-- A row per file measured.
id integer primary key,
path text,
unique (path)
)';indexsqlite_autoindex_file_1file�[�tablemetametaCREATE TABLE meta (
-- Key-value pairs, to record metadata about the data
key text,
value text,
unique (key)
-- Possible keys:
-- 'has_arcs' boolean -- Is this data recording branches?
-- 'sys_argv' text -- The coverage command line that recorded the data.
-- 'version' text -- The version of coverage.py that made the file.
-- 'when' text -- Datetime when the file was created.
)';indexsqlite_autoindex_meta_1meta�++�utablecoverage_schemacoverage_schemaCREATE TABLE coverage_schema (
-- One row, to record the version of the schema in this db.
version integer
)
��
���has_arcs0version7.11.0
���has_arcs
version
q�%�F
�
qc�K/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/media_optimizer.pyn�a/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/tests/test_media_optimizer.pyh�U/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/gemini_batch_process.pys�k/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/tests/test_gemini_batch_process.pyf�Q/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/document_converter.pyq�g/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/tests/test_document_converter.py
r&G
r��
�d�K/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/media_optimizer.pyo�a/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/tests/test_media_optimizer.pyi�U/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/gemini_batch_process.pyt�k/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/tests/test_gemini_batch_process.pyg�Q/mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/document_converter.pyq�g /mnt/d/www/claudekit/claudekit-engineer/.claude/skills/ai-multimodal/scripts/tests/test_document_converter.py
��
��
���;+��4 j�+�5ٶmv�~��v�����gk����{>�{>[���~����uղo�}Z3 h�)kk��u����.w�}�'y����?{��g/����������N�I�I �m7۬ZYaF ���`�s��O�g�M�H��[1��d�@�o�ǧ �����HB �࿃!��3��I������?K���M�x��� ~�
I�����7 p��@ʡ��N@
�������
#!/usr/bin/env python3
"""
Validate ai-multimodal skill setup and configuration.
Checks:
- API key presence and format
- Python dependencies
- Centralized resolver availability
- Directory structure
"""
import os
import sys
from pathlib import Path
# Color codes for terminal output
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
RESET = '\033[0m'
BOLD = '\033[1m'
def print_header(text):
"""Print section header."""
print(f"\n{BOLD}{BLUE}{'='*60}{RESET}")
print(f"{BOLD}{BLUE}{text}{RESET}")
print(f"{BOLD}{BLUE}{'='*60}{RESET}\n")
def print_success(text):
"""Print success message."""
print(f"{GREEN}✓ {text}{RESET}")
def print_warning(text):
"""Print warning message."""
print(f"{YELLOW}⚠ {text}{RESET}")
def print_error(text):
"""Print error message."""
print(f"{RED}✗ {text}{RESET}")
def print_info(text):
"""Print info message."""
print(f"{BLUE}ℹ {text}{RESET}")
def check_dependencies():
"""Check if required Python packages are installed."""
print_header("Checking Python Dependencies")
dependencies = {
'google.genai': 'google-genai',
'dotenv': 'python-dotenv',
'PIL': 'pillow'
}
missing = []
for module_name, package_name in dependencies.items():
try:
__import__(module_name)
print_success(f"{package_name} is installed")
except ImportError:
print_error(f"{package_name} is NOT installed")
missing.append(package_name)
if missing:
print_error("\nMissing dependencies detected!")
print_info(f"Install with: pip install {' '.join(missing)}")
return False
return True
def check_centralized_resolver():
"""Check if centralized resolver is available."""
print_header("Checking Centralized Resolver")
resolver_path = Path.home() / '.claude' / 'scripts' / 'resolve_env.py'
if resolver_path.exists():
print_success(f"Centralized resolver found: {resolver_path}")
# Try to import it
sys.path.insert(0, str(resolver_path.parent))
try:
from resolve_env import resolve_env
print_success("Centralized resolver can be imported")
return True
except ImportError as e:
print_error(f"Centralized resolver exists but cannot be imported: {e}")
return False
else:
print_warning(f"Centralized resolver not found: {resolver_path}")
print_info("Skill will use fallback resolution logic")
return True # Not critical, fallback works
def find_api_key():
"""Find and validate API key using centralized resolver."""
print_header("Checking API Key Configuration")
# Try to use centralized resolver
sys.path.insert(0, str(Path.home() / '.claude' / 'scripts'))
try:
from resolve_env import resolve_env
print_info("Using centralized resolver...")
api_key = resolve_env('GEMINI_API_KEY', skill='ai-multimodal')
if api_key:
print_success("API key found via centralized resolver")
print_info(f"Key preview: {api_key[:20]}...{api_key[-4:]}")
# Show hierarchy
print_info("\nTo see where the key was found, run:")
print_info("python ~/.claude/scripts/resolve_env.py GEMINI_API_KEY --skill ai-multimodal --verbose")
return api_key
else:
print_error("API key not found in any location")
return None
except ImportError:
print_warning("Centralized resolver not available, using fallback")
# Fallback: check environment
api_key = os.getenv('GEMINI_API_KEY')
if api_key:
print_success("API key found in process.env")
print_info(f"Key preview: {api_key[:20]}...{api_key[-4:]}")
return api_key
else:
print_error("API key not found")
return None
def validate_api_key_format(api_key):
"""Basic validation of API key format."""
if not api_key:
return False
# Google AI Studio keys typically start with 'AIza'
if api_key.startswith('AIza'):
print_success("API key format looks valid (Google AI Studio)")
return True
elif len(api_key) > 20:
print_warning("API key format not recognized (may be Vertex AI or custom)")
return True
else:
print_error("API key format looks invalid (too short)")
return False
def test_api_connection(api_key):
"""Test API connection with a simple request."""
print_header("Testing API Connection")
try:
from google import genai
print_info("Initializing Gemini client...")
client = genai.Client(api_key=api_key)
print_info("Fetching available models...")
# List models to verify API key works
models = list(client.models.list())
print_success(f"API connection successful! Found {len(models)} available models")
# Show some available models
print_info("\nSample available models:")
for model in models[:5]:
print(f" - {model.name}")
return True
except ImportError:
print_error("google-genai package not installed")
return False
except Exception as e:
print_error(f"API connection failed: {str(e)}")
return False
def check_directory_structure():
"""Verify skill directory structure."""
print_header("Checking Directory Structure")
script_dir = Path(__file__).parent
skill_dir = script_dir.parent
required_files = [
('SKILL.md', skill_dir / 'SKILL.md'),
('.env.example', skill_dir / '.env.example'),
('gemini_batch_process.py', script_dir / 'gemini_batch_process.py'),
]
all_exist = True
for name, path in required_files:
if path.exists():
print_success(f"{name} exists")
else:
print_error(f"{name} NOT found at {path}")
all_exist = False
return all_exist
def provide_setup_instructions():
"""Provide setup instructions if configuration is incomplete."""
print_header("Setup Instructions")
print_info("To configure the ai-multimodal skill:")
print("\n1. Get a Gemini API key:")
print(" → Visit: https://aistudio.google.com/apikey")
print("\n2. Configure the API key (choose one method):")
print(f"\n Option A: User global config (recommended)")
print(f" $ echo 'GEMINI_API_KEY=your-api-key-here' >> ~/.claude/.env")
script_dir = Path(__file__).parent
skill_dir = script_dir.parent
print(f"\n Option B: Skill-specific config")
print(f" $ cd {skill_dir}")
print(f" $ cp .env.example .env")
print(f" $ # Edit .env and add your API key")
print(f"\n Option C: Runtime environment (temporary)")
print(f" $ export GEMINI_API_KEY='your-api-key-here'")
print("\n3. Verify setup:")
print(f" $ python {Path(__file__)}")
print("\n4. Debug if needed:")
print(f" $ python ~/.claude/scripts/resolve_env.py --show-hierarchy --skill ai-multimodal")
print(f" $ python ~/.claude/scripts/resolve_env.py GEMINI_API_KEY --skill ai-multimodal --verbose")
def main():
"""Run all setup checks."""
print(f"\n{BOLD}AI Multimodal Skill - Setup Checker{RESET}")
all_passed = True
# Check directory structure
if not check_directory_structure():
all_passed = False
# Check centralized resolver
check_centralized_resolver()
# Check dependencies
if not check_dependencies():
all_passed = False
provide_setup_instructions()
sys.exit(1)
# Check API key
api_key = find_api_key()
if not api_key:
print_error("\n❌ GEMINI_API_KEY not found in any location")
all_passed = False
provide_setup_instructions()
sys.exit(1)
# Validate API key format
if not validate_api_key_format(api_key):
all_passed = False
# Test API connection
if not test_api_connection(api_key):
all_passed = False
# Final summary
print_header("Setup Summary")
if all_passed:
print_success("✅ All checks passed! The ai-multimodal skill is ready to use.")
print_info("\nNext steps:")
print(" • Read SKILL.md for usage examples")
print(" • Try: python scripts/gemini_batch_process.py --help")
print(" • Generate image: python scripts/gemini_batch_process.py --task generate --prompt 'A sunset' --model gemini-2.5-flash-image")
else:
print_error("❌ Some checks failed. Please fix the issues above.")
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Convert documents to Markdown using Gemini API.
Supports all document types:
- PDF documents (native vision processing)
- Images (JPEG, PNG, WEBP, HEIC)
- Office documents (DOCX, XLSX, PPTX)
- HTML, TXT, and other text formats
Features:
- Converts to clean markdown format
- Preserves structure, tables, and formatting
- Extracts text from images and scanned documents
- Batch conversion support
- Saves to docs/assets/document-extraction.md by default
"""
import argparse
import os
import sys
import time
from pathlib import Path
from typing import Optional, List, Dict, Any
try:
from google import genai
from google.genai import types
except ImportError:
print("Error: google-genai package not installed")
print("Install with: pip install google-genai")
sys.exit(1)
try:
from dotenv import load_dotenv
except ImportError:
load_dotenv = None
def find_api_key() -> Optional[str]:
"""Find Gemini API key using correct priority order.
Priority order (highest to lowest):
1. process.env (runtime environment variables)
2. .claude/skills/ai-multimodal/.env (skill-specific config)
3. .claude/skills/.env (shared skills config)
4. .claude/.env (Claude global config)
"""
# Priority 1: Already in process.env (highest)
api_key = os.getenv('GEMINI_API_KEY')
if api_key:
return api_key
# Load .env files if dotenv available
if load_dotenv:
# Determine base paths
script_dir = Path(__file__).parent
skill_dir = script_dir.parent # .claude/skills/ai-multimodal
skills_dir = skill_dir.parent # .claude/skills
claude_dir = skills_dir.parent # .claude
# Priority 2: Skill-specific .env
env_file = skill_dir / '.env'
if env_file.exists():
load_dotenv(env_file)
api_key = os.getenv('GEMINI_API_KEY')
if api_key:
return api_key
# Priority 3: Shared skills .env
env_file = skills_dir / '.env'
if env_file.exists():
load_dotenv(env_file)
api_key = os.getenv('GEMINI_API_KEY')
if api_key:
return api_key
# Priority 4: Claude global .env
env_file = claude_dir / '.env'
if env_file.exists():
load_dotenv(env_file)
api_key = os.getenv('GEMINI_API_KEY')
if api_key:
return api_key
return None
def find_project_root() -> Path:
"""Find project root directory."""
script_dir = Path(__file__).parent
# Look for .git or .claude directory
for parent in [script_dir] + list(script_dir.parents):
if (parent / '.git').exists() or (parent / '.claude').exists():
return parent
return script_dir
def get_mime_type(file_path: str) -> str:
"""Determine MIME type from file extension."""
ext = Path(file_path).suffix.lower()
mime_types = {
# Documents
'.pdf': 'application/pdf',
'.txt': 'text/plain',
'.html': 'text/html',
'.htm': 'text/html',
'.md': 'text/markdown',
'.csv': 'text/csv',
# Images
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.webp': 'image/webp',
'.heic': 'image/heic',
'.heif': 'image/heif',
# Office (need to be uploaded as binary)
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
}
return mime_types.get(ext, 'application/octet-stream')
def upload_file(client: genai.Client, file_path: str, verbose: bool = False) -> Any:
"""Upload file to Gemini File API."""
if verbose:
print(f"Uploading {file_path}...")
myfile = client.files.upload(file=file_path)
# Wait for processing if needed
max_wait = 300 # 5 minutes
elapsed = 0
while myfile.state.name == 'PROCESSING' and elapsed < max_wait:
time.sleep(2)
myfile = client.files.get(name=myfile.name)
elapsed += 2
if verbose and elapsed % 10 == 0:
print(f" Processing... {elapsed}s")
if myfile.state.name == 'FAILED':
raise ValueError(f"File processing failed: {file_path}")
if myfile.state.name == 'PROCESSING':
raise TimeoutError(f"Processing timeout after {max_wait}s: {file_path}")
if verbose:
print(f" Uploaded: {myfile.name}")
return myfile
def convert_to_markdown(
client: genai.Client,
file_path: str,
model: str = 'gemini-2.5-flash',
custom_prompt: Optional[str] = None,
verbose: bool = False,
max_retries: int = 3
) -> Dict[str, Any]:
"""Convert a document to markdown using Gemini."""
for attempt in range(max_retries):
try:
file_path_obj = Path(file_path)
file_size = file_path_obj.stat().st_size
use_file_api = file_size > 20 * 1024 * 1024 # >20MB
# Default prompt for markdown conversion
if custom_prompt:
prompt = custom_prompt
else:
prompt = """Convert this document to clean, well-formatted Markdown.
Requirements:
- Preserve all content, structure, and formatting
- Convert tables to markdown table format
- Maintain heading hierarchy (# ## ### etc)
- Preserve lists, code blocks, and quotes
- Extract text from images if present
- Keep formatting consistent and readable
Output only the markdown content without any preamble or explanation."""
# Upload or inline the file
if use_file_api:
myfile = upload_file(client, str(file_path), verbose)
content = [prompt, myfile]
else:
with open(file_path, 'rb') as f:
file_bytes = f.read()
mime_type = get_mime_type(str(file_path))
content = [
prompt,
types.Part.from_bytes(data=file_bytes, mime_type=mime_type)
]
# Generate markdown
response = client.models.generate_content(
model=model,
contents=content
)
markdown_content = response.text if hasattr(response, 'text') else ''
return {
'file': str(file_path),
'status': 'success',
'markdown': markdown_content
}
except Exception as e:
if attempt == max_retries - 1:
return {
'file': str(file_path),
'status': 'error',
'error': str(e),
'markdown': None
}
wait_time = 2 ** attempt
if verbose:
print(f" Retry {attempt + 1} after {wait_time}s: {e}")
time.sleep(wait_time)
def batch_convert(
files: List[str],
output_file: Optional[str] = None,
auto_name: bool = False,
model: str = 'gemini-2.5-flash',
custom_prompt: Optional[str] = None,
verbose: bool = False
) -> List[Dict[str, Any]]:
"""Batch convert multiple files to markdown."""
api_key = find_api_key()
if not api_key:
print("Error: GEMINI_API_KEY not found")
print("Set via: export GEMINI_API_KEY='your-key'")
print("Or create .env file with: GEMINI_API_KEY=your-key")
sys.exit(1)
client = genai.Client(api_key=api_key)
results = []
# Determine output path
if not output_file:
project_root = find_project_root()
output_dir = project_root / 'docs' / 'assets'
if auto_name and len(files) == 1:
# Auto-generate meaningful filename from input
input_path = Path(files[0])
base_name = input_path.stem
output_file = str(output_dir / f"{base_name}-extraction.md")
else:
output_file = str(output_dir / 'document-extraction.md')
output_path = Path(output_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Process each file
for i, file_path in enumerate(files, 1):
if verbose:
print(f"\n[{i}/{len(files)}] Converting: {file_path}")
result = convert_to_markdown(
client=client,
file_path=file_path,
model=model,
custom_prompt=custom_prompt,
verbose=verbose
)
results.append(result)
if verbose:
status = result.get('status', 'unknown')
print(f" Status: {status}")
# Save combined markdown
with open(output_path, 'w', encoding='utf-8') as f:
f.write("# Document Extraction Results\n\n")
f.write(f"Converted {len(files)} document(s) to markdown.\n\n")
f.write("---\n\n")
for result in results:
f.write(f"## {Path(result['file']).name}\n\n")
if result['status'] == 'success' and result.get('markdown'):
f.write(result['markdown'])
f.write("\n\n")
elif result['status'] == 'success':
f.write("**Note**: Conversion succeeded but no content was returned.\n\n")
else:
f.write(f"**Error**: {result.get('error', 'Unknown error')}\n\n")
f.write("---\n\n")
if verbose or True: # Always show output location
print(f"\n{'='*50}")
print(f"Converted: {len(results)} file(s)")
print(f"Success: {sum(1 for r in results if r['status'] == 'success')}")
print(f"Failed: {sum(1 for r in results if r['status'] == 'error')}")
print(f"Output saved to: {output_path}")
return results
def main():
parser = argparse.ArgumentParser(
description='Convert documents to Markdown using Gemini API',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Convert single PDF to markdown (default name)
%(prog)s --input document.pdf
# Auto-generate meaningful filename
%(prog)s --input testpdf.pdf --auto-name
# Output: docs/assets/testpdf-extraction.md
# Convert multiple files
%(prog)s --input doc1.pdf doc2.docx image.png
# Specify custom output location
%(prog)s --input document.pdf --output ./output.md
# Use custom prompt
%(prog)s --input document.pdf --prompt "Extract only the tables as markdown"
# Batch convert directory
%(prog)s --input ./documents/*.pdf --verbose
Supported formats:
- PDF documents (up to 1,000 pages)
- Images (JPEG, PNG, WEBP, HEIC)
- Office documents (DOCX, XLSX, PPTX)
- Text formats (TXT, HTML, Markdown, CSV)
Default output: <project-root>/docs/assets/document-extraction.md
"""
)
parser.add_argument('--input', '-i', nargs='+', required=True,
help='Input file(s) to convert')
parser.add_argument('--output', '-o',
help='Output markdown file (default: docs/assets/document-extraction.md)')
parser.add_argument('--auto-name', '-a', action='store_true',
help='Auto-generate meaningful output filename from input (e.g., document.pdf -> document-extraction.md)')
parser.add_argument('--model', default='gemini-2.5-flash',
help='Gemini model to use (default: gemini-2.5-flash)')
parser.add_argument('--prompt', '-p',
help='Custom prompt for conversion')
parser.add_argument('--verbose', '-v', action='store_true',
help='Verbose output')
args = parser.parse_args()
# Validate input files
files = []
for file_pattern in args.input:
file_path = Path(file_pattern)
if file_path.exists() and file_path.is_file():
files.append(str(file_path))
else:
# Try glob pattern
import glob
matched = glob.glob(file_pattern)
files.extend([f for f in matched if Path(f).is_file()])
if not files:
print("Error: No valid input files found")
sys.exit(1)
# Convert files
batch_convert(
files=files,
output_file=args.output,
auto_name=args.auto_name,
model=args.model,
custom_prompt=args.prompt,
verbose=args.verbose
)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Optimize media files for Gemini API processing.
Features:
- Compress videos/audio for size limits
- Resize images appropriately
- Split long videos into chunks
- Format conversion
- Quality vs size optimization
- Validation before upload
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Optional, Dict, Any, List
try:
from dotenv import load_dotenv
except ImportError:
load_dotenv = None
def load_env_files():
"""Load .env files in correct priority order.
Priority order (highest to lowest):
1. process.env (runtime environment variables)
2. .claude/skills/ai-multimodal/.env (skill-specific config)
3. .claude/skills/.env (shared skills config)
4. .claude/.env (Claude global config)
"""
if not load_dotenv:
return
# Determine base paths
script_dir = Path(__file__).parent
skill_dir = script_dir.parent # .claude/skills/ai-multimodal
skills_dir = skill_dir.parent # .claude/skills
claude_dir = skills_dir.parent # .claude
# Priority 2: Skill-specific .env
env_file = skill_dir / '.env'
if env_file.exists():
load_dotenv(env_file)
# Priority 3: Shared skills .env
env_file = skills_dir / '.env'
if env_file.exists():
load_dotenv(env_file)
# Priority 4: Claude global .env
env_file = claude_dir / '.env'
if env_file.exists():
load_dotenv(env_file)
# Load environment variables at module level
load_env_files()
def check_ffmpeg() -> bool:
"""Check if ffmpeg is installed."""
try:
subprocess.run(['ffmpeg', '-version'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
return False
def get_media_info(file_path: str) -> Dict[str, Any]:
"""Get media file information using ffprobe."""
if not check_ffmpeg():
return {}
try:
cmd = [
'ffprobe',
'-v', 'quiet',
'-print_format', 'json',
'-show_format',
'-show_streams',
file_path
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
data = json.loads(result.stdout)
info = {
'size': int(data['format'].get('size', 0)),
'duration': float(data['format'].get('duration', 0)),
'bit_rate': int(data['format'].get('bit_rate', 0)),
}
# Get video/audio specific info
for stream in data.get('streams', []):
if stream['codec_type'] == 'video':
info['width'] = stream.get('width', 0)
info['height'] = stream.get('height', 0)
info['fps'] = eval(stream.get('r_frame_rate', '0/1'))
elif stream['codec_type'] == 'audio':
info['sample_rate'] = int(stream.get('sample_rate', 0))
info['channels'] = stream.get('channels', 0)
return info
except (subprocess.CalledProcessError, json.JSONDecodeError, Exception):
return {}
def optimize_video(
input_path: str,
output_path: str,
target_size_mb: Optional[int] = None,
max_duration: Optional[int] = None,
quality: int = 23,
resolution: Optional[str] = None,
verbose: bool = False
) -> bool:
"""Optimize video file for Gemini API."""
if not check_ffmpeg():
print("Error: ffmpeg not installed")
print("Install: apt-get install ffmpeg (Linux) or brew install ffmpeg (Mac)")
return False
info = get_media_info(input_path)
if not info:
print(f"Error: Could not read media info from {input_path}")
return False
if verbose:
print(f"Input: {Path(input_path).name}")
print(f" Size: {info['size'] / (1024*1024):.2f} MB")
print(f" Duration: {info['duration']:.2f}s")
if 'width' in info:
print(f" Resolution: {info['width']}x{info['height']}")
print(f" Bit rate: {info['bit_rate'] / 1000:.0f} kbps")
# Build ffmpeg command
cmd = ['ffmpeg', '-i', input_path, '-y']
# Video codec
cmd.extend(['-c:v', 'libx264', '-crf', str(quality)])
# Resolution
if resolution:
cmd.extend(['-vf', f'scale={resolution}'])
elif 'width' in info and info['width'] > 1920:
cmd.extend(['-vf', 'scale=1920:-2']) # Max 1080p
# Audio codec
cmd.extend(['-c:a', 'aac', '-b:a', '128k', '-ac', '2'])
# Duration limit
if max_duration and info['duration'] > max_duration:
cmd.extend(['-t', str(max_duration)])
# Target size (rough estimate using bitrate)
if target_size_mb:
target_bits = target_size_mb * 8 * 1024 * 1024
duration = min(info['duration'], max_duration) if max_duration else info['duration']
target_bitrate = int(target_bits / duration)
# Reserve some for audio (128kbps)
video_bitrate = max(target_bitrate - 128000, 500000)
cmd.extend(['-b:v', str(video_bitrate)])
cmd.append(output_path)
if verbose:
print(f"\nOptimizing...")
print(f" Command: {' '.join(cmd)}")
try:
subprocess.run(cmd, check=True, capture_output=not verbose)
# Check output
output_info = get_media_info(output_path)
if output_info and verbose:
print(f"\nOutput: {Path(output_path).name}")
print(f" Size: {output_info['size'] / (1024*1024):.2f} MB")
print(f" Duration: {output_info['duration']:.2f}s")
if 'width' in output_info:
print(f" Resolution: {output_info['width']}x{output_info['height']}")
compression = (1 - output_info['size'] / info['size']) * 100
print(f" Compression: {compression:.1f}%")
return True
except subprocess.CalledProcessError as e:
print(f"Error optimizing video: {e}")
return False
def optimize_audio(
input_path: str,
output_path: str,
target_size_mb: Optional[int] = None,
bitrate: str = '64k',
sample_rate: int = 16000,
verbose: bool = False
) -> bool:
"""Optimize audio file for Gemini API."""
if not check_ffmpeg():
print("Error: ffmpeg not installed")
return False
info = get_media_info(input_path)
if not info:
print(f"Error: Could not read media info from {input_path}")
return False
if verbose:
print(f"Input: {Path(input_path).name}")
print(f" Size: {info['size'] / (1024*1024):.2f} MB")
print(f" Duration: {info['duration']:.2f}s")
# Build command
cmd = [
'ffmpeg', '-i', input_path, '-y',
'-c:a', 'aac',
'-b:a', bitrate,
'-ar', str(sample_rate),
'-ac', '1', # Mono (Gemini uses mono anyway)
output_path
]
if verbose:
print(f"\nOptimizing...")
try:
subprocess.run(cmd, check=True, capture_output=not verbose)
output_info = get_media_info(output_path)
if output_info and verbose:
print(f"\nOutput: {Path(output_path).name}")
print(f" Size: {output_info['size'] / (1024*1024):.2f} MB")
compression = (1 - output_info['size'] / info['size']) * 100
print(f" Compression: {compression:.1f}%")
return True
except subprocess.CalledProcessError as e:
print(f"Error optimizing audio: {e}")
return False
def optimize_image(
input_path: str,
output_path: str,
max_width: int = 1920,
quality: int = 85,
verbose: bool = False
) -> bool:
"""Optimize image file for Gemini API."""
try:
from PIL import Image
except ImportError:
print("Error: Pillow not installed")
print("Install with: pip install pillow")
return False
try:
img = Image.open(input_path)
if verbose:
print(f"Input: {Path(input_path).name}")
print(f" Size: {Path(input_path).stat().st_size / 1024:.2f} KB")
print(f" Resolution: {img.width}x{img.height}")
# Resize if needed
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
if verbose:
print(f" Resized to: {img.width}x{img.height}")
# Convert RGBA to RGB if saving as JPEG
if output_path.lower().endswith('.jpg') or output_path.lower().endswith('.jpeg'):
if img.mode == 'RGBA':
rgb_img = Image.new('RGB', img.size, (255, 255, 255))
rgb_img.paste(img, mask=img.split()[3])
img = rgb_img
# Save
img.save(output_path, quality=quality, optimize=True)
if verbose:
print(f"\nOutput: {Path(output_path).name}")
print(f" Size: {Path(output_path).stat().st_size / 1024:.2f} KB")
compression = (1 - Path(output_path).stat().st_size / Path(input_path).stat().st_size) * 100
print(f" Compression: {compression:.1f}%")
return True
except Exception as e:
print(f"Error optimizing image: {e}")
return False
def split_video(
input_path: str,
output_dir: str,
chunk_duration: int = 3600,
verbose: bool = False
) -> List[str]:
"""Split long video into chunks."""
if not check_ffmpeg():
print("Error: ffmpeg not installed")
return []
info = get_media_info(input_path)
if not info:
return []
total_duration = info['duration']
num_chunks = int(total_duration / chunk_duration) + 1
if num_chunks == 1:
if verbose:
print("Video is short enough, no splitting needed")
return [input_path]
Path(output_dir).mkdir(parents=True, exist_ok=True)
output_files = []
for i in range(num_chunks):
start_time = i * chunk_duration
output_file = Path(output_dir) / f"{Path(input_path).stem}_chunk_{i+1}.mp4"
cmd = [
'ffmpeg', '-i', input_path, '-y',
'-ss', str(start_time),
'-t', str(chunk_duration),
'-c', 'copy',
str(output_file)
]
if verbose:
print(f"Creating chunk {i+1}/{num_chunks}...")
try:
subprocess.run(cmd, check=True, capture_output=not verbose)
output_files.append(str(output_file))
except subprocess.CalledProcessError as e:
print(f"Error creating chunk {i+1}: {e}")
return output_files
def main():
parser = argparse.ArgumentParser(
description='Optimize media files for Gemini API',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Optimize video to 100MB
%(prog)s --input video.mp4 --output optimized.mp4 --target-size 100
# Optimize audio
%(prog)s --input audio.mp3 --output optimized.m4a --bitrate 64k
# Resize image
%(prog)s --input image.jpg --output resized.jpg --max-width 1920
# Split long video
%(prog)s --input long-video.mp4 --split --chunk-duration 3600 --output-dir ./chunks
# Batch optimize directory
%(prog)s --input-dir ./videos --output-dir ./optimized --quality 85
"""
)
parser.add_argument('--input', help='Input file')
parser.add_argument('--output', help='Output file')
parser.add_argument('--input-dir', help='Input directory for batch processing')
parser.add_argument('--output-dir', help='Output directory for batch processing')
parser.add_argument('--target-size', type=int, help='Target size in MB')
parser.add_argument('--quality', type=int, default=85,
help='Quality (video: 0-51 CRF, image: 1-100) (default: 85)')
parser.add_argument('--max-width', type=int, default=1920,
help='Max image width (default: 1920)')
parser.add_argument('--bitrate', default='64k',
help='Audio bitrate (default: 64k)')
parser.add_argument('--resolution', help='Video resolution (e.g., 1920x1080)')
parser.add_argument('--split', action='store_true', help='Split long video into chunks')
parser.add_argument('--chunk-duration', type=int, default=3600,
help='Chunk duration in seconds (default: 3600 = 1 hour)')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
args = parser.parse_args()
# Validate arguments
if not args.input and not args.input_dir:
parser.error("Either --input or --input-dir required")
# Single file processing
if args.input:
input_path = Path(args.input)
if not input_path.exists():
print(f"Error: Input file not found: {input_path}")
sys.exit(1)
if args.split:
output_dir = args.output_dir or './chunks'
chunks = split_video(str(input_path), output_dir, args.chunk_duration, args.verbose)
print(f"\nCreated {len(chunks)} chunks in {output_dir}")
sys.exit(0)
if not args.output:
parser.error("--output required for single file processing")
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Determine file type
ext = input_path.suffix.lower()
if ext in ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv']:
success = optimize_video(
str(input_path),
str(output_path),
target_size_mb=args.target_size,
quality=args.quality,
resolution=args.resolution,
verbose=args.verbose
)
elif ext in ['.mp3', '.wav', '.m4a', '.flac', '.aac']:
success = optimize_audio(
str(input_path),
str(output_path),
target_size_mb=args.target_size,
bitrate=args.bitrate,
verbose=args.verbose
)
elif ext in ['.jpg', '.jpeg', '.png', '.webp']:
success = optimize_image(
str(input_path),
str(output_path),
max_width=args.max_width,
quality=args.quality,
verbose=args.verbose
)
else:
print(f"Error: Unsupported file type: {ext}")
sys.exit(1)
sys.exit(0 if success else 1)
# Batch processing
if args.input_dir:
if not args.output_dir:
parser.error("--output-dir required for batch processing")
input_dir = Path(args.input_dir)
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Find all media files
patterns = ['*.mp4', '*.mov', '*.avi', '*.mkv', '*.webm',
'*.mp3', '*.wav', '*.m4a', '*.flac',
'*.jpg', '*.jpeg', '*.png', '*.webp']
files = []
for pattern in patterns:
files.extend(input_dir.glob(pattern))
if not files:
print(f"No media files found in {input_dir}")
sys.exit(1)
print(f"Found {len(files)} files to process")
success_count = 0
for input_file in files:
output_file = output_dir / input_file.name
ext = input_file.suffix.lower()
success = False
if ext in ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv']:
success = optimize_video(str(input_file), str(output_file),
quality=args.quality, verbose=args.verbose)
elif ext in ['.mp3', '.wav', '.m4a', '.flac', '.aac']:
success = optimize_audio(str(input_file), str(output_file),
bitrate=args.bitrate, verbose=args.verbose)
elif ext in ['.jpg', '.jpeg', '.png', '.webp']:
success = optimize_image(str(input_file), str(output_file),
max_width=args.max_width, quality=args.quality,
verbose=args.verbose)
if success:
success_count += 1
print(f"\nProcessed: {success_count}/{len(files)} files")
if __name__ == '__main__':
main()
# AI Multimodal Skill Dependencies
# Python 3.10+ required
# Google Gemini API
google-genai>=0.1.0
# PDF processing
pypdf>=4.0.0
# Document conversion
python-docx>=1.0.0
docx2pdf>=0.1.8 # Windows only, optional on Linux/macOS
# Markdown processing
markdown>=3.5.0
# Image processing
Pillow>=10.0.0
# Environment variable management
python-dotenv>=1.0.0
# Testing dependencies (dev)
pytest>=8.0.0
pytest-cov>=4.1.0
pytest-mock>=3.12.0
SQLite format 3@
.�
V�^
O-�
&�m ��a c��Q�}tabletracertracer
CREATE TABLE tracer (
-- A row per file indicating the tracer used for that file.
file_id integer primary key,
tracer text,
foreign key (file_id) references file (id)
)�
�etablearcarcCREATE TABLE arc (
-- If recording branches, a row per context per from/to line transition executed.
file_id integer, -- foreign key to `file`.
context_id integer, -- foreign key to `context`.
fromno integer, -- line number jumped from.
tono integer, -- line number jumped to.
foreign key (file_id) references file (id),
foreign key (context_id) references context (id),
unique (file_id, context_id, fromno, tono)
)%9indexsqlite_autoindex_arc_1arc��qtableline_bitsline_bits CREATE TABLE line_bits (
-- If recording lines, a row per context per file executed.
-- All of the line numbers for that file/context are in one numbits.
file_id integer, -- foreign key to `file`.
context_id integer, -- foreign key to `context`.
numbits blob, -- see the numbits functions in coverage.numbits
foreign key (file_id) references file (id),
foreign key (context_id) references context (id),
unique (file_id, context_id)
)1 Eindexsqlite_autoindex_line_bits_1line_bits
�� tablecontextcontextCREATE TABLE context (
-- A row per context measured.
id integer primary key,
context text,
unique (context)
)-Aindexsqlite_autoindex_context_1context��qtablefilefileCREATE TABLE file (
-- A row per file measured.
id integer primary key,
path text,
unique (path)
)';indexsqlite_autoindex_file_1file�[�tablemetametaCREATE TABLE meta (
-- Key-value pairs, to record metadata about the data
key text,
value text,
unique (key)
-- Possible keys:
-- 'has_arcs' boolean -- Is this data recording branches?
-- 'sys_argv' text -- The coverage command line that recorded the data.
-- 'version' text -- The version of coverage.py that made the file.
-- 'when' text -- Datetime when the file was created.
)';indexsqlite_autoindex_meta_1meta�++�utablecoverage_schemacoverage_schemaCREATE TABLE coverage_schema (
-- One row, to record the version of the schema in this db.
version integer
)
��
���has_arcs0version7.11.3
���has_arcs
version
��b�ID:\www\claudekit\claudekit-engineer\.claude\skills\ai-multimodal\scripts\document_converter.py
��b�I D:\www\claudekit\claudekit-engineer\.claude\skills\ai-multimodal\scripts\document_converter.py
��
��
��6 p��@ʡ��N@
��
# Core dependencies
google-genai>=0.2.0
python-dotenv>=1.0.0
# Image processing
pillow>=10.0.0
# PDF processing
pypdf>=3.0.0
# Document conversion
markdown>=3.5
# Testing
pytest>=7.4.0
pytest-cov>=4.1.0
pytest-mock>=3.12.0
# Optional dependencies for full functionality
# ffmpeg-python>=0.2.0 # For media optimization (requires ffmpeg installed)
"""
Tests for document_converter.py
"""
import pytest
import sys
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock, mock_open
sys.path.insert(0, str(Path(__file__).parent.parent))
import document_converter as dc
class TestAPIKeyFinder:
"""Test API key finding logic."""
@patch.dict('os.environ', {'GEMINI_API_KEY': 'test-key-from-env'})
def test_find_api_key_from_env(self):
"""Test finding API key from environment."""
api_key = dc.find_api_key()
assert api_key == 'test-key-from-env'
@patch.dict('os.environ', {}, clear=True)
@patch('document_converter.load_dotenv', None)
def test_find_api_key_no_key(self):
"""Test when no API key is available."""
api_key = dc.find_api_key()
assert api_key is None
class TestProjectRoot:
"""Test project root finding."""
@patch('pathlib.Path.exists')
def test_find_project_root_with_git(self, mock_exists):
"""Test finding project root with .git directory."""
root = dc.find_project_root()
assert isinstance(root, Path)
class TestMimeType:
"""Test MIME type detection."""
def test_pdf_mime_type(self):
"""Test PDF MIME type."""
assert dc.get_mime_type('document.pdf') == 'application/pdf'
def test_image_mime_types(self):
"""Test image MIME types."""
assert dc.get_mime_type('image.jpg') == 'image/jpeg'
assert dc.get_mime_type('image.png') == 'image/png'
def test_unknown_mime_type(self):
"""Test unknown file extension."""
assert dc.get_mime_type('file.unknown') == 'application/octet-stream'
class TestIntegration:
"""Integration tests."""
def test_mime_type_integration(self):
"""Test MIME type detection with various extensions."""
test_cases = [
('document.pdf', 'application/pdf'),
('image.jpg', 'image/jpeg'),
('unknown.xyz', 'application/octet-stream'),
]
for file_path, expected_mime in test_cases:
assert dc.get_mime_type(file_path) == expected_mime
if __name__ == '__main__':
pytest.main([__file__, '-v', '--cov=document_converter', '--cov-report=term-missing'])
Related skills
FAQ
Which API does this skill use?
It uses the Google Gemini API, including Imagen 4 for image generation and Veo 3 for video generation.
What media formats and limits are supported?
Audio WAV/MP3/AAC up to 9.5h, images PNG/JPEG/WEBP, video MP4/MOV up to 6h, and PDF up to 1k pages, with 20MB inline or 2GB via the File API.