jwf-common-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. jwf_common_python/__init__.py +14 -0
  2. jwf_common_python/db_utils/__init__.py +6 -0
  3. jwf_common_python/db_utils/clickhouse.py +124 -0
  4. jwf_common_python/db_utils/elasticsearch.py +196 -0
  5. jwf_common_python/db_utils/mysql.py +64 -0
  6. jwf_common_python/eudic/__init__.py +5 -0
  7. jwf_common_python/eudic/wordbook.py +211 -0
  8. jwf_common_python/file_storage/__init__.py +2 -0
  9. jwf_common_python/file_storage/file_interface.py +29 -0
  10. jwf_common_python/file_storage/local.py +50 -0
  11. jwf_common_python/file_storage/tencent_cos.py +65 -0
  12. jwf_common_python/geography/__init__.py +8 -0
  13. jwf_common_python/geography/district_amap.py +181 -0
  14. jwf_common_python/geography/search_amap.py +84 -0
  15. jwf_common_python/github/__init__.py +5 -0
  16. jwf_common_python/github/webhooks.py +87 -0
  17. jwf_common_python/language_utils/__init__.py +34 -0
  18. jwf_common_python/language_utils/core.py +203 -0
  19. jwf_common_python/language_utils/files.py +52 -0
  20. jwf_common_python/language_utils/web.py +117 -0
  21. jwf_common_python/llm/__init__.py +1 -0
  22. jwf_common_python/llm/aliyun_qwen.py +10 -0
  23. jwf_common_python/llm/azure_gpt.py +66 -0
  24. jwf_common_python/llm/baidu_ernie.py +112 -0
  25. jwf_common_python/message_queue/__init__.py +7 -0
  26. jwf_common_python/message_queue/interface.py +49 -0
  27. jwf_common_python/message_queue/kafka.py +145 -0
  28. jwf_common_python/message_queue/rabbitmq.py +113 -0
  29. jwf_common_python/oauth2/__init__.py +0 -0
  30. jwf_common_python/oauth2/google.py +158 -0
  31. jwf_common_python/parse_subtitle/__init__.py +6 -0
  32. jwf_common_python/parse_subtitle/aa.vtt +2364 -0
  33. jwf_common_python/parse_subtitle/parse_srt_file.py +60 -0
  34. jwf_common_python/pdf/__init__.py +0 -0
  35. jwf_common_python/pdf/aaa.py +38 -0
  36. jwf_common_python/pdf/local_parse.py +259 -0
  37. jwf_common_python/sms/__init__.py +1 -0
  38. jwf_common_python/sms/interface.py +7 -0
  39. jwf_common_python/sms/tencent_sms.py +117 -0
  40. jwf_common_python/translate/__init__.py +152 -0
  41. jwf_common_python/translate/_youdao.py +234 -0
  42. jwf_common_python/translate/microsoft.py +696 -0
  43. jwf_common_python/translate/tencent.py +194 -0
  44. jwf_common_python/tts/__init__.py +0 -0
  45. jwf_common_python/tts/microsoft.py +87 -0
  46. jwf_common_python/utils/__init__.py +0 -0
  47. jwf_common_python/utils/image_utils.py +18 -0
  48. jwf_common_python/utils/tencent_utils.py +51 -0
  49. jwf_common_python/utils/utils.py +46 -0
  50. jwf_common_python/video/__init__.py +5 -0
  51. jwf_common_python/video/merge.py +276 -0
  52. jwf_common_python/weather/__init__.py +0 -0
  53. jwf_common_python/weather/amap.py +127 -0
  54. jwf_common_python/weather/wanwei.py +58 -0
  55. jwf_common_python/youdao/__init__.py +13 -0
  56. jwf_common_python/youdao/wordbook.py +88 -0
  57. jwf_common_python-1.0.0.dist-info/METADATA +669 -0
  58. jwf_common_python-1.0.0.dist-info/RECORD +60 -0
  59. jwf_common_python-1.0.0.dist-info/WHEEL +4 -0
  60. jwf_common_python-1.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,60 @@
1
+ class ParseSrtFile:
2
+ def __init__(self, file_name: str):
3
+ self.file_name = file_name
4
+ with open(self.file_name) as f:
5
+ content = f.readlines()
6
+ self.content: list = self.clean_non_text_lines(content)
7
+
8
+ def clean_non_text_lines(self, file_content: list) -> list:
9
+ """清理控制字符和空行"""
10
+ # 清理\n控制字符
11
+ file_content = list(map(lambda line: line.strip("\n"), file_content))
12
+ # 清理空行
13
+ file_content = list(filter(lambda line: line != "", file_content))
14
+ return file_content
15
+
16
+ def get_all_timer_shaft(self) -> list[tuple[str, str]]:
17
+ """获取所有时间轴
18
+ [
19
+ (start_time,end_time)
20
+ ]
21
+ """
22
+ # `:` 和 `-->` 同时存在的行就是时间轴所在的行
23
+ time_shaft_content: list = list(
24
+ filter(lambda line: ":" in line and "-->" in line, self.content)
25
+ )
26
+
27
+ # 将时间轴数据拆分为
28
+ def _clean(time_str: str):
29
+ return tuple(map(lambda x: x.strip(" "), time_str.split("-->")))
30
+
31
+ time_shaft_data: list = list(
32
+ map(
33
+ lambda time_str_line: _clean(time_str_line),
34
+ time_shaft_content,
35
+ )
36
+ )
37
+ import json
38
+
39
+ print(json.dumps(time_shaft_data, indent=2, ensure_ascii=False))
40
+ return time_shaft_data
41
+
42
+
43
+ # p = ParseSrtFile("test.srt")
44
+ # p.get_all_timer_shaft()
45
+ import logging
46
+ import os
47
+
48
+
49
+ def split(time_range, video_name):
50
+ """单线程阻塞切割"""
51
+ for index, time in enumerate(time_range):
52
+ # cmd = f"ffmpeg -i 输入视频.mp4 -ss 00:02:10 -to 00:03:20 -c:v copy -c:a copy 输出视频.mp4"
53
+ cmd = f"ffmpeg -i {video_name} -ss {time[0]} -to {time[1]} -c:v copy -c:a copy {index}.mp4"
54
+ try:
55
+ os.system(cmd)
56
+ except Exception as e:
57
+ logging.error(f"出错: {cmd} {e}")
58
+ else:
59
+ logging.debug(f"cmd: {cmd}")
60
+ logging.info(f"{time}处理成功")
File without changes
@@ -0,0 +1,38 @@
1
+ [
2
+ BlockStruClean(
3
+ lines=[
4
+ LineStruClean(
5
+ spans=[SpanStruClean(text="Setting Up React and TypeScript")]
6
+ ),
7
+ LineStruClean(spans=[SpanStruClean(text="82")]),
8
+ ]
9
+ ),
10
+ BlockStruClean(
11
+ lines=[
12
+ LineStruClean(spans=[SpanStruClean(text="4.\t")]),
13
+ LineStruClean(
14
+ spans=[
15
+ SpanStruClean(text="Similarly, install a Babel plugin called "),
16
+ SpanStruClean(text=" that enables TypeScript "),
17
+ ]
18
+ ),
19
+ LineStruClean(
20
+ spans=[SpanStruClean(text="code to be transformed into JavaScript:")]
21
+ ),
22
+ ]
23
+ ),
24
+ ]
25
+
26
+ [
27
+ LineStruClean(spans=[SpanStruClean(text="4.\t")]),
28
+ LineStruClean(
29
+ spans=[
30
+ SpanStruClean(text="Similarly, install a Babel plugin called "),
31
+ SpanStruClean(text="@babel/preset-typescript"),
32
+ SpanStruClean(text=" that enables TypeScript "),
33
+ ]
34
+ ),
35
+ LineStruClean(
36
+ spans=[SpanStruClean(text="code to be transformed into JavaScript:")]
37
+ ),
38
+ ]
@@ -0,0 +1,259 @@
1
+ from itertools import chain
2
+
3
+ import pymupdf
4
+ from pydantic import BaseModel
5
+ from pymupdf import Document, Page, Pixmap
6
+
7
+
8
+ # span 结构:
9
+ class SpanStruClean(BaseModel):
10
+ text: str
11
+ """
12
+ _span_stru = {
13
+ "size": 10.0,
14
+ "flags": 4,
15
+ "font": "MinionPro-Regular",
16
+ "color": 0,
17
+ "ascender": 0.9890000224113464,
18
+ "descender": -0.36000001430511475,
19
+ "text": "Next, we will glue everything together with webpack.",
20
+ "origin": (81.0, 573.5889892578125),
21
+ "bbox": (81.0, 563.698974609375, 291.9960632324219, 577.18896484375),
22
+ }
23
+ """
24
+
25
+
26
+ # 行结构
27
+ class LineStruClean(BaseModel):
28
+ spans: list[SpanStruClean]
29
+ """
30
+ _line_stru = {
31
+ "spans": [
32
+ _span_stru,
33
+ ],
34
+ "wmode": 0,
35
+ "dir": (1.0, 0.0),
36
+ "bbox": (81.0, 563.698974609375, 291.9960632324219, 577.18896484375),
37
+ }
38
+ """
39
+
40
+
41
+ # 块结构
42
+ class BlockStruClean(BaseModel):
43
+ lines: list[LineStruClean]
44
+ """raw:
45
+ _block_stru = {
46
+ "number": 10,
47
+ "type": 0,
48
+ "bbox": (81.0, 563.698974609375, 291.9960632324219, 577.18896484375),
49
+ "lines": [_line_stru],
50
+ }
51
+ """
52
+
53
+ # 页结构
54
+ # class PageStruClean(BaseModel):
55
+ # blocks: list[BlockStruClean]
56
+
57
+ # """
58
+ # raw页结构
59
+ # _page_stru = {
60
+ # "width": 549.0,
61
+ # "height": 684.0,
62
+ # "blocks": [
63
+ # _block_stru,
64
+ # ],
65
+ # }
66
+ # """
67
+
68
+
69
+ # 单个目录信息
70
+ class RawCatalogueStru(BaseModel):
71
+ # level: 目录级别,也是目录缩进的空格数
72
+ level: int
73
+ # title: 目录标题
74
+ title: str
75
+ # page_num: 目录页码
76
+ page_num: int
77
+
78
+
79
+ class CatalogueStru(BaseModel):
80
+ """子目录结构(仅有2级目录)"""
81
+
82
+ title: str
83
+ page_num: int
84
+
85
+
86
+ class FatherCatalogueStru(BaseModel):
87
+ """根目录结构"""
88
+
89
+ title: str
90
+ page_num: int
91
+ children: list[CatalogueStru]
92
+
93
+
94
+ class ParsePDF:
95
+ """
96
+ sample file:
97
+ """
98
+
99
+ def __init__(self, file_name: str):
100
+ self.doc: Document = pymupdf.open(file_name)
101
+ self.page_count = self.doc.page_count
102
+
103
+ def get_cover_image(self) -> bytes:
104
+ """获取封面图片,返回图片的字节数据"""
105
+ page: Page = self.doc[0]
106
+
107
+ pix: Pixmap = page.get_pixmap()
108
+
109
+ res: bytes = pix.tobytes()
110
+ return res
111
+
112
+ def _get_raw_catalogue(self) -> list[RawCatalogueStru]:
113
+ """获取pdf的目录信息"""
114
+ catalogue_info: list[list] = self.doc.get_toc(simple=False)
115
+
116
+ res = [
117
+ RawCatalogueStru(level=item[0], title=item[1], page_num=item[2])
118
+ for item in catalogue_info
119
+ ]
120
+
121
+ return res
122
+
123
+ def get_catalogue(self) -> list[FatherCatalogueStru]:
124
+ """获取经过处理的目录信息
125
+
126
+ 返回结果数据sample:
127
+ [
128
+ FatherCatalogueStru(
129
+ title='Preface',
130
+ page_num=14,
131
+ children=[]
132
+ ),
133
+ FatherCatalogueStru(
134
+ title='Chapter 1: Introducing React',
135
+ page_num=22,
136
+ children=[
137
+ CatalogueStru(title='Technical requirements', page_num=23),
138
+ CatalogueStru(title='Understanding the benefits of React', page_num=23),
139
+ CatalogueStru(title='Understanding JSX', page_num=24),
140
+ ]
141
+ )
142
+ ]
143
+ """
144
+ raw_catalogue = self._get_raw_catalogue()
145
+
146
+ # 1. 过滤目录层级超过2的目录,只能保留1和2级目录
147
+ raw_catalogue_only = list(filter(lambda x: x.level <= 2, raw_catalogue))
148
+
149
+ # 2. 生成目录树
150
+ # 2.1 将level是1的目录作为根目录,level是2的目录作为子目录
151
+ res = []
152
+ for catelogue in raw_catalogue_only:
153
+ if catelogue.level == 1:
154
+ f = FatherCatalogueStru(
155
+ title=catelogue.title, page_num=catelogue.page_num, children=[]
156
+ )
157
+ res.append(f)
158
+
159
+ if catelogue.level == 2:
160
+ # 获取最后一个目录块,就是当前的根目录
161
+ current_fa = res.pop()
162
+ # 获取当前的根目录的子目录
163
+ current_children = current_fa.children
164
+
165
+ # 获取新的子目录
166
+ cate = CatalogueStru(title=catelogue.title, page_num=catelogue.page_num)
167
+
168
+ # 追加新的子目录
169
+ current_children.append(cate)
170
+
171
+ # 重新添加到字典中
172
+ current_fa.children = current_children
173
+
174
+ res.append(current_fa)
175
+ return res
176
+
177
+ def _get_raw_page_info(self, page_num: int) -> list[BlockStruClean]:
178
+ """获取指定页中的原生页面信息"""
179
+ page: Page = self.doc[page_num]
180
+ # 1. 已文本形式获取页面信息
181
+ page_info: dict = page.get_text("dict")
182
+
183
+ # 2. raw blocks
184
+ raw_blocks = page_info["blocks"]
185
+ # print("----", len(raw_blocks), raw_blocks[0], "----")
186
+
187
+ # 3. format block
188
+ format_blocks = [BlockStruClean(lines=block["lines"]) for block in raw_blocks]
189
+ return format_blocks
190
+
191
+ def _get_block_text(self, block_info: BlockStruClean) -> list[str]:
192
+ """获取指定块中每行文本信息
193
+ 返回值:
194
+ [
195
+ "该块的第一行文字",
196
+ "该块的第二行文字",
197
+ ]
198
+ """
199
+ # 1. 获取块中的全部行信息
200
+ lines: list[LineStruClean] = block_info.lines
201
+
202
+ # 2. 获取每行的文字信息
203
+ lines_text = []
204
+ for line in lines:
205
+ # 1. 获取每行的span
206
+ spans: list[SpanStruClean] = line.spans
207
+ # 1. 获取每个span的文字
208
+ spans_text = [span.text for span in spans]
209
+ # 2. 合并所有span的文字. 获取指定行的所有文字信息
210
+ line_text = "".join(spans_text)
211
+ lines_text.append(line_text)
212
+ return lines_text
213
+
214
+ def get_page_text(self, page_num: int):
215
+ """从页面中获取原生的文本信息
216
+ func 是判断是否合并的函数,默认如果不传入,则不合并,展开二维数组
217
+ """
218
+
219
+ # 1. 获取页面中的所有块信息
220
+ raw_page_info = self._get_raw_page_info(page_num)
221
+
222
+ # 2. 获取页面的原生文本信息
223
+ raw_page_text: list[list[str]] = [
224
+ self._get_block_text(block) for block in raw_page_info
225
+ ]
226
+
227
+ # # 3 使用ai判断,是否将这些块进行合并
228
+ # # 这些行合并到一起是不是普通文本,如果是,则合并,否则不合并
229
+ # for block_text in raw_page_text:
230
+ # # 1. 合并块中的所有行
231
+ # block_text_single = " ".join(block_text)
232
+
233
+ # # 2. 使用func判断合并完成后的文本是否是普通文本
234
+ # msg = f"请判断下面这些文本是代码还是普通文本:\n {block_text_single}"
235
+ # status = func(msg)
236
+
237
+ # # 3. 确定最终的文本信息
238
+ # if status:
239
+ # # 是普通文本
240
+ # else:
241
+ # ...
242
+ # 3. 将二维数组展开
243
+ page_text = list(chain(*raw_page_text))
244
+ return page_text
245
+
246
+
247
+ # if __name__ == "__main__":
248
+ # pdf = ParsePDF("/Users/jason/temp/test-pdf.pdf")
249
+ # # blocks = pdf._get_raw_page_info(100)
250
+ # # block = blocks[1]
251
+ # # pdf._get_block_text(block)
252
+ # print(pdf.get_page_text(100))
253
+ # import json
254
+
255
+ # print(json.dumps(pdf.get_top_catalogue(), indent=2))
256
+ # print(pdf.get_cover_image())
257
+
258
+ # with open("/Users/evans/Desktop/cover2.png", "wb") as f:
259
+ # f.write(pdf.get_cover_image())
@@ -0,0 +1 @@
1
+ from .tencent_sms import TencentSendSms
@@ -0,0 +1,7 @@
1
+ from abc import ABCMeta, abstractmethod
2
+
3
+
4
+ class SendSms(metaclass=ABCMeta):
5
+ @abstractmethod
6
+ def send(self, send_phone_number: str, veri_code: str):
7
+ ...
@@ -0,0 +1,117 @@
1
+ import logging
2
+
3
+ from tencentcloud.common import credential
4
+ from tencentcloud.common.exception.tencent_cloud_sdk_exception import (
5
+ TencentCloudSDKException,
6
+ )
7
+ from tencentcloud.common.profile.client_profile import ClientProfile
8
+ from tencentcloud.common.profile.http_profile import HttpProfile
9
+ from tencentcloud.sms.v20210111 import models, sms_client
10
+ from tencentcloud.sms.v20210111.models import SendSmsResponse, SendStatus
11
+
12
+ from jwf_common_python.sms.interface import SendSms
13
+
14
+
15
+ class TencentSendSms(SendSms):
16
+ def __init__(self, **kwargs) -> None:
17
+ """
18
+ 发送成功:
19
+ {
20
+ "SendStatusSet": [
21
+ {
22
+ "SerialNo": "2640:222395940116893272585707425",
23
+ "PhoneNumber": "+8618285574257",
24
+ "Fee": 1,
25
+ "SessionContext": "user session",
26
+ "Code": "Ok",
27
+ "Message": "send success",
28
+ "IsoCode": "CN"
29
+ }
30
+ ],
31
+ "RequestId": "bbc9e3e8-a67e-4ed0-9e85-713cda1ae041"
32
+ }
33
+
34
+ 发送失败:
35
+ {
36
+ "SendStatusSet": [
37
+ {
38
+ "SerialNo": "",
39
+ "PhoneNumber": "+8618285574257",
40
+ "Fee": 0,
41
+ "SessionContext": "user session",
42
+ "Code": "FailedOperation.InsufficientBalanceInSmsPackage",
43
+ "Message": "insufficient balance in SMS package",
44
+ "IsoCode": ""
45
+ }
46
+ ],
47
+ "RequestId": "a28c5e46-5209-4425-8bfe-97784d205a52"
48
+ }
49
+ """
50
+ if not {
51
+ "secret_id",
52
+ "secret_key",
53
+ "app_id",
54
+ "sign_name",
55
+ "template_id",
56
+ "endpoint",
57
+ "veri_code_timeout",
58
+ }.issubset(kwargs.keys()):
59
+ raise ValueError(
60
+ "secret_id,secret_key,app_id,sign_name,template_id,endpoint,veri_code_timeout must be provided"
61
+ )
62
+ self.secret_id = kwargs["secret_id"]
63
+ self.secret_key = kwargs["secret_key"]
64
+ self.app_id = kwargs["app_id"]
65
+ self.sign_name = kwargs["sign_name"]
66
+ self.template_id = kwargs["template_id"]
67
+ self.endpoint = kwargs["endpoint"]
68
+ self.veri_code_timeout = kwargs["veri_code_timeout"]
69
+
70
+ def send(self, send_phone_number: str, veri_code: str, session_context: str = ""):
71
+ """
72
+ +8618285574257
73
+ 2222
74
+
75
+ session_context:
76
+ 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回
77
+ """
78
+ try:
79
+ cred = credential.Credential(self.secret_id, self.secret_key)
80
+ httpProfile = HttpProfile()
81
+ httpProfile.reqMethod = "POST"
82
+ httpProfile.reqTimeout = 30
83
+ httpProfile.endpoint = self.endpoint
84
+
85
+ # 实例化一个客户端配置对象,可以指定超时时间等配置
86
+ clientProfile = ClientProfile()
87
+ clientProfile.signMethod = "TC3-HMAC-SHA256" # 指定签名算法
88
+ clientProfile.language = "en-US"
89
+ clientProfile.httpProfile = httpProfile
90
+
91
+ client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile)
92
+
93
+ req = models.SendSmsRequest()
94
+
95
+ req.SmsSdkAppId = self.app_id
96
+ req.SignName = self.sign_name
97
+ # req.TemplateId = "1941616"
98
+ req.TemplateId = self.template_id
99
+ # 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,,若无模板参数,则设置为空
100
+ req.TemplateParamSet = [veri_code, self.veri_code_timeout]
101
+ # req.PhoneNumberSet = ["+8618285574257"]
102
+ req.PhoneNumberSet = [send_phone_number]
103
+
104
+ req.SessionContext = session_context
105
+
106
+ response: SendSmsResponse = client.SendSms(req)
107
+ response_status: SendStatus = response.SendStatusSet[0]
108
+ assert response_status.PhoneNumber == send_phone_number, "发送号码和响应号码不一致"
109
+ if response_status.Code != "Ok":
110
+ logging.error(f"发送短信失败: {response_status}")
111
+ return False
112
+ logging.debug(f"发送短信成功: {response_status}")
113
+ return True
114
+ except TencentCloudSDKException as err:
115
+ print(err)
116
+ logging.error(f"发送短信失败: {err}")
117
+ return False
@@ -0,0 +1,152 @@
1
+ import hashlib
2
+ import os
3
+ from abc import ABCMeta, abstractmethod
4
+ from typing import Literal, TypedDict
5
+
6
+ # 后端支持的语言代码
7
+ # PREF 优化为下面这种形式
8
+ LanguageCodeOfBackend = Literal["zh-CN", "zh-TW", "en", "fr", "ja", "de", "es", "ko"]
9
+
10
+ # !!!单词没有词性,只有翻译才有词性!!!
11
+ # 翻译词性对照表,用来将词性标准化统一后端实现。
12
+ # HACK 每个提供商都需要实现这个对照表
13
+ pos_tag_table = {
14
+ "提供商-词性1": ("Adjectives", "形容词"),
15
+ "提供商-词性2": ("Adverbs", "副词"),
16
+ "提供商-词性3": ("Conjunctions", "连词"),
17
+ "提供商-词性4": ("Determiners", "限定词"),
18
+ "提供商-词性5": ("Verbs", "动词"),
19
+ "提供商-词性6": ("Nouns", "名词"),
20
+ "提供商-词性7": ("Prepositions", "介词"),
21
+ "提供商-词性8": ("Pronouns", "代词"),
22
+ "提供商-词性9": ("Verbs", "动词"),
23
+ "提供商-词性10": ("Other", "其他"),
24
+ }
25
+
26
+
27
+ class WordTranslation(TypedDict):
28
+ """单词翻译结果结构"""
29
+
30
+ # 2.1 标准化之后的翻译
31
+ normalized: str
32
+ # 用于常见显示的翻译
33
+ display: str
34
+
35
+ # 2.2 翻译词性,是 part-of-speech tag 的简写
36
+ # 参考词性对照表.
37
+ # ("Adjectives", "形容词")
38
+ pos_tag: tuple[str, str]
39
+
40
+ # 2.3 翻译频率
41
+ # 数据格式: 10.3 保留一位小数, 页面显示为10.3%
42
+ frequency: float
43
+
44
+ # 2.4 翻译的例句
45
+ """
46
+ 该单词的翻译例句的结构:
47
+ - 一个单词出现在多个例句中
48
+ - 一个例句有多个翻译
49
+ [
50
+ {
51
+ "sourcePrefix": "前半句",
52
+ "sourceTerm": "关键词",
53
+ "sourceSuffix": "后半句",
54
+ "target": [
55
+ {
56
+ "targetPrefix": "翻译前半句",
57
+ "targetTerm": "翻译关键词",
58
+ "targetSuffix": "翻译后半句",
59
+ }
60
+ ],
61
+ ]
62
+ """
63
+ examples: list[dict]
64
+
65
+ # 2.4 翻译前缀
66
+ # prefixWord: str
67
+
68
+
69
+ class WordInfo(TypedDict):
70
+ """单词查询信息结构"""
71
+
72
+ # 1. 传入word标准化之后的形式, word表中唯一标识
73
+ normalized_word: str
74
+ # 1.1 用于常见显示的的word, 通常处理大小写
75
+ display_word: str
76
+ # word: str
77
+
78
+ # 2. word翻译
79
+ translations: list[WordTranslation]
80
+
81
+ # 3. 单词其他变体形式, 例如复数, 过去式等
82
+ shapes: list
83
+
84
+ # 4. word的发音数据
85
+ audio_data: bytes
86
+ # speak_url: dict
87
+
88
+ # 5. 扩展信息
89
+ extended_info: dict
90
+
91
+ # 单词音标
92
+ # phonetic: dict
93
+ # 单词语法信息
94
+ # grammar_info: list
95
+
96
+
97
+ class SentenceInfo(TypedDict):
98
+ """句子查询信息结构"""
99
+
100
+ # 1. 原始sentence文本
101
+ sentence: str
102
+
103
+ # 2. 翻译之后的文本。部分翻译提供商提供多个翻译
104
+ translations: list[str]
105
+
106
+ # 3. 原始sentence的发音数据
107
+ audio_data: bytes
108
+ # source_speak_url: str
109
+ # translation_speak_url: str
110
+
111
+
112
+ class Translate(metaclass=ABCMeta):
113
+ @abstractmethod
114
+ def __init__(self, **kwargs) -> None:
115
+ """\n
116
+ 1. 初始化全局配置
117
+ 2. 初始化相关翻译/合成对象
118
+ """
119
+ ...
120
+
121
+ @abstractmethod
122
+ def translate_word(self, word: str) -> dict:
123
+ """获取简单处理之后的原始word翻译响应"""
124
+ ...
125
+
126
+ @abstractmethod
127
+ def translate_sentence(self, sentence: str) -> dict:
128
+ """获取简单处理之后的原始sentence翻译响应"""
129
+ ...
130
+
131
+ @classmethod
132
+ def format_word_response(cls, response: dict) -> WordInfo:
133
+ """将原始word翻译响应格式化为标准化的word查询信息"""
134
+ ...
135
+
136
+ @classmethod
137
+ def format_sentence_response(cls, response: dict) -> SentenceInfo:
138
+ """将原始sentence翻译响应格式化为标准化的sentence查询信息"""
139
+ ...
140
+
141
+ # DEPRE 移除youdao翻译时,删除这个
142
+ @classmethod
143
+ def download_audio_file(cls, url: str) -> bytes:
144
+ ...
145
+
146
+ # DEPRECATED youdao翻译时,移动到后端实现
147
+ @classmethod
148
+ def generate_word_audio_file_name(self, word: str, audio_type: str) -> str:
149
+ file_id: str = hashlib.md5(word.encode("utf-8")).hexdigest()
150
+ file_name: str = f"word_youdao_{audio_type}_{file_id}.mp3"
151
+ absolute_audio_file_path = os.path.join("/opt/", file_name)
152
+ return absolute_audio_file_path