Bonosa2 commited on
Commit
b813104
Β·
verified Β·
1 Parent(s): f7081da

Upload 4 files

Browse files
Files changed (4) hide show
  1. generate_audio.py +50 -0
  2. logger_setup.py +16 -0
  3. requirements.txt +11 -0
  4. utils.py +59 -0
generate_audio.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from elevenlabs.client import ElevenLabs
4
+ from logger_setup import logger
5
+
6
+ # Load environment variables
7
+ load_dotenv()
8
+
9
+ # Use absolute path for output
10
+ AUDIO_DIR = os.path.join(os.path.dirname(__file__), "audio_outputs")
11
+
12
+ # Verify API key
13
+ api_key = os.getenv("ELEVENLABS_API_KEY")
14
+ if not api_key:
15
+ logger.error("❌ ELEVENLABS_API_KEY is missing or not loaded from .env")
16
+ raise RuntimeError("ELEVENLABS_API_KEY missing")
17
+
18
+ client = ElevenLabs(api_key=api_key)
19
+
20
+ def generate_audio(text: str, voice_id: str, audio_key: str):
21
+ try:
22
+ logger.info("🎯 Starting ElevenLabs audio generation")
23
+ os.makedirs(AUDIO_DIR, exist_ok=True)
24
+
25
+ try:
26
+ audio_stream = client.text_to_speech.convert_as_stream(
27
+ text=text,
28
+ voice_id=voice_id,
29
+ model_id="eleven_multilingual_v2"
30
+ )
31
+ logger.info("βœ… Audio stream received from ElevenLabs")
32
+ except Exception as stream_err:
33
+ logger.error(f"❌ Failed to get audio stream: {stream_err}")
34
+ raise
35
+
36
+ output_path = os.path.join(AUDIO_DIR, f"{audio_key}.mp3")
37
+
38
+ try:
39
+ with open(output_path, "wb") as f:
40
+ for chunk in audio_stream:
41
+ if isinstance(chunk, bytes):
42
+ f.write(chunk)
43
+ logger.info(f"βœ… Audio saved to {output_path}")
44
+ except Exception as write_err:
45
+ logger.error(f"❌ Failed to save audio to file: {write_err}")
46
+ raise
47
+
48
+ except Exception as e:
49
+ logger.exception("πŸ”₯ Exception in generate_audio")
50
+ raise
logger_setup.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # logger_setup.py
2
+ import logging
3
+ import os
4
+
5
+ LOG_FILE = os.path.join(os.path.dirname(__file__), "logfile.log")
6
+
7
+ logging.basicConfig(
8
+ level=logging.INFO,
9
+ format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
10
+ handlers=[
11
+ logging.FileHandler(LOG_FILE, mode='a', encoding='utf-8'),
12
+ logging.StreamHandler()
13
+ ]
14
+ )
15
+
16
+ logger = logging.getLogger("voice-agent")
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ requests
3
+ openai
4
+ python-dotenv
5
+ PyMuPDF
6
+ python-docx
7
+ elevenlabs
8
+
9
+ qdrant-client
10
+ fastembed
11
+ firecrawl
utils.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from urllib.parse import urlparse
4
+ from bs4 import BeautifulSoup
5
+
6
+ AUDIO_DIR = "audio_outputs"
7
+
8
+ voice_map = {'grandma GG': 'rKVm0Cb9J2wrzmZupJea', 'tech wizard': 'ocn9CucaUfmmP6Two6Ik', 'perky sidekick': 'DWR3ijzKmphlRUhbBI7t', 'bill the newscaster': 'R1vZMopVRO75M5xBKX52', 'spunky charlie': 'q3yXDjF0aq4JCEo9u2g4', 'sassy teen': 'mBj2IDD9aXruPJHLGCAv'}
9
+
10
+ def sanitize_url(url):
11
+ if not url.startswith(("http://", "https://")):
12
+ return "https://" + url
13
+ return url
14
+
15
+ def extract_internal_links(html_content, base_url):
16
+ soup = BeautifulSoup(html_content, "html.parser")
17
+ parsed_base = urlparse(base_url)
18
+ base_domain = parsed_base.netloc
19
+
20
+ links = set()
21
+ for tag in soup.find_all("a", href=True):
22
+ href = tag["href"]
23
+ parsed_href = urlparse(href)
24
+
25
+ if parsed_href.netloc == "" or parsed_href.netloc == base_domain:
26
+ full_url = parsed_href.geturl()
27
+ if not full_url.startswith("http"):
28
+ full_url = f"{parsed_base.scheme}://{base_domain}{href}"
29
+ links.add(full_url)
30
+
31
+ return list(links)
32
+
33
+ def crawl_documentation(url):
34
+ import requests
35
+ try:
36
+ response = requests.get(url, timeout=10)
37
+ response.raise_for_status()
38
+ return response.text
39
+ except Exception as e:
40
+ return f"Error fetching page: {e}"
41
+
42
+ def get_voice_prompt_style(voice):
43
+ tone = {'grandma GG': 'dry, witty, and brutally honest β€” will roast you if you mess up.', 'tech wizard': 'cryptic, snarky, and a prodigy with code β€” speaks in digital spells.', 'perky sidekick': 'energetic, cheerful, and endlessly supportive β€” like a high-five machine.', 'bill the newscaster': 'polished, confident, and composed β€” delivers everything like breaking news.', 'spunky charlie': 'wildly curious, playful, and full of devil-may-care energy.', 'sassy teen': 'sarcastic, sharp-tongued, and too cool to care β€” flexes brainpower with attitude.'}
44
+ return tone.get(voice.lower(), "neutral")
45
+
46
+ def save_audio_file(audio_path, content):
47
+ os.makedirs(AUDIO_DIR, exist_ok=True)
48
+ with open(audio_path, "wb") as f:
49
+ f.write(content)
50
+
51
+ __all__ = [
52
+ "sanitize_url",
53
+ "extract_internal_links",
54
+ "crawl_documentation",
55
+ "get_voice_prompt_style",
56
+ "save_audio_file",
57
+ "voice_map",
58
+ "AUDIO_DIR",
59
+ ]