瀏覽代碼

feat: 支持朗读单词

SongZihuan 3 年之前
父節點
當前提交
32fb258a22
共有 12 個文件被更改,包括 149 次插入12 次删除
  1. 1 0
      alibabacloud-nls-python-sdk
  2. 3 4
      app/home.py
  3. 4 0
      configure/__init__.py
  4. 75 0
      core/aliyun.py
  5. 7 5
      core/db.py
  6. 7 2
      core/word.py
  7. 45 0
      job/new_data.py
  8. 2 1
      main.py
  9. 二進制
      requirements.txt
  10. 二進制
      resource/template/base.db
  11. 二進制
      resource/template/high_school.db
  12. 5 0
      templates/test.html

+ 1 - 0
alibabacloud-nls-python-sdk

@@ -0,0 +1 @@
+Subproject commit b2573ac7977f480f023db08be539e1b503c05166

+ 3 - 4
app/home.py

@@ -12,17 +12,16 @@ home = blueprints.Blueprint("home", __name__)
 
 
 class LoginForm(AuthForm):
-    remember = BooleanField("Remember me")
+    remember = BooleanField("Remember me", default=True)
     submit = SubmitField("Login")
 
     def __init__(self):
         super(LoginForm, self).__init__()
-        self.remember.data = True
 
 
 class RegisterForm(AuthForm):
     template = SelectField("Template", choices=get_template(), coerce=str,
-                           validators=[DataRequired(message="Template must be selected")])
+                           validators=[DataRequired(message="Template must be selected")], default="base")
     passwd_again = PasswordField("Passwd again",
                                  validators=[DataRequired(message="Must enter password again"),
                                              EqualTo("passwd", message="The password entered twice is different")])
@@ -31,7 +30,6 @@ class RegisterForm(AuthForm):
 
     def __init__(self):
         super(RegisterForm, self).__init__()
-        self.template.data = "base"
 
     def validate_name(self, field):
         if have_user(field.data):
@@ -92,6 +90,7 @@ def register():
         return redirect(url_for("home.index"))
 
     current_app.new_invite_passwd()
+    print(register_form.template.data)
     flat, user = create_user(register_form.template.data, register_form.name.data, register_form.passwd.data)
     if user is not None:
         current_app.logger.debug(f"{register_form.name.data} with {register_form.template.data} register success")

+ 4 - 0
configure/__init__.py

@@ -16,6 +16,10 @@ conf = {
     "MAX_CONTENT_LENGTH": 5 * 1024 * 1024,
     "LOG_FILE_NAME_PID": False,
     "INVITE_URL": "12345678",
+    "ALIYUN_KEY": "",
+    "ALIYUN_SECRET": "",
+    "TLS_URL": "",
+    "TLS_APPID": ""
 }
 
 

+ 75 - 0
core/aliyun.py

@@ -0,0 +1,75 @@
+import nls
+import base64
+import threading
+import configure
+
+
+class Result:
+    def __init__(self, text: str):
+        self.mp3 = ""
+        self.success = None
+        self.txt = text
+        self.byte = b""
+
+
+# 以下代码会根据上述TEXT文本反复进行语音合成
+class AliyunTls:
+    class Tls:
+        def __init__(self, url: str, key: str, secret: str, app: str, res: "Result", **kwargs):
+            self.__res = res
+            self.__kwargs = kwargs
+            self.__th = threading.Thread(target=self.__test_run)
+            self.url = url
+            self.key = key
+            self.secret = secret
+            self.app = app
+
+        def start(self):
+            self.__th.start()
+            self.__th.join()
+            if self.__res.success is None:
+                self.__res.mp3 = base64.b64encode(self.__res.byte).decode("ascii")
+                self.__res.success = True
+            return self.__res
+
+        def on_error(self, message, *args):
+            self.__res.success = False
+
+        def data_to_base64(self, data, *_):
+            try:
+                self.__res.byte += data
+            except Exception:
+                self.__res.success = False
+
+        def __test_run(self):
+            tts = nls.NlsSpeechSynthesizer(
+                url=self.url,
+                akid=self.key,
+                aksecret=self.secret,
+                appkey=self.app,
+                on_data=self.data_to_base64,
+                on_error=self.on_error,
+            )
+
+            tts.start(self.__res.txt, aformat="mp3", wait_complete=True, **self.__kwargs)
+
+    def __init__(self, url: str, key: str, secret: str, app: str):
+        self.url = url
+        self.key = key
+        self.secret = secret
+        self.app = app
+        self.kwargs = {
+            "voice": "Luca",
+            "speech_rate": -500,
+        }
+
+    def start(self, text: str):
+        return AliyunTls.Tls(self.url, self.key, self.secret, self.app, Result(text), **self.kwargs).start()
+
+
+if len(configure.conf["ALIYUN_KEY"]) == 0:
+    print("Not aliyun key")
+    exit(1)
+
+tls = AliyunTls(configure.conf["TLS_URL"], configure.conf["ALIYUN_KEY"],
+                configure.conf["ALIYUN_SECRET"], configure.conf["TLS_APPID"])

+ 7 - 5
core/db.py

@@ -139,7 +139,8 @@ class WordDatabase(DataBase):
             part TEXT NOT NULL,  -- 词性
             english TEXT NOT NULL,  -- 英文注释
             chinese TEXT NOT NULL,  -- 中文注释
-            eg TEXT  -- 例句
+            eg TEXT,  -- 例句
+            mp3 TEXT  -- mp3 单词音频
         )''')
         self.wd = word.WordDict()
 
@@ -153,6 +154,7 @@ class WordDatabase(DataBase):
             if ret is None:
                 ret = w
             name = w.name
+            mp3 = w.mp3
             name_lower = name.lower().replace("'", "''")
             res = self.search(columns=["word"], table="Word", where=f"LOWER(word)='{name_lower}'")
             if res is not None and len(res) > 0:
@@ -165,14 +167,14 @@ class WordDatabase(DataBase):
                 chinese = comment.chinese.replace("'", "''")
                 if add:
                     self.insert(table='Word',
-                                columns=['word', 'part', 'english', 'chinese', 'eg'],
-                                values=f"'{name_lower}', '{part}', '{english}', '{chinese}', '{eg} '")
+                                columns=['word', 'part', 'english', 'chinese', 'eg', 'mp3'],
+                                values=f"'{name_lower}', '{part}', '{english}', '{chinese}', '{eg}', '{mp3}'")
                 self.__logger.info(f"Add word name: {name_lower} part: {part}")
         return ret
 
     @staticmethod
     def __make_word(q: str, res: list):
-        w = word.Word(q)
+        w = word.Word(q, res[0][7])
         box = 6
         for i in res:
             c = word.Word.Comment(i[2], i[3], i[4])
@@ -185,7 +187,7 @@ class WordDatabase(DataBase):
 
     def find_word(self, q: str, search: bool = True, add: bool = True) -> Optional[word.Word]:
         name_lower = q.lower().replace("'", "''")
-        res = self.search(columns=["id", "word", "part", "english", "chinese", "eg", "box"],
+        res = self.search(columns=["id", "word", "part", "english", "chinese", "eg", "box", "mp3"],
                           table="Word",
                           where=f"LOWER(word)='{name_lower}'")
         if res is None or len(res) <= 0:

+ 7 - 2
core/word.py

@@ -9,6 +9,7 @@ import logging
 import requests
 from bs4 import BeautifulSoup
 from typing import Optional, Dict
+from core.aliyun import tls
 
 
 class WordDict:
@@ -90,7 +91,10 @@ class Response:
 
             word = self.res.get(name_string)
             if word is None:
-                word = Word(name_string)
+                tls_res = tls.start(name_string)
+                if not tls_res.success:
+                    continue
+                word = Word(name_string, tls_res.mp3)
                 self.res[name_string] = word
 
             h = i.find(name="div", attrs={"class": "ddef_h"})
@@ -143,12 +147,13 @@ class Word:
         def __str__(self):
             return f"{self.part} {self.english} {self.chinese} \neg: {self.eg}"
 
-    def __init__(self, name: str, box: int = 1):
+    def __init__(self, name: str, mp3: str, box: int = 1):
         self.name = name
         self.comment: Dict[str: "Word.Comment"] = {}  # 注释
         if box < 1 or box > 5:
             box = 1
         self.box = box
+        self.mp3 = mp3
 
     def add_comment(self, c: Comment):
         if self.comment.get(c.english) is None:

+ 45 - 0
job/new_data.py

@@ -0,0 +1,45 @@
+import sqlite3
+from core.aliyun import tls
+import traceback
+
+first = sqlite3.connect("high_school.db")
+new = sqlite3.connect("high_school.db")
+
+new.execute('''CREATE TABLE IF NOT EXISTS Word (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,  -- 编码
+            box INTEGER NOT NULL DEFAULT 1 CHECK (box < 6 and box > 0),
+            word TEXT NOT NULL,  -- 单词
+            part TEXT NOT NULL,  -- 词性
+            english TEXT NOT NULL,  -- 英文注释
+            chinese TEXT NOT NULL,  -- 中文注释
+            eg TEXT,  -- 例句
+            mp3 TEXT  -- mp3 单词音频
+        )''')
+
+res = first.execute("SELECT MIN(ID), MAX(id) FROM main.Word;")
+min_id, max_id = res.fetchone()
+res.close()
+
+
+error = []
+for word_id in range(min_id, max_id + 1):
+    res = first.execute(f"SELECT word, part, english, chinese, eg FROM main.Word WHERE id={word_id};")
+    word: "list | None" = res.fetchone()
+    res.close()
+
+    if word is None:
+        continue
+
+    try:
+        word = [i.replace("'", "''") for i in word]
+        res = tls.start(word[0])
+        if not res.success:
+            raise Exception
+        values = f"(1, '{word[0]}', '{word[1]}', '{word[2]}', '{word[3]}', '{word[4]}', '{res.mp3}')"
+        res = new.execute(f"INSERT INTO main.Word(box, word, part, english, chinese, eg, mp3)  VALUES {values};")
+        res.close()
+        new.commit()
+    except Exception as e:
+        print(f"Error: {word[0]} {word_id}")
+        error.append((word[0], word_id))
+        traceback.print_exception(e)

+ 2 - 1
main.py

@@ -1,5 +1,4 @@
 import configure
-import app as App
 import os
 import logging
 
@@ -11,4 +10,6 @@ if henglish_conf is not None:
     configure.configure(henglish_conf, encoding="utf-8")
 
 
+import app as App
+
 app = App.HEnglishFlask(__name__)

二進制
requirements.txt


二進制
resource/template/base.db


二進制
resource/template/high_school.db


+ 5 - 0
templates/test.html

@@ -16,6 +16,11 @@
                     {% if have_word %}
                         <p class="text-center h2"> <u> {{ word.name }} </u> </p>
                         <p class="h5 text-center"> Box: {{ word.box }} </p>
+
+                        <audio controls="controls" autoplay="autoplay">
+                            <source src="data:audio/mp3;base64,{{ word.mp3 }}">
+                        </audio>
+
                         <div class="col-12 col-lg-6 offset-lg-3">
                             {% for w in word.comment %}
                                 <p class="h6 text-left">