# -*- 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))