denovochen 2 months ago
parent 75d3d2a806
commit 741c04a1be

@ -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:
@ -30,7 +47,14 @@ class OCREngine:
self.available_languages = []
def get_available_languages(self):
"""获取系统中已安装的Tesseract语言包列表"""
"""
获取系统中已安装的Tesseract语言包列表
通过调用tesseract命令的--list-langs选项获取系统中已安装的所有语言包
Returns:
list: 已安装的语言代码列表如果获取失败则返回空列表
"""
try:
result = subprocess.run(
["tesseract", "--list-langs"],
@ -48,7 +72,18 @@ class OCREngine:
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)',
@ -91,17 +126,20 @@ class OCREngine:
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:
int: 处理结果状态码
0 - 失败
1 - 成功
0 - 处理失败
1 - 处理成功
2 - 文件已有文本层已OCR过
"""
if options is None:
@ -137,7 +175,14 @@ class OCREngine:
return 1 if result else 0
def _last_error_is_existing_text(self):
"""检查上次错误是否因为PDF已有文本层"""
"""
检查上次错误是否因为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
@ -146,11 +191,13 @@ class OCREngine:
"""
内部方法使用OCRmyPDF处理单个文件
构建OCRmyPDF命令行参数并执行命令进行OCR处理
Args:
input_file (str): 输入PDF文件路径
output_file (str): 输出PDF文件路径
options (dict): OCR选项
force_ocr (bool): 是否强制OCR
options (dict): OCR选项包括languagedeskewrotate_pagescleanoptimize等
force_ocr (bool): 是否强制OCR处理即使文件已有文本层
Returns:
bool: 处理是否成功
@ -225,16 +272,23 @@ class OCREngine:
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: 处理结果键为输入文件路径值为处理结果状态码0-失败1-成功2-已OCR过
dict: 处理结果字典键为输入文件路径值为处理结果状态码0-失败1-成功2-已OCR过
"""
results = {}
total = len(file_list)
@ -243,9 +297,14 @@ 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}")
result_code = self.process_file(input_file, output_file, options)

@ -2,7 +2,7 @@ from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QGroupBox,
QPushButton, QLabel, QFileDialog, QProgressBar,
QComboBox, QCheckBox, QListWidget, QMessageBox,
QRadioButton, QInputDialog, QLineEdit
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处理线程"""
"""
批量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,6 +46,13 @@ class BatchOCRWorker(QThread):
self.options = options
def run(self):
"""
线程执行方法
遍历文件列表对每个文件进行OCR处理
收集处理结果并通过信号报告进度
完成后发送finished信号包含所有文件的处理结果
"""
results = {}
total = len(self.files)
@ -48,9 +75,24 @@ class BatchOCRWorker(QThread):
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)
@ -62,7 +104,16 @@ class BatchDialog(QDialog):
self.init_ui()
def init_ui(self):
"""初始化UI"""
"""
初始化用户界面
创建和布局所有UI组件包括
- 文件选择区域
- 输出选项目录和文件命名
- OCR选项语言配置文件处理选项
- 进度显示总进度和文件进度
- 控制按钮
"""
# 主布局
main_layout = QVBoxLayout(self)
@ -112,10 +163,23 @@ 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选项")
@ -247,7 +311,12 @@ class BatchDialog(QDialog):
main_layout.addLayout(buttons_layout)
def add_files(self):
"""添加文件"""
"""
添加文件按钮点击处理
打开文件选择对话框允许用户选择一个或多个PDF文件
选中的文件将添加到文件列表中并显示在界面上
"""
files, _ = QFileDialog.getOpenFileNames(
self,
"选择PDF文件",
@ -259,7 +328,12 @@ class BatchDialog(QDialog):
self.add_files_to_list(files)
def add_folder(self):
"""添加文件夹"""
"""
添加文件夹按钮点击处理
打开文件夹选择对话框允许用户选择一个包含PDF文件的文件夹
文件夹中的所有PDF文件(包括子文件夹中的PDF文件)将被添加到文件列表中
"""
folder = QFileDialog.getExistingDirectory(
self,
"选择包含PDF文件的文件夹"
@ -273,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:
@ -294,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,
"选择输出目录",
@ -316,7 +410,12 @@ class BatchDialog(QDialog):
self.config.add_recent_output_dir(dir_path)
def save_current_config(self):
"""保存当前配置"""
"""
保存当前配置
将当前OCR选项保存为命名配置以便将来重用
弹出对话框让用户输入配置名称然后保存到配置文件中
"""
# 获取当前配置名称
config_name, ok = QInputDialog.getText(
self,
@ -348,7 +447,13 @@ class BatchDialog(QDialog):
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
@ -390,6 +495,21 @@ class BatchDialog(QDialog):
"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)
self.cancel_btn.setEnabled(True)
@ -418,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()
@ -428,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)
@ -440,7 +570,18 @@ class BatchDialog(QDialog):
@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)
@ -463,13 +604,29 @@ class BatchDialog(QDialog):
@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处理完成"""
"""
OCR处理完成回调
接收来自OCR工作线程的完成信号统计处理结果
更新状态显示重新启用UI元素并显示处理结果对话框
Args:
results (dict): 处理结果字典键为文件路径值为处理结果状态码
"""
success_count = 0
already_ocr_count = 0
failed_count = 0
@ -510,13 +667,25 @@ class BatchDialog(QDialog):
)
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 == "默认配置":
# 加载默认配置
@ -546,4 +715,17 @@ class BatchDialog(QDialog):
lang = config.get('language', 'eng')
index = self.language_combo.findData(lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
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,13 +150,32 @@ 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)
@ -115,15 +185,14 @@ class MainWindow(QMainWindow):
self.language_combo = QComboBox()
self.language_combo.setToolTip("选择OCR识别使用的语言")
# 添加可用的语言
# 常用语言列表
common_langs = ['eng', 'chi_sim', 'chi_tra', 'jpn', 'kor']
# 添加可用的语言,分为常用语言和其他语言两组
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)
@ -133,6 +202,7 @@ class MainWindow(QMainWindow):
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(
@ -157,7 +227,7 @@ class MainWindow(QMainWindow):
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("自动旋转页面")
@ -172,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)
@ -187,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)
@ -195,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)
@ -249,7 +328,12 @@ class MainWindow(QMainWindow):
help_menu.addAction(about_action)
def add_files(self):
"""添加文件"""
"""
添加文件按钮点击处理
打开文件选择对话框允许用户选择一个或多个PDF文件
选中的文件将添加到文件列表中并显示在界面上
"""
files, _ = QFileDialog.getOpenFileNames(
self,
"选择PDF文件",
@ -261,7 +345,12 @@ class MainWindow(QMainWindow):
self.add_files_to_list(files)
def add_folder(self):
"""添加文件夹"""
"""
添加文件夹按钮点击处理
打开文件夹选择对话框允许用户选择一个包含PDF文件的文件夹
文件夹中的所有PDF文件(包括子文件夹中的PDF文件)将被添加到文件列表中
"""
folder = QFileDialog.getExistingDirectory(
self,
"选择包含PDF文件的文件夹"
@ -275,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:
@ -297,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,
"选择输出目录",
@ -316,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
@ -351,6 +464,7 @@ class MainWindow(QMainWindow):
if "language" not in options:
options["language"] = "eng"
# 添加处理选项
options.update({
"deskew": self.deskew_cb.isChecked(),
"rotate_pages": self.rotate_cb.isChecked(),
@ -358,11 +472,27 @@ class MainWindow(QMainWindow):
"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
# 如果只有一个文件先检查是否已OCR过
if len(self.selected_files) == 1:
input_file = self.selected_files[0]
input_path = Path(input_file)
output_file = Path(output_dir) / f"{input_path.stem}_ocr{input_path.suffix}"
# 使用前缀和后缀构建输出文件名
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)
@ -417,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()
@ -428,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)
@ -439,7 +579,18 @@ 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)
@ -455,11 +606,20 @@ class MainWindow(QMainWindow):
@Slot(dict)
def ocr_finished(self, results):
"""OCR处理完成"""
"""
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
@ -495,7 +655,12 @@ class MainWindow(QMainWindow):
)
def show_settings(self):
"""显示设置对话框"""
"""
显示设置对话框
创建并显示设置对话框如果用户确认设置更改
则更新UI以反映新的默认设置
"""
dialog = SettingsDialog(self)
if dialog.exec():
# 更新UI以反映新设置
@ -505,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",
@ -521,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 = []
@ -543,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

@ -10,9 +10,23 @@ 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)
@ -21,7 +35,12 @@ class SettingsDialog(QDialog):
self.init_ui()
def init_ui(self):
"""初始化UI"""
"""
初始化用户界面
创建选项卡式布局包含常规OCR和界面三个选项卡
以及确定和取消按钮
"""
# 主布局
main_layout = QVBoxLayout(self)
@ -59,7 +78,14 @@ class SettingsDialog(QDialog):
main_layout.addLayout(button_layout)
def setup_general_tab(self, tab):
"""设置常规选项卡"""
"""
设置常规选项卡
创建并布局常规设置选项包括启动选项和文件历史设置
Args:
tab: 要设置的选项卡控件
"""
layout = QVBoxLayout(tab)
# 启动选项
@ -102,7 +128,14 @@ class SettingsDialog(QDialog):
layout.addStretch()
def setup_ocr_tab(self, tab):
"""设置OCR选项卡"""
"""
设置OCR选项卡
创建并布局OCR设置选项包括默认语言设置处理选项和输出类型设置
Args:
tab: 要设置的选项卡控件
"""
layout = QVBoxLayout(tab)
# 默认语言
@ -192,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())
@ -249,23 +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: # 确保不是分隔符
self.config.set('default_options.language', lang_data)
else:
# 如果选中了分隔符,尝试找到下一个有效选项
# 如果选择了分隔符,尝试找到下一个有效选项
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:
self.language_combo.setCurrentIndex(i)
self.config.set('default_options.language', next_data)
lang_data = next_data
break
# 如果没有找到,使用默认语言
if not self.language_combo.currentData():
self.config.set('default_options.language', 'eng')
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())
@ -273,34 +329,39 @@ 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):
"""刷新可用语言列表"""
ocr_engine = OCREngine()
# 重新获取可用语言
ocr_engine.available_languages = ocr_engine.get_available_languages()
"""
刷新语言列表
重新获取系统中已安装的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]
@ -329,13 +390,19 @@ class SettingsDialog(QDialog):
lang_name = ocr_engine.get_language_name(lang_code)
self.language_combo.addItem(lang_name, lang_code)
# 尝试恢复之前选择的语言
index = self.language_combo.findData(current_lang)
if index >= 0:
self.language_combo.setCurrentIndex(index)
QMessageBox.information(self, "刷新完成", f"已刷新语言列表,共找到 {len(ocr_engine.available_languages)} 种语言。")
# 恢复之前选择的语言
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