""" Module `Settings` provides a versatile configuration management system for your application. It includes a `Settings` class that allows users to customize various options and save them to a JSON file. * Example: First, create an instance of the `Settings` class: settings = Settings() * Example usage of the `Settings` class: settings.change_settings_save_to_file('settings.json') - If the file doesn't exist, it will be created with default settings. - If the file exists, it will be overwritten with the current settings. - If the file exists with incomplete settings, missing settings will be added with default values. * Example default settings in 'settings.json': { "translator": "Google Translator", "deepl_api_key": "null", "chat_gpt_access_token": null, "translated_line_count": "50", "tts": "TTS - Agnieszka - Ivona", "tts_speed": "5", "tts_volume": "65", "output": "Ogl\u0105dam w MM_AVH_Players (wynik: napisy i audio)" } """ from dataclasses import asdict, dataclass from json import decoder, dump, load from typing import Dict, List, Optional, Tuple import webbrowser from constants import SETTINGS_PATH, console from data.config import Config @dataclass(slots=True) class Settings: """ A class representing application settings. Attributes: - translator (Optional[str]): The selected translator. - deepl_api_key (Optional[str]): The API key for DeepL translation service. - chat_gpt_access_token (Optional[str]): The access token for ChatGPT API. - translated_line_count (Optional[str]): The number of translated lines. - tts (Optional[str]): The selected TTS engine. - tts_speed (Optional[str]): The speed of the TTS voice. - tts_volume (Optional[str]): The volume of the TTS voice. - output (Optional[str]): The selected output option. Methods: - load_from_file(cls, settings_path: str) -> 'Settings': Load settings from a file. - _set_option(prompt: str, options: List[Dict[str, str]]) -> str | None: Set an option from a list of options. - _is_valid_speed(speed: str, tts: str) -> bool: Check if a given speed value is valid for the selected TTS engine. - _is_valid_volume(volume: str, tts: str) -> bool: Check if a given volume value is valid for the selected TTS engine. - _get_translator(settings: Optional['Settings']) -> Optional[str]: Get the selected translator. - _get_deepl_api_key(settings: Optional['Settings']) -> Optional[str]: Get the DeepL API key. - _get_chat_gpt_access_token(settings: Optional['Settings']) -> Optional[str]: Get the ChatGPT access token. - _get_translated_line_count(settings: Optional['Settings']) -> Optional[str]: Get the number of translated lines. - _get_tts(settings: Optional['Settings']) -> Optional[str]: Get the selected TTS engine. - _get_default_speed_volume(tts: str) -> Tuple[Optional[str], Optional[str]]: Get the default speed and volume for a TTS engine. - _get_tts_speed(tts: str, default_speed: Optional[str]) -> Optional[str]: Get the TTS speed. - _get_tts_volume(tts: str, default_volume: Optional[str]) -> Optional[str]: Get the TTS volume. - _get_output(settings: Optional['Settings']) -> Optional[str]: Get the selected output option. - get_user_settings(settings_path: str) -> Optional['Settings']: Get user settings from a file. - change_settings_save_to_file(settings_path: str) -> None: Change and save settings to a file. """ translator: Optional[str] = None deepl_api_key: Optional[str] = None chat_gpt_access_token: Optional[str] = None translated_line_count: Optional[str] = None tts: Optional[str] = None tts_speed: Optional[str] = None tts_volume: Optional[str] = None output: Optional[str] = None @classmethod def load_from_file(cls, settings_path: str = SETTINGS_PATH) -> 'Settings': """ Load settings from a JSON file. Args: - settings_path (str): The name of the file to load settings from. Returns: - Settings: An instance of the Settings class with the loaded settings. Raises: - FileNotFoundError: If the file does not exist. - decoder.JSONDecodeError: If the file has an invalid JSON format. """ def get_default_settings(): return cls( translator='Google Translator', deepl_api_key=None, chat_gpt_access_token=None, translated_line_count='50', tts='TTS - Agnieszka - Ivona', tts_speed='5', tts_volume='65', output='Oglądam w MM_AVH_Players (wynik: napisy i audio)' ) try: with open(settings_path, 'r', encoding='utf-8') as file: data = load(file) except FileNotFoundError: console.print( f'Nie znaleziono pliku {settings_path}', style='red_bold') console.print('Musisz najpierw ustawić ustawienia.', style='red_bold') return get_default_settings() except decoder.JSONDecodeError: console.print( f'Niepoprawny format pliku {settings_path}', style='red_bold') return get_default_settings() return Settings( translator=data.get('translator'), deepl_api_key=data.get('deepl_api_key'), chat_gpt_access_token=data.get('chat_gpt_access_token'), translated_line_count=data.get('translated_line_count'), tts=data.get('tts'), tts_speed=data.get('tts_speed'), tts_volume=data.get('tts_volume'), output=data.get('output') ) @staticmethod def _set_option(prompt: str, options: List[Dict[str, str]]) -> str | None: """ Set an option from a list of options. Args: - prompt (str): The prompt to display to the user. - options (List[Dict[str, str]]): The list of options to choose from. Returns: - str | None: The selected option, or None if the selection is invalid. """ console.print(f'\n{prompt}', style='yellow_bold') for i, option in enumerate(options): console.print( f'[yellow_bold]{i + 1}.[/yellow_bold] [white]{option["name"]}') if 'suboptions' in option: for j, suboption in enumerate(option['suboptions']): console.print( f'[yellow_bold] {i + 1}.{j + 1}.[/yellow_bold] [white]{suboption["name"]}') elif 'description' in option: console.print( f' [white]{option["description"]["speed"]}') console.print( f' [white]{option["description"]["volume"]}') console.print('Wybierz opcję: ', style='green_bold', end='') choice = input() if '.' in choice: major_choice, minor_choice = map(int, choice.split('.')) if 1 <= major_choice <= len(options) and 1 <= minor_choice <= len(options[major_choice - 1]['suboptions']): return options[major_choice - 1]['suboptions'][minor_choice - 1]['name'] elif choice.isdigit(): choice_num = int(choice) if 1 <= choice_num <= len(options): return options[choice_num - 1]['name'] else: for option in options: if option['name'] == choice: return option['name'] console.print( 'Niepoprawny wybór. Nie zmieniono wartości!', style='red_bold') return None @staticmethod def _is_valid_speed(speed: str, tts: str) -> bool: """ Check if a given speed value is valid for the selected TTS engine. Args: - speed (str): The speed value to check. - tts (str): The selected TTS engine. Returns: - bool: True if the speed value is valid, False otherwise. """ if tts == 'TTS - Zosia - Harpo': return int(speed) >= 0 if tts == 'TTS - Agnieszka - Ivona': return -10 <= int(speed) <= 10 if tts in {'TTS - Zofia - Edge', 'TTS - Marek - Edge'}: return speed.startswith(('+', '-')) and speed[1:-1].isdigit() and -100 <= int( speed[1:-1]) <= 100 and speed.endswith('%') return False @staticmethod def _is_valid_volume(volume: str, tts: str) -> bool: """ Check if a given volume value is valid for the selected TTS engine. Args: - volume (str): The volume value to check. - tts (str): The selected TTS engine. Returns: - bool: True if the volume value is valid, False otherwise. """ if tts == 'TTS - Zosia - Harpo': return 0 <= float(volume) <= 1 if tts == 'TTS - Agnieszka - Ivona': return -100 <= int(volume) <= 100 if tts in {'TTS - Zofia - Edge', 'TTS - Marek - Edge'}: return volume.startswith(('+', '-')) and volume[1:-1].isdigit() and -100 <= int( volume[1:-1]) <= 100 and volume.endswith('%') return False @staticmethod def _get_translator(settings: Optional['Settings']) -> Optional[str]: """ Retrieve the selected translator from the user settings or prompt the user to choose one. Args: - settings (Optional['Settings']): The user settings object. Returns: - Optional[str]: The selected translator or None if not found. """ translator = Settings._set_option( 'Wybierz tłumacza: ', Config.get_translators()) if translator is None: translator = settings.translator if settings else None return translator @staticmethod def _get_deepl_api_key(settings: Optional['Settings']) -> Optional[str]: """ Prompt the user to set the DeepL API key or retrieve it from the user settings. Args: - settings (Optional['Settings']): The user settings object. Returns: - Optional[str]: The DeepL API key or None if not set. """ console.print( '\nCzy chcesz ustawić klucz API DeepL?', style="yellow_bold") console.print('(T lub Y - tak): ', style='green_bold', end='') if input().lower() in ('t', 'y'): console.print( 'Klucz API DeepL można wygenerować na stronie https://www.deepl.com/pro-api', style='yellow_bold') console.print('Podaj klucz API DeepL: ', style='green_bold', end='') deepl_api_key = input('') if deepl_api_key == '': deepl_api_key = settings.deepl_api_key if settings else None console.print( 'Niepoprawna wartość. Nie zmieniono wartości!', style='red_bold') else: deepl_api_key = settings.deepl_api_key if settings else None return deepl_api_key @staticmethod def _get_chat_gpt_access_token(settings: Optional['Settings']) -> Optional[str]: """ Prompt the user to set the Chat GPT access token or retrieve it from the user settings. Args: - settings (Optional['Settings']): The user settings object. Returns: - Optional[str]: The Chat GPT access token or None if not set. """ console.print( '\nCzy chcesz ustawić token dostępu do chat GPT?', style='yellow_bold') console.print('(T lub Y - tak): ', style='green_bold', end='') if input().lower() in ('t', 'y'): console.print( 'Token dostępu (accessToken): https://chat.openai.com/api/auth/session', style='yellow_bold') webbrowser.open('https://chat.openai.com/api/auth/session') console.print( 'Podaj token dostępu do chat GPT: ', style='green_bold', end='') chat_gpt_access_token = input() if chat_gpt_access_token == '': chat_gpt_access_token = settings.chat_gpt_access_token if settings else None console.print( 'Niepoprawna wartość. Nie zmieniono wartości!', style='red_bold') else: chat_gpt_access_token = settings.chat_gpt_access_token if settings else None return chat_gpt_access_token @staticmethod def _get_translated_line_count(settings: Optional['Settings']) -> Optional[str]: """ Prompt the user to set the number of translated lines or retrieve it from the user settings. Args: - settings (Optional['Settings']): The user settings object. Returns: - Optional[str]: The number of translated lines or None if not set. (Optional[str] future maybe change) """ translated_line_count = Settings._set_option('Wybierz liczbę przetłumaczonych linii: ', Config.get_translation_options()) if translated_line_count is None: translated_line_count = settings.translated_line_count if settings else None return translated_line_count @staticmethod def _get_tts(settings: Optional['Settings']) -> Optional[str]: """ Prompt the user to choose a TTS engine or retrieve it from the user settings. Args: - settings (Optional['Settings']): The user settings object. Returns: - Optional[str]: The selected TTS engine or None if not set. """ tts = Settings._set_option( 'Wybierz silnik TTS: ', Config.get_voice_actors()) if tts is None: tts = settings.tts if settings else None return tts @staticmethod def _get_default_speed_volume(tts: str) -> Tuple[Optional[str], Optional[str]]: """ Retrieve the default voice speed and volume for the specified TTS engine. Args: - tts (str): The selected TTS engine. Returns: - Tuple[Optional[str], Optional[str]]: A tuple containing the default voice speed and volume, or (None, None) if not found. """ default_speed = None default_volume = None voice_actor = next( (actor for actor in Config.get_voice_actors() if actor['name'] == tts), None) if voice_actor: default_speed = voice_actor['default_options']['default_voice_speed'] default_volume = voice_actor['default_options']['default_voice_volume'] return default_speed, default_volume @staticmethod def _get_tts_speed(tts: str, default_speed: Optional[str]) -> Optional[str]: """ Prompt the user to enter the voice speed for the selected TTS engine, or use the default speed. Args: - tts (str): The selected TTS engine. - default_speed (Optional[str]): The default voice speed for the TTS engine. Returns: - Optional[str]: The selected voice speed or the default speed if not set. """ console.print('Wpisz szybkość głosu: ', style='green_bold', end='') tts_speed_choice = input() try: tts_speed = tts_speed_choice if ( Settings._is_valid_speed( tts_speed_choice, tts) and tts_speed_choice.strip() != '' ) else default_speed except ValueError: console.print( 'Niepoprawna wartość szybkości. Używam domyślnej wartości.', style='red_bold') tts_speed = default_speed return tts_speed @staticmethod def _get_tts_volume(tts: str, default_volume: Optional[str]) -> Optional[str]: """ Prompt the user to enter the voice volume for the selected TTS engine, or use the default volume. Args: - tts (str): The selected TTS engine. - default_volume (Optional[str]): The default voice volume for the TTS engine. Returns: - Optional[str]: The selected voice volume or the default volume if not set. """ console.print('Wpisz głośność głosu: ', style='green_bold', end='') tts_volume_choice = input() try: tts_volume = tts_volume_choice if ( Settings._is_valid_volume( tts_volume_choice, tts) and tts_volume_choice.strip() != '' ) else default_volume except ValueError: console.print( 'Niepoprawna wartość głośności. Używam domyślnej wartości.', style='red_bold') tts_volume = default_volume return tts_volume @staticmethod def _get_output(settings: Optional['Settings']) -> Optional[str]: """ Prompt the user to choose an output option or retrieve it from the user settings. Args: - settings (Optional['Settings']): The user settings object. Returns: - Optional[str]: The selected output option or None if not set. """ output = Settings._set_option( 'Wybierz wyjście: ', Config.get_output()) if output is None: output = settings.output if settings else None return output @staticmethod def get_user_settings(settings_path: str = SETTINGS_PATH) -> Optional['Settings']: """ Get the user settings from a file or prompt the user to enter them. Args: - settings_path (str): The name of the settings file. Returns: - Optional['Settings']: The user settings object or None if not found. """ settings = Settings.load_from_file(settings_path) translator = Settings._get_translator(settings) deepl_api_key = Settings._get_deepl_api_key(settings) chat_gpt_access_token = Settings._get_chat_gpt_access_token(settings) translated_line_count = Settings._get_translated_line_count(settings) tts = Settings._get_tts(settings) default_speed, default_volume = Settings._get_default_speed_volume(tts) console.print(f'\nWybrałeś: {tts}', style='yellow_bold') tts_speed = Settings._get_tts_speed(tts, default_speed) tts_volume = Settings._get_tts_volume(tts, default_volume) output = Settings._get_output(settings) return Settings( translator=translator, deepl_api_key=deepl_api_key, chat_gpt_access_token=chat_gpt_access_token, translated_line_count=translated_line_count, tts=tts, tts_speed=tts_speed, tts_volume=tts_volume, output=output ) @staticmethod def change_settings_save_to_file(settings_path: str = SETTINGS_PATH) -> None: """ Prompt the user to change the settings and save them to a file. If the file does not exist, it will be created. If the file exists, it will be overwritten. Args: - settings_path (str): The name of the settings file. """ settings = Settings.get_user_settings(settings_path) with open(settings_path, 'w', encoding='utf-8') as file: dump(asdict(settings), file, indent=4)