main.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. from quart import (
  2. Quart,
  3. render_template,
  4. request,
  5. redirect,
  6. make_response,
  7. send_file,
  8. Response,
  9. abort,
  10. )
  11. from configparser import ConfigParser
  12. import argparse
  13. import os
  14. from urllib.parse import urlencode
  15. from wsgiref.util import FileWrapper
  16. from io import BytesIO
  17. from simplytranslate_engines.googletranslate import GoogleTranslateEngine
  18. from simplytranslate_engines.utils import *
  19. import requests
  20. def read_config():
  21. config.read(config_paths)
  22. if config.getboolean("google", "Enabled", fallback=True):
  23. engines.append(GoogleTranslateEngine())
  24. if not engines:
  25. raise Exception("All translation engines are disabled")
  26. config = ConfigParser()
  27. engines = []
  28. config_paths = "config.conf"
  29. # This ain't clean, but it works.
  30. if __name__ != "__main__":
  31. read_config()
  32. app = Quart(__name__)
  33. app.url_map.strict_slashes = False
  34. def str_to_bool(s, **kwargs):
  35. if s is None and "default" in kwargs:
  36. return kwargs["default"]
  37. return s == "on" or s == "True"
  38. def bool_to_str(b):
  39. return "True" if b else "False"
  40. def dict_to_prefs(d, **kwargs):
  41. # For whatever reason, in HTML forms, the values of any unchecked
  42. # checkboxes are not sent at all, so we have this parameter that basically
  43. # disables the defaulting for any boolean settings.
  44. post_form = kwargs.get("post_form")
  45. return {
  46. # We don't need to consider `post_form` for `use_text_fields` since
  47. # it's off by default anyway, unlike `tts_enabled`.
  48. "use_text_fields": str_to_bool(d.get("use_text_fields")),
  49. "tts_enabled": str_to_bool(
  50. d.get("tts_enabled"), default=False if post_form else True
  51. ),
  52. }
  53. # NOTE: Legacy Endpoint. Use "/api"
  54. @app.route(
  55. "/translate/<string:from_language>/<string:to_language>/<string:input_text>/",
  56. )
  57. async def translate(from_language, to_language, input_text):
  58. return await engines[0].translate(
  59. input_text, from_language=from_language, to_language=to_language
  60. )
  61. @app.route("/api/translate/", methods=["GET", "POST"])
  62. async def api_translate():
  63. if request.method == "POST":
  64. args = await request.form
  65. elif request.method == "GET":
  66. args = request.args
  67. engine_name = args.get("engine")
  68. text = args.get("text")
  69. from_language = args.get("from")
  70. to_language = args.get("to")
  71. if from_language == None:
  72. from_language = "auto"
  73. if to_language == None:
  74. to_language = "en"
  75. engine = get_engine(engine_name, engines, engines[0])
  76. from_language = await to_lang_code(from_language, engine, type_="source")
  77. to_language = await to_lang_code(to_language, engine, type_="target")
  78. return await engine.translate(
  79. text, from_language=from_language, to_language=to_language
  80. )
  81. @app.route("/prefs", methods=["POST", "GET"])
  82. async def prefs():
  83. if request.method == "POST":
  84. prefs = dict_to_prefs(await request.form, post_form=True)
  85. elif request.method == "GET":
  86. prefs = dict_to_prefs(request.cookies)
  87. use_text_fields = prefs["use_text_fields"]
  88. tts_enabled = prefs["tts_enabled"]
  89. if request.method == "GET":
  90. return await render_template(
  91. "prefs.html",
  92. use_text_fields=use_text_fields,
  93. tts_enabled=tts_enabled,
  94. )
  95. elif request.method == "POST":
  96. response = await make_response(
  97. await render_template(
  98. "prefs.html",
  99. use_text_fields=use_text_fields,
  100. tts_enabled=tts_enabled,
  101. )
  102. )
  103. response.set_cookie("use_text_fields", bool_to_str(use_text_fields))
  104. response.set_cookie("tts_enabled", bool_to_str(tts_enabled))
  105. return response
  106. @app.route("/api/source_languages/")
  107. async def api_source_languages():
  108. engine_name = request.args.get("engine")
  109. engine = get_engine(engine_name, engines, engines[0])
  110. langs = await engine.get_supported_source_languages()
  111. return "".join(f"{lang}\n{langs[lang]}\n" for lang in langs)
  112. @app.route("/api/target_languages/")
  113. async def api_target_languages():
  114. engine_name = request.args.get("engine")
  115. engine = get_engine(engine_name, engines, engines[0])
  116. langs = await engine.get_supported_target_languages()
  117. return "".join(f"{lang}\n{langs[lang]}\n" for lang in langs)
  118. @app.route("/api/tts/")
  119. async def api_tts():
  120. engine_name = request.args.get("engine")
  121. text = request.args.get("text")
  122. language = request.args.get("lang")
  123. engine = get_engine(engine_name, engines, engines[0])
  124. language = await to_lang_code(
  125. language, engine, type_="source"
  126. ) or await to_lang_code(language, engine, type_="target")
  127. url = await engine.get_tts(text, language)
  128. USER_AGENT = "Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0"
  129. if url is not None:
  130. b = BytesIO(
  131. requests.get(
  132. url, headers={"Referrer": None, "User-Agent": USER_AGENT}
  133. ).content
  134. )
  135. w = FileWrapper(b)
  136. return Response(w, mimetype="audio/mpeg")
  137. abort(404)
  138. @app.route("/switchlanguages/", methods=["POST"])
  139. async def switchlanguages():
  140. form = await request.form
  141. engine_name = request.args.get("engine")
  142. engine = get_engine(engine_name, engines, engines[0])
  143. text = form.get("input", "")
  144. from_lang = await to_lang_code(
  145. form.get("from_language", "Autodetect"), engine, type_="source"
  146. )
  147. to_lang = await to_lang_code(
  148. form.get("to_language", "English"), engine, type_="target"
  149. )
  150. if from_lang == "auto":
  151. detected_lang = await engine.detect_language(text)
  152. if detected_lang is not None:
  153. from_lang = detected_lang
  154. if from_lang != "auto":
  155. from_lang, to_lang = to_lang, from_lang
  156. """
  157. In case we ever want to also switch the translated text with the to-be-translated text, this is a good start.
  158. translation = engine.translate(
  159. text,
  160. to_language=to_lang,
  161. from_language=from_lang,
  162. )
  163. """
  164. redirect_params = {
  165. "engine": engine_name,
  166. "sl": from_lang,
  167. "tl": to_lang,
  168. "text": text,
  169. "could_not_switch_languages": from_lang == "auto",
  170. }
  171. response = await make_response(
  172. redirect(
  173. f"/?{urlencode(redirect_params)}",
  174. code=302,
  175. ),
  176. )
  177. response.set_cookie("from_lang", from_lang)
  178. response.set_cookie("to_lang", to_lang)
  179. return response
  180. @app.route("/", methods=["GET", "POST"])
  181. async def index():
  182. engine_name = request.args.get("engine")
  183. engine = get_engine(engine_name, engines, engines[0])
  184. from_lang, to_lang, inp, translation = "", "", "", None
  185. # This is `True` when the language switch button is pressed, `from_lang` is
  186. # "auto", and the engine doesn't support language detection.
  187. could_not_switch_languages = False
  188. if request.method == "GET":
  189. # support google format
  190. inp = request.args.get("text", "")
  191. from_lang = await to_full_name(
  192. request.args.get("sl") or request.cookies.get("from_lang") or "auto",
  193. engine,
  194. "source",
  195. )
  196. to_lang = await to_full_name(
  197. request.args.get("tl") or request.cookies.get("to_lang") or "en",
  198. engine,
  199. "target",
  200. )
  201. could_not_switch_languages = str_to_bool(
  202. request.args.get("could_not_switch_languages")
  203. )
  204. elif request.method == "POST":
  205. form = await request.form
  206. inp = form.get("input", "")
  207. from_lang = form.get("from_language", "Autodetect")
  208. to_lang = form.get("to_language", "English")
  209. from_l_code = None
  210. to_l_code = None
  211. if not (inp == "" or inp.isspace()):
  212. from_l_code = await to_lang_code(from_lang, engine, type_="source")
  213. to_l_code = await to_lang_code(to_lang, engine, type_="target")
  214. translation = await engine.translate(
  215. inp,
  216. to_language=to_l_code,
  217. from_language=from_l_code,
  218. )
  219. # TTS
  220. tts_from = None
  221. tts_to = None
  222. # check if the engine even supports TTS
  223. if await engine.get_tts("auto", "test") is not None:
  224. if inp:
  225. params = {"engine": engine_name, "lang": from_l_code, "text": inp}
  226. tts_from = f"/api/tts/?{urlencode(params)}"
  227. if translation is not None and translation["translated-text"]:
  228. params = {
  229. "engine": engine_name,
  230. "lang": to_l_code,
  231. "text": translation["translated-text"],
  232. }
  233. tts_to = f"/api/tts/?{urlencode(params)}"
  234. prefs = dict_to_prefs(request.cookies)
  235. response = await make_response(
  236. await render_template(
  237. "index.html",
  238. inp=inp,
  239. translation=translation,
  240. from_l=from_lang,
  241. from_l_code=from_l_code,
  242. tts_from=tts_from,
  243. tts_to=tts_to,
  244. to_l=to_lang,
  245. to_l_code=to_l_code,
  246. engine=engine.name,
  247. engines=engines,
  248. supported_source_languages=await engine.get_supported_source_languages(),
  249. supported_target_languages=await engine.get_supported_target_languages(),
  250. use_text_fields=prefs["use_text_fields"],
  251. tts_enabled=prefs["tts_enabled"],
  252. could_not_switch_languages=could_not_switch_languages,
  253. )
  254. )
  255. if request.method == "POST":
  256. response.set_cookie(
  257. "from_lang", await to_lang_code(from_lang, engine, type_="source")
  258. )
  259. response.set_cookie(
  260. "to_lang", await to_lang_code(to_lang, engine, type_="target")
  261. )
  262. return response
  263. if __name__ == "__main__":
  264. parser = argparse.ArgumentParser()
  265. parser.add_argument("-c", "--config", help="Specify path of config file")
  266. args = parser.parse_args()
  267. if args.config is not None:
  268. if os.path.isfile(args.config):
  269. config_paths = [args.config]
  270. else:
  271. print(
  272. f"INFO: Ignoring specified config file path '{args.config}' because the file doesn't exist."
  273. )
  274. read_config()
  275. app.run(
  276. port=config.getint("network", "port", fallback=5000),
  277. host=config.get("network", "host", fallback="0.0.0.0"),
  278. )