+
    ~i}                      a  0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
Ht ^ RIHt ^ RIt]P                  ! ]4      tRtRRRR	R
R/tRtRt ! R R4      tRsR]R&   ]P2                  ! 4       tRR R lltR tRR R lltR# )u  
FlowBrowserEngine — Headless Playwright engine for Google Labs Flow.

Reverse-engineered API (Feb 2026):
  Endpoint: POST aisandbox-pa.googleapis.com/v1/projects/{project_id}/flowMedia:batchGenerateImages
  Model:    GEM_PIX_2 (displayed as "Nano Banana Pro" in UI)
  Auth:     Bearer token (from session endpoint) + reCAPTCHA (browser-generated)
  Response: Signed GCS URLs for each generated image

Strategy:
1. Keep headless Chromium alive on a Flow project page
2. Install JS fetch() interceptor to capture reCAPTCHA + auth from real requests
3. For each generation: fill prompt → click submit → wait for API response
4. Download images from the returned signed URLs
)annotationsN)Path)Optionalz!https://labs.google/fx/tools/flowportraitIMAGE_ASPECT_RATIO_PORTRAIT	landscapeIMAGE_ASPECT_RATIO_LANDSCAPEsquareIMAGE_ASPECT_RATIO_SQUAREax	  
// 1. Hide navigator.webdriver
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });

// 2. Override Permissions API to look like real Chrome
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
    parameters.name === 'notifications'
        ? Promise.resolve({ state: Notification.permission })
        : originalQuery(parameters)
);

// 3. Fake plugins (Chrome always has at least PDF plugins)
Object.defineProperty(navigator, 'plugins', {
    get: () => {
        const plugins = [
            { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
            { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
            { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
        ];
        plugins.length = 3;
        plugins.refresh = () => {};
        plugins.item = (i) => plugins[i] || null;
        plugins.namedItem = (n) => plugins.find(p => p.name === n) || null;
        return plugins;
    }
});

// 4. Fake languages
Object.defineProperty(navigator, 'languages', { get: () => ['pt-BR', 'pt', 'en-US', 'en'] });

// 5. Fake Chrome runtime (reCAPTCHA checks window.chrome)
if (!window.chrome) {
    window.chrome = {};
}
if (!window.chrome.runtime) {
    window.chrome.runtime = {
        connect: () => {},
        sendMessage: () => {},
        id: undefined,
    };
}

// 6. Override connection info to hide automation
Object.defineProperty(navigator, 'connection', {
    get: () => ({
        effectiveType: '4g',
        rtt: 50,
        downlink: 10,
        saveData: false,
    })
});

// 7. Fake WebGL vendor/renderer (headless often has different values)
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) {
    if (param === 37445) return 'Google Inc. (NVIDIA)';
    if (param === 37446) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Direct3D11 vs_5_0 ps_5_0, D3D11)';
    return getParameter.call(this, param);
};

// 8. Fake platform
Object.defineProperty(navigator, 'platform', { get: () => 'Linux x86_64' });

// 9. Fake hardware concurrency
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });

// 10. Fake device memory
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
a  
(() => {
    if (window.__flowInterceptorReady) return;
    window.__flowCaptured = [];
    window.__flowLastGeneration = null;
    window.__flowAspectRatio = null;  // set from Python to override aspect ratio
    window.__flowReferenceMediaId = null;  // set from Python for coverage shot reference
    window.__flowReferenceWeight = 0.85;   // reference image influence weight
    window.__flowLastMediaId = null;       // last captured mediaGenerationId
    const origFetch = window.fetch;
    window.fetch = async function(...args) {
        const [url, opts] = args;
        const urlStr = typeof url === 'string' ? url : url?.url || '';
        if (urlStr.includes('aisandbox-pa') && opts?.method?.toUpperCase() === 'POST') {
            const entry = {url: urlStr, headers: {}, body: null, ts: Date.now()};
            if (opts.headers) {
                if (opts.headers instanceof Headers)
                    opts.headers.forEach((v, k) => { entry.headers[k] = v; });
                else if (typeof opts.headers === 'object')
                    entry.headers = {...opts.headers};
            }
            // Override aspect ratio + limit to 1 image in batchGenerateImages requests
            if (urlStr.includes('batchGenerateImages') && opts.body) {
                try {
                    const body = JSON.parse(opts.body);
                    if (body.requests && body.requests.length > 0) {
                        // Keep only the first request to generate 1 image
                        body.requests = [body.requests[0]];
                        if (window.__flowAspectRatio) {
                            body.requests[0].imageAspectRatio = window.__flowAspectRatio;
                        }
                        // Inject reference image for coverage shot consistency
                        if (window.__flowReferenceMediaId) {
                            body.requests[0].imageInputs = [{
                                mediaId: window.__flowReferenceMediaId,
                                weight: window.__flowReferenceWeight || 0.85
                            }];
                        } else {
                            body.requests[0].imageInputs = [];
                        }
                    }
                    opts.body = JSON.stringify(body);
                } catch {}
            }
            if (opts.body) {
                try { entry.body = JSON.parse(opts.body); } catch {}
            }
            window.__flowCaptured.push(entry);
        }
        const response = await origFetch.apply(this, args);
        if (urlStr.includes('batchGenerateImages')) {
            try {
                const clone = response.clone();
                const data = await clone.json();
                window.__flowLastGeneration = {url: urlStr, response: data, ts: Date.now()};
                // Capture mediaGenerationId for coverage shot reference
                try {
                    const mediaId = data?.media?.[0]?.image?.generatedImage?.mediaGenerationId;
                    if (mediaId) window.__flowLastMediaId = mediaId;
                } catch {}
            } catch {}
        }
        return response;
    };
    window.__flowInterceptorReady = true;
})();
c                  J   ] tR t^tRtR R lt]R R l4       t]R R l4       t]R R	 l4       t	R$R
 R llt
R R ltR tR tR tR t]R R l4       tR%R R lltR R lt]! ]P,                  ! RR4      4      t^t. R&OtR'R R lltR R lt]R R  l4       tR! R" ltR#tR# )(FlowBrowserEnginez
Headless browser engine for Flow image generation with Nano Banana Pro.

Usage (async):
    engine = FlowBrowserEngine(session_token, csrf_token)
    await engine.start()
    path = await engine.generate("a cat", Path("out.png"))
    await engine.stop()
c                    V ^8  d   QhRRRR/# )   session_tokenstr
csrf_token )formats   "Z/home/gabslocked/Desktop/Projetos/Images/video_automation/providers/flow_browser_engine.py__annotate__FlowBrowserEngine.__annotate__   s     / /c /s /    c                	    Wn         W n        R V n        R V n        R V n        R V n        RV n        \        P                  ! 4       V n	        R V n
        R V n        R V n        R # )NF)r   r   _browser_context_page_pw_readyasyncioLock_lock_project_id_http_client_last_media_id)selfr   r   s   &&&r   __init__FlowBrowserEngine.__init__   sU    *$
\\^
*.6:*.r   c                   V ^8  d   QhRR/# r   returnboolr   )r   s   "r   r   r      s     9 9D 9r   c                	F    V P                   ;'       d    V P                  R J# N)r   r   r$   s   &r   
is_runningFlowBrowserEngine.is_running   s    {{88t}}D88r   c                   V ^8  d   QhRR/# )r   r)   zOptional[str]r   )r   s   "r   r   r      s        M  r   c                	    V P                   # r,   )r!   r-   s   &r   
project_idFlowBrowserEngine.project_id   s    r   c                   V ^8  d   QhRR/# )r   r)   
str | Noner   )r   s   "r   r   r      s     # #z #r   c                    V P                   # )z@The mediaGenerationId from the last successful image generation.)r#   r-   s   &r   last_media_idFlowBrowserEngine.last_media_id   s     """r   c                    V ^8  d   QhRRRR/# )r   	timeout_sintr)   r*   r   )r   s   "r   r   r      s     b bS b$ br   c                  "   V P                   '       d   R# ^ RIHp  V! 4       P                  4       G Rj  xL
 V n        V P                  P
                  P                  R. R(OR7      G Rj  xL
 V n        V P                  P                  RRRRR	/R
R7      G Rj  xL
 V n	        V P                  P                  \        4      G Rj  xL
  V P                  P                  RRRV P                  RRRRRRRRRR/RRRV P                  RRRRRRRRRR/RRRRRRRRRRRRRR/.4      G Rj  xL
  V P                  P                  4       G Rj  xL
 V n        \"        P%                  R\&        4       V P                   P)                  \&        RVR,          R7      G Rj  xL
  \*        P,                  ! ^4      G Rj  xL
  V P/                  4       G Rj  xL
 pV'       g   \1        R4      hV P                   P3                  R4      pVP5                  4       G Rj  xL
 ^ 8  d+   VP6                  P9                  R R!7      G Rj  xL
 '       g#   \1        R"V P                   P:                   24      hV P                   P=                  \>        4      G Rj  xL
  \@        PB                  ! R#RR$7      V n"        RV n         \"        P%                  R%V PF                  4       R#  EL ELs ELG EL EL EL ELQ EL6 EL! L L Li  \H         d;   p\"        PK                  R&T4       T PM                  4       G Rj  xL 
   Rp?R'# Rp?ii ; i5i))z@Launch browser, navigate to Flow, enter a project in image mode.T)async_playwrightN)headlessargszeMozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36widthi   heighti  zpt-BR)
user_agentviewportlocalenamez __Secure-next-auth.session-tokenvaluedomainzlabs.googlepath/securehttpOnlysameSiteLaxz__Host-next-auth.csrf-tokenz__Secure-next-auth.callback-urlzhttps%3A%2F%2Flabs.googlez%FlowBrowserEngine: Loading gallery %sdomcontentloaded  
wait_untiltimeoutz Could not enter any Flow project#PINHOLE_TEXT_AREA_ELEMENT_ID  rR   z$Textarea not found after setup. URL=g      N@)rR   follow_redirectsu'   FlowBrowserEngine: Ready — project=%su&   FlowBrowserEngine: Start failed — %sF)
z--no-sandboxz---disable-blink-features=AutomationControlledz--disable-dev-shm-usagez--disable-infobarsz--window-size=1280,900z--start-maximizedz--disable-extensionsz4--disable-component-extensions-with-background-pagesz--disable-background-networkingz--disable-default-apps)'r   playwright.async_apir=   startr   chromiumlaunchr   new_contextr   add_init_script_STEALTH_JSadd_cookiesr   r   new_pager   loggerinfoFLOW_GALLERYgotor   sleep_enter_projectRuntimeErrorlocatorcountfirst
is_visibleurlevaluate_FETCH_INTERCEPTORhttpxAsyncClientr"   r!   	Exceptionerrorstop)r$   r:   r=   enteredtaes   &&    r   rX   FlowBrowserEngine.start   s    ;;;9[	-/5577DH"&(("3"3":": #; # DM #'--";";I "437 #< # DM --//<<<--++  B!3!3 - $"D"E  = - $"D"E  A!< - $"D"E'  @  $}}5577DJ KK?N**//);YQUEU "    --""" !//11G"#EFF ##$CDB((*$q(2883F3Ft3F3T-T-T":4::>>:JK 
 **%%&8999 % 1 1$QU VDDKKKA4CSCSTk 8 =@ 8 # 2 %-T :  	LLA1E))+	s   MK= K5K= ,K-.K= K"+K= K%A#K= +K(,!K= K+AK= K.K= ;K1<K= K4K=  9K= K7(K= K9	K= AK= K;AK= MK= K= "K= %K= (K= +K= .K= 1K= 4K= 7K= 9K= ;K= =M)L=1L42L=7M=MMc                   V ^8  d   QhRR/# r(   r   )r   s   "r   r   r   B  s     b bd br   c                	  "   \        ^4       EF  p\        P                  RV^,           4       V P                  P	                  R4      G Rj  xL
 pV'       d7   \        P                  RV4       \
        P                  ! ^4      G Rj  xL
  EM\        P                  R4       V P                  P                  R4      pVP                  4       G Rj  xL
 ^ 8  dl   VP                  P                  RR7      G Rj  xL
 '       dB   VP                  P                  4       G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  Ma\        P                  R	4       V P                  P                  P                  ^^4      G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  V P                  P                  pR
V9   Edi   VP                  R
4      R,          P                  R4      ^ ,          P                  R4      ^ ,          V n        \        P                  RV P                  4       V P                  P                  R4      pVP                  4       G Rj  xL
 ^ 8  d   VP                  P                  RR7      G Rj  xL
 '       dV   VP                  P                  4       G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  \        P                  R4       V P                  P	                  R4      G Rj  xL
 pVR8X  Ed   V P                  P                  R4      pVP                  4       G Rj  xL
 ^ 8  d   VP                  P                  RR7      G Rj  xL
 '       d   VP                  P                  4       G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  V P                  P                  R4      pVP                  4       G Rj  xL
 ^ 8  dA   VP                  P                  4       G Rj  xL
  \
        P                  ! ^4      G Rj  xL
   R# \        P!                  RV4       V^8  g   EKA  V P                  P#                  \$        RRR7      G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  EK  	  R#  ELQ EL EL EL EL ELk EL* EL ELX EL2 EL EL EL EL EL_ EL: EL L L L Lb LF5i)z8Enter a Flow project using robust selectors, with retry.u3   FlowBrowserEngine: Entering project (attempt %d)…a`  
                () => {
                    // Find all links that go to a project
                    const links = document.querySelectorAll('a[href*="/project/"]');
                    if (links.length > 0) {
                        links[0].click();
                        return 'link';
                    }
                    // Find clickable project cards (divs with project thumbnails)
                    const cards = document.querySelectorAll(
                        '[class*="project"], [class*="card"], [role="link"], [role="button"]');
                    for (const c of cards) {
                        const a = c.querySelector('a[href*="/project/"]') || c.closest('a[href*="/project/"]');
                        if (a) { a.click(); return 'card-link'; }
                    }
                    return null;
                }
            Nz!FlowBrowserEngine: Clicked via %suB   FlowBrowserEngine: No project links found, creating new project…zWbutton:has-text("Novo projeto"), button:has-text("New project"), button:has-text("add")i  rU   z,FlowBrowserEngine: Fallback coordinate clickz	/project/?#z%FlowBrowserEngine: Entered project %szbutton:has-text("Imagens")z)FlowBrowserEngine: Switched to Images taba  
                    () => {
                        for (const b of document.querySelectorAll('button'))
                            if (b.textContent.includes('Criar imagens')) return 'ok';
                        return 'need_switch';
                    }
                need_switchzbutton:has-text("Texto para")i  ztext="Criar imagens"Tu5   FlowBrowserEngine: Still on gallery (%s), retrying…rN   0u  rP   F)ranger`   ra   r   rl   r   rd   rg   rh   ri   rj   clickmouserk   splitr!   warningrc   rb   )	r$   attemptclickednew_btnrk   img_tabmodemode_btncis	   &        r   re    FlowBrowserEngine._enter_projectB  s    QxGKKEwQR{
 !JJ// 1  G& ?ImmA&&& X **,,m !(1,w}}7O7O  8P 8 2 2 "----///!--*** KK NO****00c:::!--*** **..Cc!IIk*2.44S9!<BB3GJ   CTEUEUV **,,-IJ (1,w}}7O7O  8P 8 2 2 "----///!--***KK KL "ZZ00 2   =(#zz112QRH%^^--1HNN<U<U $ =V = 7 7 'nn22444%mmA...!ZZ//0FG!#+a/"$((.."222")--"222KS Q;**//$1CU *    "--***}  @ u* ' ) 2 0* ;* ) 2 0* . 7 5.+22 +s  ASRS/S	R!
A	SR$(S<R'=	SS$R*%SR-ASR0S$R3%B9SR6(SR9	SS/R<0SR?7SS:S S(S)S*	S4SSS/S02S"S#&S	S
S'S(!S%S4S5SSS!S$S'S*S-S0S3S6S9S<S?SSSSSSSSSSSc                <  "   RV n          V P                  '       d#   V P                  P                  4       G Rj  xL
  RT n         T P                  '       d#   T P                  P                  4       G Rj  xL
   T P                  '       d#   T P                  P                  4       G Rj  xL
  R;T n        ;T n        T n	        RT n        \        P                  R4       R#  L  \         d     Li ; i L  \         d     Li ; i L]  \         d     Lhi ; i5i)zShutdown browser.FNzFlowBrowserEngine: Stopped)r   r"   acloserp   r   closer   rr   r   r   r`   ra   r-   s   &r   rr   FlowBrowserEngine.stop  s     	   ''..000 !	}}}mm))+++	xxxhhmmo%% 6:99
01! 1 		
 , 		 & 		s   D/C% C#C% DC8 C8 6C67C8 <D D +D	,D 03D#C% %C30D2C33D6C8 8DDDD	D DDDDc                  "   \         P                  R4       V P                  P                  \        RRR7      G Rj  xL
  \
        P                  ! ^4      G Rj  xL
  V P                  4       G Rj  xL
 pV'       g   \        R4      hV P                  P                  R4      pVP                  4       G Rj  xL
 ^ 8  d+   VP                  P                  RR	7      G Rj  xL
 '       g   \        R
4      hV P                  P                  \        4      G Rj  xL
  \         P                  R4       R#  L L L L L[ L%5i)zPRefresh reCAPTCHA context by navigating back to gallery and re-entering project.u6   FlowBrowserEngine: Refreshing session (page reload)…rN   r|   rP   Nz0Failed to re-enter project after session refreshrS   rT   rU   z(Textarea not found after session refreshu(   FlowBrowserEngine: Session refreshed ✓)r`   ra   r   rc   rb   r   rd   re   rf   rg   rh   ri   rj   rl   rm   )r$   rs   rt   s   &  r   _refresh_session"FlowBrowserEngine._refresh_session  s     LMjjoo%7  
 	
 	
 mmA++--QRRZZ ?@hhj 1$rxx/B/B4/B/P)P)PIJJjj!!"4555>?	
 	- !)P5su   ;ED6ED8E3D:4E 9E9D<:(E"D>#	E--EE E8E:E<E>E Ec                   "   \         P                  R4       RV n        V P                  4       G Rj  xL
  V P	                  4       G Rj  xL
 pV'       g   \        R4      h\         P                  R4       R#  LF L05i)z<Full browser restart for a completely clean reCAPTCHA state.u*   FlowBrowserEngine: Full browser restart…FNz#Failed to restart FlowBrowserEngineu,   FlowBrowserEngine: Full restart complete ✓)r`   ra   r   rr   rX   rf   )r$   oks   & r   _full_restartFlowBrowserEngine._full_restart  sZ     @Aiik::<DEEBC	 	s'   0A=A9A=
A;A=#A=;A=c           	       "    \        \        P                  ! ^^4      4       F  p\        P                  ! ^R4      p\        P                  ! ^dR4      pV P                  P                  P                  W#\        P                  ! ^^4      R7      G Rj  xL
  \        P                  ! \        P                  ! RR4      4      G Rj  xL
  K  	  \        P                  ! R	^4      pV P                  P                  P                  ^ V4      G Rj  xL
  \        P                  ! \        P                  ! RR4      4      G Rj  xL
  R#  L L L= L  \         d     R# i ; i5i)
zFSimulate human-like mouse/scroll behavior to maintain reCAPTCHA score.rO   i  )stepsNg?g333333?皙?      ?ij)r~   randomrandintr   r   mover   rd   uniformwheelrp   )r$   _xyscroll_ys   &    r   _human_like_behavior&FlowBrowserEngine._human_like_behavior  s     
	6>>!Q/0NN3-NN3,jj&&++Aq"8M+NNNmmFNN3$<===	 1
 ~~dC0H**""((H555--sC 8999	 O=59 		sr   E$BE E
2E EAE E2E EE E$
E E E E E!E$ E!!E$c                    V ^8  d   QhRRRR/# )r   	error_msgr   r)   r*   r   )r   s   "r   r   r     s     K Ks Kt Kr   c                   a  . ROp\         ;QJ d    V 3R lV 4       F  '       g   K   R# 	  R# ! V 3R lV 4       4      # )z@Check if an error is reCAPTCHA-related (needs session recovery).c              3  d   <"   T F%  qP                  4       SP                  4       9   x  K'  	  R # 5ir,   )lower).0indr   s   & r   	<genexpr>8FlowBrowserEngine._is_recaptcha_error.<locals>.<genexpr>  s!     Jz99;)//"33zs   -0TF)	recaptchaPERMISSION_DENIEDcaptcha)any)r   
indicatorss   f r   _is_recaptcha_error%FlowBrowserEngine._is_recaptcha_error  s4     C
sJzJssJsJsJzJJJr   c               $    V ^8  d   QhRRRRRR/# )r   media_idr   weightfloatr)   Noner   )r   s   "r   r   r     s"     
 
# 
u 
PT 
r   c                   "   V P                   P                  RRVRV/4      G Rj  xL
  \        P                  RVR,          V4       R#  L$5i)u|  Set a reference image (by mediaGenerationId) for the next generation.

For Coverage Shots: generate the master angle first, then call this with
its media_id before generating subsequent angles to maintain scene fidelity.

Args:
    media_id: The mediaGenerationId from a previous generation response.
    weight: Influence weight of the reference image (0.0–1.0). Default 0.85.
zg(args) => { window.__flowReferenceMediaId = args.mediaId; window.__flowReferenceWeight = args.weight; }mediaIdr   NuE   FlowBrowserEngine: Reference image set — media_id=%s… weight=%.2fN   Nr   rl   r`   ra   )r$   r   r   s   &&&r   set_reference_image%FlowBrowserEngine.set_reference_image  sR      jj!!u(F3
 	
 	
 	SSM	
		
s   $AA%Ac                   V ^8  d   QhRR/# )r   r)   r   r   )r   s   "r   r   r     s     B BT Br   c                   "   V P                   P                  R4      G Rj  xL
  \        P                  R4       R#  L5i)z>Clear the reference image so the next generation is text-only.z/() => { window.__flowReferenceMediaId = null; }Nz*FlowBrowserEngine: Reference image clearedr   r-   s   &r   clear_reference_image'FlowBrowserEngine.clear_reference_image  s1     jj!!"STTT@A 	Us   ?=?FLOW_GENERATION_COOLDOWN4Nc          
     ,    V ^8  d   QhRRRRRRRRRR	/# )
r   promptr   output_path
Path | straspect_ratioseed
int | Noner)   r   r   )r   s   "r   r   r     s<         	
  
r   c                   "   V P                   ;_uu_4       GRj  xL
  V P                  V\        V4      W44      G Rj  xL
 uuRRR4      GRj  xL
  #  L: L L	  + GRj  xL 
 '       g   i     R# ; i5i)z;Generate an image using Flow (Nano Banana Pro / GEM_PIX_2).N)r    _generate_implr   )r$   r   r   r   r   s   &&&&&r   generateFlowBrowserEngine.generate  sJ      ::::,,[)<  :: :::s[   A9AA9 AA AA9AA9AA9A6	"A%#
A6	.A6	0	A9c               $    V ^8  d   QhRRRRRR/# )r   r   r   r   r)   dictr   )r   s   "r   r   r     s&     1Q 1QS 1Q 1Q 1Qr   c                  "   V P                   P                  \        4      G Rj  xL
  V P                   P                  RV4      G Rj  xL
  V P                   P                  R4      pVP	                  4       G Rj  xL
 ^ 8  d+   VP
                  P                  RR7      G Rj  xL
 '       g   \        R4      hVP
                  P                  4       G Rj  xL
  \        P                  ! \        P                  ! RR4      4      G Rj  xL
  VP
                  P                  R	4      G Rj  xL
  \        P                  ! \        P                  ! R
R4      4      G Rj  xL
  VP
                  P                  V4      G Rj  xL
  \        P                  ! \        P                  ! RR4      4      G Rj  xL
  \        P                  R\!        V4      4       V P                   P                  R4      G Rj  xL
 pV'       g$   VP
                  P#                  R4      G Rj  xL
  \        P                  RV4       \%        ^Z4       Fp  p\        P                  ! ^4      G Rj  xL
  V P                   P                  R4      G Rj  xL
 pV'       g   KN  VP'                  R4      '       g   Kg  VR,          u # 	  \        R4      h EL EL ELS EL- EL EL EL EL} EL] EL- L L L Lc5i)zNFill prompt, submit, and wait for the API response. Returns the response dict.NzN(ar) => { window.__flowLastGeneration = null; window.__flowAspectRatio = ar; }rS   rT   rU   z&Prompt textarea not found on Flow pager   r    g333333?gffffff?g?g      ?z+FlowBrowserEngine: Prompt filled (%d chars)a0  
            () => {
                const btns = document.querySelectorAll('button');
                for (const btn of btns) {
                    if (!btn.offsetParent) continue;
                    const text = btn.textContent.trim();
                    const rect = btn.getBoundingClientRect();
                    if (text.includes('arrow_forward') && rect.y > 550 && rect.width < 80) {
                        btn.click();
                        return true;
                    }
                }
                return false;
            }
        Enterz0FlowBrowserEngine: Submit triggered (clicked=%s)z!() => window.__flowLastGenerationresponsez2Timeout waiting for Flow generation response (90s))r   rl   rm   rg   rh   ri   rj   rf   r   r   rd   r   r   fillr`   ra   lenpressr~   get)r$   r   r   rt   r   r   gens   &&&    r   _submit_and_wait"FlowBrowserEngine._submit_and_wait  s      jj!!"4555jj!!\
 	
 	
 ZZ ?@hhj 1$rxx/B/B4/B/P)P)PGHHhhnnmmFNN34555hhmmBmmFNN46777hhmmF###mmFNN34555A3v;O 

++ -   ((..)))FP rA--"""

++,OPPCsswwz**:&	  OPP] 	6	
 !)P57#5  *
 #Ps  #K/K	#K/	K
2K/<K=(K/%K&	K/0(K/K2K/K"K/.K/2K/!K""K/K!2K/7K$8AK/9K':K/K/$K)%AK/&K+'"K/	K-
	K/K/1K/K/K/K/K/K/K/K/!K/$K/'K/)K/+K/-K/c                    V ^8  d   QhRRRR/# )r   r   r   r)   r   r   )r   s   "r   r   r   M  s       # r   c                   V P                  R/ 4      p\        V\        4      '       dU   VP                  RR4      pVP                  RR4      pVP                  RR4      pV'       d   RV RV RV 2# \        V4      # \        V4      # )	z5Extract human-readable error from API error response.rq   coder   messagestatus[z] z: )r   
isinstancer   r   )r   errr   msgr   s   &    r   _extract_error FlowBrowserEngine._extract_errorL  s~     ll7B'c4  7762&D'')R(CWWXr*F26QtfBvhb.DCHD3xr   c          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r   r   r   r   r   r   r   r   r)   r   )r   s   "r   r   r   W  sC     v* v*v* v* 	v*
 v* 
v*r   c                		  "   V P                   '       g   \        R 4      hVP                  P                  RRR7       \        P                  RVP                  V4       Rp\        V P                  ^,           4       EF  pV^ 8  Ed   V P                  \        V^,
          \        V P                  4      ^,
          4      ,          pV'       d   V P                  V4      '       d}    V^8:  d;   \        P                  RVV P                  4       V P                  4       G Rj  xL
  M]\        P                  RVV P                  4       V P                  4       G Rj  xL
  M#\        P                  R	VV P                  VV4       \$        P&                  ! V4      G Rj  xL
  V P)                  W4      G Rj  xL
 p	\        P                  R
\+        V	P-                  4       4      4       RV	9   dV   V P/                  V	4      p\        P#                  RV4       W`P                  8  d   EK  \        RV P                   RV 24      hV	P1                  R. 4      p
V
'       gQ   R\+        V	P-                  4       4       2p\        P#                  RV4       W`P                  8  d   EK'  \        V4      hV
^ ,          pVP1                  R/ 4      P1                  R/ 4      pVP1                  RR4      pV'       gQ   R\+        VP-                  4       4       2p\        P#                  RV4       W`P                  8  d   EK  \        V4      hV P2                  P1                  V4      G Rj  xL
 pVP4                  ^8w  d   \        RVP4                   24      hVP7                  VP8                  4       VP1                  R/ 4      P1                  R/ 4      P1                  R4      pV'       d$   Wn        \        P=                  RVR,          4       VP?                  4       P@                  p\        P                  RVP                  V4       V PC                  4       G Rj  xL
  \$        P&                  ! V PD                  4      G Rj  xL
  Vu # 	  \        R4      h ELq EL:  \          d+   p\        P#                  RT4       \        RT 24      ThRp?ii ; i EL3 EL ELu L L^5i)zFlowBrowserEngine not started.T)parentsexist_oku0   FlowBrowserEngine: Generating → %s (aspect=%s)NuI   FlowBrowserEngine: reCAPTCHA recovery L1 — page refresh (attempt %d/%d)uI   FlowBrowserEngine: reCAPTCHA recovery L2 — full restart (attempt %d/%d)u)   FlowBrowserEngine: Recovery failed — %szSession recovery failed: zAFlowBrowserEngine: Retry %d/%d after %ds backoff (last error: %s)z$FlowBrowserEngine: Response keys: %srq   u#   FlowBrowserEngine: API error — %szFlow API error after z
 retries: mediazNo media in response: zFlowBrowserEngine: %simagegeneratedImagefifeUrlr   zNo fifeUrl in image: zDownload failed: HTTP mediaGenerationIdu*   FlowBrowserEngine: Captured media_id=%s…r   u0   FlowBrowserEngine: Image saved → %s (%d bytes)Unreachable)#r   rf   parentmkdirr`   ra   rE   r~   MAX_RETRIESRETRY_BACKOFFminr   r   r   r   r   rp   rq   r   rd   r   listkeysr   r   r"   status_codewrite_bytescontentr#   debugstatst_sizer   GENERATION_COOLDOWN)r$   r   r   r   r   
last_errorr   backoffrecovery_errr   
media_listfirst_media
image_datafife_urldl_respr   sizes   &&&&&            r   r    FlowBrowserEngine._generate_implW  s%     {{{?@@   =>	
 
T--12G{,,!S););%<q%@A
 $":"::"F"F,"a<"NN k ' $ 0 0
 #'"7"7"999"NN k ' $ 0 0
 #'"4"4"666 NN[((" mmG,,,!226HHHKK>X]]_@UV ("!00:
BJO---"+D,<,<+=Z
|T 
 "gr2J5d8==?6K5LM
4jA---":..$Q-K$"599:JBOJ!~~i4H4T*//:K5L4MN
4jA---":.. !--11(;;G""c)"%;G<O<O;P#QRR##GOO4 ,%r*() 
 &.#I8TW=Y##%--DKKBKDTDTVZ
 ++----- 8 8999E 3H =))k : 7$ ,G +7~F+,	,  -HD <* .9s   C"S&;R!R"R&S'4RRR =SSS6S7B&SBS3A.S!S"B S#A1SS'S<S=SRRS#%SSSSSSS)r   r   r"   r#   r    r   r!   r   r   r   r   )<   )333333?)   
   r      )r   N)__name__
__module____qualname____firstlineno____doc__r%   propertyr.   r2   r7   rX   re   rr   r   r   r   staticmethodr   r   r   r;   osgetenvr  r   r   r   r   r   r   __static_attributes__r   r   r   r   r      s    / 9 9     # #bHbH22@ D K K
(B
 bii(BCHIK#M1Qf  v* v*r   r   zOptional[FlowBrowserEngine]_enginec               $    V ^8  d   QhRRRRRR/# )r   r   r5   r   r)   r   r   )r   s   "r   r   r     s$      2<r   c                P  "   \         ;_uu_4       GRj  xL
  \        '       d/   \        P                  '       d   \        uuRRR4      GRj  xL
  # T ;'       g    \        P                  ! RR4      pT;'       g    \        P                  ! RR4      pV'       d	   V'       g   \        R4      h\        W#4      s\        P                  4       G Rj  xL
 '       g   \        R4      h\        uuRRR4      GRj  xL
  #  L L L1 L  + GRj  xL 
 '       g   i     R# ; i5i)zGet or create the global FlowBrowserEngine singleton.

Credentials can be passed explicitly (from DB) or fall back to env vars.
NGOOGLE_FLOW_SESSION_TOKENr   GOOGLE_FLOW_CSRF_TOKENz#Google Flow cookies not configured.z!Failed to start FlowBrowserEngine)_engine_lockr  r.   r  r  rf   r   rX   )r   r   sessioncsrfs   &&  r   
get_enginer#    s      ||7w))) ||  MM299-H"#MDDRYY'?DdDEE#G2]]_$$BCC || % |||s   D&DD&"D	D	D&DD&D	 D	>D	D	&-D	D	D	D	.D&;D<D&D&D	D&	D#	D
D#	D#		D&c                 j   "   \         '       d!   \         P                  4       G Rj  xL
  Rs R# R#  L
5i)zStop the global engine.N)r  rr   r   r   r   stop_enginer%    s(      wlln s   $313c               4    V ^8  d   QhRRRRRRRRRR	R
RRR/# )r   r   r   r   r   r   r   r   reference_media_idr5   reference_weightr   r)   r   r   )r   s   "r   r   r     sN     1 111 1 	1
 #1 1 
1r   c                F  "   \        4       G Rj  xL
 pV'       d   VP                  WE4      G Rj  xL
   VP                  WW#4      G Rj  xL
 V'       d   VP                  4       G Rj  xL
  # #  La LB L) L  T'       d   TP                  4       G Rj  xL 
  i i ; i5i)uL  High-level: generate an image using the Flow browser engine.

Args:
    prompt: Text prompt for image generation.
    output_path: Where to save the generated image.
    aspect_ratio: IMAGE_ASPECT_RATIO_PORTRAIT / LANDSCAPE / SQUARE.
    seed: Optional fixed seed for reproducibility.
    reference_media_id: If set, uses this mediaGenerationId as imageInputs
        reference (image-to-image). Use for Coverage Shot angles after the
        master frame — maintains scene environment consistency across angles.
    reference_weight: Influence strength of reference image (0.0–1.0).
N)r#  r   r   r   )r   r   r   r   r'  r(  engines   &&&&&& r   generate_imager+    s     ( <F(();NNN1__V,MM..000   NM 1 ..000 sk   B!A2B!B!A4B!A: A6A: B!+A8,B!4B!6A: 8B!:BBBB!)NN)r   NNr  )__conditional_annotations__r  
__future__r   r   jsonloggingr  r   timepathlibr   typingr   rn   	getLoggerr  r`   rb   ASPECT_RATIOSr]   rm   r   r  __annotations__r   r   r#  r%  r+  )r,  s   @r   <module>r6     s     #    	     			8	$2-/)ETB JV* V*v (,	$ +||~*1 1r   