欢迎光临殡葬白事网
详情描述

1. 安装必要的依赖

首先创建 requirements.txt

markdown==3.5.1
ebooklib==0.18
beautifulsoup4==4.12.2
Pillow==10.1.0
Jinja2==3.1.2
pygments==2.16.1

2. 主要实现代码

创建 md2epub.py

#!/usr/bin/env python3
"""
Markdown转EPUB电子书生成工具
支持:目录生成、图片处理、代码高亮、样式自定义
"""

import os
import re
import argparse
import logging
from pathlib import Path
from typing import List, Dict, Optional, Tuple
import markdown
from ebooklib import epub
from bs4 import BeautifulSoup
from PIL import Image
import base64
import io
import jinja2

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


class MarkdownToEPUB:
    def __init__(self, title: str = "", author: str = "Unknown"):
        """
        初始化EPUB生成器

        Args:
            title: 书籍标题
            author: 作者
        """
        self.title = title
        self.author = author
        self.book = epub.EpubBook()
        self.chapters = []
        self.images = []
        self.css_content = ""
        self.toc = []

    def load_css_template(self, css_file: Optional[str] = None) -> str:
        """
        加载CSS样式模板

        Args:
            css_file: 自定义CSS文件路径

        Returns:
            CSS内容
        """
        if css_file and os.path.exists(css_file):
            with open(css_file, 'r', encoding='utf-8') as f:
                return f.read()

        # 默认样式
        return """
body {
    font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 20px;
    color: #333;
}

h1 {
    color: #2c3e50;
    border-bottom: 2px solid #3498db;
    padding-bottom: 10px;
    margin-top: 30px;
}

h2 {
    color: #34495e;
    margin-top: 25px;
    border-left: 4px solid #3498db;
    padding-left: 10px;
}

h3 {
    color: #4a6572;
    margin-top: 20px;
}

p {
    text-align: justify;
    margin: 15px 0;
    text-indent: 2em;
}

code {
    background-color: #f8f9fa;
    padding: 2px 4px;
    border-radius: 3px;
    font-family: "Consolas", "Monaco", monospace;
    font-size: 0.9em;
    color: #c7254e;
}

pre {
    background-color: #282c34;
    color: #abb2bf;
    padding: 15px;
    border-radius: 5px;
    overflow: auto;
    margin: 20px 0;
    font-family: "Consolas", "Monaco", monospace;
    line-height: 1.5;
}

pre code {
    background-color: transparent;
    padding: 0;
    color: inherit;
    text-indent: 0;
}

blockquote {
    border-left: 4px solid #3498db;
    margin: 20px 0;
    padding: 10px 20px;
    background-color: #f8f9fa;
    color: #555;
}

img {
    max-width: 90%;
    height: auto;
    display: block;
    margin: 20px auto;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

table {
    border-collapse: collapse;
    margin: 20px 0;
    width: 100%;
}

th, td {
    border: 1px solid #ddd;
    padding: 8px 12px;
    text-align: left;
}

th {
    background-color: #f2f2f2;
    font-weight: bold;
}

ul, ol {
    margin: 15px 0;
    padding-left: 30px;
}

li {
    margin: 5px 0;
}

a {
    color: #3498db;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

hr {
    border: none;
    border-top: 1px solid #eee;
    margin: 30px 0;
}

.nav {
    page-break-before: always;
}

.nav h2 {
    text-align: center;
    color: #2c3e50;
}

.nav ul {
    list-style-type: none;
    padding: 0;
}

.nav li {
    margin: 10px 0;
    padding: 5px 0;
    border-bottom: 1px dotted #eee;
}
        """

    def parse_markdown(self, md_content: str) -> Tuple[str, List[Dict]]:
        """
        解析Markdown内容为HTML并提取章节信息

        Args:
            md_content: Markdown内容

        Returns:
            (HTML内容, 章节列表)
        """
        # 配置Markdown扩展
        extensions = [
            'extra',
            'codehilite',  # 代码高亮
            'toc',        # 目录生成
            'tables',     # 表格支持
            'fenced_code', # 代码块
            'admonition',  # 警告框
            'attr_list',   # 属性列表
        ]

        md = markdown.Markdown(extensions=extensions, output_format='html5')
        html_content = md.convert(md_content)

        # 提取章节(基于h1, h2, h3标题)
        chapters = []
        soup = BeautifulSoup(html_content, 'html.parser')

        # 为每个标题添加id以便链接
        for i, header in enumerate(soup.find_all(['h1', 'h2', 'h3'])):
            if not header.get('id'):
                header_id = f"section_{i}"
                header['id'] = header_id

                chapters.append({
                    'level': int(header.name[1]),
                    'title': header.get_text(),
                    'id': header_id
                })

        return str(soup), chapters

    def process_images(self, html_content: str, md_file_path: str) -> Tuple[str, List]:
        """
        处理HTML中的图片,转换为EPUB可用的格式

        Args:
            html_content: HTML内容
            md_file_path: Markdown文件路径

        Returns:
            (处理后的HTML内容, 图片文件列表)
        """
        soup = BeautifulSoup(html_content, 'html.parser')
        images = []
        img_dir = os.path.dirname(os.path.abspath(md_file_path))

        for i, img_tag in enumerate(soup.find_all('img')):
            img_src = img_tag.get('src', '')

            if not img_src:
                continue

            # 处理base64图片
            if img_src.startswith('data:image'):
                try:
                    # 提取base64数据
                    match = re.match(r'data:image/(\w+);base64,(.*)', img_src)
                    if match:
                        img_format, b64_data = match.groups()
                        img_data = base64.b64decode(b64_data)
                        img_filename = f"image_{i}.{img_format}"

                        # 创建图片章节
                        epub_image = epub.EpubItem(
                            uid=f"img_{i}",
                            file_name=f"images/{img_filename}",
                            media_type=f"image/{img_format}",
                            content=img_data
                        )
                        self.book.add_item(epub_image)
                        images.append(epub_image)

                        # 更新img标签src
                        img_tag['src'] = f"images/{img_filename}"
                except Exception as e:
                    logger.error(f"处理base64图片失败: {e}")
                    continue

            # 处理本地文件图片
            elif not img_src.startswith(('http://', 'https://')):
                img_path = os.path.join(img_dir, img_src)
                if os.path.exists(img_path):
                    try:
                        with open(img_path, 'rb') as f:
                            img_data = f.read()

                        # 获取图片格式
                        img = Image.open(io.BytesIO(img_data))
                        img_format = img.format.lower() if img.format else 'png'

                        # 生成唯一文件名
                        img_filename = f"image_{i}.{img_format}"

                        # 创建图片章节
                        epub_image = epub.EpubItem(
                            uid=f"img_{i}",
                            file_name=f"images/{img_filename}",
                            media_type=f"image/{img_format}",
                            content=img_data
                        )
                        self.book.add_item(epub_image)
                        images.append(epub_image)

                        # 更新img标签src
                        img_tag['src'] = f"images/{img_filename}"

                    except Exception as e:
                        logger.error(f"处理本地图片失败 {img_path}: {e}")
                        continue

        return str(soup), images

    def create_chapter(self, title: str, content: str, chapter_num: int) -> epub.EpubHtml:
        """
        创建章节

        Args:
            title: 章节标题
            content: 章节内容
            chapter_num: 章节编号

        Returns:
            EpubHtml对象
        """
        chapter = epub.EpubHtml(
            uid=f'chapter_{chapter_num}',
            title=title,
            file_name=f'chap_{chapter_num:03d}.xhtml',
            lang='zh-CN'
        )

        # 构建完整的HTML内容
        html_template = f"""
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head>
    <meta charset="UTF-8"/>
    <title>{title}</title>
    <link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
    <h1>{title}</h1>
    {content}
</body>
</html>
        """

        chapter.content = html_template.strip()
        return chapter

    def create_navigation(self) -> epub.EpubHtml:
        """
        创建导航页面

        Returns:
            导航页面
        """
        nav_content = """
<h2>目录</h2>
<ul>
"""

        for chapter in self.chapters:
            indent = "    " * (chapter['level'] - 1)
            nav_content += f'{indent}<li><a href="#{chapter["id"]}">{chapter["title"]}</a></li>\n'

        nav_content += "</ul>"

        nav_page = epub.EpubHtml(
            uid='nav',
            title='目录',
            file_name='nav.xhtml'
        )

        nav_page.content = f"""
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8"/>
    <title>目录</title>
    <link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body class="nav">
    {nav_content}
</body>
</html>
        """

        return nav_page

    def convert(self, md_file: str, output_file: str, css_file: Optional[str] = None):
        """
        转换Markdown文件为EPUB

        Args:
            md_file: 输入的Markdown文件路径
            output_file: 输出的EPUB文件路径
            css_file: 自定义CSS文件路径
        """
        try:
            logger.info(f"开始转换: {md_file}")

            # 读取Markdown文件
            with open(md_file, 'r', encoding='utf-8') as f:
                md_content = f.read()

            # 如果没有指定标题,使用文件名
            if not self.title:
                self.title = os.path.splitext(os.path.basename(md_file))[0]

            # 设置书籍元数据
            self.book.set_identifier(f"book_{hash(md_file)}")
            self.book.set_title(self.title)
            self.book.set_language('zh-CN')
            self.book.add_author(self.author)

            # 添加封面(如果存在)
            cover_path = os.path.join(os.path.dirname(md_file), 'cover.jpg')
            if os.path.exists(cover_path):
                with open(cover_path, 'rb') as f:
                    self.book.set_cover("cover.jpg", f.read())

            # 解析Markdown
            html_content, chapters = self.parse_markdown(md_content)
            self.chapters = chapters

            # 处理图片
            html_content, images = self.process_images(html_content, md_file)
            self.images = images

            # 创建主章节
            main_chapter = self.create_chapter(self.title, html_content, 1)
            self.book.add_item(main_chapter)

            # 创建导航页面
            nav_page = self.create_navigation()
            self.book.add_item(nav_page)

            # 添加CSS样式
            self.css_content = self.load_css_template(css_file)
            css_item = epub.EpubItem(
                uid="style",
                file_name="style.css",
                media_type="text/css",
                content=self.css_content
            )
            self.book.add_item(css_item)

            # 设置目录结构
            self.book.toc = [
                epub.Link('nav.xhtml', '目录', 'nav'),
                (epub.Section(self.title), [main_chapter])
            ]

            # 添加导航
            self.book.add_item(epub.EpubNcx())
            self.book.add_item(epub.EpubNav())

            # 设置阅读顺序
            self.book.spine = ['nav', main_chapter]

            # 写入EPUB文件
            epub.write_epub(output_file, self.book, {})
            logger.info(f"转换完成: {output_file}")

        except Exception as e:
            logger.error(f"转换失败: {e}")
            raise

    def convert_multiple(self, md_files: List[str], output_file: str, css_file: Optional[str] = None):
        """
        转换多个Markdown文件为一个EPUB

        Args:
            md_files: Markdown文件列表
            output_file: 输出EPUB文件路径
            css_file: 自定义CSS文件路径
        """
        try:
            logger.info(f"开始合并转换 {len(md_files)} 个文件")

            # 设置书籍元数据
            self.book.set_identifier(f"book_{hash(str(md_files))}")
            self.book.set_title(self.title or "合并电子书")
            self.book.set_language('zh-CN')
            self.book.add_author(self.author)

            all_chapters = []
            epub_chapters = []

            # 处理每个Markdown文件
            for i, md_file in enumerate(md_files):
                logger.info(f"处理文件 {i+1}/{len(md_files)}: {md_file}")

                with open(md_file, 'r', encoding='utf-8') as f:
                    md_content = f.read()

                # 解析Markdown
                html_content, chapters = self.parse_markdown(md_content)

                # 处理图片
                html_content, images = self.process_images(html_content, md_file)
                self.images.extend(images)

                # 获取章节标题(使用文件第一个h1标题或文件名)
                soup = BeautifulSoup(html_content, 'html.parser')
                h1 = soup.find('h1')
                chapter_title = h1.get_text() if h1 else os.path.splitext(os.path.basename(md_file))[0]

                # 创建章节
                chapter = self.create_chapter(chapter_title, html_content, i+1)
                self.book.add_item(chapter)
                epub_chapters.append(chapter)

                # 收集所有章节信息
                for chap in chapters:
                    chap['file'] = f'chap_{i+1:03d}.xhtml'
                    all_chapters.append(chap)

            self.chapters = all_chapters

            # 创建导航页面
            nav_page = self.create_navigation()
            self.book.add_item(nav_page)

            # 添加CSS样式
            self.css_content = self.load_css_template(css_file)
            css_item = epub.EpubItem(
                uid="style",
                file_name="style.css",
                media_type="text/css",
                content=self.css_content
            )
            self.book.add_item(css_item)

            # 设置目录结构
            toc_items = [epub.Link('nav.xhtml', '目录', 'nav')]
            for chapter in epub_chapters:
                toc_items.append(chapter)

            self.book.toc = toc_items

            # 添加导航
            self.book.add_item(epub.EpubNcx())
            self.book.add_item(epub.EpubNav())

            # 设置阅读顺序
            spine_items = ['nav'] + epub_chapters
            self.book.spine = spine_items

            # 写入EPUB文件
            epub.write_epub(output_file, self.book, {})
            logger.info(f"合并转换完成: {output_file}")

        except Exception as e:
            logger.error(f"合并转换失败: {e}")
            raise


def main():
    """命令行入口函数"""
    parser = argparse.ArgumentParser(description='Markdown转EPUB电子书生成工具')
    parser.add_argument('input', help='输入的Markdown文件或目录')
    parser.add_argument('-o', '--output', help='输出的EPUB文件路径', default='output.epub')
    parser.add_argument('-t', '--title', help='书籍标题', default='')
    parser.add_argument('-a', '--author', help='作者', default='Unknown')
    parser.add_argument('-c', '--css', help='自定义CSS文件路径')
    parser.add_argument('-r', '--recursive', action='store_true', 
                       help='递归处理目录下的所有Markdown文件')

    args = parser.parse_args()

    # 创建转换器
    converter = MarkdownToEPUB(title=args.title, author=args.author)

    try:
        if os.path.isdir(args.input):
            # 处理目录
            md_files = []
            if args.recursive:
                # 递归查找所有md文件
                for root, dirs, files in os.walk(args.input):
                    for file in files:
                        if file.lower().endswith(('.md', '.markdown')):
                            md_files.append(os.path.join(root, file))
            else:
                # 只处理当前目录
                for file in os.listdir(args.input):
                    if file.lower().endswith(('.md', '.markdown')):
                        md_files.append(os.path.join(args.input, file))

            if not md_files:
                logger.error("目录中没有找到Markdown文件")
                return

            # 按文件名排序
            md_files.sort()

            if len(md_files) == 1:
                # 单个文件
                converter.convert(md_files[0], args.output, args.css)
            else:
                # 多个文件合并
                converter.convert_multiple(md_files, args.output, args.css)

        else:
            # 处理单个文件
            if not os.path.exists(args.input):
                logger.error(f"文件不存在: {args.input}")
                return

            converter.convert(args.input, args.output, args.css)

        logger.info(f"成功生成EPUB文件: {args.output}")

    except Exception as e:
        logger.error(f"处理失败: {e}")
        exit(1)


if __name__ == "__main__":
    main()

3. 高级功能扩展(可选)

创建 md2epub_advanced.py 添加更多高级功能:

#!/usr/bin/env python3
"""
高级Markdown转EPUB工具
添加:元数据处理、章节分割、模板系统等
"""

import yaml
import frontmatter
from datetime import datetime
from typing import List, Dict, Any
from md2epub import MarkdownToEPUB


class AdvancedMarkdownToEPUB(MarkdownToEPUB):
    """高级EPUB生成器,支持更多功能"""

    def __init__(self, title: str = "", author: str = "Unknown"):
        super().__init__(title, author)
        self.metadata = {}
        self.custom_template = None

    def extract_metadata(self, md_content: str) -> Dict[str, Any]:
        """
        从Markdown中提取元数据(支持YAML frontmatter)

        Args:
            md_content: Markdown内容

        Returns:
            元数据字典
        """
        try:
            # 使用frontmatter库解析
            post = frontmatter.loads(md_content)
            metadata = post.metadata

            # 设置默认元数据
            if not self.title and 'title' in metadata:
                self.title = metadata['title']
            if self.author == "Unknown" and 'author' in metadata:
                self.author = metadata['author']

            return metadata

        except Exception as e:
            logger.warning(f"解析元数据失败: {e}")
            return {}

    def split_by_heading(self, md_content: str, split_level: int = 1) -> List[Dict[str, str]]:
        """
        根据标题分割Markdown内容

        Args:
            md_content: Markdown内容
            split_level: 分割级别(1=h1, 2=h2等)

        Returns:
            分割后的章节列表
        """
        chapters = []
        current_chapter = {"title": "", "content": ""}
        lines = md_content.split('\n')

        for line in lines:
            # 检查是否是标题行
            if line.startswith('#' * split_level + ' '):
                # 保存前一个章节
                if current_chapter["content"]:
                    chapters.append(current_chapter.copy())

                # 开始新章节
                current_chapter = {
                    "title": line.strip('# '),
                    "content": line + '\n'
                }
            else:
                current_chapter["content"] += line + '\n'

        # 添加最后一个章节
        if current_chapter["content"]:
            chapters.append(current_chapter)

        return chapters

    def add_custom_template(self, template_file: str, context: Dict[str, Any] = None):
        """
        添加自定义模板

        Args:
            template_file: 模板文件路径
            context: 模板上下文数据
        """
        if os.path.exists(template_file):
            with open(template_file, 'r', encoding='utf-8') as f:
                template_content = f.read()

            # 使用Jinja2渲染模板
            template = jinja2.Template(template_content)
            context = context or {}
            context.update({
                'title': self.title,
                'author': self.author,
                'date': datetime.now().strftime('%Y-%m-%d')
            })

            self.custom_template = template.render(context)

    def generate_advanced(self, md_file: str, output_file: str, **kwargs):
        """
        高级生成方法

        Args:
            md_file: Markdown文件路径
            output_file: 输出文件路径
            **kwargs: 其他参数
        """
        # 读取文件
        with open(md_file, 'r', encoding='utf-8') as f:
            md_content = f.read()

        # 提取元数据
        self.metadata = self.extract_metadata(md_content)

        # 应用模板
        if self.custom_template:
            # 这里可以整合模板到生成过程
            pass

        # 调用父类转换方法
        super().convert(md_file, output_file, kwargs.get('css_file'))


def batch_convert(input_dir: str, output_dir: str, **kwargs):
    """
    批量转换目录中的所有Markdown文件

    Args:
        input_dir: 输入目录
        output_dir: 输出目录
        **kwargs: 其他参数
    """
    os.makedirs(output_dir, exist_ok=True)

    for file in os.listdir(input_dir):
        if file.lower().endswith(('.md', '.markdown')):
            input_file = os.path.join(input_dir, file)
            output_file = os.path.join(output_dir, 
                                      os.path.splitext(file)[0] + '.epub')

            try:
                converter = AdvancedMarkdownToEPUB()
                converter.generate_advanced(input_file, output_file, **kwargs)
                logger.info(f"已转换: {file}")
            except Exception as e:
                logger.error(f"转换失败 {file}: {e}")


if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1:
        input_path = sys.argv[1]
        output_path = sys.argv[2] if len(sys.argv) > 2 else 'output.epub'

        converter = AdvancedMarkdownToEPUB()
        converter.generate_advanced(input_path, output_path)
    else:
        print("使用方法: python md2epub_advanced.py <input.md> [output.epub]")

4. 使用示例

创建示例Markdown文件 example.md

# Python编程入门

作者:Python开发者  
日期:2023-12-01

## 第一章:Python基础

Python是一种高级编程语言,以其简洁明了的语法而闻名。

### 1.1 变量和数据类型

```python
# 定义变量
name = "Python"
version = 3.11

# 打印输出
print(f"Hello {name} {version}!")

# 列表操作
numbers = [1, 2, 3, 4, 5]
squares = [n**2 for n in numbers]
print(squares)

1.2 控制结构

Python支持常见的控制结构:

  • 条件判断
  • 循环结构
  • 异常处理

注意:Python使用缩进来表示代码块结构。

第二章:函数和模块

函数是组织代码的基本单元。

def calculate_average(numbers):
    """计算平均数"""
    if not numbers:
        return 0
    return sum(numbers) / len(numbers)

# 使用函数
scores = [85, 90, 78, 92, 88]
avg_score = calculate_average(scores)
print(f"平均分: {avg_score:.2f}")

表格示例

功能 描述 示例
列表推导 快速创建列表 [x**2 for x in range(10)]
字典推导 快速创建字典 {x: x**2 for x in range(5)}
生成器 惰性计算序列 (x**2 for x in range(10))

总结

Python是一种功能强大且易于学习的编程语言。


## 5. 使用说明

### 基本使用

```bash
# 安装依赖
pip install -r requirements.txt

# 单个文件转换
python md2epub.py example.md -o example.epub -t "Python教程" -a "张三"

# 使用自定义样式
python md2epub.py example.md -o example.epub -c custom.css

# 转换整个目录
python md2epub.py ./docs -o book.epub

# 递归转换目录
python md2epub.py ./docs -o book.epub -r

高级功能

# 使用高级版本
python md2epub_advanced.py example.md -o advanced.epub

# 批量转换
python -c "from md2epub_advanced import batch_convert; batch_convert('./docs', './output')"

6. 项目结构

markdown-epub-converter/
├── md2epub.py           # 主转换脚本
├── md2epub_advanced.py  # 高级功能扩展
├── requirements.txt     # 依赖文件
├── example.md           # 示例Markdown文件
├── custom.css          # 自定义CSS样式(可选)
└── README.md          # 说明文档

7. 功能特点

完整的EPUB支持

  • 符合EPUB 3.0标准
  • 完整的目录导航
  • 支持图片嵌入
  • 元数据设置

Markdown扩展支持

  • 代码高亮(支持Pygments)
  • 表格支持
  • 任务列表
  • 脚注
  • 定义列表

图片处理

  • 自动处理本地图片
  • 支持Base64图片
  • 图片压缩和格式转换

自定义样式

  • 默认精美CSS样式
  • 支持自定义CSS
  • 响应式设计

批量处理

  • 单文件转换
  • 多文件合并
  • 目录递归处理

这个工具提供了完整的Markdown到EPUB的转换功能,支持各种自定义选项,可以生成高质量的电子书文件。你可以根据自己的需求进一步扩展功能。