图片插入word(支持wps)

查看 89|回复 11
作者:deepxia   
帮一个监理弄的,他们插入图片挺多的.看见我在Python弄压力容器排版图生成插件,叫我帮着弄个这个,就按照他的需求弄了一个这个,目前使用没啥问题.
成品下载地址:https://wwej.lanzouu.com/iuP4m33ia6de密码:52pj
[Asm] 纯文本查看 复制代码import sys
import os
import win32com.client as win32
import pythoncom
import threading
import time
import shutil
import tempfile
import logging
import traceback
import re  # 新增:导入正则表达式模块
from PIL import Image
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QLabel, QLineEdit, QPushButton, QListWidget, QProgressBar,
    QGroupBox, QGridLayout, QHBoxLayout, QCheckBox, QFileDialog, QMessageBox,
    QColorDialog, QFontDialog, QFrame
)
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QPixmap, QDragEnterEvent, QDropEvent, QFont, QColor, QPalette
# 确保 win32com 能找到类型库
if getattr(sys, 'frozen', False):
    # 打包后模式 - 使用更安全的访问方式
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(sys.executable)))
    gen_py_path = os.path.join(base_path, "gen_py")
    if not os.path.exists(gen_py_path):
        os.makedirs(gen_py_path)
    os.environ["GENPY_PATH"] = gen_py_path
# 添加全局线程锁
com_lock = threading.Lock()
class WorkerThread(QThread):
    progress_updated = Signal(int, str)
    task_completed = Signal()
    error_occurred = Signal(str)
    compress_completed = Signal(list)
    def __init__(self, target, args=None):
        super().__init__()
        self.target = target
        self.args = args if args else []
    def run(self):
        try:
            self.target(*self.args)
            self.task_completed.emit()
        except (OSError, IOError, pythoncom.com_error) as e:
            self.error_occurred.emit(str(e))
            logging.error(f"Worker thread error: {str(e)}\n{traceback.format_exc()}")
        except Exception as e:
            self.error_occurred.emit(f"Unexpected error: {str(e)}")
            logging.error(f"Unexpected worker thread error: {str(e)}\n{traceback.format_exc()}")
class WordImageInserter(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Word表格图片插入工具")
        self.resize(600, 300)  # 增加高度以适应新控件
        # 设置中文字体支持
        self.font_family = "Microsoft YaHei"
        self.small_font = QFont(self.font_family, 9)
        self.normal_font = QFont(self.font_family, 10)
        self.title_font = QFont(self.font_family, 10, QFont.Weight.Bold)
        # 创建主部件
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        # 主布局
        main_layout = QHBoxLayout(main_widget)
        main_layout.setContentsMargins(15, 15, 15, 15)
        main_layout.setSpacing(15)
        # 左侧:图片插入设置
        left_frame = QGroupBox("图片插入设置")
        left_frame.setFont(self.title_font)
        left_frame.setStyleSheet("""
            QGroupBox {
                background-color: #ffffff;
                border: 1px solid #cccccc;
                border-radius: 5px;
                margin-top: 1.5ex;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top center;
                padding: 0 5px;
                color: #2c3e50;
            }
        """)
        left_layout = QGridLayout(left_frame)
        left_layout.setSpacing(10)
        # 图片宽度(插入时)
        width_label = QLabel("宽度(厘米):")
        width_label.setFont(self.small_font)
        self.insert_width = QLineEdit("8.0")
        self.insert_width.setFont(self.small_font)
        self.insert_width.setFixedWidth(100)
        left_layout.addWidget(width_label, 0, 0)
        left_layout.addWidget(self.insert_width, 0, 1)
        # 图片高度(插入时)
        height_label = QLabel("高度(厘米):")
        height_label.setFont(self.small_font)
        self.insert_height = QLineEdit("6.0")
        self.insert_height.setFont(self.small_font)
        self.insert_height.setFixedWidth(100)
        left_layout.addWidget(height_label, 1, 0)
        left_layout.addWidget(self.insert_height, 1, 1)
        # 行间距
        spacing_label = QLabel("行间距:")
        spacing_label.setFont(self.small_font)
        self.line_spacing = QLineEdit("8")
        self.line_spacing.setFont(self.small_font)
        self.line_spacing.setFixedWidth(100)
        left_layout.addWidget(spacing_label, 3, 0)
        left_layout.addWidget(self.line_spacing, 3, 1)
        # 每行显示图片数(固定为2列)
        per_row_label = QLabel("每行数量:")
        per_row_label.setFont(self.small_font)
        self.images_per_row = QLineEdit("2")
        self.images_per_row.setFont(self.small_font)
        self.images_per_row.setFixedWidth(100)
        self.images_per_row.setReadOnly(True)
        left_layout.addWidget(per_row_label, 4, 0)
        left_layout.addWidget(self.images_per_row, 4, 1)
        # 字体设置区域
        font_frame = QFrame()
        font_frame.setFrameShape(QFrame.Shape.NoFrame)
        font_layout = QHBoxLayout(font_frame)
        font_layout.setContentsMargins(0, 0, 0, 0)
        font_layout.setSpacing(5)
        # 字体选择按钮
        self.font_btn = QPushButton("选择字体")
        self.font_btn.setFont(self.small_font)
        self.font_btn.setStyleSheet("padding: 5px;")
        self.font_btn.clicked.connect(self.select_font)
        font_layout.addWidget(self.font_btn)
        # 字体颜色选择按钮
        self.color_btn = QPushButton("选择颜色")
        self.color_btn.setFont(self.small_font)
        self.color_btn.setStyleSheet("padding: 5px;")
        self.color_btn.clicked.connect(self.select_color)
        font_layout.addWidget(self.color_btn)
        # 当前字体预览
        self.font_preview = QLabel("字体预览")
        self.font_preview.setStyleSheet("border: 1px solid #cccccc; padding: 3px;")
        font_layout.addWidget(self.font_preview)
        left_layout.addWidget(font_frame, 5, 0, 1, 2)
        # 功能按钮区
        btn_frame = QWidget()
        btn_layout = QHBoxLayout(btn_frame)
        btn_layout.setContentsMargins(0, 0, 0, 0)
        btn_layout.setSpacing(5)
        # 图片选择按钮
        self.select_btn = QPushButton("选择图片")
        self.select_btn.setFont(self.small_font)
        self.select_btn.setStyleSheet("background-color: #28a745; color: white; padding: 5px;")
        self.select_btn.clicked.connect(self.select_images)
        btn_layout.addWidget(self.select_btn)
        # 插入按钮
        self.insert_btn = QPushButton("插入到Word")
        self.insert_btn.setFont(self.small_font)
        self.insert_btn.setStyleSheet("background-color: #007bff; color: white; padding: 5px;")
        self.insert_btn.clicked.connect(self.start_insert_thread)
        self.insert_btn.setEnabled(False)
        btn_layout.addWidget(self.insert_btn)
        left_layout.addWidget(btn_frame, 6, 0, 1, 2)
        # 图片压缩功能区
        compress_frame = QWidget()
        compress_layout = QHBoxLayout(compress_frame)
        compress_layout.setContentsMargins(0, 0, 0, 0)
        compress_layout.setSpacing(5)
        compress_label = QLabel("压缩至(K):")
        compress_label.setFont(self.small_font)
        self.compression_quality = QLineEdit("200")
        self.compression_quality.setFont(self.small_font)
        compress_layout.addWidget(compress_label)
        compress_layout.addWidget(self.compression_quality)
        self.compress_btn = QPushButton("图片压缩")
        self.compress_btn.setFont(self.small_font)
        self.compress_btn.setStyleSheet("background-color: #17a2b8; color: white; padding: 5px;")
        self.compress_btn.clicked.connect(self.start_compress_thread)
        self.compress_btn.setEnabled(False)
        compress_layout.addWidget(self.compress_btn)
        left_layout.addWidget(compress_frame, 7, 0, 1, 2)
        # 批量改名功能区
        rename_frame = QWidget()
        rename_layout = QHBoxLayout(rename_frame)
        rename_layout.setContentsMargins(0, 0, 0, 0)
        rename_layout.setSpacing(5)
        rename_label = QLabel("头文件名:")
        rename_label.setFont(self.small_font)
        self.rename_prefix = QLineEdit("图片")
        self.rename_prefix.setFont(self.small_font)
        rename_layout.addWidget(rename_label)
        rename_layout.addWidget(self.rename_prefix)
        self.rename_btn = QPushButton("批量改名")
        self.rename_btn.setFont(self.small_font)
        self.rename_btn.setStyleSheet("background-color: #6610f2; color: white; padding: 5px;")
        self.rename_btn.clicked.connect(self.batch_rename_images)
        self.rename_btn.setEnabled(False)
        rename_layout.addWidget(self.rename_btn)
        left_layout.addWidget(rename_frame, 8, 0, 1, 2)
        # 图片列表标题
        list_label = QLabel("已选图片列表 (支持拖放图片到此处)")
        list_label.setFont(self.small_font)
        left_layout.addWidget(list_label, 9, 0, 1, 2)
        # 图片列表框
        self.image_list = QListWidget()
        self.image_list.setFont(QFont("Consolas", 9))
        self.image_list.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
        self.image_list.itemSelectionChanged.connect(self.update_preview)
        self.image_list.setAcceptDrops(True)
        self.image_list.setStyleSheet("""
            QListWidget {
                border: 1px solid #cccccc;
                background-color: white;
            }
        """)
        left_layout.addWidget(self.image_list, 10, 0, 1, 2)
        # 进度条
        self.progress = QProgressBar()
        self.progress.setRange(0, 100)
        left_layout.addWidget(self.progress, 11, 0, 1, 2)
        # 状态标签
        self.status_label = QLabel("就绪")
        self.status_label.setFont(QFont(self.font_family, 8))
        self.status_label.setStyleSheet("color: #666666;")
        left_layout.addWidget(self.status_label, 12, 0, 1, 2)
        # 设置左侧布局权重
        left_layout.setRowStretch(10, 1)
        # 右侧:批量调整区域
        right_frame = QGroupBox("批量调整图片")
        right_frame.setFont(self.title_font)
        right_frame.setStyleSheet("""
            QGroupBox {
                background-color: #ffffff;
                border: 1px solid #cccccc;
                border-radius: 5px;
                margin-top: 1.5ex;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top center;
                padding: 0 5px;
                color: #2c3e50;
            }
        """)
        right_layout = QGridLayout(right_frame)
        right_layout.setSpacing(10)
        # 调整宽度
        resize_width_label = QLabel("新宽度(cm):")
        resize_width_label.setFont(self.small_font)
        self.resize_width = QLineEdit("8.0")
        self.resize_width.setFont(self.small_font)
        self.resize_width.setFixedWidth(100)
        right_layout.addWidget(resize_width_label, 0, 0)
        right_layout.addWidget(self.resize_width, 0, 1)
        # 调整高度
        resize_height_label = QLabel("新高度(cm):")
        resize_height_label.setFont(self.small_font)
        self.resize_height = QLineEdit("6.0")
        self.resize_height.setFont(self.small_font)
        self.resize_height.setFixedWidth(100)
        right_layout.addWidget(resize_height_label, 1, 0)
        right_layout.addWidget(self.resize_height, 1, 1)
        # 保持纵横比
        self.lock_aspect = QCheckBox("保持纵横比")
        self.lock_aspect.setFont(self.small_font)
        self.lock_aspect.setChecked(True)
        right_layout.addWidget(self.lock_aspect, 2, 0, 1, 2)
        # 应用按钮
        self.resize_btn = QPushButton("应用到所有图片")
        self.resize_btn.setFont(self.small_font)
        self.resize_btn.setStyleSheet("background-color: #ff9800; color: white; padding: 5px;")
        self.resize_btn.clicked.connect(self.start_resize_thread)
        right_layout.addWidget(self.resize_btn, 3, 0, 1, 2)
        # 一键清除所有图片按钮
        self.clear_btn = QPushButton("清除文档中所有图片")
        self.clear_btn.setFont(self.small_font)
        self.clear_btn.setStyleSheet("background-color: #dc3545; color: white; padding: 5px;")
        self.clear_btn.clicked.connect(self.start_clear_thread)
        right_layout.addWidget(self.clear_btn, 4, 0, 1, 2)
        # 复制设置按钮
        copy_btn = QPushButton("从插入设置复制尺寸")
        copy_btn.setFont(QFont(self.font_family, 8))
        copy_btn.setStyleSheet("background-color: #f5f5f5; color: #555555; padding: 3px; border: 1px solid #cccccc;")
        copy_btn.clicked.connect(self.copy_insert_settings)
        right_layout.addWidget(copy_btn, 5, 0, 1, 2)
        # 图片预览区域
        preview_label = QLabel("图片预览")
        preview_label.setFont(QFont(self.font_family, 9, QFont.Weight.Bold))
        right_layout.addWidget(preview_label, 6, 0, 1, 2)
        # 图片预览 - 增大预览区域
        self.preview_label = QLabel()
        self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.preview_label.setStyleSheet("""
            QLabel {
                background-color: #f0f0f0;
                border: 1px solid #cccccc;
            }
        """)
        self.preview_label.setMinimumSize(200, 200)  # 增大预览区域
        self.preview_label.setText("请选择图片预览")
        self.preview_label.setFont(QFont("Arial", 10))
        self.preview_label.setStyleSheet("color: #999999;")
        right_layout.addWidget(self.preview_label, 7, 0, 1, 2)
        # 图片信息标签
        self.info_label = QLabel()
        self.info_label.setFont(QFont("Arial", 8))
        self.info_label.setStyleSheet("color: #666666;")
        self.info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        right_layout.addWidget(self.info_label, 8, 0, 1, 2)
        # 图片列表操作按钮
        list_btn_frame = QFrame()
        list_btn_layout = QHBoxLayout(list_btn_frame)
        list_btn_layout.setContentsMargins(0, 0, 0, 0)
        list_btn_layout.setSpacing(5)
        # 删除选中图片按钮
        self.delete_btn = QPushButton("删除选中图片")
        self.delete_btn.setFont(self.small_font)
        self.delete_btn.setStyleSheet("background-color: #dc3545; color: white; padding: 5px;")
        self.delete_btn.clicked.connect(self.delete_selected_images)
        self.delete_btn.setEnabled(False)
        list_btn_layout.addWidget(self.delete_btn)
        # 清空图片列表按钮
        self.clear_list_btn = QPushButton("清空图片列表")
        self.clear_list_btn.setFont(self.small_font)
        self.clear_list_btn.setStyleSheet("background-color: #6c757d; color: white; padding: 5px;")
        self.clear_list_btn.clicked.connect(self.clear_all_list_images)
        self.clear_list_btn.setEnabled(False)
        list_btn_layout.addWidget(self.clear_list_btn)
        right_layout.addWidget(list_btn_frame, 9, 0, 1, 2)
        # 设置右侧布局权重
        right_layout.setRowStretch(7, 1)
        # 添加左右框架到主布局
        main_layout.addWidget(left_frame, 1)
        main_layout.addWidget(right_frame, 1)
        # 存储图片路径
        self.image_paths = []
        # 存储压缩后的图片路径
        self.compressed_paths = []
        # 字体设置
        self.font_name = "宋体"  # 使用界面默认字体
        self.font_size = 9  # 使用正常字体大小
        self.font_color = QColor(0, 0, 0)  # 默认黑色
        # 设置日志记录
        logging.basicConfig(filename='word_image_inserter.log', level=logging.DEBUG,
                            format='%(asctime)s - %(levelname)s - %(message)s')
        # 启用拖放功能
        self.setAcceptDrops(True)
        self.image_list.setAcceptDrops(True)
        # 在创建完所有控件后调用 update_font_preview
        self.update_font_preview()
        # 初始化 worker_thread 属性
        self.worker_thread = None
    def select_font(self):
        """选择字体和大小"""
        # 创建初始字体对象
        initial_font = QFont(self.font_name, self.font_size)
        # 打开字体对话框
        dialog = QFontDialog(initial_font, self)
        dialog.setWindowTitle("选择字体")
        if dialog.exec() == QFontDialog.DialogCode.Accepted:
            selected_font = dialog.selectedFont()
            if selected_font:
                # 更新字体属性
                self.font_name = selected_font.family()
                self.font_size = selected_font.pointSize()
                # 更新预览
                self.update_font_preview()
    def select_color(self):
        """选择字体颜色"""
        color = QColorDialog.getColor()
        if color.isValid():
            self.font_color = color
            # 更新预览
            self.update_font_preview()
    def update_font_preview(self):
        """更新字体预览显示"""
        # 设置预览文本
        preview_text = f"{self.font_name} {self.font_size}pt"
        self.font_preview.setText(preview_text)
        # 使用选择的字体和大小设置预览标签
        preview_font = QFont(self.font_name, self.font_size)
        self.font_preview.setFont(preview_font)
        # 设置文本颜色
        palette = self.font_preview.palette()
        palette.setColor(QPalette.ColorRole.WindowText, self.font_color)
        self.font_preview.setPalette(palette)
        # 强制重绘确保更新
        self.font_preview.update()
    def select_images(self):
        """选择图片文件 - 追加模式"""
        file_dialog = QFileDialog()
        file_dialog.setNameFilter("图片文件 (*.jpg *.jpeg *.png *.bmp *.gif)")
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
        if file_dialog.exec():
            selected_files = file_dialog.selectedFiles()
            if selected_files:
                # 追加图片而不是覆盖
                self.image_paths.extend(selected_files)
                for path in selected_files:
                    self.image_list.addItem(os.path.basename(path))
                # 启用相关按钮
                self.insert_btn.setEnabled(True)
                self.compress_btn.setEnabled(True)
                self.rename_btn.setEnabled(True)
                self.delete_btn.setEnabled(True)
                self.clear_list_btn.setEnabled(True)
                self.status_label.setText(f"已添加 {len(selected_files)} 张图片,共 {len(self.image_paths)} 张")
                # 更新预览
                self.update_preview()
    def dragEnterEvent(self, event: QDragEnterEvent):
        """处理拖入事件"""
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
    def dropEvent(self, event: QDropEvent):
        """处理拖放事件 - 追加图片而不是覆盖"""
        urls = event.mimeData().urls()
        if not urls:
            return
        new_paths = []
        for url in urls:
            path = url.toLocalFile()
            if os.path.isfile(path) and self.is_image_file(path):
                new_paths.append(path)
        if new_paths:
            # 追加图片而不是覆盖
            self.image_paths.extend(new_paths)
            for path in new_paths:
                self.image_list.addItem(os.path.basename(path))
            # 启用相关按钮
            self.insert_btn.setEnabled(True)
            self.compress_btn.setEnabled(True)
            self.rename_btn.setEnabled(True)
            self.delete_btn.setEnabled(True)
            self.clear_list_btn.setEnabled(True)
            self.status_label.setText(f"已添加 {len(new_paths)} 张图片,共 {len(self.image_paths)} 张")
            # 更新预览
            self.update_preview()
    @staticmethod
    def is_image_file(path):
        """检查文件是否为支持的图片格式(静态方法)"""
        image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
        return os.path.splitext(path)[1].lower() in image_extensions
    def delete_selected_images(self):
        """删除选中的图片"""
        selected_items = self.image_list.selectedItems()
        if not selected_items:
            return
        confirm = QMessageBox.question(
            self,
            "确认删除",
            f"确定要删除选中的 {len(selected_items)} 张图片吗?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if confirm != QMessageBox.StandardButton.Yes:
            return
        # 获取选中的索引(从后往前删除,避免索引变化)
        selected_indices = [self.image_list.row(item) for item in selected_items]
        selected_indices.sort(reverse=True)
        for index in selected_indices:
            self.image_list.takeItem(index)
            self.image_paths.pop(index)
        # 更新状态
        self.status_label.setText(f"已删除 {len(selected_indices)} 张图片")
        # 如果没有图片了,禁用相关按钮
        if not self.image_paths:
            self.insert_btn.setEnabled(False)
            self.compress_btn.setEnabled(False)
            self.rename_btn.setEnabled(False)
            self.delete_btn.setEnabled(False)
            self.clear_list_btn.setEnabled(False)
            self.preview_label.setText("无图片预览")
            self.preview_label.setStyleSheet("color: #999999;")
            self.info_label.setText("")
        else:
            # 更新预览
            self.update_preview()
    def clear_all_list_images(self):
        """清空图片列表中的所有图片"""
        if not self.image_paths:
            return
        confirm = QMessageBox.question(
            self,
            "确认清空",
            "确定要清空所有图片吗?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if confirm != QMessageBox.StandardButton.Yes:
            return
        self.image_list.clear()
        self.image_paths = []
        self.compressed_paths = []
        # 禁用相关按钮
        self.insert_btn.setEnabled(False)
        self.compress_btn.setEnabled(False)
        self.rename_btn.setEnabled(False)
        self.delete_btn.setEnabled(False)
        self.clear_list_btn.setEnabled(False)
        self.preview_label.setText("无图片预览")
        self.preview_label.setStyleSheet("color: #999999;")
        self.info_label.setText("")
        self.status_label.setText("已清空所有图片")
    def update_preview(self):
        """更新图片预览 - 增大预览区域"""
        selected_items = self.image_list.selectedItems()
        if not selected_items:
            self.delete_btn.setEnabled(False)
            self.preview_label.setText("请选择图片预览")
            self.preview_label.setStyleSheet("color: #999999;")
            self.info_label.setText("")
            return
        # 启用删除按钮
        self.delete_btn.setEnabled(True)
        # 只显示第一张选中的图片
        index = self.image_list.row(selected_items[0])
        img_path = self.image_paths[index]
        try:
            # 使用Pillow获取图片信息
            with Image.open(img_path) as img:
                img_width, img_height = img.size
            file_size = os.path.getsize(img_path) // 1024  # KB
            # 创建QPixmap对象
            pixmap = QPixmap(img_path)
            if pixmap.isNull():
                raise IOError("无法加载图片")
            # 调整图片大小以适应预览区域
            max_size = 450
            if pixmap.width() > max_size or pixmap.height() > max_size:
                pixmap = pixmap.scaled(max_size, max_size,
                                       Qt.AspectRatioMode.KeepAspectRatio,
                                       Qt.TransformationMode.SmoothTransformation)
            # 显示图片
            self.preview_label.setPixmap(pixmap)
            self.preview_label.setStyleSheet("")
            # 显示图片信息
            self.info_label.setText(
                f"文件名: {os.path.basename(img_path)}\n"
                f"尺寸: {img_width}×{img_height}像素 | 大小: {file_size}KB"
            )
        except (IOError, OSError) as e:
            self.preview_label.setText("无法预览图片")
            self.preview_label.setStyleSheet("color: #cc0000;")
            self.info_label.setText(f"错误: {str(e)}")
            logging.error(f"预览图片时出错: {str(e)}")
        except Exception as e:
            self.preview_label.setText("无法预览图片")
            self.preview_label.setStyleSheet("color: #cc0000;")
            self.info_label.setText("未知错误")
            logging.error(f"预览图片时发生未知错误: {str(e)}")
    def copy_insert_settings(self):
        """将插入图片的尺寸设置复制到批量调整区域"""
        self.resize_width.setText(self.insert_width.text())
        self.resize_height.setText(self.insert_height.text())
        self.status_label.setText("已复制插入图片的尺寸设置")
    def start_compress_thread(self):
        """启动图片压缩线程"""
        # 验证输入
        try:
            target_size_kb = int(self.compression_quality.text())
            if target_size_kb  10240:  # 限制在1-10240KB之间
                QMessageBox.warning(self, "输入无效", "请输入1-10240之间的数值")
                return
        except ValueError:
            QMessageBox.warning(self, "输入无效", "请输入有效的数值")
            return
        # 启动线程
        self.set_buttons_enabled(False)
        self.status_label.setText("正在压缩图片...")
        self.progress.setValue(0)
        self.worker_thread = WorkerThread(self.compress_images, (target_size_kb,))
        self.worker_thread.progress_updated.connect(self.update_compress_progress)
        self.worker_thread.task_completed.connect(self.compress_completed)
        self.worker_thread.error_occurred.connect(self.handle_error)
        self.worker_thread.start()
    def compress_images(self, target_size_kb):
        """执行图片压缩操作"""
        try:
            total_images = len(self.image_paths)
            target_size = target_size_kb * 1024  # 转换为字节
            self.compressed_paths = []
            # 创建压缩图片保存目录
            compress_dir = os.path.join(os.path.dirname(self.image_paths[0]), "压缩图片")
            os.makedirs(compress_dir, exist_ok=True)
            for i, img_path in enumerate(self.image_paths):
                try:
                    # 打开图片
                    with Image.open(img_path) as img:
                        # 获取原图片格式
                        img_format = img.format if img.format else "JPEG"
                        if img_format.upper() == "PNG" and img.mode in ('RGBA', 'LA'):
                            # 处理透明背景
                            background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
                            background.paste(img, img.split()[-1])
                            img = background
                        # 生成保存路径
                        filename = os.path.basename(img_path)
                        name, ext = os.path.splitext(filename)
                        save_path = os.path.join(compress_dir, f"{name}_compressed{ext}")
                        # 初始质量
                        quality = 95
                        step = 5
                        # 循环调整质量直到达到目标大小
                        while quality > 0:
                            # 保存临时图片
                            with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as temp_file:
                                img.save(temp_file, format=img_format, quality=quality, optimize=True)
                                temp_path = temp_file.name
                            # 检查文件大小
                            file_size = os.path.getsize(temp_path)
                            if file_size  2:
                    start_col = 1
                self.worker_thread.progress_updated.emit(0, f"从表格位置: 行{start_row}, 列{start_col}开始插入")
            except (pythoncom.com_error, AttributeError) as e:
                start_row = 1
                start_col = 1
                try:
                    table.Cell(start_row, start_col).Select()
                    selection = word_app.Selection
                    self.worker_thread.progress_updated.emit(0, "从表格左上角开始插入")
                except:
                    error_message = "无法定位表格起始位置。"
                    raise Exception(error_message) from e
            # 插入图片和文件名(按照单个单元格陆续插入)
            total_images = len(self.image_paths)
            # 设置当前插入位置
            current_row = start_row
            current_col = start_col
            for i, img_path in enumerate(self.image_paths):
                # 更新进度
                progress = (i + 1) / total_images * 100
                self.worker_thread.progress_updated.emit(progress,
                                                         f"插入图片: {os.path.basename(img_path)} ({i + 1}/{total_images})")
                try:
                    # 检查是否需要添加新行
                    if current_row > table.Rows.Count:
                        table.Rows.Add()
                        self.worker_thread.progress_updated.emit(progress, f"添加新行: 共{table.Rows.Count}行")
                    # 检查是否需要添加新列
                    if current_col > table.Columns.Count:
                        table.Columns.Add()
                        self.worker_thread.progress_updated.emit(progress, f"添加新列: 共{table.Columns.Count}列")
                    # 获取图片单元格
                    img_cell = table.Cell(current_row, current_col)
                    # 添加图片
                    success = self.add_image_to_cell(img_cell, img_path, params)
                    # 获取名称单元格(图片行的下一行)
                    name_row = current_row + 1
                    if name_row > table.Rows.Count:
                        table.Rows.Add()
                        self.worker_thread.progress_updated.emit(progress, f"补充名称行: 共{table.Rows.Count}行")
                    name_cell = table.Cell(name_row, current_col)
                    self.add_filename(name_cell, img_path, params, success)
                    # 移动到下一个位置
                    current_col += 1
                    if current_col > 2:  # 如果超过2列(即一行最多2张图)
                        current_col = 1  # 回到第一列
                        current_row += 2  # 向下移动两行(图片行+文件名行)
                except (OSError, IOError, pythoncom.com_error) as e:
                    error_msg = f"处理图片 {os.path.basename(img_path)} 时出错: {str(e)}"
                    self.worker_thread.progress_updated.emit(progress, error_msg)
                    logging.error(f"处理第{i + 1}张图片时出错: {error_msg}\n{traceback.format_exc()}")
                except Exception as e:
                    error_msg = f"处理图片 {os.path.basename(img_path)} 时发生未知错误: {str(e)}"
                    self.worker_thread.progress_updated.emit(progress, error_msg)
                    logging.error(f"处理第{i + 1}张图片时发生未知错误: {error_msg}\n{traceback.format_exc()}")
            # 完成
            self.worker_thread.progress_updated.emit(100, f"成功插入 {total_images} 张图片到表格")
            word_app.Activate()
        except (pythoncom.com_error, OSError, IOError) as e:
            error_message = str(e) or "发生错误"
            self.worker_thread.error_occurred.emit(error_message)
            logging.error(f"插入图片时出错: {error_message}\n{traceback.format_exc()}")
        except Exception as e:
            error_message = f"发生未知错误: {str(e)}"
            self.worker_thread.error_occurred.emit(error_message)
            logging.error(f"插入图片时发生未知错误: {error_message}\n{traceback.format_exc()}")
        finally:
            # 清理COM
            with com_lock:
                try:
                    pythoncom.CoUninitialize()
                except:
                    pass
    def update_insert_progress(self, progress, message):
        """更新插入进度"""
        self.progress.setValue(int(progress))
        self.status_label.setText(message)
    def insert_completed(self):
        """插入完成处理"""
        self.status_label.setText("图片插入操作完成")
        self.progress.setValue(100)
        self.set_buttons_enabled(True)
        QMessageBox.information(self, "完成", "图片已成功插入到Word文档")
    def start_resize_thread(self):
        """启动调整图片大小的线程"""
        # 验证输入
        try:
            new_width = float(self.resize_width.text())
            new_height = float(self.resize_height.text())
            if new_width  30.0:
                QMessageBox.warning(self, "输入无效", "图片宽度应在1-30厘米之间")
                return
            if new_height  30.0:
                QMessageBox.warning(self, "输入无效", "图片高度应在1-30厘米之间")
                return
        except ValueError:
            QMessageBox.warning(self, "输入无效", "请输入有效的数值")
            return
        # 启动线程
        self.set_buttons_enabled(False)
        self.status_label.setText("正在修改所有图片大小...")
        self.progress.setValue(0)
        lock_aspect = self.lock_aspect.isChecked()
        self.worker_thread = WorkerThread(self.resize_all_images, (new_width, new_height, lock_aspect))
        self.worker_thread.progress_updated.connect(self.update_resize_progress)
        self.worker_thread.task_completed.connect(self.resize_completed)
        self.worker_thread.error_occurred.connect(self.handle_error)
        self.worker_thread.start()
    def resize_all_images(self, new_width, new_height, lock_aspect):
        """实际执行修改所有图片大小的操作"""
        try:
            # 使用线程锁保护COM初始化
            with com_lock:
                pythoncom.CoInitialize()
            # 连接到Word
            try:
                word_app = win32.gencache.EnsureDispatch("Word.Application")
                word_app.Visible = True
            except (pythoncom.com_error, AttributeError) as e:
                error_msg = "无法连接到Word应用程序"
                raise Exception(error_msg) from e
            # 检查是否有打开的文档
            if word_app.Documents.Count == 0:
                error_msg = "没有打开的Word文档"
                raise Exception(error_msg)
            doc = word_app.ActiveDocument
            self.worker_thread.progress_updated.emit(0, f"正在处理文档: {doc.Name}")
            # 转换厘米到磅
            cm_to_points = 28.3465
            target_width = new_width * cm_to_points
            target_height = new_height * cm_to_points
            # 统计图片数量
            total_images = doc.InlineShapes.Count
            if total_images == 0:
                self.worker_thread.progress_updated.emit(100, "文档中没有图片")
                return
            # 遍历所有图片并修改大小
            for i in range(1, total_images + 1):
                try:
                    shape = doc.InlineShapes(i)
                    if lock_aspect:
                        # 保持纵横比,按宽度计算高度
                        original_ratio = shape.Height / shape.Width
                        shape.Width = target_width
                        shape.Height = target_width * original_ratio
                    else:
                        # 不锁定纵横比,按指定尺寸调整
                        shape.LockAspectRatio = 0
                        shape.Width = target_width
                        shape.Height = target_height
                    # 更新进度
                    progress = (i / total_images) * 100
                    self.worker_thread.progress_updated.emit(progress, f"正在修改第{i}/{total_images}张图片")
                    time.sleep(0.1)  # 短暂延迟
                except (pythoncom.com_error, AttributeError) as e:
                    logging.error(f"修改第{i}张图片时出错: {str(e)}")
                    continue
                except Exception as e:
                    logging.error(f"修改第{i}张图片时发生未知错误: {str(e)}")
                    continue
            # 完成
            self.worker_thread.progress_updated.emit(100, f"已完成!共修改了 {total_images} 张图片的大小")
        except (pythoncom.com_error, OSError, IOError) as e:
            error_msg = f"修改图片大小时出错: {str(e)}"
            logging.error(error_msg, exc_info=True)
            self.worker_thread.error_occurred.emit(error_msg)
        except Exception as e:
            error_msg = f"修改图片大小时发生未知错误: {str(e)}"
            logging.error(error_msg, exc_info=True)
            self.worker_thread.error_occurred.emit(error_msg)
        finally:
            # 清理
            with com_lock:
                try:
                    pythoncom.CoUninitialize()
                except:
                    pass
    def update_resize_progress(self, progress, message):
        """更新调整大小进度"""
        self.progress.setValue(int(progress))
        self.status_label.setText(message)
    def resize_completed(self):
        """调整大小完成处理"""
        self.status_label.setText("图片大小调整操作完成")
        self.set_buttons_enabled(True)
        QMessageBox.information(self, "完成", "图片大小调整操作完成")
    def start_clear_thread(self):
        """启动清除所有图片的线程"""
        # 确认操作
        confirm = QMessageBox.question(
            self,
            "确认清除",
            "确定要删除文档中所有图片吗?此操作不可恢复!",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if confirm != QMessageBox.StandardButton.Yes:
            return
        # 启动线程
        self.set_buttons_enabled(False)
        self.status_label.setText("正在清除所有图片...")
        self.progress.setValue(0)
        self.worker_thread = WorkerThread(self.clear_all_images)
        self.worker_thread.progress_updated.connect(self.update_clear_progress)
        self.worker_thread.task_completed.connect(self.clear_completed)
        self.worker_thread.error_occurred.connect(self.handle_error)
        self.worker_thread.start()
    def clear_all_images(self):
        """实际执行清除所有图片的操作"""
        try:
            # 使用线程锁保护COM初始化
            with com_lock:
                pythoncom.CoInitialize()
            # 连接到Word
            try:
                word_app = win32.gencache.EnsureDispatch("Word.Application")
                word_app.Visible = True
            except (pythoncom.com_error, AttributeError) as e:
                error_msg = "无法连接到Word应用程序"
                raise Exception(error_msg) from e
            # 检查是否有打开的文档
            if word_app.Documents.Count == 0:
                error_msg = "没有打开的Word文档"
                raise Exception(error_msg)
            doc = word_app.ActiveDocument
            self.worker_thread.progress_updated.emit(0, f"正在处理文档: {doc.Name}")
            # 统计图片数量
            total_images = doc.InlineShapes.Count
            if total_images == 0:
                self.worker_thread.progress_updated.emit(100, "文档中没有图片")
                return
            # 从后往前删除图片(避免索引变化问题)
            removed = 0
            for i in range(total_images, 0, -1):
                try:
                    # 删除图片
                    doc.InlineShapes(i).Delete()
                    removed += 1
                    # 更新进度
                    progress = (removed / total_images) * 100
                    self.worker_thread.progress_updated.emit(progress, f"已清除 {removed}/{total_images} 张图片")
                    time.sleep(0.1)  # 短暂延迟
                except (pythoncom.com_error, AttributeError) as e:
                    logging.error(f"清除第{i}张图片时出错: {str(e)}")
                    continue
                except Exception as e:
                    logging.error(f"清除第{i}张图片时发生未知错误: {str(e)}")
                    continue
            # 完成
            self.worker_thread.progress_updated.emit(100, f"已完成!共清除了 {removed} 张图片")
        except (pythoncom.com_error, OSError, IOError) as e:
            error_msg = f"清除图片时出错: {str(e)}"
            logging.error(error_msg, exc_info=True)
            self.worker_thread.error_occurred.emit(error_msg)
        except Exception as e:
            error_msg = f"清除图片时发生未知错误: {str(e)}"
            logging.error(error_msg, exc_info=True)
            self.worker_thread.error_occurred.emit(error_msg)
        finally:
            # 清理
            with com_lock:
                try:
                    pythoncom.CoUninitialize()
                except:
                    pass
    def update_clear_progress(self, progress, message):
        """更新清除进度"""
        self.progress.setValue(int(progress))
        self.status_label.setText(message)
    def clear_completed(self):
        """清除图片完成处理"""
        self.status_label.setText("图片清除操作完成")
        self.set_buttons_enabled(True)
        QMessageBox.information(self, "完成", "图片清除操作完成")
    def handle_error(self, error_message):
        """处理错误"""
        self.status_label.setText(f"错误: {error_message}")
        self.progress.setValue(0)
        self.set_buttons_enabled(True)
        QMessageBox.critical(self, "错误", error_message)
    def set_buttons_enabled(self, enabled):
        """设置按钮启用状态"""
        self.insert_btn.setEnabled(enabled and bool(self.image_paths))
        self.select_btn.setEnabled(enabled)
        self.resize_btn.setEnabled(enabled)
        self.clear_btn.setEnabled(enabled)
        self.compress_btn.setEnabled(enabled and bool(self.image_paths))
        self.rename_btn.setEnabled(enabled and bool(self.image_paths))
        self.delete_btn.setEnabled(enabled and bool(self.image_list.selectedItems()))
        self.clear_list_btn.setEnabled(enabled and bool(self.image_paths))
    def add_image_to_cell(self, cell, img_path, params):
        """在Word表格单元格中添加图片并调整大小"""
        temp_dir = None
        try:
            if not os.path.exists(img_path):
                raise FileNotFoundError(f"图片文件不存在: {img_path}")
            # 创建临时副本(解决特殊字符问题)
            temp_dir = tempfile.mkdtemp()
            safe_filename = f"img_{os.urandom(4).hex()}{os.path.splitext(img_path)[1]}"
            temp_path = os.path.join(temp_dir, safe_filename)
            shutil.copy2(img_path, temp_path)
            # 计算目标尺寸(厘米转磅)
            cm_to_points = 28.3465
            target_width = params["width"] * cm_to_points
            target_height = params["height"] * cm_to_points
            # 清除单元格原有内容
            cell.Range.Delete()
            # 添加图片
            inline_shape = cell.Range.InlineShapes.AddPicture(
                FileName=temp_path,
                LinkToFile=False,
                SaveWithDocument=True
            )
            # 调整图片大小
            inline_shape.LockAspectRatio = 0  # 不锁定纵横比
            inline_shape.Width = target_width
            inline_shape.Height = target_height
            # 图片居中
            cell.Range.Paragraphs.Alignment = 1  # 居中
            return True
        except (OSError, IOError, pythoncom.com_error) as e:
            error_msg = f"无法处理图片 {os.path.basename(img_path)}: {str(e)}"
            self.worker_thread.progress_updated.emit(0, error_msg)
            return False
        except Exception as e:
            error_msg = f"无法处理图片 {os.path.basename(img_path)}: 未知错误 {str(e)}"
            self.worker_thread.progress_updated.emit(0, error_msg)
            return False
        finally:
            # 清理临时文件
            if temp_dir and os.path.exists(temp_dir):
                try:
                    shutil.rmtree(temp_dir)
                except Exception as e:
                    logging.error(f"清理临时文件失败: {str(e)}", exc_info=True)
    def add_filename(self, cell, img_path, params, success):
        """在图片下方的单元格中添加文件名 - 应用字体设置"""
        try:
            # 获取文件名(不含扩展名)
            filename = os.path.splitext(os.path.basename(img_path))[0]
            # 清除单元格原有内容
            cell.Range.Delete()
            # 添加文件名文本
            if not success:
                filename = f"[失败] {filename}"
            # 先设置文本内容
            cell.Range.Text = filename
            # 获取文本范围
            text_range = cell.Range
            # 设置中文字体名称(重要!)
            text_range.Font.NameFarEast = params["font_name"]
            # 设置英文字体名称
            text_range.Font.Name = params["font_name"]
            # 设置字体大小(使用原始字体大小,不需要转换)
            text_range.Font.Size = params["font_size"]
            # 设置段落对齐
            text_range.Paragraphs.Alignment = 1  # 居中
            # 设置行间距
            text_range.Paragraphs.LineSpacing = params["line_spacing"]
            # 设置字体颜色
            if not success:
                text_range.Font.Color = win32.constants.wdColorRed
            else:
                # 将QColor转换为Word颜色常量
                red = params["font_color"].red()
                green = params["font_color"].green()
                blue = params["font_color"].blue()
                color_val = blue * 65536 + green * 256 + red
                text_range.Font.Color = color_val
        except (pythoncom.com_error, AttributeError) as e:
            error_msg = f"添加文件名失败: {os.path.basename(img_path)}: {str(e)}"
            self.worker_thread.progress_updated.emit(0, error_msg)
        except Exception as e:
            error_msg = f"添加文件名失败: {os.path.basename(img_path)}: 未知错误 {str(e)}"
            self.worker_thread.progress_updated.emit(0, error_msg)
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = WordImageInserter()
    window.show()
    sys.exit(app.exec())

图片, 错误

sxsllgq   

2025-08-22 04:31:52,553 - ERROR - 插入图片时发生未知错误: 发生未知错误: 无法启动Word应用程序。请确保已安装Microsoft Word。
Traceback (most recent call last):
  File "5.0.py", line 903, in insert_to_existing_table
  File "win32com\client\gencache.py", line 654, in EnsureDispatch
  File "win32com\client\gencache.py", line 481, in EnsureModule
  File "win32com\client\gencache.py", line 312, in GetModuleForTypelib
  File "win32com\client\gencache.py", line 687, in AddModuleToCache
AttributeError: module 'win32com.gen_py.00020905-0000-0000-C000-000000000046x0x8x7' has no attribute 'CLSIDToClassMap'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "5.0.py", line 907, in insert_to_existing_table
Exception: 无法启动Word应用程序。请确保已安装Microsoft Word。
deepxia
OP
  


sxsllgq 发表于 2025-8-22 04:33
2025-08-22 04:31:52,553 - ERROR - 插入图片时发生未知错误: 发生未知错误: 无法启动Word应用程序。请确保 ...

win32com.gen_py.00020905-0000-0000-C000-000000000046x0x8x7' has no attribute 'CLSIDToClassMap' 临时简单解决办法,删除C:\Users\[用户名]\AppData\Local\Temp\gen_py文件夹,重新打开软件,应该就能用了。想一劳永逸的解决办法,只有改源代码了或者看看Office版本兼容性问题(就是64位的电脑装了32位的office之类的)
王者之剑0   

老哥,有没有成品呀,看不懂
njznl   

学习学习,谢谢分享!
qqxx   

请给个软件下载链接呀,谢谢
KusicFack   

支持开源,感谢楼主分享
deepxia
OP
  


王者之剑0 发表于 2025-8-14 09:17
老哥,有没有成品呀,看不懂

https://wwej.lanzouu.com/iuP4m33ia6de密码:52pj
deepxia
OP
  


qqxx 发表于 2025-8-14 09:21
请给个软件下载链接呀,谢谢

https://wwej.lanzouu.com/iuP4m33ia6de密码:52pj
jacktong85   

【公告】发帖代码插入以及添加链接教程(有福利)
https://www.52pojie.cn/thread-713042-1-1.html
(出处: 吾爱破解论坛)
您需要登录后才可以回帖 登录 | 立即注册

返回顶部