from groq import Groq from pydantic import BaseModel, ValidationError from typing import List, Literal import os import tiktoken import json import re import tempfile from gtts import gTTS from bs4 import BeautifulSoup import requests groq_client = Groq(api_key=os.environ["GROQ_API_KEY"]) tokenizer = tiktoken.get_encoding("cl100k_base") class DialogueItem(BaseModel): speaker: Literal["Sarah", "Maria"] text: str class Dialogue(BaseModel): dialogue: List[DialogueItem] def truncate_text(text, max_tokens=2048): tokens = tokenizer.encode(text) if len(tokens) > max_tokens: return tokenizer.decode(tokens[:max_tokens]) return text def extract_text_from_url(url): try: response = requests.get(url) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') for script in soup(["script", "style"]): script.decompose() text = soup.get_text() lines = (line.strip() for line in text.splitlines()) chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) text = '\n'.join(chunk for chunk in chunks if chunk) return text except Exception as e: raise ValueError(f"Error extracting text from URL: {str(e)}") def generate_script(system_prompt: str, input_text: str, tone: str, target_length: str): input_text = truncate_text(input_text) word_limit = 300 if target_length == "Short (1-2 min)" else 750 prompt = f""" {system_prompt} TONE: {tone} TARGET LENGTH: {target_length} (approximately {word_limit} words) INPUT TEXT: {input_text} Generate a complete, well-structured podcast script that: 1. Starts with a proper introduction 2. Covers the main points from the input text 3. Has a natural flow of conversation between Sarah (American accent) and Maria (British accent) 4. Concludes with a summary and sign-off 5. Fits within the {word_limit} word limit for the target length of {target_length} 6. Strongly emphasizes the {tone} tone throughout the conversation For a humorous tone, include jokes, puns, and playful banter. For a casual tone, use colloquial language and make it sound like a conversation between college students. For a formal tone, maintain a professional podcast style with well-structured arguments and formal language. Ensure the script is not abruptly cut off and forms a complete conversation. """ response = groq_client.chat.completions.create( messages=[ {"role": "system", "content": prompt}, ], model="llama-3.1-70b-versatile", max_tokens=2048, temperature=0.7 ) content = response.choices[0].message.content content = re.sub(r'```json\s*|\s*```', '', content) try: json_data = json.loads(content) dialogue = Dialogue.model_validate(json_data) except json.JSONDecodeError as json_error: match = re.search(r'\{.*\}', content, re.DOTALL) if match: try: json_data = json.loads(match.group()) dialogue = Dialogue.model_validate(json_data) except (json.JSONDecodeError, ValidationError) as e: raise ValueError(f"Failed to parse dialogue JSON: {e}\nContent: {content}") else: raise ValueError(f"Failed to find valid JSON in the response: {content}") except ValidationError as e: raise ValueError(f"Failed to validate dialogue structure: {e}\nContent: {content}") return dialogue def generate_audio(text: str, speaker: str) -> str: tld = 'com' if speaker == "Sarah" else 'co.uk' tts = gTTS(text=text, lang='en', tld=tld) with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio: tts.save(temp_audio.name) return temp_audio.name