A standalone system (no Veogent dependency) that lets a local Python agent generate AI videos via Google Flow API, using a Chrome extension as the browser bridge.
┌──────────────────┐ WebSocket ┌──────────────────────┐
│ Python Agent │◄──────────────────►│ Chrome Extension │
│ (FastAPI+SQLite)│ localhost:9222 │ (MV3 Service Worker) │
│ │ │ │
│ - REST API │ ── commands ──► │ - Token capture │
│ - Queue worker │ ◄── results ── │ - reCAPTCHA solve │
│ - Post-process │ │ - API proxy │
│ - DB │ │ (on labs.google) │
└──────────────────┘ └──────────────────────┘
- Chrome MV3 service workers cannot run WebSocket servers
- Agent runs WS server on
localhost:9222 - Extension connects to agent's WS server on startup + auto-reconnect
- Protocol: JSON messages with
{id, method, params}→{id, result/error}
| Operation | Endpoint | captchaAction |
|---|---|---|
| Generate Image | POST /v1/projects/{projectId}/flowMedia:batchGenerateImages |
IMAGE_GENERATION |
| Generate Video (start frame) | POST /v1/video:batchAsyncGenerateVideoStartImage |
VIDEO_GENERATION |
| Generate Video (start+end) | POST /v1/video:batchAsyncGenerateVideoStartAndEndImage |
VIDEO_GENERATION |
| Generate Video (references) | POST /v1/video:batchAsyncGenerateVideoReferenceImages |
VIDEO_GENERATION |
| Upscale Video | POST /v1/video:batchAsyncGenerateVideoUpsampleVideo |
VIDEO_GENERATION |
| Upscale Image | POST /v1/flow/upsampleImage |
IMAGE_GENERATION |
| Upload Image | POST /v1:uploadImage |
none |
| Check Video Status | POST /v1/video:batchCheckAsyncVideoGenerationStatus |
none |
| Get Credits | GET /v1/credits |
none |
| Get Media | GET /v1/media/{mediaId} |
none |
Base: https://aisandbox-pa.googleapis.com
API Key (query param): AIzaSyBtrm0o5ab1c-Ec8ZuLcGt3oJAA5VWt3pY
Every generation request has:
{
"clientContext": {
"projectId": "<numeric_project_id>",
"recaptchaContext": {
"applicationType": "RECAPTCHA_APPLICATION_TYPE_WEB",
"token": "<solved_token>"
},
"sessionId": ";<timestamp_ms>",
"tool": "PINHOLE",
"userPaygateTier": "PAYGATE_TIER_TWO"
},
"requests": [{ ... }]
}{
"seed": 123456,
"prompt": "...",
"imageAspectRatio": "IMAGE_ASPECT_RATIO_PORTRAIT",
"imageModelName": "GEM_PIX_2",
"clientContext": { "projectId": "...", "tool": "PINHOLE", "recaptchaContext": {...}, "sessionId": "..." }
}Same endpoint (batchGenerateImages), same wrapper. Each request item adds imageInputs:
{
"imageInputs": [
{"name": "<character_media_id>", "imageInputType": "IMAGE_INPUT_TYPE_BASE_IMAGE"}
]
}Character media_id lifecycle:
- Character has a reference image URL (
imageUri/reference_image_url) - Upload via
POST /v1:uploadImage→ returns{mediaId: {mediaId: "actual_id"}} - Store
actual_idas character'smedia_id - Before using in image gen, validate:
GET /v1/media/{media_id}→ 200 = valid - If expired/invalid → re-upload reference image → update
media_id - Pass valid IDs as
imageInputsinbatchGenerateImages
This is different from scene image mediaId!
- Character
media_id= fromuploadImage(user-uploaded reference) - Scene image
mediaId= frombatchGenerateImagesresponse (AI-generated) - Video
startImage.mediaId= scene image'smediaId
#### Image Generation Response
```json
{
"media": [
{
"image": {
"generatedImage": {
"mediaId": "CkIK...", // ← KEY: used as startImage.mediaId for video gen
"encodedImage": "base64...", // legacy (may be null)
"fifeUrl": "https://...", // public URL (new flow)
"imageUri": "https://..." // alias
}
}
}
]
}
{
"mediaId": {
"mediaId": "actual_media_id" // ← nested!
}
}{
"aspectRatio": "VIDEO_ASPECT_RATIO_PORTRAIT",
"seed": 1234,
"textInput": { "prompt": "..." },
"videoModelKey": "veo_3_1_i2v_s_fast_portrait_ultra",
"startImage": { "mediaId": "<media_id>" },
"metadata": { "sceneId": "..." }
}Same as above but add:
{ "endImage": { "mediaId": "<end_media_id>" } }{
"aspectRatio": "VIDEO_ASPECT_RATIO_PORTRAIT",
"resolution": "VIDEO_RESOLUTION_4K",
"seed": 12345,
"metadata": { "sceneId": "..." },
"videoInput": { "mediaId": "<media_id>" },
"videoModelKey": "veo_3_1_upsampler_4k"
}| Type | Portrait (TIER_TWO) | Landscape (TIER_TWO) |
|---|---|---|
| frame→video | veo_3_1_i2v_s_fast_portrait_ultra |
veo_3_1_i2v_s_fast_ultra |
| start+end→video | veo_3_1_i2v_s_fast_portrait_ultra_fl |
veo_3_1_i2v_s_fast_ultra_fl |
| reference→video | veo_3_0_r2v_fast_portrait_ultra |
veo_3_0_r2v_fast_ultra |
| upscale 4K | veo_3_1_upsampler_4k |
veo_3_1_upsampler_4k |
| upscale 1080p | veo_3_1_upsampler_1080p |
veo_3_1_upsampler_1080p |
TIER_ONE uses non-_ultra variants.
3-layer architecture:
- injected.js — runs in MAIN world on
labs.google, callsgrecaptcha.enterprise.execute(SITE_KEY, {action}) - content.js — bridge between background and injected.js via CustomEvents
- background.js — receives captcha requests, forwards to content.js, gets token back
Flow:
Agent → WS → background.js → chrome.tabs.sendMessage → content.js
→ CustomEvent('GET_CAPTCHA') → injected.js → grecaptcha.enterprise.execute()
→ CustomEvent('CAPTCHA_RESULT') → content.js → sendResponse → background.js
→ WS → Agent
Site key: 6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV
Randomized browser fingerprint headers required:
sec-ch-ua,sec-ch-ua-mobile,sec-ch-ua-platformx-browser-channel,x-browser-copyright,x-browser-yearx-browser-validation(rotated from pool)x-client-data(rotated from pool)user-agent(rotated Chrome versions)origin: https://labs.google,referer: https://labs.google/
| Type | Portrait | Landscape |
|---|---|---|
| Image | IMAGE_ASPECT_RATIO_PORTRAIT |
IMAGE_ASPECT_RATIO_LANDSCAPE |
| Video | VIDEO_ASPECT_RATIO_PORTRAIT |
VIDEO_ASPECT_RATIO_LANDSCAPE |
| File | Purpose |
|---|---|
manifest.json |
MV3, permissions: storage/alarms/tabs/webRequest/scripting |
background.js |
WS client → agent, task dispatch, token capture, API proxy |
content.js |
Bridge background↔injected via CustomEvents |
injected.js |
MAIN world: grecaptcha.enterprise.execute() |
popup.html/js |
Status display (connected, token age, queue) |
Key differences from current scaffold:
- Extension is WS client (connects to agent), not server
- Has full 3-layer reCAPTCHA solving
- API calls happen IN the extension (browser context, residential IP, cookies)
- Token capture via
webRequest.onBeforeSendHeaders
| Module | Files | Purpose |
|---|---|---|
main.py |
1 | FastAPI app + WS server (dual) |
config.py |
1 | All config constants |
db/ |
3 | schema.py, crud.py, init.py (aiosqlite!) |
models/ |
6 | Pydantic models with Literal types |
api/ |
6 | REST routes (characters, projects, videos, scenes, requests, ws) |
services/ |
4 | flow_client, scene_chain, post_process, headers |
worker/ |
2 | processor, status_poller |
Key fixes from review:
- Use
aiosqlitefor all DB operations (async, non-blocking) - WS server in agent (not client)
- flow_client sends commands over WS, extension executes API calls
- Add Literal types for enums
- Add CORS middleware
- Bind to
127.0.0.1by default - Add proper header randomization
- Cleanup pending futures on disconnect
- Fix
_now()format - Add
ON DELETE CASCADEto request FKs - Add upscale_status columns
Agent → Extension messages:
{"id": "uuid", "method": "api_request", "params": {
"url": "https://aisandbox-pa.googleapis.com/v1/...",
"method": "POST",
"headers": {...randomized...},
"body": {...payload...},
"captchaAction": "VIDEO_GENERATION"
}}Extension → Agent responses:
{"id": "uuid", "status": 200, "data": {...response...}}
{"id": "uuid", "status": 403, "error": "reCAPTCHA failed"}Special messages:
{"type": "token_captured", "flowKey": "ya29.xxx"}— extension notifies agent{"type": "status"}— agent queries extension state{"type": "ping"}/{"type": "pong"}— keepalive
injected.js— reCAPTCHA solver (copy from production, it's 37 lines)content.js— bridge (copy from production, it's 38 lines)manifest.json— permissions, web_accessible_resources for injected.jsbackground.js— WS client, token capture, API proxy handlerpopup.html/js— basic status
config.py— all constantsdb/schema.py— fixed schema (aiosqlite, cascades, upscale_status)db/crud.py— async CRUD with column whitelistmodels/— Pydantic with Literal enumsservices/headers.py— randomized header generation (from production)services/flow_client.py— WS-based, sends to extension, handles responses
api/— REST routes with CORS, validation, authapi/ws.py— WebSocket endpoint for extensionworker/processor.py— async queue processorworker/status_poller.py— polls video generation statusmain.py— FastAPI + WS server lifespanservices/post_process.py— ffmpeg (already good)services/scene_chain.py— chain logic (already good)
- Test extension in Chrome with real Flow tab
- Test end-to-end: create project → generate image → generate video
- Add error handling, retry logic
- Push to GitHub
| Component | Status | Notes |
|---|---|---|
ARCHITECTURE.md |
✅ Keep | Good overview |
requirements.txt |
🔧 Fix | Add aiosqlite, websockets |
agent/config.py |
🔧 Fix | Change host to 127.0.0.1, add header pools |
agent/db/schema.py |
🔧 Fix | Switch to aiosqlite, add upscale_status, fix cascades |
agent/db/crud.py |
🔄 Rewrite | Async + column whitelist |
agent/models/ |
🔧 Fix | Add Literal types, fix character_names |
agent/api/ |
🔧 Fix | Add CORS, auth, validation, WS endpoint |
agent/services/flow_client.py |
🔄 Rewrite | WS-based, don't call API directly |
agent/services/post_process.py |
✅ Keep | Already correct |
agent/services/scene_chain.py |
✅ Keep | Already correct |
agent/worker/processor.py |
🔧 Fix | Async DB calls |
agent/main.py |
🔧 Fix | Add WS server, CORS, auth |
extension/* |
🔄 Rewrite | Everything wrong |