Skip to content

Commit 7e34cdd

Browse files
hgmzhnclaude
andcommitted
Release v1.6.3 - 添加 PaddleOCR 支持
新功能: - ✨ 新增 PaddleOCR (PP-OCRv5) 支持 - 支持中文/日文/英文识别 (paddleocr) - 支持韩文/英文识别 (paddleocr_korean) - 使用透视变换和自动旋转处理倾斜文本 - 自动识别并旋转竖排文本 技术改进: - 🔧 修复 PyInstaller 打包后的资源路径问题 - 统一使用 sys._MEIPASS 处理打包环境 - .env 文件与 exe 同级目录 - 所有资源文件在 _internal 目录 - 🔧 修复配置文件路径适配(config-example.json, gpt_config-example.yaml) - 🔧 更新 GitHub Actions 模型下载到 v1.6.0 - 🔧 添加 PaddleOCR-main 到 .gitignore 🤖 Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d26827b commit 7e34cdd

19 files changed

Lines changed: 469 additions & 152 deletions

File tree

.github/workflows/build-and-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
GH_TOKEN: ${{ github.token }}
6969
run: |
7070
echo "Downloading models from release assets..."
71-
gh release download v1.4.6 -p "models.7z"
71+
gh release download v1.6.0 -p "models.7z"
7272
7373
echo "Decompressing models..."
7474
7z x "models.7z" -o./extracted_models

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ result/
3232
# Wheel files
3333
*.whl
3434

35+
# Archive files
36+
*.7z
37+
38+
# Model files
39+
models/ocr/*.onnx
40+
models/ocr/*.txt
41+
3542
# Backup files
3643
*.bak
3744
*.backup
@@ -51,6 +58,7 @@ desktop-ui/temp/
5158
19_translations.json
5259
desktop-ui/user_data/
5360
manga-image-translator-main/
61+
PaddleOCR-main/
5462

5563
# Ignore temporary clone check directory
5664
temp_clone_check/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
- `ocr48px`: 48像素OCR模型
123123
- `ocr48px_ctc`: CTC OCR模型
124124
- `mocr`: Manga OCR专用模型
125+
- `paddleocr`: PaddleOCR (基于PaddlePaddle的OCR引擎)
125126

126127
## 安装和运行
127128

cache_tufup/metadata/root.json

Lines changed: 0 additions & 71 deletions
This file was deleted.

desktop_qt_ui/app_logic.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def open_output_folder(self):
197197
def open_font_directory(self):
198198
import subprocess
199199
import sys
200+
# fonts目录在_internal里(打包后)或项目根目录(开发时)
200201
fonts_dir = os.path.join(self.config_service.root_dir, 'fonts')
201202
try:
202203
if not os.path.exists(fonts_dir):
@@ -213,6 +214,7 @@ def open_font_directory(self):
213214
def open_dict_directory(self):
214215
import subprocess
215216
import sys
217+
# dict目录在_internal里(打包后)或项目根目录(开发时)
216218
dict_dir = os.path.join(self.config_service.root_dir, 'dict')
217219
try:
218220
if not os.path.exists(dict_dir):
@@ -228,13 +230,14 @@ def open_dict_directory(self):
228230

229231
def get_hq_prompt_options(self) -> List[str]:
230232
try:
233+
# dict目录在_internal里(打包后)或项目根目录(开发时)
231234
dict_dir = os.path.join(self.config_service.root_dir, 'dict')
232235
if not os.path.isdir(dict_dir):
233236
return []
234237
prompt_files = sorted([
235-
f for f in os.listdir(dict_dir)
238+
f for f in os.listdir(dict_dir)
236239
if f.lower().endswith('.json') and f not in [
237-
'system_prompt_hq.json',
240+
'system_prompt_hq.json',
238241
'system_prompt_line_break.json'
239242
]
240243
])

desktop_qt_ui/core/config_models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ class TranslatorSettings(BaseModel):
77
translator: str = "openai_hq"
88
target_lang: str = "CHS"
99
no_text_lang_skip: bool = False
10-
gpt_config: Optional[str] = "../examples/gpt_config-example.yaml"
10+
# 相对路径,后端会用BASE_PATH拼接(打包后=_internal,开发时=项目根目录)
11+
gpt_config: Optional[str] = "examples/gpt_config-example.yaml"
1112
high_quality_prompt_path: Optional[str] = "dict/prompt_example.json"
1213

1314
class OcrSettings(BaseModel):

desktop_qt_ui/editor/graphics_items.py

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -482,48 +482,7 @@ def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
482482
local_pos = event.pos()
483483

484484
# 限制日志频率:每秒最多打印一次
485-
import time
486-
current_time = time.time()
487-
if not hasattr(self, '_last_mouse_log_time'):
488-
self._last_mouse_log_time = 0
489-
490-
should_log = current_time - self._last_mouse_log_time >= 1.0
491-
492-
if should_log:
493-
print(f"\n[MOUSE DEBUG] Region {self.region_index}")
494-
print(f" scenePos={event.scenePos()}")
495-
print(f" self.pos()={self.pos()}")
496-
print(f" local_pos={local_pos}")
497-
print(f" isSelected={self.isSelected()}")
498-
print(f" zValue={self.zValue()}")
499-
print(f" boundingRect={self.boundingRect()}")
500-
501-
# 检查Qt在这个位置找到了哪些items
502-
if self.scene():
503-
items_at_pos = self.scene().items(event.scenePos())
504-
print(f" Qt found {len(items_at_pos)} items at click position:")
505-
for idx, item in enumerate(items_at_pos):
506-
is_self = (item == self)
507-
item_type = type(item).__name__
508-
z = item.zValue() if hasattr(item, 'zValue') else 'N/A'
509-
region_idx = item.region_index if hasattr(item, 'region_index') else 'N/A'
510-
print(f" [{idx}] {item_type} (region={region_idx}, z={z}) {'<-- THIS' if is_self else ''}")
511-
512-
print(f" polygons count={len(self.polygons)}")
513-
for i, poly in enumerate(self.polygons):
514-
if poly.containsPoint(local_pos, Qt.FillRule.WindingFill):
515-
print(f" polygon {i}: CONTAINS local_pos!")
516-
517-
# 测试shape()是否包含点击点
518-
test_shape = self.shape()
519-
shape_contains = test_shape.contains(local_pos)
520-
print(f" shape().contains(local_pos)={shape_contains}")
521-
print(f" shape().boundingRect()={test_shape.boundingRect()}")
522-
523-
self._last_mouse_log_time = current_time
524-
525485
if event.button() == Qt.MouseButton.LeftButton:
526-
print(f"[ITEM] RegionTextItem.mousePressEvent: region={self.region_index}, isSelected={self.isSelected()}")
527486
if self.isSelected():
528487
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
529488

@@ -540,7 +499,6 @@ def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
540499
# --- End Snapshot ---
541500

542501
handle, indices = self._get_handle_at(local_pos)
543-
print(f"[HANDLE DEBUG] 检测到 handle='{handle}', indices={indices}, isSelected={self.isSelected()}, show_white_box={self._show_white_box}, white_frame_rect_local={self._white_frame_rect_local}")
544502
if handle:
545503
self._interaction_mode = handle
546504
self._drag_handle_indices = indices
@@ -576,12 +534,10 @@ def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
576534
if self.scene() and self.scene().views():
577535
view = self.scene().views()[0]
578536
if hasattr(view, 'model'):
579-
print(f"[ITEM] Region {self.region_index} not selected, calling model.set_selection directly")
580537
view.model.set_selection([self.region_index])
581538
event.accept() # Accept 事件,阻止传播到 GraphicsView
582539
return
583540
# 如果无法获取 model,fallback 到原来的行为
584-
print(f"[ITEM] Region {self.region_index} not selected, letting GraphicsView handle selection")
585541
super().mousePressEvent(event)
586542
event.ignore()
587543
return

desktop_qt_ui/editor/graphics_view.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,6 @@ def _perform_single_item_update(self, index):
457457

458458
def _perform_render_update(self):
459459
"""执行实际的渲染更新,由防抖计时器调用。"""
460-
print(f"[_perform_render_update] 开始完全更新")
461460
# Clear old region items safely
462461
for item in self._region_items:
463462
try:
@@ -470,11 +469,9 @@ def _perform_render_update(self):
470469

471470
# Add a new item for each REGION
472471
regions = self.model.get_regions()
473-
print(f"[_perform_render_update] regions 数量: {len(regions)}")
474472
for i, region_data in enumerate(regions):
475473
if not region_data.get('lines'):
476474
continue
477-
print(f"[_perform_render_update] 创建 item {i}")
478475
item = RegionTextItem(
479476
region_data,
480477
i,
@@ -483,7 +480,6 @@ def _perform_render_update(self):
483480
item.setZValue(100)
484481
self.scene.addItem(item)
485482
self._region_items.append(item)
486-
print(f"[_perform_render_update] 完成,共创建 {len(self._region_items)} 个 items")
487483

488484
# After updating items, recalculate all rendering data
489485
self.recalculate_render_data()
@@ -598,9 +594,6 @@ def _update_text_visuals(self):
598594
item.update_text_pixmap(QPixmap(), QPointF(0, 0))
599595

600596
# 无论是否有渲染结果,都设置绿框数据(即使 translation 为空)
601-
print(f"[_update_single_region_text_visual] region {i}: 设置绿框, dst_points is None: {self._dst_points_cache[i] is None}")
602-
if self._dst_points_cache[i] is not None:
603-
print(f"[_update_single_region_text_visual] region {i}: dst_points shape: {self._dst_points_cache[i].shape}")
604597
item.set_dst_points(self._dst_points_cache[i])
605598

606599
def _recalculate_single_region_render_data(self, index):
@@ -937,7 +930,6 @@ def mousePressEvent(self, event):
937930
# 如果有 item 处理了事件,event 会被 accept
938931
# 只有真正点击空白时,event 才不会被 accept
939932
if not event.isAccepted():
940-
print(f"[VIEW] Click on empty area (event not accepted), clearing selection")
941933
self.model.set_selection([])
942934

943935
def mouseMoveEvent(self, event):
@@ -1109,7 +1101,11 @@ def _finish_drawing(self):
11091101
# Convert final mask to numpy and update model
11101102
ptr = mask_image.constBits()
11111103
ptr.setsize(mask_image.sizeInBytes())
1112-
new_mask_np = np.array(ptr).reshape(mask_image.height(), mask_image.width())
1104+
# Use bytesPerLine to handle row padding correctly
1105+
bytes_per_line = mask_image.bytesPerLine()
1106+
new_mask_np = np.array(ptr).reshape(mask_image.height(), bytes_per_line)
1107+
# Crop to actual width if there's padding
1108+
new_mask_np = new_mask_np[:, :mask_image.width()].copy()
11131109

11141110
# --- Refactored to Command Pattern ---
11151111
from .commands import MaskEditCommand
@@ -1160,7 +1156,6 @@ def _on_brush_size_changed(self, size: int):
11601156

11611157
def _on_selection_changed(self, selected_indices: list):
11621158
"""同步model的selection到Qt item的selected状态"""
1163-
print(f"[VIEW] _on_selection_changed called: selected_indices={selected_indices}")
11641159

11651160
# 先清除所有item的selection
11661161
for item in self._region_items:
@@ -1170,9 +1165,7 @@ def _on_selection_changed(self, selected_indices: list):
11701165
# 设置新选中的items
11711166
for idx in selected_indices:
11721167
if 0 <= idx < len(self._region_items):
1173-
print(f"[VIEW] Setting item {idx} selected=True")
11741168
self._region_items[idx].setSelected(True)
1175-
print(f"[VIEW] After setSelected, item.isSelected()={self._region_items[idx].isSelected()}")
11761169

11771170
def _update_cursor(self):
11781171
"""Updates the cursor to match the selected tool and brush size."""

desktop_qt_ui/editor/text_renderer_backend.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ def update_font_config(font_filename: str):
3434
if os.path.exists(font_path):
3535
try:
3636
set_font(font_path)
37-
print(f"[BackendTextRenderer] Font updated: {font_path}")
3837
except Exception as e:
39-
print(f"[BackendTextRenderer] Failed to update font: {e}")
38+
pass # Silently ignore font update errors
4039

4140
def render_text_for_region(text_block: TextBlock, dst_points: np.ndarray, transform, render_params: dict, pure_zoom: float = 1.0, total_regions: int = 1):
4241
"""

desktop_qt_ui/main.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,14 @@ def main():
4141
app = QApplication(sys.argv)
4242

4343
# 2. 初始化所有服务
44-
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
44+
# 设置正确的根目录:打包后指向_internal,开发时指向项目根目录
45+
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
46+
# PyInstaller打包环境:所有资源在_internal目录
47+
root_dir = sys._MEIPASS
48+
else:
49+
# 开发环境:资源在项目根目录
50+
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
51+
4552
if not init_services(root_dir):
4653
logging.fatal("Fatal: Service initialization failed.")
4754
sys.exit(1)

0 commit comments

Comments
 (0)