docs/guides/video_generation.md

# Video Generation Guide

Generate high-quality videos from text descriptions using Google's Veo models through the Vertex AI API.

## Overview

The Video Generation API (Veo) allows you to:
- Generate high-quality videos from text prompts
- Create videos with customizable duration, aspect ratio, and frame rate
- Monitor generation progress with long-running operations

**Important Notes:**
- Video generation requires Vertex AI authentication (not available on Gemini API)
- Generation is asynchronous and can take 2-5 minutes per video
- Videos are typically 4-8 seconds in duration
- Generated videos are stored in Google Cloud Storage (GCS)
- Subject to Google's safety filters and Responsible AI policies

## Quick Start

```elixir
alias Gemini.APIs.Videos
alias Gemini.Types.Generation.Video.VideoGenerationConfig

# Start video generation
{:ok, operation} = Videos.generate(
  "A cat playing piano in a cozy living room"
)

# Wait for completion (automatic polling)
{:ok, completed_op} = Videos.wait_for_completion(operation.name)

# Extract video URIs
{:ok, videos} = Gemini.Types.Generation.Video.extract_videos(completed_op)

# Get the GCS URI
video_uri = hd(videos).video_uri
IO.puts("Video ready: #{video_uri}")
```

## Generating Videos

### Basic Generation

```elixir
# Default configuration (8 seconds, 16:9, 24fps)
{:ok, operation} = Videos.generate(
  "A serene mountain landscape with flowing river"
)
```

### Custom Configuration

```elixir
config = %VideoGenerationConfig{
  number_of_videos: 2,
  duration_seconds: 4,
  aspect_ratio: "9:16",  # Vertical for mobile
  fps: 30,
  compression_format: :h265
}

{:ok, operation} = Videos.generate(
  "Cinematic drone shot of a futuristic city at night",
  config
)
```

### Video Durations

Supported durations:
- `4` seconds - Shorter, faster generation
- `8` seconds - Default, more content

```elixir
config = %VideoGenerationConfig{
  duration_seconds: 4
}

{:ok, operation} = Videos.generate("Quick action sequence", config)
```

### Aspect Ratios

Supported aspect ratios:
- `"16:9"` - Horizontal/desktop (1280x720) - default
- `"9:16"` - Vertical/mobile (720x1280)
- `"1:1"` - Square (1024x1024)

```elixir
# Vertical video for social media
config = %VideoGenerationConfig{
  aspect_ratio: "9:16",
  duration_seconds: 4
}

{:ok, operation} = Videos.generate(
  "A person dancing in a vibrant street",
  config
)
```

### Frame Rates

Supported frame rates:
- `24` fps - Cinematic (default)
- `25` fps - PAL standard
- `30` fps - Smoother motion

```elixir
config = %VideoGenerationConfig{
  fps: 30,
  duration_seconds: 8
}

{:ok, operation} = Videos.generate("Fast-paced sports action", config)
```

## Waiting for Completion

### Automatic Polling

The recommended way to wait for video generation:

```elixir
{:ok, operation} = Videos.generate("A beautiful sunset over ocean")

# Wait with automatic polling
{:ok, completed} = Videos.wait_for_completion(
  operation.name,
  poll_interval: 10_000,  # Check every 10 seconds
  timeout: 300_000,       # Wait up to 5 minutes
  on_progress: fn op ->
    if progress = Gemini.Types.Operation.get_progress(op) do
      IO.puts("Progress: #{progress}%")
    end
  end
)

# Check if successful
if Gemini.Types.Operation.succeeded?(completed) do
  {:ok, videos} = Gemini.Types.Generation.Video.extract_videos(completed)
  IO.puts("Success! Video: #{hd(videos).video_uri}")
else
  IO.puts("Failed: #{completed.error.message}")
end
```

### Manual Polling

For more control over the polling process:

```elixir
{:ok, operation} = Videos.generate("A cat playing with toys")

# Poll manually in a loop
defmodule VideoPoller do
  def poll_until_complete(operation_name, max_attempts \\ 30) do
    poll_loop(operation_name, 0, max_attempts)
  end

  defp poll_loop(operation_name, attempt, max_attempts) when attempt < max_attempts do
    {:ok, op} = Videos.get_operation(operation_name)

    cond do
      Gemini.Types.Operation.succeeded?(op) ->
        {:ok, op}

      Gemini.Types.Operation.failed?(op) ->
        {:error, op.error}

      true ->
        # Still running, wait and try again
        Process.sleep(10_000)
        poll_loop(operation_name, attempt + 1, max_attempts)
    end
  end

  defp poll_loop(_operation_name, _attempt, _max_attempts) do
    {:error, "Timeout: Video generation took too long"}
  end
end

case VideoPoller.poll_until_complete(operation.name) do
  {:ok, completed} ->
    {:ok, videos} = Gemini.Types.Generation.Video.extract_videos(completed)
    IO.puts("Video ready!")

  {:error, reason} ->
    IO.puts("Error: #{inspect(reason)}")
end
```

### Progress Tracking

Monitor generation progress:

```elixir
{:ok, operation} = Videos.generate("An animated forest scene")

# Wrap operation for video-specific helpers
video_op = Videos.wrap_operation(operation)

IO.puts("Progress: #{video_op.progress_percent}%")
IO.puts("ETA: #{video_op.estimated_completion_time}")
```

## Working with Generated Videos

### Downloading Videos

Videos are stored in GCS and can be downloaded:

```elixir
{:ok, completed} = Videos.wait_for_completion(operation.name)
{:ok, videos} = Gemini.Types.Generation.Video.extract_videos(completed)

video = hd(videos)

# GCS URI format: gs://bucket-name/path/to/video.mp4
gcs_uri = video.video_uri

# Download using Google Cloud Storage client or gsutil
# gsutil cp #{gcs_uri} ./my_video.mp4
```

### Video Metadata

```elixir
{:ok, videos} = Gemini.Types.Generation.Video.extract_videos(completed_op)
video = hd(videos)

IO.inspect(video.mime_type)          # "video/mp4"
IO.inspect(video.duration_seconds)   # 8.0
IO.inspect(video.resolution)         # %{"width" => 1280, "height" => 720}
IO.inspect(video.safety_attributes)  # Safety classification
IO.inspect(video.rai_info)           # Responsible AI info
```

## Advanced Configuration

### Compression Formats

```elixir
# H.264 (widely compatible, default)
config = %VideoGenerationConfig{
  compression_format: :h264
}

# H.265 (better quality, smaller file size)
config = %VideoGenerationConfig{
  compression_format: :h265
}
```

### Negative Prompts

Specify what to avoid in the video:

```elixir
config = %VideoGenerationConfig{
  negative_prompt: "blurry, low quality, distorted, shaky camera",
  guidance_scale: 10.0
}

{:ok, operation} = Videos.generate("High quality cinematic shot", config)
```

### Guidance Scale

Control how closely the model follows your prompt:

```elixir
# Lower values = more creative/varied
config = %VideoGenerationConfig{
  guidance_scale: 5.0
}

# Higher values = stricter adherence to prompt
config = %VideoGenerationConfig{
  guidance_scale: 15.0
}
```

### Reproducible Generation

Use seeds for consistent results:

```elixir
config = %VideoGenerationConfig{
  seed: 12345,
  number_of_videos: 1
}

# Generate the same video multiple times
{:ok, op1} = Videos.generate("A red balloon floating", config)
{:ok, op2} = Videos.generate("A red balloon floating", config)
# Videos will be identical
```

## Safety and Content Filtering

### Safety Filter Levels

```elixir
# Strict filtering (recommended for public applications)
config = %VideoGenerationConfig{
  safety_filter_level: :block_most
}

# Moderate filtering (default)
config = %VideoGenerationConfig{
  safety_filter_level: :block_some
}

# Permissive filtering
config = %VideoGenerationConfig{
  safety_filter_level: :block_few
}
```

### Person Generation Policy

```elixir
# Allow adult humans (18+)
config = %VideoGenerationConfig{
  person_generation: :allow_adult
}

# Allow people of all ages
config = %VideoGenerationConfig{
  person_generation: :allow_all
}

# Don't generate recognizable people (default)
config = %VideoGenerationConfig{
  person_generation: :dont_allow
}
```

## Operation Management

### Listing Operations

```elixir
# List all video generation operations
{:ok, response} = Videos.list_operations()

Enum.each(response.operations, fn op ->
  IO.puts("#{op.name}: #{if op.done, do: "complete", else: "running"}")
end)

# List only completed operations
{:ok, response} = Videos.list_operations(filter: "done=true")

# Pagination
{:ok, response} = Videos.list_operations(page_size: 10)

if Gemini.Types.ListOperationsResponse.has_more_pages?(response) do
  {:ok, next_page} = Videos.list_operations(
    page_token: response.next_page_token
  )
end
```

### Canceling Operations

```elixir
{:ok, operation} = Videos.generate("A long video")

# Cancel if taking too long
:ok = Videos.cancel(operation.name)
```

## Error Handling

```elixir
case Videos.generate("A realistic video") do
  {:ok, operation} ->
    case Videos.wait_for_completion(operation.name) do
      {:ok, completed} ->
        if Gemini.Types.Operation.succeeded?(completed) do
          {:ok, videos} = Gemini.Types.Generation.Video.extract_videos(completed)
          IO.puts("Success! Generated #{length(videos)} videos")
        else
          IO.puts("Generation failed: #{completed.error.message}")
        end

      {:error, :timeout} ->
        IO.puts("Timeout: Video generation took too long")
        # Operation may still complete later
        Videos.cancel(operation.name)

      {:error, reason} ->
        IO.puts("Error: #{inspect(reason)}")
    end

  {:error, %{type: :auth_error}} ->
    IO.puts("Authentication failed. Check Vertex AI credentials.")

  {:error, %{type: :api_error, message: msg}} ->
    IO.puts("API error: #{msg}")

  {:error, reason} ->
    IO.puts("Error: #{inspect(reason)}")
end
```

## Best Practices

### 1. Be Specific and Descriptive

```elixir
# Vague
"A landscape"

# Specific
"Cinematic aerial drone shot slowly panning over a serene mountain lake at sunrise, with mist rising from the water and golden light illuminating snow-capped peaks in the background"
```

### 2. Specify Camera Movement

```elixir
prompts = [
  "Static shot of a bustling city street",
  "Slow zoom in on a blooming flower",
  "Pan left across a vast desert landscape",
  "Dolly forward through a dark forest",
  "Orbital shot circling around a modern building"
]
```

### 3. Use Temporal Descriptions

```elixir
"Time-lapse of clouds moving across the sky at sunset"
"Slow motion shot of water droplets splashing"
"Quick cut montage of city life"
```

### 4. Batch Processing

```elixir
prompts = [
  "A red car driving down a highway",
  "A blue ocean with waves crashing",
  "A green forest with sunlight filtering through trees"
]

config = %VideoGenerationConfig{
  duration_seconds: 4,
  aspect_ratio: "16:9"
}

# Start all generations
operations = prompts
|> Enum.map(fn prompt ->
  {:ok, op} = Videos.generate(prompt, config)
  op
end)

# Wait for all to complete
results = operations
|> Task.async_stream(fn op ->
  Videos.wait_for_completion(op.name, timeout: 300_000)
end, timeout: 310_000)
|> Enum.to_list()
```

### 5. Handle Long-Running Operations

```elixir
# Start generation
{:ok, operation} = Videos.generate("Epic cinematic scene")

# Store operation name for later
operation_id = operation.name

# Later, in another process/request
{:ok, current_status} = Videos.get_operation(operation_id)

if current_status.done do
  {:ok, videos} = Gemini.Types.Generation.Video.extract_videos(current_status)
  # Process videos
else
  IO.puts("Still generating... #{current_status.metadata}")
end
```

## Performance Considerations

### Generation Time

Typical generation times:
- **4 seconds** video: ~2-3 minutes
- **8 seconds** video: ~3-5 minutes

Factors affecting speed:
- Video duration (longer = slower)
- Complexity of the prompt
- Resolution and frame rate
- System load

### Resource Management

```elixir
# Limit concurrent video generations
max_concurrent = 3

prompts
|> Task.async_stream(
  fn prompt ->
    {:ok, op} = Videos.generate(prompt)
    Videos.wait_for_completion(op.name)
  end,
  max_concurrency: max_concurrent,
  timeout: 600_000  # 10 minutes per video
)
|> Enum.to_list()
```

## Configuration Options

### `VideoGenerationConfig`

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `number_of_videos` | `1..4` | `1` | Number of videos to generate |
| `duration_seconds` | `4` or `8` | `8` | Video duration |
| `aspect_ratio` | `String.t()` | `"16:9"` | Video aspect ratio |
| `fps` | `24`, `25`, or `30` | `24` | Frames per second |
| `compression_format` | `:h264` or `:h265` | `:h264` | Video compression |
| `safety_filter_level` | `atom()` | `:block_some` | Content filtering level |
| `negative_prompt` | `String.t()` | `nil` | What to avoid |
| `seed` | `integer()` | `nil` | Random seed for reproducibility |
| `guidance_scale` | `float()` | `nil` | Prompt adherence (1.0-20.0) |
| `person_generation` | `atom()` | `:dont_allow` | Person generation policy |

## Troubleshooting

### Operation Times Out

```elixir
# Increase timeout
{:ok, completed} = Videos.wait_for_completion(
  operation.name,
  timeout: 600_000  # 10 minutes
)

# Or poll manually with longer intervals
{:ok, op} = Videos.get_operation(operation.name)
```

### Content Blocked by Safety Filters

```elixir
{:ok, completed} = Videos.wait_for_completion(operation.name)

if completed.error do
  IO.puts("Error: #{completed.error.message}")
  # Try with different prompt or safety settings
end
```

### Downloading from GCS

```elixir
# Use Google Cloud Storage client
# Or gsutil command line tool:
# gsutil cp gs://bucket/path/video.mp4 ./local_video.mp4

# With authentication
# gcloud auth application-default login
# gsutil cp #{video.video_uri} ./output.mp4
```

## See Also

- [Vertex AI Veo Documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/video/overview)
- [Image Generation Guide](image_generation.md)
- [Operations API Guide](operations.md)
- [Long-Running Operations](../README.md#long-running-operations)