siddhartharyaai commited on
Commit
67c5a3a
·
verified ·
1 Parent(s): c3f5bd4

Update utils.py

Browse files
Files changed (1) hide show
  1. utils.py +258 -260
utils.py CHANGED
@@ -15,9 +15,7 @@ import numpy as np
15
  import torch
16
  import random
17
 
18
- # --- CHANGE THIS IMPORT ---
19
- # OLD: from opendeepresearch.agent import OpenDeepResearcher
20
- # NEW:
21
  from smolagents.examples.open_deep_research.agent import OpenDeepResearcher
22
  from report_structure import generate_report
23
 
@@ -84,8 +82,8 @@ def query_llm_for_additional_info(topic: str, existing_text: str) -> str:
84
  # No longer needed
85
  pass
86
  def research_topic(topic: str) -> str:
87
- # No longer needed
88
- pass
89
 
90
  def fetch_wikipedia_summary(topic: str) -> str:
91
  # No longer needed
@@ -192,7 +190,7 @@ def generate_script(
192
  "temperature": 0.7
193
  }
194
  response = requests.post("https://openrouter.ai/api/v1/chat/completions",
195
- headers=headers, data=json.dumps(data))
196
  response.raise_for_status()
197
  raw_content = response.json()["choices"][0]["message"]["content"].strip()
198
  except Exception as e:
@@ -222,13 +220,13 @@ def generate_script(
222
  d["speaker"] = "Jane"
223
  d["display_speaker"] = raw_speaker
224
 
225
- new_dialogue_items = []
226
- for d in dialogue_list:
227
- if "display_speaker" not in d:
228
- d["display_speaker"] = d["speaker"]
229
- new_dialogue_items.append(DialogueItem(**d))
230
 
231
- return Dialogue(dialogue=new_dialogue_items)
232
  except json.JSONDecodeError as e:
233
  print("[ERROR] JSON decoding (format) failed:", e)
234
  raise ValueError(f"Failed to parse dialogue: {str(e)}")
@@ -236,261 +234,261 @@ def generate_script(
236
  print("[ERROR] JSON decoding failed:", e)
237
  raise ValueError(f"Failed to parse dialogue: {str(e)}")
238
 
239
- def transcribe_youtube_video(video_url: str) -> str:
240
- print("[LOG] Transcribing YouTube video via RapidAPI:", video_url)
241
- video_id_match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11})", video_url)
242
- if not video_id_match:
243
- raise ValueError(f"Invalid YouTube URL: {video_url}, cannot extract video ID.")
244
-
245
- video_id = video_id_match.group(1)
246
- print("[LOG] Extracted video ID:", video_id)
247
-
248
- base_url = "https://youtube-transcriptor.p.rapidapi.com/transcript"
249
- params = {"video_id": video_id, "lang": "en"}
250
- headers = {
251
- "x-rapidapi-host": "youtube-transcriptor.p.rapidapi.com",
252
- "x-rapidapi-key": os.environ.get("RAPIDAPI_KEY")
253
- }
254
-
255
- try:
256
- response = requests.get(base_url, headers=headers, params=params, timeout=30)
257
- print("[LOG] RapidAPI Response Status Code:", response.status_code)
258
- print("[LOG] RapidAPI Response Body:", response.text)
259
-
260
- if response.status_code != 200:
261
- raise ValueError(f"RapidAPI transcription error: {response.status_code}, {response.text}")
262
-
263
- data = response.json()
264
- if not isinstance(data, list) or not data:
265
- raise ValueError(f"Unexpected transcript format or empty transcript: {data}")
266
 
267
- transcript_as_text = data[0].get('transcriptionAsText', '').strip()
268
- if not transcript_as_text:
269
- raise ValueError("transcriptionAsText field is missing or empty.")
270
 
271
- print("[LOG] Transcript retrieval successful.")
272
- print(f"[DEBUG] Transcript Length: {len(transcript_as_text)} characters.")
273
- snippet = transcript_as_text[:200] + "..." if len(transcript_as_text) > 200 else transcript_as_text
274
- print(f"[DEBUG] Transcript Snippet: {snippet}")
 
 
275
 
276
- return transcript_as_text
277
- except Exception as e:
278
- print("[ERROR] RapidAPI transcription error:", e)
279
- raise ValueError(f"Error transcribing YouTube video via RapidAPI: {str(e)}")
280
 
281
- def generate_audio_mp3(text: str, speaker: str) -> str:
282
- try:
283
- import streamlit as st
284
- print(f"[LOG] Generating audio for speaker: {speaker}")
285
- language_selection = st.session_state.get("language_selection", "English (American)")
286
- if language_selection == "English (American)":
287
- print(f"[LOG] Using Deepgram for English (American)")
288
- if speaker in ["John", "Jane"]:
289
- processed_text = text
290
- else:
291
- processed_text = _preprocess_text_for_tts(text, speaker)
292
- deepgram_api_url = "https://api.deepgram.com/v1/speak"
293
- params = {"model": "aura-asteria-en"}
294
- if speaker == "John":
295
- params["model"] = "aura-zeus-en"
296
- headers = {
297
- "Accept": "audio/mpeg",
298
- "Content-Type": "application/json",
299
- "Authorization": f"Token {os.environ.get('DEEPGRAM_API_KEY')}"
300
- }
301
- body = {"text": processed_text}
302
- response = requests.post(deepgram_api_url, params=params, headers=headers, json=body, stream=True)
303
  if response.status_code != 200:
304
- raise ValueError(f"Deepgram TTS error: {response.status_code}, {response.text}")
305
- content_type = response.headers.get('Content-Type', '')
306
- if 'audio/mpeg' not in content_type:
307
- raise ValueError("Unexpected Content-Type from Deepgram.")
308
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as mp3_file:
309
- for chunk in response.iter_content(chunk_size=8192):
310
- if chunk:
311
- mp3_file.write(chunk)
312
- mp3_path = mp3_file.name
313
- audio_seg = AudioSegment.from_file(mp3_path, format="mp3")
314
- audio_seg = effects.normalize(audio_seg)
315
- final_mp3_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3").name
316
- audio_seg.export(final_mp3_path, format="mp3")
317
- if os.path.exists(mp3_path):
318
- os.remove(mp3_path)
319
- return final_mp3_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  else:
321
- print(f"[LOG] Using Murf API for language: {language_selection}")
322
- if language_selection == "Hinglish":
323
- from indic_transliteration.sanscript import transliterate, DEVANAGARI, IAST
324
- text = transliterate(text, DEVANAGARI, IAST)
325
- api_key = os.environ.get("MURF_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  headers = {
 
327
  "Content-Type": "application/json",
328
- "Accept": "application/json",
329
- "api-key": api_key
330
  }
331
- multi_native_locale = "hi-IN" if language_selection in ["Hinglish", "Hindi"] else "en-IN"
332
- if language_selection == "English (Indian)":
333
- voice_id = "en-IN-aarav" if speaker == "John" else "en-IN-isha"
334
- elif language_selection == "Hindi":
335
- voice_id = "hi-IN-kabir" if speaker == "John" else "hi-IN-shweta"
336
- elif language_selection == "Hinglish":
337
- voice_id = "hi-IN-kabir" if speaker == "John" else "hi-IN-shweta"
338
- else:
339
- voice_id = "en-IN-aarav" if speaker == "John" else "en-IN-isha"
340
- payload = {
341
- "audioDuration": 0,
342
- "channelType": "MONO",
343
- "encodeAsBase64": False,
344
- "format": "WAV",
345
- "modelVersion": "GEN2",
346
- "multiNativeLocale": multi_native_locale,
347
- "pitch": 0,
348
- "pronunciationDictionary": {},
349
- "rate": 0,
350
- "sampleRate": 48000,
351
- "style": "Conversational",
352
- "text": text,
353
- "variation": 1,
354
- "voiceId": voice_id
355
  }
356
- response = requests.post("https://api.murf.ai/v1/speech/generate", headers=headers, json=payload)
357
- if response.status_code != 200:
358
- raise ValueError(f"Murf API error: {response.status_code}, {response.text}")
359
- json_resp = response.json()
360
- audio_url = json_resp.get("audioFile")
361
- if not audio_url:
362
- raise ValueError("No audio file URL returned by Murf API")
363
- audio_response = requests.get(audio_url)
364
- if audio_response.status_code != 200:
365
- raise ValueError(f"Error fetching audio from {audio_url}")
366
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as wav_file:
367
- wav_file.write(audio_response.content)
368
- wav_path = wav_file.name
369
- audio_seg = AudioSegment.from_file(wav_path, format="wav")
370
- audio_seg = effects.normalize(audio_seg)
371
- final_mp3_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3").name
372
- audio_seg.export(final_mp3_path, format="mp3")
373
- os.remove(wav_path)
374
- return final_mp3_path
375
- except Exception as e:
376
- print("[ERROR] Error generating audio:", e)
377
- raise ValueError(f"Error generating audio: {str(e)}")
378
-
379
- def transcribe_youtube_video_OLD_YTDLP(video_url: str) -> str:
380
- pass
381
-
382
- def _preprocess_text_for_tts(text: str, speaker: str) -> str:
383
- text = re.sub(r"\bNo\.\b", "Number", text)
384
- text = re.sub(r"\b(?i)SaaS\b", "sass", text)
385
- abbreviations_as_words = {"NASA", "NATO", "UNESCO"}
386
- def insert_periods_for_abbrev(m):
387
- abbr = m.group(0)
388
- if abbr in abbreviations_as_words:
389
- return abbr
390
- return ".".join(list(abbr)) + "."
391
- text = re.sub(r"\b([A-Z]{2,})\b", insert_periods_for_abbrev, text)
392
- text = re.sub(r"\.\.", ".", text)
393
- def remove_periods_for_tts(m):
394
- return m.group().replace(".", " ").strip()
395
- text = re.sub(r"[A-Z]\.[A-Z](?:\.[A-Z])*\.", remove_periods_for_tts, text)
396
- text = re.sub(r"-", " ", text)
397
- text = re.sub(r"\b(ha(ha)?|heh|lol)\b", "(* laughs *)", text, flags=re.IGNORECASE)
398
- text = re.sub(r"\bsigh\b", "(* sighs *)", text, flags=re.IGNORECASE)
399
- text = re.sub(r"\b(groan|moan)\b", "(* groans *)", text, flags=re.IGNORECASE)
400
- if speaker != "Jane":
401
- def insert_thinking_pause(m):
402
- word = m.group(1)
403
- if random.random() < 0.3:
404
- filler = random.choice(['hmm,', 'well,', 'let me see,'])
405
- return f"{word}..., {filler}"
406
- else:
407
- return f"{word}...,"
408
- keywords_pattern = r"\b(important|significant|crucial|point|topic)\b"
409
- text = re.sub(keywords_pattern, insert_thinking_pause, text, flags=re.IGNORECASE)
410
- conj_pattern = r"\b(and|but|so|because|however)\b"
411
- text = re.sub(conj_pattern, lambda m: f"{m.group()}...", text, flags=re.IGNORECASE)
412
- text = re.sub(r"\b(uh|um|ah)\b", "", text, flags=re.IGNORECASE)
413
- def capitalize_match(m):
414
- return m.group().upper()
415
- text = re.sub(r'(^\s*\w)|([.!?]\s*\w)', capitalize_match, text)
416
- return text.strip()
417
-
418
- def _spell_digits(d: str) -> str:
419
- digit_map = {
420
- '0': 'zero', '1': 'one', '2': 'two', '3': 'three',
421
- '4': 'four', '5': 'five', '6': 'six', '7': 'seven',
422
- '8': 'eight', '9': 'nine'
423
- }
424
- return " ".join(digit_map[ch] for ch in d if ch in digit_map)
425
-
426
- def mix_with_bg_music(spoken: AudioSegment, custom_music_path=None) -> AudioSegment:
427
- if custom_music_path:
428
- music_path = custom_music_path
429
- else:
430
- music_path = "bg_music.mp3"
431
-
432
- try:
433
- bg_music = AudioSegment.from_file(music_path, format="mp3")
434
- except Exception as e:
435
- print("[ERROR] Failed to load background music:", e)
436
- return spoken
437
-
438
- bg_music = bg_music - 18.0
439
- total_length_ms = len(spoken) + 2000
440
- looped_music = AudioSegment.empty()
441
- while len(looped_music) < total_length_ms:
442
- looped_music += bg_music
443
- looped_music = looped_music[:total_length_ms]
444
- final_mix = looped_music.overlay(spoken, position=2000)
445
- return final_mix
446
-
447
- def call_groq_api_for_qa(system_prompt: str) -> str:
448
- #Kept for use, Changed model
449
- try:
450
- headers = {
451
- "Authorization": f"Bearer {os.environ.get('GROQ_API_KEY')}", # Use GROQ API KEY
452
- "Content-Type": "application/json",
453
- "Accept": "application/json"
454
- }
455
- data = {
456
- "model": "deepseek-r1-distill-llama-70b", #Using Deepseek
457
- "messages": [{"role": "user", "content": system_prompt}],
458
- "max_tokens": 512,
459
- "temperature": 0.7
460
- }
461
- response = requests.post("https://api.groq.com/openai/v1/chat/completions", #Using groq endpoint
462
- headers=headers, data=json.dumps(data))
463
- response.raise_for_status()
464
- return response.json()["choices"][0]["message"]["content"].strip()
465
- except Exception as e:
466
- print("[ERROR] Groq API error:", e)
467
- fallback = {"speaker": "John", "text": "I'm sorry, I'm having trouble answering right now."}
468
- return json.dumps(fallback)
469
-
470
- # --- Agent and Tavily Integration ---
471
-
472
- def run_research_agent(topic: str, report_type: str = "research_report", max_results: int = 20) -> str:
473
- """
474
- Runs the Open Deep Research agent to generate a research report.
475
-
476
- Args:
477
- topic: The research topic.
478
- report_type: The type of report to generate (currently only supports "research_report").
479
- max_results: The maximum number of search results to use.
480
-
481
- Returns:
482
- A string containing the generated research report. Or, in case of error,
483
- an error message.
484
- """
485
- print(f"[LOG] Starting research agent for topic: {topic}")
486
- try:
487
- agent = OpenDeepResearcher(topic, report_type=report_type, max_results=max_results, tavily_api_key=os.environ.get("TAVILY_API_KEY"))
488
- report_content = agent.run()
489
- print("[LOG] Research agent completed successfully.")
490
-
491
- # Now, use the report_structure module to generate the structured report.
492
- structured_report = generate_report(report_content)
493
- return structured_report
494
- except Exception as e:
495
- print(f"[ERROR] Error in research agent: {e}")
496
- return f"Sorry, I encountered an error during research: {e}"
 
15
  import torch
16
  import random
17
 
18
+ # --- IMPORTANT: USE THIS IMPORT NOW ---
 
 
19
  from smolagents.examples.open_deep_research.agent import OpenDeepResearcher
20
  from report_structure import generate_report
21
 
 
82
  # No longer needed
83
  pass
84
  def research_topic(topic: str) -> str:
85
+ # No longer needed
86
+ pass
87
 
88
  def fetch_wikipedia_summary(topic: str) -> str:
89
  # No longer needed
 
190
  "temperature": 0.7
191
  }
192
  response = requests.post("https://openrouter.ai/api/v1/chat/completions",
193
+ headers=headers, data=json.dumps(data))
194
  response.raise_for_status()
195
  raw_content = response.json()["choices"][0]["message"]["content"].strip()
196
  except Exception as e:
 
220
  d["speaker"] = "Jane"
221
  d["display_speaker"] = raw_speaker
222
 
223
+ new_dialogue_items = []
224
+ for d in dialogue_list:
225
+ if "display_speaker" not in d:
226
+ d["display_speaker"] = d["speaker"]
227
+ new_dialogue_items.append(DialogueItem(**d))
228
 
229
+ return Dialogue(dialogue=new_dialogue_items)
230
  except json.JSONDecodeError as e:
231
  print("[ERROR] JSON decoding (format) failed:", e)
232
  raise ValueError(f"Failed to parse dialogue: {str(e)}")
 
234
  print("[ERROR] JSON decoding failed:", e)
235
  raise ValueError(f"Failed to parse dialogue: {str(e)}")
236
 
237
+ def transcribe_youtube_video(video_url: str) -> str:
238
+ print("[LOG] Transcribing YouTube video via RapidAPI:", video_url)
239
+ video_id_match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11})", video_url)
240
+ if not video_id_match:
241
+ raise ValueError(f"Invalid YouTube URL: {video_url}, cannot extract video ID.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
+ video_id = video_id_match.group(1)
244
+ print("[LOG] Extracted video ID:", video_id)
 
245
 
246
+ base_url = "https://youtube-transcriptor.p.rapidapi.com/transcript"
247
+ params = {"video_id": video_id, "lang": "en"}
248
+ headers = {
249
+ "x-rapidapi-host": "youtube-transcriptor.p.rapidapi.com",
250
+ "x-rapidapi-key": os.environ.get("RAPIDAPI_KEY")
251
+ }
252
 
253
+ try:
254
+ response = requests.get(base_url, headers=headers, params=params, timeout=30)
255
+ print("[LOG] RapidAPI Response Status Code:", response.status_code)
256
+ print("[LOG] RapidAPI Response Body:", response.text)
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  if response.status_code != 200:
259
+ raise ValueError(f"RapidAPI transcription error: {response.status_code}, {response.text}")
260
+
261
+ data = response.json()
262
+ if not isinstance(data, list) or not data:
263
+ raise ValueError(f"Unexpected transcript format or empty transcript: {data}")
264
+
265
+ transcript_as_text = data[0].get('transcriptionAsText', '').strip()
266
+ if not transcript_as_text:
267
+ raise ValueError("transcriptionAsText field is missing or empty.")
268
+
269
+ print("[LOG] Transcript retrieval successful.")
270
+ print(f"[DEBUG] Transcript Length: {len(transcript_as_text)} characters.")
271
+ snippet = transcript_as_text[:200] + "..." if len(transcript_as_text) > 200 else transcript_as_text
272
+ print(f"[DEBUG] Transcript Snippet: {snippet}")
273
+
274
+ return transcript_as_text
275
+ except Exception as e:
276
+ print("[ERROR] RapidAPI transcription error:", e)
277
+ raise ValueError(f"Error transcribing YouTube video via RapidAPI: {str(e)}")
278
+
279
+ def generate_audio_mp3(text: str, speaker: str) -> str:
280
+ try:
281
+ import streamlit as st
282
+ print(f"[LOG] Generating audio for speaker: {speaker}")
283
+ language_selection = st.session_state.get("language_selection", "English (American)")
284
+ if language_selection == "English (American)":
285
+ print(f"[LOG] Using Deepgram for English (American)")
286
+ if speaker in ["John", "Jane"]:
287
+ processed_text = text
288
+ else:
289
+ processed_text = _preprocess_text_for_tts(text, speaker)
290
+ deepgram_api_url = "https://api.deepgram.com/v1/speak"
291
+ params = {"model": "aura-asteria-en"}
292
+ if speaker == "John":
293
+ params["model"] = "aura-zeus-en"
294
+ headers = {
295
+ "Accept": "audio/mpeg",
296
+ "Content-Type": "application/json",
297
+ "Authorization": f"Token {os.environ.get('DEEPGRAM_API_KEY')}"
298
+ }
299
+ body = {"text": processed_text}
300
+ response = requests.post(deepgram_api_url, params=params, headers=headers, json=body, stream=True)
301
+ if response.status_code != 200:
302
+ raise ValueError(f"Deepgram TTS error: {response.status_code}, {response.text}")
303
+ content_type = response.headers.get('Content-Type', '')
304
+ if 'audio/mpeg' not in content_type:
305
+ raise ValueError("Unexpected Content-Type from Deepgram.")
306
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as mp3_file:
307
+ for chunk in response.iter_content(chunk_size=8192):
308
+ if chunk:
309
+ mp3_file.write(chunk)
310
+ mp3_path = mp3_file.name
311
+ audio_seg = AudioSegment.from_file(mp3_path, format="mp3")
312
+ audio_seg = effects.normalize(audio_seg)
313
+ final_mp3_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3").name
314
+ audio_seg.export(final_mp3_path, format="mp3")
315
+ if os.path.exists(mp3_path):
316
+ os.remove(mp3_path)
317
+ return final_mp3_path
318
+ else:
319
+ print(f"[LOG] Using Murf API for language: {language_selection}")
320
+ if language_selection == "Hinglish":
321
+ from indic_transliteration.sanscript import transliterate, DEVANAGARI, IAST
322
+ text = transliterate(text, DEVANAGARI, IAST)
323
+ api_key = os.environ.get("MURF_API_KEY")
324
+ headers = {
325
+ "Content-Type": "application/json",
326
+ "Accept": "application/json",
327
+ "api-key": api_key
328
+ }
329
+ multi_native_locale = "hi-IN" if language_selection in ["Hinglish", "Hindi"] else "en-IN"
330
+ if language_selection == "English (Indian)":
331
+ voice_id = "en-IN-aarav" if speaker == "John" else "en-IN-isha"
332
+ elif language_selection == "Hindi":
333
+ voice_id = "hi-IN-kabir" if speaker == "John" else "hi-IN-shweta"
334
+ elif language_selection == "Hinglish":
335
+ voice_id = "hi-IN-kabir" if speaker == "John" else "hi-IN-shweta"
336
+ else:
337
+ voice_id = "en-IN-aarav" if speaker == "John" else "en-IN-isha"
338
+ payload = {
339
+ "audioDuration": 0,
340
+ "channelType": "MONO",
341
+ "encodeAsBase64": False,
342
+ "format": "WAV",
343
+ "modelVersion": "GEN2",
344
+ "multiNativeLocale": multi_native_locale,
345
+ "pitch": 0,
346
+ "pronunciationDictionary": {},
347
+ "rate": 0,
348
+ "sampleRate": 48000,
349
+ "style": "Conversational",
350
+ "text": text,
351
+ "variation": 1,
352
+ "voiceId": voice_id
353
+ }
354
+ response = requests.post("https://api.murf.ai/v1/speech/generate", headers=headers, json=payload)
355
+ if response.status_code != 200:
356
+ raise ValueError(f"Murf API error: {response.status_code}, {response.text}")
357
+ json_resp = response.json()
358
+ audio_url = json_resp.get("audioFile")
359
+ if not audio_url:
360
+ raise ValueError("No audio file URL returned by Murf API")
361
+ audio_response = requests.get(audio_url)
362
+ if audio_response.status_code != 200:
363
+ raise ValueError(f"Error fetching audio from {audio_url}")
364
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as wav_file:
365
+ wav_file.write(audio_response.content)
366
+ wav_path = wav_file.name
367
+ audio_seg = AudioSegment.from_file(wav_path, format="wav")
368
+ audio_seg = effects.normalize(audio_seg)
369
+ final_mp3_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3").name
370
+ audio_seg.export(final_mp3_path, format="mp3")
371
+ os.remove(wav_path)
372
+ return final_mp3_path
373
+ except Exception as e:
374
+ print("[ERROR] Error generating audio:", e)
375
+ raise ValueError(f"Error generating audio: {str(e)}")
376
+
377
+ def transcribe_youtube_video_OLD_YTDLP(video_url: str) -> str:
378
+ pass
379
+
380
+ def _preprocess_text_for_tts(text: str, speaker: str) -> str:
381
+ text = re.sub(r"\bNo\.\b", "Number", text)
382
+ text = re.sub(r"\b(?i)SaaS\b", "sass", text)
383
+ abbreviations_as_words = {"NASA", "NATO", "UNESCO"}
384
+ def insert_periods_for_abbrev(m):
385
+ abbr = m.group(0)
386
+ if abbr in abbreviations_as_words:
387
+ return abbr
388
+ return ".".join(list(abbr)) + "."
389
+ text = re.sub(r"\b([A-Z]{2,})\b", insert_periods_for_abbrev, text)
390
+ text = re.sub(r"\.\.", ".", text)
391
+ def remove_periods_for_tts(m):
392
+ return m.group().replace(".", " ").strip()
393
+ text = re.sub(r"[A-Z]\.[A-Z](?:\.[A-Z])*\.", remove_periods_for_tts, text)
394
+ text = re.sub(r"-", " ", text)
395
+ text = re.sub(r"\b(ha(ha)?|heh|lol)\b", "(* laughs *)", text, flags=re.IGNORECASE)
396
+ text = re.sub(r"\bsigh\b", "(* sighs *)", text, flags=re.IGNORECASE)
397
+ text = re.sub(r"\b(groan|moan)\b", "(* groans *)", text, flags=re.IGNORECASE)
398
+ if speaker != "Jane":
399
+ def insert_thinking_pause(m):
400
+ word = m.group(1)
401
+ if random.random() < 0.3:
402
+ filler = random.choice(['hmm,', 'well,', 'let me see,'])
403
+ return f"{word}..., {filler}"
404
+ else:
405
+ return f"{word}...,"
406
+ keywords_pattern = r"\b(important|significant|crucial|point|topic)\b"
407
+ text = re.sub(keywords_pattern, insert_thinking_pause, text, flags=re.IGNORECASE)
408
+ conj_pattern = r"\b(and|but|so|because|however)\b"
409
+ text = re.sub(conj_pattern, lambda m: f"{m.group()}...", text, flags=re.IGNORECASE)
410
+ text = re.sub(r"\b(uh|um|ah)\b", "", text, flags=re.IGNORECASE)
411
+ def capitalize_match(m):
412
+ return m.group().upper()
413
+ text = re.sub(r'(^\s*\w)|([.!?]\s*\w)', capitalize_match, text)
414
+ return text.strip()
415
+
416
+ def _spell_digits(d: str) -> str:
417
+ digit_map = {
418
+ '0': 'zero', '1': 'one', '2': 'two', '3': 'three',
419
+ '4': 'four', '5': 'five', '6': 'six', '7': 'seven',
420
+ '8': 'eight', '9': 'nine'
421
+ }
422
+ return " ".join(digit_map[ch] for ch in d if ch in digit_map)
423
+
424
+ def mix_with_bg_music(spoken: AudioSegment, custom_music_path=None) -> AudioSegment:
425
+ if custom_music_path:
426
+ music_path = custom_music_path
427
  else:
428
+ music_path = "bg_music.mp3"
429
+
430
+ try:
431
+ bg_music = AudioSegment.from_file(music_path, format="mp3")
432
+ except Exception as e:
433
+ print("[ERROR] Failed to load background music:", e)
434
+ return spoken
435
+
436
+ bg_music = bg_music - 18.0
437
+ total_length_ms = len(spoken) + 2000
438
+ looped_music = AudioSegment.empty()
439
+ while len(looped_music) < total_length_ms:
440
+ looped_music += bg_music
441
+ looped_music = looped_music[:total_length_ms]
442
+ final_mix = looped_music.overlay(spoken, position=2000)
443
+ return final_mix
444
+
445
+ def call_groq_api_for_qa(system_prompt: str) -> str:
446
+ #Kept for use, Changed model
447
+ try:
448
  headers = {
449
+ "Authorization": f"Bearer {os.environ.get('GROQ_API_KEY')}", # Use GROQ API KEY
450
  "Content-Type": "application/json",
451
+ "Accept": "application/json"
 
452
  }
453
+ data = {
454
+ "model": "deepseek-r1-distill-llama-70b", #Using Deepseek
455
+ "messages": [{"role": "user", "content": system_prompt}],
456
+ "max_tokens": 512,
457
+ "temperature": 0.7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  }
459
+ response = requests.post("https://api.groq.com/openai/v1/chat/completions", #Using groq endpoint
460
+ headers=headers, data=json.dumps(data))
461
+ response.raise_for_status()
462
+ return response.json()["choices"][0]["message"]["content"].strip()
463
+ except Exception as e:
464
+ print("[ERROR] Groq API error:", e)
465
+ fallback = {"speaker": "John", "text": "I'm sorry, I'm having trouble answering right now."}
466
+ return json.dumps(fallback)
467
+
468
+ # --- Agent and Tavily Integration ---
469
+
470
+ def run_research_agent(topic: str, report_type: str = "research_report", max_results: int = 20) -> str:
471
+ """
472
+ Runs the Open Deep Research agent to generate a research report.
473
+
474
+ Args:
475
+ topic: The research topic.
476
+ report_type: The type of report to generate (currently only supports "research_report").
477
+ max_results: The maximum number of search results to use.
478
+
479
+ Returns:
480
+ A string containing the generated research report. Or, in case of error,
481
+ an error message.
482
+ """
483
+ print(f"[LOG] Starting research agent for topic: {topic}")
484
+ try:
485
+ agent = OpenDeepResearcher(topic, report_type=report_type, max_results=max_results, tavily_api_key=os.environ.get("TAVILY_API_KEY"))
486
+ report_content = agent.run()
487
+ print("[LOG] Research agent completed successfully.")
488
+
489
+ # Now, use the report_structure module to generate the structured report.
490
+ structured_report = generate_report(report_content)
491
+ return structured_report
492
+ except Exception as e:
493
+ print(f"[ERROR] Error in research agent: {e}")
494
+ return f"Sorry, I encountered an error during research: {e}"