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.
- jwf_common_python/__init__.py +14 -0
- jwf_common_python/db_utils/__init__.py +6 -0
- jwf_common_python/db_utils/clickhouse.py +124 -0
- jwf_common_python/db_utils/elasticsearch.py +196 -0
- jwf_common_python/db_utils/mysql.py +64 -0
- jwf_common_python/eudic/__init__.py +5 -0
- jwf_common_python/eudic/wordbook.py +211 -0
- jwf_common_python/file_storage/__init__.py +2 -0
- jwf_common_python/file_storage/file_interface.py +29 -0
- jwf_common_python/file_storage/local.py +50 -0
- jwf_common_python/file_storage/tencent_cos.py +65 -0
- jwf_common_python/geography/__init__.py +8 -0
- jwf_common_python/geography/district_amap.py +181 -0
- jwf_common_python/geography/search_amap.py +84 -0
- jwf_common_python/github/__init__.py +5 -0
- jwf_common_python/github/webhooks.py +87 -0
- jwf_common_python/language_utils/__init__.py +34 -0
- jwf_common_python/language_utils/core.py +203 -0
- jwf_common_python/language_utils/files.py +52 -0
- jwf_common_python/language_utils/web.py +117 -0
- jwf_common_python/llm/__init__.py +1 -0
- jwf_common_python/llm/aliyun_qwen.py +10 -0
- jwf_common_python/llm/azure_gpt.py +66 -0
- jwf_common_python/llm/baidu_ernie.py +112 -0
- jwf_common_python/message_queue/__init__.py +7 -0
- jwf_common_python/message_queue/interface.py +49 -0
- jwf_common_python/message_queue/kafka.py +145 -0
- jwf_common_python/message_queue/rabbitmq.py +113 -0
- jwf_common_python/oauth2/__init__.py +0 -0
- jwf_common_python/oauth2/google.py +158 -0
- jwf_common_python/parse_subtitle/__init__.py +6 -0
- jwf_common_python/parse_subtitle/aa.vtt +2364 -0
- jwf_common_python/parse_subtitle/parse_srt_file.py +60 -0
- jwf_common_python/pdf/__init__.py +0 -0
- jwf_common_python/pdf/aaa.py +38 -0
- jwf_common_python/pdf/local_parse.py +259 -0
- jwf_common_python/sms/__init__.py +1 -0
- jwf_common_python/sms/interface.py +7 -0
- jwf_common_python/sms/tencent_sms.py +117 -0
- jwf_common_python/translate/__init__.py +152 -0
- jwf_common_python/translate/_youdao.py +234 -0
- jwf_common_python/translate/microsoft.py +696 -0
- jwf_common_python/translate/tencent.py +194 -0
- jwf_common_python/tts/__init__.py +0 -0
- jwf_common_python/tts/microsoft.py +87 -0
- jwf_common_python/utils/__init__.py +0 -0
- jwf_common_python/utils/image_utils.py +18 -0
- jwf_common_python/utils/tencent_utils.py +51 -0
- jwf_common_python/utils/utils.py +46 -0
- jwf_common_python/video/__init__.py +5 -0
- jwf_common_python/video/merge.py +276 -0
- jwf_common_python/weather/__init__.py +0 -0
- jwf_common_python/weather/amap.py +127 -0
- jwf_common_python/weather/wanwei.py +58 -0
- jwf_common_python/youdao/__init__.py +13 -0
- jwf_common_python/youdao/wordbook.py +88 -0
- jwf_common_python-1.0.0.dist-info/METADATA +669 -0
- jwf_common_python-1.0.0.dist-info/RECORD +60 -0
- jwf_common_python-1.0.0.dist-info/WHEEL +4 -0
- 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,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
|