File size: 3,879 Bytes
a375dbf
 
 
c8f7e68
a375dbf
 
 
 
f8ddf74
a375dbf
 
f8ddf74
a375dbf
 
f8ddf74
5a007ca
a375dbf
 
f8ddf74
a375dbf
 
 
d4b2b49
a375dbf
36b195f
a375dbf
681c05f
36b195f
5a007ca
a375dbf
 
 
5a007ca
a375dbf
 
36b195f
 
 
a375dbf
 
 
d1ed6b1
 
a375dbf
 
 
 
d1ed6b1
 
a375dbf
 
 
 
 
 
 
 
 
 
 
63ef86b
a375dbf
d1ed6b1
a375dbf
 
 
 
 
 
 
 
 
 
 
 
 
 
d1ed6b1
a375dbf
7f25817
a375dbf
8047063
a375dbf
 
7f25817
8047063
a375dbf
 
7f25817
a375dbf
 
63ef86b
a375dbf
d1ed6b1
7f25817
d1ed6b1
a375dbf
d4b2b49
 
a375dbf
bc5091e
7f25817
 
a375dbf
 
 
7f25817
 
 
 
 
a375dbf
d4b2b49
 
7f25817
 
a375dbf
 
7f25817
63ef86b
7f25817
a375dbf
7f25817
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""
elevenlabs_api.py

This file defines the interaction with the ElevenLabs text-to-speech (TTS) API using the ElevenLabs Python SDK.
It includes functionality for API request handling and processing API responses.

Key Features:
- Encapsulates all logic related to the ElevenLabs TTS API.
- Implements retry logic using Tenacity for handling transient API errors.
- Handles received audio and processes it for playback on the web.
- Provides detailed logging for debugging and error tracking.
- Utilizes robust error handling (EAFP) to validate API responses.

Classes:
- ElevenLabsConfig: Immutable configuration for interacting with ElevenLabs' TTS API.
- ElevenLabsError: Custom exception for ElevenLabs API-related errors.

Functions:
- text_to_speech_with_elevenlabs: Synthesizes speech from text using ElevenLabs' TTS API.
"""

# Standard Library Imports
import base64
from dataclasses import dataclass
from enum import Enum
import logging
import random
from typing import Literal, Optional, Tuple

# Third-Party Library Imports
from elevenlabs import ElevenLabs
from tenacity import retry, stop_after_attempt, wait_fixed, before_log, after_log

# Local Application Imports
from src.config import logger
from src.utils import validate_env_var


@dataclass(frozen=True)
class ElevenLabsConfig:
    """Immutable configuration for interacting with the ElevenLabs TTS API."""

    api_key: str = validate_env_var("ELEVENLABS_API_KEY")

    def __post_init__(self):
        # Validate that required attributes are set
        if not self.api_key:
            raise ValueError("ElevenLabs API key is not set.")

    @property
    def client(self) -> ElevenLabs:
        """
        Lazy initialization of the ElevenLabs client.

        Returns:
            ElevenLabs: Configured client instance.
        """
        return ElevenLabs(api_key=self.api_key)


class ElevenLabsError(Exception):
    """Custom exception for errors related to the ElevenLabs TTS API."""

    def __init__(self, message: str, original_exception: Optional[Exception] = None):
        super().__init__(message)
        self.original_exception = original_exception


# Initialize the ElevenLabs client
elevenlabs_config = ElevenLabsConfig()


@retry(
    stop=stop_after_attempt(3),
    wait=wait_fixed(2),
    before=before_log(logger, logging.DEBUG),
    after=after_log(logger, logging.DEBUG),
    reraise=True,
)
def text_to_speech_with_elevenlabs(prompt: str, text: str) -> bytes:
    """
    Synthesizes text to speech using the ElevenLabs TTS API.

    Args:
        prompt (str): The original user prompt used as the voice description.
        text (str): The text to be synthesized to speech.

    Returns:
        bytes: The raw binary audio data for playback.

    Raises:
        ElevenLabsError: If there is an error communicating with the ElevenLabs API or processing the response.
    """
    logger.debug(
        f"Synthesizing speech with ElevenLabs. Text length: {len(text)} characters."
    )

    request_body = {"text": text, "voice_description": prompt}

    try:
        # Synthesize speech using the ElevenLabs SDK
        response = elevenlabs_config.client.text_to_voice.create_previews(
            voice_description=prompt,
            text=text,
        )

        previews = response.previews
        if not previews:
            msg = "No previews returned by ElevenLabs API."
            logger.error(msg)
            raise ElevenLabsError(message=msg)

        preview = random.choice(previews)
        base64_audio = preview.audio_base_64
        audio = base64.b64decode(base64_audio)
        return audio

    except Exception as e:
        logger.exception(f"Error synthesizing speech with ElevenLabs: {e}")
        raise ElevenLabsError(
            message=f"Failed to synthesize speech with ElevenLabs: {e}",
            original_exception=e,
        ) from e