Auto-update: Tue Jul 9 16:14:52 PDT 2024

This commit is contained in:
sanj 2024-07-09 16:14:52 -07:00
parent 77a9e35f2b
commit 4973707596
7 changed files with 105 additions and 91 deletions

View file

@ -6,11 +6,11 @@ from dotenv import load_dotenv
from dateutil import tz
from pathlib import Path
from .logs import Logger
from .classes import Database, Geocoder, APIConfig, Configuration, Dir
from .classes import Database, Geocoder, APIConfig, Configuration, EmailConfiguration, Dir
### Initial initialization
API = APIConfig.load('api', 'secrets')
Dir = Dir.load('dirs')
Dir = Dir()
ENV_PATH = Dir.CONFIG / ".env"
LOGS_DIR = Dir.LOGS
L = Logger("Central", LOGS_DIR)
@ -23,9 +23,11 @@ DB = Database.from_yaml('db.yaml')
ASR = Configuration.load('asr')
IMG = Configuration.load('img')
Cal = Configuration.load('cal', 'secrets')
Email = Configuration.load('email', 'secrets')
print(f"Cal configuration: {Cal.__dict__}")
Email = EmailConfiguration.load('email', 'secrets')
LLM = Configuration.load('llm', 'secrets')
News = Configuration.load('news', 'secrets')
Obsidian = Configuration.load('obsidian')
TTS = Configuration.load('tts', 'secrets')
CourtListener = Configuration.load('courtlistener', 'secrets')
Tailscale = Configuration.load('tailscale', 'secrets')

View file

@ -207,7 +207,6 @@ class Configuration(BaseModel):
try:
with yaml_path.open('r') as file:
config_data = yaml.safe_load(file)
print(f"Loaded configuration data from {yaml_path}")
if secrets_path:
@ -220,7 +219,6 @@ class Configuration(BaseModel):
instance._dir_config = dir_config or instance
resolved_data = instance.resolve_placeholders(config_data)
return cls._create_nested_config(resolved_data)
except Exception as e:
print(f"Error loading configuration: {str(e)}")
@ -229,6 +227,8 @@ class Configuration(BaseModel):
@classmethod
def _create_nested_config(cls, data):
if isinstance(data, dict):
print(f"Creating nested config for: {cls.__name__}")
print(f"Data: {data}")
return cls(**{k: cls._create_nested_config(v) for k, v in data.items()})
elif isinstance(data, list):
return [cls._create_nested_config(item) for item in data]
@ -267,15 +267,7 @@ class Configuration(BaseModel):
for match in matches:
parts = match.split('.')
if len(parts) == 1: # Internal reference
replacement = getattr(self._dir_config, parts[0], str(Path.home() / parts[0].lower()))
elif len(parts) == 2 and parts[0] == 'Dir':
replacement = getattr(self._dir_config, parts[1], str(Path.home() / parts[1].lower()))
elif len(parts) == 2 and parts[0] == 'ENV':
replacement = os.getenv(parts[1], '')
else:
replacement = value
replacement = self._resolve_nested_placeholder(parts)
value = value.replace('{{' + match + '}}', str(replacement))
# Convert to Path if it looks like a file path
@ -283,6 +275,17 @@ class Configuration(BaseModel):
return Path(value).expanduser()
return value
def _resolve_nested_placeholder(self, parts: List[str]) -> Any:
current = self._dir_config
for part in parts:
if part == 'ENV':
return os.getenv(parts[-1], '')
elif hasattr(current, part):
current = getattr(current, part)
else:
return str(Path.home() / part.lower())
return current
class APIConfig(BaseModel):
HOST: str
@ -788,6 +791,31 @@ class EmailConfiguration(Configuration):
autoresponders: List[AutoResponder]
accounts: List[EmailAccount]
@classmethod
def _create_nested_config(cls, data):
if isinstance(data, dict):
if 'imaps' in data:
return cls(
imaps=[IMAPConfig(**imap) for imap in data['imaps']],
smtps=[SMTPConfig(**smtp) for smtp in data['smtps']],
autoresponders=[AutoResponder(**ar) for ar in data['autoresponders']],
accounts=[EmailAccount(**account) for account in data['accounts']],
**{k: v for k, v in data.items() if k not in ['imaps', 'smtps', 'autoresponders', 'accounts']}
)
else:
return data # Return the dict as-is for nested structures
elif isinstance(data, list):
return [cls._create_nested_config(item) for item in data]
else:
return data
@classmethod
def load(cls, yaml_path: Union[str, Path], secrets_path: Optional[Union[str, Path]] = None, dir_config: Optional['Configuration'] = None) -> 'EmailConfiguration':
config_data = super().load(yaml_path, secrets_path, dir_config)
return cls._create_nested_config(config_data)
# ... (rest of the methods remain the same)
def get_imap(self, username: str) -> Optional[IMAPConfig]:
return next((imap for imap in self.imaps if imap.username == username), None)
@ -800,6 +828,9 @@ class EmailConfiguration(Configuration):
def get_account(self, name: str) -> Optional[EmailAccount]:
return next((account for account in self.accounts if account.name == name), None)
def get_email_accounts(self) -> List[EmailAccount]:
return self.accounts
class EmailContact(BaseModel):
email: str
name: Optional[str] = None

View file

@ -1,16 +0,0 @@
HOME: ~
BASE: '{{ HOME }}/workshop/sijapi'
SIJAPI: '{{ BASE }}/sijapi'
CONFIG: '{{ SIJAPI }}/config'
CONFIG.email: '{{ CONFIG }}/email.yaml'
CONFIG.img: '{{ CONFIG }}/img.yaml'
CONFIG.news: '{{ CONFIG }}/news.yaml'
SECRETS: '{{ CONFIG }}/secrets.yaml'
DATA: '{{ SIJAPI }}/data'
DATA.ALERTS: '{{ DATA }}/alerts'
DATA.ASR: '{{ DATA }}/asr'
DATA.BASE: '{{ DATA }}/db'
DATA.IMG: '{{ DATA }}/img'
DATA.TTS: '{{ DATA }}/tts'
TTS.VOICES: '{{ TTS }}/voices'
LOGS: '{{ SIJAPI }}/logs'

View file

@ -23,7 +23,10 @@ cal = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
timeout = httpx.Timeout(12)
print(f"Configuration MS365: {Cal.MS365}")
print(f"Cal object: {Cal}")
print(f"Cal.__dict__: {Cal.__dict__}")
print(f"Cal.MS365: {Cal.MS365}")
if Cal.MS365.toggle == 'on':
L.CRIT(f"Visit https://api.sij.ai/MS365/login to obtain your Microsoft 365 authentication token.")

View file

@ -363,7 +363,7 @@ async def save_processed_uid(filename: Path, account_name: str, uid: str):
async def process_all_accounts():
email_accounts = load_email_accounts(EMAIL_CONFIG)
email_accounts = Email.get_email_accounts()
summarization_tasks = [asyncio.create_task(process_account_archival(account)) for account in email_accounts]
autoresponding_tasks = [asyncio.create_task(process_account_autoresponding(account)) for account in email_accounts]
await asyncio.gather(*summarization_tasks, *autoresponding_tasks)
@ -371,4 +371,4 @@ async def process_all_accounts():
@email.on_event("startup")
async def startup_event():
await asyncio.sleep(5)
asyncio.create_task(process_all_accounts())
asyncio.create_task(process_all_accounts())

View file

@ -26,7 +26,7 @@ import tempfile
import shutil
import html2text
import markdown
from sijapi import L, Dir, API, LLM, TTS
from sijapi import L, Dir, API, LLM, TTS, Obsidian
from sijapi.utilities import convert_to_unix_time, sanitize_filename, ocr_pdf, clean_text, should_use_ocr, extract_text_from_pdf, extract_text_from_docx, read_text_file, str_to_bool, get_extension
from sijapi.routers import tts
from sijapi.routers.asr import transcribe_audio
@ -49,7 +49,7 @@ def read_markdown_files(folder: Path):
return documents, file_paths
# Read markdown files and generate embeddings
documents, file_paths = read_markdown_files(DOC_DIR)
documents, file_paths = read_markdown_files(Obsidian.docs)
for i, doc in enumerate(documents):
response = ollama.embeddings(model="mxbai-embed-large", prompt=doc)
embedding = response["embedding"]
@ -83,7 +83,7 @@ async def generate_response(prompt: str):
return {"response": output['response']}
async def query_ollama(usr: str, sys: str = LLM_SYS_MSG, model: str = DEFAULT_LLM, max_tokens: int = 200):
async def query_ollama(usr: str, sys: str = LLM.chat.sys, model: str = LLM.chat.model, max_tokens: int = LLM.chat.max_tokens):
messages = [{"role": "system", "content": sys},
{"role": "user", "content": usr}]
LLM = Ollama()
@ -100,8 +100,8 @@ async def query_ollama(usr: str, sys: str = LLM_SYS_MSG, model: str = DEFAULT_LL
async def query_ollama_multishot(
message_list: List[str],
sys: str = LLM_SYS_MSG,
model: str = DEFAULT_LLM,
sys: str = LLM.chat.sys,
model: str = LLM.chat.model,
max_tokens: int = 200
):
if len(message_list) % 2 == 0:
@ -130,7 +130,7 @@ async def chat_completions(request: Request):
body = await request.json()
timestamp = dt_datetime.now().strftime("%Y%m%d_%H%M%S%f")
filename = REQUESTS_DIR / f"request_{timestamp}.json"
filename = Dir.logs.requests / f"request_{timestamp}.json"
async with aiofiles.open(filename, mode='w') as file:
await file.write(json.dumps(body, indent=4))
@ -227,9 +227,9 @@ async def stream_messages_with_vision(message: dict, model: str, num_predict: in
def get_appropriate_model(requested_model):
if requested_model == "gpt-4-vision-preview":
return DEFAULT_VISION
return LLM.vision.model
elif not is_model_available(requested_model):
return DEFAULT_LLM
return LLM.chat.model
else:
return requested_model
@ -310,7 +310,7 @@ async def chat_completions_options(request: Request):
],
"created": int(time.time()),
"id": str(uuid.uuid4()),
"model": DEFAULT_LLM,
"model": LLM.chat.model,
"object": "chat.completion.chunk",
},
status_code=200,
@ -431,7 +431,7 @@ def llava(image_base64, prompt):
return "" if "pass" in response["response"].lower() else response["response"]
def gpt4v(image_base64, prompt_sys: str, prompt_usr: str, max_tokens: int = 150):
VISION_LLM = OpenAI(api_key=OPENAI_API_KEY)
VISION_LLM = OpenAI(api_key=LLM.OPENAI_API_KEY)
response_1 = VISION_LLM.chat.completions.create(
model="gpt-4-vision-preview",
messages=[
@ -512,12 +512,12 @@ def gpt4v(image_base64, prompt_sys: str, prompt_usr: str, max_tokens: int = 150)
@llm.get("/summarize")
async def summarize_get(text: str = Form(None), instruction: str = Form(SUMMARY_INSTRUCT)):
async def summarize_get(text: str = Form(None), instruction: str = Form(LLM.summary.instruct)):
summarized_text = await summarize_text(text, instruction)
return summarized_text
@llm.post("/summarize")
async def summarize_post(file: Optional[UploadFile] = File(None), text: Optional[str] = Form(None), instruction: str = Form(SUMMARY_INSTRUCT)):
async def summarize_post(file: Optional[UploadFile] = File(None), text: Optional[str] = Form(None), instruction: str = Form(LLM.summary.instruct)):
text_content = text if text else await extract_text(file)
summarized_text = await summarize_text(text_content, instruction)
return summarized_text
@ -526,10 +526,10 @@ async def summarize_post(file: Optional[UploadFile] = File(None), text: Optional
@llm.post("/speaksummary")
async def summarize_tts_endpoint(
bg_tasks: BackgroundTasks,
instruction: str = Form(SUMMARY_INSTRUCT),
instruction: str = Form(LLM.summary.instruct),
file: Optional[UploadFile] = File(None),
text: Optional[str] = Form(None),
voice: Optional[str] = Form(DEFAULT_VOICE),
voice: Optional[str] = Form(TTS.xtts.voice),
speed: Optional[float] = Form(1.2),
podcast: Union[bool, str] = Form(False)
):
@ -572,8 +572,8 @@ async def summarize_tts_endpoint(
async def summarize_tts(
text: str,
instruction: str = SUMMARY_INSTRUCT,
voice: Optional[str] = DEFAULT_VOICE,
instruction: str = LLM.summary.instruct,
voice: Optional[str] = TTS.xtts.voice,
speed: float = 1.1,
podcast: bool = False,
LLM: Ollama = None
@ -605,9 +605,9 @@ def split_text_into_chunks(text: str) -> List[str]:
sentences = re.split(r'(?<=[.!?])\s+', text)
words = text.split()
total_words = len(words)
L.DEBUG(f"Total words: {total_words}. SUMMARY_CHUNK_SIZE: {SUMMARY_CHUNK_SIZE}. SUMMARY_TPW: {SUMMARY_TPW}.")
L.DEBUG(f"Total words: {total_words}. LLM.summary.chunk_size: {LLM.summary.chunk_size}. LLM.tpw: {LLM.tpw}.")
max_words_per_chunk = int(SUMMARY_CHUNK_SIZE / SUMMARY_TPW)
max_words_per_chunk = int(LLM.summary.chunk_size / LLM.tpw)
L.DEBUG(f"Maximum words per chunk: {max_words_per_chunk}")
chunks = []
@ -633,8 +633,8 @@ def split_text_into_chunks(text: str) -> List[str]:
def calculate_max_tokens(text: str) -> int:
tokens_count = max(1, int(len(text.split()) * SUMMARY_TPW)) # Ensure at least 1
return min(tokens_count // 4, SUMMARY_CHUNK_SIZE)
tokens_count = max(1, int(len(text.split()) * LLM.tpw)) # Ensure at least 1
return min(tokens_count // 4, LLM.summary.chunk_size)
@ -694,7 +694,7 @@ async def extract_text(file: Union[UploadFile, bytes, bytearray, str, Path], bg_
raise ValueError(f"Error extracting text: {str(e)}")
async def summarize_text(text: str, instruction: str = SUMMARY_INSTRUCT, length_override: int = None, length_quotient: float = SUMMARY_LENGTH_RATIO, LLM: Ollama = None):
async def summarize_text(text: str, instruction: str = LLM.summary.instruct, length_override: int = None, length_quotient: float = LLM.summary.length_ratio, LLM: Ollama = None):
LLM = LLM if LLM else Ollama()
chunked_text = split_text_into_chunks(text)
@ -703,12 +703,12 @@ async def summarize_text(text: str, instruction: str = SUMMARY_INSTRUCT, length_
total_words_count = sum(len(chunk.split()) for chunk in chunked_text)
L.DEBUG(f"Total words count: {total_words_count}")
total_tokens_count = max(1, int(total_words_count * SUMMARY_TPW))
total_tokens_count = max(1, int(total_words_count * LLM.tpw))
L.DEBUG(f"Total tokens count: {total_tokens_count}")
total_summary_length = length_override if length_override else total_tokens_count // length_quotient
L.DEBUG(f"Total summary length: {total_summary_length}")
corrected_total_summary_length = min(total_summary_length, SUMMARY_TOKEN_LIMIT)
corrected_total_summary_length = min(total_summary_length, LLM.summary.max_tokens)
L.DEBUG(f"Corrected total summary length: {corrected_total_summary_length}")
summaries = await asyncio.gather(*[
@ -738,11 +738,11 @@ async def process_chunk(instruction: str, text: str, part: int, total_parts: int
LLM = LLM if LLM else Ollama()
words_count = len(text.split())
tokens_count = max(1, int(words_count * SUMMARY_TPW))
tokens_count = max(1, int(words_count * LLM.tpw))
summary_length_ratio = length_ratio if length_ratio else SUMMARY_LENGTH_RATIO
max_tokens = min(tokens_count // summary_length_ratio, SUMMARY_CHUNK_SIZE)
max_tokens = max(max_tokens, SUMMARY_MIN_LENGTH)
summary_length_ratio = length_ratio if length_ratio else LLM.summary.length_ratio
max_tokens = min(tokens_count // summary_length_ratio, LLM.summary.chunk_size)
max_tokens = max(max_tokens, LLM.summary.min_length)
L.DEBUG(f"Processing part {part} of {total_parts}: Words: {words_count}, Estimated tokens: {tokens_count}, Max output tokens: {max_tokens}")
@ -753,7 +753,7 @@ async def process_chunk(instruction: str, text: str, part: int, total_parts: int
L.DEBUG(f"Starting LLM.generate for part {part} of {total_parts}")
response = await LLM.generate(
model=SUMMARY_MODEL,
model=LLM.summary.model,
prompt=prompt,
stream=False,
options={'num_predict': max_tokens, 'temperature': 0.5}

View file

@ -12,7 +12,7 @@ import asyncio
from pydantic import BaseModel
from typing import Optional, Union, List
from pydub import AudioSegment
from TTS.api import TTS
from TTS.api import TTS as XTTSv2
from pathlib import Path
from datetime import datetime as dt_datetime
from time import time
@ -25,7 +25,7 @@ import tempfile
import random
import re
import os
from sijapi import L, DEFAULT_VOICE, TTS_SEGMENTS_DIR, VOICE_DIR, PODCAST_DIR, TTS_OUTPUT_DIR, ELEVENLABS_API_KEY
from sijapi import L, Dir, API, TTS
from sijapi.utilities import sanitize_filename
@ -39,14 +39,14 @@ MODEL_NAME = "tts_models/multilingual/multi-dataset/xtts_v2"
@tts.get("/tts/local_voices", response_model=List[str])
async def list_wav_files():
wav_files = [file.split('.')[0] for file in os.listdir(VOICE_DIR) if file.endswith(".wav")]
wav_files = [file.split('.')[0] for file in os.listdir(Dir.data.tts.voices) if file.endswith(".wav")]
return wav_files
@tts.get("/tts/elevenlabs_voices")
async def list_11l_voices():
formatted_list = ""
url = "https://api.elevenlabs.io/v1/voices"
headers = {"xi-api-key": ELEVENLABS_API_KEY}
headers = {"xi-api-key": TTS.elevenlabs.api_key}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers)
@ -71,10 +71,10 @@ async def select_voice(voice_name: str) -> str:
# Case Insensitive comparison
voice_name_lower = voice_name.lower()
L.DEBUG(f"Looking for {voice_name_lower}")
for item in VOICE_DIR.iterdir():
for item in Dir.data.tts.voices.iterdir():
L.DEBUG(f"Checking {item.name.lower()}")
if item.name.lower() == f"{voice_name_lower}.wav":
L.DEBUG(f"select_voice received query to use voice: {voice_name}. Found {item} inside {VOICE_DIR}.")
L.DEBUG(f"select_voice received query to use voice: {voice_name}. Found {item} inside {Dir.data.tts.voices}.")
return str(item)
L.ERR(f"Voice file not found")
@ -131,7 +131,7 @@ async def generate_speech(
title: str = None,
output_dir = None
) -> str:
output_dir = Path(output_dir) if output_dir else TTS_OUTPUT_DIR
output_dir = Path(output_dir) if output_dir else TTS.data.tts.outputs
if not output_dir.exists():
output_dir.mkdir(parents=True)
@ -149,7 +149,7 @@ async def generate_speech(
# raise HTTPException(status_code=400, detail="Invalid model specified")
if podcast == True:
podcast_path = Path(PODCAST_DIR) / audio_file_path.name
podcast_path = TTS.podcast_dir / audio_file_path.name
L.DEBUG(f"Podcast path: {podcast_path}")
shutil.copy(str(audio_file_path), str(podcast_path))
bg_tasks.add_task(os.remove, str(audio_file_path))
@ -196,7 +196,7 @@ async def determine_voice_id(voice_name: str) -> str:
L.DEBUG(f"Requested voice not among the hardcoded options.. checking with 11L next.")
url = "https://api.elevenlabs.io/v1/voices"
headers = {"xi-api-key": ELEVENLABS_API_KEY}
headers = {"xi-api-key": TTS.elevenlabs.api_key}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers)
@ -222,10 +222,10 @@ async def elevenlabs_tts(model: str, input_text: str, voice: str, title: str = N
"text": input_text,
"model_id": model
}
headers = {"Content-Type": "application/json", "xi-api-key": ELEVENLABS_API_KEY}
headers = {"Content-Type": "application/json", "xi-api-key": TTS.elevenlabs.api_key}
async with httpx.AsyncClient(timeout=httpx.Timeout(300.0)) as client: # 5 minutes timeout
response = await client.post(url, json=payload, headers=headers)
output_dir = output_dir if output_dir else TTS_OUTPUT_DIR
output_dir = output_dir if output_dir else TTS.podcast_dir
title = title if title else dt_datetime.now().strftime("%Y%m%d%H%M%S")
filename = f"{sanitize_filename(title)}.mp3"
file_path = Path(output_dir) / filename
@ -236,9 +236,6 @@ async def elevenlabs_tts(model: str, input_text: str, voice: str, title: str = N
else:
raise HTTPException(status_code=response.status_code, detail="Error from ElevenLabs API")
async def get_text_content(text: Optional[str], file: Optional[UploadFile]) -> str:
if file:
return (await file.read()).decode("utf-8").strip()
@ -247,20 +244,17 @@ async def get_text_content(text: Optional[str], file: Optional[UploadFile]) -> s
else:
raise HTTPException(status_code=400, detail="No text provided")
async def get_voice_file_path(voice: str = None, voice_file: UploadFile = None) -> str:
if voice:
L.DEBUG(f"Looking for voice: {voice}")
selected_voice = await select_voice(voice)
selected_voice = await select_voice(voice)
return selected_voice
elif voice_file and isinstance(voice_file, UploadFile):
VOICE_DIR.mkdir(exist_ok=True)
Dir.data.tts.voices.mkdir(exist_ok=True)
content = await voice_file.read()
checksum = hashlib.md5(content).hexdigest()
existing_file = VOICE_DIR / voice_file.filename
existing_file = Dir.data.tts.voices / voice_file.filename
if existing_file.is_file():
with open(existing_file, 'rb') as f:
existing_checksum = hashlib.md5(f.read()).hexdigest()
@ -272,7 +266,7 @@ async def get_voice_file_path(voice: str = None, voice_file: UploadFile = None)
counter = 1
new_file = existing_file
while new_file.is_file():
new_file = VOICE_DIR / f"{base_name}{counter:02}.wav"
new_file = Dir.data.tts.voices / f"{base_name}{counter:02}.wav"
counter += 1
with open(new_file, 'wb') as f:
@ -280,8 +274,8 @@ async def get_voice_file_path(voice: str = None, voice_file: UploadFile = None)
return str(new_file)
else:
L.DEBUG(f"{dt_datetime.now().strftime('%Y%m%d%H%M%S')}: No voice specified or file provided, using default voice: {DEFAULT_VOICE}")
selected_voice = await select_voice(DEFAULT_VOICE)
L.DEBUG(f"{dt_datetime.now().strftime('%Y%m%d%H%M%S')}: No voice specified or file provided, using default voice: {TTS.xtts.voice}")
selected_voice = await select_voice(TTS.xtts.voice)
return selected_voice
@ -302,7 +296,7 @@ async def local_tts(
datetime_str = dt_datetime.now().strftime("%Y%m%d%H%M%S")
title = sanitize_filename(title) if title else "Audio"
filename = f"{datetime_str}_{title}.wav"
file_path = TTS_OUTPUT_DIR / filename
file_path = Dir.data.tts.outputs / filename
# Ensure the parent directory exists
file_path.parent.mkdir(parents=True, exist_ok=True)
@ -310,14 +304,14 @@ async def local_tts(
voice_file_path = await get_voice_file_path(voice, voice_file)
# Initialize TTS model in a separate thread
XTTS = await asyncio.to_thread(TTS, model_name=MODEL_NAME)
XTTS = await asyncio.to_thread(XTTSv2, model_name=MODEL_NAME)
await asyncio.to_thread(XTTS.to, DEVICE)
segments = split_text(text_content)
combined_audio = AudioSegment.silent(duration=0)
for i, segment in enumerate(segments):
segment_file_path = TTS_SEGMENTS_DIR / f"segment_{i}.wav"
segment_file_path = Dir.data.tts.segments / f"segment_{i}.wav"
L.DEBUG(f"Segment file path: {segment_file_path}")
# Run TTS in a separate thread
@ -340,7 +334,7 @@ async def local_tts(
# Export the combined audio in a separate thread
if podcast:
podcast_file_path = Path(PODCAST_DIR) / file_path.name
podcast_file_path = Path(TTS.podcast_dir) / file_path.name
await asyncio.to_thread(combined_audio.export, podcast_file_path, format="wav")
await asyncio.to_thread(combined_audio.export, file_path, format="wav")
@ -368,7 +362,7 @@ async def stream_tts(text_content: str, speed: float, voice: str, voice_file) ->
async def generate_tts(text: str, speed: float, voice_file_path: str) -> str:
output_dir = tempfile.mktemp(suffix=".wav", dir=tempfile.gettempdir())
XTTS = TTS(model_name=MODEL_NAME).to(DEVICE)
XTTS = XTTSv2(model_name=MODEL_NAME).to(DEVICE)
XTTS.tts_to_file(text=text, speed=speed, file_path=output_dir, speaker_wav=[voice_file_path], language="en")
return output_dir
@ -381,7 +375,7 @@ async def get_audio_stream(model: str, input_text: str, voice: str):
"text": input_text,
"model_id": "eleven_turbo_v2"
}
headers = {"Content-Type": "application/json", "xi-api-key": ELEVENLABS_API_KEY}
headers = {"Content-Type": "application/json", "xi-api-key": TTS.elevenlabs.api_key}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
@ -434,7 +428,7 @@ def copy_to_podcast_dir(file_path):
file_name = Path(file_path).name
# Construct the destination path in the PODCAST_DIR
destination_path = Path(PODCAST_DIR) / file_name
destination_path = TTS.podcast_dir / file_name
# Copy the file to the PODCAST_DIR
shutil.copy(file_path, destination_path)