cli.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. # -*- coding: utf-8 -*-
  2. from gtts import gTTS, gTTSError, __version__
  3. from gtts.lang import tts_langs, _fallback_deprecated_lang
  4. import click
  5. import logging
  6. import logging.config
  7. # Click settings
  8. CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
  9. # Logger settings
  10. LOGGER_SETTINGS = {
  11. "version": 1,
  12. "formatters": {"default": {"format": "%(name)s - %(levelname)s - %(message)s"}},
  13. "handlers": {"console": {"class": "logging.StreamHandler", "formatter": "default"}},
  14. "loggers": {"gtts": {"handlers": ["console"], "level": "WARNING"}},
  15. }
  16. # Logger
  17. logging.config.dictConfig(LOGGER_SETTINGS)
  18. log = logging.getLogger("gtts")
  19. def sys_encoding():
  20. """Charset to use for --file <path>|- (stdin)"""
  21. return "utf8"
  22. def validate_text(ctx, param, text):
  23. """Validation callback for the <text> argument.
  24. Ensures <text> (arg) and <file> (opt) are mutually exclusive
  25. """
  26. if not text and "file" not in ctx.params:
  27. # No <text> and no <file>
  28. raise click.BadParameter("<text> or -f/--file <file> required")
  29. if text and "file" in ctx.params:
  30. # Both <text> and <file>
  31. raise click.BadParameter("<text> and -f/--file <file> can't be used together")
  32. return text
  33. def validate_lang(ctx, param, lang):
  34. """Validation callback for the <lang> option.
  35. Ensures <lang> is a supported language unless the <nocheck> flag is set
  36. """
  37. if ctx.params["nocheck"]:
  38. return lang
  39. # Fallback from deprecated language if needed
  40. lang = _fallback_deprecated_lang(lang)
  41. try:
  42. if lang not in tts_langs():
  43. raise click.UsageError(
  44. "'%s' not in list of supported languages.\n"
  45. "Use --all to list languages or "
  46. "add --nocheck to disable language check." % lang
  47. )
  48. else:
  49. # The language is valid.
  50. # No need to let gTTS re-validate.
  51. ctx.params["nocheck"] = True
  52. except RuntimeError as e:
  53. # Only case where the <nocheck> flag can be False
  54. # Non-fatal. gTTS will try to re-validate.
  55. log.debug(str(e), exc_info=True)
  56. return lang
  57. def print_languages(ctx, param, value):
  58. """Callback for <all> flag.
  59. Prints formatted sorted list of supported languages and exits
  60. """
  61. if not value or ctx.resilient_parsing:
  62. return
  63. try:
  64. langs = tts_langs()
  65. langs_str_list = sorted("{}: {}".format(k, langs[k]) for k in langs)
  66. click.echo(" " + "\n ".join(langs_str_list))
  67. except RuntimeError as e: # pragma: no cover
  68. log.debug(str(e), exc_info=True)
  69. raise click.ClickException("Couldn't fetch language list.")
  70. ctx.exit()
  71. def set_debug(ctx, param, debug):
  72. """Callback for <debug> flag.
  73. Sets logger level to DEBUG
  74. """
  75. if debug:
  76. log.setLevel(logging.DEBUG)
  77. return
  78. @click.command(context_settings=CONTEXT_SETTINGS)
  79. @click.argument("text", metavar="<text>", required=False, callback=validate_text)
  80. @click.option(
  81. "-f",
  82. "--file",
  83. metavar="<file>",
  84. # For py2.7/unicode. If encoding not None Click uses io.open
  85. type=click.File(encoding=sys_encoding()),
  86. help="Read from <file> instead of <text>.",
  87. )
  88. @click.option(
  89. "-o",
  90. "--output",
  91. metavar="<file>",
  92. type=click.File(mode="wb"),
  93. help="Write to <file> instead of stdout.",
  94. )
  95. @click.option("-s", "--slow", default=False, is_flag=True, help="Read more slowly.")
  96. @click.option(
  97. "-l",
  98. "--lang",
  99. metavar="<lang>",
  100. default="en",
  101. show_default=True,
  102. callback=validate_lang,
  103. help="IETF language tag. Language to speak in. List documented tags with --all.",
  104. )
  105. @click.option(
  106. "-t",
  107. "--tld",
  108. metavar="<tld>",
  109. default="com",
  110. show_default=True,
  111. is_eager=True, # Prioritize <tld> to ensure it gets set before <lang>
  112. help="Top-level domain for the Google host, i.e https://translate.google.<tld>",
  113. )
  114. @click.option(
  115. "--nocheck",
  116. default=False,
  117. is_flag=True,
  118. is_eager=True, # Prioritize <nocheck> to ensure it gets set before <lang>
  119. help="Disable strict IETF language tag checking. Allow undocumented tags.",
  120. )
  121. @click.option(
  122. "--all",
  123. default=False,
  124. is_flag=True,
  125. is_eager=True,
  126. expose_value=False,
  127. callback=print_languages,
  128. help="Print all documented available IETF language tags and exit.",
  129. )
  130. @click.option(
  131. "--debug",
  132. default=False,
  133. is_flag=True,
  134. is_eager=True, # Prioritize <debug> to see debug logs of callbacks
  135. expose_value=False,
  136. callback=set_debug,
  137. help="Show debug information.",
  138. )
  139. @click.version_option(version=__version__)
  140. def tts_cli(text, file, output, slow, tld, lang, nocheck):
  141. """Read <text> to mp3 format using Google Translate's Text-to-Speech API
  142. (set <text> or --file <file> to - for standard input)
  143. """
  144. # stdin for <text>
  145. if text == "-":
  146. text = click.get_text_stream("stdin").read()
  147. # stdout (when no <output>)
  148. if not output:
  149. output = click.get_binary_stream("stdout")
  150. # <file> input (stdin on '-' is handled by click.File)
  151. if file:
  152. try:
  153. text = file.read()
  154. except UnicodeDecodeError as e: # pragma: no cover
  155. log.debug(str(e), exc_info=True)
  156. raise click.FileError(
  157. file.name, "<file> must be encoded using '%s'." % sys_encoding()
  158. )
  159. # TTS
  160. try:
  161. tts = gTTS(text=text, lang=lang, slow=slow, tld=tld, lang_check=not nocheck)
  162. tts.write_to_fp(output)
  163. except (ValueError, AssertionError) as e:
  164. raise click.UsageError(str(e))
  165. except gTTSError as e:
  166. raise click.ClickException(str(e))