EPUB转TXT格式转换工具是瓜皮猪借助AI编写的一款电子书转换工具,功能非常简单就是将EPUB格式的电子书转TXT格式。
起因是同事找想下载一部小说放到手里观看,并且使用博主推荐的SoNovel-交互式小说下载器已经下载好图书,不过因为他没看说明,没有修改配置文件导致下载的格式是EPUB,网上找的转换软件电子书超过一定大小就需要收费,于是就让我帮他处理。
其实修改一下配置文件,或者换个工具照样能下载到TXT格式的电子书,不过既然有要求咱就解决不是,直接打开AI,丢给AI需求,然后接杯水的功夫,代码就出来了。
运行后还行,就打包分享出来,需要的朋友直接拿去。
使用说明
打开软件,选择EPUB文件和保存目录。调整一下转换选项,一般默认即可,除非出现乱码,需要修改一下编码格式。
然后点击「开始转换」按钮即可。
小说下载工具
如果想直接下载TXT格式的小说,可以尝试下面工具
SoNovel-交互式小说下载器(看文章说明修改一下配置文件,很简单)
EhPG小说下载器 (使用时可能间歇抽风,可以多尝试几次)
EPUB转TXT格式转换工具源代码
动手能力强的同学可以对源代码自行修改
- import tkinter as tk
- from tkinter import filedialog, messagebox
- import ttkbootstrap as ttk
- from ttkbootstrap.constants import *
- import os
- import ebooklib
- from ebooklib import epub
- from bs4 import BeautifulSoup
- import threading
- import re
- from pathlib import Path
- import time
- class EPUBToTXTConverter:
- def __init__(self, root):
- self.root = root
- self.root.title("EPUB转TXT格式转换工具")
- # 设置窗口大小和位置
- window_width = 1285
- window_height = 815
- screen_width = root.winfo_screenwidth()
- screen_height = root.winfo_screenheight()
- center_x = int(screen_width/2 - window_width/2)
- center_y = int(screen_height/2 - window_height/2)
- self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
- # 设置最小窗口大小
- self.root.minsize(700, 600)
- # 变量
- self.input_file = tk.StringVar()
- self.output_dir = tk.StringVar()
- self.status_text = tk.StringVar(value="就绪,等待操作...")
- self.progress_value = tk.DoubleVar(value=0)
- self.file_info = tk.StringVar(value="")
- # 转换设置
- self.encoding_var = tk.StringVar(value="utf-8")
- self.remove_empty_lines = tk.BooleanVar(value=True)
- self.preserve_paragraphs = tk.BooleanVar(value=True)
- self.add_chapter_markers = tk.BooleanVar(value=True)
- self.setup_ui()
- self.setup_styles()
- def setup_styles(self):
- """配置自定义样式"""
- style = ttk.Style()
- # 配置标题样式
- style.configure('Title.TLabel',
- font=('Microsoft YaHei UI', 18, 'bold'),
- foreground=style.colors.primary)
- # 配置副标题样式
- style.configure('Subtitle.TLabel',
- font=('Microsoft YaHei UI', 9),
- foreground=style.colors.secondary)
- # 配置图标按钮样式
- style.configure('Icon.TButton',
- font=('Microsoft YaHei UI', 9))
- # 配置转换按钮样式
- style.configure('Convert.TButton',
- font=('Microsoft YaHei UI', 11, 'bold'),
- padding=(30, 10))
- # 配置状态标签样式
- style.configure('Status.TLabel',
- font=('Microsoft YaHei UI', 9))
- style.configure('Success.TLabel',
- font=('Microsoft YaHei UI', 9),
- foreground=style.colors.success)
- style.configure('Error.TLabel',
- font=('Microsoft YaHei UI', 9),
- foreground=style.colors.danger)
- # 配置Card框架样式
- style.configure('Card.TLabelframe',
- relief='solid',
- borderwidth=1)
- style.configure('Card.TLabelframe.Label',
- font=('Microsoft YaHei UI', 10, 'bold'))
- def setup_ui(self):
- """设置用户界面"""
- # 创建主容器
- main_container = ttk.Frame(self.root, padding=(30, 20))
- main_container.pack(fill=BOTH, expand=YES)
- # 头部区域
- self.create_header(main_container)
- # 分隔线
- separator = ttk.Separator(main_container, bootstyle="primary")
- separator.pack(fill=X, pady=(15, 20))
- # 文件选择区域
- self.create_file_section(main_container)
- # 转换选项区域
- self.create_options_section(main_container)
- # 文件信息显示
- self.create_info_section(main_container)
- # 进度区域
- self.create_progress_section(main_container)
- # 按钮区域
- self.create_button_section(main_container)
- # 底部状态栏
- self.create_status_bar()
- def create_header(self, parent):
- """创建头部区域"""
- header_frame = ttk.Frame(parent)
- header_frame.pack(fill=X, pady=(0, 10))
- # 左侧标题
- left_frame = ttk.Frame(header_frame)
- left_frame.pack(side=LEFT)
- # 图标和标题
- title_container = ttk.Frame(left_frame)
- title_container.pack(fill=X)
- # 使用emoji作为图标
- icon_label = ttk.Label(title_container, text="📚",
- font=('Microsoft YaHei UI', 24))
- icon_label.pack(side=LEFT, padx=(0, 10))
- title_frame = ttk.Frame(title_container)
- title_frame.pack(side=LEFT)
- title_label = ttk.Label(title_frame, text="EPUB 转 TXT 转换器",
- style='Title.TLabel')
- title_label.pack(anchor=W)
- subtitle_label = ttk.Label(title_frame,
- text="轻松将电子书转换为纯文本格式",
- style='Subtitle.TLabel')
- subtitle_label.pack(anchor=W)
- # 右侧主题切换按钮
- theme_frame = ttk.Frame(header_frame)
- theme_frame.pack(side=RIGHT)
- # 主题切换按钮
- self.theme_btn = ttk.Button(theme_frame, text="🌓 切换主题",
- command=self.toggle_theme,
- style='Icon.TButton',
- bootstyle="outline-secondary")
- self.theme_btn.pack(side=RIGHT)
- # 当前主题标识
- self.theme_label = ttk.Label(theme_frame, text="当前: 浅色模式",
- font=('Microsoft YaHei UI', 8),
- foreground='gray')
- self.theme_label.pack(side=RIGHT, padx=(0, 10))
- def create_file_section(self, parent):
- """创建文件选择区域"""
- # 使用LabelFrame,去掉padding参数
- file_card = ttk.Labelframe(parent, text="📁 文件设置",
- bootstyle="primary",
- padding=20)
- file_card.pack(fill=X, pady=(0, 15))
- # 输入文件选择
- input_frame = ttk.Frame(file_card)
- input_frame.pack(fill=X, pady=(0, 15))
- input_label = ttk.Label(input_frame, text="选择EPUB文件:",
- font=('Microsoft YaHei UI', 10, 'bold'))
- input_label.pack(anchor=W, pady=(0, 5))
- input_row = ttk.Frame(input_frame)
- input_row.pack(fill=X)
- self.file_entry = ttk.Entry(input_row, textvariable=self.input_file,
- state='readonly',
- font=('Microsoft YaHei UI', 9))
- self.file_entry.pack(side=LEFT, fill=X, expand=YES, padx=(0, 10))
- # 文件选择按钮带图标
- browse_file_btn = ttk.Button(input_row, text="📂 浏览文件",
- command=self.select_input_file,
- style='Icon.TButton',
- bootstyle="outline-primary")
- browse_file_btn.pack(side=RIGHT)
- # 文件类型提示
- file_hint = ttk.Label(input_frame, text="支持的格式: .epub",
- font=('Microsoft YaHei UI', 8),
- foreground='gray')
- file_hint.pack(anchor=W, pady=(5, 0))
- # 输出目录选择
- output_frame = ttk.Frame(file_card)
- output_frame.pack(fill=X)
- output_label = ttk.Label(output_frame, text="选择保存目录:",
- font=('Microsoft YaHei UI', 10, 'bold'))
- output_label.pack(anchor=W, pady=(0, 5))
- output_row = ttk.Frame(output_frame)
- output_row.pack(fill=X)
- self.dir_entry = ttk.Entry(output_row, textvariable=self.output_dir,
- state='readonly',
- font=('Microsoft YaHei UI', 9))
- self.dir_entry.pack(side=LEFT, fill=X, expand=YES, padx=(0, 10))
- browse_dir_btn = ttk.Button(output_row, text="📁 浏览目录",
- command=self.select_output_dir,
- style='Icon.TButton',
- bootstyle="outline-primary")
- browse_dir_btn.pack(side=RIGHT)
- def create_options_section(self, parent):
- """创建转换选项区域"""
- options_frame = ttk.Labelframe(parent, text="⚙️ 转换选项",
- bootstyle="info",
- padding=20)
- options_frame.pack(fill=X, pady=(0, 15))
- # 第一行选项
- first_row = ttk.Frame(options_frame)
- first_row.pack(fill=X, pady=(0, 10))
- # 编码选择
- encoding_frame = ttk.Frame(first_row)
- encoding_frame.pack(side=LEFT, padx=(0, 30))
- encoding_label = ttk.Label(encoding_frame, text="输出编码:",
- font=('Microsoft YaHei UI', 9))
- encoding_label.pack(side=LEFT, padx=(0, 10))
- encoding_combo = ttk.Combobox(encoding_frame,
- textvariable=self.encoding_var,
- values=["utf-8", "utf-8-sig", "gbk", "gb2312", "utf-16"],
- state="readonly",
- width=12,
- font=('Microsoft YaHei UI', 9),
- bootstyle="primary")
- encoding_combo.pack(side=LEFT)
- # 第二行选项
- second_row = ttk.Frame(options_frame)
- second_row.pack(fill=X)
- # 删除空行选项
- empty_lines_check = ttk.Checkbutton(second_row,
- text="🗑️ 删除多余空行",
- variable=self.remove_empty_lines,
- bootstyle="round-toggle")
- empty_lines_check.pack(side=LEFT, padx=(0, 20))
- # 保留段落格式选项
- paragraphs_check = ttk.Checkbutton(second_row,
- text="📝 保留段落格式",
- variable=self.preserve_paragraphs,
- bootstyle="round-toggle")
- paragraphs_check.pack(side=LEFT, padx=(0, 20))
- # 章节标记选项
- chapters_check = ttk.Checkbutton(second_row,
- text="📑 添加章节标记",
- variable=self.add_chapter_markers,
- bootstyle="round-toggle")
- chapters_check.pack(side=LEFT)
- def create_info_section(self, parent):
- """创建文件信息显示区域"""
- self.info_frame = ttk.Labelframe(parent, text="📊 文件信息",
- bootstyle="secondary",
- padding=15)
- self.info_frame.pack(fill=X, pady=(0, 15))
- # 信息显示标签
- self.info_label = ttk.Label(self.info_frame,
- text="请选择EPUB文件以查看文件信息",
- font=('Microsoft YaHei UI', 9),
- foreground='gray')
- self.info_label.pack(fill=X)
- def create_progress_section(self, parent):
- """创建进度显示区域"""
- self.progress_frame = ttk.Labelframe(parent, text="🔄 转换进度",
- bootstyle="warning",
- padding=15)
- self.progress_frame.pack(fill=X, pady=(0, 15))
- # 进度条
- self.progress_bar = ttk.Progressbar(self.progress_frame,
- variable=self.progress_value,
- mode='determinate',
- bootstyle="success-striped")
- self.progress_bar.pack(fill=X, pady=(0, 10))
- # 进度百分比和状态
- progress_info = ttk.Frame(self.progress_frame)
- progress_info.pack(fill=X)
- self.percentage_label = ttk.Label(progress_info,
- text="0%",
- font=('Microsoft YaHei UI', 12, 'bold'),
- bootstyle="primary")
- self.percentage_label.pack(side=LEFT)
- self.status_label = ttk.Label(progress_info,
- textvariable=self.status_text,
- font=('Microsoft YaHei UI', 9))
- self.status_label.pack(side=RIGHT)
- def create_button_section(self, parent):
- """创建按钮区域"""
- button_frame = ttk.Frame(parent)
- button_frame.pack(fill=X, pady=(10, 0))
- # 左侧按钮
- left_buttons = ttk.Frame(button_frame)
- left_buttons.pack(side=LEFT)
- # 清空按钮
- clear_btn = ttk.Button(left_buttons, text="🔄 清空设置",
- command=self.clear_settings,
- style='Icon.TButton',
- bootstyle="outline-secondary")
- clear_btn.pack(side=LEFT, padx=(0, 10))
- # 右侧按钮
- right_buttons = ttk.Frame(button_frame)
- right_buttons.pack(side=RIGHT)
- # 退出按钮
- exit_btn = ttk.Button(right_buttons, text="❌ 退出程序",
- command=self.root.quit,
- style='Icon.TButton',
- bootstyle="outline-danger")
- exit_btn.pack(side=RIGHT, padx=(10, 0))
- # 转换按钮(居中)
- center_frame = ttk.Frame(button_frame)
- center_frame.pack(side=TOP, expand=YES)
- self.convert_btn = ttk.Button(center_frame, text="🚀 开始转换",
- command=self.start_conversion,
- style='Convert.TButton',
- bootstyle="success")
- self.convert_btn.pack(pady=(0, 15))
- def create_status_bar(self):
- """创建底部状态栏"""
- status_bar = ttk.Frame(self.root, bootstyle="secondary")
- status_bar.pack(side=BOTTOM, fill=X)
- # 左侧状态信息
- status_left = ttk.Label(status_bar,
- text=" 📍 EPUB转换工具 v1.0 | 支持批量处理",
- font=('Microsoft YaHei UI', 8),
- padding=(10, 5))
- status_left.pack(side=LEFT)
- # 右侧状态信息
- status_right = ttk.Label(status_bar,
- text="💡 提示: 支持中文路径和文件名 ",
- font=('Microsoft YaHei UI', 8),
- padding=(10, 5))
- status_right.pack(side=RIGHT)
- def toggle_theme(self):
- """切换主题"""
- current_theme = self.root.style.theme_use()
- if 'dark' in current_theme.lower():
- self.root.style.theme_use('flatly')
- self.theme_label.config(text="当前: 浅色模式")
- else:
- self.root.style.theme_use('darkly')
- self.theme_label.config(text="当前: 深色模式")
- def select_input_file(self):
- """选择EPUB文件"""
- filename = filedialog.askopenfilename(
- title="选择EPUB文件",
- filetypes=[("EPUB files", "*.epub"), ("All files", "*.*")]
- )
- if filename:
- self.input_file.set(filename)
- # 自动设置输出目录
- if not self.output_dir.get():
- self.output_dir.set(os.path.dirname(filename))
- # 显示文件信息
- self.show_file_info(filename)
- def select_output_dir(self):
- """选择输出目录"""
- directory = filedialog.askdirectory(
- title="选择保存目录"
- )
- if directory:
- self.output_dir.set(directory)
- def show_file_info(self, filepath):
- """显示文件信息"""
- try:
- file_size = os.path.getsize(filepath)
- size_str = self.format_file_size(file_size)
- filename = os.path.basename(filepath)
- modified_time = time.strftime('%Y-%m-%d %H:%M:%S',
- time.localtime(os.path.getmtime(filepath)))
- info_text = f"📄 文件名: {filename} | 📦 大小: {size_str} | 🕒 修改时间: {modified_time}"
- self.info_label.config(text=info_text)
- except Exception as e:
- self.info_label.config(text=f"无法读取文件信息: {str(e)}")
- def format_file_size(self, size):
- """格式化文件大小"""
- for unit in ['B', 'KB', 'MB', 'GB']:
- if size < 1024.0:
- return f"{size:.2f} {unit}"
- size /= 1024.0
- return f"{size:.2f} TB"
- def clear_settings(self):
- """清空所有设置"""
- self.input_file.set("")
- self.output_dir.set("")
- self.progress_value.set(0)
- self.percentage_label.config(text="0%")
- self.status_text.set("就绪,等待操作...")
- self.info_label.config(text="请选择EPUB文件以查看文件信息")
- self.progress_bar.configure(bootstyle="success-striped")
- def update_progress(self, value, status=""):
- """更新进度条"""
- self.progress_value.set(value)
- self.percentage_label.config(text=f"{int(value)}%")
- if status:
- self.status_text.set(status)
- self.root.update_idletasks()
- def start_conversion(self):
- """开始转换"""
- if not self.input_file.get():
- messagebox.showerror("错误", "❌ 请选择EPUB文件!")
- return
- if not self.output_dir.get():
- messagebox.showerror("错误", "❌ 请选择保存目录!")
- return
- # 禁用转换按钮
- self.convert_btn.config(state='disabled', text="⏳ 转换中...")
- self.progress_bar.configure(bootstyle="warning-striped")
- # 在单独线程中运行转换
- thread = threading.Thread(target=self.convert_epub_to_txt)
- thread.daemon = True
- thread.start()
- def extract_text_from_epub(self, epub_path):
- """从EPUB文件中提取文本内容"""
- try:
- book = epub.read_epub(epub_path)
- all_text = []
- total_items = len(list(book.get_items()))
- processed = 0
- # 获取所有文档项目
- for item in book.get_items():
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
- # 更新进度
- processed += 1
- progress = (processed / max(total_items, 1)) * 50 # 前50%用于提取
- self.root.after(0, self.update_progress, progress,
- f"正在提取文本内容... ({processed}/{total_items})")
- # 使用BeautifulSoup解析HTML内容
- soup = BeautifulSoup(item.get_content(), 'html.parser')
- # 提取标题(如果启用章节标记)
- if self.add_chapter_markers.get():
- for heading in soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
- heading_text = heading.get_text().strip()
- if heading_text:
- level = int(heading.name[1])
- prefix = '=' * level + ' '
- all_text.append(prefix + heading_text)
- all_text.append('')
- # 提取段落文本
- for paragraph in soup.find_all('p'):
- text = paragraph.get_text().strip()
- if text:
- all_text.append(text)
- if self.preserve_paragraphs.get():
- all_text.append('')
- # 提取其他文本元素
- for element in soup.find_all(['div', 'span', 'li']):
- if not element.find(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
- text = element.get_text().strip()
- if text and len(text) > 20:
- all_text.append(text)
- if self.preserve_paragraphs.get():
- all_text.append('')
- # 更新进度到75%
- self.root.after(0, self.update_progress, 75, "文本提取完成,正在处理格式...")
- return '\n'.join(all_text)
- except Exception as e:
- raise Exception(f"读取EPUB文件失败: {str(e)}")
- def process_text(self, text):
- """处理提取的文本"""
- # 处理空行
- if self.remove_empty_lines.get():
- text = re.sub(r'\n\s*\n', '\n\n', text)
- text = text.strip()
- # 如果不保留段落格式,将所有文本合并
- if not self.preserve_paragraphs.get():
- lines = [line.strip() for line in text.split('\n') if line.strip()]
- text = ' '.join(lines)
- return text
- def convert_epub_to_txt(self):
- """执行EPUB到TXT的转换"""
- try:
- input_path = self.input_file.get()
- output_dir = self.output_dir.get()
- # 重置进度
- self.root.after(0, self.update_progress, 0, "开始转换...")
- time.sleep(0.1)
- # 获取文件名
- base_name = os.path.splitext(os.path.basename(input_path))[0]
- output_path = os.path.join(output_dir, f"{base_name}.txt")
- # 更新进度
- self.root.after(0, self.update_progress, 10, "正在读取EPUB文件...")
- # 提取文本
- text_content = self.extract_text_from_epub(input_path)
- # 处理文本
- self.root.after(0, self.update_progress, 80, "正在处理文本格式...")
- processed_text = self.process_text(text_content)
- # 保存文件
- self.root.after(0, self.update_progress, 90, "正在保存文件...")
- encoding = self.encoding_var.get()
- # 如果选择utf-8-sig,添加BOM以更好地支持中文
- with open(output_path, 'w', encoding=encoding) as f:
- f.write(processed_text)
- # 获取文件大小
- file_size = os.path.getsize(output_path)
- size_str = self.format_file_size(file_size)
- # 完成
- self.root.after(0, self.update_progress, 100, "转换完成!")
- self.root.after(0, self.conversion_complete, output_path, size_str)
- except Exception as e:
- self.root.after(0, self.conversion_failed, str(e))
- def conversion_complete(self, output_path, size_str):
- """转换完成回调"""
- self.convert_btn.config(state='normal', text="🚀 开始转换")
- self.progress_bar.configure(bootstyle="success-striped")
- # 更新状态标签样式
- self.status_label.configure(style='Success.TLabel')
- # 显示成功消息
- filename = os.path.basename(output_path)
- success_msg = (f"✅ 转换成功!\n\n"
- f"📄 输出文件: {filename}\n"
- f"📦 文件大小: {size_str}\n"
- f"📁 保存位置: {os.path.dirname(output_path)}")
- # 询问是否打开文件所在目录
- result = messagebox.askyesno(
- "转换成功",
- f"{success_msg}\n\n是否打开文件所在目录?",
- icon='info'
- )
- if result:
- try:
- os.startfile(os.path.dirname(output_path))
- except:
- import subprocess
- subprocess.run(['xdg-open', os.path.dirname(output_path)])
- def conversion_failed(self, error_message):
- """转换失败回调"""
- self.convert_btn.config(state='normal', text="🚀 开始转换")
- self.progress_bar.configure(bootstyle="danger-striped")
- self.progress_value.set(0)
- self.percentage_label.config(text="0%")
- # 更新状态标签样式
- self.status_label.configure(style='Error.TLabel')
- self.status_text.set("转换失败")
- messagebox.showerror("转换失败",
- f"❌ 转换过程中出现错误:\n\n{error_message}\n\n"
- "请检查EPUB文件是否完整,并重试。")
- def main():
- # 创建主窗口,使用flatly主题
- root = ttk.Window(themename="flatly")
- # 设置窗口图标(如果需要)
- try:
- root.iconbitmap('icon.ico')
- except:
- pass
- # 创建应用
- app = EPUBToTXTConverter(root)
- # 运行应用
- root.mainloop()
- if __name__ == "__main__":
- # 检查依赖库
- try:
- import ebooklib
- from bs4 import BeautifulSoup
- import ttkbootstrap
- except ImportError as e:
- print("缺少必要的依赖库。请运行以下命令安装:")
- print("pip install ebooklib beautifulsoup4 ttkbootstrap")
- print("\n或运行: pip install -r requirements.txt")
- # 尝试使用tkinter显示错误信息
- try:
- import tkinter as tk
- from tkinter import messagebox
- root = tk.Tk()
- root.withdraw()
- messagebox.showerror("依赖缺失",
- "缺少必要的依赖库。\n\n请运行以下命令安装:\n"
- "pip install ebooklib beautifulsoup4 ttkbootstrap\n\n"
- "或运行: pip install -r requirements.txt")
- root.destroy()
- except:
- pass
- else:
- main()





