Coverage for coffee_maker/utils/text_to_speech.py: 0%

54 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-21 05:58 +0000

1# co-author : Gemini 2.5 Pro Preview 

2import logging 

3 

4# Set up a logger for this module. 

5# In a larger application, the root logger would typically be configured elsewhere. 

6# For a standalone script or a module intended to be used as a utility, 

7# getting a logger instance like this is standard. 

8logger = logging.getLogger(__name__) 

9 

10 

11def text_to_speech_pyttsx3(text, voice_id=None, rate=150, volume=1.0): 

12 """ 

13 Synthesizes text to speech using pyttsx3. 

14 

15 Args: 

16 text (str): The text to synthesize. 

17 voice_id (str, optional): The ID of the voice to use. If None, uses the default voice. 

18 Defaults to None. 

19 rate (int, optional): The speech rate (words per minute). Defaults to 150. 

20 volume (float, optional): The speech volume (0.0 to 1.0). Defaults to 1.0. 

21 """ 

22 try: 

23 import pyttsx3 

24 except ImportError: 

25 logger.error("pyttsx3 is not installed. Please install.") 

26 raise RuntimeError("pyttsx3 is not installed. Please install it.") 

27 

28 try: 

29 logger.info("Initializing pyttsx3 engine.") 

30 engine = pyttsx3.init() 

31 

32 # Configure speech rate 

33 logger.debug(f"Setting speech rate to: {rate}") 

34 engine.setProperty("rate", rate) 

35 

36 # Configure volume 

37 logger.debug(f"Setting volume to: {volume}") 

38 engine.setProperty("volume", volume) 

39 

40 # Get available voices 

41 voices = engine.getProperty("voices") 

42 logger.debug(f"Found {len(voices)} voices available on the system.") 

43 

44 # To list available voices and their properties in detail, uncomment the following: 

45 # for voice in voices: 

46 # logger.debug(f"Voice Details - ID: {voice.id} | Name: {voice.name} | Lang: {voice.languages} | Gender: {voice.gender}") 

47 

48 # Select a specific voice 

49 if voice_id: 

50 logger.info(f"Attempting to set voice ID to: {voice_id}") 

51 engine.setProperty("voice", voice_id) 

52 # Verify if voice was set (optional, pyttsx3 doesn't always provide easy verification) 

53 # current_voice = engine.getProperty('voice') 

54 # logger.debug(f"Current voice ID set to: {current_voice}") 

55 # if current_voice != voice_id: 

56 # logger.warning(f"Failed to set voice ID to {voice_id}. Current voice is {current_voice}") 

57 else: 

58 logger.info("No specific voice_id provided, using default voice.") 

59 # Example to select a specific voice type if no voice_id is given 

60 # This part is illustrative and would need adaptation based on available voices on your system. 

61 # for voice in voices: 

62 # if "english" in voice.name.lower() and voice.gender == "female": # Adapt condition 

63 # engine.setProperty('voice', voice.id) 

64 # logger.info(f"Selected voice programmatically: {voice.name} ({voice.id})") 

65 # break 

66 

67 logger.info(f"Synthesizing text: '{text}'") 

68 engine.say(text) 

69 engine.runAndWait() # Blocks until all spoken text has been heard 

70 

71 # engine.stop() # Usually not needed after runAndWait() for simple use cases. 

72 # Consider if managing event loop manually or for specific platform issues. 

73 

74 logger.info("Speech synthesis complete.") 

75 

76 except Exception as e: 

77 logger.error(f"An error occurred during pyttsx3 operation: {e}", exc_info=True) 

78 # exc_info=True will include traceback information in the log for errors. 

79 

80 

81if __name__ == "__main__": 

82 # Basic logging configuration for when the script is run directly. 

83 # This will print log messages of level INFO and above to the console. 

84 logging.basicConfig( 

85 level=logging.INFO, # Change to logging.DEBUG for more detailed output 

86 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 

87 datefmt="%Y-%m-%d %H:%M:%S", 

88 ) 

89 

90 logger.info("--- Text-to-Speech Example Script Starting ---") 

91 

92 # Example: Listing available voices (more detailed logging if DEBUG is enabled) 

93 logger.info("Attempting to list available voices (details will show if log level is DEBUG)...") 

94 try: 

95 temp_engine = pyttsx3.init() 

96 available_voices = temp_engine.getProperty("voices") 

97 if not available_voices: 

98 logger.warning("No voices found by pyttsx3 engine.") 

99 for i, v in enumerate(available_voices): 

100 log_message = f"Voice {i + 1}: ID='{v.id}', Name='{v.name}', Langs={v.languages}, Gender='{v.gender}'" 

101 if logger.isEnabledFor(logging.DEBUG): # Log detailed voice info only if DEBUG is on 

102 logger.debug(log_message) 

103 elif i < 5: # Log first few voices at INFO level for a quick glance 

104 logger.info(f"Voice {i + 1} (sample): Name='{v.name}', ID='{v.id}'") 

105 if len(available_voices) > 5 and not logger.isEnabledFor(logging.DEBUG): 

106 logger.info(f"... and {len(available_voices) - 5} more voices (set log level to DEBUG to see all).") 

107 temp_engine.stop() # Clean up the temporary engine 

108 except Exception as e: 

109 logger.error(f"Could not list voices: {e}", exc_info=True) 

110 

111 logger.info("--- Default voice example ---") 

112 text_to_speech_pyttsx3("Hello, this is a test using the default voice settings.") 

113 

114 logger.info("--- Custom settings example (faster and louder) ---") 

115 text_to_speech_pyttsx3("This is a faster and louder message.", rate=220, volume=0.95) 

116 

117 # Example for finding and using a specific voice ID 

118 # Replace with an actual voice ID from your system after listing them. 

119 # macos_daniel_voice_id = 'com.apple.speech.synthesis.voice.daniel' # Example for macOS 

120 # windows_zira_voice_id = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_EN-US_ZIRA_11.0' # Example for Windows 

121 

122 # selected_voice_id = None # Set this to a valid ID from your system 

123 # if selected_voice_id: 

124 # logger.info(f"--- Attempting to use specific voice ID: {selected_voice_id} ---") 

125 # text_to_speech_pyttsx3("This should be a specifically selected voice.", voice_id=selected_voice_id) 

126 # else: 

127 # logger.info("--- Specific voice ID example skipped (no voice_id set) ---") 

128 

129 logger.info("--- French example (will use default if no French voice is specifically set/found) ---") 

130 text_to_speech_pyttsx3("Bonjour le monde.") 

131 

132 logger.info("--- Text-to-Speech Example Script Finished ---")