IdlecloudX commited on
Commit
73b5b76
·
verified ·
1 Parent(s): 329cf76

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -58
app.py CHANGED
@@ -1,15 +1,127 @@
1
  import os
2
  import json
 
 
3
  import gradio as gr
4
  import huggingface_hub
5
  import numpy as np
6
  import onnxruntime as rt
7
  import pandas as pd
8
- from PIL import Image
9
  from huggingface_hub import login
10
 
11
  from translator import translate_texts
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # ------------------------------------------------------------------
14
  # 模型配置
15
  # ------------------------------------------------------------------
@@ -96,24 +208,23 @@ class Tagger:
96
  res["characters"][tag_name] = float(outputs[idx])
97
  tag_categories_for_translation["characters"].append(tag_name)
98
 
99
-
100
  res["general"] = dict(sorted(res["general"].items(), key=lambda kv: kv[1], reverse=True))
101
  res["characters"] = dict(sorted(res["characters"].items(), key=lambda kv: kv[1], reverse=True))
102
  res["ratings"] = dict(sorted(res["ratings"].items(), key=lambda kv: kv[1], reverse=True))
103
 
104
-
105
  tag_categories_for_translation["general"] = list(res["general"].keys())
106
  tag_categories_for_translation["characters"] = list(res["characters"].keys())
107
  tag_categories_for_translation["ratings"] = list(res["ratings"].keys())
108
 
109
  return res, tag_categories_for_translation
110
 
 
111
  # 全局 Tagger 实例
112
  try:
113
  tagger_instance = Tagger()
114
  except RuntimeError as e:
115
  print(f"应用启动时Tagger初始化失败: {e}")
116
- tagger_instance = None # 允许应用启动,但在处理时会失败
117
 
118
  # ------------------------------------------------------------------
119
  # Gradio UI
@@ -143,7 +254,7 @@ custom_css = """
143
  .tag-en {
144
  font-weight: bold;
145
  color: #333;
146
- cursor: pointer; /* Indicates clickable */
147
  }
148
  .tag-zh {
149
  color: #666;
@@ -153,7 +264,7 @@ custom_css = """
153
  color: #999;
154
  font-size: 0.9em;
155
  }
156
- .btn-analyze-container { /* Custom class for analyze button container */
157
  margin-top: 15px;
158
  margin-bottom: 15px;
159
  }
@@ -164,20 +275,17 @@ function copyToClipboard(text) {
164
  console.log('copyToClipboard function was called.');
165
  console.log('Received text:', text);
166
 
167
- // 如果 text 未定义或为 null
168
  if (typeof text === 'undefined' || text === null) {
169
  console.warn('copyToClipboard was called with undefined or null text. Aborting this specific copy operation.');
170
  return;
171
  }
172
 
173
  navigator.clipboard.writeText(text).then(() => {
174
- // console.log('Tag copied to clipboard: ' + text);
175
  const feedback = document.createElement('div');
176
-
177
- // 确保 text 是字符串类型,再进行 substring 操作
178
  let displayText = String(text);
179
  displayText = displayText.substring(0, 30) + (displayText.length > 30 ? '...' : '');
180
-
181
  feedback.textContent = '已复制: ' + displayText;
182
  feedback.style.position = 'fixed';
183
  feedback.style.bottom = '20px';
@@ -193,7 +301,7 @@ function copyToClipboard(text) {
193
  setTimeout(() => {
194
  feedback.style.opacity = '0';
195
  setTimeout(() => {
196
- if (document.body.contains(feedback)) { // 确保元素还在DOM中
197
  document.body.removeChild(feedback);
198
  }
199
  }, 500);
@@ -227,24 +335,27 @@ function copyToClipboard(text) {
227
 
228
  with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=custom_css, js=_js_functions) as demo:
229
  gr.Markdown("# 🖼️ AI 图像标签分析器")
230
- gr.Markdown("上传图片自动识别标签,支持中英文显示和一键复制。[NovelAI在线绘画](https://nai.idlecloud.cc/)")
 
 
 
231
 
232
  state_res = gr.State({})
233
  state_translations_dict = gr.State({})
234
  state_tag_categories_for_translation = gr.State({})
235
 
236
-
237
  with gr.Row():
238
  with gr.Column(scale=1):
239
- img_in = gr.Image(type="pil", label="上传图片", height=300)
240
-
 
241
  btn = gr.Button("🚀 开始分析", variant="primary", elem_classes=["btn-analyze-container"])
242
-
243
  with gr.Accordion("⚙️ 高级设置", open=False):
244
  gen_slider = gr.Slider(0, 1, value=0.35, step=0.01, label="通用标签阈值", info="越高 → 标签更少更准")
245
  char_slider = gr.Slider(0, 1, value=0.85, step=0.01, label="角色标签阈值", info="推荐保持较高阈值")
246
  show_tag_scores = gr.Checkbox(True, label="在列表中显示标签置信度")
247
-
248
  with gr.Accordion("📊 标签汇总设置", open=True):
249
  gr.Markdown("选择要包含在下方汇总文本框中的标签类别:")
250
  with gr.Row():
@@ -265,7 +376,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
265
  out_char = gr.HTML(label="Character Tags")
266
  with gr.TabItem("⭐ 评分标签"):
267
  out_rating = gr.HTML(label="Rating Tags")
268
-
269
  gr.Markdown("### 标签汇总结果")
270
  out_summary = gr.Textbox(
271
  label="标签汇总",
@@ -278,9 +389,9 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
278
  def format_tags_html(tags_dict, translations_list, category_name, show_scores=True, show_translation_in_list=True):
279
  if not tags_dict:
280
  return "<p>暂无标签</p>"
281
-
282
  html = '<div class="label-container">'
283
-
284
  if not isinstance(translations_list, list):
285
  translations_list = []
286
 
@@ -288,14 +399,14 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
288
 
289
  for i, tag in enumerate(tag_keys):
290
  score = tags_dict[tag]
291
- escaped_tag = tag.replace("'", "\\'") # Escape for JS
292
-
293
  html += '<div class="tag-item">'
294
  tag_display_html = f'<span class="tag-en" onclick="copyToClipboard(\'{escaped_tag}\')">{tag}</span>'
295
-
296
  if show_translation_in_list and i < len(translations_list) and translations_list[i]:
297
  tag_display_html += f'<span class="tag-zh">({translations_list[i]})</span>'
298
-
299
  html += f'<div>{tag_display_html}</div>'
300
  if show_scores:
301
  html += f'<span class="tag-score">{score:.3f}</span>'
@@ -315,9 +426,12 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
315
  separator = separators.get(s_sep_type, ", ")
316
 
317
  categories_to_summarize = []
318
- if s_gen: categories_to_summarize.append("general")
319
- if s_char: categories_to_summarize.append("characters")
320
- if s_rat: categories_to_summarize.append("ratings")
 
 
 
321
 
322
  if not categories_to_summarize:
323
  return "请至少选择一个标签类别进行汇总。"
@@ -333,83 +447,99 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
333
  tags_to_join.append(f"{en_tag}/*{cat_translations[i]}*/")
334
  else:
335
  tags_to_join.append(en_tag)
336
- if tags_to_join: # only add if there are tags for this category
337
- summary_parts.append(separator.join(tags_to_join))
338
-
339
  joiner = "\n\n" if separator != "\n" and len(summary_parts) > 1 else separator if separator == "\n" else " "
340
-
341
  final_summary = joiner.join(summary_parts)
342
  return final_summary if final_summary else "选定的类别中没有找到标签。"
343
 
344
-
345
  def process_image_and_generate_outputs(
346
- img, g_th, c_th, s_scores, # Main inputs
347
  s_gen, s_char, s_rat, s_sep, s_zh_in_sum
348
- ):
349
- if img is None:
350
  yield (
351
  gr.update(interactive=True, value="🚀 开始分析"),
352
  gr.update(visible=True, value="❌ 请先上传图片。"),
353
  "", "", "", "",
354
- gr.update(placeholder="请先上传图片并开始分析..."),
355
- {}, {}, {}
356
  )
357
  return
358
-
359
  if tagger_instance is None:
360
  yield (
361
  gr.update(interactive=True, value="🚀 开始分析"),
362
  gr.update(visible=True, value="❌ 分析器未成功初始化,请检查控制台错误。"),
363
  "", "", "", "",
364
- gr.update(placeholder="分析器初始化失败..."),
365
  {}, {}, {}
366
  )
367
  return
368
 
369
  yield (
370
  gr.update(interactive=False, value="🔄 处理中..."),
371
- gr.update(visible=True, value="🔄 正在分析图像,请稍候..."),
372
- gr.HTML(value="<p>分析中...</p>"), # General
373
- gr.HTML(value="<p>分析中...</p>"), # Character
374
- gr.HTML(value="<p>分析中...</p>"), # Rating
375
- gr.update(value="分析中,请稍候..."), # Summary
376
- {}, {}, {} # Clear states initially
377
  )
378
-
379
  try:
 
380
  res, tag_categories_original_order = tagger_instance.predict(img, g_th, c_th)
381
 
382
  all_tags_to_translate = []
383
  for cat_key in ["general", "characters", "ratings"]:
384
  all_tags_to_translate.extend(tag_categories_original_order.get(cat_key, []))
385
-
386
  all_translations_flat = []
387
  if all_tags_to_translate:
388
  all_translations_flat = translate_texts(all_tags_to_translate, src_lang="auto", tgt_lang="zh")
389
-
390
  current_translations_dict = {}
391
  offset = 0
392
  for cat_key in ["general", "characters", "ratings"]:
393
  cat_original_tags = tag_categories_original_order.get(cat_key, [])
394
  num_tags_in_cat = len(cat_original_tags)
395
  if num_tags_in_cat > 0:
396
- current_translations_dict[cat_key] = all_translations_flat[offset : offset + num_tags_in_cat]
397
  offset += num_tags_in_cat
398
  else:
399
  current_translations_dict[cat_key] = []
400
-
401
- general_html = format_tags_html(res.get("general", {}), current_translations_dict.get("general", []), "general", s_scores, True)
402
- char_html = format_tags_html(res.get("characters", {}), current_translations_dict.get("characters", []), "characters", s_scores, True)
403
- rating_html = format_tags_html(res.get("ratings", {}), current_translations_dict.get("ratings", []), "ratings", s_scores, True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
 
405
  summary_text = generate_summary_text_content(
406
  res, current_translations_dict,
407
- s_gen, s_char, s_rat, s_sep, s_zh_in_sum
408
  )
409
 
410
  yield (
411
  gr.update(interactive=True, value="🚀 开始分析"),
412
- gr.update(visible=True, value="✅ 分析完成!"),
413
  general_html,
414
  char_html,
415
  rating_html,
@@ -418,7 +548,17 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
418
  current_translations_dict,
419
  tag_categories_original_order
420
  )
421
-
 
 
 
 
 
 
 
 
 
 
422
  except Exception as e:
423
  import traceback
424
  tb_str = traceback.format_exc()
@@ -469,4 +609,4 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=cus
469
  if __name__ == "__main__":
470
  if tagger_instance is None:
471
  print("CRITICAL: Tagger 未能初始化,应用功能将受限。请检查之前的错误信息。")
472
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
  import os
2
  import json
3
+ import warnings
4
+
5
  import gradio as gr
6
  import huggingface_hub
7
  import numpy as np
8
  import onnxruntime as rt
9
  import pandas as pd
10
+ from PIL import Image, ImageFile
11
  from huggingface_hub import login
12
 
13
  from translator import translate_texts
14
 
15
+ # ------------------------------------------------------------------
16
+ # 安全配置
17
+ # ------------------------------------------------------------------
18
+ # 1) 限制上传文件原始体积,拦截伪装图片/图片中塞入额外数据/高熵噪声导致的超大文件
19
+ MAX_UPLOAD_BYTES = 8 * 1024 * 1024 # 8 MB
20
+ # 2) 限制单边尺寸,避免异常超大分辨率
21
+ MAX_IMAGE_SIDE = 4096
22
+ # 3) 限制总像素数,防止“像素炸弹”或解码后内存占用过高
23
+ MAX_IMAGE_PIXELS = 20_000_000 # 2000 万像素
24
+ # 4) 限制解码后的估算内存占用
25
+ MAX_DECOMPRESSED_BYTES = 160 * 1024 * 1024 # 160 MB
26
+ # 5) 仅允许常见安全图片格式
27
+ ALLOWED_IMAGE_FORMATS = {"PNG", "JPEG", "WEBP", "BMP", "GIF"}
28
+
29
+ # Pillow 安全设置
30
+ Image.MAX_IMAGE_PIXELS = MAX_IMAGE_PIXELS
31
+ ImageFile.LOAD_TRUNCATED_IMAGES = False
32
+ warnings.simplefilter("error", Image.DecompressionBombWarning)
33
+
34
+
35
+ class ImageValidationError(ValueError):
36
+ """上传图片校验失败。"""
37
+
38
+
39
+ def _format_size(num_bytes: int) -> str:
40
+ if num_bytes < 1024:
41
+ return f"{num_bytes} B"
42
+ if num_bytes < 1024 * 1024:
43
+ return f"{num_bytes / 1024:.2f} KB"
44
+ return f"{num_bytes / (1024 * 1024):.2f} MB"
45
+
46
+
47
+ def validate_and_open_image(image_path: str) -> Image.Image:
48
+ """
49
+ 安全打开用户上传图片:
50
+ - 校验原始文件体积
51
+ - 校验图片格式
52
+ - 校验宽高/总像素
53
+ - 校验解码后预估内存占用
54
+ - 拦截 Pillow 解压炸弹警告
55
+ """
56
+ if not image_path:
57
+ raise ImageValidationError("未检测到上传文件。")
58
+
59
+ if not os.path.isfile(image_path):
60
+ raise ImageValidationError("上传文件不存在或无法访问。")
61
+
62
+ file_size = os.path.getsize(image_path)
63
+ if file_size <= 0:
64
+ raise ImageValidationError("上传文件为空。")
65
+
66
+ if file_size > MAX_UPLOAD_BYTES:
67
+ raise ImageValidationError(
68
+ f"图片文件过大:{_format_size(file_size)},超过限制 {_format_size(MAX_UPLOAD_BYTES)}。"
69
+ )
70
+
71
+ try:
72
+ with Image.open(image_path) as probe:
73
+ img_format = (probe.format or "").upper()
74
+ width, height = probe.size
75
+ probe.verify()
76
+ except Image.DecompressionBombWarning:
77
+ raise ImageValidationError("图片疑似像素炸弹,已被拒绝处理。")
78
+ except Exception as e:
79
+ raise ImageValidationError(f"无法解析为有效图片文件:{e}")
80
+
81
+ if img_format not in ALLOWED_IMAGE_FORMATS:
82
+ raise ImageValidationError(
83
+ f"不支持的图片格式:{img_format or '未知'}。仅允许:{', '.join(sorted(ALLOWED_IMAGE_FORMATS))}。"
84
+ )
85
+
86
+ if width <= 0 or height <= 0:
87
+ raise ImageValidationError("图片尺寸非法。")
88
+
89
+ if width > MAX_IMAGE_SIDE or height > MAX_IMAGE_SIDE:
90
+ raise ImageValidationError(
91
+ f"图片尺寸过大:{width}×{height},单边不得超过 {MAX_IMAGE_SIDE} 像素。"
92
+ )
93
+
94
+ total_pixels = width * height
95
+ if total_pixels > MAX_IMAGE_PIXELS:
96
+ raise ImageValidationError(
97
+ f"图片总像素过大:{total_pixels:,},超过限制 {MAX_IMAGE_PIXELS:,}。"
98
+ )
99
+
100
+ # 估算解码为 RGB 后的内存占用
101
+ estimated_decompressed_bytes = total_pixels * 3
102
+ if estimated_decompressed_bytes > MAX_DECOMPRESSED_BYTES:
103
+ raise ImageValidationError(
104
+ "图片解码后的内存占用过高,已拒绝处理。"
105
+ f" 预计占用约 {_format_size(estimated_decompressed_bytes)},"
106
+ f"超过限制 {_format_size(MAX_DECOMPRESSED_BYTES)}。"
107
+ )
108
+
109
+ # 第二次打开,真正加载像素数据
110
+ try:
111
+ with Image.open(image_path) as img:
112
+ img.load()
113
+ if img.mode != "RGB":
114
+ img = img.convert("RGB")
115
+ else:
116
+ img = img.copy()
117
+ except Image.DecompressionBombWarning:
118
+ raise ImageValidationError("图片在解码阶段触发像素炸弹保护,已拒绝处理。")
119
+ except Exception as e:
120
+ raise ImageValidationError(f"图片加载失败:{e}")
121
+
122
+ return img
123
+
124
+
125
  # ------------------------------------------------------------------
126
  # 模型配置
127
  # ------------------------------------------------------------------
 
208
  res["characters"][tag_name] = float(outputs[idx])
209
  tag_categories_for_translation["characters"].append(tag_name)
210
 
 
211
  res["general"] = dict(sorted(res["general"].items(), key=lambda kv: kv[1], reverse=True))
212
  res["characters"] = dict(sorted(res["characters"].items(), key=lambda kv: kv[1], reverse=True))
213
  res["ratings"] = dict(sorted(res["ratings"].items(), key=lambda kv: kv[1], reverse=True))
214
 
 
215
  tag_categories_for_translation["general"] = list(res["general"].keys())
216
  tag_categories_for_translation["characters"] = list(res["characters"].keys())
217
  tag_categories_for_translation["ratings"] = list(res["ratings"].keys())
218
 
219
  return res, tag_categories_for_translation
220
 
221
+
222
  # 全局 Tagger 实例
223
  try:
224
  tagger_instance = Tagger()
225
  except RuntimeError as e:
226
  print(f"应用启动时Tagger初始化失败: {e}")
227
+ tagger_instance = None # 允许应用启动,但在处理时会失败
228
 
229
  # ------------------------------------------------------------------
230
  # Gradio UI
 
254
  .tag-en {
255
  font-weight: bold;
256
  color: #333;
257
+ cursor: pointer;
258
  }
259
  .tag-zh {
260
  color: #666;
 
264
  color: #999;
265
  font-size: 0.9em;
266
  }
267
+ .btn-analyze-container {
268
  margin-top: 15px;
269
  margin-bottom: 15px;
270
  }
 
275
  console.log('copyToClipboard function was called.');
276
  console.log('Received text:', text);
277
 
 
278
  if (typeof text === 'undefined' || text === null) {
279
  console.warn('copyToClipboard was called with undefined or null text. Aborting this specific copy operation.');
280
  return;
281
  }
282
 
283
  navigator.clipboard.writeText(text).then(() => {
 
284
  const feedback = document.createElement('div');
285
+
 
286
  let displayText = String(text);
287
  displayText = displayText.substring(0, 30) + (displayText.length > 30 ? '...' : '');
288
+
289
  feedback.textContent = '已复制: ' + displayText;
290
  feedback.style.position = 'fixed';
291
  feedback.style.bottom = '20px';
 
301
  setTimeout(() => {
302
  feedback.style.opacity = '0';
303
  setTimeout(() => {
304
+ if (document.body.contains(feedback)) {
305
  document.body.removeChild(feedback);
306
  }
307
  }, 500);
 
335
 
336
  with gr.Blocks(theme=gr.themes.Soft(), title="AI 图像标签分析器", css=custom_css, js=_js_functions) as demo:
337
  gr.Markdown("# 🖼️ AI 图像标签分析器")
338
+ gr.Markdown(
339
+ "上传图片自动识别标签,支持中英文显示和一键复制。"
340
+ "[NovelAI在线绘画](https://nai.idlecloud.cc/)\n\n"
341
+ )
342
 
343
  state_res = gr.State({})
344
  state_translations_dict = gr.State({})
345
  state_tag_categories_for_translation = gr.State({})
346
 
 
347
  with gr.Row():
348
  with gr.Column(scale=1):
349
+ # 改为 filepath,确保可以拿到原始文件路径与体积进行校验
350
+ img_in = gr.Image(type="filepath", label="上传图片", height=300)
351
+
352
  btn = gr.Button("🚀 开始分析", variant="primary", elem_classes=["btn-analyze-container"])
353
+
354
  with gr.Accordion("⚙️ 高级设置", open=False):
355
  gen_slider = gr.Slider(0, 1, value=0.35, step=0.01, label="通用标签阈值", info="越高 → 标签更少更准")
356
  char_slider = gr.Slider(0, 1, value=0.85, step=0.01, label="角色标签阈值", info="推荐保持较高阈值")
357
  show_tag_scores = gr.Checkbox(True, label="在列表中显示标签置信度")
358
+
359
  with gr.Accordion("📊 标签汇总设置", open=True):
360
  gr.Markdown("选择要包含在下方汇总文本框中的标签类别:")
361
  with gr.Row():
 
376
  out_char = gr.HTML(label="Character Tags")
377
  with gr.TabItem("⭐ 评分标签"):
378
  out_rating = gr.HTML(label="Rating Tags")
379
+
380
  gr.Markdown("### 标签汇总结果")
381
  out_summary = gr.Textbox(
382
  label="标签汇总",
 
389
  def format_tags_html(tags_dict, translations_list, category_name, show_scores=True, show_translation_in_list=True):
390
  if not tags_dict:
391
  return "<p>暂无标签</p>"
392
+
393
  html = '<div class="label-container">'
394
+
395
  if not isinstance(translations_list, list):
396
  translations_list = []
397
 
 
399
 
400
  for i, tag in enumerate(tag_keys):
401
  score = tags_dict[tag]
402
+ escaped_tag = tag.replace("'", "\\'")
403
+
404
  html += '<div class="tag-item">'
405
  tag_display_html = f'<span class="tag-en" onclick="copyToClipboard(\'{escaped_tag}\')">{tag}</span>'
406
+
407
  if show_translation_in_list and i < len(translations_list) and translations_list[i]:
408
  tag_display_html += f'<span class="tag-zh">({translations_list[i]})</span>'
409
+
410
  html += f'<div>{tag_display_html}</div>'
411
  if show_scores:
412
  html += f'<span class="tag-score">{score:.3f}</span>'
 
426
  separator = separators.get(s_sep_type, ", ")
427
 
428
  categories_to_summarize = []
429
+ if s_gen:
430
+ categories_to_summarize.append("general")
431
+ if s_char:
432
+ categories_to_summarize.append("characters")
433
+ if s_rat:
434
+ categories_to_summarize.append("ratings")
435
 
436
  if not categories_to_summarize:
437
  return "请至少选择一个标签类别进行汇总。"
 
447
  tags_to_join.append(f"{en_tag}/*{cat_translations[i]}*/")
448
  else:
449
  tags_to_join.append(en_tag)
450
+ if tags_to_join:
451
+ summary_parts.append(separator.join(tags_to_join))
452
+
453
  joiner = "\n\n" if separator != "\n" and len(summary_parts) > 1 else separator if separator == "\n" else " "
454
+
455
  final_summary = joiner.join(summary_parts)
456
  return final_summary if final_summary else "选定的类别中没有找到标签。"
457
 
 
458
  def process_image_and_generate_outputs(
459
+ image_path, g_th, c_th, s_scores,
460
  s_gen, s_char, s_rat, s_sep, s_zh_in_sum
461
+ ):
462
+ if image_path is None:
463
  yield (
464
  gr.update(interactive=True, value="🚀 开始分析"),
465
  gr.update(visible=True, value="❌ 请先上传图片。"),
466
  "", "", "", "",
467
+ {}, {}, {}
 
468
  )
469
  return
470
+
471
  if tagger_instance is None:
472
  yield (
473
  gr.update(interactive=True, value="🚀 开始分析"),
474
  gr.update(visible=True, value="❌ 分析器未成功初始化,请检查控制台错误。"),
475
  "", "", "", "",
 
476
  {}, {}, {}
477
  )
478
  return
479
 
480
  yield (
481
  gr.update(interactive=False, value="🔄 处理中..."),
482
+ gr.update(visible=True, value="🔄 正在校验并分析图像,请稍候..."),
483
+ gr.HTML(value="<p>分析中...</p>"),
484
+ gr.HTML(value="<p>分析中...</p>"),
485
+ gr.HTML(value="<p>分析中...</p>"),
486
+ gr.update(value="分析中,请稍候..."),
487
+ {}, {}, {}
488
  )
489
+
490
  try:
491
+ img = validate_and_open_image(image_path)
492
  res, tag_categories_original_order = tagger_instance.predict(img, g_th, c_th)
493
 
494
  all_tags_to_translate = []
495
  for cat_key in ["general", "characters", "ratings"]:
496
  all_tags_to_translate.extend(tag_categories_original_order.get(cat_key, []))
497
+
498
  all_translations_flat = []
499
  if all_tags_to_translate:
500
  all_translations_flat = translate_texts(all_tags_to_translate, src_lang="auto", tgt_lang="zh")
501
+
502
  current_translations_dict = {}
503
  offset = 0
504
  for cat_key in ["general", "characters", "ratings"]:
505
  cat_original_tags = tag_categories_original_order.get(cat_key, [])
506
  num_tags_in_cat = len(cat_original_tags)
507
  if num_tags_in_cat > 0:
508
+ current_translations_dict[cat_key] = all_translations_flat[offset: offset + num_tags_in_cat]
509
  offset += num_tags_in_cat
510
  else:
511
  current_translations_dict[cat_key] = []
512
+
513
+ general_html = format_tags_html(
514
+ res.get("general", {}),
515
+ current_translations_dict.get("general", []),
516
+ "general",
517
+ s_scores,
518
+ True,
519
+ )
520
+ char_html = format_tags_html(
521
+ res.get("characters", {}),
522
+ current_translations_dict.get("characters", []),
523
+ "characters",
524
+ s_scores,
525
+ True,
526
+ )
527
+ rating_html = format_tags_html(
528
+ res.get("ratings", {}),
529
+ current_translations_dict.get("ratings", []),
530
+ "ratings",
531
+ s_scores,
532
+ True,
533
+ )
534
 
535
  summary_text = generate_summary_text_content(
536
  res, current_translations_dict,
537
+ s_gen, s_char, s_rat, s_sep, s_zh_in_sum
538
  )
539
 
540
  yield (
541
  gr.update(interactive=True, value="🚀 开始分析"),
542
+ gr.update(visible=True, value="✅ 分析完成!"),
543
  general_html,
544
  char_html,
545
  rating_html,
 
548
  current_translations_dict,
549
  tag_categories_original_order
550
  )
551
+
552
+ except ImageValidationError as e:
553
+ yield (
554
+ gr.update(interactive=True, value="🚀 开始分析"),
555
+ gr.update(visible=True, value=f"❌ 上传图片未通过安全校验:{str(e)}"),
556
+ "<p>图片已被安全策略拒绝</p>",
557
+ "<p>图片已被安全策略拒绝</p>",
558
+ "<p>图片已被安全策略拒绝</p>",
559
+ gr.update(value=f"错误: {str(e)}", placeholder="上传图片未通过安全校验..."),
560
+ {}, {}, {}
561
+ )
562
  except Exception as e:
563
  import traceback
564
  tb_str = traceback.format_exc()
 
609
  if __name__ == "__main__":
610
  if tagger_instance is None:
611
  print("CRITICAL: Tagger 未能初始化,应用功能将受限。请检查之前的错误信息。")
612
+ demo.launch(server_name="0.0.0.0", server_port=7860)