nihalaninihal commited on
Commit
3e4f04a
Β·
verified Β·
1 Parent(s): 4ba56a4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +281 -219
app.py CHANGED
@@ -6,9 +6,14 @@ import re
6
  import struct
7
  import tempfile
8
  import asyncio
 
9
  from google import genai
10
  from google.genai import types
11
 
 
 
 
 
12
  # Direct API key - WARNING: This is not recommended for production use
13
  GEMINI_API_KEY = "AIzaSyDy5hjn9NFamWhBjqsVsD2WSoFNr2MrHSw"
14
 
@@ -76,267 +81,324 @@ def parse_audio_mime_type(mime_type: str) -> dict[str, int | None]:
76
 
77
  def fetch_web_content(url, progress=gr.Progress()):
78
  """Fetch and analyze web content using Gemini with tools."""
79
- progress(0.1, desc="Initializing Gemini client...")
80
-
81
- if not GEMINI_API_KEY:
82
- raise ValueError("GEMINI_API_KEY is not set")
83
-
84
- client = genai.Client(api_key=GEMINI_API_KEY)
85
-
86
- progress(0.2, desc="Fetching web content...")
 
 
 
87
 
88
- model = "gemini-2.5-flash-preview-04-17"
89
- contents = [
90
- types.Content(
91
- role="user",
92
- parts=[
93
- types.Part.from_text(text=f"""Please analyze the content from this URL: {url}
94
-
95
- Create a comprehensive summary that would be suitable for a podcast discussion between two hosts.
96
- Focus on the key points, interesting aspects, and discussion-worthy topics.
97
-
98
- Format your response as a natural conversation between two podcast hosts discussing the content."""),
99
- ],
100
- ),
101
- ]
102
-
103
- tools = [
104
- types.Tool(url_context=types.UrlContext()),
105
- types.Tool(google_search=types.GoogleSearch()),
106
- ]
107
-
108
- generate_content_config = types.GenerateContentConfig(
109
- tools=tools,
110
- response_mime_type="text/plain",
111
- )
112
 
113
- progress(0.4, desc="Analyzing content with AI...")
114
-
115
- content_text = ""
116
- for chunk in client.models.generate_content_stream(
117
- model=model,
118
- contents=contents,
119
- config=generate_content_config,
120
- ):
121
- content_text += chunk.text
122
-
123
- progress(0.6, desc="Content analysis complete!")
124
- return content_text
 
 
 
 
 
 
 
125
 
126
 
127
  def generate_podcast_from_content(content_text, speaker1_name="Anna Chope", speaker2_name="Adam Chan", progress=gr.Progress()):
128
  """Generate audio podcast from text content."""
129
- progress(0.7, desc="Generating podcast audio...")
130
-
131
- if not GEMINI_API_KEY:
132
- raise ValueError("GEMINI_API_KEY is not set")
133
-
134
- client = genai.Client(api_key=GEMINI_API_KEY)
 
 
135
 
136
- model = "gemini-2.5-flash-preview-tts"
137
-
138
- podcast_prompt = f"""Please read aloud the following content in a natural podcast interview style with two distinct speakers.
139
- Make it sound conversational and engaging:
140
 
141
- {content_text}
142
-
143
- If the content is not already in dialogue format, please convert it into a natural conversation between two podcast hosts Speaker 1 {speaker1_name} and Speaker 2 {speaker2_name} discussing the topic. They should introduce themselves at the beginning."""
144
 
145
- contents = [
146
- types.Content(
147
- role="user",
148
- parts=[
149
- types.Part.from_text(text=podcast_prompt),
 
 
 
 
 
 
 
 
150
  ],
151
- ),
152
- ]
153
-
154
- generate_content_config = types.GenerateContentConfig(
155
- temperature=1,
156
- response_modalities=[
157
- "audio",
158
- ],
159
- speech_config=types.SpeechConfig(
160
- multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
161
- speaker_voice_configs=[
162
- types.SpeakerVoiceConfig(
163
- speaker="Speaker 1",
164
- voice_config=types.VoiceConfig(
165
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
166
- voice_name="Zephyr"
167
- )
168
  ),
169
- ),
170
- types.SpeakerVoiceConfig(
171
- speaker="Speaker 2",
172
- voice_config=types.VoiceConfig(
173
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
174
- voice_name="Puck"
175
- )
176
  ),
177
- ),
178
- ]
179
  ),
180
- ),
181
- )
182
 
183
- progress(0.8, desc="Converting to audio...")
184
-
185
- # Create temporary file
186
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
187
- temp_file.close()
188
-
189
- audio_chunks = []
190
-
191
- for chunk in client.models.generate_content_stream(
192
- model=model,
193
- contents=contents,
194
- config=generate_content_config,
195
- ):
196
- if (
197
- chunk.candidates is None
198
- or chunk.candidates[0].content is None
199
- or chunk.candidates[0].content.parts is None
200
  ):
201
- continue
202
-
203
- if (chunk.candidates[0].content.parts[0].inline_data and
204
- chunk.candidates[0].content.parts[0].inline_data.data):
205
-
206
- inline_data = chunk.candidates[0].content.parts[0].inline_data
207
- data_buffer = inline_data.data
208
-
209
- # Convert to WAV if needed
210
- if inline_data.mime_type != "audio/wav":
211
- data_buffer = convert_to_wav(inline_data.data, inline_data.mime_type)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- audio_chunks.append(data_buffer)
214
-
215
- # Combine all audio chunks
216
- if audio_chunks:
217
- # For simplicity, just use the first chunk (you might want to concatenate them)
218
- final_audio = audio_chunks[0]
219
- save_binary_file(temp_file.name, final_audio)
220
- progress(1.0, desc="Podcast generated successfully!")
221
- return temp_file.name
222
- else:
223
- raise ValueError("No audio data generated")
224
 
225
 
226
  def generate_web_podcast(url, speaker1_name, speaker2_name, progress=gr.Progress()):
227
  """Main function to fetch web content and generate podcast."""
228
  try:
229
  progress(0.0, desc="Starting podcast generation...")
 
 
 
 
 
230
 
231
- # Validate URL
232
- if not url or not url.startswith(('http://', 'https://')):
233
  raise ValueError("Please enter a valid URL starting with http:// or https://")
234
 
 
 
 
 
 
 
235
  # Step 1: Fetch and analyze web content
236
- content_text = fetch_web_content(url, progress)
 
 
 
237
 
238
  # Step 2: Generate podcast from the content
239
- audio_file = generate_podcast_from_content(content_text, speaker1_name, speaker2_name, progress)
240
 
 
241
  return audio_file, "βœ… Podcast generated successfully!", content_text
242
 
243
  except Exception as e:
244
  error_msg = f"❌ Error generating podcast: {str(e)}"
 
245
  return None, error_msg, ""
246
 
247
 
248
  # Create Gradio interface
249
  def create_interface():
250
- with gr.Blocks(title="πŸŽ™οΈ Web-to-Podcast Generator", theme=gr.themes.Soft()) as demo:
251
- gr.Markdown("""
252
- # πŸŽ™οΈ Web-to-Podcast Generator
253
-
254
- Transform any website into an engaging podcast conversation between two AI hosts!
255
-
256
- Simply paste a URL and let AI create a natural dialogue discussing the content.
257
- """)
258
-
259
- with gr.Row():
260
- with gr.Column(scale=2):
261
- url_input = gr.Textbox(
262
- label="Website URL",
263
- placeholder="https://example.com",
264
- info="Enter the URL of the website you want to convert to a podcast"
265
- )
266
-
267
- with gr.Row():
268
- speaker1_input = gr.Textbox(
269
- label="Host 1 Name",
270
- value="Anna Chope",
271
- info="Name of the first podcast host"
272
- )
273
- speaker2_input = gr.Textbox(
274
- label="Host 2 Name",
275
- value="Adam Chan",
276
- info="Name of the second podcast host"
277
  )
278
-
279
- generate_btn = gr.Button("πŸŽ™οΈ Generate Podcast", variant="primary", size="lg")
280
-
281
- with gr.Column(scale=1):
282
- gr.Markdown("""
283
- ### Instructions:
284
- 1. Enter a website URL
285
- 2. Customize host names (optional)
286
- 3. Click "Generate Podcast"
287
- 4. Wait for the AI to analyze content and create audio
288
- 5. Download your podcast!
289
-
290
- ### Examples:
291
- - News articles
292
- - Blog posts
293
- - Product pages
294
- - Documentation
295
- - Research papers
296
- """)
297
-
298
- with gr.Row():
299
- status_output = gr.Textbox(label="Status", interactive=False)
300
-
301
- with gr.Row():
302
- audio_output = gr.Audio(label="Generated Podcast", type="filepath")
303
-
304
- with gr.Accordion("πŸ“ Generated Script Preview", open=False):
305
- script_output = gr.Textbox(
306
- label="Podcast Script",
307
- lines=10,
308
- interactive=False,
309
- info="Preview of the conversation script generated from the website content"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
 
312
- # Event handlers
313
- generate_btn.click(
314
- fn=generate_web_podcast,
315
- inputs=[url_input, speaker1_input, speaker2_input],
316
- outputs=[audio_output, status_output, script_output],
317
- show_progress=True
318
- )
319
-
320
- # Examples
321
- gr.Examples(
322
- examples=[
323
- ["https://github.com/weaviate/weaviate", "Anna", "Adam"],
324
- ["https://huggingface.co/blog", "Sarah", "Mike"],
325
- ["https://openai.com/blog", "Emma", "John"],
326
- ],
327
- inputs=[url_input, speaker1_input, speaker2_input],
328
- )
329
-
330
- gr.Markdown("""
331
- ---
332
- **Note:** API key is now directly embedded in the code for convenience.
333
-
334
- The generated podcast will feature two AI voices having a natural conversation about the website content.
335
- """)
336
 
337
- return demo
 
 
338
 
339
 
340
  if __name__ == "__main__":
341
- demo = create_interface()
342
- demo.launch(debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import struct
7
  import tempfile
8
  import asyncio
9
+ import logging
10
  from google import genai
11
  from google.genai import types
12
 
13
+ # Configure logging
14
+ logging.basicConfig(level=logging.INFO)
15
+ logger = logging.getLogger(__name__)
16
+
17
  # Direct API key - WARNING: This is not recommended for production use
18
  GEMINI_API_KEY = "AIzaSyDy5hjn9NFamWhBjqsVsD2WSoFNr2MrHSw"
19
 
 
81
 
82
  def fetch_web_content(url, progress=gr.Progress()):
83
  """Fetch and analyze web content using Gemini with tools."""
84
+ try:
85
+ progress(0.1, desc="Initializing Gemini client...")
86
+ logger.info("Initializing Gemini client...")
87
+
88
+ if not GEMINI_API_KEY:
89
+ raise ValueError("GEMINI_API_KEY is not set")
90
+
91
+ client = genai.Client(api_key=GEMINI_API_KEY)
92
+
93
+ progress(0.2, desc="Fetching web content...")
94
+ logger.info(f"Fetching content from URL: {url}")
95
 
96
+ model = "gemini-2.5-flash-preview-04-17"
97
+ contents = [
98
+ types.Content(
99
+ role="user",
100
+ parts=[
101
+ types.Part.from_text(text=f"""Please analyze the content from this URL: {url}
102
+
103
+ Create a comprehensive summary that would be suitable for a podcast discussion between two hosts.
104
+ Focus on the key points, interesting aspects, and discussion-worthy topics.
105
+
106
+ Format your response as a natural conversation between two podcast hosts discussing the content."""),
107
+ ],
108
+ ),
109
+ ]
110
+
111
+ tools = [
112
+ types.Tool(url_context=types.UrlContext()),
113
+ types.Tool(google_search=types.GoogleSearch()),
114
+ ]
115
+
116
+ generate_content_config = types.GenerateContentConfig(
117
+ tools=tools,
118
+ response_mime_type="text/plain",
119
+ )
120
 
121
+ progress(0.4, desc="Analyzing content with AI...")
122
+ logger.info("Generating content with Gemini...")
123
+
124
+ content_text = ""
125
+ for chunk in client.models.generate_content_stream(
126
+ model=model,
127
+ contents=contents,
128
+ config=generate_content_config,
129
+ ):
130
+ if chunk.text:
131
+ content_text += chunk.text
132
+
133
+ progress(0.6, desc="Content analysis complete!")
134
+ logger.info(f"Content generation complete. Length: {len(content_text)} characters")
135
+ return content_text
136
+
137
+ except Exception as e:
138
+ logger.error(f"Error in fetch_web_content: {e}")
139
+ raise e
140
 
141
 
142
  def generate_podcast_from_content(content_text, speaker1_name="Anna Chope", speaker2_name="Adam Chan", progress=gr.Progress()):
143
  """Generate audio podcast from text content."""
144
+ try:
145
+ progress(0.7, desc="Generating podcast audio...")
146
+ logger.info("Starting audio generation...")
147
+
148
+ if not GEMINI_API_KEY:
149
+ raise ValueError("GEMINI_API_KEY is not set")
150
+
151
+ client = genai.Client(api_key=GEMINI_API_KEY)
152
 
153
+ model = "gemini-2.5-flash-preview-tts"
154
+
155
+ podcast_prompt = f"""Please read aloud the following content in a natural podcast interview style with two distinct speakers.
156
+ Make it sound conversational and engaging:
157
 
158
+ {content_text}
159
+
160
+ If the content is not already in dialogue format, please convert it into a natural conversation between two podcast hosts Speaker 1 {speaker1_name} and Speaker 2 {speaker2_name} discussing the topic. They should introduce themselves at the beginning."""
161
 
162
+ contents = [
163
+ types.Content(
164
+ role="user",
165
+ parts=[
166
+ types.Part.from_text(text=podcast_prompt),
167
+ ],
168
+ ),
169
+ ]
170
+
171
+ generate_content_config = types.GenerateContentConfig(
172
+ temperature=1,
173
+ response_modalities=[
174
+ "audio",
175
  ],
176
+ speech_config=types.SpeechConfig(
177
+ multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
178
+ speaker_voice_configs=[
179
+ types.SpeakerVoiceConfig(
180
+ speaker="Speaker 1",
181
+ voice_config=types.VoiceConfig(
182
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
183
+ voice_name="Zephyr"
184
+ )
185
+ ),
 
 
 
 
 
 
 
186
  ),
187
+ types.SpeakerVoiceConfig(
188
+ speaker="Speaker 2",
189
+ voice_config=types.VoiceConfig(
190
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
191
+ voice_name="Puck"
192
+ )
193
+ ),
194
  ),
195
+ ]
196
+ ),
197
  ),
198
+ )
 
199
 
200
+ progress(0.8, desc="Converting to audio...")
201
+ logger.info("Generating audio stream...")
202
+
203
+ # Create temporary file
204
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
205
+ temp_file.close()
206
+
207
+ audio_chunks = []
208
+
209
+ for chunk in client.models.generate_content_stream(
210
+ model=model,
211
+ contents=contents,
212
+ config=generate_content_config,
 
 
 
 
213
  ):
214
+ if (
215
+ chunk.candidates is None
216
+ or chunk.candidates[0].content is None
217
+ or chunk.candidates[0].content.parts is None
218
+ ):
219
+ continue
220
+
221
+ if (chunk.candidates[0].content.parts[0].inline_data and
222
+ chunk.candidates[0].content.parts[0].inline_data.data):
223
+
224
+ inline_data = chunk.candidates[0].content.parts[0].inline_data
225
+ data_buffer = inline_data.data
226
+
227
+ # Convert to WAV if needed
228
+ if inline_data.mime_type != "audio/wav":
229
+ data_buffer = convert_to_wav(inline_data.data, inline_data.mime_type)
230
+
231
+ audio_chunks.append(data_buffer)
232
+
233
+ # Combine all audio chunks
234
+ if audio_chunks:
235
+ # For simplicity, just use the first chunk (you might want to concatenate them)
236
+ final_audio = audio_chunks[0]
237
+ save_binary_file(temp_file.name, final_audio)
238
+ progress(1.0, desc="Podcast generated successfully!")
239
+ logger.info(f"Audio file saved: {temp_file.name}")
240
+ return temp_file.name
241
+ else:
242
+ raise ValueError("No audio data generated")
243
 
244
+ except Exception as e:
245
+ logger.error(f"Error in generate_podcast_from_content: {e}")
246
+ raise e
 
 
 
 
 
 
 
 
247
 
248
 
249
  def generate_web_podcast(url, speaker1_name, speaker2_name, progress=gr.Progress()):
250
  """Main function to fetch web content and generate podcast."""
251
  try:
252
  progress(0.0, desc="Starting podcast generation...")
253
+ logger.info(f"Starting podcast generation for URL: {url}")
254
+
255
+ # Validate inputs
256
+ if not url or not url.strip():
257
+ raise ValueError("Please enter a valid URL")
258
 
259
+ if not url.startswith(('http://', 'https://')):
 
260
  raise ValueError("Please enter a valid URL starting with http:// or https://")
261
 
262
+ if not speaker1_name or not speaker1_name.strip():
263
+ speaker1_name = "Anna Chope"
264
+
265
+ if not speaker2_name or not speaker2_name.strip():
266
+ speaker2_name = "Adam Chan"
267
+
268
  # Step 1: Fetch and analyze web content
269
+ content_text = fetch_web_content(url.strip(), progress)
270
+
271
+ if not content_text or len(content_text.strip()) < 50:
272
+ raise ValueError("Unable to extract sufficient content from the URL")
273
 
274
  # Step 2: Generate podcast from the content
275
+ audio_file = generate_podcast_from_content(content_text, speaker1_name.strip(), speaker2_name.strip(), progress)
276
 
277
+ logger.info("Podcast generation completed successfully")
278
  return audio_file, "βœ… Podcast generated successfully!", content_text
279
 
280
  except Exception as e:
281
  error_msg = f"❌ Error generating podcast: {str(e)}"
282
+ logger.error(f"Error in generate_web_podcast: {e}")
283
  return None, error_msg, ""
284
 
285
 
286
  # Create Gradio interface
287
  def create_interface():
288
+ try:
289
+ with gr.Blocks(
290
+ title="πŸŽ™οΈ Web-to-Podcast Generator",
291
+ theme=gr.themes.Soft(),
292
+ analytics_enabled=False
293
+ ) as demo:
294
+ gr.Markdown("""
295
+ # πŸŽ™οΈ Web-to-Podcast Generator
296
+
297
+ Transform any website into an engaging podcast conversation between two AI hosts!
298
+
299
+ Simply paste a URL and let AI create a natural dialogue discussing the content.
300
+ """)
301
+
302
+ with gr.Row():
303
+ with gr.Column(scale=2):
304
+ url_input = gr.Textbox(
305
+ label="Website URL",
306
+ placeholder="https://example.com",
307
+ info="Enter the URL of the website you want to convert to a podcast",
308
+ lines=1
 
 
 
 
 
 
309
  )
310
+
311
+ with gr.Row():
312
+ speaker1_input = gr.Textbox(
313
+ label="Host 1 Name",
314
+ value="Anna Chope",
315
+ info="Name of the first podcast host",
316
+ lines=1
317
+ )
318
+ speaker2_input = gr.Textbox(
319
+ label="Host 2 Name",
320
+ value="Adam Chan",
321
+ info="Name of the second podcast host",
322
+ lines=1
323
+ )
324
+
325
+ generate_btn = gr.Button("πŸŽ™οΈ Generate Podcast", variant="primary", size="lg")
326
+
327
+ with gr.Column(scale=1):
328
+ gr.Markdown("""
329
+ ### Instructions:
330
+ 1. Enter a website URL
331
+ 2. Customize host names (optional)
332
+ 3. Click "Generate Podcast"
333
+ 4. Wait for the AI to analyze content and create audio
334
+ 5. Download your podcast!
335
+
336
+ ### Examples:
337
+ - News articles
338
+ - Blog posts
339
+ - Product pages
340
+ - Documentation
341
+ - Research papers
342
+ """)
343
+
344
+ with gr.Row():
345
+ status_output = gr.Textbox(label="Status", interactive=False, lines=2)
346
+
347
+ with gr.Row():
348
+ audio_output = gr.Audio(label="Generated Podcast", type="filepath")
349
+
350
+ with gr.Accordion("πŸ“ Generated Script Preview", open=False):
351
+ script_output = gr.Textbox(
352
+ label="Podcast Script",
353
+ lines=10,
354
+ interactive=False,
355
+ info="Preview of the conversation script generated from the website content"
356
+ )
357
+
358
+ # Event handlers
359
+ generate_btn.click(
360
+ fn=generate_web_podcast,
361
+ inputs=[url_input, speaker1_input, speaker2_input],
362
+ outputs=[audio_output, status_output, script_output],
363
+ show_progress=True
364
  )
365
+
366
+ # Examples
367
+ gr.Examples(
368
+ examples=[
369
+ ["https://github.com/weaviate/weaviate", "Anna", "Adam"],
370
+ ["https://huggingface.co/blog", "Sarah", "Mike"],
371
+ ["https://openai.com/blog", "Emma", "John"],
372
+ ],
373
+ inputs=[url_input, speaker1_input, speaker2_input],
374
+ )
375
+
376
+ gr.Markdown("""
377
+ ---
378
+ **Note:** API key is now directly embedded in the code for convenience.
379
+
380
+ The generated podcast will feature two AI voices having a natural conversation about the website content.
381
+ """)
382
 
383
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
+ except Exception as e:
386
+ logger.error(f"Error creating interface: {e}")
387
+ raise e
388
 
389
 
390
  if __name__ == "__main__":
391
+ try:
392
+ logger.info("Starting Web-to-Podcast Generator...")
393
+ demo = create_interface()
394
+ demo.launch(
395
+ server_name="0.0.0.0",
396
+ server_port=7860,
397
+ share=False,
398
+ debug=False,
399
+ show_error=True
400
+ )
401
+ except Exception as e:
402
+ logger.error(f"Failed to launch application: {e}")
403
+ print(f"Error: {e}")
404
+ raise e