Generated banner for Gemini Omni Flash API
Generated logo for Gemini Omni Flash API

Gemini Omni Flash API

Google (Gemini)OfficialPublished by Google (Gemini)

  • Skill
  • Tool integration
  • Data and AI
  • Apache-2.0
  • Low risk
  • Has scripts

From the author

Use this skill for generative video editing, text-to-video, image- and video-referenced video generation, first-frame-to-video, first-and-last-frame transitions, and video extensions using Gemini Omni 1.2 Flash (gemini-omni-1.2-flash) via the official google-genai SDK. Includes workflows for pre-processing/optimizing high-resolution or long source videos with ffmpeg, stripping audio for full sound regeneration, and handling turn-by-turn video editing and parallel execution.
— the description by Google (Gemini)

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a set of Python scripts to interface with the Gemini Omni 1.2 Flash model via the google-genai SDK. It enables users to generate videos from text, images, or reference videos, as well as perform video editing, frame-to-frame transitions, and video extensions. The toolset includes utilities for pre-processing media with ffmpeg, inspecting video metadata, and managing file uploads to the Gemini Files API.

What it does

  • Generates videos from text prompts, single images, or two-frame keyframe transitions.
  • Extends existing video clips by 3 to 10 seconds per turn, up to a 30-second total.
  • Performs video editing tasks such as style changes or object modification.
  • Supports reference-guided generation using uploaded images or videos.
  • Provides utilities to trim, scale, and format source videos for model compatibility.
  • Enables parallel batch processing of generation and editing jobs.

When to use it

  • When you need to generate new video content from text or image inputs.
  • When you need to extend or edit existing video clips within specified duration limits.
  • When you need to prepare or inspect video files before using them with the Gemini API.
  • When you require programmatic control over video generation parameters like resolution and aspect ratio.

Advantages

  • Supports multiple generation modes including text-to-video and image-to-video.
  • Includes built-in pre-processing tools for video normalization and audio stripping.
  • Allows for turn-by-turn editing and extension of previous interactions.
  • Provides flexible configuration for resolution, duration, and batch execution.

Usage examples

“Generate a 10-second video of a cat drinking tea.”
The tool uses generate_video.py to create the video and saves it to the specified local path.
“Extend this video by 5 seconds and add a character from this reference image.”
The tool uses the --extend and --image flags to generate a continuation of the source video incorporating the reference.
“Transform the style of this video to anime and strip the original audio.”
The tool processes the video with --strip-audio to allow for full audio regeneration in the new style.
“Prepare a 15-second video for use as a 3-second reference clip.”
The tool uses prep_video.py to trim the source video to the required 3-second duration.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/google-gemini/gemini-omni-flash-api/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip gemini-omni-flash-api.zip -d .agents/skills

Documentation by Google (Gemini)

Gemini Omni Flash Skill

This skill uses the Gemini Omni 1.2 Flash model (gemini-omni-1.2-flash) to perform text-to-video generation, image-to-video generation (first-frame and two-frame keyframe transitions), reference-to-video (R2V) generation, video extensions (3s–10s per turn, up to 30s total), and video editing.

[!WARNING] Important Regional Restrictions: Uploading videos to use for video edits, extensions, or references is NOT available in the EEA, Switzerland, the United Kingdom, and some US states. If a video-to-video edit completes quickly with empty outputs (total_output_tokens: 0 or no video content), it is likely due to this restriction.

Core capabilities

  1. Text to video: Generating videos (3s–15s, default 10s) from a text prompt.

  2. First frame to video: Generating videos (3s–15s, default 10s) from a starting image (--first-frame).

  3. First and last frame transition: Generating videos (3s–10s) interpolating between a starting image and a final image (--first-frame and --last-frame; note: --last-frame must be used with --first-frame).

  4. Video extensions: Extending existing videos by 3 to 10 seconds per turn (10s default), up to a cumulative length of 30 seconds (--extend or --previous-interaction-id).

  5. Video editing and refinement: Editing existing videos (uploaded source video up to 10 seconds), applying stylistic changes, or performing object addition/removal while keeping everything else consistent.

  6. Reference-to-video (R2V) & image-guided editing/extension: Guiding new video generation (3s–15s) with reference images (--image, up to 6, including 15s clips) or reference videos (--video-reference, up to 3 for 3s–10s clips and max 1 for 11s–15s clips). Reference images (--image) also work during video editing and prompt-based video extension, whereas only reference videos (--video-reference) are restricted to new video generation.

Workflow

  1. Analyze request: Determine the target workflow (text-to-video, first-frame-to-video, two-frame keyframe transition, video extension, video editing, or reference-to-video generation) and inspect any input media assets.

  2. Run SDK scripts:

    • Directly run the appropriate utility (scripts/video/generate_video.py or scripts/upload_file.py).

    • Configure settings like --aspect-ratio (16:9 default, 9:16), --resolution (360p, 720p default, 1080p, 4k), and --duration (integer from 3 to 15 seconds, default 10; 3 to 10 seconds when combining --first-frame and --last-frame or extending). Note: 4k requests take longer to generate.

  3. Retrieve and process output: Outputs are saved to the local filesystem (e.g. media/). Report back the completed media path to the user.

Reference Documentation

  • Video Generation & Editing (Omni Flash): Full parameter reference, duration limits, and prompting patterns at aistudio.google.com/docs/omni.md.

  • Interactions API: All operations and state management for gemini-omni-1.2-flash use the Interactions API.

  • Files API: Input media files (reference images and videos) are uploaded via the Files API and polled until ACTIVE before passing their URI and MIME type in interactions.create.

  • Gemini API Skill Reference: Platform-wide guidelines, current model specifications, and SDK usage rules for the Gemini API.

Dependencies and Prerequisites

  • Python SDK (google-genai): Requires google-genai >= 2.30.1 (Python) to support the current interactions steps schema, output helpers (interaction.output_video), and full video output resolution configuration (360p, 720p, 1080p, 4k). Install or upgrade using:

    pip install -U google-genai
  • Python Runtime: Requires Python >= 3.10 (for compatibility with modern google-genai SDK types and methods).

  • ffmpeg & ffprobe: prep_video.py, inspect_video.py, and generate_video.py (when stripping audio via --strip-audio) require ffmpeg and ffprobe binaries installed and available in your system PATH.

  • API Key: Set the GEMINI_API_KEY environment variable:

    export GEMINI_API_KEY="your-api-key"

Available scripts

Use the following Python scripts to upload media with the Files API, prepare input videos with ffmpeg, and generate video outputs using the Interactions API.

  1. upload_file.py: Uploads local media (images and videos) to the Files API and polls until ACTIVE. If uploading a video larger than 25MB, it prints an informative warning/tip highlighting that Gemini Omni Flash is optimized for editing 10s source videos at 720p/24fps, and recommends pre-processing with prep_video.py first to speed up the upload.

    ./scripts/upload_file.py path/to/image.png
  2. generate_video.py: Performs end-to-end video generation and downloads the output video. It detects and uploads local media references (images or videos) before calling the Interactions API.

    • Text to video (3s–15s, default 10s):

      ./scripts/video/generate_video.py "A close-up of a cat drinking tea" --duration 12 --output media/cat_tea.mp4
    • Output resolution options (--resolution):

      Gemini Omni 1.2 Flash natively supports four output resolutions across both landscape (16:9) and portrait (9:16) aspect ratios:

      • 360p: 640x360 (16:9) or 360x640 (9:16)

      • 720p: 1280x720 (16:9) or 720x1280 (9:16) — (default)

      • 1080p: 1920x1080 (16:9) or 1080x1920 (9:16)

      • 4k: 3840x2160 (16:9) or 2160x3840 (9:16)

      # High-definition (1080p)
      ./scripts/video/generate_video.py "A cinematic drone shot over misty mountains at sunrise" --resolution 1080p --output media/mountains_1080p.mp4
      
      # Ultra-high-definition 4K (Note: 4K requests take longer to generate; pass --timeout if needed)
      ./scripts/video/generate_video.py "A macro shot of a dewdrop on a flower petal in golden sunlight" --resolution 4k --timeout 900 --output media/flower_4k.mp4
    • Configurable request timeouts (--timeout):

      Default HTTP timeout is 600 seconds (10 minutes). For computationally intensive requests — such as generating 15s 4K clips or extending a clip up to the maximum 30s total length — generation can take several minutes. Use --timeout 900 (or 1200) to provide an extended execution budget.

    • First frame to video (3s–15s):

      ./scripts/video/generate_video.py "The waves crash against the shore." --first-frame start.png --duration 12 --output media/waves.mp4
    • First and last frame transition (3s–10s max):

      Provide a starting frame and an ending frame to generate a smooth transition between them (--last-frame must be used together with --first-frame, and two-frame keyframe interpolation is capped at 10 seconds):

      ./scripts/video/generate_video.py "A smooth timelapse from sunrise to sunset" --first-frame start.png --last-frame end.png --duration 10 --output media/interpolation.mp4
    • Looping video (identical start and end frame, 3s–10s):

      ./scripts/video/generate_video.py "A crystal orb spinning continuously in place" --first-frame orb.png --last-frame orb.png --output media/loop.mp4
    • Image-referenced video generation (R2V):

      Describe how the uploaded reference image(s) should guide the generation in natural language:

      ./scripts/video/generate_video.py "A cybernetic warrior walking through a rain-soaked street in the painterly style of the reference image" --image reference.png --output media/warrior.mp4
    • Video-referenced video generation:

      Provide reference video(s) (--video-reference / -vr) when generating a new video to guide character appearance, object details, or camera/motion style (ideal reference clip duration is ~3s; up to 3 reference videos for 3s–10s clips, and at most 1 reference video for 11s–15s clips; unlike --image, --video-reference cannot be used for video editing or extension):

      ./scripts/video/generate_video.py "A cellist performing on stage with the expressive camera movement and lighting style of the reference video" --video-reference ref_camera.mp4 --output media/cello.mp4
    • Video extension (3s–10s per turn, up to 30s total):

      ./scripts/video/generate_video.py "The scene continues as the sun sets below the horizon" --extend media/sunset.mp4 --duration 10 --output media/sunset_extended.mp4
    • Video extension with reference images:

      Prompt-based extension (omitting --task) supports passing reference images (--image, up to 6 images) to introduce new characters, objects, or styles into the extended scene (only reference videos --video-reference are restricted to new video generation):

      ./scripts/video/generate_video.py "Extend this video: the character shown in the reference image walks into the frame and waves." --extend media/sunset.mp4 --image character.png --output media/sunset_extended_with_ref.mp4
    • Video editing (keep original audio, optionally with reference images):

      ./scripts/video/generate_video.py "Transform the style to Japanese anime. Keep everything else the same." --video input.mp4 --output media/anime_style.mp4
      
      # Guide an edit with a reference image (--image works for editing too; only --video-reference is restricted):
      ./scripts/video/generate_video.py "Replace the coat with the red coat from the reference image. Keep everything else the same." --video input.mp4 --image red_coat.png --output media/coat_swap.mp4
    • Video editing (regenerate all audio from scratch):

      ./scripts/video/generate_video.py "Transform the style to Japanese anime" --video input.mp4 --strip-audio --output media/anime_style_new_audio.mp4
    • Turn-by-turn video editing (edit previous interaction):

      Edit a prior video generation without re-uploading assets by passing the interaction ID:

      ./scripts/video/generate_video.py "Change the setting to a snowy winter wonderland. Keep everything else the same." --previous-interaction-id "v1_..." --output media/winter_wonderland.mp4
    • Turn-by-turn video extension (extend previous interaction):

      Extend a prior video generation by 3s–10s by passing the previous interaction ID:

      ./scripts/video/generate_video.py "Extend this video. The character turns around and begins to run." --previous-interaction-id "v1_..." --duration 10 --output media/extended_turn.mp4
    • Parallel batch execution (prompts file): Run multiple prompts from a line-by-line text file concurrently:

      ./scripts/video/generate_video.py --prompts-file prompts.txt --concurrency 3
    • Parallel batch execution (JSON config): Execute fully configured, distinct generation and editing jobs in parallel:

      ./scripts/video/generate_video.py --batch jobs.json --concurrency 3

      Example jobs.json:

      [
        {
          "prompt": "A smooth timelapse from sunrise to sunset.",
          "first_frame": "start.png",
          "last_frame": "end.png",
          "duration": 10,
          "resolution": "1080p",
          "output": "media/interpolation.mp4"
        },
        {
          "prompt": "Extend this video: the character shown in the reference image enters the scene.",
          "extend": "media/sunset.mp4",
          "image": "character.png",
          "duration": 15,
          "output": "media/extended_with_image_ref.mp4"
        },
        {
          "prompt": "A macro shot of a crystal orb refracting cosmic nebula colors.",
          "duration": 15,
          "resolution": "4k",
          "output": "media/nebula_orb_4k.mp4"
        },
        {
          "prompt": "A musician playing cello with the camera motion of the reference video.",
          "video_reference": "cello_ref.mp4",
          "output": "media/cello.mp4"
        },
        {
          "prompt": "Transform the style to Japanese anime. Keep everything else the same.",
          "video": "input.mp4",
          "output": "media/anime_style.mp4",
          "strip_audio": false,
          "aspect_ratio": "16:9"
        }
      ]
  3. inspect_video.py: Inspects a local video file (using ffprobe) to check its duration, resolution, frame rate (FPS), audio stream presence, and format details.

    ./scripts/video/inspect_video.py media/output.mp4
    • To get a pre-parsed, structured JSON summary:

      ./scripts/video/inspect_video.py media/output.mp4 --json
    • To get the complete, unmodified ffprobe raw JSON dump:

      ./scripts/video/inspect_video.py media/output.mp4 --raw
  4. prep_video.py: Normalizes, trims, and formats any video file to fit standard Gemini Omni Flash editing and uploaded-extension input limits (up to 10s source length). It handles timecode-based trimming, optional frame rate conversion, and proportional scaling of large videos (max 1280x720 for landscape, 720x1280 for portrait) to optimize upload times without stretching. If the video is longer than 10 seconds and the script is run interactively (in a TTY), it prompts the user to select the first 10s, last 10s, or enter a custom timecode (defaulting to the first 10s).

    • Trim first 10s (default):

      ./scripts/video/prep_video.py path/to/source.mp4

      or explicitly specify the start and duration:

      ./scripts/video/prep_video.py path/to/source.mp4 --start 0 --duration 10
    • Trim a 3s reference clip for R2V (--video-reference):

      ./scripts/video/prep_video.py path/to/source.mp4 --start 0 --duration 3 --output media/ref_3s.mp4
    • Trim last 10s (automatically calculates starting point based on source length):

      ./scripts/video/prep_video.py path/to/source.mp4 --start last
    • Trim 10s starting at specific timecode (MM:SS or HH:MM:SS):

      ./scripts/video/prep_video.py path/to/source.mp4 --start 00:03 --output media/custom.mp4
    • Custom frame rate and resolution:

      ./scripts/video/prep_video.py path/to/source.mp4 --fps 30 --resolution 1920x1080
    • Strip audio for audio regeneration:

      ./scripts/video/prep_video.py path/to/source.mp4 --strip-audio --output media/video_with_no_audio.mp4

Audio handling in video editing

When editing a source video that contains audio, you must choose between keeping the original audio or regenerating all audio from scratch.

  • Keep original audio: By default, Gemini Omni Flash preserves the existing audio layer (though it may modify or adapt it slightly during generation). Use this when the original background music, dialogue, or sound effects are desired.

  • Regenerate all audio from scratch: If you want Gemini Omni Flash to re-create a brand-new audio layer tailored to the new visual style or prompt, you must upload the video with its audio stream stripped out. If any audio stream is present, Gemini Omni Flash will attempt to preserve/modify it instead of starting from scratch.

    • Use --strip-audio (or -a) when pre-processing with scripts/video/prep_video.py or executing scripts/video/generate_video.py.

    • This forces Gemini Omni Flash to perform full audio generation.

Prompting Gemini Omni 1.2 Flash

Single scene

By default Gemini Omni Flash will try to create a video with a few different shots. It'll attempt to craft an interesting narrative based on the prompt.

If you need the output video to contain a single scene, prompt explicitly for it:

  • In a single unbroken scene

  • In a single continuous shot

  • No scene cuts

For example:

Continuous, unbroken handheld shot of a fluffy tabby cat sitting on a sunny windowsill, looking out into a leafy garden. The cat's tail twitches slowly, and its ears rotate slightly toward ambient noises. Sunbeams illuminate dust motes in the air. Sound design: Gentle breeze, distant bird chirps. No dialogue.
Removing unwanted elements

If the generated video contains things you don't want, include simple negative prompts to avoid them:

  • No dialogue

  • No embellishments

  • No extra sound effects

Prompts for editing

Simple prompts work best for video editing. Overly descriptive prompts can lead to unintended changes.

Examples of effective editing prompts:

  • Make this video anime

  • Put a fashionable hat on this person

  • Change the lighting to be more dramatic

  • Change the text on the sign to say "Omni Flash"

When editing a specific aspect of the video, include "Keep everything else the same" to maintain visual consistency:

  • Avoid: In the video of the man sitting on the sofa, please add a small black cat that runs from the right side of the screen, jumps onto his lap, and then he starts to stroke its head while looking down.

    • Simplify: Add a cat that jumps onto his lap, he begins to pet it. Keep everything else the same.

  • Avoid: Please remove the cell phone that the person is holding in their hand and fill in the background so it looks like they are just holding their hand empty.

    • Simplify: Make the phone invisible. Keep everything else the same.

Prompting the audio

By default the model generates an appropriate audio track for a video. Use your prompt to specify the exact soundscape or music style:

  • Include calm background music

  • The video has a high energy techno beat

  • The audio is a low tinny radio broadcast in the background, playing a song

Timing events

You can prompt for events at specific timestamps using natural language or bracketed timecodes:

  • After 3 seconds, a woman enters the scene.

  • At 5s the chorus starts in the background audio.

  • Every 2s cut to a new frame.

  • In a rapid fire sequence, every half a second (12 frames at 24fps) change the scene to a new location.

Timecode syntax example (up to 15s):

[0-5s] A runner stretches at the starting line in early morning mist
[5-10s] They sprint forward along the wet coastal road
[10-15s] Wide aerial shot as the sun breaks over the cliffs
Meta prompting

You can ask Gemini Omni Flash to pay attention to general qualities or principles of video generation:

  • Consider micro-detail, expression and timing to create a rich, detailed, natural scene.

  • Be specific in your descriptions of characters and environments, applying costume and production design details.

  • Include realistic background activity so the environment feels lived-in.

  • Make a rapid-fire video that shows a different rare [thing] every 1s with upbeat music and on-screen labels.

Text in videos

You can prompt to include readable on-screen or diegetic text:

  • One word on the screen at a time: "did, you, know, that, Omni, can, do, awesome, text?" Each word appears for 1s with a different animated style. No dialogue.

  • There is a street sign that says: "This is an AI generation by Omni", there is a storefront that says: "All you need AI", there's a car with the number plate: "OMNI1.2"

Prompts for extending a video

With Gemini Omni 1.2 Flash you can extend videos with prompts like "Extend this video" or "The scene continues" by 3s to 10s per turn (10s default), up to a cumulative length of 30s.

Omni creates a continuation that keeps visuals, motion, characters, and audio coherent by using up to the last 10s of your original video as context. Some final frames of the source clip may be blended to make the transition seamless.

[!TIP] Extending with Reference Images (Omit task): Leave generation_config.video_config.task unset when extending via prompt ("Extend this video..."). Omitting task lets you pass reference images (--image) alongside the source video to introduce new characters, objects, or styles into the extended clip. Setting task="extend" enforces a strict single-video gateway constraint and blocks reference images.

When extending, apply these guidelines:

  • Describe how the audio evolves in the extended segment, e.g., "The music swells into the chorus".

  • Specify whether the shot continues unbroken or cuts to a new angle with the same characters, e.g., "Cut to a wide shot showing the same two characters entering the station".

  • Include reference images (--image) when introducing new characters or props into the continuation, e.g., "Extend this video: the person in the reference image enters through the doorway".

  • Timestamps in extension prompts are relative to the start of the extended segment (0s = start of the extension).

Video extension & reference constraints
  • Uploaded source video limit: Uploaded input videos for editing or extension must be 10 seconds or shorter (multi-turn extensions via previous_interaction_id can extend clips by 3s–10s per turn up to 30s total).

  • Two-frame keyframe cap: First-and-last-frame interpolation (--first-frame + --last-frame) supports 3s to 10s (11s–15s requires single-frame, text-to-video, or R2V).

  • Reference images (--image) work across generation, editing, and extension: You can pass up to 6 reference images (including on 15s clips) for standard reference-to-video (R2V) generation, video editing (--video or --previous-interaction-id), and prompt-based video extension (--extend or --previous-interaction-id, when --task is omitted).

  • Only reference videos (--video-reference) are restricted: Unlike reference images, reference videos are supported only when generating a new video — not during video editing (--video or --previous-interaction-id) or video extension (--extend or --previous-interaction-id) — and 11s–15s video generations support at most 1 reference video (up to 3 reference videos for 3s–10s clips; ideal reference clip length is ~3s, trim longer clips with prep_video.py --duration 3).

  • Spoken dialogue on uploaded videos: You cannot extend an uploaded video where someone is already speaking in order to add new spoken dialogue (multi-turn voice extension via previous_interaction_id is supported).

  • task parameter is fallback-only: Rely on natural-language prompting by default and pass --task (generation_config.video_config.task) only as a fallback when natural-language prompting alone fails to select the intended mode.

Binding uploaded images and videos with natural language

Gemini Omni 1.2 Flash binds uploaded images and videos to their roles using natural language based on their order in input — do not use deprecated <FIRST_FRAME>, <LAST_FRAME>, <IMAGE_REF_N>, <VIDEO_REF_N>, or [# Sources ...] prompt tags.

  • Starting frame (--first-frame): Upload one image followed by your motion prompt, e.g., "Use this image as the starting frame. The camera slowly pushes in as neon signs flicker on."

  • Starting and ending keyframes (--first-frame + --last-frame, up to 10s): Upload the start image first and the end image second, e.g., "Use the first image as the starting frame and the second image as the final frame, smoothly transitioning from day to night." (Pass the same image twice for a seamless loop: "Use this image as both the starting frame and the final frame.")

  • Multiple reference images (--image): Upload up to several reference images and refer to them clearly by order or visual content, e.g., "A studio fashion sequence: the woman from the first image holds the leather bag from the second image while walking across the set shown in the third image. Use the given images as character, prop, and environment references rather than literal starting frames."

  • Combined image + video references in R2V (--image + --video-reference): Upload the reference image(s) and reference video(s) and describe each asset's role directly, e.g., "The violinist shown in the reference image performs on a dimly lit stage using the bow movement and camera framing from the reference video."

Files

scripts/upload_file.py

#!/usr/bin/env python3
"""
Uploads a file to the Gemini Files API and waits for it to become ACTIVE.
Uses the official google-genai SDK.
"""

import argparse
import json
import mimetypes
import os
import re
import sys
import time
import urllib.parse
from google import genai
from google.genai import types, errors

def get_api_key():
    return os.environ.get("GEMINI_API_KEY")

def strip_query_params(url):
    """Strips query parameters from URL for clean logging and security."""
    if not url:
        return ""
    parsed = urllib.parse.urlparse(url)
    return urllib.parse.urlunparse(parsed._replace(query=""))

def sanitize_error(err):
    """Sanitizes error messages by redacting API keys, tokens, query parameters, internal provider URLs, and raw response bodies."""
    if not err:
        return ""

    # If it is an SDK APIError, prefer clean code + status + message over raw dictionary dump
    if isinstance(err, errors.APIError):
        parts = []
        if getattr(err, "code", None):
            parts.append(str(err.code))
        if getattr(err, "status", None):
            parts.append(f"({err.status})")
        if getattr(err, "message", None):
            parts.append(f": {err.message}")
        elif getattr(err, "details", None):
            parts.append(f": {err.details}")
        err_str = " ".join(parts) if parts else str(err)
    else:
        err_str = str(err)

    # 1. Directly redact the active API key value if present
    key = get_api_key()
    if key and len(key) >= 8:
        err_str = err_str.replace(key, "[REDACTED_KEY]")

    # 2. Redact known Google API key & OAuth token patterns:
    # Classic Google API key: AIza... (30-45 chars)
    err_str = re.sub(r'AIza[0-9A-Za-z_\-]{30,}', '[REDACTED_KEY]', err_str)
    # Newer Google Cloud / Gemini API key: AQ....
    err_str = re.sub(r'AQ\.[0-9A-Za-z_\-]{20,}', '[REDACTED_KEY]', err_str)
    # Google OAuth access token: ya29....
    err_str = re.sub(r'ya29\.[0-9A-Za-z_\-]+', '[REDACTED_TOKEN]', err_str)
    # Bearer tokens
    err_str = re.sub(r'Bearer\s+[A-Za-z0-9_\-\.]+', 'Bearer [REDACTED_TOKEN]', err_str, flags=re.IGNORECASE)

    # 3. Strip sensitive query parameters from any URLs (key, token, secret, signature)
    err_str = re.sub(r'([?&](?:key|api_key|apiKey|access_token|auth|signature)=)[^&\s"\'<>()]+', r'\1[REDACTED]', err_str)

    # 4. Strip internal Google / provider infrastructure URLs & hosts
    err_str = re.sub(r'https?://[a-zA-Z0-9.\-_]*\.corp\.goog[^\s"\'<>)]*', '[INTERNAL_HOST]', err_str)
    err_str = re.sub(r'https?://[a-zA-Z0-9.\-_]*\.sandbox\.googleapis\.com[^\s"\'<>)]*', '[SANDBOX_ENDPOINT]', err_str)
    err_str = re.sub(r'https?://(?:generativelanguage|aiplatform)\.googleapis\.com/v[0-9a-z_]+/', '[API_ENDPOINT]/', err_str)

    # 5. Redact raw response bodies (once a body= marker is found, redact the remainder)
    err_str = re.sub(r'\bbody\s*=.*\Z', 'body=[REDACTED_BODY]', err_str, flags=re.IGNORECASE | re.DOTALL)

    # 6. Strip HTML error pages / raw HTML response bodies
    if "<html" in err_str.lower() or "<!doctype" in err_str.lower() or "<body" in err_str.lower():
        title_match = re.search(r'<title>(.*?)</title>', err_str, re.IGNORECASE)
        if title_match:
            err_str = f"HTTP Error Response: {title_match.group(1).strip()}"
        else:
            err_str = re.sub(r'<[^>]+>', ' ', err_str)

    # 7. Collapse excessive whitespace and cap oversized error dumps
    err_str = re.sub(r'\s+', ' ', err_str).strip()
    if len(err_str) > 500:
        err_str = err_str[:497] + "..."

    return err_str

def detect_mime_type(file_path):
    """Determines MIME type based on file extension, falling back to standard mimetypes module."""
    ext = os.path.splitext(file_path)[1].lower()
    mime_map = {
        ".png": "image/png",
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".webp": "image/webp",
        ".mp4": "video/mp4",
        ".mp3": "audio/mpeg",
        ".wav": "audio/wav",
        ".pdf": "application/pdf",
        ".txt": "text/plain",
    }

    if ext in mime_map:
        return mime_map[ext]

    mime_type, _ = mimetypes.guess_type(file_path)
    if mime_type:
        return mime_type

    return "application/octet-stream"

def upload_file(file_path, display_name=None):
    """Performs an upload using google-genai SDK, with automatic pre-processing for large videos."""
    if not get_api_key():
        raise RuntimeError("Error: GEMINI_API_KEY environment variable is not set.")

    file_size = os.path.getsize(file_path)
    mime_type = detect_mime_type(file_path)

    # Large video file size check (>25MB)
    is_video = mime_type.startswith("video/")
    if is_video and file_size > 25 * 1024 * 1024:
        size_mb = file_size / (1024 * 1024)
        print(f"\nWARNING: Video file '{file_path}' is very large ({size_mb:.2f} MB)!")
        print("Note: Gemini Omni Flash is optimized for 10s videos at 720p and 24fps. Uploading very large or")
        print("high-resolution videos will significantly increase upload times and may cause Out-Of-Memory (OOM) errors.")

        # Determine if terminal is interactive
        if sys.stdin.isatty():
            print("\nWould you like to automatically pre-process this video first using prep_video.py?")
            print("This will trim, scale, and optimize the video to ensure a fast, OOM-safe upload.")
            try:
                choice = input("Pre-process video? [Y/n]: ").strip().lower()
                if choice in ("", "y", "yes"):
                    prepped_output_path = os.path.join("media", f"prepped_{os.path.basename(file_path)}")
                    os.makedirs("media", exist_ok=True)

                    # Resolve prep_video.py script path
                    import subprocess
                    prep_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "video", "prep_video.py")
                    if not os.path.exists(prep_script):
                        prep_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "prep_video.py")

                    cmd = [sys.executable, prep_script, file_path, "--output", prepped_output_path]
                    print(f"Running: {' '.join(cmd)}")

                    try:
                        result = subprocess.run(cmd)
                        if result.returncode == 0 and os.path.exists(prepped_output_path):
                            file_path = prepped_output_path
                            file_size = os.path.getsize(file_path)
                            print(f"\nPre-processing completed successfully! Proceeding with upload of prepped video ({file_size / (1024*1024):.2f} MB)...")
                        else:
                            raise RuntimeError("Error: Video pre-processing failed. Proceeding with original file upload is not recommended.")
                    except Exception as e:
                        raise RuntimeError(f"Error executing prep_video.py: {sanitize_error(e)}")
                else:
                    proceed_choice = input("Do you want to proceed with uploading the original large video anyway? [y/N]: ").strip().lower()
                    if proceed_choice not in ("y", "yes"):
                        raise RuntimeError("Upload cancelled by user. Please pre-process the video manually first.")
            except (KeyboardInterrupt, EOFError):
                raise RuntimeError("\nNo input received. Upload cancelled to prevent OOM.")
        else:
            # Non-interactive mode
            if file_size > 100 * 1024 * 1024: # Block files larger than 100MB in non-interactive mode
                err_msg = (
                    f"Error: Video file is extremely large ({size_mb:.2f} MB) and script is running in non-interactive mode.\n"
                    "To prevent Out-Of-Memory (OOM) errors, upload has been blocked.\n"
                    "Please pre-process the video first using prep_video.py."
                )
                raise RuntimeError(err_msg)
            else:
                print("Proceeding with upload in non-interactive mode...", file=sys.stderr)

    if not display_name:
        display_name = os.path.basename(file_path)

    print(f"Preparing upload of '{file_path}' ({file_size} bytes, type: {mime_type})...")

    # Step 1: Initialize Client with explicit request timeout bounds (300s = 300,000ms)
    client = genai.Client(http_options=types.HttpOptions(timeout=300 * 1000))

    # Step 2: Upload file using SDK
    print("Uploading file bytes using google-genai SDK...")
    try:
        config = types.UploadFileConfig(
            display_name=display_name,
            mime_type=mime_type,
        )
        file_obj = client.files.upload(file=file_path, config=config)
        # Convert Pydantic File model to dictionary with both camelCase and snake_case keys for compatibility
        file_dict = json.loads(file_obj.model_dump_json())
        # Ensure URI is stripped of any sensitive query parameters
        if "uri" in file_dict and file_dict["uri"]:
            file_dict["uri"] = strip_query_params(file_dict["uri"])
        # Add camelCase field for mimeType
        if "mime_type" in file_dict:
            file_dict["mimeType"] = file_dict["mime_type"]
        return file_dict
    except errors.APIError as e:
        raise RuntimeError(f"API Error uploading file via SDK: {sanitize_error(e)}")
    except Exception as e:
        raise RuntimeError(f"Error uploading file via SDK: {sanitize_error(e)}")

def wait_for_active(file_name, poll_interval=3, max_attempts=60, backoff_factor=1.2, max_interval=15, max_timeout=600):
    """Polls the file status until state is ACTIVE or FAILED using exponential backoff with finite request bounds."""
    if not get_api_key():
        raise RuntimeError("Error: GEMINI_API_KEY environment variable is not set.")

    print(f"Waiting for file {file_name} to finish processing...")

    client = genai.Client(http_options=types.HttpOptions(timeout=30 * 1000))
    attempt = 0
    current_interval = poll_interval
    consecutive_errors = 0
    max_consecutive_errors = 5
    start_time = time.time()

    while attempt < max_attempts:
        if time.time() - start_time > max_timeout:
            raise TimeoutError(f"Error: Maximum timeout ({max_timeout}s) reached waiting for file {file_name} to become ACTIVE.")

        try:
            file_obj = client.files.get(name=file_name)
        except errors.APIError as e:
            consecutive_errors += 1
            if consecutive_errors >= max_consecutive_errors:
                raise RuntimeError(f"Error: Too many consecutive API errors checking status ({sanitize_error(e)}). Exiting.")

            print(f"Warning: API error checking status ({sanitize_error(e)}). Retrying in {current_interval:.1f}s...")
            time.sleep(current_interval)
            current_interval = min(current_interval * backoff_factor, max_interval)
            attempt += 1
            continue
        except Exception as e:
            consecutive_errors += 1
            if consecutive_errors >= max_consecutive_errors:
                raise RuntimeError(f"Error: Too many consecutive errors checking status ({sanitize_error(e)}). Exiting.")

            print(f"Warning: Error checking status ({sanitize_error(e)}). Retrying in {current_interval:.1f}s...")
            time.sleep(current_interval)
            current_interval = min(current_interval * backoff_factor, max_interval)
            attempt += 1
            continue

        # Reset consecutive errors on successful API response
        consecutive_errors = 0
        state = file_obj.state
        state_str = state.name if hasattr(state, "name") else str(state)

        if state == types.FileState.ACTIVE or state_str == "ACTIVE":
            print("File is ACTIVE and ready for generations!")
            file_dict = json.loads(file_obj.model_dump_json())
            if "uri" in file_dict and file_dict["uri"]:
                file_dict["uri"] = strip_query_params(file_dict["uri"])
            if "mime_type" in file_dict:
                file_dict["mimeType"] = file_dict["mime_type"]
            return file_dict
        elif state == types.FileState.FAILED or state_str == "FAILED":
            # Terminal FAILED state: abort immediately without retrying
            err_details = getattr(file_obj, "error", None)
            err_msg = f"Error: File processing failed on backend for '{file_name}' (State: FAILED)"
            if err_details:
                err_msg += f": {sanitize_error(err_details)}"
            raise RuntimeError(err_msg)

        print(f"Current state: {state_str}. Retrying in {current_interval:.1f}s...")
        time.sleep(current_interval)

        # Increase interval for the next poll (backoff)
        current_interval = min(current_interval * backoff_factor, max_interval)
        attempt += 1

    raise RuntimeError(f"Error: Maximum polling attempts ({max_attempts}) reached. File is still not ACTIVE.")

class SanitizedArgumentParser(argparse.ArgumentParser):
    """Custom ArgumentParser that ensures any error output is sanitized to prevent secret leaks."""
    def error(self, message):
        sanitized_msg = sanitize_error(message)
        self.print_usage(sys.stderr)
        self.exit(2, f"{self.prog}: error: {sanitized_msg}\n")

def main():
    parser = SanitizedArgumentParser(description="Upload files to Gemini Files API using google-genai SDK.")
    parser.add_argument("file", help="Path to the file to upload")
    parser.add_argument("--name", help="Custom display name for the file")
    parser.add_argument("--no-wait", action="store_true", help="Don't wait for ACTIVE status")

    args = parser.parse_args()

    api_key = get_api_key()
    if not api_key:
        print("Error: GEMINI_API_KEY environment variable is not set.", file=sys.stderr)
        sys.exit(1)

    if not os.path.exists(args.file):
        print(f"Error: File '{args.file}' not found.", file=sys.stderr)
        sys.exit(1)

    try:
        file_meta = upload_file(args.file, args.name)
        file_name = file_meta.get("name")

        print(f"File metadata created:")
        print(f"  Name: {file_name}")
        print(f"  URI:  {strip_query_params(file_meta.get('uri'))}")
        print(f"  Type: {file_meta.get('mimeType')}")

        if not args.no_wait:
            file_meta = wait_for_active(file_name)

        print("\nFile upload successfully completed! JSON Output:")
        print(json.dumps(file_meta, indent=2))
        sys.exit(0)
    except Exception as e:
        print(f"Error: {sanitize_error(e)}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an API key, Needs Python

Security

What our scanner noticed· Low risk

  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Google (Gemini)
Publisher
Google (Gemini) (Official)
Source
google-gemini/gemini-skills · skills/gemini-omni-flash-api · commit 7cfc690
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Google (Gemini) or Google (Gemini).

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download gemini-omni-flash-api.zip