././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1731275870.670288 gtts-2.5.4/0000755000175100001770000000000014714226137012117 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/LICENSE0000644000175100001770000000210514714226132013115 0ustar00runnerdockerThe MIT License (MIT) Copyright © 2014-2024 Pierre Nicolas Durette Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1731275870.670288 gtts-2.5.4/PKG-INFO0000644000175100001770000001004414714226137013213 0ustar00runnerdockerMetadata-Version: 2.1 Name: gTTS Version: 2.5.4 Summary: gTTS (Google Text-to-Speech), a Python library and CLI tool to interface with Google Translate text-to-speech API Author-email: Pierre Nicolas Durette License: MIT Project-URL: homepage, https://github.com/pndurette/gTTS Project-URL: documentation, https://gtts.readthedocs.io Project-URL: repository, https://github.com/pndurette/gTTS Project-URL: changelog, https://github.com/pndurette/gTTS/blob/main/CHANGELOG.md Keywords: gtts,text to speech,Google Translate,TTS Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: MacOS Classifier: Operating System :: Unix Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Multimedia :: Sound/Audio :: Speech Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: requests<3,>=2.27 Requires-Dist: click<8.2,>=7.1 Provides-Extra: tests Requires-Dist: pytest<8.4.0,>=7.1.3; extra == "tests" Requires-Dist: pytest-cov; extra == "tests" Requires-Dist: testfixtures; extra == "tests" Provides-Extra: docs Requires-Dist: sphinx; extra == "docs" Requires-Dist: sphinx-autobuild; extra == "docs" Requires-Dist: sphinx_rtd_theme; extra == "docs" Requires-Dist: sphinx-click; extra == "docs" Requires-Dist: sphinx-mdinclude; extra == "docs" # gTTS **gTTS** (*Google Text-to-Speech*), a Python library and CLI tool to interface with Google Translate's text-to-speech API. Write spoken `mp3` data to a file, a file-like object (bytestring) for further audio manipulation, or `stdout`. [![PyPI version](https://img.shields.io/pypi/v/gTTS.svg)](https://pypi.org/project/gTTS/) [![Python versions](https://img.shields.io/pypi/pyversions/gTTS.svg)](https://pypi.org/project/gTTS/) [![Tests workflow](https://github.com/pndurette/gtts/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/pndurette/gTTS/actions) [![codecov](https://codecov.io/gh/pndurette/gTTS/branch/master/graph/badge.svg)](https://codecov.io/gh/pndurette/gTTS) [![Commits Since](https://img.shields.io/github/commits-since/pndurette/gTTS/latest.svg)](https://github.com/pndurette/gTTS/commits/) [![PyPi Downloads](https://static.pepy.tech/badge/gtts)](http://pepy.tech/project/gtts) [![Buy me a Coffee](https://img.shields.io/badge/buy%20me%20a-coffee-orange)](https://www.buymeacoffee.com/pndurette) ## Features - Customizable speech-specific sentence tokenizer that allows for unlimited lengths of text to be read, all while keeping proper intonation, abbreviations, decimals and more; - Customizable text pre-processors which can, for example, provide pronunciation corrections; ### Installation $ pip install gTTS ### Quickstart Command Line: $ gtts-cli 'hello' --output hello.mp3 Module: >>> from gtts import gTTS >>> tts = gTTS('hello') >>> tts.save('hello.mp3') See for documentation and examples. ### Disclaimer This project is *not* affiliated with Google or Google Cloud. Breaking upstream changes *can* occur without notice. This project is leveraging the undocumented [Google Translate](https://translate.google.com) speech functionality and is *different* from [Google Cloud Text-to-Speech](https://cloud.google.com/text-to-speech/). ### Project - [Questions & community](https://github.com/pndurette/gTTS/discussions) - [Changelog](CHANGELOG.rst) - [Contributing](CONTRIBUTING.rst) ### Licence [The MIT License (MIT)](LICENSE) Copyright © 2014-2024 Pierre Nicolas Durette & [Contributors](https://github.com/pndurette/gTTS/graphs/contributors) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/README.md0000644000175100001770000000440514714226132013374 0ustar00runnerdocker# gTTS **gTTS** (*Google Text-to-Speech*), a Python library and CLI tool to interface with Google Translate's text-to-speech API. Write spoken `mp3` data to a file, a file-like object (bytestring) for further audio manipulation, or `stdout`. [![PyPI version](https://img.shields.io/pypi/v/gTTS.svg)](https://pypi.org/project/gTTS/) [![Python versions](https://img.shields.io/pypi/pyversions/gTTS.svg)](https://pypi.org/project/gTTS/) [![Tests workflow](https://github.com/pndurette/gtts/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/pndurette/gTTS/actions) [![codecov](https://codecov.io/gh/pndurette/gTTS/branch/master/graph/badge.svg)](https://codecov.io/gh/pndurette/gTTS) [![Commits Since](https://img.shields.io/github/commits-since/pndurette/gTTS/latest.svg)](https://github.com/pndurette/gTTS/commits/) [![PyPi Downloads](https://static.pepy.tech/badge/gtts)](http://pepy.tech/project/gtts) [![Buy me a Coffee](https://img.shields.io/badge/buy%20me%20a-coffee-orange)](https://www.buymeacoffee.com/pndurette) ## Features - Customizable speech-specific sentence tokenizer that allows for unlimited lengths of text to be read, all while keeping proper intonation, abbreviations, decimals and more; - Customizable text pre-processors which can, for example, provide pronunciation corrections; ### Installation $ pip install gTTS ### Quickstart Command Line: $ gtts-cli 'hello' --output hello.mp3 Module: >>> from gtts import gTTS >>> tts = gTTS('hello') >>> tts.save('hello.mp3') See for documentation and examples. ### Disclaimer This project is *not* affiliated with Google or Google Cloud. Breaking upstream changes *can* occur without notice. This project is leveraging the undocumented [Google Translate](https://translate.google.com) speech functionality and is *different* from [Google Cloud Text-to-Speech](https://cloud.google.com/text-to-speech/). ### Project - [Questions & community](https://github.com/pndurette/gTTS/discussions) - [Changelog](CHANGELOG.rst) - [Contributing](CONTRIBUTING.rst) ### Licence [The MIT License (MIT)](LICENSE) Copyright © 2014-2024 Pierre Nicolas Durette & [Contributors](https://github.com/pndurette/gTTS/graphs/contributors) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731275870.6662881 gtts-2.5.4/gTTS.egg-info/0000755000175100001770000000000014714226137014432 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275870.0 gtts-2.5.4/gTTS.egg-info/PKG-INFO0000644000175100001770000001004414714226136015525 0ustar00runnerdockerMetadata-Version: 2.1 Name: gTTS Version: 2.5.4 Summary: gTTS (Google Text-to-Speech), a Python library and CLI tool to interface with Google Translate text-to-speech API Author-email: Pierre Nicolas Durette License: MIT Project-URL: homepage, https://github.com/pndurette/gTTS Project-URL: documentation, https://gtts.readthedocs.io Project-URL: repository, https://github.com/pndurette/gTTS Project-URL: changelog, https://github.com/pndurette/gTTS/blob/main/CHANGELOG.md Keywords: gtts,text to speech,Google Translate,TTS Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: MacOS Classifier: Operating System :: Unix Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Multimedia :: Sound/Audio :: Speech Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: requests<3,>=2.27 Requires-Dist: click<8.2,>=7.1 Provides-Extra: tests Requires-Dist: pytest<8.4.0,>=7.1.3; extra == "tests" Requires-Dist: pytest-cov; extra == "tests" Requires-Dist: testfixtures; extra == "tests" Provides-Extra: docs Requires-Dist: sphinx; extra == "docs" Requires-Dist: sphinx-autobuild; extra == "docs" Requires-Dist: sphinx_rtd_theme; extra == "docs" Requires-Dist: sphinx-click; extra == "docs" Requires-Dist: sphinx-mdinclude; extra == "docs" # gTTS **gTTS** (*Google Text-to-Speech*), a Python library and CLI tool to interface with Google Translate's text-to-speech API. Write spoken `mp3` data to a file, a file-like object (bytestring) for further audio manipulation, or `stdout`. [![PyPI version](https://img.shields.io/pypi/v/gTTS.svg)](https://pypi.org/project/gTTS/) [![Python versions](https://img.shields.io/pypi/pyversions/gTTS.svg)](https://pypi.org/project/gTTS/) [![Tests workflow](https://github.com/pndurette/gtts/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/pndurette/gTTS/actions) [![codecov](https://codecov.io/gh/pndurette/gTTS/branch/master/graph/badge.svg)](https://codecov.io/gh/pndurette/gTTS) [![Commits Since](https://img.shields.io/github/commits-since/pndurette/gTTS/latest.svg)](https://github.com/pndurette/gTTS/commits/) [![PyPi Downloads](https://static.pepy.tech/badge/gtts)](http://pepy.tech/project/gtts) [![Buy me a Coffee](https://img.shields.io/badge/buy%20me%20a-coffee-orange)](https://www.buymeacoffee.com/pndurette) ## Features - Customizable speech-specific sentence tokenizer that allows for unlimited lengths of text to be read, all while keeping proper intonation, abbreviations, decimals and more; - Customizable text pre-processors which can, for example, provide pronunciation corrections; ### Installation $ pip install gTTS ### Quickstart Command Line: $ gtts-cli 'hello' --output hello.mp3 Module: >>> from gtts import gTTS >>> tts = gTTS('hello') >>> tts.save('hello.mp3') See for documentation and examples. ### Disclaimer This project is *not* affiliated with Google or Google Cloud. Breaking upstream changes *can* occur without notice. This project is leveraging the undocumented [Google Translate](https://translate.google.com) speech functionality and is *different* from [Google Cloud Text-to-Speech](https://cloud.google.com/text-to-speech/). ### Project - [Questions & community](https://github.com/pndurette/gTTS/discussions) - [Changelog](CHANGELOG.rst) - [Contributing](CONTRIBUTING.rst) ### Licence [The MIT License (MIT)](LICENSE) Copyright © 2014-2024 Pierre Nicolas Durette & [Contributors](https://github.com/pndurette/gTTS/graphs/contributors) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275870.0 gtts-2.5.4/gTTS.egg-info/SOURCES.txt0000644000175100001770000000143114714226136016314 0ustar00runnerdockerLICENSE README.md pyproject.toml gTTS.egg-info/PKG-INFO gTTS.egg-info/SOURCES.txt gTTS.egg-info/dependency_links.txt gTTS.egg-info/entry_points.txt gTTS.egg-info/requires.txt gTTS.egg-info/top_level.txt gtts/__init__.py gtts/accents.py gtts/cli.py gtts/lang.py gtts/langs.py gtts/tts.py gtts/utils.py gtts/version.py gtts/tests/__init__.py gtts/tests/test_cli.py gtts/tests/test_lang.py gtts/tests/test_tts.py gtts/tests/test_utils.py gtts/tests/input_files/test_cli_test_ascii.txt gtts/tests/input_files/test_cli_test_utf8.txt gtts/tokenizer/__init__.py gtts/tokenizer/core.py gtts/tokenizer/pre_processors.py gtts/tokenizer/symbols.py gtts/tokenizer/tokenizer_cases.py gtts/tokenizer/tests/test_core.py gtts/tokenizer/tests/test_pre_processors.py gtts/tokenizer/tests/test_tokenizer_cases.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275870.0 gtts-2.5.4/gTTS.egg-info/dependency_links.txt0000644000175100001770000000000114714226136020477 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275870.0 gtts-2.5.4/gTTS.egg-info/entry_points.txt0000644000175100001770000000005614714226136017730 0ustar00runnerdocker[console_scripts] gtts-cli = gtts.cli:tts_cli ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275870.0 gtts-2.5.4/gTTS.egg-info/requires.txt0000644000175100001770000000024714714226136017034 0ustar00runnerdockerrequests<3,>=2.27 click<8.2,>=7.1 [docs] sphinx sphinx-autobuild sphinx_rtd_theme sphinx-click sphinx-mdinclude [tests] pytest<8.4.0,>=7.1.3 pytest-cov testfixtures ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275870.0 gtts-2.5.4/gTTS.egg-info/top_level.txt0000644000175100001770000000000514714226136017156 0ustar00runnerdockergtts ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731275870.6662881 gtts-2.5.4/gtts/0000755000175100001770000000000014714226137013100 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/__init__.py0000644000175100001770000000023014714226132015177 0ustar00runnerdocker# -*- coding: utf-8 -*- from .version import __version__ # noqa: F401 from .tts import gTTS, gTTSError __all__ = ["__version__", "gTTS", "gTTSError"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/accents.py0000644000175100001770000000400214714226132015061 0ustar00runnerdocker# try adding these to the tld accents = [ "com", "ad", "ae", "com.af", "com.ag", "com.ai", "com.ar", "as", "at", "com.au", "az", "ba", "com.bd", "be", "bf", "bg", "bj", "br", "bs", "bt", "co.bw", "by", "com.bz", "ca", "cd", "ch", "ci", "co.ck", "cl", "cm", "cn", "com.co", "co.cr", "cv", "dj", "dm", "com.do", "dz", "com.ec", "ee", "com.eg", "es", "et", "fi", "com.fj", "fm", "fr", "ga", "ge", "gg", "com.gh", "com.gi", "gl", "gm", "gr", "com.gt", "gy", "com.hk", "hn", "ht", "hr", "hu", "co.id", "ie", "co.il", "im", "co.in", "iq", "is", "it", "iw", "je", "com.je", "jo", "co.jp", "co.ke", "com.kh", "ki", "kg", "co.kr", "com.kw", "kz", "la", "com.lb", "li", "lk", "co.ls", "lt", "lu", "lv", "com.ly", "com.ma", "md", "me", "mg", "mk", "ml", "mm", "mn", "ms", "com.mt", "mu", "mv", "mw", "com.mx", "com.my", "co.mz", "na", "ng", "ni", "ne", "nl", "no", "com.np", "nr", "nu", "co.nz", "com.om", "pa", "pe", "pg", "ph", "pk", "pl", "pn", "com.pr", "ps", "pt", "com.py", "com.qa", "ro", "ru", "rw", "com.sa", "com.sb", "sc", "se", "com.sg", "sh", "si", "sk", "com.sl", "sn", "so", "sm", "sr", "st", "com.sv", "td", "tg", "co.th", "com.tj", "tl", "tm", "tn", "to", "com.tr", "tt", "com.tw", "co.tz", "com.ua", "co.ug", "co.uk", "com,uy", "co.uz", "com.vc", "co.ve", "vg", "co.vi", "com.vn", "vu", "ws", "rs", "co.za", "co.zm", "co.zw", "cat", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/cli.py0000644000175100001770000001310114714226132014210 0ustar00runnerdocker# -*- coding: utf-8 -*- from gtts import gTTS, gTTSError, __version__ from gtts.lang import tts_langs, _fallback_deprecated_lang import click import logging import logging.config # Click settings CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]} # Logger settings LOGGER_SETTINGS = { "version": 1, "formatters": {"default": {"format": "%(name)s - %(levelname)s - %(message)s"}}, "handlers": {"console": {"class": "logging.StreamHandler", "formatter": "default"}}, "loggers": {"gtts": {"handlers": ["console"], "level": "WARNING"}}, } # Logger logging.config.dictConfig(LOGGER_SETTINGS) log = logging.getLogger("gtts") def sys_encoding(): """Charset to use for --file |- (stdin)""" return "utf8" def validate_text(ctx, param, text): """Validation callback for the argument. Ensures (arg) and (opt) are mutually exclusive """ if not text and "file" not in ctx.params: # No and no raise click.BadParameter(" or -f/--file required") if text and "file" in ctx.params: # Both and raise click.BadParameter(" and -f/--file can't be used together") return text def validate_lang(ctx, param, lang): """Validation callback for the option. Ensures is a supported language unless the flag is set """ if ctx.params["nocheck"]: return lang # Fallback from deprecated language if needed lang = _fallback_deprecated_lang(lang) try: if lang not in tts_langs(): raise click.UsageError( "'%s' not in list of supported languages.\n" "Use --all to list languages or " "add --nocheck to disable language check." % lang ) else: # The language is valid. # No need to let gTTS re-validate. ctx.params["nocheck"] = True except RuntimeError as e: # Only case where the flag can be False # Non-fatal. gTTS will try to re-validate. log.debug(str(e), exc_info=True) return lang def print_languages(ctx, param, value): """Callback for flag. Prints formatted sorted list of supported languages and exits """ if not value or ctx.resilient_parsing: return try: langs = tts_langs() langs_str_list = sorted("{}: {}".format(k, langs[k]) for k in langs) click.echo(" " + "\n ".join(langs_str_list)) except RuntimeError as e: # pragma: no cover log.debug(str(e), exc_info=True) raise click.ClickException("Couldn't fetch language list.") ctx.exit() def set_debug(ctx, param, debug): """Callback for flag. Sets logger level to DEBUG """ if debug: log.setLevel(logging.DEBUG) return @click.command(context_settings=CONTEXT_SETTINGS) @click.argument("text", metavar="", required=False, callback=validate_text) @click.option( "-f", "--file", metavar="", # For py2.7/unicode. If encoding not None Click uses io.open type=click.File(encoding=sys_encoding()), help="Read from instead of .", ) @click.option( "-o", "--output", metavar="", type=click.File(mode="wb"), help="Write to instead of stdout.", ) @click.option("-s", "--slow", default=False, is_flag=True, help="Read more slowly.") @click.option( "-l", "--lang", metavar="", default="en", show_default=True, callback=validate_lang, help="IETF language tag. Language to speak in. List documented tags with --all.", ) @click.option( "-t", "--tld", metavar="", default="com", show_default=True, is_eager=True, # Prioritize to ensure it gets set before help="Top-level domain for the Google host, i.e https://translate.google.", ) @click.option( "--nocheck", default=False, is_flag=True, is_eager=True, # Prioritize to ensure it gets set before help="Disable strict IETF language tag checking. Allow undocumented tags.", ) @click.option( "--all", default=False, is_flag=True, is_eager=True, expose_value=False, callback=print_languages, help="Print all documented available IETF language tags and exit.", ) @click.option( "--debug", default=False, is_flag=True, is_eager=True, # Prioritize to see debug logs of callbacks expose_value=False, callback=set_debug, help="Show debug information.", ) @click.version_option(version=__version__) def tts_cli(text, file, output, slow, tld, lang, nocheck): """Read to mp3 format using Google Translate's Text-to-Speech API (set or --file to - for standard input) """ # stdin for if text == "-": text = click.get_text_stream("stdin").read() # stdout (when no ) if not output: output = click.get_binary_stream("stdout") # input (stdin on '-' is handled by click.File) if file: try: text = file.read() except UnicodeDecodeError as e: # pragma: no cover log.debug(str(e), exc_info=True) raise click.FileError( file.name, " must be encoded using '%s'." % sys_encoding() ) # TTS try: tts = gTTS(text=text, lang=lang, slow=slow, tld=tld, lang_check=not nocheck) tts.write_to_fp(output) except (ValueError, AssertionError) as e: raise click.UsageError(str(e)) except gTTSError as e: raise click.ClickException(str(e)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/lang.py0000644000175100001770000000552314714226132014373 0ustar00runnerdocker# -*- coding: utf-8 -*- from gtts.langs import _main_langs from warnings import warn import logging __all__ = ["tts_langs"] # Logger log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) def tts_langs(): """Languages Google Text-to-Speech supports. Returns: dict: A dictionary of the type `{ '': ''}` Where `` is an IETF language tag such as `en` or `zh-TW`, and `` is the full English name of the language, such as `English` or `Chinese (Mandarin/Taiwan)`. The dictionary returned combines languages from two origins: - Languages fetched from Google Translate (pre-generated in :mod:`gtts.langs`) - Languages that are undocumented variations that were observed to work and present different dialects or accents. """ langs = dict() langs.update(_main_langs()) langs.update(_extra_langs()) log.debug("langs: {}".format(langs)) return langs def _extra_langs(): """Define extra languages. Returns: dict: A dictionary of extra languages manually defined. Variations of the ones generated in `_main_langs`, observed to provide different dialects or accents or just simply accepted by the Google Translate Text-to-Speech API. """ return { # Chinese "zh-TW": "Chinese (Mandarin/Taiwan)", "zh": "Chinese (Mandarin)", } def _fallback_deprecated_lang(lang): """Languages Google Text-to-Speech used to support. Language tags that don't work anymore, but that can fallback to a more general language code to maintain compatibility. Args: lang (string): The language tag. Returns: string: The language tag, as-is if not deprecated, or a fallback if it exits. Example: ``en-GB`` returns ``en``. ``en-gb`` returns ``en``. """ deprecated = { # '': [] "en": [ "en-us", "en-ca", "en-uk", "en-gb", "en-au", "en-gh", "en-in", "en-ie", "en-nz", "en-ng", "en-ph", "en-za", "en-tz", ], "fr": ["fr-ca", "fr-fr"], "pt": ["pt-br", "pt-pt"], "es": ["es-es", "es-us"], "zh-CN": ["zh-cn"], "zh-TW": ["zh-tw"], } for fallback_lang, deprecated_langs in deprecated.items(): if lang.lower() in deprecated_langs: msg = ( "'{}' has been deprecated, falling back to '{}'. " "This fallback will be removed in a future version." ).format(lang, fallback_lang) warn(msg, DeprecationWarning) log.warning(msg) return fallback_lang return lang ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/langs.py0000644000175100001770000000310314714226132014546 0ustar00runnerdocker# Note: this file is generated _langs = { "af": "Afrikaans", "am": "Amharic", "ar": "Arabic", "bg": "Bulgarian", "bn": "Bengali", "bs": "Bosnian", "ca": "Catalan", "cs": "Czech", "cy": "Welsh", "da": "Danish", "de": "German", "el": "Greek", "en": "English", "es": "Spanish", "et": "Estonian", "eu": "Basque", "fi": "Finnish", "fr": "French", "fr-CA": "French (Canada)", "gl": "Galician", "gu": "Gujarati", "ha": "Hausa", "hi": "Hindi", "hr": "Croatian", "hu": "Hungarian", "id": "Indonesian", "is": "Icelandic", "it": "Italian", "iw": "Hebrew", "ja": "Japanese", "jw": "Javanese", "km": "Khmer", "kn": "Kannada", "ko": "Korean", "la": "Latin", "lt": "Lithuanian", "lv": "Latvian", "ml": "Malayalam", "mr": "Marathi", "ms": "Malay", "my": "Myanmar (Burmese)", "ne": "Nepali", "nl": "Dutch", "no": "Norwegian", "pa": "Punjabi (Gurmukhi)", "pl": "Polish", "pt": "Portuguese (Brazil)", "pt-PT": "Portuguese (Portugal)", "ro": "Romanian", "ru": "Russian", "si": "Sinhala", "sk": "Slovak", "sq": "Albanian", "sr": "Serbian", "su": "Sundanese", "sv": "Swedish", "sw": "Swahili", "ta": "Tamil", "te": "Telugu", "th": "Thai", "tl": "Filipino", "tr": "Turkish", "uk": "Ukrainian", "ur": "Urdu", "vi": "Vietnamese", "yue": "Cantonese", "zh-CN": "Chinese (Simplified)", "zh-TW": "Chinese (Traditional)" } def _main_langs(): return _langs ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731275870.6662881 gtts-2.5.4/gtts/tests/0000755000175100001770000000000014714226137014242 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/__init__.py0000644000175100001770000000000014714226132016334 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731275870.6662881 gtts-2.5.4/gtts/tests/input_files/0000755000175100001770000000000014714226137016563 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/input_files/test_cli_test_ascii.txt0000644000175100001770000000027414714226132023337 0ustar00runnerdockerCan you make pink a little more pinkish can you make pink a little more pinkish, nor can you make the font bigger? How much will it cost the website doesn't have the theme i was going for.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/input_files/test_cli_test_utf8.txt0000644000175100001770000000035014714226132023130 0ustar00runnerdocker这是一个三岁的小孩 在讲述她从一系列照片里看到的东西。 对这个世界, 她也许还有很多要学的东西, 但在一个重要的任务上, 她已经是专家了: 去理解她所看到的东西。 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/test_cli.py0000644000175100001770000001561314714226132016423 0ustar00runnerdocker# -*- coding: utf-8 -*- import pytest import re import os from click.testing import CliRunner from gtts.cli import tts_cli # Need to look into gTTS' log output to test proper instantiation # - Use testfixtures.LogCapture() b/c TestCase.assertLogs() needs py3.4+ # - Clear 'gtts' logger handlers (set in gtts.cli) to reduce test noise import logging from testfixtures import LogCapture logger = logging.getLogger("gtts") logger.handlers = [] """Test options and arguments""" def runner(args, input=None): return CliRunner().invoke(tts_cli, args, input) def runner_debug(args, input=None): return CliRunner().invoke(tts_cli, args + ["--debug"], input) # tests def test_text_no_text_or_file(): """One of (arg) and should be set""" result = runner_debug([]) assert " required" in result.output assert result.exit_code != 0 def test_text_text_and_file(tmp_path): """ (arg) and should not be set together""" filename = tmp_path / "test_and_file.txt" filename.touch() result = runner_debug(["--file", str(filename), "test"]) assert " can't be used together" in result.output assert result.exit_code != 0 def test_text_empty(tmp_path): """Exit on no text to speak (via )""" filename = tmp_path / "text_empty.txt" filename.touch() result = runner_debug(["--file", str(filename)]) assert "No text to speak" in result.output assert result.exit_code != 0 # tests def test_file_not_exists(): """ should exist""" result = runner_debug(["--file", "notexist.txt", "test"]) assert "No such file or directory" in result.output assert result.exit_code != 0 # tests @pytest.mark.net def test_all(): """Option should return a list of languages""" result = runner(["--all"]) # One or more of " xy: name" (\n optional to match the last) # Ex. " xx: xxxxx\n xx-yy: xxxxx\n xx: xxxxx" assert re.match(r"(\s{2}[\w-]{2,5}: .+\n?)", result.output) assert result.exit_code == 0 # tests @pytest.mark.net def test_lang_not_valid(): """Invalid should display an error""" result = runner(["--lang", "xx", "test"]) assert "xx' not in list of supported languages" in result.output assert result.exit_code != 0 @pytest.mark.net def test_lang_nocheck(): """Invalid (with ) should display an error message from gtts""" with LogCapture() as lc: result = runner_debug(["--lang", "xx", "--nocheck", "test"]) log = str(lc) assert "lang: xx" in log assert "lang_check: False" in log assert "Unsupported language 'xx'" in result.output assert result.exit_code != 0 # Param set tests @pytest.mark.net def test_params_set(): """Options should set gTTS instance arguments (read from debug log)""" with LogCapture() as lc: result = runner_debug( ["--lang", "fr", "--tld", "es", "--slow", "--nocheck", "test"] ) log = str(lc) assert "lang: fr" in log assert "tld: es" in log assert "lang_check: False" in log assert "slow: True" in log assert "text: test" in log assert result.exit_code == 0 # Test all input methods pwd = os.path.dirname(__file__) # Text for stdin ('-' for or ) textstdin = """stdin test 123""" # Text for stdin ('-' for or ) (Unicode) textstdin_unicode = u"""你吃饭了吗? 你最喜欢哪部电影? 我饿了,我要去做饭了。""" # Text for and text = """Can you make pink a little more pinkish can you make pink a little more pinkish, nor can you make the font bigger? How much will it cost the website doesn't have the theme i was going for.""" textfile_ascii = os.path.join(pwd, "input_files", "test_cli_test_ascii.txt") # Text for and (Unicode) text_unicode = u"""这是一个三岁的小孩 在讲述她从一系列照片里看到的东西。 对这个世界, 她也许还有很多要学的东西, 但在一个重要的任务上, 她已经是专家了: 去理解她所看到的东西。""" textfile_utf8 = os.path.join(pwd, "input_files", "test_cli_test_utf8.txt") """ Method that mimics's LogCapture's __str__ method to make the string in the comprehension a unicode literal for P2.7 https://github.com/Simplistix/testfixtures/blob/32c87902cb111b7ede5a6abca9b597db551c88ef/testfixtures/logcapture.py#L149 """ def logcapture_str(lc): if not lc.records: return "No logging captured" return "\n".join([u"%s %s\n %s" % r for r in lc.actual()]) @pytest.mark.net def test_stdin_text(): with LogCapture() as lc: result = runner_debug(["-"], textstdin) log = logcapture_str(lc) assert "text: %s" % textstdin in log assert result.exit_code == 0 @pytest.mark.net def test_stdin_text_unicode(): with LogCapture() as lc: result = runner_debug(["-"], textstdin_unicode) log = logcapture_str(lc) assert "text: %s" % textstdin_unicode in log assert result.exit_code == 0 @pytest.mark.net def test_stdin_file(): with LogCapture() as lc: result = runner_debug(["--file", "-"], textstdin) log = logcapture_str(lc) assert "text: %s" % textstdin in log assert result.exit_code == 0 @pytest.mark.net def test_stdin_file_unicode(): with LogCapture() as lc: result = runner_debug(["--file", "-"], textstdin_unicode) log = logcapture_str(lc) assert "text: %s" % textstdin_unicode in log assert result.exit_code == 0 @pytest.mark.net def test_text(): with LogCapture() as lc: result = runner_debug([text]) log = logcapture_str(lc) assert "text: %s" % text in log assert result.exit_code == 0 @pytest.mark.net def test_text_unicode(): with LogCapture() as lc: result = runner_debug([text_unicode]) log = logcapture_str(lc) assert "text: %s" % text_unicode in log assert result.exit_code == 0 @pytest.mark.net def test_file_ascii(): with LogCapture() as lc: result = runner_debug(["--file", textfile_ascii]) log = logcapture_str(lc) assert "text: %s" % text in log assert result.exit_code == 0 @pytest.mark.net def test_file_utf8(): with LogCapture() as lc: result = runner_debug(["--file", textfile_utf8]) log = logcapture_str(lc) assert "text: %s" % text_unicode in log assert result.exit_code == 0 @pytest.mark.net def test_stdout(): result = runner(["test"]) # The MP3 encoding (LAME 3.99.5) used to leave a signature in the raw output # This no longer appears to be the case assert result.exit_code == 0 @pytest.mark.net def test_file(tmp_path): filename = tmp_path / "out.mp3" result = runner(["test", "--output", str(filename)]) # Check if files created is > 2k assert filename.stat().st_size > 2000 assert result.exit_code == 0 if __name__ == "__main__": pytest.main(["-x", __file__]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/test_lang.py0000644000175100001770000000111714714226132016567 0ustar00runnerdocker# -*- coding: utf-8 -*- import pytest from gtts.lang import tts_langs, _extra_langs, _fallback_deprecated_lang from gtts.langs import _main_langs """Test language list""" def test_main_langs(): """Fetch languages successfully""" # Safe to assume 'en' (English) will always be there scraped_langs = _main_langs() assert "en" in scraped_langs def test_deprecated_lang(): """Test language deprecation fallback""" with pytest.deprecated_call(): assert _fallback_deprecated_lang("en-gb") == "en" if __name__ == "__main__": pytest.main(["-x", __file__]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/test_tts.py0000644000175100001770000001340314714226132016461 0ustar00runnerdocker# -*- coding: utf-8 -*- import os import pytest from unittest.mock import Mock from gtts.tts import gTTS, gTTSError from gtts.langs import _main_langs from gtts.lang import _extra_langs # Testing all languages takes some time. # Set TEST_LANGS envvar to choose languages to test. # * 'main': Languages extracted from the Web # * 'extra': Language set in Languages.EXTRA_LANGS # * 'all': All of the above # * : Languages tags list to test # Unset TEST_LANGS to test everything ('all') # See: langs_dict() """Construct a dict of suites of languages to test. { '' : } ex.: { 'fetch' : {'en': 'English', 'fr': 'French'}, 'extra' : {'en': 'English', 'fr': 'French'} } ex.: { 'environ' : ['en', 'fr'] } """ env = os.environ.get("TEST_LANGS") if not env or env == "all": langs = _main_langs() langs.update(_extra_langs()) elif env == "main": langs = _main_langs() elif env == "extra": langs = _extra_langs() else: env_langs = {l: l for l in env.split(",") if l} langs = env_langs @pytest.mark.net @pytest.mark.parametrize("lang", langs.keys(), ids=list(langs.values())) def test_TTS(tmp_path, lang): """Test all supported languages and file save""" text = "This is a test" """Create output .mp3 file successfully""" for slow in (False, True): filename = tmp_path / "test_{}_.mp3".format(lang) # Create gTTS and save tts = gTTS(text=text, lang=lang, slow=slow, lang_check=False) tts.save(filename) # Check if files created is > 1.5 assert filename.stat().st_size > 1500 @pytest.mark.net def test_unsupported_language_check(): """Raise ValueError on unsupported language (with language check)""" lang = "xx" text = "Lorem ipsum" check = True with pytest.raises(ValueError): gTTS(text=text, lang=lang, lang_check=check) def test_empty_string(): """Raise AssertionError on empty string""" text = "" with pytest.raises(AssertionError): gTTS(text=text) def test_no_text_parts(tmp_path): """Raises AssertionError on no content to send to API (no text_parts)""" text = " ..,\n" with pytest.raises(AssertionError): filename = tmp_path / "no_content.txt" tts = gTTS(text=text) tts.save(filename) # Test write_to_fp()/save() cases not covered elsewhere in this file @pytest.mark.net def test_bad_fp_type(): """Raise TypeError if fp is not a file-like object (no .write())""" # Create gTTS and save tts = gTTS(text="test") with pytest.raises(TypeError): tts.write_to_fp(5) @pytest.mark.net def test_save(tmp_path): """Save .mp3 file successfully""" filename = tmp_path / "save.mp3" # Create gTTS and save tts = gTTS(text="test") tts.save(filename) # Check if file created is > 2k assert filename.stat().st_size > 2000 @pytest.mark.net def test_get_bodies(): """get request bodies list""" tts = gTTS(text="test", tld="com", lang="en") body = tts.get_bodies()[0] assert "test" in body # \"en\" url-encoded assert "%5C%22en%5C%22" in body def test_msg(): """Test gTTsError internal exception handling Set exception message successfully""" error1 = gTTSError("test") assert "test" == error1.msg error2 = gTTSError() assert error2.msg is None def test_infer_msg(): """Infer message successfully based on context""" # Without response: # Bad TLD ttsTLD = Mock(tld="invalid") errorTLD = gTTSError(tts=ttsTLD) assert ( errorTLD.msg == "Failed to connect. Probable cause: Host 'https://translate.google.invalid/' is not reachable" ) # With response: # 403 tts403 = Mock() response403 = Mock(status_code=403, reason="aaa") error403 = gTTSError(tts=tts403, response=response403) assert ( error403.msg == "403 (aaa) from TTS API. Probable cause: Bad token or upstream API changes" ) # 200 (and not lang_check) tts200 = Mock(lang="xx", lang_check=False) response404 = Mock(status_code=200, reason="bbb") error200 = gTTSError(tts=tts200, response=response404) assert ( error200.msg == "200 (bbb) from TTS API. Probable cause: No audio stream in response. Unsupported language 'xx'" ) # >= 500 tts500 = Mock() response500 = Mock(status_code=500, reason="ccc") error500 = gTTSError(tts=tts500, response=response500) assert ( error500.msg == "500 (ccc) from TTS API. Probable cause: Upstream API error. Try again later." ) # Unknown (ex. 100) tts100 = Mock() response100 = Mock(status_code=100, reason="ddd") error100 = gTTSError(tts=tts100, response=response100) assert error100.msg == "100 (ddd) from TTS API. Probable cause: Unknown" @pytest.mark.net def test_WebRequest(tmp_path): """Test Web Requests""" text = "Lorem ipsum" """Raise gTTSError on unsupported language (without language check)""" lang = "xx" check = False with pytest.raises(gTTSError): filename = tmp_path / "xx.txt" # Create gTTS tts = gTTS(text=text, lang=lang, lang_check=check) tts.save(filename) @pytest.mark.net def test_timeout(tmp_path): # Check default timeout tts = gTTS(text="test") assert tts.timeout is None # Check passed in timeout timeout = 1.2 tts = gTTS(text="test", timeout=timeout) assert tts.timeout == timeout # Make sure an exception is raised when a timeout occurs tts = gTTS(text="test", timeout=0.000001) filename = tmp_path / "save.mp3" with pytest.raises(gTTSError): tts.save(filename) if __name__ == "__main__": pytest.main(["-x", __file__]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tests/test_utils.py0000644000175100001770000000302114714226132017002 0ustar00runnerdocker# -*- coding: utf-8 -*- import pytest from gtts.utils import _minimize, _clean_tokens, _translate_url delim = " " Lmax = 10 def test_ascii(): _in = "Bacon ipsum dolor sit amet" _out = ["Bacon", "ipsum", "dolor sit", "amet"] assert _minimize(_in, delim, Lmax) == _out def test_ascii_no_delim(): _in = "Baconipsumdolorsitametflankcornedbee" _out = ["Baconipsum", "dolorsitam", "etflankcor", "nedbee"] assert _minimize(_in, delim, Lmax) == _out def test_unicode(): _in = u"这是一个三岁的小孩在讲述他从一系列照片里看到的东西。" _out = [u"这是一个三岁的小孩在", u"讲述他从一系列照片里", u"看到的东西。"] assert _minimize(_in, delim, Lmax) == _out def test_startwith_delim(): _in = delim + "test" _out = ["test"] assert _minimize(_in, delim, Lmax) == _out def test_len_ascii(): text = "Bacon ipsum dolor sit amet flank corned beef." assert len(text) == 45 def test_len_unicode(): text = u"但在一个重要的任务上" assert len(text) == 10 def test_only_space_and_punc(): _in = [",(:)?", "\t ", "\n"] _out = [] assert _clean_tokens(_in) == _out def test_strip(): _in = [" Bacon ", "& ", "ipsum\r", "."] _out = ["Bacon", "&", "ipsum"] assert _clean_tokens(_in) == _out def test_translate_url(): _in = {"tld": "qwerty", "path": "asdf"} _out = "https://translate.google.qwerty/asdf" assert _translate_url(**_in) == _out if __name__ == "__main__": pytest.main(["-x", __file__]) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731275870.6662881 gtts-2.5.4/gtts/tokenizer/0000755000175100001770000000000014714226137015112 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/__init__.py0000644000175100001770000000021014714226132017207 0ustar00runnerdocker# -*- coding: utf-8 -* from .core import ( RegexBuilder, PreProcessorRegex, PreProcessorSub, Tokenizer, ) # noqa: F401 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/core.py0000644000175100001770000002523114714226132016412 0ustar00runnerdocker# -*- coding: utf-8 -*- import re class RegexBuilder: r"""Builds regex using arguments passed into a pattern template. Builds a regex object for which the pattern is made from an argument passed into a template. If more than one argument is passed (iterable), each pattern is joined by "|" (regex alternation 'or') to create a single pattern. Args: pattern_args (iterable): String element(s) to be each passed to ``pattern_func`` to create a regex pattern. Each element is ``re.escape``'d before being passed. pattern_func (callable): A 'template' function that should take a string and return a string. It should take an element of ``pattern_args`` and return a valid regex pattern group string. flags: ``re`` flag(s) to compile with the regex. Example: To create a simple regex that matches on the characters "a", "b", or "c", followed by a period:: >>> rb = RegexBuilder('abc', lambda x: "{}\.".format(x)) Looking at ``rb.regex`` we get the following compiled regex:: >>> print(rb.regex) 'a\.|b\.|c\.' The above is fairly simple, but this class can help in writing more complex repetitive regex, making them more readable and easier to create by using existing data structures. Example: To match the character following the words "lorem", "ipsum", "meili" or "koda":: >>> words = ['lorem', 'ipsum', 'meili', 'koda'] >>> rb = RegexBuilder(words, lambda x: "(?<={}).".format(x)) Looking at ``rb.regex`` we get the following compiled regex:: >>> print(rb.regex) '(?<=lorem).|(?<=ipsum).|(?<=meili).|(?<=koda).' """ def __init__(self, pattern_args, pattern_func, flags=0): self.pattern_args = pattern_args self.pattern_func = pattern_func self.flags = flags # Compile self.regex = self._compile() def _compile(self): alts = [] for arg in self.pattern_args: arg = re.escape(arg) alt = self.pattern_func(arg) alts.append(alt) pattern = "|".join(alts) return re.compile(pattern, self.flags) def __repr__(self): # pragma: no cover return str(self.regex) class PreProcessorRegex: r"""Regex-based substitution text pre-processor. Runs a series of regex substitutions (``re.sub``) from each ``regex`` of a :class:`gtts.tokenizer.core.RegexBuilder` with an extra ``repl`` replacement parameter. Args: search_args (iterable): String element(s) to be each passed to ``search_func`` to create a regex pattern. Each element is ``re.escape``'d before being passed. search_func (callable): A 'template' function that should take a string and return a string. It should take an element of ``search_args`` and return a valid regex search pattern string. repl (string): The common replacement passed to the ``sub`` method for each ``regex``. Can be a raw string (the case of a regex backreference, for example) flags: ``re`` flag(s) to compile with each `regex`. Example: Add "!" after the words "lorem" or "ipsum", while ignoring case:: >>> import re >>> words = ['lorem', 'ipsum'] >>> pp = PreProcessorRegex(words, ... lambda x: "({})".format(x), r'\\1!', ... re.IGNORECASE) In this case, the regex is a group and the replacement uses its backreference ``\\1`` (as a raw string). Looking at ``pp`` we get the following list of search/replacement pairs:: >>> print(pp) (re.compile('(lorem)', re.IGNORECASE), repl='\1!'), (re.compile('(ipsum)', re.IGNORECASE), repl='\1!') It can then be run on any string of text:: >>> pp.run("LOREM ipSuM") "LOREM! ipSuM!" See :mod:`gtts.tokenizer.pre_processors` for more examples. """ def __init__(self, search_args, search_func, repl, flags=0): self.repl = repl # Create regex list self.regexes = [] for arg in search_args: rb = RegexBuilder([arg], search_func, flags) self.regexes.append(rb.regex) def run(self, text): """Run each regex substitution on ``text``. Args: text (string): the input text. Returns: string: text after all substitutions have been sequentially applied. """ for regex in self.regexes: text = regex.sub(self.repl, text) return text def __repr__(self): # pragma: no cover subs_strs = [] for r in self.regexes: subs_strs.append("({}, repl='{}')".format(r, self.repl)) return ", ".join(subs_strs) class PreProcessorSub: r"""Simple substitution text preprocessor. Performs string-for-string substitution from list a find/replace pairs. It abstracts :class:`gtts.tokenizer.core.PreProcessorRegex` with a default simple substitution regex. Args: sub_pairs (list): A list of tuples of the style ``(, )`` ignore_case (bool): Ignore case during search. Defaults to ``True``. Example: Replace all occurrences of "Mac" to "PC" and "Firefox" to "Chrome":: >>> sub_pairs = [('Mac', 'PC'), ('Firefox', 'Chrome')] >>> pp = PreProcessorSub(sub_pairs) Looking at the ``pp``, we get the following list of search (regex)/replacement pairs:: >>> print(pp) (re.compile('Mac', re.IGNORECASE), repl='PC'), (re.compile('Firefox', re.IGNORECASE), repl='Chrome') It can then be run on any string of text:: >>> pp.run("I use firefox on my mac") "I use Chrome on my PC" See :mod:`gtts.tokenizer.pre_processors` for more examples. """ def __init__(self, sub_pairs, ignore_case=True): def search_func(x): return u"{}".format(x) flags = re.I if ignore_case else 0 # Create pre-processor list self.pre_processors = [] for sub_pair in sub_pairs: pattern, repl = sub_pair pp = PreProcessorRegex([pattern], search_func, repl, flags) self.pre_processors.append(pp) def run(self, text): """Run each substitution on ``text``. Args: text (string): the input text. Returns: string: text after all substitutions have been sequentially applied. """ for pp in self.pre_processors: text = pp.run(text) return text def __repr__(self): # pragma: no cover return ", ".join([str(pp) for pp in self.pre_processors]) class Tokenizer: r"""An extensible but simple generic rule-based tokenizer. A generic and simple string tokenizer that takes a list of functions (called `tokenizer cases`) returning ``regex`` objects and joins them by "|" (regex alternation 'or') to create a single regex to use with the standard ``regex.split()`` function. ``regex_funcs`` is a list of any function that can return a ``regex`` (from ``re.compile()``) object, such as a :class:`gtts.tokenizer.core.RegexBuilder` instance (and its ``regex`` attribute). See the :mod:`gtts.tokenizer.tokenizer_cases` module for examples. Args: regex_funcs (list): List of compiled ``regex`` objects. Each function's pattern will be joined into a single pattern and compiled. flags: ``re`` flag(s) to compile with the final regex. Defaults to ``re.IGNORECASE`` Note: When the ``regex`` objects obtained from ``regex_funcs`` are joined, their individual ``re`` flags are ignored in favour of ``flags``. Raises: TypeError: When an element of ``regex_funcs`` is not a function, or a function that does not return a compiled ``regex`` object. Warning: Joined ``regex`` patterns can easily interfere with one another in unexpected ways. It is recommended that each tokenizer case operate on distinct or non-overlapping characters/sets of characters (For example, a tokenizer case for the period (".") should also handle not matching/cutting on decimals, instead of making that a separate tokenizer case). Example: A tokenizer with a two simple case (*Note: these are bad cases to tokenize on, this is simply a usage example*):: >>> import re, RegexBuilder >>> >>> def case1(): ... return re.compile("\,") >>> >>> def case2(): ... return RegexBuilder('abc', lambda x: "{}\.".format(x)).regex >>> >>> t = Tokenizer([case1, case2]) Looking at ``case1().pattern``, we get:: >>> print(case1().pattern) '\\,' Looking at ``case2().pattern``, we get:: >>> print(case2().pattern) 'a\\.|b\\.|c\\.' Finally, looking at ``t``, we get them combined:: >>> print(t) 're.compile('\\,|a\\.|b\\.|c\\.', re.IGNORECASE) from: [, ]' It can then be run on any string of text:: >>> t.run("Hello, my name is Linda a. Call me Lin, b. I'm your friend") ['Hello', ' my name is Linda ', ' Call me Lin', ' ', " I'm your friend"] """ def __init__(self, regex_funcs, flags=re.IGNORECASE): self.regex_funcs = regex_funcs self.flags = flags try: # Combine self.total_regex = self._combine_regex() except (TypeError, AttributeError) as e: # pragma: no cover raise TypeError( "Tokenizer() expects a list of functions returning " "regular expression objects (i.e. re.compile). " + str(e) ) def _combine_regex(self): alts = [] for func in self.regex_funcs: alts.append(func()) pattern = "|".join(alt.pattern for alt in alts) return re.compile(pattern, self.flags) def run(self, text): """Tokenize `text`. Args: text (string): the input text to tokenize. Returns: list: A list of strings (token) split according to the tokenizer cases. """ return self.total_regex.split(text) def __repr__(self): # pragma: no cover return str(self.total_regex) + " from: " + str(self.regex_funcs) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/pre_processors.py0000644000175100001770000000305314714226132020530 0ustar00runnerdocker# -*- coding: utf-8 -*- from gtts.tokenizer import PreProcessorRegex, PreProcessorSub, symbols import re def tone_marks(text): """Add a space after tone-modifying punctuation. Because the `tone_marks` tokenizer case will split after a tone-modifying punctuation mark, make sure there's whitespace after. """ return PreProcessorRegex( search_args=symbols.TONE_MARKS, search_func=lambda x: u"(?<={})".format(x), repl=" ", ).run(text) def end_of_line(text): """Re-form words cut by end-of-line hyphens. Remove "". """ return PreProcessorRegex( search_args="-", search_func=lambda x: u"{}\n".format(x), repl="" ).run(text) def abbreviations(text): """Remove periods after an abbreviation from a list of known abbreviations that can be spoken the same without that period. This prevents having to handle tokenization of that period. Note: Could potentially remove the ending period of a sentence. Note: Abbreviations that Google Translate can't pronounce without (or even with) a period should be added as a word substitution with a :class:`PreProcessorSub` pre-processor. Ex.: 'Esq.', 'Esquire'. """ return PreProcessorRegex( search_args=symbols.ABBREVIATIONS, search_func=lambda x: r"(?<={})(?=\.).".format(x), repl="", flags=re.IGNORECASE, ).run(text) def word_sub(text): """Word-for-word substitutions.""" return PreProcessorSub(sub_pairs=symbols.SUB_PAIRS).run(text) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/symbols.py0000644000175100001770000000040214714226132017143 0ustar00runnerdocker# -*- coding: utf-8 -*- ABBREVIATIONS = ["dr", "jr", "mr", "mrs", "ms", "msgr", "prof", "sr", "st"] SUB_PAIRS = [("Esq.", "Esquire")] ALL_PUNC = u"?!?!.,¡()[]¿…‥،;:—。,、:\n" TONE_MARKS = u"?!?!" PERIOD_COMMA = ".," COLON = u":" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731275870.6662881 gtts-2.5.4/gtts/tokenizer/tests/0000755000175100001770000000000014714226137016254 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/tests/test_core.py0000644000175100001770000000416214714226132020613 0ustar00runnerdocker# -*- coding: utf-8 -*- import unittest import re from gtts.tokenizer.core import ( RegexBuilder, PreProcessorRegex, PreProcessorSub, Tokenizer, ) # Tests based on classes usage examples # See class documentation for details class TestRegexBuilder(unittest.TestCase): def test_regexbuilder(self): rb = RegexBuilder("abc", lambda x: "{}".format(x)) self.assertEqual(rb.regex, re.compile("a|b|c")) class TestPreProcessorRegex(unittest.TestCase): def test_preprocessorregex(self): pp = PreProcessorRegex("ab", lambda x: "{}".format(x), "c") self.assertEqual(len(pp.regexes), 2) self.assertEqual(pp.regexes[0].pattern, "a") self.assertEqual(pp.regexes[1].pattern, "b") class TestPreProcessorSub(unittest.TestCase): def test_proprocessorsub(self): sub_pairs = [("Mac", "PC"), ("Firefox", "Chrome")] pp = PreProcessorSub(sub_pairs) _in = "I use firefox on my mac" _out = "I use Chrome on my PC" self.assertEqual(pp.run(_in), _out) class TestTokenizer(unittest.TestCase): # tokenizer case 1 def case1(self): return re.compile(r"\,") # tokenizer case 2 def case2(self): return RegexBuilder("abc", lambda x: r"{}\.".format(x)).regex def test_tokenizer(self): t = Tokenizer([self.case1, self.case2]) _in = "Hello, my name is Linda a. Call me Lin, b. I'm your friend" _out = ["Hello", " my name is Linda ", " Call me Lin", " ", " I'm your friend"] self.assertEqual(t.run(_in), _out) def test_bad_params_not_list(self): # original exception: TypeError with self.assertRaises(TypeError): Tokenizer(self.case1) def test_bad_params_not_callable(self): # original exception: TypeError with self.assertRaises(TypeError): Tokenizer([100]) def test_bad_params_not_callable_returning_regex(self): # original exception: AttributeError def not_regex(): return 1 with self.assertRaises(TypeError): Tokenizer([not_regex]) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/tests/test_pre_processors.py0000644000175100001770000000143614714226132022734 0ustar00runnerdocker# -*- coding: utf-8 -*- import unittest from gtts.tokenizer.pre_processors import ( tone_marks, end_of_line, abbreviations, word_sub, ) class TestPreProcessors(unittest.TestCase): def test_tone_marks(self): _in = "lorem!ipsum?" _out = "lorem! ipsum? " self.assertEqual(tone_marks(_in), _out) def test_end_of_line(self): _in = """test- ing""" _out = "testing" self.assertEqual(end_of_line(_in), _out) def test_abbreviations(self): _in = "jr. sr. dr." _out = "jr sr dr" self.assertEqual(abbreviations(_in), _out) def test_word_sub(self): _in = "Esq. Bacon" _out = "Esquire Bacon" self.assertEqual(word_sub(_in), _out) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/tests/test_tokenizer_cases.py0000644000175100001770000000306414714226132023053 0ustar00runnerdocker# -*- coding: utf-8 -*- import unittest from gtts.tokenizer.tokenizer_cases import ( tone_marks, period_comma, colon, other_punctuation, legacy_all_punctuation, ) from gtts.tokenizer import Tokenizer, symbols class TestPreTokenizerCases(unittest.TestCase): def test_tone_marks(self): t = Tokenizer([tone_marks]) _in = "Lorem? Ipsum!" _out = ["Lorem?", "Ipsum!"] self.assertEqual(t.run(_in), _out) def test_period_comma(self): t = Tokenizer([period_comma]) _in = "Hello, it's 24.5 degrees in the U.K. today. $20,000,000." _out = ["Hello", "it's 24.5 degrees in the U.K. today", "$20,000,000."] self.assertEqual(t.run(_in), _out) def test_colon(self): t = Tokenizer([colon]) _in = "It's now 6:30 which means: morning missing:space" _out = ["It's now 6:30 which means", " morning missing", "space"] self.assertEqual(t.run(_in), _out) def test_other_punctuation(self): # String of the unique 'other punctuations' other_punc_str = "".join( set(symbols.ALL_PUNC) - set(symbols.TONE_MARKS) - set(symbols.PERIOD_COMMA) - set(symbols.COLON) ) t = Tokenizer([other_punctuation]) self.assertEqual(len(t.run(other_punc_str)) - 1, len(other_punc_str)) def test_legacy_all_punctuation(self): t = Tokenizer([legacy_all_punctuation]) self.assertEqual(len(t.run(symbols.ALL_PUNC)) - 1, len(symbols.ALL_PUNC)) if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/tokenizer/tokenizer_cases.py0000644000175100001770000000364214714226132020654 0ustar00runnerdocker# -*- coding: utf-8 -*- from gtts.tokenizer import RegexBuilder, symbols def tone_marks(): """Keep tone-modifying punctuation by matching following character. Assumes the `tone_marks` pre-processor was run for cases where there might not be any space after a tone-modifying punctuation mark. """ return RegexBuilder( pattern_args=symbols.TONE_MARKS, pattern_func=lambda x: u"(?<={}).".format(x) ).regex def period_comma(): """Period and comma case. Match if not preceded by "." and only if followed by space. Won't cut in the middle/after dotted abbreviations; won't cut numbers. Note: Won't match if a dotted abbreviation ends a sentence. Note: Won't match the end of a sentence if not followed by a space. """ return RegexBuilder( pattern_args=symbols.PERIOD_COMMA, pattern_func=lambda x: r"(?`. Different Google domains can produce different localized 'accents' for a given language. This is also useful when ``google.com`` might be blocked within a network but a local or different Google host (e.g. ``google.com.hk``) is not. Default is ``com``. lang (string, optional): The language (IETF language tag) to read the text in. Default is ``en``. slow (bool, optional): Reads text more slowly. Defaults to ``False``. lang_check (bool, optional): Strictly enforce an existing ``lang``, to catch a language error early. If set to ``True``, a ``ValueError`` is raised if ``lang`` doesn't exist. Setting ``lang_check`` to ``False`` skips Web requests (to validate language) and therefore speeds up instantiation. Default is ``True``. pre_processor_funcs (list): A list of zero or more functions that are called to transform (pre-process) text before tokenizing. Those functions must take a string and return a string. Defaults to:: [ pre_processors.tone_marks, pre_processors.end_of_line, pre_processors.abbreviations, pre_processors.word_sub ] tokenizer_func (callable): A function that takes in a string and returns a list of string (tokens). Defaults to:: Tokenizer([ tokenizer_cases.tone_marks, tokenizer_cases.period_comma, tokenizer_cases.colon, tokenizer_cases.other_punctuation ]).run timeout (float or tuple, optional): Seconds to wait for the server to send data before giving up, as a float, or a ``(connect timeout, read timeout)`` tuple. ``None`` will wait forever (default). See Also: :doc:`Pre-processing and tokenizing ` Raises: AssertionError: When ``text`` is ``None`` or empty; when there's nothing left to speak after pre-processing, tokenizing and cleaning. ValueError: When ``lang_check`` is ``True`` and ``lang`` is not supported. RuntimeError: When ``lang_check`` is ``True`` but there's an error loading the languages dictionary. """ GOOGLE_TTS_MAX_CHARS = 100 # Max characters the Google TTS API takes at a time GOOGLE_TTS_HEADERS = { "Referer": "http://translate.google.com/", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/47.0.2526.106 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", } GOOGLE_TTS_RPC = "jQ1olc" def __init__( self, text, tld="com", lang="en", slow=False, lang_check=True, pre_processor_funcs=[ pre_processors.tone_marks, pre_processors.end_of_line, pre_processors.abbreviations, pre_processors.word_sub, ], tokenizer_func=Tokenizer( [ tokenizer_cases.tone_marks, tokenizer_cases.period_comma, tokenizer_cases.colon, tokenizer_cases.other_punctuation, ] ).run, timeout=None, ): # Debug for k, v in dict(locals()).items(): if k == "self": continue log.debug("%s: %s", k, v) # Text assert text, "No text to speak" self.text = text # Translate URL top-level domain self.tld = tld # Language self.lang_check = lang_check self.lang = lang if self.lang_check: # Fallback lang in case it is deprecated self.lang = _fallback_deprecated_lang(lang) try: langs = tts_langs() if self.lang not in langs: raise ValueError("Language not supported: %s" % lang) except RuntimeError as e: log.debug(str(e), exc_info=True) log.warning(str(e)) # Read speed if slow: self.speed = Speed.SLOW else: self.speed = Speed.NORMAL # Pre-processors and tokenizer self.pre_processor_funcs = pre_processor_funcs self.tokenizer_func = tokenizer_func self.timeout = timeout def _tokenize(self, text): # Pre-clean text = text.strip() # Apply pre-processors for pp in self.pre_processor_funcs: log.debug("pre-processing: %s", pp) text = pp(text) if len(text) <= self.GOOGLE_TTS_MAX_CHARS: return _clean_tokens([text]) # Tokenize log.debug("tokenizing: %s", self.tokenizer_func) tokens = self.tokenizer_func(text) # Clean tokens = _clean_tokens(tokens) # Minimize min_tokens = [] for t in tokens: min_tokens += _minimize(t, " ", self.GOOGLE_TTS_MAX_CHARS) # Filter empty tokens, post-minimize tokens = [t for t in min_tokens if t] return tokens def _prepare_requests(self): """Created the TTS API the request(s) without sending them. Returns: list: ``requests.PreparedRequests_``. `_``. """ # TTS API URL translate_url = _translate_url( tld=self.tld, path="_/TranslateWebserverUi/data/batchexecute" ) text_parts = self._tokenize(self.text) log.debug("text_parts: %s", str(text_parts)) log.debug("text_parts: %i", len(text_parts)) assert text_parts, "No text to send to TTS API" prepared_requests = [] for idx, part in enumerate(text_parts): data = self._package_rpc(part) log.debug("data-%i: %s", idx, data) # Request r = requests.Request( method="POST", url=translate_url, data=data, headers=self.GOOGLE_TTS_HEADERS, ) # Prepare request prepared_requests.append(r.prepare()) return prepared_requests def _package_rpc(self, text): parameter = [text, self.lang, self.speed, "null"] escaped_parameter = json.dumps(parameter, separators=(",", ":")) rpc = [[[self.GOOGLE_TTS_RPC, escaped_parameter, None, "generic"]]] espaced_rpc = json.dumps(rpc, separators=(",", ":")) return "f.req={}&".format(urllib.parse.quote(espaced_rpc)) def get_bodies(self): """Get TTS API request bodies(s) that would be sent to the TTS API. Returns: list: A list of TTS API request bodies to make. """ return [pr.body for pr in self._prepare_requests()] def stream(self): """Do the TTS API request(s) and stream bytes Raises: :class:`gTTSError`: When there's an error with the API request. """ # When disabling ssl verify in requests (for proxies and firewalls), # urllib3 prints an insecure warning on stdout. We disable that. try: requests.packages.urllib3.disable_warnings( requests.packages.urllib3.exceptions.InsecureRequestWarning ) except: pass prepared_requests = self._prepare_requests() for idx, pr in enumerate(prepared_requests): try: with requests.Session() as s: # Send request r = s.send( request=pr, verify=False, proxies=urllib.request.getproxies(), timeout=self.timeout, ) log.debug("headers-%i: %s", idx, r.request.headers) log.debug("url-%i: %s", idx, r.request.url) log.debug("status-%i: %s", idx, r.status_code) r.raise_for_status() except requests.exceptions.HTTPError as e: # pragma: no cover # Request successful, bad response log.debug(str(e)) raise gTTSError(tts=self, response=r) except requests.exceptions.RequestException as e: # pragma: no cover # Request failed log.debug(str(e)) raise gTTSError(tts=self) # Write for line in r.iter_lines(chunk_size=1024): decoded_line = line.decode("utf-8") if "jQ1olc" in decoded_line: audio_search = re.search(r'jQ1olc","\[\\"(.*)\\"]', decoded_line) if audio_search: as_bytes = audio_search.group(1).encode("ascii") yield base64.b64decode(as_bytes) else: # Request successful, good response, # no audio stream in response raise gTTSError(tts=self, response=r) log.debug("part-%i created", idx) def write_to_fp(self, fp): """Do the TTS API request(s) and write bytes to a file-like object. Args: fp (file object): Any file-like object to write the ``mp3`` to. Raises: :class:`gTTSError`: When there's an error with the API request. TypeError: When ``fp`` is not a file-like object that takes bytes. """ try: for idx, decoded in enumerate(self.stream()): fp.write(decoded) log.debug("part-%i written to %s", idx, fp) except (AttributeError, TypeError) as e: raise TypeError( "'fp' is not a file-like object or it does not take bytes: %s" % str(e) ) def save(self, savefile): """Do the TTS API request and write result to file. Args: savefile (string): The path and file name to save the ``mp3`` to. Raises: :class:`gTTSError`: When there's an error with the API request. """ with open(str(savefile), "wb") as f: self.write_to_fp(f) f.flush() log.debug("Saved to %s", savefile) class gTTSError(Exception): """Exception that uses context to present a meaningful error message""" def __init__(self, msg=None, **kwargs): self.tts = kwargs.pop("tts", None) self.rsp = kwargs.pop("response", None) if msg: self.msg = msg elif self.tts is not None: self.msg = self.infer_msg(self.tts, self.rsp) else: self.msg = None super(gTTSError, self).__init__(self.msg) def infer_msg(self, tts, rsp=None): """Attempt to guess what went wrong by using known information (e.g. http response) and observed behaviour """ cause = "Unknown" if rsp is None: premise = "Failed to connect" if tts.tld != "com": host = _translate_url(tld=tts.tld) cause = "Host '{}' is not reachable".format(host) else: # rsp should be # http://docs.python-requests.org/en/master/api/ status = rsp.status_code reason = rsp.reason premise = "{:d} ({}) from TTS API".format(status, reason) if status == 403: cause = "Bad token or upstream API changes" elif status == 404 and tts.tld != "com": cause = "Unsupported tld '{}'".format(tts.tld) elif status == 200 and not tts.lang_check: cause = ( "No audio stream in response. Unsupported language '%s'" % self.tts.lang ) elif status >= 500: cause = "Upstream API error. Try again later." return "{}. Probable cause: {}".format(premise, cause) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/utils.py0000644000175100001770000000573414714226132014616 0ustar00runnerdocker# -*- coding: utf-8 -*- from gtts.tokenizer.symbols import ALL_PUNC as punc from string import whitespace as ws import re _ALL_PUNC_OR_SPACE = re.compile(u"^[{}]*$".format(re.escape(punc + ws))) """Regex that matches if an entire line is only comprised of whitespace and punctuation """ def _minimize(the_string, delim, max_size): """Recursively split a string in the largest chunks possible from the highest position of a delimiter all the way to a maximum size Args: the_string (string): The string to split. delim (string): The delimiter to split on. max_size (int): The maximum size of a chunk. Returns: list: the minimized string in tokens Every chunk size will be at minimum ``the_string[0:idx]`` where ``idx`` is the highest index of ``delim`` found in ``the_string``; and at maximum ``the_string[0:max_size]`` if no ``delim`` was found in ``the_string``. In the latter case, the split will occur at ``the_string[max_size]`` which can be any character. The function runs itself again on the rest of ``the_string`` (``the_string[idx:]``) until no chunk is larger than ``max_size``. """ # Remove `delim` from start of `the_string` # i.e. prevent a recursive infinite loop on `the_string[0:0]` # if `the_string` starts with `delim` and is larger than `max_size` if the_string.startswith(delim): the_string = the_string[len(delim):] if len(the_string) > max_size: try: # Find the highest index of `delim` in `the_string[0:max_size]` # i.e. `the_string` will be cut in half on `delim` index idx = the_string.rindex(delim, 0, max_size) except ValueError: # `delim` not found in `the_string`, index becomes `max_size` # i.e. `the_string` will be cut in half arbitrarily on `max_size` idx = max_size # Call itself again for `the_string[idx:]` return [the_string[:idx]] + _minimize(the_string[idx:], delim, max_size) else: return [the_string] def _clean_tokens(tokens): """Clean a list of strings Args: tokens (list): A list of strings (tokens) to clean. Returns: list: Stripped strings ``tokens`` without the original elements that only consisted of whitespace and/or punctuation characters. """ return [t.strip() for t in tokens if not _ALL_PUNC_OR_SPACE.match(t)] def _translate_url(tld="com", path=""): """Generates a Google Translate URL Args: tld (string): Top-level domain for the Google Translate host, i.e ``https://translate.google.``. Default is ``com``. path: (string): A path to append to the Google Translate host, i.e ``https://translate.google.com/``. Default is ``""``. Returns: string: A Google Translate URL `https://translate.google./path` """ _GOOGLE_TTS_URL = "https://translate.google.{}/{}" return _GOOGLE_TTS_URL.format(tld, path) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/gtts/version.py0000644000175100001770000000002614714226132015130 0ustar00runnerdocker__version__ = "2.5.4" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1731275866.0 gtts-2.5.4/pyproject.toml0000644000175100001770000000505314714226132015031 0ustar00runnerdocker[project] name = "gTTS" version = "2.5.4" description = "gTTS (Google Text-to-Speech), a Python library and CLI tool to interface with Google Translate text-to-speech API" authors = [{name = "Pierre Nicolas Durette", email = "pndurette@gmail.com"}] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"} keywords = [ "gtts", "text to speech", "Google Translate", "TTS", ] classifiers = [ "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Unix", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries", "Topic :: Multimedia :: Sound/Audio :: Speech", ] dependencies = [ "requests >=2.27, <3", # https://docs.python-requests.org/en/latest/community/updates/ "click >=7.1, <8.2", # https://click.palletsprojects.com/en/latest/changes/ ] # TODO: release-please [yet] doesn't support dynamic version for pyproject.toml # https://github.com/googleapis/release-please/blob/909d310defdf24adfd4858bbe1604668c14ef77f/src/updaters/python/pyproject-toml.ts#L54 # dynamic = ["version"] # [tool.setuptools.dynamic] # version = {attr = "gtts.version.__version__"} [project.optional-dependencies] tests = [ "pytest >= 7.1.3,< 8.4.0", "pytest-cov", "testfixtures", ] docs = [ "sphinx", "sphinx-autobuild", "sphinx_rtd_theme", "sphinx-click", "sphinx-mdinclude" ] [project.scripts] gtts-cli = "gtts.cli:tts_cli" [project.urls] homepage = "https://github.com/pndurette/gTTS" documentation = "https://gtts.readthedocs.io" repository = "https://github.com/pndurette/gTTS" changelog = "https://github.com/pndurette/gTTS/blob/main/CHANGELOG.md" [tool.setuptools.package-data] # Tests support files "gtts.tests.input_files" = ["*.txt"] [tool.coverage.run] omit = [ "gtts/tests/*", "gtts/tokenizer/tests/*", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "log.debug", "log.warning", ] [tool.pytest.ini_options] markers = ["net: marks tests that call use the net (using the URL endpoint, deselect with '-m \"not net\"')"] [build-system] requires = ["setuptools>=61", "wheel"] build-backend = "setuptools.build_meta" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1731275870.670288 gtts-2.5.4/setup.cfg0000644000175100001770000000004614714226137013740 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0