Skip to content

Commit b99c111

Browse files
committed
feat: 增加AI断句居中、字体缩放、Gemini/OpenAI断句支持等功能 (v1.6.5)
1 parent 7e34cdd commit b99c111

27 files changed

Lines changed: 706 additions & 2615 deletions

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.6.0
1+
1.6.5

desktop_qt_ui/app_logic.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,17 @@ def on_file_completed(self, result):
121121
os.makedirs(final_output_folder, exist_ok=True)
122122

123123
save_kwargs = {}
124-
if final_output_path.lower().endswith(('.jpg', '.jpeg', '.webp')):
124+
image_to_save = result['image_data']
125+
126+
# Convert RGBA to RGB for JPEG format
127+
if final_output_path.lower().endswith(('.jpg', '.jpeg')):
128+
if image_to_save.mode == 'RGBA':
129+
image_to_save = image_to_save.convert('RGB')
130+
save_kwargs['quality'] = save_quality
131+
elif final_output_path.lower().endswith('.webp'):
125132
save_kwargs['quality'] = save_quality
126133

127-
result['image_data'].save(final_output_path, **save_kwargs)
134+
image_to_save.save(final_output_path, **save_kwargs)
128135

129136
# 更新translation_map.json
130137
self._update_translation_map(original_path, final_output_path)
@@ -335,10 +342,10 @@ def get_display_mapping(self, key: str) -> Optional[Dict[str, str]]:
335342
},
336343
"translator": {
337344
"youdao": "有道翻译", "baidu": "百度翻译", "deepl": "DeepL", "papago": "Papago",
338-
"caiyun": "彩云小译", "chatgpt": "ChatGPT", "chatgpt_2stage": "ChatGPT (2-Stage)",
339-
"none": "无", "original": "原文", "sakura": "Sakura", "deepseek": "DeepSeek",
340-
"groq": "Groq", "gemini": "Google Gemini", "gemini_2stage": "Gemini (2-Stage)",
341-
"openai_hq": "高质量翻译 OpenAI", "gemini_hq": "高质量翻译 Gemini", "custom_openai": "自定义 OpenAI",
345+
"caiyun": "彩云小译", "openai": "OpenAI",
346+
"none": "无", "original": "原文", "sakura": "Sakura",
347+
"groq": "Groq", "gemini": "Google Gemini",
348+
"openai_hq": "高质量翻译 OpenAI", "gemini_hq": "高质量翻译 Gemini",
342349
"offline": "离线翻译", "nllb": "NLLB", "nllb_big": "NLLB (Big)", "sugoi": "Sugoi",
343350
"jparacrawl": "JParaCrawl", "jparacrawl_big": "JParaCrawl (Big)", "m2m100": "M2M100",
344351
"m2m100_big": "M2M100 (Big)", "mbart50": "mBART50", "qwen2": "Qwen2", "qwen2_big": "Qwen2 (Big)",
@@ -357,6 +364,8 @@ def get_display_mapping(self, key: str) -> Optional[Dict[str, str]]:
357364
"inpainter": "修复模型", "inpainting_size": "修复大小", "inpainting_precision": "修复精度",
358365
"renderer": "渲染器", "alignment": "对齐方式", "disable_font_border": "禁用字体边框",
359366
"disable_auto_wrap": "AI断句", "font_size_offset": "字体大小偏移量", "font_size_minimum": "最小字体大小",
367+
"max_font_size": "最大字体大小", "font_scale_ratio": "字体缩放比例",
368+
"center_text_in_bubble": "AI断句时文本居中",
360369
"direction": "文本方向", "uppercase": "大写", "lowercase": "小写", "gimp_font": "GIMP字体",
361370
"font_path": "字体路径", "no_hyphenation": "禁用连字符", "font_color": "字体颜色",
362371
"auto_rotate_symbols": "竖排内横排", "rtl": "从右到左", "layout_mode": "排版模式",
@@ -572,10 +581,17 @@ def on_task_finished(self, results):
572581
os.makedirs(output_folder, exist_ok=True)
573582

574583
save_kwargs = {}
575-
if file_extension in ['.jpg', '.jpeg', '.webp']:
584+
image_to_save = result['image_data']
585+
586+
# Convert RGBA to RGB for JPEG format
587+
if file_extension in ['.jpg', '.jpeg']:
588+
if image_to_save.mode == 'RGBA':
589+
image_to_save = image_to_save.convert('RGB')
576590
save_kwargs['quality'] = save_quality
577-
578-
result['image_data'].save(final_output_path, **save_kwargs)
591+
elif file_extension == '.webp':
592+
save_kwargs['quality'] = save_quality
593+
594+
image_to_save.save(final_output_path, **save_kwargs)
579595
saved_files.append(final_output_path)
580596
self.logger.info(f"成功保存文件: {final_output_path}")
581597
except Exception as e:

desktop_qt_ui/core/config_models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class RenderSettings(BaseModel):
5757
auto_rotate_symbols: bool = True
5858
rtl: bool = False
5959
layout_mode: str = "smart_scaling"
60+
max_font_size: int = 0
61+
font_scale_ratio: float = 1.0
62+
center_text_in_bubble: bool = False
6063

6164
class UpscaleSettings(BaseModel):
6265
upscaler: str = "esrgan"

desktop_qt_ui/editor/commands.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def __init__(self, model: EditorModel, region_index: int, old_data: Dict[str, An
3737
def _apply_data(self, data_to_apply: Dict[str, Any]):
3838
"""将给定的数据字典应用到模型中的区域。"""
3939
if not (0 <= self._index < len(self._model._regions)):
40-
print(f"DEBUG: Invalid index {self._index} for model with {len(self._model._regions)} regions")
4140
return
4241

4342
# 检查 center 是否改变
@@ -46,16 +45,11 @@ def _apply_data(self, data_to_apply: Dict[str, Any]):
4645
center_changed = old_center != new_center
4746

4847
# 直接更新模型中的区域字典
49-
old_angle = self._model._regions[self._index].get('angle', 0.0)
50-
new_angle = data_to_apply.get('angle', 0.0)
51-
print(f"DEBUG: Applying data to region {self._index}: angle {old_angle:.2f} -> {new_angle:.2f}, center_changed={center_changed}")
52-
5348
self._model._regions[self._index] = data_to_apply
5449

5550
# 如果 center 改变了,需要触发完全更新,重新创建 item
5651
# 否则只触发单个 item 更新
5752
if center_changed:
58-
print(f"DEBUG: Center changed, triggering full update")
5953
# 保存当前选择状态
6054
old_selection = self._model.get_selection()
6155
# 触发完全更新

desktop_qt_ui/editor/graphics_items.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,35 @@ def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
619619
event.accept()
620620

621621
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
622+
# CRITICAL FIX: 即使 _interaction_mode 是 'none',也要检查位置是否变化
623+
# Qt 的 ItemIsMovable 会自动移动 item,但不会触发我们的 move 逻辑
624+
current_pos = self.pos()
625+
if current_pos != self.visual_center:
626+
# 位置发生了变化,需要更新模型
627+
delta_x = current_pos.x() - self.visual_center.x()
628+
delta_y = current_pos.y() - self.visual_center.y()
629+
630+
if abs(delta_x) >= 0.1 or abs(delta_y) >= 0.1:
631+
# 更新 lines:所有点都加上偏移量
632+
new_lines = []
633+
for poly in self.desktop_geometry.lines:
634+
new_poly = [[p[0] + delta_x, p[1] + delta_y] for p in poly]
635+
new_lines.append(new_poly)
636+
637+
# 更新内部状态
638+
self.visual_center = current_pos
639+
self.desktop_geometry.center = [current_pos.x(), current_pos.y()]
640+
self.desktop_geometry.lines = new_lines
641+
642+
import copy
643+
new_region_data = copy.deepcopy(self.region_data)
644+
new_region_data['center'] = [current_pos.x(), current_pos.y()]
645+
new_region_data['lines'] = new_lines
646+
647+
# 更新模型
648+
self.geometry_callback(self.region_index, new_region_data)
649+
self.region_data.update(new_region_data)
650+
622651
if self._interaction_mode != 'none':
623652
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
624653

@@ -653,8 +682,6 @@ def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
653682
new_region_data['center'] = [new_center.x(), new_center.y()]
654683
new_region_data['lines'] = new_lines
655684

656-
print(f"[MOVE DEBUG] Region {self.region_index}: old_center=({old_center.x():.1f}, {old_center.y():.1f}), new_center=({new_center.x():.1f}, {new_center.y():.1f})")
657-
658685
# Update the model via callback
659686
self.geometry_callback(self.region_index, new_region_data)
660687
self.region_data.update(new_region_data)

desktop_qt_ui/editor/graphics_view.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,9 @@ def _update_text_visuals(self):
558558
render_params.get('hyphenate'),
559559
render_params.get('font_size_offset'),
560560
render_params.get('font_size_minimum'),
561+
render_params.get('max_font_size'),
562+
render_params.get('font_scale_ratio'),
563+
render_params.get('center_text_in_bubble'),
561564
render_params.get('text_stroke_width'),
562565
render_params.get('shadow_radius'),
563566
render_params.get('shadow_strength'),

desktop_qt_ui/editor/text_renderer_backend.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,30 +113,45 @@ def render_text_for_region(text_block: TextBlock, dst_points: np.ndarray, transf
113113
h_ext = int((w_temp / r_orig - h_temp) // 2) if r_orig > 0 else 0
114114
if h_ext >= 0:
115115
box = np.zeros((h_temp + h_ext * 2, w_temp, 4), dtype=np.uint8)
116-
box[h_ext:h_ext+h_temp, 0:w_temp] = rendered_surface
116+
# Center vertically when enabled
117+
if config_obj and config_obj.render.center_text_in_bubble and config_obj.render.disable_auto_wrap:
118+
box[h_ext:h_ext+h_temp, 0:w_temp] = rendered_surface
119+
else:
120+
box[0:h_temp, 0:w_temp] = rendered_surface
117121
else:
118122
box = rendered_surface.copy()
119123
else:
120124
w_ext = int((h_temp * r_orig - w_temp) // 2)
121125
if w_ext >= 0:
122126
box = np.zeros((h_temp, w_temp + w_ext * 2, 4), dtype=np.uint8)
123-
# 完全匹配后端逻辑:文字放在左侧而不是居中
124-
box[0:h_temp, 0:w_temp] = rendered_surface
127+
# Center horizontally when enabled
128+
if config_obj and config_obj.render.center_text_in_bubble and config_obj.render.disable_auto_wrap:
129+
box[0:h_temp, w_ext:w_ext+w_temp] = rendered_surface
130+
else:
131+
box[0:h_temp, 0:w_temp] = rendered_surface
125132
else:
126133
box = rendered_surface.copy()
127134
else: # Vertical
128135
if r_temp > r_orig:
129136
h_ext = int(w_temp / (2 * r_orig) - h_temp / 2) if r_orig > 0 else 0
130137
if h_ext >= 0:
131138
box = np.zeros((h_temp + h_ext * 2, w_temp, 4), dtype=np.uint8)
132-
box[0:h_temp, 0:w_temp] = rendered_surface
139+
# Center vertically when enabled
140+
if config_obj and config_obj.render.center_text_in_bubble and config_obj.render.disable_auto_wrap:
141+
box[h_ext:h_ext+h_temp, 0:w_temp] = rendered_surface
142+
else:
143+
box[0:h_temp, 0:w_temp] = rendered_surface
133144
else:
134145
box = rendered_surface.copy()
135146
else:
136147
w_ext = int((h_temp * r_orig - w_temp) / 2)
137148
if w_ext >= 0:
138149
box = np.zeros((h_temp, w_temp + w_ext * 2, 4), dtype=np.uint8)
139-
box[0:h_temp, w_ext:w_ext+w_temp] = rendered_surface
150+
# Center horizontally when enabled
151+
if config_obj and config_obj.render.center_text_in_bubble and config_obj.render.disable_auto_wrap:
152+
box[0:h_temp, w_ext:w_ext+w_temp] = rendered_surface
153+
else:
154+
box[0:h_temp, 0:w_temp] = rendered_surface
140155
else:
141156
box = rendered_surface.copy()
142157

desktop_qt_ui/main_window.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _connect_signals(self):
100100

101101
# --- View Switching Connections ---
102102
self.main_view_action.triggered.connect(lambda: self.stacked_widget.setCurrentWidget(self.main_view))
103-
self.editor_view_action.triggered.connect(self.enter_editor_mode)
103+
self.editor_view_action.triggered.connect(self.switch_to_editor_view)
104104

105105
# --- 撤销/重做连接到编辑器controller ---
106106
self.undo_action.triggered.connect(self.editor_controller.undo)
@@ -133,6 +133,13 @@ def on_task_completed(self, saved_files: list):
133133
self.logger.info("User chose to open results in editor.")
134134
self.enter_editor_mode(files_to_load=saved_files)
135135

136+
def switch_to_editor_view(self):
137+
"""
138+
Simply switches to the editor view without reloading file lists.
139+
Used when user manually switches views.
140+
"""
141+
self.stacked_widget.setCurrentWidget(self.editor_view)
142+
136143
def enter_editor_mode(self, file_to_load: str = None, files_to_load: list = None):
137144
"""
138145
Switches to the editor view and loads the necessary files.

desktop_qt_ui/services/config_service.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from dataclasses import dataclass, field
1111
from typing import Any, Dict, List, Optional
1212

13-
from dotenv import dotenv_values, set_key
13+
from dotenv import dotenv_values, set_key, load_dotenv
1414

1515
from core.config_models import AppSettings
1616

@@ -237,11 +237,13 @@ def save_env_var(self, key: str, value: str) -> bool:
237237
os.makedirs(os.path.dirname(self.env_path), exist_ok=True)
238238
with open(self.env_path, 'w'):
239239
pass
240-
240+
241241
set_key(self.env_path, key, value)
242+
# 重新加载环境变量到os.environ,使其立即生效
243+
load_dotenv(self.env_path, override=True)
242244
self.logger.info(f"保存环境变量: {key}")
243245
return True
244-
246+
245247
except Exception as e:
246248
self.logger.error(f"保存环境变量失败: {e}")
247249
return False

0 commit comments

Comments
 (0)