1
+ import asyncio
2
+ import argparse
3
+ import logging
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import Dict
10
+
11
+ def setup_environment (debug : bool ):
12
+ if not debug :
13
+ os .environ ['BROWSER_USE_SETUP_LOGGING' ] = 'false'
14
+ os .environ ['BROWSER_USE_LOGGING_LEVEL' ] = 'critical'
15
+ logging .getLogger ().setLevel (logging .CRITICAL )
16
+ else :
17
+ os .environ ['BROWSER_USE_SETUP_LOGGING' ] = 'true'
18
+ os .environ ['BROWSER_USE_LOGGING_LEVEL' ] = 'info'
19
+
20
+ parser = argparse .ArgumentParser (description = 'Generate ads from landing pages using browser-use + 🍌' )
21
+ parser .add_argument ('url' , nargs = '?' , help = 'Landing page URL to analyze' )
22
+ parser .add_argument ('--debug' , action = 'store_true' , default = False , help = 'Enable debug mode (show browser, verbose logs)' )
23
+ args = parser .parse_args ()
24
+ setup_environment (args .debug )
25
+
26
+ from PIL import Image
27
+ from google import genai
28
+ from browser_use import Agent , BrowserSession
29
+ from browser_use .llm .google import ChatGoogle
30
+ import aiofiles
31
+
32
+ GOOGLE_API_KEY = os .getenv ("GOOGLE_API_KEY" )
33
+
34
+ class LandingPageAnalyzer :
35
+ def __init__ (self , debug : bool = False ):
36
+ self .debug = debug
37
+ self .llm = ChatGoogle (
38
+ model = "gemini-2.0-flash-exp" ,
39
+ api_key = GOOGLE_API_KEY
40
+ )
41
+ self .output_dir = Path ("output" )
42
+ self .output_dir .mkdir (exist_ok = True )
43
+
44
+ async def analyze_landing_page (self , url : str ) -> Dict :
45
+ browser_session = BrowserSession (
46
+ headless = not self .debug , # headless=False only when debug=True
47
+ disable_security = True
48
+ )
49
+
50
+ agent = Agent (
51
+ task = f"""Go to { url } and quickly extract key brand information for Instagram ad creation.
52
+
53
+ Steps:
54
+ 1. Navigate to the website
55
+ 2. From the initial view, extract ONLY these essentials:
56
+ - Brand/Product name
57
+ - Main tagline or value proposition (one sentence)
58
+ - Primary call-to-action text
59
+ - Any visible pricing or special offer
60
+ 3. Scroll down half a page, twice (0.5 pages each) to check for any key info
61
+ 4. Done - keep it simple and focused on the brand
62
+
63
+ Return ONLY the key brand info, not page structure details.""" ,
64
+ llm = self .llm ,
65
+ browser_session = browser_session ,
66
+ max_actions_per_step = 2 ,
67
+ step_timeout = 30 ,
68
+ use_thinking = False ,
69
+ vision_detail_level = 'high' ,
70
+ )
71
+
72
+ screenshot_path = None
73
+ timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
74
+
75
+ # Take screenshot after page fully loads
76
+ async def screenshot_callback (agent_instance ):
77
+ nonlocal screenshot_path
78
+ import asyncio
79
+ await asyncio .sleep (4 )
80
+ screenshot_path = self .output_dir / f"landing_page_{ timestamp } .png"
81
+ active_session = agent_instance .browser_session
82
+ screenshot_data = await active_session .take_screenshot (path = str (screenshot_path ), full_page = False )
83
+
84
+ import asyncio
85
+ screenshot_task = asyncio .create_task (screenshot_callback (agent ))
86
+
87
+ history = await agent .run ()
88
+
89
+ try :
90
+ await screenshot_task
91
+ except Exception as e :
92
+ print (f"Screenshot task failed: { e } " )
93
+
94
+ analysis = history .final_result ()
95
+ if not analysis :
96
+ analysis = "No analysis content extracted"
97
+
98
+ return {
99
+ 'url' : url ,
100
+ 'analysis' : analysis ,
101
+ 'screenshot_path' : screenshot_path ,
102
+ 'timestamp' : timestamp
103
+ }
104
+
105
+ class AdGenerator :
106
+ def __init__ (self , api_key : str = GOOGLE_API_KEY ):
107
+ self .client = genai .Client (api_key = api_key )
108
+ self .output_dir = Path ("output" )
109
+ self .output_dir .mkdir (exist_ok = True )
110
+
111
+ def create_ad_prompt (self , browser_analysis : str ) -> str :
112
+ prompt = f"""Create an Instagram ad for this brand:
113
+
114
+ { browser_analysis }
115
+
116
+ Create a vibrant, eye-catching Instagram ad image with:
117
+ - Try to use the colors and style of the logo or brand, else:
118
+ - Bold, modern gradient background with bright colors
119
+ - Large, playful sans-serif text with the product/service name from the analysis
120
+ - Trendy design elements: geometric shapes, sparkles, emojis
121
+ - Fun bubbles or badges for any pricing or special offers mentioned
122
+ - Call-to-action button with text from the analysis
123
+ - Emphasizes the key value proposition from the analysis
124
+ - Uses visual elements that match the brand personality
125
+ - Square format (1:1 ratio)
126
+ - Use color psychology to drive action
127
+
128
+ Style: Modern Instagram advertisement, (1:1), scroll-stopping, professional but playful, conversion-focused"""
129
+ return prompt
130
+
131
+ async def generate_ad_image (self , prompt : str , screenshot_path : Path = None ) -> bytes :
132
+ try :
133
+ contents = [prompt ]
134
+
135
+ if screenshot_path and screenshot_path .exists ():
136
+ screenshot_prompt = f"\n \n Here is the actual landing page screenshot to reference for design inspiration, colors, layout, and visual style:"
137
+ text_part = prompt + screenshot_prompt
138
+ img = Image .open (screenshot_path )
139
+ w ,h = img .size
140
+ img = img .crop (((w - min (w ,h ))// 2 ,(h - min (w ,h ))// 2 ,(w + min (w ,h ))// 2 ,(h + min (w ,h ))// 2 ))
141
+ contents = [text_part ,img ]
142
+
143
+ response = self .client .models .generate_content (
144
+ model = "gemini-2.5-flash-image-preview" ,
145
+ contents = contents
146
+ )
147
+
148
+ for part in response .candidates [0 ].content .parts :
149
+ if hasattr (part , 'inline_data' ) and part .inline_data :
150
+ return part .inline_data .data
151
+
152
+ except Exception as e :
153
+ print (f"❌ Image generation failed: { e } " )
154
+
155
+ async def save_results (self , ad_image : bytes , prompt : str , analysis : str , url : str , timestamp : str ) -> str :
156
+ image_path = self .output_dir / f"ad_{ timestamp } .png"
157
+ with open (image_path , 'wb' ) as f :
158
+ f .write (ad_image )
159
+
160
+ analysis_path = self .output_dir / f"analysis_{ timestamp } .txt"
161
+ async with aiofiles .open (analysis_path , 'w' , encoding = 'utf-8' ) as f :
162
+ await f .write (f"URL: { url } \n \n " )
163
+ await f .write ("BROWSER-USE ANALYSIS:\n " )
164
+ await f .write (analysis )
165
+ await f .write ("\n \n GENERATED PROMPT:\n " )
166
+ await f .write (prompt )
167
+
168
+ return str (image_path )
169
+
170
+ def open_image (image_path : str ):
171
+ """Open image with default system viewer"""
172
+ try :
173
+ if sys .platform .startswith ('darwin' ):
174
+ # macOS
175
+ subprocess .run (['open' , image_path ], check = True )
176
+ elif sys .platform .startswith ('win' ):
177
+ # Windows
178
+ os .startfile (image_path )
179
+ else :
180
+ # Linux
181
+ subprocess .run (['xdg-open' , image_path ], check = True )
182
+ except Exception as e :
183
+ print (f"❌ Could not open image: { e } " )
184
+
185
+ async def create_ad_from_landing_page (url : str , debug : bool = False ):
186
+ analyzer = LandingPageAnalyzer (debug = debug )
187
+ generator = AdGenerator ()
188
+
189
+ try :
190
+ print (f"🚀 Analyzing { url } ..." )
191
+ page_data = await analyzer .analyze_landing_page (url )
192
+
193
+ prompt = generator .create_ad_prompt (page_data ['analysis' ])
194
+ ad_image = await generator .generate_ad_image (prompt , page_data .get ('screenshot_path' ))
195
+ result_path = await generator .save_results (
196
+ ad_image ,
197
+ prompt ,
198
+ page_data ['analysis' ],
199
+ url ,
200
+ page_data ['timestamp' ]
201
+ )
202
+
203
+ print (f"🎨 Generated ad: { result_path } " )
204
+ if page_data .get ('screenshot_path' ):
205
+ print (f"📸 Page screenshot: { page_data ['screenshot_path' ]} " )
206
+ open_image (result_path )
207
+
208
+ return result_path
209
+
210
+ except Exception as e :
211
+ print (f"❌ Error: { e } " )
212
+ raise
213
+
214
+
215
+ if __name__ == "__main__" :
216
+ url = args .url
217
+ if not url :
218
+ url = input ("🔗 Enter URL: " ).strip () or "https://www.apple.com/iphone-16-pro/"
219
+
220
+ asyncio .run (create_ad_from_landing_page (url , debug = args .debug ))
0 commit comments