批量转换文档为电子书的脚本

wen 实用脚本 1

本文目录导读:

批量转换文档为电子书的脚本

  1. 基础版:支持TXT、PDF、DOCX转EPUB
  2. 高级版:加图形界面(可选)
  3. 使用说明

我来为您提供一个批量转换文档为电子书的Python脚本,支持常见的文档格式转换。

基础版:支持TXT、PDF、DOCX转EPUB

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量文档转电子书脚本
支持格式: TXT, PDF, DOCX → EPUB, MOBI
"""
import os
import sys
import argparse
from pathlib import Path
from typing import List, Optional
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
# 第三方库导入
try:
    from ebooklib import epub
    from bs4 import BeautifulSoup
    import fitz  # PyMuPDF
    from docx import Document
    from tqdm import tqdm
except ImportError as e:
    print(f"缺少必要库: {e}")
    print("请安装: pip install ebooklib beautifulsoup4 pymupdf python-docx tqdm")
    sys.exit(1)
# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class DocumentConverter:
    """文档转换器类"""
    SUPPORTED_INPUT = {'.txt', '.pdf', '.docx'}
    SUPPORTED_OUTPUT = {'.epub', '.mobi'}
    def __init__(self, input_dir: str, output_dir: str, output_format: str = 'epub'):
        """
        初始化转换器
        Args:
            input_dir: 输入目录
            output_dir: 输出目录
            output_format: 输出格式 (epub/mobi)
        """
        self.input_dir = Path(input_dir)
        self.output_dir = Path(output_dir)
        self.output_format = output_format.lower()
        if self.output_format not in self.SUPPORTED_OUTPUT:
            raise ValueError(f"不支持的输出格式: {output_format}")
        # 创建输出目录
        self.output_dir.mkdir(parents=True, exist_ok=True)
    def convert_document(self, file_path: Path) -> Optional[Path]:
        """
        转换单个文档
        Args:
            file_path: 输入文件路径
        Returns:
            转换后的文件路径,失败返回None
        """
        try:
            # 根据文件类型选择转换方法
            suffix = file_path.suffix.lower()
            if suffix == '.txt':
                content = self._read_txt(file_path)
            elif suffix == '.pdf':
                content = self._read_pdf(file_path)
            elif suffix == '.docx':
                content = self._read_docx(file_path)
            else:
                logger.warning(f"不支持的文件格式: {file_path}")
                return None
            # 生成电子书
            output_file = self.output_dir / f"{file_path.stem}.{self.output_format}"
            if self.output_format == 'epub':
                self._create_epub(file_path.stem, content, output_file)
            else:  # mobi格式可能需要额外转换,这里简单处理
                self._create_epub(file_path.stem, content, output_file.with_suffix('.epub'))
                # 注意: 真正的MOBI转换需要Calibre的ebook-convert工具
            logger.info(f"转换成功: {file_path.name} → {output_file.name}")
            return output_file
        except Exception as e:
            logger.error(f"转换失败 {file_path.name}: {str(e)}")
            return None
    def _read_txt(self, file_path: Path) -> str:
        """读取TXT文件"""
        try:
            # 尝试多种编码
            encodings = ['utf-8', 'gbk', 'gb2312', 'big5', 'latin-1']
            for encoding in encodings:
                try:
                    with open(file_path, 'r', encoding=encoding) as f:
                        return f.read()
                except UnicodeDecodeError:
                    continue
            raise ValueError(f"无法识别文件编码: {file_path}")
        except Exception as e:
            raise Exception(f"读取TXT失败: {str(e)}")
    def _read_pdf(self, file_path: Path) -> str:
        """读取PDF文件"""
        try:
            doc = fitz.open(str(file_path))
            text = ""
            for page in doc:
                text += page.get_text()
            doc.close()
            return text
        except Exception as e:
            raise Exception(f"读取PDF失败: {str(e)}")
    def _read_docx(self, file_path: Path) -> str:
        """读取DOCX文件"""
        try:
            doc = Document(file_path)
            paragraphs = [p.text for p in doc.paragraphs]
            return '\n\n'.join(paragraphs)
        except Exception as e:
            raise Exception(f"读取DOCX失败: {str(e)}")
    def _create_epub(self, title: str, content: str, output_file: Path):
        """创建EPUB文件"""
        try:
            # 创建EPUB对象
            book = epub.EpubBook()
            book.set_identifier(f'id_{hash(title)}')
            book.set_title(title)
            book.set_language('zh')
            # 添加作者元数据
            book.add_author('Converter')
            # 创建章节
            # 简单分章:每2000字分一章
            chapter_size = 2000
            chapters = [content[i:i+chapter_size] 
                       for i in range(0, len(content), chapter_size)]
            if not chapters:
                chapters = ['']  # 空内容时添加空章节
            for i, chapter_content in enumerate(chapters):
                chapter = epub.EpubHtml(
                    title=f'第{i+1}章',
                    file_name=f'chapter_{i+1}.xhtml',
                    lang='zh'
                )
                # 将纯文本转换为HTML
                html_content = f'<h2>第{i+1}章</h2><p>{chapter_content}</p>'
                chapter.content = html_content
                book.add_item(chapter)
                book.toc.append(chapter)
            # 添加样式
            style = '''
                body { font-family: "Noto Serif CJK SC", serif; }
                h2 { text-align: center; margin-bottom: 1em; }
                p { text-indent: 2em; line-height: 1.8; }
            '''
            nav_css = epub.EpubItem(
                uid='style',
                file_name='style.css',
                media_type='text/css',
                content=style
            )
            book.add_item(nav_css)
            # 设置spine
            book.spine = ['nav'] + book.toc
            # 添加默认导航
            book.add_item(epub.EpubNcx())
            book.add_item(epub.EpubNav())
            # 保存EPUB文件
            epub.write_epub(str(output_file), book)
        except Exception as e:
            raise Exception(f"创建EPUB失败: {str(e)}")
    def batch_convert(self, max_workers: int = 4) -> List[Path]:
        """
        批量转换所有支持的文件
        Args:
            max_workers: 最大线程数
        Returns:
            成功转换的文件列表
        """
        # 收集所有支持的文件
        files = []
        for ext in self.SUPPORTED_INPUT:
            files.extend(self.input_dir.glob(f"*{ext}"))
            files.extend(self.input_dir.glob(f"*{ext.upper()}"))
        if not files:
            logger.warning(f"在 {self.input_dir} 中未找到支持的文档")
            return []
        logger.info(f"找到 {len(files)} 个文档,开始转换...")
        # 多线程转换
        converted_files = []
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # 使用tqdm显示进度
            futures = {executor.submit(self.convert_document, f): f for f in files}
            with tqdm(total=len(files), desc="转换进度", unit="个") as pbar:
                for future in as_completed(futures):
                    result = future.result()
                    if result:
                        converted_files.append(result)
                    pbar.update(1)
                    pbar.set_postfix(success=len(converted_files))
        logger.info(f"转换完成!成功: {len(converted_files)}/{len(files)} 个文件")
        return converted_files
def main():
    """主函数"""
    parser = argparse.ArgumentParser(
        description='批量转换文档为电子书',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
示例:
  python convert_ebook.py -i ./documents -o ./ebooks
  python convert_ebook.py -i ./docs -o ./epubs --format epub --threads 8
  python convert_ebook.py -i ./docs -o ./mobis --format mobi
        '''
    )
    parser.add_argument('-i', '--input', required=True, help='输入文档目录')
    parser.add_argument('-o', '--output', default='./output', help='输出电子书目录 (默认: ./output)')
    parser.add_argument('-f', '--format', choices=['epub', 'mobi'], default='epub', 
                       help='输出格式 (默认: epub)')
    parser.add_argument('-t', '--threads', type=int, default=4, 
                       help='并行线程数 (默认: 4)')
    parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], 
                       default='INFO', help='日志级别')
    args = parser.parse_args()
    # 设置日志级别
    logger.setLevel(getattr(logging, args.log_level))
    try:
        # 检查输入目录
        input_dir = Path(args.input)
        if not input_dir.exists():
            raise ValueError(f"输入目录不存在: {args.input}")
        if not input_dir.is_dir():
            raise ValueError(f"输入路径不是目录: {args.input}")
        # 创建转换器并执行转换
        converter = DocumentConverter(
            input_dir=input_dir,
            output_dir=Path(args.output),
            output_format=args.format
        )
        converted_files = converter.batch_convert(max_workers=args.threads)
        # 输出总结
        print("\n" + "="*50)
        print(f"转换完成!成功处理 {len(converted_files)} 个文件")
        for i, file in enumerate(converted_files, 1):
            print(f"{i}. {file}")
        print("="*50)
    except Exception as e:
        logger.error(f"转换过程出错: {str(e)}")
        sys.exit(1)
if __name__ == "__main__":
    main()

高级版:加图形界面(可选)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
带GUI的批量转换器(需要安装 tkinter)
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
from pathlib import Path
import sys
import os
# 导入转换器
from convert_ebook import DocumentConverter  # 假设上面的代码保存为 convert_ebook.py
class ConverterGUI:
    """图形界面转换器"""
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("批量文档转电子书工具")
        self.root.geometry("600x400")
        # 变量
        self.input_dir = tk.StringVar()
        self.output_dir = tk.StringVar(value="./output")
        self.output_format = tk.StringVar(value="epub")
        self.threads = tk.IntVar(value=4)
        self.progress_var = tk.DoubleVar(value=0)
        self.converter = None
        self.setup_ui()
    def setup_ui(self):
        """设置UI"""
        # 输入目录
        ttk.Label(self.root, text="输入目录:").grid(row=0, column=0, padx=5, pady=5, sticky='w')
        ttk.Entry(self.root, textvariable=self.input_dir, width=40).grid(row=0, column=1, padx=5, pady=5)
        ttk.Button(self.root, text="浏览...", command=self.select_input).grid(row=0, column=2, padx=5)
        # 输出目录
        ttk.Label(self.root, text="输出目录:").grid(row=1, column=0, padx=5, pady=5, sticky='w')
        ttk.Entry(self.root, textvariable=self.output_dir, width=40).grid(row=1, column=1, padx=5, pady=5)
        ttk.Button(self.root, text="浏览...", command=self.select_output).grid(row=1, column=2, padx=5)
        # 输出格式
        ttk.Label(self.root, text="输出格式:").grid(row=2, column=0, padx=5, pady=5, sticky='w')
        format_frame = ttk.Frame(self.root)
        format_frame.grid(row=2, column=1, padx=5, pady=5, sticky='w')
        ttk.Radiobutton(format_frame, text="EPUB", variable=self.output_format, value="epub").pack(side='left')
        ttk.Radiobutton(format_frame, text="MOBI", variable=self.output_format, value="mobi").pack(side='left')
        # 线程数
        ttk.Label(self.root, text="并行线程:").grid(row=3, column=0, padx=5, pady=5, sticky='w')
        ttk.Spinbox(self.root, from_=1, to=16, textvariable=self.threads, width=5).grid(row=3, column=1, padx=5, pady=5, sticky='w')
        # 进度条
        self.progress = ttk.Progressbar(self.root, variable=self.progress_var, 
                                        maximum=100, length=400)
        self.progress.grid(row=4, column=0, columnspan=3, padx=20, pady=20)
        # 日志文本框
        self.log_text = tk.Text(self.root, height=8, width=70)
        self.log_text.grid(row=5, column=0, columnspan=3, padx=20, pady=10)
        # 滚动条
        scrollbar = ttk.Scrollbar(self.root, command=self.log_text.yview)
        scrollbar.grid(row=5, column=3, sticky='ns')
        self.log_text.config(yscrollcommand=scrollbar.set)
        # 按钮
        button_frame = ttk.Frame(self.root)
        button_frame.grid(row=6, column=0, columnspan=3, pady=10)
        ttk.Button(button_frame, text="开始转换", command=self.start_convert).pack(side='left', padx=10)
        ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side='left', padx=10)
        ttk.Button(button_frame, text="退出", command=self.root.quit).pack(side='left', padx=10)
    def select_input(self):
        """选择输入目录"""
        directory = filedialog.askdirectory(title="选择文档目录")
        if directory:
            self.input_dir.set(directory)
    def select_output(self):
        """选择输出目录"""
        directory = filedialog.askdirectory(title="选择输出目录")
        if directory:
            self.output_dir.set(directory)
    def start_convert(self):
        """开始转换"""
        if not self.input_dir.get():
            messagebox.showerror("错误", "请选择输入目录")
            return
        # 禁用转换按钮
        self.log("开始转换...")
        # 在新线程中执行
        thread = threading.Thread(target=self.do_convert)
        thread.daemon = True
        thread.start()
    def do_convert(self):
        """执行转换"""
        try:
            converter = DocumentConverter(
                input_dir=self.input_dir.get(),
                output_dir=self.output_dir.get(),
                output_format=self.output_format.get()
            )
            # 覆盖日志方法
            def log_callback(msg):
                self.root.after(0, self.log, msg)
            # 转换文件
            files = converter.batch_convert(max_workers=self.threads.get())
            self.root.after(0, lambda: self.log(f"转换完成!共处理 {len(files)} 个文件"))
            self.root.after(0, lambda: messagebox.showinfo("完成", "转换完成!"))
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("错误", str(e)))
            self.root.after(0, lambda: self.log(f"转换失败: {str(e)}"))
    def log(self, message):
        """输出日志"""
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        # 更新进度(简单模拟)
        self.progress_var.set(min(self.progress_var.get() + 10, 100))
    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)
        self.progress_var.set(0)
    def run(self):
        """运行主循环"""
        self.root.mainloop()
if __name__ == "__main__":
    app = ConverterGUI()
    app.run()

使用说明

安装依赖

pip install ebooklib beautifulsoup4 pymupdf python-docx tqdm
# 如果下载慢,可以使用国内镜像
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple ebooklib beautifulsoup4 pymupdf python-docx tqdm

命令行使用

# 基本用法
python convert_ebook.py -i ./documents -o ./ebooks
# 指定输出格式
python convert_ebook.py -i ./docs -o ./epubs --format epub
# 增加并行线程
python convert_ebook.py -i ./docs -o ./ebooks --threads 8
# 查看帮助
python convert_ebook.py --help

目录结构示例

documents/
├── 小说1.txt
├── 说明书.pdf
├── 报告.docx
└── 文章.txt
# 转换后
output/
├── 小说1.epub
├── 说明书.epub
├── 报告.epub
└── 文章.epub

这个脚本提供了以下功能:

  1. 多格式支持:TXT、PDF、DOCX
  2. 多线程处理:并行转换提高效率
  3. 进度显示:转换过程清晰可见
  4. 错误处理:单个文件失败不影响其他文件
  5. 编码识别:自动识别TXT文件编码
  6. 图形界面(可选)**:方便不熟悉命令行的用户

如果需要支持更多格式或转换到Kindle格式(MOBI),建议安装Calibre并使用其命令行工具配合此脚本使用。

抱歉,评论功能暂时关闭!