You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RedditVideoMakerBot/TTS/aws_polly.py

102 lines
4.9 KiB

import random
import sys
import logging # Added for logging
from boto3 import Session
from botocore.exceptions import BotoCoreError, ClientError, ProfileNotFound
from utils import settings
voices = [
"Brian",
"Emma",
"Russell",
"Joey",
"Matthew",
"Joanna",
"Kimberly",
"Amy",
"Geraint",
"Nicole",
"Justin",
"Ivy",
"Kendra",
"Salli",
"Raveena",
]
logger = logging.getLogger(__name__)
class AWSPolly:
def __init__(self):
logger.debug("Initializing AWS Polly TTS engine.")
self.max_chars = 3000 # Max characters for Polly synthesize_speech if not using SSML.
self.voices = voices # Keep this list for random selection and validation.
def run(self, text: str, filepath: str, random_voice: bool = False):
logger.info(f"Requesting AWS Polly TTS for text: '{text[:30]}...' Output: {filepath}")
try:
# It's good practice to fetch profile from config or environment variables
# rather than hardcoding "polly" if flexibility is needed.
# For now, assuming "polly" profile is standard for this app.
profile_name = settings.config["settings"]["tts"].get("aws_profile_name") or "polly"
logger.debug(f"Attempting to create AWS session with profile: {profile_name}")
session = Session(profile_name=profile_name)
polly = session.client("polly")
logger.debug("AWS session and Polly client created successfully.")
selected_voice_id = ""
if random_voice:
selected_voice_id = self.randomvoice()
logger.debug(f"Using random AWS Polly voice: {selected_voice_id}")
else:
selected_voice_id = settings.config["settings"]["tts"].get("aws_polly_voice")
if not selected_voice_id:
logger.error(f"AWS Polly voice not set in config. Available options: {self.voices}")
raise ValueError(f"AWS_VOICE not set. Options: {self.voices}")
selected_voice_id = selected_voice_id.capitalize()
if selected_voice_id not in self.voices:
logger.error(f"Invalid AWS Polly voice '{selected_voice_id}' in config. Available: {self.voices}")
raise ValueError(f"Invalid AWS_VOICE '{selected_voice_id}'. Options: {self.voices}")
logger.debug(f"Using configured AWS Polly voice: {selected_voice_id}")
# Request speech synthesis
logger.debug(f"Synthesizing speech with Polly. VoiceId: {selected_voice_id}, Engine: neural")
response = polly.synthesize_speech(
Text=text, OutputFormat="mp3", VoiceId=selected_voice_id, Engine="neural" # Consider making Engine configurable
)
# Access the audio stream from the response
if "AudioStream" in response:
logger.debug("AudioStream received from Polly. Writing to file.")
with open(filepath, "wb") as audio_file:
audio_file.write(response["AudioStream"].read())
logger.info(f"Successfully saved AWS Polly TTS audio to {filepath}")
else:
logger.error("Could not stream audio from Polly response. 'AudioStream' not in response.")
# Log part of the response if it's small enough and doesn't contain sensitive info
logger.debug(f"Polly response without AudioStream: {str(response)[:200]}")
raise RuntimeError("AWS Polly: Could not stream audio, 'AudioStream' missing from response.")
except ProfileNotFound as e:
logger.error(f"AWS profile '{profile_name}' not found: {e}. Please configure AWS CLI.")
logger.error("Refer to AWS documentation for setup: "
"Linux: https://docs.aws.amazon.com/polly/latest/dg/setup-aws-cli.html, "
"Windows: https://docs.aws.amazon.com/polly/latest/dg/install-voice-plugin2.html")
# sys.exit(-1) is too abrupt for a library. Raise an exception.
raise RuntimeError(f"AWS Profile '{profile_name}' not found. Configure AWS CLI.")
except (BotoCoreError, ClientError) as error:
logger.error(f"AWS Polly API error: {error}", exc_info=True)
raise RuntimeError(f"AWS Polly API error: {error}")
except ValueError as e: # Catch voice configuration errors
logger.error(f"Configuration error for AWS Polly: {e}")
raise # Re-raise to be handled by calling code
except Exception as e: # Catch any other unexpected errors
logger.error(f"An unexpected error occurred with AWS Polly: {e}", exc_info=True)
raise RuntimeError(f"Unexpected AWS Polly failure: {e}")
def randomvoice(self) -> str:
choice = random.choice(self.voices)
logger.debug(f"Randomly selected AWS Polly voice: {choice}")
return choice