Skip to content

Repository files navigation

gemflow

Automated Batch Image Generation for Google Gemini & Flow

Python 3.11+PlaywrightLicense: MITPlatform

Real browser automation. Not an API wrapper.

Generate hundreds of images across Google's Gemini and Flow (ImageFX) platforms using actual Chrome browsers with your logged-in Google accounts.

Features · Quick Start · Architecture · Usage · AI Integration


Why gemflow?

Google's image generation tools (Gemini, Flow/ImageFX) don't have public APIs. gemflow bridges that gap by automating real Chrome browsers through Playwright — the same way a human would use them, but at scale.

  • No API keys needed — uses your existing Google account sessions
  • No rate limit guessing — built-in smart delays and anti-detection
  • Full feature access — reference images, project management, tier selection
  • Batch processing — feed it 100 prompts, walk away, come back to images

Features

Gemini Image Generation

FeatureDetails
Single accountSequential prompt processing with smart delays
Multi-account parallelMultiple Chrome instances across accounts simultaneously
Auto-detectionHandles promo dialogs, cookie consents, login checks
Image extractionDetects generated images and downloads automatically
Output1 high-quality image per prompt

Flow (ImageFX) Image Generation

FeatureDetails
Tier systemSeparate accounts for different quality tiers (ultra/pro)
Project managementCreate, find, and reuse named projects
Batch generation4 images per prompt, sequential with delays
Reference imagesUpload style reference, auto-reattach across batch via ingredient picker
Content detectionCopyright / policy rejection detection with clear error messages
Multi-languageEnglish and Korean Google UI selectors

Side-by-Side Comparison

 Gemini Flow (ImageFX)
────── ──────────────
Images/prompt 1 4 (configurable)
Projects N/A (new chat each) Named, reusable
Reference images N/A Full support
Tier system N/A Ultra / Pro
Output structure Flat files Per-prompt folders
Generation time ~30s / prompt ~20s / prompt

Quick Start

1. Install dependencies

pip install -r requirements.txt
playwright install chromium

2. Login to Google

python cli.py login

A Chrome browser opens. Log into your Google account manually. The session is saved to a local Chrome profile for future use.

3. Generate images

# Gemini - single prompt
python cli.py generate --prompts "a serene Japanese zen garden, watercolor style"# Gemini - batch
python cli.py generate --prompts "prompt one""prompt two""prompt three"

4. Configure accounts (optional)

Create accounts.json in the project root:

{
"accounts": [
{
"name": "my-account",
"tier": "ultra",
"profile_dir": "./gemini_profiles/my-account"
}
]
}

Architecture

gemflow/
├── gemini_automation/ Core automation library
│ ├── config.py Gemini configuration & selectors
│ ├── browser.py BrowserManager — Playwright Chrome lifecycle
│ ├── generator.py ImageGenerator — prompt → image URLs
│ ├── downloader.py ImageDownloader — URLs → PNG files
│ ├── accounts.py AccountManager — multi-account profiles
│ ├── parallel.py Parallel multi-account orchestration
│ ├── flow_config.py Flow configuration & dynamic tier loading
│ └── flow_generator.py FlowImageGenerator + FlowImageDownloader
│
├── cli.py CLI entry point (login, generate, status)
├── requirements.txt Python dependencies
├── accounts.json Account config (gitignored)
└── gemini_profiles/ Chrome profiles with sessions (gitignored)

How It Works

┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Prompts │────▶│ BrowserManager│────▶│ Chrome │
│ (text list) │ │ (Playwright) │ │ (headful) │
└─────────────┘ └──────┬───────┘ └──────┬──────┘
│ │
┌──────▼───────┐ ┌──────▼──────┐
│ Generator │────▶│ Google UI │
│ (enter prompt│ │ (Gemini or │
│ click create│ │ Flow/ImageFX)│
│ poll images)│ │ │
└──────┬───────┘ └──────┬──────┘
│ │
┌──────▼───────┐ ┌──────▼──────┐
│ Downloader │◀────│ GCS URLs │
│ (fetch PNGs, │ │ (detected via│
│ save to disk│ │ DOM polling)│
└──────────────┘ └─────────────┘

Generation Flow (Flow/ImageFX)

1. Launch Chrome with saved profile
2. Navigate to labs.google/fx/tools/flow
3. Find or create named project
4. Switch to Images mode, configure settings
5. [If reference] Upload image → Crop & Save → wait for thumbnail
6. Enter prompt text → Click Create
7. Poll DOM for new <img> elements with GCS URLs
8. Wait for naturalWidth >= 256 (fully loaded)
9. Download all images to per-prompt subfolder
10. [Next prompt] Re-attach reference from ingredient picker
11. Repeat 6-10 for remaining prompts

Usage

CLI

# Login (opens browser for manual Google login)
python cli.py login
# Check status
python cli.py status
# Generate with Gemini
python cli.py generate --prompts "a cat on the moon""a dog in space"

Flow Worker (Advanced)

# Single tier
python flow_worker.py --tier ultra --prompts "a landscape""a portrait"# With named project (reuses if exists)
python flow_worker.py --tier ultra --project-name "MyProject" --prompts "prompt 1"# With reference image
python flow_worker.py --tier ultra \
--reference-image "/path/to/reference.png" \
--prompts "style transfer prompt 1""style transfer prompt 2"# Both tiers in parallel
python flow_worker.py --tier all --prompts "a sunset over mountains"

Gemini Worker (Advanced)

# Single account
python gemini_worker.py --account my-account --prompts "a watercolor painting"# Parallel across multiple accounts
python gemini_worker.py --accounts account1,account2 --prompts "p1""p2""p3""p4"

Reference Images

gemflow supports reference/style images for Flow generation. Upload an image once and it's automatically reused across all prompts in a batch.

How it works

  1. First prompt: Full upload flow — file picker → crop modal → save
  2. Subsequent prompts: Opens ingredient picker → clicks most recent reference tile → instant reattach

This means batch generation with references is fast — only the first prompt has the ~10s upload overhead.

Output Structure

output/
├── flow_ultra/ Flow ultra-tier outputs
│ ├── A_serene_zen_garden/
│ │ ├── flow_0_20260203_032847.png
│ │ ├── flow_1_20260203_032848.png
│ │ ├── flow_2_20260203_032849.png
│ │ └── flow_3_20260203_032849.png
│ └── A_cute_cat_on_rainbow/
│ ├── flow_0_20260203_041217.png
│ └── ...
├── flow_pro/ Flow pro-tier outputs
│ └── ...
└── gemini_{prompt}_{timestamp}.png Gemini outputs (flat)

Configuration

accounts.json

{
"accounts": [
{
"name": "account-one",
"tier": "ultra",
"profile_dir": "./gemini_profiles/account-one",
"created_at": "2026-01-01T00:00:00+00:00"
},
{
"name": "account-two",
"tier": "pro",
"profile_dir": "./gemini_profiles/account-two",
"created_at": "2026-01-01T00:00:00+00:00"
}
]
}

Tier System

TierUse CaseOutput Directory
ultraHighest quality generationoutput/flow_ultra/
proStandard generationoutput/flow_pro/
allBoth tiers in parallelBoth directories

Tiers are mapped to accounts via the "tier" field in accounts.json.


Anti-Detection

gemflow uses several strategies to avoid bot detection:

  • Headful Chrome — real browser window, not headless
  • Persistent profiles — reuses cookies/sessions like a real user
  • Random delays — 3-10 second waits between actions
  • No automation flags — disables AutomationControlled blink feature
  • Natural interaction — types text, clicks buttons, waits for UI responses

Tech Stack

ComponentTechnology
Browser automationPlaywright for Python
LanguagePython 3.11+ (async/await throughout)
Chrome managementPersistent Chromium contexts with saved profiles
Image detectionDOM polling with naturalWidth verification
TUI (optional)Textual
DownloadDirect GCS URL fetching via Playwright request context

Limitations

  • No public API — relies on web UI automation, which can break if Google updates their interface
  • Requires Google account — must be logged in with a real account
  • Generation speed — limited by Google's actual generation time (~20-30s per prompt)
  • Content policy — Google's content filters still apply; copyrighted characters will be refused

AI Coding Assistant Integration

gemflow can be integrated with AI coding assistants like OpenCode, Claude Code, Cline, and similar tools that support custom tools/skills.

Overview

The integration consists of three parts:

ComponentPurposeLocation
Worker ScriptsCLI wrappers that call the automationworkers/ or project root
Tool DefinitionsSchema + execution logic for AI tools~/.config/opencode/tools/
Skill FilesUsage instructions for the AI~/.config/opencode/skills/

1. Worker Scripts

Create thin CLI wrappers that the AI tools can execute:

flow_worker.py — Flow image generation wrapper:

#!/usr/bin/env python3"""Flow image generation worker for AI tool integration."""importargparseimportasyncioimportjsonimportsysfrompathlibimportPath# Add project to pathsys.path.insert(0, str(Path(__file__).parent.parent))
fromgemini_automation.flow_generatorimportFlowImageGenerator, FlowImageDownloaderfromgemini_automation.flow_configimportFlowConfig, TIER_ACCOUNTSasyncdefmain():
parser=argparse.ArgumentParser(description="Flow image generation worker")
parser.add_argument("--tier", required=True, choices=["ultra", "pro", "all"])
parser.add_argument("--prompts", nargs="+", required=True)
parser.add_argument("--output-dir", type=Path, default=None)
parser.add_argument("--project-name", type=str, default=None)
parser.add_argument("--reference-image", type=str, default=None)
args=parser.parse_args()
tiers= ["ultra", "pro"] ifargs.tier=="all"else [args.tier]
all_results= {}
fortierintiers:
account=TIER_ACCOUNTS.get(tier)
ifnotaccount:
print(json.dumps({"error": f"No account configured for tier: {tier}"}))
return1config=FlowConfig.for_account(account)
ifargs.output_dir:
config.output_dir=args.output_dir/f"flow_{tier}"asyncwithFlowImageGenerator(config) asgenerator:
results=awaitgenerator.generate_batch(
prompts=args.prompts,
project_name=args.project_name,
reference_image_path=args.reference_image,
)
downloader=FlowImageDownloader(generator.page, config)
tier_results= []
forresultinresults:
entry= {"prompt": result.prompt, "success": result.success, "images": []}
ifresult.success:
dl=awaitdownloader.download_all(result)
entry["images"] = [str(p) forpindl.saved_files]
else:
entry["error"] =result.errortier_results.append(entry)
all_results[tier] =tier_resultsprint(json.dumps(all_results, indent=2, ensure_ascii=False))
return0if__name__=="__main__":
sys.exit(asyncio.run(main()))

gemini_worker.py — Gemini image generation wrapper:

#!/usr/bin/env python3"""Gemini image generation worker for AI tool integration."""importargparseimportasyncioimportjsonimportsysfrompathlibimportPathsys.path.insert(0, str(Path(__file__).parent.parent))
fromgemini_automation.accountsimportAccountManagerfromgemini_automation.parallelimportParallelGeneratorasyncdefmain():
parser=argparse.ArgumentParser(description="Gemini image generation worker")
parser.add_argument("--prompts", nargs="+", required=True)
parser.add_argument("--account", type=str, default=None)
parser.add_argument("--accounts", type=str, default=None) # comma-separatedparser.add_argument("--output-dir", type=Path, default=None)
parser.add_argument("--max-concurrent", type=int, default=None)
args=parser.parse_args()
manager=AccountManager()
ifargs.accounts:
account_names= [a.strip() forainargs.accounts.split(",")]
elifargs.account:
account_names= [args.account]
else:
account_names= [manager.list_accounts()[0]["name"]]
generator=ParallelGenerator(
account_names=account_names,
output_dir=args.output_dir,
max_concurrent=args.max_concurrentorlen(account_names),
)
results=awaitgenerator.generate_batch(args.prompts)
print(json.dumps(results, indent=2, ensure_ascii=False, default=str))
return0if__name__=="__main__":
sys.exit(asyncio.run(main()))

2. Tool Definitions (OpenCode)

Create tool definitions that the AI can invoke. These go in ~/.config/opencode/tools/:

flow-generate.ts:

import{tool}from"@opencode-ai/plugin"// Update this to your gemflow installation pathconstGEMFLOW_ROOT="/path/to/gemflow"constWORKER_SCRIPT=`${GEMFLOW_ROOT}/flow_worker.py`exportdefaulttool({description: "Generate images using Google Flow. Choose tier: ultra, pro, or all.",args: {prompts: tool.schema.array(tool.schema.string()).describe("Image prompts"),tier: tool.schema.enum(["ultra","pro","all"]).describe("Account tier"),outputDir: tool.schema.string().optional().describe("Output directory"),projectName: tool.schema.string().optional().describe("Flow project name"),referenceImage: tool.schema.string().optional().describe("Reference image path"),},asyncexecute(args){constcmdArgs=[WORKER_SCRIPT,"--tier",args.tier,"--prompts", ...args.prompts]if(args.outputDir)cmdArgs.push("--output-dir",args.outputDir)if(args.projectName)cmdArgs.push("--project-name",args.projectName)if(args.referenceImage)cmdArgs.push("--reference-image",args.referenceImage)constproc=Bun.spawn(["python", ...cmdArgs],{cwd: GEMFLOW_ROOT,stdout: "pipe",stderr: "pipe"})conststdout=awaitnewResponse(proc.stdout).text()conststderr=awaitnewResponse(proc.stderr).text()constexitCode=awaitproc.exitedreturnexitCode===0 ? stdout.trim() : `Error (exit ${exitCode}):\n${stderr||stdout}`},})

gemini-generate.ts:

import{tool}from"@opencode-ai/plugin"constGEMFLOW_ROOT="/path/to/gemflow"constWORKER_SCRIPT=`${GEMFLOW_ROOT}/gemini_worker.py`exportdefaulttool({description: "Generate images using Google Gemini web automation.",args: {prompts: tool.schema.array(tool.schema.string()).describe("Image prompts"),account: tool.schema.string().optional().describe("Account name"),accounts: tool.schema.array(tool.schema.string()).optional().describe("Accounts for parallel mode"),outputDir: tool.schema.string().optional().describe("Output directory"),maxConcurrent: tool.schema.number().optional().describe("Max parallel browsers"),},asyncexecute(args){constcmdArgs=[WORKER_SCRIPT,"--prompts", ...args.prompts]if(args.outputDir)cmdArgs.push("--output-dir",args.outputDir)if(args.accounts?.length)cmdArgs.push("--accounts",args.accounts.join(","))elseif(args.account)cmdArgs.push("--account",args.account)if(args.maxConcurrent)cmdArgs.push("--max-concurrent",String(args.maxConcurrent))constproc=Bun.spawn(["python", ...cmdArgs],{cwd: GEMFLOW_ROOT,stdout: "pipe",stderr: "pipe"})conststdout=awaitnewResponse(proc.stdout).text()conststderr=awaitnewResponse(proc.stderr).text()constexitCode=awaitproc.exitedreturnexitCode===0 ? stdout.trim() : `Error (exit ${exitCode}):\n${stderr||stdout}`},})

3. Skill Files (OpenCode)

Skills teach the AI when and how to use the tools. Create these in ~/.config/opencode/skills/:

flow-image/SKILL.md:

---name: flow-imagedescription: Generate images using Google Flow. Use when user wants 4 images per prompt or tier-separated output.---## `flow-generate` tool**Basic usage:**\`\`\`json
{ "prompts": ["a serene landscape", "a futuristic city"], "tier": "ultra" }
\`\`\`**With reference image:**\`\`\`json
{
"prompts": ["character in battle pose", "character menu screen"],
"tier": "ultra",
"referenceImage": "/path/to/style-reference.png"
}
\`\`\`## Output- 4 images per prompt
- Saved to: `output/flow_{tier}/{prompt_folder}/`## When to use Flow vs Gemini- Flow: 4 images/prompt, project organization, reference images, tier system
- Gemini: 1 image/prompt, simpler workflow

gemini-image/SKILL.md:

---name: gemini-imagedescription: Generate images using Google Gemini. Use when user wants single high-quality images.---## `gemini-generate` tool**Single account:**\`\`\`json
{ "prompts": ["a watercolor sunset", "abstract geometric art"] }
\`\`\`**Parallel (multiple accounts):**\`\`\`json
{
"prompts": ["prompt1", "prompt2", "prompt3", "prompt4"],
"accounts": ["account-one", "account-two"]
}
\`\`\`## Output- 1 image per prompt
- Saved to: `output/gemini_{prompt}_{timestamp}.png`

4. Directory Structure

After setup, your config should look like:

~/.config/opencode/
├── tools/
│ ├── flow-generate.ts
│ └── gemini-generate.ts
├── skills/
│ ├── flow-image/
│ │ └── SKILL.md
│ └── gemini-image/
│ └── SKILL.md
└── package.json # Add @opencode-ai/plugin dependency

5. Installation

# In your opencode config directorycd~/.config/opencode
bun add @opencode-ai/plugin

Usage with AI

Once configured, you can ask your AI assistant:

"Generate 4 images of a cyberpunk cityscape using Flow"

The AI will:

  1. Load the flow-image skill
  2. Call the flow-generate tool with appropriate parameters
  3. Return the generated image paths

Other AI Assistants

For Claude Code, Cline, Cursor, or other MCP-compatible tools, adapt the tool definitions to their plugin format. The core pattern remains:

  1. Worker script — Python CLI that wraps the automation
  2. Tool schema — Defines parameters the AI can pass
  3. Skill/instruction — Teaches the AI when/how to use it

Disclaimer

This tool automates interactions with Google's web interfaces. Usage may be subject to Google's Terms of Service. Use responsibly and at your own risk. This project is not affiliated with, endorsed by, or sponsored by Google.


Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feat/amazing-feature)
  5. Open a Pull Request

License

MIT License. See LICENSE for details.


Built with Playwright and persistence.

Because sometimes the best API is no API at all.

About

Automated batch image generation for Google Gemini & Flow (ImageFX) via browser automation

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages