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

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:

  1. 安装依赖:

```

pip install -r requirements.txt

```

  1. 运行应用:

```

python app.py

```

  1. 在浏览器中访问:

```

http://127.0.0.1:5000

```

使用说明

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

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

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

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

效果演示

python 复制代码
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
            # 检查是否安装了LibreOffice
            libreoffice_paths = [
                '/Applications/LibreOffice.app/Contents/MacOS/soffice',
                '/Applications/OpenOffice.app/Contents/MacOS/soffice'
            ]
            
            soffice_path = None
            for path in libreoffice_paths:
                if os.path.exists(path):
                    soffice_path = path
                    break
            
            if 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 = None
            for path in libreoffice_paths:
                if os.path.exists(path):
                    soffice_path = path
                    break
            
            if 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文件名可能与我们期望的不同
        # 它通常使用输入文件名,但扩展名改为.pdf
        input_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 False
        
        logger.info(f"PDF到Word转换成功: {output_file}, 大小: {os.path.getsize(output_file)} 字节")
        return True
    except 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.filename
        filename = 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 到 PDF
                output_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转换失败或未安装,尝试使用docx2pdf
                if 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 = True
                            logger.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.name
                    with 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 response
                
            elif conversion_type == 'pdf_to_word' and filename.lower().endswith('.pdf'):
                # 转换 PDF 到 Word
                output_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.name
                    with 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 response
            
            else:
                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__':
    # 在启动应用程序之前检查LibreOffice
    check_libreoffice.main()
    app.run(debug=True) 
相关推荐
一颗无畏豆儿7 小时前
word出现“错误!未找到引用源”问题,以及锁定(和解除)目录更新域
word
2601_958492558 小时前
7 Best WordPress Tools to Help Your News Site Actually Make Money
前端·word
w2018009 小时前
新高考答题卡模板全套PDF可打印(语文数学英语等)
pdf·高考
奋斗的老史9 小时前
LibreOffice封装文档转 PDF 工具类
java·pdf
优化控制仿真模型9 小时前
【26年最新】新高考英语大纲词汇表3500个电子版PDF(含正序版、乱序版和默写版)
经验分享·pdf
诸葛大钢铁9 小时前
OFD如何转Word?OFD转为可编辑Word的两种方法
经验分享·word·ofd·ofd转word
Eiceblue10 小时前
使用 C# 高效替换 PDF 中的文本:全页、区域与正则匹配
visualstudio·pdf·c#
Upsy-Daisy10 小时前
AI Agent 项目学习笔记(十):文件操作、终端执行与 PDF 生成工具
笔记·学习·pdf
霸道流氓气质10 小时前
批量收集多源 URL 并异步转 PDF 打包下载的完整实现(Spring Boot + Feign + 异步任务)
windows·spring boot·pdf
2601_9584925511 小时前
7 WordPress Tools I Trust for Building a High-Traffic Magazine Site
前端·word