python语言写的一款pdf转word、word转pdf的免费工具

news/2025/3/20 4:19:39/

Word 与 PDF 文件转换工具

这是一个简单的 Web 应用程序,允许用户将 Word 文档转换为 PDF 文件,或将 PDF 文件转换为 Word 文档。

功能特点

- Word (.docx) 转换为 PDF

- PDF 转换为 Word (.docx)

- 简单易用的 Web 界面

- 即时转换和下载

- 详细的错误处理和日志记录

安装要求

- Python 3.7 或更高版本

- 依赖库(见 requirements.txt)

- 对于 Word 到 PDF 的转换,建议安装 LibreOffice 或 OpenOffice(可选但推荐)

安装 LibreOffice(推荐)

为了确保 Word 到 PDF 的转换更加可靠,建议安装 LibreOffice:

- **Windows**: 从 [LibreOffice 官网](https://www.libreoffice.org/download/download/) 下载并安装

- **macOS**: 从 [LibreOffice 官网](https://www.libreoffice.org/download/download/) 下载并安装,或使用 Homebrew 安装:`brew install --cask libreoffice`

- **Linux**: 使用包管理器安装,例如 Ubuntu:`sudo apt-get install libreoffice`

3. 安装依赖:

```

pip install -r requirements.txt

```

4. 运行应用:

```

python app.py

```

5. 在浏览器中访问:

```

http://127.0.0.1:5000

```

## 使用说明

1. 在 Web 界面上选择转换类型(Word 转 PDF 或 PDF 转 Word)

2. 点击"选择文件"按钮并上传您的文件

3. 点击"开始转换"按钮

4. 转换完成后,文件将自动下载

效果演示

import os
import tempfile
import logging
import subprocess
import platform
import time
import shutil
from flask import Flask, render_template, request, redirect, url_for, flash, send_file, jsonify
from werkzeug.utils import secure_filename
from docx2pdf import convert as docx_to_pdf
from pdf2docx import Converter as pdf_to_docx
import check_libreoffice# 创建日志目录
log_dir = os.path.join(os.getcwd(), 'logs')
os.makedirs(log_dir, exist_ok=True)# 配置日志
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',handlers=[logging.FileHandler(os.path.join(log_dir, 'app.log'), encoding='utf-8'),logging.StreamHandler()]
)
logger = logging.getLogger(__name__)# 检查是否安装了LibreOffice
libreoffice_installed, libreoffice_path = check_libreoffice.check_libreoffice()
if libreoffice_installed:logger.info(f"LibreOffice 已安装: {libreoffice_path}")
else:logger.warning("LibreOffice 未安装,Word 到 PDF 转换可能不可靠")app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['UPLOAD_FOLDER'] = os.path.join(os.getcwd(), 'uploads')
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB max upload size# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
logger.info(f"上传目录: {app.config['UPLOAD_FOLDER']}")# 确保上传目录有正确的权限
try:os.chmod(app.config['UPLOAD_FOLDER'], 0o777)logger.info("已设置上传目录权限")
except Exception as e:logger.warning(f"无法设置上传目录权限: {str(e)}")ALLOWED_EXTENSIONS = {'docx', 'pdf'}def allowed_file(filename):return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS# 使用LibreOffice/OpenOffice转换Word到PDF
def convert_word_to_pdf_with_libreoffice(input_file, output_file):"""使用LibreOffice/OpenOffice转换Word到PDF"""system = platform.system()try:if system == 'Darwin':  # macOS# 检查是否安装了LibreOfficelibreoffice_paths = ['/Applications/LibreOffice.app/Contents/MacOS/soffice','/Applications/OpenOffice.app/Contents/MacOS/soffice']soffice_path = Nonefor path in libreoffice_paths:if os.path.exists(path):soffice_path = pathbreakif soffice_path is None:logger.error("在macOS上未找到LibreOffice或OpenOffice")return False# 添加字体嵌入和编码参数cmd = [soffice_path,'--headless','--convert-to', 'pdf:writer_pdf_Export:EmbedStandardFonts=true','--outdir', os.path.dirname(output_file),input_file]elif system == 'Windows':# Windows上的LibreOffice路径libreoffice_paths = [r'C:\Program Files\LibreOffice\program\soffice.exe',r'C:\Program Files (x86)\LibreOffice\program\soffice.exe',]soffice_path = Nonefor path in libreoffice_paths:if os.path.exists(path):soffice_path = pathbreakif soffice_path is None:logger.error("在Windows上未找到LibreOffice")return False# 添加字体嵌入和编码参数cmd = [soffice_path,'--headless','--convert-to', 'pdf:writer_pdf_Export:EmbedStandardFonts=true','--outdir', os.path.dirname(output_file),input_file]else:  # Linux# 添加字体嵌入和编码参数cmd = ['libreoffice','--headless','--convert-to', 'pdf:writer_pdf_Export:EmbedStandardFonts=true','--outdir', os.path.dirname(output_file),input_file]logger.info(f"执行命令: {' '.join(cmd)}")process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)stdout, stderr = process.communicate()if process.returncode != 0:logger.error(f"LibreOffice转换失败: {stderr.decode('utf-8', errors='ignore')}")return False# LibreOffice生成的PDF文件名可能与我们期望的不同# 它通常使用输入文件名,但扩展名改为.pdfinput_filename = os.path.basename(input_file)expected_output_filename = os.path.splitext(input_filename)[0] + '.pdf'expected_output_path = os.path.join(os.path.dirname(output_file), expected_output_filename)# 如果生成的文件名与期望的不同,重命名它if expected_output_path != output_file and os.path.exists(expected_output_path):os.rename(expected_output_path, output_file)return os.path.exists(output_file)except Exception as e:logger.exception(f"使用LibreOffice转换时出错: {str(e)}")return False# 使用pdf2docx转换PDF到Word,添加更好的字体处理
def convert_pdf_to_word(input_file, output_file):"""使用pdf2docx转换PDF到Word,添加更好的字体处理"""try:logger.info(f"开始转换PDF到Word: {input_file} -> {output_file}")# 使用pdf2docx库进行转换converter = pdf_to_docx(input_file)# 设置转换参数,提高字体处理能力converter.convert(output_file, start=0, end=None, pages=None, kwargs={'debug': False,'min_section_height': 20,'connected_border': False,'line_overlap_threshold': 0.9,'line_break_width_threshold': 2.0,'line_break_free_space_ratio': 0.3,'line_separate_threshold': 5.0,'new_paragraph_free_space_ratio': 0.85,'float_image_ignorable_gap': 5.0,'page_margin_factor': 0.1})converter.close()# 检查输出文件是否存在if not os.path.exists(output_file) or os.path.getsize(output_file) == 0:logger.error(f"转换后的文件不存在或为空: {output_file}")return Falselogger.info(f"PDF到Word转换成功: {output_file}, 大小: {os.path.getsize(output_file)} 字节")return Trueexcept Exception as e:logger.exception(f"PDF到Word转换失败: {str(e)}")return False@app.route('/')
def index():return render_template('index.html', libreoffice_installed=libreoffice_installed)@app.route('/convert', methods=['POST'])
def convert_file():# 检查是否有文件上传if 'file' not in request.files:flash('没有文件部分')logger.error('没有文件部分')return redirect(url_for('index'))file = request.files['file']# 如果用户没有选择文件,浏览器也会# 提交一个没有文件名的空部分if file.filename == '':flash('没有选择文件')logger.error('没有选择文件')return redirect(url_for('index'))if file and allowed_file(file.filename):# 安全地处理文件名original_filename = file.filenamefilename = secure_filename(original_filename)logger.info(f"原始文件名: {original_filename}, 安全文件名: {filename}")# 确保文件名不包含可能导致问题的字符filename = filename.replace('__', '_')# 创建完整的文件路径file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)logger.info(f"保存文件到: {file_path}")try:# 保存上传的文件file.save(file_path)# 检查文件是否成功保存if not os.path.exists(file_path):raise FileNotFoundError(f"文件保存失败: {file_path}")logger.info(f"文件成功保存: {file_path}, 大小: {os.path.getsize(file_path)} 字节")conversion_type = request.form.get('conversion_type')logger.info(f"转换类型: {conversion_type}")if conversion_type == 'word_to_pdf' and filename.lower().endswith('.docx'):# 转换 Word 到 PDFoutput_filename = filename.rsplit('.', 1)[0] + '.pdf'output_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)logger.info(f"开始转换 Word 到 PDF: {file_path} -> {output_path}")# 确保输出路径可写output_dir = os.path.dirname(output_path)if not os.access(output_dir, os.W_OK):logger.warning(f"输出目录不可写: {output_dir}")try:os.chmod(output_dir, 0o777)logger.info(f"已修改输出目录权限: {output_dir}")except Exception as e:logger.error(f"无法修改输出目录权限: {str(e)}")# 尝试使用多种方法转换conversion_success = False# 如果安装了LibreOffice,优先使用它if libreoffice_installed:logger.info("尝试使用LibreOffice转换...")conversion_success = convert_word_to_pdf_with_libreoffice(file_path, output_path)if conversion_success:logger.info("使用LibreOffice转换成功")# 如果LibreOffice转换失败或未安装,尝试使用docx2pdfif not conversion_success:try:logger.info("尝试使用docx2pdf转换...")docx_to_pdf(file_path, output_path)# 等待一段时间,确保文件已经写入time.sleep(2)if os.path.exists(output_path) and os.path.getsize(output_path) > 0:conversion_success = Truelogger.info("使用docx2pdf转换成功")except Exception as e:logger.warning(f"使用docx2pdf转换失败: {str(e)}")# 检查转换是否成功if not conversion_success or not os.path.exists(output_path) or os.path.getsize(output_path) == 0:# 尝试列出目录内容,看看文件是否在其他位置logger.info(f"列出目录 {output_dir} 的内容:")for f in os.listdir(output_dir):logger.info(f"  - {f} ({os.path.getsize(os.path.join(output_dir, f))} 字节)")if not libreoffice_installed:error_msg = "转换失败: 建议安装 LibreOffice 以获得更可靠的转换"flash(error_msg)logger.error(error_msg)else:error_msg = f"转换后的文件不存在或为空: {output_path}"flash(error_msg)logger.error(error_msg)return redirect(url_for('index'))logger.info(f"转换成功: {output_path}, 大小: {os.path.getsize(output_path)} 字节")# 使用临时文件复制一份,以防send_file后被删除with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:temp_path = temp_file.namewith open(output_path, 'rb') as f:temp_file.write(f.read())logger.info(f"已创建临时文件: {temp_path}")# 设置响应头,确保浏览器知道这是一个下载response = send_file(temp_path,as_attachment=True,download_name=output_filename,mimetype='application/pdf')# 添加自定义头,防止缓存response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"response.headers["Pragma"] = "no-cache"response.headers["Expires"] = "0"response.headers["X-Conversion-Success"] = "true"return responseelif conversion_type == 'pdf_to_word' and filename.lower().endswith('.pdf'):# 转换 PDF 到 Wordoutput_filename = filename.rsplit('.', 1)[0] + '.docx'output_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)logger.info(f"开始转换 PDF 到 Word: {file_path} -> {output_path}")# 确保输出路径可写output_dir = os.path.dirname(output_path)if not os.access(output_dir, os.W_OK):logger.warning(f"输出目录不可写: {output_dir}")try:os.chmod(output_dir, 0o777)logger.info(f"已修改输出目录权限: {output_dir}")except Exception as e:logger.error(f"无法修改输出目录权限: {str(e)}")# 使用改进的PDF到Word转换函数conversion_success = convert_pdf_to_word(file_path, output_path)# 检查转换是否成功if not conversion_success or not os.path.exists(output_path) or os.path.getsize(output_path) == 0:# 尝试列出目录内容,看看文件是否在其他位置logger.info(f"列出目录 {output_dir} 的内容:")for f in os.listdir(output_dir):logger.info(f"  - {f} ({os.path.getsize(os.path.join(output_dir, f))} 字节)")error_msg = f"转换后的文件不存在或为空: {output_path}"flash(error_msg)logger.error(error_msg)return redirect(url_for('index'))logger.info(f"转换成功: {output_path}, 大小: {os.path.getsize(output_path)} 字节")# 使用临时文件复制一份,以防send_file后被删除with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as temp_file:temp_path = temp_file.namewith open(output_path, 'rb') as f:temp_file.write(f.read())logger.info(f"已创建临时文件: {temp_path}")# 设置响应头,确保浏览器知道这是一个下载response = send_file(temp_path,as_attachment=True,download_name=output_filename,mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document')# 添加自定义头,防止缓存response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"response.headers["Pragma"] = "no-cache"response.headers["Expires"] = "0"response.headers["X-Conversion-Success"] = "true"return responseelse:flash('无效的转换类型或文件格式')logger.error(f"无效的转换类型或文件格式: {conversion_type}, 文件名: {filename}")return redirect(url_for('index'))except Exception as e:error_msg = f'转换过程中出错: {str(e)}'flash(error_msg)logger.exception(error_msg)return redirect(url_for('index'))finally:# 清理上传的文件try:if os.path.exists(file_path):os.remove(file_path)logger.info(f"已删除上传的文件: {file_path}")# 清理输出文件(如果存在)output_filename = filename.rsplit('.', 1)[0]pdf_output = os.path.join(app.config['UPLOAD_FOLDER'], output_filename + '.pdf')docx_output = os.path.join(app.config['UPLOAD_FOLDER'], output_filename + '.docx')if os.path.exists(pdf_output):os.remove(pdf_output)logger.info(f"已删除输出文件: {pdf_output}")if os.path.exists(docx_output):os.remove(docx_output)logger.info(f"已删除输出文件: {docx_output}")except Exception as e:logger.error(f"清理文件时出错: {str(e)}")flash('无效的文件类型。请上传 DOCX 或 PDF 文件。')logger.error(f"无效的文件类型: {file.filename if file else 'None'}")return redirect(url_for('index'))# 添加一个路由来检查转换状态
@app.route('/check_conversion_status')
def check_conversion_status():return jsonify({"status": "success"})if __name__ == '__main__':# 在启动应用程序之前检查LibreOfficecheck_libreoffice.main()app.run(debug=True) 


http://www.ppmy.cn/news/1580495.html

相关文章

分享vue好用的pdf 工具实测

vue3-pdf-app: 带大纲,带分页,带缩放,带全屏,带打印,带下载,带旋转 下载依赖: yarn add vue3-pdf-appornpm install vue3-pdf-app 配置类: 创建文件 pdfConfig.ts /…

51单片机和STM32 入门分析

51单片机和STM32是嵌入式开发中两种主流的微控制器,它们在架构、性能、应用场景等方面存在显著差异。以下是两者的对比分析及选择建议: 1. 51单片机与STM32的定义与特点 51单片机 定义:基于Intel 8051内核的8位微控制器,结构简单…

小程序API —— 51小程序界面交互 - loading 提示框

小程序提供了一些用于界面交互的 API,例如 loading 提示框、消息提示框、模态对话框等 API; loading 提示框常配合网络请求来使用,用于提高用户体验,对应的 API 有两个: wx.showLoading() 显示 loading 提示框&#…

下载指定版本的transformers

如果你想手动下载 transformers 库的 v4.49.0-Gemma-3 版本,而不是通过 pip install 命令直接安装,可以按照以下步骤操作。以下是详细的步骤说明: 步骤 1:访问 GitHub 仓库 打开浏览器,访问 Hugging Face 的 transform…

Canary Capital 向 SEC 递交首个 SUI ETF 申请文件

随着对 Sui 这一 L1 区块链的机构兴趣不断增长,其生态正在加速迈向大规模采用。作为一项重大里程碑,Canary Capital 已向美国证券交易委员会(SEC)提交注册申请,拟推出首支基于 SUI 的交易所交易基金(Exchan…

C语言和C++到底有什么关系?

C 读作“C 加加”,是“C Plus Plus”的简称。 顾名思义,C 就是在 C 语言的基础上增加了新特性,玩出了新花样,所以才说“Plus”,就像 Win11 和 Win10、iPhone 15 和 iPhone 15 Pro 的关系。 C 语言是 1972 年由美国贝…

鸿蒙Next与API 12深度解析:架构、开发实践与代码示例

文章目录 1. 鸿蒙Next核心架构1.1 系统架构演进 2. API 12关键特性2.1 分布式能力增强2.2 ArkUI增强特性 3. 开发环境配置3.1 工具链升级3.2 工程结构 4. 核心开发模式4.1 元服务开发流程4.2 跨设备调用示例 5. 性能优化实践5.1 渲染优化技巧5.2 内存管理最佳实践 6. 安全增强特…

如何在 Github 上获得 1000 star?

作为程序员,Github 是第一个绕不开的网站。我们每天都在上面享受着开源带来的便利,我相信很多同学也想自己做一个开源项目,从而获得大家的关注。然而,理想很丰满,现实却是开发了很久的项目仍然无人问津。 最近&#x…