Compare commits

...

6 Commits
dev ... main

BIN
.DS_Store vendored

Binary file not shown.

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.9 (OCRmyPDF-GUI)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (OCRmyPDF-GUI)" project-jdk-type="Python SDK" />
</project>

@ -1,15 +1,34 @@
# OCRmyPDF GUI
OCRmyPDF的图形用户界面让OCR处理PDF文件变得简单
OCRmyPDF-GUI是一个图形用户界面让[OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF)命令行工具的强大功能变得简单易用。通过直观的界面您可以为扫描的PDF文件添加文本层使其可搜索和可复制粘贴而无需记忆复杂的命令行参数
## 功能特点
![img.png](docs/images/img.png)
- 简洁直观的图形界面
- 批量处理PDF文件
- 拖放支持
- 多语言OCR支持
- 可自定义OCR选项
- 保存处理配置
## 主要特点
- **简洁直观的图形界面**无需命令行知识即可使用OCRmyPDF的全部功能
- **批量处理**一次处理多个PDF文件并显示详细进度
- **拖放支持**:直接拖放文件到程序窗口
- **多语言OCR支持**支持100多种语言的文本识别
- **智能文件命名**:支持多种输出文件命名选项,包括自定义前缀
- **高级OCR选项**:自动校正倾斜页面、自动旋转、清理图像等
- **配置管理**保存和加载常用OCR配置
- **详细状态反馈**:提供处理状态和结果的清晰反馈
## 功能演示
```
OCRmyPDF-GUI提供以下功能
✓ 添加OCR文本层到PDF文件
✓ 处理单个或批量PDF文件
✓ 多语言文档识别
✓ 自动校正倾斜页面
✓ 自动旋转页面
✓ 优化输出文件大小
✓ 自定义输出文件命名
✓ 保存常用处理配置
```
## 安装要求
@ -20,57 +39,157 @@ OCRmyPDF的图形用户界面让OCR处理PDF文件变得简单。
## 安装步骤
1. 安装OCRmyPDF和其依赖
### 1. 安装OCRmyPDF和其依赖
```bash
# macOS
brew install ocrmypdf
# Ubuntu/Debian
apt install ocrmypdf
sudo apt install ocrmypdf
# Fedora
sudo dnf install ocrmypdf
# Windows (WSL)
sudo apt install ocrmypdf
# 或使用pip
pip install ocrmypdf
```
2. 安装GUI依赖
### 2. 安装GUI依赖
```bash
pip install PySide6
```
3. 克隆本仓库
### 3. 克隆本仓库
```bash
git clone https://github.com/yourusername/OCRmyPDF-GUI.git
cd OCRmyPDF-GUI
```
## 使用方法
运行启动脚本:
### 4. 运行应用程序
```bash
python run.py
```
或在Windows上双击`run.py`文件。
## 安装Tesseract语言包
默认情况下OCRmyPDF只安装英语语言包。要使用其他语言进行OCR需要安装额外的语言包
### macOS
```bash
# 安装所有语言包
brew install tesseract-lang
# 或者手动安装特定语言包
# 1. 下载语言包文件,例如简体中文:
# https://github.com/tesseract-ocr/tessdata/raw/main/chi_sim.traineddata
# 2. 复制到Tesseract的tessdata目录
# sudo cp chi_sim.traineddata /opt/homebrew/share/tessdata/
# 或
# sudo cp chi_sim.traineddata /usr/local/share/tessdata/
```
### Ubuntu/Debian
```bash
# 安装特定语言包,例如简体中文:
sudo apt-get install tesseract-ocr-chi-sim
# 查看所有可用语言包:
apt-cache search tesseract-ocr
```
### Fedora
```bash
# 安装特定语言包,例如简体中文:
sudo dnf install tesseract-langpack-chi_sim
# 查看所有可用语言包:
dnf search tesseract
```
### Windows
1. 从以下网址下载所需语言包文件:
https://github.com/tesseract-ocr/tessdata/
2. 将下载的`.traineddata`文件放置在Tesseract安装目录的tessdata文件夹中通常位于
`C:\Program Files\Tesseract-OCR\tessdata`
### 常用语言代码
- `eng` - 英语
- `chi_sim` - 简体中文
- `chi_tra` - 繁体中文
- `jpn` - 日语
- `kor` - 韩语
- `fra` - 法语
- `deu` - 德语
- `rus` - 俄语
- `spa` - 西班牙语
- `ita` - 意大利语
更多信息请参考:[OCRmyPDF语言包文档](https://ocrmypdf.readthedocs.io/en/latest/languages.html)
## 项目结构
```
OCRmyPDF-GUI/
├── src/ # 源代码
│ ├── core/ # 核心功能
│ │ ├── config.py # 配置管理
│ │ └── ocr_engine.py # OCR引擎封装
│ ├── gui/ # 图形界面
│ │ ├── main_window.py # 主窗口
│ │ ├── batch_dialog.py # 批量处理对话框
│ │ └── settings.py # 设置对话框
│ └── utils/ # 工具函数
│ └── file_utils.py # 文件操作工具
├── run.py # 启动脚本
└── README.md # 项目说明
```
## 开发计划
- [ ] 高级OCR选项
- [ ] 多语言界面
- [ ] 高级OCR选项扩展
- [ ] 多语言界面支持
- [ ] 暗黑模式
- [ ] 自定义输出文件名模板
- [ ] 处理历史记录
- [ ] 集成PDF预览功能
## 贡献指南
我们欢迎并感谢所有形式的贡献!以下是一些参与项目的方式:
1. **提交问题和建议**如果您发现bug或有改进建议请[创建issue](https://github.com/yourusername/OCRmyPDF-GUI/issues/new)。
2. **提交代码**
- Fork 这个仓库
- 创建您的特性分支 (`git checkout -b feature/amazing-feature`)
- 提交您的更改 (`git commit -m 'Add some amazing feature'`)
- 推送到分支 (`git push origin feature/amazing-feature`)
- 开启一个Pull Request
3. **改进文档**帮助我们完善文档包括README、安装说明或用户指南。
请确保您的代码符合项目的代码风格,并添加适当的测试。
## 贡献
## 关于OCRmyPDF
欢迎提交Pull Request或Issue。
本项目是[OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF)命令行工具的图形界面封装。OCRmyPDF是一个强大的工具可以为扫描的PDF文件添加OCR文本层使其可搜索和可复制粘贴。OCRmyPDF-GUI旨在让更多不熟悉命令行的用户能够轻松使用OCRmyPDF的强大功能
## 许可证
本项目采用与OCRmyPDF相同的许可证。
本项目采用[Mozilla Public License 2.0 (MPL-2.0)](https://www.mozilla.org/en-US/MPL/2.0/)许可证与OCRmyPDF原项目保持一致
## 致谢

BIN
docs/.DS_Store vendored

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

@ -1,2 +1,3 @@
PySide6>=6.5.0
pytest>=7.0.0
pytest>=7.0.0
ocrmypdf>=15.0.0

BIN
resources/.DS_Store vendored

Binary file not shown.

BIN
src/.DS_Store vendored

Binary file not shown.

@ -4,32 +4,57 @@ from pathlib import Path
import logging
class Config:
"""配置管理类,负责加载和保存应用程序配置"""
"""
配置管理类
负责应用程序配置的加载保存和访问操作
管理用户偏好设置最近使用的文件和目录以及默认OCR选项
使用JSON格式存储配置保存在用户主目录的.ocrmypdf-gui文件夹中
属性:
logger: 日志记录器用于记录配置操作的日志
config_dir: 配置目录路径
config_file: 配置文件路径
default_config: 默认配置字典
current_config: 当前使用的配置字典
"""
def __init__(self):
"""
初始化配置管理器
设置配置文件路径初始化默认配置并从磁盘加载现有配置如果存在
如果配置文件不存在将使用默认配置并创建新的配置文件
"""
self.logger = logging.getLogger(__name__)
self.config_dir = Path.home() / ".ocrmypdf-gui"
self.config_file = self.config_dir / "config.json"
self.default_config = {
"recent_files": [],
"recent_output_dirs": [],
"default_options": {
"deskew": True,
"rotate_pages": True,
"clean": False,
"output_type": "pdfa",
"jobs": 4
"recent_files": [], # 最近使用的文件列表
"recent_output_dirs": [], # 最近使用的输出目录列表
"default_options": { # 默认OCR选项
"deskew": True, # 自动校正倾斜页面
"rotate_pages": True, # 自动旋转页面
"clean": False, # 清理图像
"output_type": "pdfa", # 输出文件类型
"jobs": 4 # 并行处理任务数
},
"ui": {
"theme": "system",
"language": "zh_CN"
"ui": { # 用户界面设置
"theme": "system", # 主题(跟随系统、亮色、暗色)
"language": "zh_CN" # 界面语言
}
}
self.current_config = self.default_config.copy()
self.load_config()
def load_config(self):
"""加载配置文件"""
"""
从磁盘加载配置文件
如果配置目录不存在则创建该目录
如果配置文件存在则读取并与默认配置合并
如果配置文件不存在或加载失败则使用默认配置
"""
if not self.config_dir.exists():
self.config_dir.mkdir(parents=True, exist_ok=True)
@ -47,7 +72,12 @@ class Config:
self.save_config()
def save_config(self):
"""保存配置文件"""
"""
保存配置到磁盘
将当前配置以JSON格式写入配置文件
如果保存过程中出现错误将记录错误日志
"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.current_config, f, indent=2, ensure_ascii=False)
@ -56,7 +86,16 @@ class Config:
self.logger.error(f"保存配置文件出错: {e}")
def _merge_config(self, target, source):
"""递归合并配置字典"""
"""
递归合并配置字典
将source字典中的值合并到target字典中保留原有结构
对于嵌套字典递归合并内部结构
Args:
target: 目标字典合并结果将存储在此
source: 源字典其值将合并到目标字典
"""
for key, value in source.items():
if key in target and isinstance(target[key], dict) and isinstance(value, dict):
self._merge_config(target[key], value)
@ -64,14 +103,17 @@ class Config:
target[key] = value
def get(self, key, default=None):
"""获取配置项
"""
获取配置项值
支持使用点号分隔的多级键名访问嵌套配置项
Args:
key: 配置项键名支持点号分隔的多级键名 'ui.theme'
key: 配置项键名'ui.theme''default_options.deskew'
default: 如果配置项不存在返回的默认值
Returns:
配置项的值
配置项的值如果不存在则返回默认值
"""
keys = key.split('.')
value = self.current_config
@ -85,11 +127,15 @@ class Config:
return value
def set(self, key, value):
"""设置配置项
"""
设置配置项值
支持使用点号分隔的多级键名设置嵌套配置项
设置后会自动保存配置到磁盘
Args:
key: 配置项键名支持点号分隔的多级键名 'ui.theme'
value: 配置项的值
key: 配置项键名'ui.theme''default_options.deskew'
value: 要设置的值
"""
keys = key.split('.')
target = self.current_config
@ -103,7 +149,11 @@ class Config:
self.save_config()
def add_recent_file(self, file_path):
"""添加最近使用的文件
"""
添加最近使用的文件
如果文件已在列表中则将其移到列表首位
保留最近使用的10个文件
Args:
file_path: 文件路径
@ -116,7 +166,11 @@ class Config:
self.set('recent_files', recent_files[:10])
def add_recent_output_dir(self, dir_path):
"""添加最近使用的输出目录
"""
添加最近使用的输出目录
如果目录已在列表中则将其移到列表首位
保留最近使用的10个目录
Args:
dir_path: 目录路径

@ -5,9 +5,26 @@ import sys
import os
class OCREngine:
"""OCR引擎类封装OCRmyPDF的调用"""
"""
OCR引擎类
封装OCRmyPDF命令行工具的调用提供PDF文件的OCR处理功能
负责检测OCRmyPDF和Tesseract的可用性获取支持的语言列表
处理单个文件和批量处理多个文件以及处理各种错误状态
属性:
logger: 日志记录器
available_languages: 系统中可用的Tesseract语言包列表
last_error: 最近一次处理错误的详细信息
"""
def __init__(self):
"""
初始化OCR引擎
检查OCRmyPDF命令行工具是否可用并获取系统中已安装的Tesseract语言包列表
如果OCRmyPDF不可用将记录错误并将可用语言列表设为空
"""
self.logger = logging.getLogger(__name__)
# 检查命令行工具是否可用
try:
@ -19,51 +36,216 @@ class OCREngine:
)
if result.returncode == 0:
self.logger.info(f"OCRmyPDF命令行工具可用: {result.stdout.strip()}")
# 获取支持的语言列表
self.available_languages = self.get_available_languages()
self.logger.info(f"可用的OCR语言: {', '.join(self.available_languages)}")
else:
self.logger.warning("OCRmyPDF命令行工具返回错误")
self.available_languages = []
except FileNotFoundError:
self.logger.error("OCRmyPDF命令行工具未找到")
self.available_languages = []
def get_available_languages(self):
"""
获取系统中已安装的Tesseract语言包列表
通过调用tesseract命令的--list-langs选项获取系统中已安装的所有语言包
Returns:
list: 已安装的语言代码列表如果获取失败则返回空列表
"""
try:
result = subprocess.run(
["tesseract", "--list-langs"],
capture_output=True,
text=True,
check=False
)
if result.returncode == 0:
# 解析输出,跳过第一行(标题行)
languages = result.stdout.strip().split('\n')[1:]
return [lang.strip() for lang in languages]
return []
except Exception as e:
self.logger.error(f"获取语言列表失败: {e}")
return []
def get_language_name(self, lang_code):
"""
获取语言代码对应的显示名称
将Tesseract语言代码转换为用户友好的显示名称同时显示中文和英文名称
Args:
lang_code (str): 语言代码'eng''chi_sim'
Returns:
str: 语言的显示名称'英语 (English)''简体中文 (Chinese Simplified)'
如果没有对应的显示名称则返回原始语言代码
"""
language_names = {
'eng': '英语 (English)',
'chi_sim': '简体中文 (Chinese Simplified)',
'chi_tra': '繁体中文 (Chinese Traditional)',
'jpn': '日语 (Japanese)',
'kor': '韩语 (Korean)',
'fra': '法语 (French)',
'deu': '德语 (German)',
'rus': '俄语 (Russian)',
'spa': '西班牙语 (Spanish)',
'ita': '意大利语 (Italian)',
'por': '葡萄牙语 (Portuguese)',
'nld': '荷兰语 (Dutch)',
'ara': '阿拉伯语 (Arabic)',
'hin': '印地语 (Hindi)',
'vie': '越南语 (Vietnamese)',
'tha': '泰语 (Thai)',
'tur': '土耳其语 (Turkish)',
'heb': '希伯来语 (Hebrew)',
'swe': '瑞典语 (Swedish)',
'fin': '芬兰语 (Finnish)',
'dan': '丹麦语 (Danish)',
'nor': '挪威语 (Norwegian)',
'pol': '波兰语 (Polish)',
'ukr': '乌克兰语 (Ukrainian)',
'ces': '捷克语 (Czech)',
'slk': '斯洛伐克语 (Slovak)',
'hun': '匈牙利语 (Hungarian)',
'ron': '罗马尼亚语 (Romanian)',
'bul': '保加利亚语 (Bulgarian)',
'ell': '希腊语 (Greek)',
'ind': '印度尼西亚语 (Indonesian)',
'msa': '马来语 (Malay)',
'cat': '加泰罗尼亚语 (Catalan)',
'lav': '拉脱维亚语 (Latvian)',
'lit': '立陶宛语 (Lithuanian)',
'est': '爱沙尼亚语 (Estonian)'
}
return language_names.get(lang_code, lang_code)
def process_file(self, input_file, output_file, options=None):
"""
使用OCRmyPDF处理单个文件
使用OCRmyPDF处理单个PDF文件
处理前会检查输入文件是否存在是否可读以及输出目录是否可写
会自动检测文件是否已经OCR过并返回相应的状态码
Args:
input_file (str): 输入PDF文件路径
output_file (str): 输出PDF文件路径
options (dict): OCR选项
options (dict): OCR处理选项包括languagedeskewrotate_pagescleanoptimize等
Returns:
bool: 处理是否成功
int: 处理结果状态码
0 - 处理失败
1 - 处理成功
2 - 文件已有文本层已OCR过
"""
if options is None:
options = {}
self.logger.info(f"处理文件: {input_file} -> {output_file}")
# 检查输入文件是否存在
if not Path(input_file).exists():
self.logger.error(f"输入文件不存在: {input_file}")
return 0
# 检查输入文件是否可读
if not os.access(input_file, os.R_OK):
self.logger.error(f"输入文件不可读: {input_file}")
return 0
# 检查输出目录是否可写
output_dir = Path(output_file).parent
if not os.access(output_dir, os.W_OK):
self.logger.error(f"输出目录不可写: {output_dir}")
return 0
# 处理文件
result = self._process_file_internal(input_file, output_file, options, force_ocr=False)
# 如果失败且错误是因为已有文本层,返回特殊状态码
if not result and self._last_error_is_existing_text():
self.logger.info(f"文件 {input_file} 已有文本层无需OCR处理")
return 2
# 返回常规状态码
return 1 if result else 0
def _last_error_is_existing_text(self):
"""
检查上次错误是否因为PDF已有文本层
通过分析最近一次OCRmyPDF命令的错误输出判断错误是否是因为文件已经有文本层
Returns:
bool: 如果错误是因为文件已有文本层则返回True否则返回False
"""
if hasattr(self, 'last_error') and isinstance(self.last_error, str):
return "page already has text" in self.last_error
return False
def _process_file_internal(self, input_file, output_file, options, force_ocr=False):
"""
内部方法使用OCRmyPDF处理单个文件
构建OCRmyPDF命令行参数并执行命令进行OCR处理
Args:
input_file (str): 输入PDF文件路径
output_file (str): 输出PDF文件路径
options (dict): OCR选项包括languagedeskewrotate_pagescleanoptimize等
force_ocr (bool): 是否强制OCR处理即使文件已有文本层
Returns:
bool: 处理是否成功
"""
# 构建命令行参数
cmd = ["ocrmypdf"]
# 添加语言选项 - 默认使用英文
cmd.extend(["-l", "eng"])
# 添加优化选项(必须在其他选项之前)
if options.get('optimize', False):
cmd.extend(["-O", "1"]) # 使用1级优化
self.logger.info("启用优化输出文件大小")
# 添加语言选项
lang = options.get('language', 'eng')
if lang in self.available_languages:
cmd.extend(["-l", lang])
self.logger.info(f"使用语言: {lang}")
else:
self.logger.warning(f"不支持的语言: {lang},使用默认语言(eng)")
cmd.extend(["-l", "eng"])
# 添加其他选项
if options.get('deskew', False):
cmd.append("--deskew")
self.logger.info("启用自动校正倾斜页面")
if options.get('rotate_pages', False):
cmd.append("--rotate-pages")
self.logger.info("启用自动旋转页面")
if options.get('clean', False):
cmd.append("--clean")
self.logger.info("启用清理图像")
if 'jobs' in options:
cmd.extend(["--jobs", str(options['jobs'])])
self.logger.info(f"使用 {options['jobs']} 个处理线程")
if 'output_type' in options:
cmd.extend(["--output-type", options['output_type']])
self.logger.info(f"输出类型: {options['output_type']}")
# 添加强制OCR选项
if force_ocr:
cmd.append("--force-ocr")
self.logger.info("启用强制OCR处理")
# 添加输入和输出文件
# 添加输入和输出文件(必须在最后)
cmd.extend([str(input_file), str(output_file)])
# 执行命令
@ -79,24 +261,34 @@ class OCREngine:
self.logger.info("OCRmyPDF命令执行成功")
return True
else:
self.last_error = result.stderr
self.logger.error(f"OCRmyPDF命令执行失败: {result.stderr}")
self.logger.error(f"命令输出: {result.stdout}")
return False
except Exception as e:
self.last_error = str(e)
self.logger.error(f"执行OCRmyPDF命令时出错: {e}")
return False
def process_batch(self, file_list, output_dir, options=None, progress_callback=None):
"""
批量处理文件
批量处理多个PDF文件
对多个PDF文件进行OCR处理并可通过回调函数报告处理进度
支持自定义文件命名规则包括添加前缀和后缀
Args:
file_list (list): 输入文件列表
output_dir (str): 输出目录
options (dict): OCR选项
file_list (list): 输入PDF文件路径列表
output_dir (str): 输出目录路径
options (dict): OCR选项除了process_file支持的选项外还支持file_prefix和file_suffix
progress_callback (callable): 进度回调函数接收参数(current, total, file, success)
current - 当前处理的文件索引从1开始
total - 总文件数
file - 当前处理的文件路径
success - 处理是否成功包括已OCR过
Returns:
dict: 处理结果键为输入文件路径值为处理是否成功
dict: 处理结果字典键为输入文件路径值为处理结果状态码0-失败1-成功2-已OCR过
"""
results = {}
total = len(file_list)
@ -105,15 +297,22 @@ class OCREngine:
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# 获取文件命名选项
file_prefix = options.get("file_prefix", "")
file_suffix = options.get("file_suffix", "_ocr")
for i, input_file in enumerate(file_list):
input_path = Path(input_file)
output_file = output_path / f"{input_path.stem}_ocr{input_path.suffix}"
# 使用前缀和后缀构建输出文件名
output_file = output_path / f"{file_prefix}{input_path.stem}{file_suffix}{input_path.suffix}"
self.logger.info(f"处理文件 {i+1}/{total}: {input_file}")
success = self.process_file(input_file, output_file, options)
results[input_file] = success
result_code = self.process_file(input_file, output_file, options)
results[input_file] = result_code
if progress_callback:
# 对于回调我们将状态码2已OCR过也视为"成功",只是一种特殊的成功情况
success = result_code > 0
progress_callback(i + 1, total, input_file, success)
return results

BIN
src/gui/.DS_Store vendored

Binary file not shown.

@ -2,7 +2,7 @@ from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QGroupBox,
QPushButton, QLabel, QFileDialog, QProgressBar,
QComboBox, QCheckBox, QListWidget, QMessageBox,
QRadioButton
QRadioButton, QInputDialog, QLineEdit, QWidget
)
from PySide6.QtCore import Qt, Signal, Slot, QThread
from pathlib import Path
@ -13,12 +13,32 @@ from src.core.config import Config
from src.utils.file_utils import FileUtils
class BatchOCRWorker(QThread):
"""批量OCR处理线程"""
progress_updated = Signal(int, int, str, bool)
"""
批量OCR处理线程
继承自QThread用于在后台线程中执行批量OCR处理任务
避免在处理大量PDF文件时阻塞主UI线程
可以报告总体进度单个文件进度和处理结果
信号:
progress_updated: 发送处理进度信息 (当前索引, 总数, 文件路径, 结果码)
file_progress_updated: 发送单个文件的处理进度 (当前进度, 总进度)
finished: 处理完成后发送结果字典
"""
progress_updated = Signal(int, int, str, int) # 修改为发送状态码而不是布尔值
file_progress_updated = Signal(int, int) # 当前文件的进度
finished = Signal(dict)
def __init__(self, engine, files, output_dir, options):
"""
初始化批量OCR工作线程
Args:
engine (OCREngine): OCR引擎实例
files (list): 要处理的文件路径列表
output_dir (str): 输出目录路径
options (dict): OCR处理选项
"""
super().__init__()
self.engine = engine
self.files = files
@ -26,18 +46,53 @@ class BatchOCRWorker(QThread):
self.options = options
def run(self):
results = self.engine.process_batch(
self.files,
self.output_dir,
self.options,
lambda current, total, file, success: self.progress_updated.emit(current, total, file, success)
)
"""
线程执行方法
遍历文件列表对每个文件进行OCR处理
收集处理结果并通过信号报告进度
完成后发送finished信号包含所有文件的处理结果
"""
results = {}
total = len(self.files)
# 确保输出目录存在
output_path = Path(self.output_dir)
output_path.mkdir(parents=True, exist_ok=True)
for i, input_file in enumerate(self.files):
input_path = Path(input_file)
output_file = output_path / f"{input_path.stem}_ocr{input_path.suffix}"
# 处理文件并获取结果码
result_code = self.engine.process_file(input_file, output_file, self.options)
results[input_file] = result_code
# 发送进度更新
success = result_code > 0 # 成功或已OCR过都视为"成功"
self.progress_updated.emit(i + 1, total, input_file, result_code)
self.finished.emit(results)
class BatchDialog(QDialog):
"""批量处理对话框"""
"""
批量处理对话框
提供批量OCR处理的用户界面包括文件选择OCR选项设置
配置管理和处理控制等功能
相比主窗口提供了更详细的批处理选项和进度显示
"""
def __init__(self, parent=None):
"""
初始化批量处理对话框
设置窗口基本属性创建配置和OCR引擎实例
初始化UI组件
Args:
parent: 父窗口默认为None
"""
super().__init__(parent)
self.setWindowTitle("批量OCR处理")
self.resize(700, 500)
@ -49,7 +104,16 @@ class BatchDialog(QDialog):
self.init_ui()
def init_ui(self):
"""初始化UI"""
"""
初始化用户界面
创建和布局所有UI组件包括
- 文件选择区域
- 输出选项目录和文件命名
- OCR选项语言配置文件处理选项
- 进度显示总进度和文件进度
- 控制按钮
"""
# 主布局
main_layout = QVBoxLayout(self)
@ -99,20 +163,84 @@ class BatchDialog(QDialog):
naming_layout.addWidget(QLabel("输出文件命名:"))
self.naming_combo = QComboBox()
self.naming_combo.addItems(["原文件名_ocr", "原文件名", "自定义前缀_原文件名"])
self.naming_combo.currentIndexChanged.connect(self.on_naming_option_changed)
naming_layout.addWidget(self.naming_combo, 1)
# 添加自定义前缀输入框
self.prefix_layout = QHBoxLayout()
self.prefix_layout.addWidget(QLabel("自定义前缀:"))
self.prefix_edit = QLineEdit("OCR_")
self.prefix_layout.addWidget(self.prefix_edit, 1)
# 初始时隐藏前缀输入框
self.prefix_widget = QWidget()
self.prefix_widget.setLayout(self.prefix_layout)
self.prefix_widget.setVisible(False)
output_layout.addLayout(output_dir_layout)
output_layout.addLayout(naming_layout)
output_layout.addWidget(self.prefix_widget)
# OCR选项
ocr_group = QGroupBox("OCR选项")
ocr_layout = QVBoxLayout(ocr_group)
# 语言选择
language_layout = QHBoxLayout()
language_layout.addWidget(QLabel("OCR语言:"))
self.language_combo = QComboBox()
self.language_combo.setToolTip("选择OCR识别使用的语言")
# 添加可用的语言
# 常用语言列表
common_langs = ['eng', 'chi_sim', 'chi_tra', 'jpn', 'kor']
# 首先添加常用语言
if self.ocr_engine.available_languages:
# 添加常用语言组
common_available = [lang for lang in common_langs if lang in self.ocr_engine.available_languages]
if common_available:
self.language_combo.addItem("--- 常用语言 ---", None)
for lang_code in common_available:
lang_name = self.ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 添加其他语言组
other_available = [lang for lang in self.ocr_engine.available_languages
if lang not in common_langs]
if other_available:
self.language_combo.addItem("--- 其他语言 ---", None)
# 按名称排序
other_langs_sorted = sorted(
other_available,
key=lambda x: self.ocr_engine.get_language_name(x)
)
for lang_code in other_langs_sorted:
lang_name = self.ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
else:
# 如果没有常用语言,直接添加所有语言
for lang_code in self.ocr_engine.available_languages:
lang_name = self.ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 设置默认语言
default_lang = self.config.get('default_options.language', 'eng')
index = self.language_combo.findData(default_lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
language_layout.addWidget(self.language_combo)
ocr_layout.addLayout(language_layout)
# 使用配置文件
config_layout = QHBoxLayout()
config_layout.addWidget(QLabel("使用配置文件:"))
self.config_combo = QComboBox()
self.config_combo.addItems(["默认配置"])
self.config_combo.addItem("默认配置")
# 添加已保存的配置
self.load_saved_configs()
self.config_combo.currentIndexChanged.connect(self.on_config_changed)
self.save_config_btn = QPushButton("保存当前配置")
self.save_config_btn.clicked.connect(self.save_current_config)
config_layout.addWidget(self.config_combo, 1)
@ -183,7 +311,12 @@ class BatchDialog(QDialog):
main_layout.addLayout(buttons_layout)
def add_files(self):
"""添加文件"""
"""
添加文件按钮点击处理
打开文件选择对话框允许用户选择一个或多个PDF文件
选中的文件将添加到文件列表中并显示在界面上
"""
files, _ = QFileDialog.getOpenFileNames(
self,
"选择PDF文件",
@ -195,7 +328,12 @@ class BatchDialog(QDialog):
self.add_files_to_list(files)
def add_folder(self):
"""添加文件夹"""
"""
添加文件夹按钮点击处理
打开文件夹选择对话框允许用户选择一个包含PDF文件的文件夹
文件夹中的所有PDF文件(包括子文件夹中的PDF文件)将被添加到文件列表中
"""
folder = QFileDialog.getExistingDirectory(
self,
"选择包含PDF文件的文件夹"
@ -209,7 +347,14 @@ class BatchDialog(QDialog):
QMessageBox.information(self, "提示", "所选文件夹中未找到PDF文件")
def add_files_to_list(self, files):
"""添加文件到列表"""
"""
将文件添加到文件列表
过滤掉已经在列表中的文件将新文件添加到列表并更新界面显示
Args:
files (list): 要添加的文件路径列表
"""
# 过滤已存在的文件
new_files = [f for f in files if f not in self.selected_files]
if not new_files:
@ -230,17 +375,30 @@ class BatchDialog(QDialog):
self.config.add_recent_file(file)
def clear_files(self):
"""清除文件列表"""
"""
清除文件列表
清空选定的文件列表和界面上的文件列表显示
"""
self.selected_files = []
self.file_list.clear()
self.status_label.setText("文件列表已清空")
def select_all_files(self):
"""全选文件"""
"""
全选文件
选中文件列表中的所有文件
"""
self.file_list.selectAll()
def select_output_dir(self):
"""选择输出目录"""
"""
选择输出目录
打开文件夹选择对话框允许用户选择OCR处理结果的保存目录
选中的目录将显示在输出目录编辑框中并保存到最近使用的目录列表中
"""
dir_path = QFileDialog.getExistingDirectory(
self,
"选择输出目录",
@ -252,12 +410,50 @@ class BatchDialog(QDialog):
self.config.add_recent_output_dir(dir_path)
def save_current_config(self):
"""保存当前配置"""
# 这里可以实现保存当前配置的功能
QMessageBox.information(self, "提示", "配置保存功能尚未实现")
"""
保存当前配置
将当前OCR选项保存为命名配置以便将来重用
弹出对话框让用户输入配置名称然后保存到配置文件中
"""
# 获取当前配置名称
config_name, ok = QInputDialog.getText(
self,
"保存配置",
"请输入配置名称:",
QLineEdit.Normal,
"我的OCR配置"
)
if ok and config_name:
# 收集当前配置
current_config = {
"language": self.language_combo.currentData(),
"deskew": self.deskew_cb.isChecked(),
"rotate_pages": self.rotate_cb.isChecked(),
"clean": self.clean_cb.isChecked(),
"optimize": self.optimize_cb.isChecked()
}
# 保存到配置中
saved_configs = self.config.get('saved_configs', {})
saved_configs[config_name] = current_config
self.config.set('saved_configs', saved_configs)
# 更新下拉框
self.config_combo.addItem(config_name)
self.config_combo.setCurrentText(config_name)
QMessageBox.information(self, "成功", f"配置 \"{config_name}\" 已保存")
def start_batch_ocr(self):
"""开始批量OCR处理"""
"""
开始批量OCR处理
收集用户设置的OCR选项和文件命名选项创建工作线程执行批量OCR处理
处理前会进行必要的参数检查如确保选择了文件和输出目录
开始处理后会禁用UI元素直到处理完成或取消
"""
if not self.selected_files:
QMessageBox.warning(self, "警告", "未选择文件")
return
@ -273,12 +469,46 @@ class BatchDialog(QDialog):
return
# 收集OCR选项
options = {
options = {}
# 获取选中的语言代码
lang_index = self.language_combo.currentIndex()
lang_data = self.language_combo.itemData(lang_index)
if lang_data: # 确保不是分隔符
options["language"] = lang_data
else:
# 如果选中了分隔符,尝试找到下一个有效选项
for i in range(lang_index + 1, self.language_combo.count()):
next_data = self.language_combo.itemData(i)
if next_data:
self.language_combo.setCurrentIndex(i)
options["language"] = next_data
break
# 如果没有找到,使用默认语言
if "language" not in options:
options["language"] = "eng"
options.update({
"deskew": self.deskew_cb.isChecked(),
"rotate_pages": self.rotate_cb.isChecked(),
"clean": self.clean_cb.isChecked(),
"optimize": self.optimize_cb.isChecked()
}
})
# 收集文件命名选项
naming_option = self.naming_combo.currentIndex()
if naming_option == 0: # 原文件名_ocr
file_suffix = "_ocr"
file_prefix = ""
elif naming_option == 1: # 原文件名
file_suffix = ""
file_prefix = ""
else: # 自定义前缀_原文件名
file_suffix = ""
file_prefix = self.prefix_edit.text()
options["file_prefix"] = file_prefix
options["file_suffix"] = file_suffix
# 禁用UI元素
self.start_btn.setEnabled(False)
@ -308,7 +538,12 @@ class BatchDialog(QDialog):
self.worker.start()
def cancel_batch_ocr(self):
"""取消批量OCR处理"""
"""
取消批量OCR处理
终止正在运行的OCR工作线程更新状态显示
并重新启用被禁用的UI元素
"""
if hasattr(self, 'worker') and self.worker.isRunning():
self.worker.terminate()
self.worker.wait()
@ -318,7 +553,12 @@ class BatchDialog(QDialog):
self.enable_ui()
def enable_ui(self):
"""启用UI元素"""
"""
启用UI元素
在OCR处理完成或取消后重新启用之前被禁用的UI元素
使界面恢复到可交互状态
"""
self.start_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
self.add_files_btn.setEnabled(True)
@ -328,36 +568,164 @@ class BatchDialog(QDialog):
self.output_dir_btn.setEnabled(True)
self.output_dir_edit.setEnabled(True)
@Slot(int, int, str, bool)
def update_progress(self, current, total, file, success):
"""更新总进度"""
@Slot(int, int, str, int)
def update_progress(self, current, total, file, result_code):
"""
更新总进度显示
接收来自OCR工作线程的进度信号更新总进度条和状态文本
根据结果码显示不同颜色的状态文本成功(绿色)已OCR过(蓝色)失败(红色)
Args:
current (int): 当前处理的文件索引从1开始
total (int): 总文件数
file (str): 当前处理的文件路径
result_code (int): 处理结果状态码0-失败1-成功2-已OCR过
"""
percent = int(current * 100 / total)
self.total_progress_bar.setValue(percent)
file_name = Path(file).name
status = "成功" if success else "失败"
self.status_label.setText(f"处理 {file_name}: {status} ({current}/{total})")
# 根据状态码设置状态文本和颜色
if result_code == 1:
status = "成功"
status_color = "green"
elif result_code == 2:
status = "已OCR过"
status_color = "blue"
else:
status = "失败"
status_color = "red"
# 使用HTML格式化状态文本
status_text = f"处理 {file_name}: <span style='color: {status_color};'>{status}</span> ({current}/{total})"
self.status_label.setText(status_text)
@Slot(int, int)
def update_file_progress(self, current, total):
"""更新当前文件进度"""
"""
更新当前文件进度
接收来自OCR工作线程的文件进度信号更新文件进度条
Args:
current (int): 当前处理进度
total (int): 总进度
"""
percent = int(current * 100 / total) if total > 0 else 0
self.file_progress_bar.setValue(percent)
@Slot(dict)
def ocr_finished(self, results):
"""OCR处理完成"""
success_count = sum(1 for success in results.values() if success)
"""
OCR处理完成回调
接收来自OCR工作线程的完成信号统计处理结果
更新状态显示重新启用UI元素并显示处理结果对话框
Args:
results (dict): 处理结果字典键为文件路径值为处理结果状态码
"""
success_count = 0
already_ocr_count = 0
failed_count = 0
for result_code in results.values():
if result_code == 1: # 成功
success_count += 1
elif result_code == 2: # 已OCR过
already_ocr_count += 1
else: # 失败
failed_count += 1
total_count = len(results)
self.status_label.setText(f"处理完成: {success_count}/{total_count} 文件成功")
# 构建状态消息
status_msg = f"处理完成: {success_count}/{total_count} 文件成功"
if already_ocr_count > 0:
status_msg += f", {already_ocr_count} 文件已OCR过"
# 启用UI元素
self.enable_ui()
self.status_label.setText(status_msg)
# 启用按钮
self.start_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
self.close_btn.setEnabled(True)
# 构建完成消息
message = f"批量OCR处理已完成\n成功: {success_count} 文件"
if already_ocr_count > 0:
message += f"\n已OCR过: {already_ocr_count} 文件"
message += f"\n失败: {failed_count} 文件"
# 显示完成消息
QMessageBox.information(
self,
"处理完成",
f"批量OCR处理已完成\n成功: {success_count} 文件\n失败: {total_count - success_count} 文件"
)
message
)
def load_saved_configs(self):
"""
加载已保存的配置
从配置文件中读取已保存的OCR配置并添加到配置下拉列表中
"""
saved_configs = self.config.get('saved_configs', {})
for config_name in saved_configs.keys():
self.config_combo.addItem(config_name)
def on_config_changed(self, index):
"""
配置选择变更事件处理
当用户选择不同的配置文件时触发
根据选择的配置更新UI中的OCR选项
Args:
index (int): 当前选中配置的索引
"""
config_name = self.config_combo.currentText()
if config_name == "默认配置":
# 加载默认配置
self.deskew_cb.setChecked(self.config.get('default_options.deskew', True))
self.rotate_cb.setChecked(self.config.get('default_options.rotate_pages', True))
self.clean_cb.setChecked(self.config.get('default_options.clean', False))
self.optimize_cb.setChecked(self.config.get('default_options.optimize', True))
# 设置默认语言
default_lang = self.config.get('default_options.language', 'eng')
index = self.language_combo.findData(default_lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
else:
# 加载已保存的配置
saved_configs = self.config.get('saved_configs', {})
if config_name in saved_configs:
config = saved_configs[config_name]
# 设置选项
self.deskew_cb.setChecked(config.get('deskew', True))
self.rotate_cb.setChecked(config.get('rotate_pages', True))
self.clean_cb.setChecked(config.get('clean', False))
self.optimize_cb.setChecked(config.get('optimize', True))
# 设置语言
lang = config.get('language', 'eng')
index = self.language_combo.findData(lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
def on_naming_option_changed(self, index):
"""
命名选项变更事件处理
当用户选择不同的文件命名选项时触发
如果选择了"自定义前缀_原文件名"选项则显示前缀输入框否则隐藏
Args:
index (int): 当前选中选项的索引
"""
# 如果选择了"自定义前缀_原文件名",显示前缀输入框
self.prefix_widget.setVisible(index == 2) # 第三个选项的索引是2

@ -6,7 +6,7 @@ from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QFileDialog, QProgressBar,
QComboBox, QCheckBox, QGroupBox, QListWidget,
QMessageBox, QStatusBar, QMenu, QMenuBar
QMessageBox, QStatusBar, QMenu, QMenuBar, QLineEdit
)
from PySide6.QtCore import Qt, Signal, Slot, QThread
from PySide6.QtGui import QIcon, QDragEnterEvent, QDropEvent, QAction
@ -18,11 +18,30 @@ from src.gui.settings import SettingsDialog
from src.gui.batch_dialog import BatchDialog
class OCRWorker(QThread):
"""OCR处理线程"""
"""
OCR处理线程
继承自QThread用于在后台线程中执行OCR处理任务
避免在处理大型PDF文件时阻塞主UI线程
使用信号机制向主线程报告处理进度和结果
信号:
progress_updated: 发送处理进度信息 (当前文件索引, 总文件数, 文件路径, 是否成功)
finished: 处理完成后发送结果字典
"""
progress_updated = Signal(int, int, str, bool)
finished = Signal(dict)
def __init__(self, engine, files, output_dir, options):
"""
初始化OCR工作线程
Args:
engine (OCREngine): OCR引擎实例
files (list): 要处理的文件路径列表
output_dir (str): 输出目录路径
options (dict): OCR处理选项
"""
super().__init__()
self.engine = engine
self.files = files
@ -30,6 +49,13 @@ class OCRWorker(QThread):
self.options = options
def run(self):
"""
线程执行方法
调用OCREngine的process_batch方法处理文件
并通过进度回调函数发送进度信号
完成后发送finished信号包含处理结果
"""
results = self.engine.process_batch(
self.files,
self.output_dir,
@ -39,24 +65,47 @@ class OCRWorker(QThread):
self.finished.emit(results)
class MainWindow(QMainWindow):
"""主窗口类"""
"""
应用程序主窗口类
提供主要的用户界面包括文件选择OCR选项设置和处理控制
支持文件拖放批处理和基本设置管理
处理单个或多个PDF文件的OCR并显示处理进度和结果
"""
def __init__(self):
"""
初始化主窗口
设置窗口基本属性创建配置和OCR引擎实例
初始化UI组件并启用文件拖放功能
"""
super().__init__()
self.logger = logging.getLogger(__name__)
self.setWindowTitle("OCRmyPDF GUI")
self.resize(800, 600)
self.setAcceptDrops(True) # 启用拖放
self.setAcceptDrops(True) # 启用拖放功能
# 创建配置和OCR引擎实例
self.config = Config()
self.ocr_engine = OCREngine()
self.selected_files = []
self.selected_files = [] # 存储选中的文件路径
# 初始化UI组件
self.init_ui()
self.logger.info("主窗口初始化完成")
def init_ui(self):
"""初始化UI"""
"""
初始化用户界面
创建和布局所有UI组件包括
- 菜单栏和状态栏
- 文件选择区域
- 输出目录和文件命名选项
- OCR语言和处理选项
- 进度显示和控制按钮
"""
# 创建菜单栏
self.create_menu_bar()
@ -76,6 +125,7 @@ class MainWindow(QMainWindow):
file_group = QGroupBox("文件选择")
file_layout = QVBoxLayout(file_group)
# 添加文件按钮
file_buttons_layout = QHBoxLayout()
self.add_files_btn = QPushButton("添加文件")
self.add_files_btn.clicked.connect(self.add_files)
@ -88,6 +138,7 @@ class MainWindow(QMainWindow):
file_buttons_layout.addWidget(self.clear_files_btn)
file_buttons_layout.addStretch()
# 文件列表
self.file_list = QListWidget()
self.file_list.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
@ -99,17 +150,84 @@ class MainWindow(QMainWindow):
output_layout.addWidget(QLabel("输出目录:"))
self.output_dir_edit = QComboBox()
self.output_dir_edit.setEditable(True)
self.output_dir_edit.addItems(self.config.get('recent_output_dirs', []))
self.output_dir_edit.addItems(self.config.get('recent_output_dirs', [])) # 加载最近使用的目录
self.output_dir_btn = QPushButton("浏览...")
self.output_dir_btn.clicked.connect(self.select_output_dir)
output_layout.addWidget(self.output_dir_edit, 1)
output_layout.addWidget(self.output_dir_btn)
# OCR选项
# 输出文件命名选项
naming_layout = QHBoxLayout()
naming_layout.addWidget(QLabel("输出文件命名:"))
self.naming_combo = QComboBox()
self.naming_combo.addItems(["原文件名_ocr", "原文件名", "自定义前缀_原文件名"])
self.naming_combo.currentIndexChanged.connect(self.on_naming_option_changed)
naming_layout.addWidget(self.naming_combo, 1)
# 自定义前缀输入框
self.prefix_layout = QHBoxLayout()
self.prefix_layout.addWidget(QLabel("自定义前缀:"))
self.prefix_edit = QLineEdit("OCR_")
self.prefix_layout.addWidget(self.prefix_edit, 1)
# 初始时隐藏前缀输入框
self.prefix_widget = QWidget()
self.prefix_widget.setLayout(self.prefix_layout)
self.prefix_widget.setVisible(False) # 默认隐藏,仅当选择"自定义前缀"选项时显示
# OCR选项组
options_group = QGroupBox("OCR选项")
options_layout = QVBoxLayout(options_group)
# 处理选项
# 语言选择
language_layout = QHBoxLayout()
language_layout.addWidget(QLabel("OCR语言:"))
self.language_combo = QComboBox()
self.language_combo.setToolTip("选择OCR识别使用的语言")
# 添加可用的语言,分为常用语言和其他语言两组
common_langs = ['eng', 'chi_sim', 'chi_tra', 'jpn', 'kor'] # 常用语言列表
if self.ocr_engine.available_languages:
# 添加常用语言组
common_available = [lang for lang in common_langs if lang in self.ocr_engine.available_languages]
if common_available:
# 添加组标题项(不可选)
self.language_combo.addItem("--- 常用语言 ---", None)
for lang_code in common_available:
lang_name = self.ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 添加其他语言组
other_available = [lang for lang in self.ocr_engine.available_languages
if lang not in common_langs]
if other_available:
# 添加组标题项(不可选)
self.language_combo.addItem("--- 其他语言 ---", None)
# 按名称排序
other_langs_sorted = sorted(
other_available,
key=lambda x: self.ocr_engine.get_language_name(x)
)
for lang_code in other_langs_sorted:
lang_name = self.ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
else:
# 如果没有常用语言,直接添加所有语言
for lang_code in self.ocr_engine.available_languages:
lang_name = self.ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 设置默认语言
default_lang = self.config.get('default_options.language', 'eng')
index = self.language_combo.findData(default_lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
language_layout.addWidget(self.language_combo)
options_layout.addLayout(language_layout)
# OCR处理选项复选框
self.deskew_cb = QCheckBox("自动校正倾斜页面")
self.deskew_cb.setChecked(self.config.get('default_options.deskew', True))
self.rotate_cb = QCheckBox("自动旋转页面")
@ -124,7 +242,7 @@ class MainWindow(QMainWindow):
options_layout.addWidget(self.clean_cb)
options_layout.addWidget(self.optimize_cb)
# 进度
# 进度显示区域
progress_layout = QVBoxLayout()
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
@ -139,7 +257,7 @@ class MainWindow(QMainWindow):
self.start_btn.clicked.connect(self.start_ocr)
self.cancel_btn = QPushButton("取消")
self.cancel_btn.clicked.connect(self.cancel_ocr)
self.cancel_btn.setEnabled(False)
self.cancel_btn.setEnabled(False) # 初始状态下禁用取消按钮
buttons_layout.addStretch()
buttons_layout.addWidget(self.start_btn)
buttons_layout.addWidget(self.cancel_btn)
@ -147,12 +265,21 @@ class MainWindow(QMainWindow):
# 添加所有元素到主布局
main_layout.addWidget(file_group)
main_layout.addLayout(output_layout)
main_layout.addLayout(naming_layout)
main_layout.addWidget(self.prefix_widget)
main_layout.addWidget(options_group)
main_layout.addLayout(progress_layout)
main_layout.addLayout(buttons_layout)
def create_menu_bar(self):
"""创建菜单栏"""
"""
创建应用程序菜单栏
包含文件编辑和帮助三个主菜单提供常用功能的快捷访问
文件菜单添加文件添加文件夹批量处理退出
编辑菜单清除文件列表设置
帮助菜单关于
"""
menu_bar = QMenuBar()
self.setMenuBar(menu_bar)
@ -201,7 +328,12 @@ class MainWindow(QMainWindow):
help_menu.addAction(about_action)
def add_files(self):
"""添加文件"""
"""
添加文件按钮点击处理
打开文件选择对话框允许用户选择一个或多个PDF文件
选中的文件将添加到文件列表中并显示在界面上
"""
files, _ = QFileDialog.getOpenFileNames(
self,
"选择PDF文件",
@ -213,7 +345,12 @@ class MainWindow(QMainWindow):
self.add_files_to_list(files)
def add_folder(self):
"""添加文件夹"""
"""
添加文件夹按钮点击处理
打开文件夹选择对话框允许用户选择一个包含PDF文件的文件夹
文件夹中的所有PDF文件(包括子文件夹中的PDF文件)将被添加到文件列表中
"""
folder = QFileDialog.getExistingDirectory(
self,
"选择包含PDF文件的文件夹"
@ -227,7 +364,15 @@ class MainWindow(QMainWindow):
QMessageBox.information(self, "提示", "所选文件夹中未找到PDF文件")
def add_files_to_list(self, files):
"""添加文件到列表"""
"""
将文件添加到文件列表
过滤掉已经在列表中的文件将新文件添加到列表并更新界面显示
同时更新状态栏显示添加的文件数量并将文件路径保存到最近使用文件列表中
Args:
files (list): 要添加的文件路径列表
"""
# 过滤已存在的文件
new_files = [f for f in files if f not in self.selected_files]
if not new_files:
@ -249,14 +394,24 @@ class MainWindow(QMainWindow):
self.config.add_recent_file(file)
def clear_files(self):
"""清除文件列表"""
"""
清除文件列表
清空选定的文件列表和界面上的文件列表显示
同时更新状态栏显示
"""
self.selected_files = []
self.file_list.clear()
self.status_label.setText("文件列表已清空")
self.statusBar.showMessage("文件列表已清空")
def select_output_dir(self):
"""选择输出目录"""
"""
选择输出目录
打开文件夹选择对话框允许用户选择OCR处理结果的保存目录
选中的目录将显示在输出目录编辑框中并保存到最近使用的目录列表中
"""
dir_path = QFileDialog.getExistingDirectory(
self,
"选择输出目录",
@ -268,7 +423,13 @@ class MainWindow(QMainWindow):
self.config.add_recent_output_dir(dir_path)
def start_ocr(self):
"""开始OCR处理"""
"""
开始OCR处理
收集用户设置的OCR选项创建工作线程执行OCR处理
处理前会进行必要的参数检查如确保选择了文件和输出目录
对于单个文件直接处理并显示结果对于多个文件启动工作线程并显示进度
"""
if not self.selected_files:
QMessageBox.warning(self, "警告", "未选择文件")
return
@ -284,14 +445,85 @@ class MainWindow(QMainWindow):
return
# 收集OCR选项
options = {
options = {}
# 获取选中的语言代码
lang_index = self.language_combo.currentIndex()
lang_data = self.language_combo.itemData(lang_index)
if lang_data: # 确保不是分隔符
options["language"] = lang_data
else:
# 如果选中了分隔符,尝试找到下一个有效选项
for i in range(lang_index + 1, self.language_combo.count()):
next_data = self.language_combo.itemData(i)
if next_data:
self.language_combo.setCurrentIndex(i)
options["language"] = next_data
break
# 如果没有找到,使用默认语言
if "language" not in options:
options["language"] = "eng"
# 添加处理选项
options.update({
"deskew": self.deskew_cb.isChecked(),
"rotate_pages": self.rotate_cb.isChecked(),
"clean": self.clean_cb.isChecked(),
"optimize": self.optimize_cb.isChecked()
}
# 禁用UI元素
})
# 收集文件命名选项
naming_option = self.naming_combo.currentIndex()
if naming_option == 0: # 原文件名_ocr
file_suffix = "_ocr"
file_prefix = ""
elif naming_option == 1: # 原文件名
file_suffix = ""
file_prefix = ""
else: # 自定义前缀_原文件名
file_suffix = ""
file_prefix = self.prefix_edit.text()
options["file_prefix"] = file_prefix
options["file_suffix"] = file_suffix
# 如果只有一个文件先检查是否已OCR过
if len(self.selected_files) == 1:
input_file = self.selected_files[0]
input_path = Path(input_file)
# 使用前缀和后缀构建输出文件名
output_file = Path(output_dir) / f"{file_prefix}{input_path.stem}{file_suffix}{input_path.suffix}"
# 检查是否已OCR过
result_code = self.ocr_engine.process_file(input_file, output_file, options)
if result_code == 2: # 已OCR过
QMessageBox.information(
self,
"文件已OCR过",
f"文件 {input_path.name} 已有文本层无需再次OCR处理。"
)
return
# 如果成功或失败,也直接显示结果并返回
if result_code == 1:
QMessageBox.information(
self,
"处理完成",
f"文件 {input_path.name} OCR处理成功。"
)
# 添加到最近使用的输出目录
self.config.add_recent_output_dir(output_dir)
return
else:
QMessageBox.critical(
self,
"处理失败",
f"文件 {input_path.name} OCR处理失败请查看日志了解详情。"
)
return
# 多个文件时禁用UI元素
self.start_btn.setEnabled(False)
self.cancel_btn.setEnabled(True)
self.add_files_btn.setEnabled(False)
@ -315,7 +547,12 @@ class MainWindow(QMainWindow):
self.worker.start()
def cancel_ocr(self):
"""取消OCR处理"""
"""
取消OCR处理
终止正在运行的OCR工作线程更新状态显示
并重新启用被禁用的UI元素
"""
if hasattr(self, 'worker') and self.worker.isRunning():
self.worker.terminate()
self.worker.wait()
@ -326,7 +563,12 @@ class MainWindow(QMainWindow):
self.enable_ui()
def enable_ui(self):
"""启用UI元素"""
"""
启用UI元素
在OCR处理完成或取消后重新启用之前被禁用的UI元素
使界面恢复到可交互状态
"""
self.start_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
self.add_files_btn.setEnabled(True)
@ -337,36 +579,88 @@ class MainWindow(QMainWindow):
@Slot(int, int, str, bool)
def update_progress(self, current, total, file, success):
"""更新进度"""
"""
更新进度显示
接收来自OCR工作线程的进度信号更新进度条和状态文本
使用HTML格式化状态文本成功显示为绿色失败显示为红色
Args:
current (int): 当前处理的文件索引从1开始
total (int): 总文件数
file (str): 当前处理的文件路径
success (bool): 处理是否成功
"""
percent = int(current * 100 / total)
self.progress_bar.setValue(percent)
file_name = Path(file).name
status = "成功" if success else "失败"
self.status_label.setText(f"处理 {file_name}: {status} ({current}/{total})")
self.statusBar.showMessage(f"处理 {file_name}: {status} ({current}/{total})")
if success:
status = "<span style='color: green;'>成功</span>"
else:
status = "<span style='color: red;'>失败</span>"
status_text = f"处理 {file_name}: {status} ({current}/{total})"
self.status_label.setText(status_text)
self.statusBar.showMessage(f"处理 {file_name}: {'成功' if success else '失败'} ({current}/{total})")
@Slot(dict)
def ocr_finished(self, results):
"""OCR处理完成"""
success_count = sum(1 for success in results.values() if success)
"""
OCR处理完成回调
接收来自OCR工作线程的完成信号统计处理结果
更新状态显示重新启用UI元素并显示处理结果对话框
Args:
results (dict): 处理结果字典键为文件路径值为处理结果状态码
"""
success_count = 0
already_ocr_count = 0
failed_count = 0
# 统计处理结果
for result_code in results.values():
if result_code == 1: # 成功
success_count += 1
elif result_code == 2: # 已OCR过
already_ocr_count += 1
else: # 失败
failed_count += 1
total_count = len(results)
self.status_label.setText(f"处理完成: {success_count}/{total_count} 文件成功")
self.statusBar.showMessage(f"OCR处理完成: {success_count}/{total_count} 文件成功")
# 构建状态消息
status_msg = f"处理完成: {success_count}/{total_count} 文件成功"
if already_ocr_count > 0:
status_msg += f", {already_ocr_count} 文件已OCR过"
self.status_label.setText(status_msg)
self.statusBar.showMessage(status_msg)
# 启用UI元素
self.enable_ui()
# 构建完成消息
message = f"OCR处理已完成\n成功: {success_count} 文件"
if already_ocr_count > 0:
message += f"\n已OCR过: {already_ocr_count} 文件"
message += f"\n失败: {failed_count} 文件"
# 显示完成消息
QMessageBox.information(
self,
"处理完成",
f"OCR处理已完成\n成功: {success_count} 文件\n失败: {total_count - success_count} 文件"
message
)
def show_settings(self):
"""显示设置对话框"""
"""
显示设置对话框
创建并显示设置对话框如果用户确认设置更改
则更新UI以反映新的默认设置
"""
dialog = SettingsDialog(self)
if dialog.exec():
# 更新UI以反映新设置
@ -376,12 +670,21 @@ class MainWindow(QMainWindow):
self.optimize_cb.setChecked(self.config.get('default_options.optimize', True))
def show_batch_dialog(self):
"""显示批量处理对话框"""
"""
显示批量处理对话框
创建并显示批量处理对话框允许用户一次处理多个文件
并提供更详细的批处理选项
"""
dialog = BatchDialog(self)
dialog.exec()
def show_about(self):
"""显示关于对话框"""
"""
显示关于对话框
显示应用程序的版本信息和基本说明
"""
QMessageBox.about(
self,
"关于 OCRmyPDF GUI",
@ -392,12 +695,28 @@ class MainWindow(QMainWindow):
)
def dragEnterEvent(self, event: QDragEnterEvent):
"""拖拽进入事件"""
"""
拖拽进入事件处理
当用户拖拽文件到窗口上方时触发
如果拖拽内容包含URL文件路径则接受拖拽动作
Args:
event (QDragEnterEvent): 拖拽进入事件对象
"""
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
"""拖拽放下事件"""
"""
拖拽放下事件处理
当用户在窗口中放下拖拽的文件时触发
处理拖拽的文件或文件夹将PDF文件添加到文件列表中
Args:
event (QDropEvent): 拖拽放下事件对象
"""
urls = event.mimeData().urls()
files = []
@ -414,4 +733,17 @@ class MainWindow(QMainWindow):
if files:
self.add_files_to_list(files)
event.acceptProposedAction()
event.acceptProposedAction()
def on_naming_option_changed(self, index):
"""
命名选项变更事件处理
当用户选择不同的文件命名选项时触发
如果选择了"自定义前缀_原文件名"选项则显示前缀输入框否则隐藏
Args:
index (int): 当前选中选项的索引
"""
# 如果选择了"自定义前缀_原文件名",显示前缀输入框
self.prefix_widget.setVisible(index == 2) # 第三个选项的索引是2

@ -1,16 +1,32 @@
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTabWidget,
QPushButton, QLabel, QComboBox, QCheckBox,
QGroupBox, QSpinBox, QRadioButton
QGroupBox, QSpinBox, QRadioButton, QMessageBox,
QWidget
)
from PySide6.QtCore import Qt
from src.core.config import Config
from src.core.ocr_engine import OCREngine
class SettingsDialog(QDialog):
"""设置对话框"""
"""
设置对话框
提供应用程序各项设置的配置界面包括常规设置OCR设置和界面设置
使用选项卡组织不同类别的设置提供直观的设置界面
设置更改后保存到配置文件中供应用程序其他部分使用
"""
def __init__(self, parent=None):
"""
初始化设置对话框
创建配置实例设置窗口基本属性初始化UI组件
Args:
parent: 父窗口默认为None
"""
super().__init__(parent)
self.setWindowTitle("设置")
self.resize(500, 400)
@ -19,7 +35,12 @@ class SettingsDialog(QDialog):
self.init_ui()
def init_ui(self):
"""初始化UI"""
"""
初始化用户界面
创建选项卡式布局包含常规OCR和界面三个选项卡
以及确定和取消按钮
"""
# 主布局
main_layout = QVBoxLayout(self)
@ -57,7 +78,14 @@ class SettingsDialog(QDialog):
main_layout.addLayout(button_layout)
def setup_general_tab(self, tab):
"""设置常规选项卡"""
"""
设置常规选项卡
创建并布局常规设置选项包括启动选项和文件历史设置
Args:
tab: 要设置的选项卡控件
"""
layout = QVBoxLayout(tab)
# 启动选项
@ -100,9 +128,72 @@ class SettingsDialog(QDialog):
layout.addStretch()
def setup_ocr_tab(self, tab):
"""设置OCR选项卡"""
"""
设置OCR选项卡
创建并布局OCR设置选项包括默认语言设置处理选项和输出类型设置
Args:
tab: 要设置的选项卡控件
"""
layout = QVBoxLayout(tab)
# 默认语言
language_group = QGroupBox("默认OCR语言")
language_layout = QVBoxLayout(language_group)
self.language_combo = QComboBox()
self.language_combo.setToolTip("选择默认的OCR识别语言")
# 添加可用的语言
ocr_engine = OCREngine()
# 常用语言列表
common_langs = ['eng', 'chi_sim', 'chi_tra', 'jpn', 'kor']
# 首先添加常用语言
if ocr_engine.available_languages:
# 添加常用语言组
common_available = [lang for lang in common_langs if lang in ocr_engine.available_languages]
if common_available:
self.language_combo.addItem("--- 常用语言 ---", None)
for lang_code in common_available:
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 添加其他语言组
other_available = [lang for lang in ocr_engine.available_languages
if lang not in common_langs]
if other_available:
self.language_combo.addItem("--- 其他语言 ---", None)
# 按名称排序
other_langs_sorted = sorted(
other_available,
key=lambda x: ocr_engine.get_language_name(x)
)
for lang_code in other_langs_sorted:
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
else:
# 如果没有常用语言,直接添加所有语言
for lang_code in ocr_engine.available_languages:
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 设置当前默认语言
default_lang = self.config.get('default_options.language', 'eng')
index = self.language_combo.findData(default_lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
# 添加刷新语言列表按钮
lang_buttons_layout = QHBoxLayout()
self.refresh_langs_btn = QPushButton("刷新语言列表")
self.refresh_langs_btn.clicked.connect(self.refresh_languages)
lang_buttons_layout.addWidget(self.refresh_langs_btn)
language_layout.addWidget(self.language_combo)
language_layout.addLayout(lang_buttons_layout)
# 默认选项
options_group = QGroupBox("默认处理选项")
options_layout = QVBoxLayout(options_group)
@ -134,56 +225,81 @@ class SettingsDialog(QDialog):
output_layout.addWidget(self.output_type_combo)
layout.addWidget(language_group)
layout.addWidget(options_group)
layout.addWidget(output_group)
layout.addStretch()
def setup_ui_tab(self, tab):
"""设置界面选项卡"""
layout = QVBoxLayout(tab)
"""
设置界面选项卡
# 语言
language_group = QGroupBox("界面语言")
language_layout = QVBoxLayout(language_group)
self.ui_language_combo = QComboBox()
self.ui_language_combo.addItems(["简体中文", "English"])
current_lang = "简体中文" if self.config.get('ui.language') == 'zh_CN' else "English"
self.ui_language_combo.setCurrentText(current_lang)
创建并布局界面设置选项包括主题和语言设置
language_layout.addWidget(self.ui_language_combo)
Args:
tab: 要设置的选项卡控件
"""
layout = QVBoxLayout(tab)
# 主题
# 主题设置
theme_group = QGroupBox("主题")
theme_layout = QVBoxLayout(theme_group)
self.light_theme_rb = QRadioButton("浅色")
self.dark_theme_rb = QRadioButton("深色")
self.system_theme_rb = QRadioButton("跟随系统")
self.theme_system_rb = QRadioButton("跟随系统")
self.theme_light_rb = QRadioButton("浅色主题")
self.theme_dark_rb = QRadioButton("深色主题")
current_theme = self.config.get('ui.theme', 'system')
if current_theme == 'light':
self.light_theme_rb.setChecked(True)
self.theme_light_rb.setChecked(True)
elif current_theme == 'dark':
self.dark_theme_rb.setChecked(True)
self.theme_dark_rb.setChecked(True)
else:
self.system_theme_rb.setChecked(True)
self.theme_system_rb.setChecked(True)
theme_layout.addWidget(self.light_theme_rb)
theme_layout.addWidget(self.dark_theme_rb)
theme_layout.addWidget(self.system_theme_rb)
theme_layout.addWidget(self.theme_system_rb)
theme_layout.addWidget(self.theme_light_rb)
theme_layout.addWidget(self.theme_dark_rb)
# 语言设置
language_group = QGroupBox("界面语言")
language_layout = QVBoxLayout(language_group)
self.ui_language_combo = QComboBox()
self.ui_language_combo.addItem("简体中文", "zh_CN")
self.ui_language_combo.addItem("English", "en_US")
current_lang = self.config.get('ui.language', 'zh_CN')
index = self.ui_language_combo.findData(current_lang)
if index >= 0:
self.ui_language_combo.setCurrentIndex(index)
language_note = QLabel("注:更改语言设置需要重启应用程序才能生效")
language_note.setStyleSheet("color: gray;")
language_layout.addWidget(self.ui_language_combo)
language_layout.addWidget(language_note)
layout.addWidget(language_group)
layout.addWidget(theme_group)
layout.addWidget(language_group)
layout.addStretch()
def clear_history(self):
"""清除历史记录"""
"""
清除历史记录
清空最近使用的文件和输出目录列表并弹出确认提示
"""
self.config.set('recent_files', [])
self.config.set('recent_output_dirs', [])
QMessageBox.information(self, "已清除", "已清除所有历史记录")
def accept(self):
"""确定按钮点击事件"""
"""
确定按钮点击处理
保存所有设置到配置文件中并关闭对话框
"""
# 保存常规设置
self.config.set('general.check_update_on_startup', self.check_update_cb.isChecked())
self.config.set('general.show_welcome', self.show_welcome_cb.isChecked())
@ -191,6 +307,21 @@ class SettingsDialog(QDialog):
self.config.set('general.max_recent_files', self.recent_files_spin.value())
# 保存OCR设置
lang_index = self.language_combo.currentIndex()
lang_data = self.language_combo.itemData(lang_index)
# 如果选择了分隔符,尝试找到下一个有效选项
if lang_data is None:
for i in range(lang_index + 1, self.language_combo.count()):
next_data = self.language_combo.itemData(i)
if next_data:
lang_data = next_data
break
# 如果没有找到,使用默认语言
if lang_data is None:
lang_data = 'eng'
self.config.set('default_options.language', lang_data)
self.config.set('default_options.deskew', self.deskew_cb.isChecked())
self.config.set('default_options.rotate_pages', self.rotate_cb.isChecked())
self.config.set('default_options.clean', self.clean_cb.isChecked())
@ -198,14 +329,80 @@ class SettingsDialog(QDialog):
self.config.set('default_options.output_type', self.output_type_combo.currentText())
# 保存界面设置
ui_lang = 'zh_CN' if self.ui_language_combo.currentText() == '简体中文' else 'en_US'
self.config.set('ui.language', ui_lang)
if self.light_theme_rb.isChecked():
self.config.set('ui.theme', 'light')
elif self.dark_theme_rb.isChecked():
self.config.set('ui.theme', 'dark')
if self.theme_light_rb.isChecked():
theme = 'light'
elif self.theme_dark_rb.isChecked():
theme = 'dark'
else:
self.config.set('ui.theme', 'system')
theme = 'system'
self.config.set('ui.theme', theme)
self.config.set('ui.language', self.ui_language_combo.currentData())
super().accept()
def refresh_languages(self):
"""
刷新语言列表
重新获取系统中已安装的Tesseract语言包并更新语言下拉列表
保存当前选择的语言并在刷新后尝试恢复选择
"""
# 保存当前选择的语言
current_lang = self.language_combo.currentData()
# 清空语言下拉列表
self.language_combo.clear()
# 重新获取语言列表
ocr_engine = OCREngine()
# 这会重新检测可用的语言
ocr_engine = OCREngine()
# 重新填充语言下拉列表
common_langs = ['eng', 'chi_sim', 'chi_tra', 'jpn', 'kor']
if ocr_engine.available_languages:
# 添加常用语言组
common_available = [lang for lang in common_langs if lang in ocr_engine.available_languages]
if common_available:
self.language_combo.addItem("--- 常用语言 ---", None)
for lang_code in common_available:
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 添加其他语言组
other_available = [lang for lang in ocr_engine.available_languages
if lang not in common_langs]
if other_available:
self.language_combo.addItem("--- 其他语言 ---", None)
# 按名称排序
other_langs_sorted = sorted(
other_available,
key=lambda x: ocr_engine.get_language_name(x)
)
for lang_code in other_langs_sorted:
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
else:
# 如果没有常用语言,直接添加所有语言
for lang_code in ocr_engine.available_languages:
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 恢复之前选择的语言
if current_lang:
index = self.language_combo.findData(current_lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
# 显示刷新结果
QMessageBox.information(
self,
"刷新完成",
f"已刷新语言列表,找到 {len(ocr_engine.available_languages)} 种语言"
)
def download_language_pack(self):
"""下载Tesseract语言包 - 已移除"""
pass

@ -4,18 +4,34 @@ from pathlib import Path
import logging
class FileUtils:
"""文件工具类,提供文件操作相关的功能"""
"""
文件工具类
提供文件和目录操作的通用工具方法包括目录创建文件验证
文件搜索文件大小计算和文件复制等功能
所有方法均为静态方法可直接通过类名调用无需实例化
主要功能:
- 目录创建和验证
- PDF文件验证
- 目录内PDF文件搜索
- 文件大小格式化
- 文件复制
"""
@staticmethod
def ensure_dir(dir_path):
"""
确保目录存在如果不存在则创建
使用pathlib创建目录支持创建多级目录结构
如果目录已存在则不会引发错误
Args:
dir_path: 目录路径
dir_path (str or Path): 要创建的目录路径
Returns:
bool: 操作是否成功
bool: 如果目录创建成功或已存在则返回True创建失败则返回False
"""
try:
Path(dir_path).mkdir(parents=True, exist_ok=True)
@ -29,11 +45,16 @@ class FileUtils:
"""
检查文件是否是有效的PDF文件
检查包含三个步骤:
1. 检查文件是否存在
2. 检查文件扩展名是否为.pdf (不区分大小写)
3. 检查文件头部是否包含PDF标识 (%PDF-)
Args:
file_path: 文件路径
file_path (str or Path): 要检查的文件路径
Returns:
bool: 是否是有效的PDF文件
bool: 如果文件是有效的PDF文件则返回True否则返回False
"""
if not Path(file_path).exists():
return False
@ -55,12 +76,15 @@ class FileUtils:
"""
获取目录中的所有PDF文件
搜索指定目录中的所有PDF文件可选择是否递归搜索子目录
使用is_valid_pdf方法验证每个找到的PDF文件
Args:
dir_path: 目录路径
recursive: 是否递归搜索子目录
dir_path (str or Path): 要搜索的目录路径
recursive (bool): 是否递归搜索子目录默认为False
Returns:
list: PDF文件路径列表
list: 包含所有找到的PDF文件绝对路径的列表如果目录不存在或不是目录则返回空列表
"""
pdf_files = []
dir_path = Path(dir_path)
@ -69,12 +93,14 @@ class FileUtils:
return pdf_files
if recursive:
# 递归搜索目录及其所有子目录
for root, _, files in os.walk(dir_path):
for file in files:
file_path = Path(root) / file
if FileUtils.is_valid_pdf(file_path):
pdf_files.append(str(file_path))
else:
# 只搜索当前目录,不包括子目录
for file in dir_path.iterdir():
if file.is_file() and FileUtils.is_valid_pdf(file):
pdf_files.append(str(file))
@ -84,13 +110,16 @@ class FileUtils:
@staticmethod
def get_file_size_str(file_path):
"""
获取文件大小的字符串表示
获取文件大小的人类可读字符串表示
将文件大小从字节转换为更易读的单位B, KB, MB, GB, TB, PB
使用1024作为转换基数保留一位小数
Args:
file_path: 文件路径
file_path (str or Path): 文件路径
Returns:
str: 文件大小字符串 "1.2 MB"
str: 格式化的文件大小字符串 "1.2 MB"如果文件不存在或发生错误则返回"未知大小"
"""
try:
size = Path(file_path).stat().st_size
@ -107,14 +136,17 @@ class FileUtils:
@staticmethod
def copy_file(src, dst):
"""
复制文件
复制文件保留元数据
使用shutil.copy2复制文件该方法会尝试保留文件的元数据
如创建时间修改时间访问时间权限等
Args:
src: 源文件路径
dst: 目标文件路径
src (str or Path): 源文件路径
dst (str or Path): 目标文件路径
Returns:
bool: 操作是否成功
bool: 复制成功则返回True失败则返回False
"""
try:
shutil.copy2(src, dst)

Loading…
Cancel
Save