admin commited on
Commit
7cf86e5
·
1 Parent(s): 75ba758
.gitignore CHANGED
@@ -1,2 +1,3 @@
 
 
1
  *__pycache__*
2
- test.*
 
1
+ *.gif
2
+ test.*
3
  *__pycache__*
 
app.py CHANGED
@@ -1,42 +1,64 @@
1
- import gradio as gr
2
- from data import data_converter
3
- from exif import clexif
4
- from gif import video2gif
5
- from github import github_release_creator
6
- from qr import qrcode
7
- from rct import rct_generator
8
- from smtp import smtp_tester
9
- from trans import translator
10
- from url import url_shortner
11
-
12
- if __name__ == "__main__":
13
- with gr.Blocks() as demo:
14
- gr.Markdown("# Online Tools Collection")
15
- with gr.Tab("Data Converter"):
16
- data_converter()
17
-
18
- with gr.Tab("Image EXIF Cleaner"):
19
- clexif()
20
-
21
- with gr.Tab("Video to GIF"):
22
- video2gif()
23
-
24
- with gr.Tab("GitHub Releaser"):
25
- github_release_creator()
26
-
27
- with gr.Tab("QR Code"):
28
- qrcode()
29
-
30
- with gr.Tab("RCT Generator"):
31
- rct_generator()
32
-
33
- with gr.Tab("SMTP Test"):
34
- smtp_tester()
35
-
36
- with gr.Tab("Translator"):
37
- translator()
38
-
39
- with gr.Tab("URL Shortner"):
40
- url_shortner()
41
-
42
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.data import data_converter
3
+ from modules.exif import clexif
4
+ from modules.gif import video2gif
5
+ from modules.github import github_release_creator
6
+ from modules.qr import qrcode
7
+ from modules.rct import rct_generator
8
+ from modules.smtp import smtp_tester
9
+ from modules.trans import translator
10
+ from modules.url import url_shortner
11
+ from utils import LANG
12
+
13
+ ZH2EN = {
14
+ "# 在线工具合集": "# Online Tools Collection",
15
+ "数据文件转换": "Data Converter",
16
+ "图片 EXIF 清理": "Image EXIF Cleaner",
17
+ "视频转 GIF 动图": "Video to GIF",
18
+ "GitHub 发布工具": "GitHub Releaser",
19
+ "二维码生成": "QR Code",
20
+ "随机对照试验生成": "RCT Generator",
21
+ "SMTP 测试": "SMTP Test",
22
+ "翻译器": "Translator",
23
+ "短链接生成": "URL Shortner",
24
+ }
25
+
26
+
27
+ def _L(zh_txt: str):
28
+ if LANG:
29
+ return ZH2EN[zh_txt]
30
+ else:
31
+ return zh_txt
32
+
33
+
34
+ if __name__ == "__main__":
35
+ with gr.Blocks() as demo:
36
+ gr.Markdown(_L("# 在线工具合集"))
37
+ with gr.Tab(_L("数据文件转换")):
38
+ data_converter()
39
+
40
+ with gr.Tab(_L("图片 EXIF 清理")):
41
+ clexif()
42
+
43
+ with gr.Tab(_L("视频转 GIF 动图")):
44
+ video2gif()
45
+
46
+ with gr.Tab(_L("GitHub 发布工具")):
47
+ github_release_creator()
48
+
49
+ with gr.Tab(_L("二维码生成")):
50
+ qrcode()
51
+
52
+ with gr.Tab(_L("随机对照试验生成")):
53
+ rct_generator()
54
+
55
+ with gr.Tab(_L("SMTP 测试")):
56
+ smtp_tester()
57
+
58
+ with gr.Tab(_L("翻译器")):
59
+ translator()
60
+
61
+ with gr.Tab(_L("短链接生成")):
62
+ url_shortner()
63
+
64
+ demo.launch()
config.py DELETED
@@ -1,13 +0,0 @@
1
- import os
2
-
3
- TMP_DIR = "./__pycache__"
4
-
5
- TAB_CONFIG = ["jsonl ⇆ csv", "json ⇆ csv", "json ⇆ jsonl"]
6
- MODE = {"from": "jsonl", "to": "csv"}
7
- HEADER = {
8
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0",
9
- }
10
-
11
- API_SMTP = os.getenv("api_smtp")
12
- API_TRANS = os.getenv("api_caiyun")
13
- KEY_TRANS = os.getenv("apikey_caiyun")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data.py → modules/data.py RENAMED
@@ -1,18 +1,29 @@
1
- import os
2
  import csv
3
  import json
4
- import shutil
5
  import gradio as gr
6
  import pandas as pd
7
- from config import TMP_DIR, TAB_CONFIG, MODE
8
-
9
-
10
- def clean_cache(dir_path=TMP_DIR):
11
- if os.path.exists(dir_path):
12
- shutil.rmtree(dir_path)
13
-
14
- if not os.path.exists(dir_path):
15
- os.makedirs(dir_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
 
18
  def encoder_json(file_path: str):
@@ -26,6 +37,7 @@ def encoder_jsonl(file_path: str):
26
  data_list = []
27
  with open(file_path, "r", encoding="utf-8") as file:
28
  for line in file:
 
29
  json_data = json.loads(line.strip())
30
  data_list.append(json_data)
31
 
@@ -34,45 +46,55 @@ def encoder_jsonl(file_path: str):
34
 
35
  def encoder_csv(file_path: str):
36
  data_list = []
37
- with open(file_path, "r", encoding="utf-8") as file:
38
- csv_reader = csv.DictReader(file)
39
- for row in csv_reader:
40
- data_list.append(dict(row))
 
 
 
 
 
 
 
41
 
42
  return data_list
43
 
44
 
45
- def decoder_json(data_list: list, file_path=f"{TMP_DIR}/output.json"):
46
  if data_list:
47
  with open(file_path, "w", encoding="utf-8") as file:
 
48
  json.dump(data_list, file, ensure_ascii=False, indent=4)
49
 
50
  return file_path
51
 
52
 
53
- def decoder_csv(data_list: list, file_path=f"{TMP_DIR}/output.csv"):
54
- if data_list:
55
  header = list(data_list[0].keys())
56
  with open(file_path, "w", newline="", encoding="utf-8") as file:
57
- csv_writer = csv.writer(file)
58
- csv_writer.writerow(header)
59
  for item in data_list:
60
  csv_writer.writerow([item[key] for key in header])
61
 
62
  return file_path
63
 
64
 
65
- def decoder_jsonl(data_list: list, file_path=f"{TMP_DIR}/output.jsonl"):
66
  if data_list:
67
  with open(file_path, "w", encoding="utf-8") as file:
68
  for data in data_list:
 
69
  json_line = json.dumps(data, ensure_ascii=False)
70
- file.write(json_line + "\n")
71
 
72
  return file_path
73
 
74
 
75
  def change_mode(input: str):
 
76
  affix = input.split(" ")
77
  if affix[1] == "→":
78
  MODE["from"] = affix[0]
@@ -83,20 +105,27 @@ def change_mode(input: str):
83
  MODE["to"] = affix[0]
84
 
85
 
86
- def infer(input_file: str):
87
- clean_cache()
 
 
88
  try:
89
- data_list = eval(f'encoder_{MODE["from"]}')(input_file)
90
- output_file = eval(f'decoder_{MODE["to"]}')(data_list)
91
- return output_file, pd.DataFrame(data_list)
 
 
 
92
 
93
  except Exception as e:
94
- return None, pd.DataFrame([{"Please upload a standard data file": f"{e}"}])
 
 
95
 
96
 
97
- def data_converter():
98
  with gr.Blocks() as data:
99
- for item in TAB_CONFIG:
100
  types = item.split(" ⇆ ")
101
  with gr.Tab(item) as tab:
102
  with gr.Row():
@@ -106,55 +135,57 @@ def data_converter():
106
  f"{types[0]} → {types[1]}",
107
  f"{types[0]} ← {types[1]}",
108
  ],
109
- label="Mode",
110
  value=f"{types[0]} → {types[1]}",
111
  )
112
- input_file = gr.components.File(
113
  type="filepath",
114
- label="Upload input file",
115
  file_types=[f".{types[0]}", f".{types[1]}"],
116
  )
117
- convert_btn = gr.Button("Convert")
118
 
119
  with gr.Column():
120
- output_file = gr.components.File(
121
- type="filepath", label="Download output file"
 
122
  )
123
- data_viewer = gr.Dataframe(label="Data viewer")
 
124
 
125
  option.change(change_mode, inputs=option)
126
  tab.select(change_mode, inputs=option)
127
  convert_btn.click(
128
  infer,
129
  inputs=input_file,
130
- outputs=[output_file, data_viewer],
131
  )
132
 
133
  gr.Markdown(
134
- """
135
- ## Supported JSON format
136
  ```
137
  [
138
- {
139
  "key1": "val11",
140
  "key2": "val12",
141
  ...
142
- },
143
- {
144
  "key1": "val21",
145
  "key2": "val22",
146
  ...
147
- },
148
  ...
149
  ]
150
  ```
151
- ## Supported jsonl format
152
  ```
153
- {"key1": "val11", "key2": "val12", ...}
154
- {"key1": "val21", "key2": "val22", ...}
155
  ...
156
  ```
157
- ## Supported CSV format
158
  ```
159
  key1, key2, ...
160
  val11, val12, ...
 
 
1
  import csv
2
  import json
 
3
  import gradio as gr
4
  import pandas as pd
5
+ from utils import clean_dir, TMP_DIR, LANG
6
+
7
+
8
+ MODE = {"from": "jsonl", "to": "csv"}
9
+ ZH2EN = {
10
+ "模式": "Mode",
11
+ "上传原数据": "Upload input file",
12
+ "转换": "Convert",
13
+ "下载转换数据": "Download output file",
14
+ "数据预览": "Data viewer",
15
+ "支持的 JSON 格式": "Supported JSON format",
16
+ "支持的 JSON Lines 格式": "Supported jsonl format",
17
+ "支持的 CSV 格式": "Supported CSV format",
18
+ "状态栏": "Status",
19
+ }
20
+
21
+
22
+ def _L(zh_txt: str):
23
+ if LANG:
24
+ return ZH2EN[zh_txt]
25
+ else:
26
+ return zh_txt
27
 
28
 
29
  def encoder_json(file_path: str):
 
37
  data_list = []
38
  with open(file_path, "r", encoding="utf-8") as file:
39
  for line in file:
40
+ # 加载每一行的 JSON 数据
41
  json_data = json.loads(line.strip())
42
  data_list.append(json_data)
43
 
 
46
 
47
  def encoder_csv(file_path: str):
48
  data_list = []
49
+ try:
50
+ with open(file_path, "r", encoding="utf-8") as file:
51
+ csv_reader = csv.DictReader(file)
52
+ for row in csv_reader:
53
+ data_list.append(dict(row))
54
+
55
+ except UnicodeDecodeError:
56
+ with open(file_path, "r", encoding="GBK") as file:
57
+ csv_reader = csv.DictReader(file)
58
+ for row in csv_reader:
59
+ data_list.append(dict(row))
60
 
61
  return data_list
62
 
63
 
64
+ def decoder_json(data_list: list, file_path: str):
65
  if data_list:
66
  with open(file_path, "w", encoding="utf-8") as file:
67
+ # 将整个列表转换成 JSON 格式并写入文件
68
  json.dump(data_list, file, ensure_ascii=False, indent=4)
69
 
70
  return file_path
71
 
72
 
73
+ def decoder_csv(data_list: list, file_path: str):
74
+ if data_list: # 提取第一个字典的键作为表头
75
  header = list(data_list[0].keys())
76
  with open(file_path, "w", newline="", encoding="utf-8") as file:
77
+ csv_writer = csv.writer(file) # 写入表头
78
+ csv_writer.writerow(header) # 逐项写入字典的值
79
  for item in data_list:
80
  csv_writer.writerow([item[key] for key in header])
81
 
82
  return file_path
83
 
84
 
85
+ def decoder_jsonl(data_list: list, file_path: str):
86
  if data_list:
87
  with open(file_path, "w", encoding="utf-8") as file:
88
  for data in data_list:
89
+ # 将每个 JSON 对象转换成字符串并写入文件,每行一个对象
90
  json_line = json.dumps(data, ensure_ascii=False)
91
+ file.write(f"{json_line}\n")
92
 
93
  return file_path
94
 
95
 
96
  def change_mode(input: str):
97
+ global MODE
98
  affix = input.split(" ")
99
  if affix[1] == "→":
100
  MODE["from"] = affix[0]
 
105
  MODE["to"] = affix[0]
106
 
107
 
108
+ # outer func
109
+ def infer(input_file: str, cache=f"{TMP_DIR}/data"):
110
+ status = "Success"
111
+ output_file = previews = None
112
  try:
113
+ clean_dir(cache)
114
+ src_fmt = MODE["from"]
115
+ dst_fmt = MODE["to"]
116
+ data_list = eval(f"encoder_{src_fmt}")(input_file)
117
+ output_file = eval(f"decoder_{dst_fmt}")(data_list, f"{cache}/output.{dst_fmt}")
118
+ previews = pd.DataFrame(data_list)
119
 
120
  except Exception as e:
121
+ status = f"{e}"
122
+
123
+ return status, output_file, previews
124
 
125
 
126
+ def data_converter(tab_cfgs=["jsonl ⇆ csv", "json ⇆ csv", "json ⇆ jsonl"]):
127
  with gr.Blocks() as data:
128
+ for item in tab_cfgs:
129
  types = item.split(" ⇆ ")
130
  with gr.Tab(item) as tab:
131
  with gr.Row():
 
135
  f"{types[0]} → {types[1]}",
136
  f"{types[0]} ← {types[1]}",
137
  ],
138
+ label=_L("模式"),
139
  value=f"{types[0]} → {types[1]}",
140
  )
141
+ input_file = gr.File(
142
  type="filepath",
143
+ label=_L("上传原数据"),
144
  file_types=[f".{types[0]}", f".{types[1]}"],
145
  )
146
+ convert_btn = gr.Button(_L("转换"))
147
 
148
  with gr.Column():
149
+ status_bar = gr.Textbox(
150
+ label=_L("状态栏"),
151
+ show_copy_button=True,
152
  )
153
+ output_file = gr.File(type="filepath", label=_L("下载转换数据"))
154
+ data_viewer = gr.Dataframe(label=_L("数据预览"))
155
 
156
  option.change(change_mode, inputs=option)
157
  tab.select(change_mode, inputs=option)
158
  convert_btn.click(
159
  infer,
160
  inputs=input_file,
161
+ outputs=[status_bar, output_file, data_viewer],
162
  )
163
 
164
  gr.Markdown(
165
+ f"""
166
+ ## {_L('支持的 JSON 格式')}
167
  ```
168
  [
169
+ {{
170
  "key1": "val11",
171
  "key2": "val12",
172
  ...
173
+ }},
174
+ {{
175
  "key1": "val21",
176
  "key2": "val22",
177
  ...
178
+ }},
179
  ...
180
  ]
181
  ```
182
+ ## {_L('支持的 JSON Lines 格式')}
183
  ```
184
+ {{"key1": "val11", "key2": "val12", ...}}
185
+ {{"key1": "val21", "key2": "val22", ...}}
186
  ...
187
  ```
188
+ ## {_L('支持的 CSV 格式')}
189
  ```
190
  key1, key2, ...
191
  val11, val12, ...
exif.py → modules/exif.py RENAMED
@@ -1,16 +1,35 @@
1
  import os
2
  import imghdr
3
- import shutil
4
  import hashlib
5
- import zipfile
6
  import exifread
7
  import gradio as gr
8
  import pandas as pd
9
  from PIL import Image
10
- from config import TMP_DIR
11
-
12
-
13
- def get_exif_data(origin_file_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  with open(origin_file_path, "rb") as image_file:
15
  tags = exifread.process_file(image_file)
16
 
@@ -22,112 +41,91 @@ def get_exif_data(origin_file_path):
22
  return output
23
 
24
 
25
- def clear_exif_data(image_path: str, img_mode=None, outdir=""):
26
- save_path = f"{TMP_DIR}/{outdir}output." + image_path.split(".")[-1]
27
- try:
28
- img = Image.open(image_path)
29
- data = list(img.getdata())
30
- if img_mode:
31
- save_path = (
32
- f"{TMP_DIR}/{outdir}{hashlib.md5(image_path.encode()).hexdigest()}.jpg"
33
- )
34
- else:
35
- img_mode = img.mode
36
-
37
- img_without_exif = Image.new(img_mode, img.size)
38
- img_without_exif.putdata(data)
39
- img_without_exif.save(save_path)
40
-
41
- except Exception as e:
42
- print(f"\n{image_path} Error: {e}")
43
 
 
 
 
44
  return save_path
45
 
46
 
47
- def unzip_file(zip_path: str, extract_to=f"{TMP_DIR}/inputs"):
48
- if not os.path.exists(extract_to):
49
- os.makedirs(extract_to)
50
-
51
- # 打开ZIP文件
52
- with zipfile.ZipFile(zip_path, "r") as zip_ref:
53
- # 解压文件
54
- zip_ref.extractall(extract_to)
55
-
56
- return extract_to
57
-
58
-
59
- def find_files(directory: str):
60
- found_files = []
61
- for root, _, files in os.walk(directory):
62
  for file in files:
63
  fpath = os.path.join(root, file).replace("\\", "/")
64
  if imghdr.what(fpath) != None:
65
- found_files.append(fpath)
66
-
67
- return found_files
68
-
69
-
70
- def compress(folder_path=f"{TMP_DIR}/outputs", zip_file_path=f"{TMP_DIR}/outputs.zip"):
71
- if not os.path.exists(folder_path):
72
- print(f"Error: Folder '{folder_path}' does not exist.")
73
- return
74
-
75
- with zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED) as zipf:
76
- for root, _, files in os.walk(folder_path):
77
- for file in files:
78
- file_path = os.path.join(root, file)
79
- relative_path = os.path.relpath(file_path, folder_path)
80
- zipf.write(
81
- file_path,
82
- arcname=os.path.join(os.path.basename(folder_path), relative_path),
83
- )
84
 
85
- return zip_file_path
86
 
87
 
88
- def infer(image_path: str, original_ext: bool):
89
- if not image_path or imghdr.what(image_path) == None:
90
- return None, "Please input a picture!"
91
-
92
- if os.path.exists(TMP_DIR):
93
- shutil.rmtree(TMP_DIR)
 
94
 
95
- os.makedirs(TMP_DIR, exist_ok=True)
96
- return clear_exif_data(
97
- image_path, img_mode="RGB" if not original_ext else None
98
- ), get_exif_data(image_path)
99
 
 
 
100
 
101
- def batch_infer(imgs_zip: str, original_ext: bool):
102
- if not imgs_zip:
103
- return None, pd.DataFrame([{"Warning": "Please upload pictures zip!"}])
104
 
105
- if os.path.exists(TMP_DIR):
106
- shutil.rmtree(TMP_DIR)
107
 
108
- os.makedirs(f"{TMP_DIR}/outputs", exist_ok=True)
109
- extract_to = unzip_file(imgs_zip)
110
- imgs = find_files(extract_to)
111
- mode = "RGB" if not original_ext else None
112
- exifs = []
113
- for img in imgs:
114
- clear_exif_data(img, img_mode=mode, outdir="outputs/")
115
- exifs.append({"filename": os.path.basename(img), "exif": get_exif_data(img)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
- if not exifs:
118
- exifs = [{"Warning": "No picture in the zip"}]
119
 
120
- return compress(), pd.DataFrame(exifs)
121
 
122
 
123
  def clexif():
124
  with gr.Blocks() as iface:
125
- with gr.Tab("Process single picture"):
126
  gr.Interface(
127
  fn=infer,
128
  inputs=[
129
  gr.File(
130
- label="Upload picture",
131
  file_types=[
132
  ".jpg",
133
  ".jpeg",
@@ -141,43 +139,47 @@ def clexif():
141
  ],
142
  ),
143
  gr.Checkbox(
144
- label="Export original format",
145
  value=False,
146
  ),
147
  ],
148
  outputs=[
 
149
  gr.Image(
150
- label="Download cleaned picture",
151
  type="filepath",
152
  show_share_button=False,
153
  ),
154
  gr.Textbox(label="EXIF", show_copy_button=True),
155
  ],
156
- allow_flagging="never",
157
  )
158
 
159
- with gr.Tab("Batch processor"):
160
  gr.Interface(
161
  fn=batch_infer,
162
  inputs=[
163
  gr.File(
164
- label="Upload pictures zip",
165
  file_types=[".zip"],
166
  ),
167
  gr.Checkbox(
168
- label="Export original format",
169
  value=False,
170
  ),
171
  ],
172
  outputs=[
 
173
  gr.File(
174
- label="Download cleaned pictures",
175
  type="filepath",
176
  ),
177
- gr.Dataframe(label="EXIF list"),
178
  ],
179
- description="When uploading pictures zip, please make sure the zip is completely uploaded before clicking Submit",
180
- allow_flagging="never",
 
 
181
  )
182
 
183
  return iface
 
1
  import os
2
  import imghdr
 
3
  import hashlib
 
4
  import exifread
5
  import gradio as gr
6
  import pandas as pd
7
  from PIL import Image
8
+ from utils import clean_dir, compress, mk_dir, unzip, TMP_DIR, LANG
9
+
10
+ ZH2EN = {
11
+ "单图片处理": "Process single picture",
12
+ "上传图片": "Upload picture",
13
+ "导出原格式": "Export original format",
14
+ "下载清理 EXIF 后的图片": "Download cleaned picture",
15
+ "批量处理": "Batch processor",
16
+ "上传包含多图片的 zip 压缩包": "Upload pictures zip",
17
+ "导出原格式": "Export original format",
18
+ "下载清理 EXIF 后的多图片压缩包": "Download cleaned pictures",
19
+ "EXIF 列表": "EXIF list",
20
+ "上传包含多图片的 zip 压缩包时确保上传进度至 100% 后再提交": "When uploading pictures zip, please make sure the zip is completely uploaded before clicking Submit",
21
+ "状态栏": "Status",
22
+ }
23
+
24
+
25
+ def _L(zh_txt: str):
26
+ if LANG:
27
+ return ZH2EN[zh_txt]
28
+ else:
29
+ return zh_txt
30
+
31
+
32
+ def get_exif(origin_file_path):
33
  with open(origin_file_path, "rb") as image_file:
34
  tags = exifread.process_file(image_file)
35
 
 
41
  return output
42
 
43
 
44
+ def clear_exif(img_path: str, cache: str, img_mode=None, outdir=""):
45
+ save_path = f"{cache}/{outdir}output." + img_path.split(".")[-1]
46
+ img = Image.open(img_path)
47
+ data = list(img.getdata())
48
+ if img_mode:
49
+ save_path = f"{cache}/{outdir}{hashlib.md5(img_path.encode()).hexdigest()}.jpg"
50
+ else:
51
+ img_mode = img.mode
 
 
 
 
 
 
 
 
 
 
52
 
53
+ img_without_exif = Image.new(img_mode, img.size)
54
+ img_without_exif.putdata(data)
55
+ img_without_exif.save(save_path)
56
  return save_path
57
 
58
 
59
+ def find_images(dir_path: str):
60
+ found_images = []
61
+ for root, _, files in os.walk(dir_path):
 
 
 
 
 
 
 
 
 
 
 
 
62
  for file in files:
63
  fpath = os.path.join(root, file).replace("\\", "/")
64
  if imghdr.what(fpath) != None:
65
+ found_images.append(fpath)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ return found_images
68
 
69
 
70
+ # outer func
71
+ def infer(img_path: str, keep_ext: bool, cache=f"{TMP_DIR}/exif"):
72
+ status = "Success"
73
+ out_img = out_exif = None
74
+ try:
75
+ if not img_path or imghdr.what(img_path) == None:
76
+ raise ValueError("请输入图片!")
77
 
78
+ clean_dir(cache)
79
+ img_mode = "RGB" if not keep_ext else None
80
+ out_img = clear_exif(img_path, cache, img_mode)
81
+ out_exif = get_exif(img_path)
82
 
83
+ except Exception as e:
84
+ status = f"{e}"
85
 
86
+ return status, out_img, out_exif
 
 
87
 
 
 
88
 
89
+ # outer func
90
+ def batch_infer(imgs_zip: str, keep_ext: bool, cache=f"{TMP_DIR}/exif"):
91
+ status = "Success"
92
+ out_images = out_exifs = None
93
+ try:
94
+ if not imgs_zip:
95
+ raise ValueError("Please upload pictures zip!")
96
+
97
+ clean_dir(cache)
98
+ mk_dir(f"{cache}/outputs")
99
+ extract_to = f"{cache}/inputs"
100
+ unzip(imgs_zip, extract_to)
101
+ imgs = find_images(extract_to)
102
+ img_mode = "RGB" if not keep_ext else None
103
+ exifs = []
104
+ for img in imgs:
105
+ clear_exif(img, cache, img_mode, "outputs/")
106
+ exifs.append({"filename": os.path.basename(img), "exif": get_exif(img)})
107
+
108
+ if not exifs:
109
+ raise ValueError("No picture in the zip")
110
+
111
+ out_images = f"{cache}/outputs.zip"
112
+ compress(f"{cache}/outputs", out_images)
113
+ out_exifs = pd.DataFrame(exifs)
114
 
115
+ except Exception as e:
116
+ status = f"{e}"
117
 
118
+ return status, out_images, out_exifs
119
 
120
 
121
  def clexif():
122
  with gr.Blocks() as iface:
123
+ with gr.Tab(_L("单图片处理")):
124
  gr.Interface(
125
  fn=infer,
126
  inputs=[
127
  gr.File(
128
+ label=_L("上传图片"),
129
  file_types=[
130
  ".jpg",
131
  ".jpeg",
 
139
  ],
140
  ),
141
  gr.Checkbox(
142
+ label=_L("导出原格式"),
143
  value=False,
144
  ),
145
  ],
146
  outputs=[
147
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
148
  gr.Image(
149
+ label=_L("下载清理 EXIF 后的图片"),
150
  type="filepath",
151
  show_share_button=False,
152
  ),
153
  gr.Textbox(label="EXIF", show_copy_button=True),
154
  ],
155
+ flagging_mode="never",
156
  )
157
 
158
+ with gr.Tab(_L("批量处理")):
159
  gr.Interface(
160
  fn=batch_infer,
161
  inputs=[
162
  gr.File(
163
+ label=_L("上传包含多图片的 zip 压缩包"),
164
  file_types=[".zip"],
165
  ),
166
  gr.Checkbox(
167
+ label=_L("导出原格式"),
168
  value=False,
169
  ),
170
  ],
171
  outputs=[
172
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
173
  gr.File(
174
+ label=_L("下载清理 EXIF 后的多图片压缩包"),
175
  type="filepath",
176
  ),
177
+ gr.Dataframe(label=_L("EXIF 列表")),
178
  ],
179
+ description=_L(
180
+ "上传包含多图片的 zip 压缩包时确保上传进度至 100% 后再提交"
181
+ ),
182
+ flagging_mode="never",
183
  )
184
 
185
  return iface
gif.py → modules/gif.py RENAMED
@@ -1,45 +1,44 @@
1
  import os
2
  import math
3
- import shutil
4
  import gradio as gr
5
  from PIL import Image, ImageSequence
6
  from moviepy.editor import VideoFileClip
7
- from config import TMP_DIR
8
 
9
 
10
- def clean_cache(tmp_dir=TMP_DIR):
11
- if os.path.exists(tmp_dir):
12
- shutil.rmtree(tmp_dir)
 
 
 
 
 
13
 
14
- os.mkdir(tmp_dir)
15
- return f"{tmp_dir}/input.gif"
16
 
 
 
 
 
 
17
 
18
- def get_frame_duration(gif: Image):
19
- duration = gif.info.get("duration", 100)
20
- return [
21
- frame.info.get("duration", duration) for frame in ImageSequence.Iterator(gif)
22
- ]
23
 
 
 
 
 
 
24
 
25
- def resize_gif(
26
- target_width: int,
27
- target_height: int,
28
- input_path=f"{TMP_DIR}/input.gif",
29
- output_path=f"{TMP_DIR}/output.gif",
30
- ):
31
- # Open the GIF image
32
  gif = Image.open(input_path)
33
- # Create a list to hold the modified frames
34
  modified_frames = []
35
- # Loop through each frame of the GIF
36
  for frame in ImageSequence.Iterator(gif):
37
- # Resize the frame
38
  resized_frame = frame.resize((target_width, target_height), Image.LANCZOS)
39
- # Append the resized frame to the list
40
  modified_frames.append(resized_frame)
41
 
42
- frame_durations = get_frame_duration(gif)
 
43
  modified_frames[0].save(
44
  output_path,
45
  format="GIF",
@@ -49,45 +48,50 @@ def resize_gif(
49
  loop=0,
50
  )
51
 
52
- return output_path
53
-
54
 
55
- def infer(video_path: str, speed: float):
56
- target_w = 640
57
- gif_path = clean_cache()
 
58
  try:
 
59
  with VideoFileClip(video_path, audio_fps=16000) as clip:
60
  if clip.duration > 5:
61
- raise ValueError("The uploaded video is too long")
62
 
63
- # clip.write_gif(gif_path, fps=12, progress_bar=True)
64
- clip.speedx(speed).to_gif(gif_path, fps=12)
65
  w, h = clip.size
66
 
 
 
67
  target_h = math.ceil(target_w * h / w)
68
- return os.path.basename(video_path), resize_gif(target_w, target_h)
 
69
 
70
  except Exception as e:
71
- return f"{e}", None
 
 
72
 
73
 
74
  def video2gif():
75
  return gr.Interface(
76
  fn=infer,
77
  inputs=[
78
- gr.Video(label="Upload video"),
79
- gr.Slider(
80
- label="Speed",
81
- minimum=0.5,
82
- maximum=2.0,
83
- step=0.25,
84
- value=1.0,
85
- ),
86
  ],
87
  outputs=[
88
- gr.Textbox(label="Filename", show_copy_button=True),
89
- gr.Image(label="Download GIF", type="filepath", show_share_button=False),
 
90
  ],
91
- description="Please make sure the video is completely uploaded before clicking Submit, you can crop it online first if the video size is >5s",
92
  flagging_mode="never",
 
 
 
 
 
 
93
  )
 
1
  import os
2
  import math
 
3
  import gradio as gr
4
  from PIL import Image, ImageSequence
5
  from moviepy.editor import VideoFileClip
6
+ from utils import clean_dir, TMP_DIR, LANG
7
 
8
 
9
+ ZH2EN = {
10
+ "上传视频": "Upload video",
11
+ "倍速": "Speed",
12
+ "状态栏": "Status",
13
+ "文件名": "Filename",
14
+ "下载动图": "Download GIF",
15
+ "请确保视频上传完整后再点击提交,若时长大于五秒可先在线裁剪": "Please make sure the video is completely uploaded before clicking Submit, you can crop it online first if the video size is >5s",
16
+ }
17
 
 
 
18
 
19
+ def _L(zh_txt: str):
20
+ if LANG:
21
+ return ZH2EN[zh_txt]
22
+ else:
23
+ return zh_txt
24
 
 
 
 
 
 
25
 
26
+ def get_frame_dur(gif: Image):
27
+ # 获取 GIF 图像中第一帧的 duration
28
+ dur = gif.info.get("duration", 100)
29
+ # 返回每一帧的 duration
30
+ return [frame.info.get("duration", dur) for frame in ImageSequence.Iterator(gif)]
31
 
32
+
33
+ def resize_gif(target_width: int, target_height: int, input_path, output_path):
 
 
 
 
 
34
  gif = Image.open(input_path)
 
35
  modified_frames = []
 
36
  for frame in ImageSequence.Iterator(gif):
 
37
  resized_frame = frame.resize((target_width, target_height), Image.LANCZOS)
 
38
  modified_frames.append(resized_frame)
39
 
40
+ frame_durations = get_frame_dur(gif)
41
+ # 将修改后的帧作为新的 GIF 保存
42
  modified_frames[0].save(
43
  output_path,
44
  format="GIF",
 
48
  loop=0,
49
  )
50
 
 
 
51
 
52
+ # outer func
53
+ def infer(video_path: str, speed: float, target_w=640, cache=f"{TMP_DIR}/gif"):
54
+ status = "Success"
55
+ gif_name = out_gif = None
56
  try:
57
+ clean_dir(cache)
58
  with VideoFileClip(video_path, audio_fps=16000) as clip:
59
  if clip.duration > 5:
60
+ raise ValueError("上传的视频过长")
61
 
62
+ clip.speedx(speed).to_gif(f"{cache}/input.gif", fps=12)
 
63
  w, h = clip.size
64
 
65
+ in_gif = f"{cache}/input.gif"
66
+ out_gif = f"{cache}/output.gif"
67
  target_h = math.ceil(target_w * h / w)
68
+ gif_name = os.path.basename(video_path)
69
+ resize_gif(target_w, target_h, in_gif, out_gif)
70
 
71
  except Exception as e:
72
+ status = f"{e}"
73
+
74
+ return status, gif_name, out_gif
75
 
76
 
77
  def video2gif():
78
  return gr.Interface(
79
  fn=infer,
80
  inputs=[
81
+ gr.Video(label=_L("上传视频")),
82
+ gr.Slider(label=_L("倍速"), minimum=0.5, maximum=2.0, step=0.25, value=1.0),
 
 
 
 
 
 
83
  ],
84
  outputs=[
85
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
86
+ gr.Textbox(label=_L("文件名"), show_copy_button=True),
87
+ gr.Image(label=_L("下载动图"), type="filepath", show_share_button=False),
88
  ],
89
+ description=_L("请确保视频上传完整后再点击提交,若时长大于五秒可先在线裁剪"),
90
  flagging_mode="never",
91
+ examples=[
92
+ [
93
+ "https://www.modelscope.cn/studio/Genius-Society/online_tools/resolve/master/examples/herta.mp4",
94
+ 2,
95
+ ]
96
+ ],
97
  )
github.py → modules/github.py RENAMED
@@ -1,47 +1,61 @@
1
  import os
2
  import requests
3
  import gradio as gr
 
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- def create_github_release(
7
- owner: str,
8
- repo: str,
9
- token: str,
10
- release_tag: str,
11
- release_name: str,
12
- release_description: str,
13
- files: list,
14
- ):
15
  try:
16
  # 创建 Release
17
- release_response = requests.post(
18
  f"https://api.github.com/repos/{owner}/{repo}/releases",
19
  headers={
20
  "Authorization": f"token {token}",
21
  "Accept": "application/vnd.github.v3+json",
22
  },
23
  json={
24
- "tag_name": release_tag,
25
- "name": release_name,
26
- "body": release_description,
27
  "draft": False,
28
  "prerelease": False,
29
  },
30
  )
31
 
32
- if release_response.status_code != 201:
33
- return f"Failed to create release: {release_response.status_code}, {release_response.json()}"
34
 
35
  # 获取上传 URL
36
- release = release_response.json()
37
  upload_url = release["upload_url"].split("{")[0]
38
 
39
  # 上传多个二进制文件
40
- results = []
41
  for file_path in files:
42
  file_name = os.path.basename(file_path)
43
  with open(file_path, "rb") as binary_file:
44
- upload_response = requests.post(
45
  f"{upload_url}?name={file_name}",
46
  headers={
47
  "Authorization": f"token {token}",
@@ -50,42 +64,34 @@ def create_github_release(
50
  data=binary_file,
51
  )
52
 
53
- if upload_response.status_code == 201:
54
- results.append(f"Binary file '{file_name}' uploaded successfully!")
55
  else:
56
- results.append(
57
- f"Failed to upload binary file '{file_name}': {upload_response.status_code}, {upload_response.json()}"
58
- )
59
 
60
- return "\n".join(results)
61
 
62
  except Exception as e:
63
- return f"Release failed: {e}"
64
 
65
 
66
  def github_release_creator():
67
  return gr.Interface(
68
  fn=create_github_release,
69
  inputs=[
70
- gr.Textbox(
71
- label="GitHub Owner",
72
- placeholder="username / organization name",
73
- ),
74
- gr.Textbox(label="GitHub Repo", placeholder="repo name"),
75
  gr.Textbox(
76
  label="GitHub Token",
77
- placeholder="personal access token",
78
  type="password",
79
  ),
80
- gr.Textbox(label="Release Tag", placeholder="v1.0.0"),
81
- gr.Textbox(label="Release Name", placeholder="My New Release"),
82
- gr.TextArea(
83
- label="Describe this release",
84
- placeholder="Release with binary file(s) and source code.",
85
- ),
86
- gr.File(label="Binary File(s)", file_count="multiple"),
87
  ],
88
- outputs=gr.TextArea(label="Status", show_copy_button=True),
89
- description="Upload binary file(s) to create a new GitHub release.",
90
  flagging_mode="never",
91
  )
 
1
  import os
2
  import requests
3
  import gradio as gr
4
+ from utils import LANG
5
 
6
+ ZH2EN = {
7
+ "仓库拥有者": "Repo owner",
8
+ "输入用户名或组织名": "Username / organization name",
9
+ "仓库名": "GitHub repo",
10
+ "输入仓库名": "Repo name",
11
+ "输入 personal access token": "Personal access token",
12
+ "发布标签": "Release tag",
13
+ "发布名": "Release title",
14
+ "发布描述": "Describe this release",
15
+ "上传发布文件(可多选)": "Binary File(s)",
16
+ "状态栏": "Status",
17
+ "上传文件创建一个新的 GitHub 发布": "Upload binary file(s) to create a new GitHub release.",
18
+ }
19
 
20
+
21
+ def _L(zh_txt: str):
22
+ if LANG:
23
+ return ZH2EN[zh_txt]
24
+ else:
25
+ return zh_txt
26
+
27
+
28
+ def create_github_release(owner, repo, token, tag, name, description, files):
29
  try:
30
  # 创建 Release
31
+ response = requests.post(
32
  f"https://api.github.com/repos/{owner}/{repo}/releases",
33
  headers={
34
  "Authorization": f"token {token}",
35
  "Accept": "application/vnd.github.v3+json",
36
  },
37
  json={
38
+ "tag_name": tag,
39
+ "name": name,
40
+ "body": description,
41
  "draft": False,
42
  "prerelease": False,
43
  },
44
  )
45
 
46
+ if response.status_code != 201:
47
+ raise ConnectionError(f"{response.status_code}: {response.json()}")
48
 
49
  # 获取上传 URL
50
+ release = response.json()
51
  upload_url = release["upload_url"].split("{")[0]
52
 
53
  # 上传多个二进制文件
54
+ results = ""
55
  for file_path in files:
56
  file_name = os.path.basename(file_path)
57
  with open(file_path, "rb") as binary_file:
58
+ upl_response = requests.post(
59
  f"{upload_url}?name={file_name}",
60
  headers={
61
  "Authorization": f"token {token}",
 
64
  data=binary_file,
65
  )
66
 
67
+ if upl_response.status_code == 201:
68
+ results += f"Upload '{file_name}' success!"
69
  else:
70
+ results += f"Failed to upload {file_name}: {upl_response.status_code}, {upl_response.json()}"
 
 
71
 
72
+ return results
73
 
74
  except Exception as e:
75
+ return f"{e}"
76
 
77
 
78
  def github_release_creator():
79
  return gr.Interface(
80
  fn=create_github_release,
81
  inputs=[
82
+ gr.Textbox(label=_L("仓库拥有者"), placeholder=_L("输入用户名或组织名")),
83
+ gr.Textbox(label=_L("仓库名"), placeholder=_L("输入仓库名")),
 
 
 
84
  gr.Textbox(
85
  label="GitHub Token",
86
+ placeholder=_L("输入 personal access token"),
87
  type="password",
88
  ),
89
+ gr.Textbox(label=_L("发布标签")),
90
+ gr.Textbox(label=_L("发布名")),
91
+ gr.TextArea(label=_L("发布描述")),
92
+ gr.File(label=_L("上传发布文件(可多选)"), file_count="multiple"),
 
 
 
93
  ],
94
+ outputs=gr.TextArea(label=_L("状态栏"), show_copy_button=True),
95
+ description=_L("上传文件创建一个新的 GitHub 发布"),
96
  flagging_mode="never",
97
  )
modules/qr.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from utils import LANG
3
+
4
+ ZH2EN = {
5
+ "二维码输出尺寸": "Image size",
6
+ "输入文本": "Input text",
7
+ "输出二维码": "QR code",
8
+ "输入文字在线生成二维码": "Enter text to generate a QR code.",
9
+ "状态栏": "Status",
10
+ }
11
+
12
+
13
+ def _L(zh_txt: str):
14
+ if LANG:
15
+ return ZH2EN[zh_txt]
16
+ else:
17
+ return zh_txt
18
+
19
+
20
+ def infer(img_size: int, input_txt: str):
21
+ status = "Success"
22
+ url = None
23
+ try:
24
+ if not input_txt:
25
+ raise ValueError("Please input text!")
26
+
27
+ url = f"https://api.qrserver.com/v1/create-qr-code/?size={img_size}x{img_size}&data={input_txt}"
28
+
29
+ except Exception as e:
30
+ status = f"{e}"
31
+
32
+ return status, url
33
+
34
+
35
+ def qrcode():
36
+ return gr.Interface(
37
+ fn=infer,
38
+ inputs=[
39
+ gr.Slider(35, 1000, 217, label=_L("二维码输出尺寸")),
40
+ gr.Textbox(label=_L("输入文本")),
41
+ ],
42
+ outputs=[
43
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
44
+ gr.Image(label=_L("输出二维码"), show_share_button=False),
45
+ ],
46
+ description=_L("输入文字在线生成二维码"),
47
+ flagging_mode="never",
48
+ )
rct.py → modules/rct.py RENAMED
@@ -1,26 +1,39 @@
1
- import os
2
  import csv
3
  import random
4
- import shutil
5
  import pandas as pd
6
  import gradio as gr
7
- from config import TMP_DIR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  def list_to_csv(list_of_dicts: list, filename: str):
11
  keys = dict(list_of_dicts[0]).keys()
 
12
  with open(filename, "w", newline="", encoding="utf-8") as csvfile:
13
  writer = csv.DictWriter(csvfile, fieldnames=keys)
14
  writer.writeheader()
15
  for data in list_of_dicts:
16
  writer.writerow(data)
17
 
18
- return filename
19
-
20
 
21
- def random_allocation(participants: int, ratio: list):
22
- total = sum(ratio)
23
  splits = [0]
 
24
  for i, r in enumerate(ratio):
25
  splits.append(splits[i] + int(1.0 * r / total * participants))
26
 
@@ -36,40 +49,47 @@ def random_allocation(participants: int, ratio: list):
36
  allocation.append({"id": participant, "group": i + 1})
37
 
38
  sorted_data = sorted(allocation, key=lambda x: x["id"])
39
- filename = list_to_csv(sorted_data, f"{TMP_DIR}/output.csv")
40
- return filename, pd.DataFrame(sorted_data)
41
 
42
 
43
- def infer(participants: float, ratios: str):
44
- if os.path.exists(TMP_DIR):
45
- shutil.rmtree(TMP_DIR)
46
-
47
- os.makedirs(TMP_DIR, exist_ok=True)
48
- ratio_list = ratios.split(":")
49
  ratio = []
 
 
 
50
  try:
 
 
51
  for r in ratio_list:
52
  current_ratio = float(r.strip())
53
  if current_ratio > 0:
54
  ratio.append(current_ratio)
55
 
56
- except Exception:
57
- print("Invalid input of ratio!")
 
 
 
58
 
59
- return random_allocation(int(participants), ratio)
60
 
61
 
62
  def rct_generator():
63
  return gr.Interface(
64
  fn=infer,
65
  inputs=[
66
- gr.Number(label="Number of participants", value=10),
67
- gr.Textbox(label="Grouping ratio", value="8:1:1"),
68
  ],
69
  outputs=[
70
- gr.File(label="Download data CSV"),
71
- gr.Dataframe(label="Data preview"),
 
72
  ],
73
- description="Enter the number of participants and the grouping ratio in the format of numbers separated by : to generate randomized controlled trial.",
 
 
74
  flagging_mode="never",
75
  )
 
 
1
  import csv
2
  import random
 
3
  import pandas as pd
4
  import gradio as gr
5
+ from utils import clean_dir, TMP_DIR, LANG
6
+
7
+ ZH2EN = {
8
+ "输入参与者数量": "Number of participants",
9
+ "输入分组比率": "Grouping ratio",
10
+ "状态栏": "Status",
11
+ "下载随机分组数据 CSV": "Download data CSV",
12
+ "随机分组数据预览": "Data preview",
13
+ "输入参与者数量和分组比率,格式为用:隔开的数字,生成随机分组数据。": "Enter the number of participants and the grouping ratio in the format of numbers separated by : to generate randomized controlled trial.",
14
+ }
15
+
16
+
17
+ def _L(zh_txt: str):
18
+ if LANG:
19
+ return ZH2EN[zh_txt]
20
+ else:
21
+ return zh_txt
22
 
23
 
24
  def list_to_csv(list_of_dicts: list, filename: str):
25
  keys = dict(list_of_dicts[0]).keys()
26
+ # 将列表中的字典写入 CSV 文件
27
  with open(filename, "w", newline="", encoding="utf-8") as csvfile:
28
  writer = csv.DictWriter(csvfile, fieldnames=keys)
29
  writer.writeheader()
30
  for data in list_of_dicts:
31
  writer.writerow(data)
32
 
 
 
33
 
34
+ def random_allocate(participants: int, ratio: list, out_csv: str):
 
35
  splits = [0]
36
+ total = sum(ratio)
37
  for i, r in enumerate(ratio):
38
  splits.append(splits[i] + int(1.0 * r / total * participants))
39
 
 
49
  allocation.append({"id": participant, "group": i + 1})
50
 
51
  sorted_data = sorted(allocation, key=lambda x: x["id"])
52
+ list_to_csv(sorted_data, out_csv)
53
+ return pd.DataFrame(sorted_data)
54
 
55
 
56
+ # outer func
57
+ def infer(participants: float, ratios: str, cache=f"{TMP_DIR}/rct"):
 
 
 
 
58
  ratio = []
59
+ previews = None
60
+ status = "Success"
61
+ out_csv = f"{cache}/output.csv"
62
  try:
63
+ ratio_list = ratios.split(":")
64
+ clean_dir(cache)
65
  for r in ratio_list:
66
  current_ratio = float(r.strip())
67
  if current_ratio > 0:
68
  ratio.append(current_ratio)
69
 
70
+ previews = random_allocate(int(participants), ratio, out_csv)
71
+
72
+ except Exception as e:
73
+ out_csv = None
74
+ status = f"{e}"
75
 
76
+ return status, out_csv, previews
77
 
78
 
79
  def rct_generator():
80
  return gr.Interface(
81
  fn=infer,
82
  inputs=[
83
+ gr.Number(label=_L("输入参与者数量"), value=10),
84
+ gr.Textbox(label=_L("输入分组比率"), value="8:1:1"),
85
  ],
86
  outputs=[
87
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
88
+ gr.File(label=_L("下载随机分组数据 CSV")),
89
+ gr.Dataframe(label=_L("随机分组数据预览")),
90
  ],
91
+ description=_L(
92
+ "输入参与者数量和分组比率,格式为用:隔开的数字,生成随机分组数据。"
93
+ ),
94
  flagging_mode="never",
95
  )
modules/smtp.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import gradio as gr
3
+ from utils import API_SMTP, LANG
4
+
5
+ ZH2EN = {
6
+ "收信人邮箱": "To email",
7
+ "标题": "Title",
8
+ "测试标题": "Test title",
9
+ "正文": "Content",
10
+ "测试正文": "Test content",
11
+ "发信人昵称": "Sender name",
12
+ "测试昵称": "Test nickname",
13
+ "发信人邮箱": "From email",
14
+ "应用密钥": "API key",
15
+ "SMTP服务器": "SMTP host",
16
+ "端口": "Port",
17
+ "发送状态": "Status",
18
+ }
19
+
20
+
21
+ def _L(zh_txt: str):
22
+ if LANG:
23
+ return ZH2EN[zh_txt]
24
+ else:
25
+ return zh_txt
26
+
27
+
28
+ def infer(target, title, content, name, email, password, host, port):
29
+ try:
30
+ response = requests.get(
31
+ API_SMTP,
32
+ params={
33
+ "host": host,
34
+ "Port": port,
35
+ "key": password, # apikey
36
+ "email": email, # from
37
+ "mail": target, # to
38
+ "title": title, # subject
39
+ "name": name, # nickname
40
+ "text": content, # content
41
+ },
42
+ )
43
+ if response.status_code == 200:
44
+ result: dict = response.json()
45
+ return result.get("status")
46
+
47
+ else:
48
+ raise ConnectionError(f"{response.status_code}")
49
+
50
+ except Exception as e:
51
+ return f"{e}"
52
+
53
+
54
+ def smtp_tester():
55
+ return gr.Interface(
56
+ fn=infer,
57
+ inputs=[
58
+ gr.Textbox(label=_L("收信人邮箱")),
59
+ gr.Textbox(label=_L("标题"), value=_L("测试标题")),
60
+ gr.TextArea(label=_L("正文"), value=_L("测试正文")),
61
+ gr.Textbox(label=_L("发信人昵称"), value=_L("测试昵称")),
62
+ gr.Textbox(label=_L("发信人邮箱")),
63
+ gr.Textbox(label=_L("应用密钥")),
64
+ gr.Textbox(label=_L("SMTP服务器"), value="smtp.163.com"),
65
+ gr.Slider(label=_L("端口"), minimum=0, maximum=65535, step=1, value=25),
66
+ ],
67
+ outputs=gr.TextArea(label=_L("发送状态"), show_copy_button=True),
68
+ flagging_mode="never",
69
+ )
trans.py → modules/trans.py RENAMED
@@ -1,14 +1,27 @@
1
- import os
2
  import json
3
  import requests
4
  import gradio as gr
5
- from config import API_TRANS, KEY_TRANS
6
 
 
 
 
 
 
 
 
7
 
8
- def translate(source, direction):
9
- if not source or not direction:
10
- return "Please enter valid text and select the mode!"
11
 
 
 
 
 
 
 
 
 
 
 
12
  # WARNING, this token is a test token for new developers, and it should be replaced by your token
13
  payload = {
14
  "source": source,
@@ -21,6 +34,9 @@ def translate(source, direction):
21
  "x-authorization": f"token {KEY_TRANS}",
22
  }
23
  try:
 
 
 
24
  response = requests.request(
25
  "POST",
26
  API_TRANS,
@@ -28,24 +44,28 @@ def translate(source, direction):
28
  headers=headers,
29
  )
30
 
31
- return json.loads(response.text)["target"]
32
 
33
  except Exception as e:
34
- return f"{e}"
 
 
35
 
36
 
37
  def translator():
38
  return gr.Interface(
39
- fn=translate,
40
  inputs=[
41
- gr.TextArea(label="Input text area", placeholder="Type the text here..."),
42
- gr.Dropdown(choices=["auto2en", "auto2zh", "auto2ja"], label="Mode"),
 
 
 
 
43
  ],
44
- outputs=gr.TextArea(label="Translation results", show_copy_button=True),
45
  flagging_mode="never",
46
  examples=[
47
- ["彩云小译は最高の翻訳サービスです", "auto2en"],
48
  ["Lingocloud is the best translation service.", "auto2zh"],
49
  ],
50
- cache_examples=False,
51
  )
 
 
1
  import json
2
  import requests
3
  import gradio as gr
4
+ from utils import API_TRANS, KEY_TRANS, LANG
5
 
6
+ ZH2EN = {
7
+ "输入文本区域": "Input text area",
8
+ "在这里输入文本...": "Type the text here...",
9
+ "模式": "Mode",
10
+ "翻译结果": "Translation results",
11
+ "状态栏": "Status",
12
+ }
13
 
 
 
 
14
 
15
+ def _L(zh_txt: str):
16
+ if LANG:
17
+ return ZH2EN[zh_txt]
18
+ else:
19
+ return zh_txt
20
+
21
+
22
+ def infer(source, direction):
23
+ status = "Success"
24
+ result = None
25
  # WARNING, this token is a test token for new developers, and it should be replaced by your token
26
  payload = {
27
  "source": source,
 
34
  "x-authorization": f"token {KEY_TRANS}",
35
  }
36
  try:
37
+ if not source or not direction:
38
+ raise ValueError("请输入有效文本并选择模式!")
39
+
40
  response = requests.request(
41
  "POST",
42
  API_TRANS,
 
44
  headers=headers,
45
  )
46
 
47
+ result = json.loads(response.text)["target"]
48
 
49
  except Exception as e:
50
+ status = f"{e}"
51
+
52
+ return status, result
53
 
54
 
55
  def translator():
56
  return gr.Interface(
57
+ fn=infer,
58
  inputs=[
59
+ gr.TextArea(label=_L("输入文本区域"), placeholder=_L("在这里输入文本...")),
60
+ gr.Dropdown(choices=["auto2en", "auto2zh", "auto2ja"], label=_L("模式")),
61
+ ],
62
+ outputs=[
63
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
64
+ gr.TextArea(label=_L("翻译结果"), show_copy_button=True),
65
  ],
 
66
  flagging_mode="never",
67
  examples=[
68
+ ["彩云小译は最高の翻訳サービスです", "auto2zh"],
69
  ["Lingocloud is the best translation service.", "auto2zh"],
70
  ],
 
71
  )
url.py → modules/url.py RENAMED
@@ -1,83 +1,90 @@
1
- import re
2
  import json
3
  import requests
4
  import gradio as gr
5
- from config import HEADER
6
 
 
 
 
 
 
 
 
 
7
 
8
- def is_valid_url(url):
9
- pattern = re.compile(
10
- r"^(https?://)?" r"([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}" r"(:\d+)?" r"(/[^ ]*)?$"
11
- )
12
- return bool(pattern.match(url))
 
13
 
14
 
15
  def noxlink(longUrl: str):
16
  domain = "https://noxlink.net"
17
  api = f"{domain}/zh-CN/shorten"
18
- payload = {"longUrl": longUrl}
19
- try:
20
- response = requests.post(api, json=payload, headers=HEADER)
21
- if response.status_code == 200:
22
- retcode = json.loads(response.text)
23
- if retcode["success"]:
24
- return f"{domain}/" + retcode["message"]
25
-
26
- return response.text
27
 
28
- except Exception as e:
29
- return f"{e}"
30
 
31
 
32
  def monojson(longUrl: str):
33
- api = "https://monojson.com/api/short-link"
34
- payload = {"url": longUrl}
35
- try:
36
- response = requests.post(api, json=payload, headers=HEADER)
37
- if response.status_code == 200:
38
- return json.loads(response.text)["shortUrl"]
39
-
40
- else:
41
- return response.text
42
-
43
- except Exception as e:
44
- return f"{e}"
45
 
46
 
 
47
  def infer(longUrl: str, tool: str):
 
48
  shortUrl = ""
49
- if tool == "monojson":
50
- shortUrl = monojson(longUrl)
51
- elif tool == "noxlink":
52
- shortUrl = noxlink(longUrl)
53
- else:
54
- shortUrl = "Please select an API provider!"
 
 
 
 
55
 
56
- if is_valid_url(shortUrl):
57
- return f'<a href="{shortUrl}" target="_blank">{shortUrl}</a>'
58
 
59
- return shortUrl
60
 
61
 
62
  def url_shortner():
63
  return gr.Interface(
64
  fn=infer,
65
  inputs=[
66
- gr.Textbox(label="Input a long URL", placeholder="Input a long URL"),
67
  gr.Dropdown(
68
  choices=["noxlink", "monojson"],
69
- label="Select an API provider",
70
  value="noxlink",
71
  ),
72
  ],
73
  outputs=[
 
74
  gr.HTML(
75
  container=True,
76
  show_label=True,
77
- label="Output short URL",
78
- )
79
  ],
80
- description="Convert long urls into short, easy-to-share links",
81
  flagging_mode="never",
82
  examples=[
83
  ["https://www.bing.com", "noxlink"],
 
 
1
  import json
2
  import requests
3
  import gradio as gr
4
+ from utils import is_valid_url, HEADER, LANG
5
 
6
+ ZH2EN = {
7
+ "输入长 URL": "Input a long URL",
8
+ "输入待变短的长 URL": "Input a long URL",
9
+ "选择 API 提供商": "Select an API provider",
10
+ "输出短 URL": "Output short URL",
11
+ "将长 URL 转换为短的、易于共享的链接": "Convert long urls into short, easy-to-share links",
12
+ "状态栏": "Status",
13
+ }
14
 
15
+
16
+ def _L(zh_txt: str):
17
+ if LANG:
18
+ return ZH2EN[zh_txt]
19
+ else:
20
+ return zh_txt
21
 
22
 
23
  def noxlink(longUrl: str):
24
  domain = "https://noxlink.net"
25
  api = f"{domain}/zh-CN/shorten"
26
+ response = requests.post(api, json={"longUrl": longUrl}, headers=HEADER)
27
+ if response.status_code == 200:
28
+ retcode = json.loads(response.text)
29
+ if retcode["success"]:
30
+ return f"{domain}/" + retcode["message"]
 
 
 
 
31
 
32
+ raise ConnectionError(response.text)
 
33
 
34
 
35
  def monojson(longUrl: str):
36
+ response = requests.post(
37
+ "https://monojson.com/api/short-link",
38
+ json={"url": longUrl},
39
+ headers=HEADER,
40
+ )
41
+ if response.status_code == 200:
42
+ return json.loads(response.text)["shortUrl"]
43
+ else:
44
+ raise ConnectionError(response.text)
 
 
 
45
 
46
 
47
+ # outer func
48
  def infer(longUrl: str, tool: str):
49
+ status = "Success"
50
  shortUrl = ""
51
+ try:
52
+ if tool == "monojson":
53
+ shortUrl = monojson(longUrl)
54
+ elif tool == "noxlink":
55
+ shortUrl = noxlink(longUrl)
56
+ else:
57
+ raise ValueError("请选择一个 API 提供商!")
58
+
59
+ if is_valid_url(shortUrl):
60
+ shortUrl = f'<a href="{shortUrl}" target="_blank">{shortUrl}</a>'
61
 
62
+ except Exception as e:
63
+ status = f"{e}"
64
 
65
+ return status, shortUrl
66
 
67
 
68
  def url_shortner():
69
  return gr.Interface(
70
  fn=infer,
71
  inputs=[
72
+ gr.Textbox(label=_L("输入长 URL"), placeholder=_L("输入待变短的长 URL")),
73
  gr.Dropdown(
74
  choices=["noxlink", "monojson"],
75
+ label=_L("选择 API 提供商"),
76
  value="noxlink",
77
  ),
78
  ],
79
  outputs=[
80
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
81
  gr.HTML(
82
  container=True,
83
  show_label=True,
84
+ label=_L("输出短 URL"),
85
+ ),
86
  ],
87
+ description=_L("将长 URL 转换为短的、易于共享的链接"),
88
  flagging_mode="never",
89
  examples=[
90
  ["https://www.bing.com", "noxlink"],
qr.py DELETED
@@ -1,21 +0,0 @@
1
- import gradio as gr
2
-
3
-
4
- def infer(img_size: int, input_txt: str):
5
- if not input_txt:
6
- return None
7
-
8
- return f"https://api.qrserver.com/v1/create-qr-code/?size={img_size}x{img_size}&data={input_txt}"
9
-
10
-
11
- def qrcode():
12
- return gr.Interface(
13
- fn=infer,
14
- inputs=[
15
- gr.Slider(35, 1000, 217, label="Image size"),
16
- gr.Textbox(label="Input text"),
17
- ],
18
- outputs=gr.Image(label="QR code", show_share_button=False),
19
- description="Enter text to generate a QR code.",
20
- flagging_mode="never",
21
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
smtp.py DELETED
@@ -1,53 +0,0 @@
1
- import requests
2
- import gradio as gr
3
- from config import API_SMTP
4
-
5
-
6
- def send_email(
7
- target: str,
8
- title: str,
9
- content: str,
10
- name: str,
11
- email: str,
12
- password: str,
13
- host: str,
14
- port: int,
15
- ):
16
- response = requests.get(
17
- API_SMTP,
18
- params={
19
- "host": host,
20
- "Port": port,
21
- "key": password, # apikey
22
- "email": email, # from
23
- "mail": target, # to
24
- "title": title, # subject
25
- "name": name, # nickname
26
- "text": content, # content
27
- },
28
- )
29
- if response.status_code == 200:
30
- result: dict = response.json()
31
- return result.get("status")
32
-
33
- else:
34
- return f"Request failed with status code: {response.status_code}"
35
-
36
-
37
- def smtp_tester():
38
- return gr.Interface(
39
- fn=send_email,
40
- inputs=[
41
- gr.Textbox(label="To email"),
42
- gr.Textbox(label="Title", value="Test title"),
43
- gr.TextArea(label="Content", value="Test content"),
44
- gr.Textbox(label="Sender name", value="Test nickname"),
45
- gr.Textbox(label="From email"),
46
- gr.Textbox(label="API key"),
47
- gr.Textbox(label="SMTP host", value="smtp.163.com"),
48
- gr.Slider(label="Port", minimum=0, maximum=65535, step=1, value=25),
49
- ],
50
- outputs=gr.Textbox(label="status", show_copy_button=True),
51
- description="SMTP Online Test Tool",
52
- flagging_mode="never",
53
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import shutil
4
+ import zipfile
5
+
6
+ LANG = os.getenv("language")
7
+ API_SMTP = os.getenv("api_smtp")
8
+ API_TRANS = os.getenv("api_caiyun")
9
+ KEY_TRANS = os.getenv("apikey_caiyun")
10
+ if not (API_SMTP and API_TRANS and KEY_TRANS):
11
+ print("请检查环境变量")
12
+ exit()
13
+
14
+
15
+ TMP_DIR = "./__pycache__"
16
+ HEADER = {
17
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0",
18
+ }
19
+
20
+
21
+ def mk_dir(dir_path: str):
22
+ if not os.path.exists(dir_path):
23
+ os.makedirs(dir_path)
24
+
25
+
26
+ def rm_dir(dir_path: str):
27
+ if os.path.exists(dir_path):
28
+ shutil.rmtree(dir_path)
29
+
30
+
31
+ def clean_dir(dir_path: str):
32
+ rm_dir(dir_path)
33
+ os.makedirs(dir_path)
34
+
35
+
36
+ def unzip(zip_path: str, extract_to: str):
37
+ mk_dir(extract_to)
38
+ # 打开ZIP文件
39
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
40
+ # 解压文件
41
+ zip_ref.extractall(extract_to)
42
+
43
+
44
+ def compress(folder_path: str, zip_file: str):
45
+ # 确保文件夹存在
46
+ if not os.path.exists(folder_path):
47
+ raise ValueError(f"错误: 文件夹 '{folder_path}' 不存在")
48
+ # 打开 ZIP 文件,使用 'w' 模式表示写入
49
+ with zipfile.ZipFile(zip_file, "w", zipfile.ZIP_DEFLATED) as zipf:
50
+ # 遍历文件夹中的文件和子文件夹
51
+ for root, _, files in os.walk(folder_path):
52
+ for file in files:
53
+ file_path = os.path.join(root, file)
54
+ # 计算相对路径,保留文件夹的根目录
55
+ relative_path = os.path.relpath(file_path, folder_path)
56
+ zipf.write(
57
+ file_path,
58
+ arcname=os.path.join(os.path.basename(folder_path), relative_path),
59
+ )
60
+
61
+
62
+ def is_valid_url(url):
63
+ # 定义 URL 的正则表达式
64
+ pattern = re.compile(
65
+ r"^(https?://)?" # 协议(http 或 https,可选)
66
+ r"([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}" # 域名
67
+ r"(:\d+)?" # 端口号(可选)
68
+ r"(/[^ ]*)?$" # 路径(可选)
69
+ )
70
+ # 使用正则表达式匹配 URL
71
+ return bool(pattern.match(url))