news 2026/9/2 7:09:08

将电子书文本转换为盲文格式,生成可打印的盲文文档,供视障用户阅读。

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
将电子书文本转换为盲文格式,生成可打印的盲文文档,供视障用户阅读。

电子书转盲文转换器

一、实际应用场景与痛点

应用场景

视障学生小李需要阅读教材和课外书籍。虽然市面上有少量盲文书籍,但种类有限、价格昂贵、更新缓慢。当前的数字阅读器如读屏软件虽然能朗读文本,但无法替代盲文的触觉阅读体验。盲文具有独特的优势:帮助视障人士学习正确的拼写、理解文本结构、进行深度学习。小李急需一款能将普通电子书转换为盲文格式的工具,以便通过盲文点字显示器阅读或打印成盲文书籍。

核心痛点

1. 盲文资源匮乏:市面盲文书籍种类少,更新慢

2. 转换工具缺失:缺乏高效的中文盲文转换工具

3. 格式兼容性差:不同电子书格式转换困难

4. 盲文印刷昂贵:专用盲文打印机价格高昂

5. 转换准确性低:自动化转换容易出错

6. 排版复杂:盲文特殊的排版规则难以处理

7. 多语言支持:中英文混合文本处理困难

二、核心逻辑设计

1. 输入电子书文件(txt, epub, pdf等)

2. 提取和清理文本内容

3. 中文文本转拼音(汉语盲文基于拼音)

4. 拼音转盲文符号(按盲文规则)

5. 应用盲文缩写和缩略规则

6. 自动分段和分页处理

7. 生成盲文格式文档

8. 支持多种输出格式(文本、PDF、BRF)

9. 提供打印模板和预览

三、模块化代码实现

主程序文件:ebook_to_braille_converter.py

#!/usr/bin/env python3

# -*- coding: utf-8 -*-

"""

电子书转盲文转换器

将电子书转换为盲文格式,生成可打印的盲文文档

版本:3.0.0

作者:无障碍智能助手

"""

import os

import sys

import re

import json

import zipfile

import xml.etree.ElementTree as ET

from typing import Dict, List, Tuple, Optional, Any, Set

from dataclasses import dataclass, asdict, field

from enum import Enum

import warnings

warnings.filterwarnings('ignore')

# 文本处理

try:

import jieba

import pypinyin

from pypinyin import pinyin, Style

JIEBA_AVAILABLE = True

PYPINYIN_AVAILABLE = True

except ImportError:

JIEBA_AVAILABLE = False

PYPINYIN_AVAILABLE = False

print("警告: 中文处理库未安装")

# PDF处理

try:

import PyPDF2

PDF_AVAILABLE = True

except ImportError:

PDF_AVAILABLE = False

print("警告: PDF处理库未安装")

# 文档生成

try:

from reportlab.lib.pagesizes import A4, letter

from reportlab.lib.units import mm, inch

from reportlab.pdfgen import canvas

from reportlab.pdfbase import pdfmetrics

from reportlab.pdfbase.ttfonts import TTFont

REPORTLAB_AVAILABLE = True

except ImportError:

REPORTLAB_AVAILABLE = False

print("警告: PDF生成库未安装")

class BrailleSystem(Enum):

"""盲文体系枚举"""

CHINESE_MANDARIN = "chinese_mandarin" # 中文普通话盲文

ENGLISH_UEB = "english_ueb" # 英文统一盲文

NUMERIC = "numeric" # 数字盲文

MUSIC = "music" # 音乐盲文

MATH = "math" # 数学盲文

class BrailleCell:

"""盲文单元格(6点或8点)"""

def __init__(self, dots: List[int] = None, is_6dot: bool = True):

"""

初始化盲文单元格

Args:

dots: 点的列表,如[1,3,5]表示第1、3、5点凸起

is_6dot: 是否为6点盲文(True为6点,False为8点)

"""

self.is_6dot = is_6dot

self.max_dots = 6 if is_6dot else 8

if dots is None:

self.dots = []

else:

# 验证并排序点

self.dots = sorted([d for d in dots if 1 <= d <= self.max_dots])

def __str__(self) -> str:

"""字符串表示"""

if not self.dots:

return "⠀" # 空盲文单元格

# 转换为Unicode盲文字符

return self.to_unicode()

def to_unicode(self) -> str:

"""转换为Unicode字符"""

if not self.dots:

# 空单元格

return "⠀" if self.is_6dot else "⣀"

# 计算Unicode码点

# 盲文Unicode范围:U+2800 - U+28FF

base = 0x2800

if self.is_6dot:

# 6点盲文

dot_map = {1: 0x01, 2: 0x02, 3: 0x04, 4: 0x08, 5: 0x10, 6: 0x20}

else:

# 8点盲文

dot_map = {1: 0x01, 2: 0x02, 3: 0x04, 4: 0x08,

5: 0x10, 6: 0x20, 7: 0x40, 8: 0x80}

code = base

for dot in self.dots:

code += dot_map.get(dot, 0)

return chr(code)

def to_ascii(self) -> str:

"""转换为ASCII表示(如134表示1,3,4点凸起)"""

if not self.dots:

return "0"

return ''.join(str(d) for d in self.dots)

def to_binary(self) -> str:

"""转换为二进制表示"""

binary = ['0'] * self.max_dots

for dot in self.dots:

if 1 <= dot <= self.max_dots:

binary[dot-1] = '1'

return ''.join(binary)

@classmethod

def from_binary(cls, binary_str: str, is_6dot: bool = True) -> 'BrailleCell':

"""从二进制字符串创建"""

max_dots = 6 if is_6dot else 8

if len(binary_str) != max_dots:

raise ValueError(f"二进制字符串长度必须为{max_dots}")

dots = []

for i, bit in enumerate(binary_str):

if bit == '1':

dots.append(i + 1)

return cls(dots, is_6dot)

@classmethod

def from_unicode(cls, char: str) -> 'BrailleCell':

"""从Unicode字符创建"""

code = ord(char)

if 0x2800 <= code <= 0x28FF:

# 盲文Unicode字符

dot_value = code - 0x2800

# 判断是6点还是8点

is_6dot = dot_value <= 0x3F # 6点盲文范围:0x2800-0x283F

if is_6dot:

dots = []

dot_map = {0x01: 1, 0x02: 2, 0x04: 3, 0x08: 4, 0x10: 5, 0x20: 6}

else:

dots = []

dot_map = {0x01: 1, 0x02: 2, 0x04: 3, 0x08: 4,

0x10: 5, 0x20: 6, 0x40: 7, 0x80: 8}

for value, dot_num in dot_map.items():

if dot_value & value:

dots.append(dot_num)

return cls(dots, is_6dot)

raise ValueError(f"不是有效的盲文Unicode字符: {char}")

def __eq__(self, other) -> bool:

"""相等比较"""

if not isinstance(other, BrailleCell):

return False

return self.dots == other.dots and self.is_6dot == other.is_6dot

@dataclass

class BrailleCharacter:

"""盲文字符(可能由多个单元格组成)"""

cells: List[BrailleCell]

original_char: str

description: str = ""

def __str__(self) -> str:

"""字符串表示"""

return ''.join(str(cell) for cell in self.cells)

def to_ascii(self) -> str:

"""转换为ASCII表示"""

return '-'.join(cell.to_ascii() for cell in self.cells)

def is_contracted(self) -> bool:

"""是否为缩写形式"""

return len(self.cells) > 1

class BrailleTable:

"""盲文对照表基类"""

def __init__(self, system: BrailleSystem):

self.system = system

self.table = {}

self.contractions = {} # 缩写表

self.init_table()

def init_table(self):

"""初始化对照表(子类实现)"""

pass

def get_braille(self, char: str) -> Optional[BrailleCharacter]:

"""获取字符的盲文表示"""

return self.table.get(char)

def get_contraction(self, word: str) -> Optional[BrailleCharacter]:

"""获取单词的缩写形式"""

return self.contractions.get(word.lower())

class ChineseBrailleTable(BrailleTable):

"""中文盲文对照表"""

def __init__(self):

super().__init__(BrailleSystem.CHINESE_MANDARIN)

# 加载配置文件

self.load_config()

def load_config(self):

"""加载配置文件"""

config_path = "config/chinese_braille.json"

default_config = self.get_default_config()

try:

if os.path.exists(config_path):

with open(config_path, 'r', encoding='utf-8') as f:

config = json.load(f)

else:

config = default_config

# 保存默认配置

os.makedirs(os.path.dirname(config_path), exist_ok=True)

with open(config_path, 'w', encoding='utf-8') as f:

json.dump(config, f, indent=2, ensure_ascii=False)

self.load_from_config(config)

except Exception as e:

print(f"加载配置文件失败,使用默认配置: {e}")

self.load_from_config(default_config)

def get_default_config(self) -> Dict:

"""获取默认配置"""

return {

"system": "chinese_mandarin",

"description": "中文普通话盲文(现行盲文)",

"tone_marks": True,

"use_contractions": True,

"characters": {

# 声母

"b": [[1]],

"p": [[1,2,3,4]],

"m": [[1,3,4]],

"f": [[1,2,4]],

"d": [[1,4,5]],

"t": [[2,3,4,5]],

"n": [[1,3,4,5]],

"l": [[1,2,3]],

"g": [[1,2,4,5]],

"k": [[1,3]],

"h": [[1,2,5]],

"j": [[2,4,5]],

"q": [[1,2,3,4,5]],

"x": [[1,3,4,5]],

"zh": [[1,3,5,6]],

"ch": [[1,6]],

"sh": [[1,4,6]],

"r": [[2,4,5,6]],

"z": [[1,3,5]],

"c": [[1,3,4,6]],

"s": [[2,3,4]],

"y": [[1,3,4,5,6]],

"w": [[2,4,5,6]],

# 韵母

"a": [[3,5]],

"o": [[1,3,5]],

"e": [[2,6]],

"i": [[2,4]],

"u": [[1,3,6]],

"v": [[1,2,4,5,6]],

"ai": [[1,6]],

"ei": [[2,3,4,6]],

"ao": [[3,5,6]],

"ou": [[2,3,5,6]],

"an": [[3,6]],

"en": [[2,6], [3,6]],

"ang": [[1,3,5,6]],

"eng": [[2,6], [2,3,4,6]],

"er": [[2,3,4,5,6]],

"i_": [[2,4]], # 单独i

"ia": [[2,4], [3,5]],

"iao": [[2,4], [3,5,6]],

"ie": [[2,4], [2,6]],

"iu": [[2,4], [1,3,6]],

"ian": [[2,4], [3,6]],

"in": [[2,4], [2,3,4,6]],

"iang": [[2,4], [1,3,5,6]],

"ing": [[2,4], [2,6], [2,3,4,6]],

"u_": [[1,3,6]], # 单独u

"ua": [[1,3,6], [3,5]],

"uo": [[1,3,6], [1,3,5]],

"uai": [[1,3,6], [1,6]],

"ui": [[1,3,6], [2,4]],

"uan": [[1,3,6], [3,6]],

"un": [[1,3,6], [2,3,4,6]],

"uang": [[1,3,6], [1,3,5,6]],

"ong": [[1,3,6], [2,3,5,6]],

"v_": [[1,2,4,5,6]], # 单独ü

"ve": [[1,2,4,5,6], [2,6]],

"van": [[1,2,4,5,6], [3,6]],

"vn": [[1,2,4,5,6], [2,3,4,6]],

# 声调

"1": [[3,4,5]], # 阴平

"2": [[3,4]], # 阳平

"3": [[3,5,6]], # 上声

"4": [[3,4,5,6]], # 去声

"5": [[3,4,6]], # 轻声

# 数字

"0": [[3,5,6]],

"1": [[1]],

"2": [[1,2]],

"3": [[1,4]],

"4": [[1,4,5]],

"5": [[1,5]],

"6": [[1,2,4]],

"7": [[1,2,4,5]],

"8": [[1,2,5]],

"9": [[2,4]],

# 标点符号

",": [[2]], # 逗号

"。": [[2,5,6]], # 句号

"!": [[2,3,5]], # 感叹号

"?": [[2,3,6]], # 问号

";": [[2,3]], # 分号

":": [[2,5]], # 冒号

"、": [[2,6]], # 顿号

"「": [[2,3,5,6]], # 左引号

"」": [[3,5,6]], # 右引号

"(": [[2,3,5,6]], # 左括号

")": [[3,5,6]], # 右括号

"《": [[2,3,5,6]], # 左书名号

"》": [[3,5,6]], # 右书名号

"—": [[3,6]], # 破折号

"…": [[2,3,6]], # 省略号

"·": [[3]], # 间隔号

# 特殊符号

" ": [[0]], # 空格

"\n": [], # 换行

"\t": [[0,0,0,0]], # 制表符

},

"contractions": {

"的": [[1,4,5,6]], # 的

"了": [[1,2,3,5,6]], # 了

"是": [[2,3,4,6]], # 是

"不": [[1,2]], # 不

"在": [[1,2,6]], # 在

"有": [[1,2,4,6]], # 有

"和": [[1,2,3,4,6]], # 和

"这": [[1,4,5,6], [3,5]], # 这

"个": [[1,2,3,4,5]], # 个

"我": [[2,4,6]], # 我

"们": [[1,2,3,4,5,6]], # 们

}

}

def load_from_config(self, config: Dict):

"""从配置加载"""

characters = config.get("characters", {})

contractions = config.get("contractions", {})

# 加载字符表

for char, dots_list in characters.items():

cells = []

for dots in dots_list:

if dots == 0 or dots == [0]:

cells.append(BrailleCell([]))

else:

cells.append(BrailleCell(dots))

self.table[char] = BrailleCharacter(cells, char)

# 加载缩写表

for word, dots_list in contractions.items():

cells = []

for dots in dots_list:

cells.append(BrailleCell(dots))

self.contractions[word] = BrailleCharacter(cells, word, f"缩写: {word}")

def get_braille_for_pinyin(self, pinyin_str: str, tone: int = 0) -> BrailleCharacter:

"""

获取拼音的盲文表示

Args:

pinyin_str: 拼音字符串

tone: 声调(1-5,0表示无调)

Returns:

盲文字符

"""

# 标准化拼音

pinyin_str = pinyin_str.lower()

# 特殊处理ü

pinyin_str = pinyin_str.replace('ü', 'v')

# 分割声母和韵母

initials = ['b', 'p', 'm', 'f', 'd', 't', 'n', 'l', 'g', 'k', 'h',

'j', 'q', 'x', 'zh', 'ch', 'sh', 'r', 'z', 'c', 's', 'y', 'w']

# 查找最长匹配的声母

initial = ""

remainder = pinyin_str

for init in sorted(initials, key=len, reverse=True):

if pinyin_str.startswith(init):

initial = init

remainder = pinyin_str[len(init):]

break

# 获取声母盲文

cells = []

if initial and initial in self.table:

cells.append(self.table[initial].cells[0])

elif initial: # 如果没有单独的声母,可能需要特殊处理

# 尝试分解

pass

# 获取韵母盲文

if remainder in self.table:

cells.append(self.table[remainder].cells[0])

elif remainder: # 尝试匹配部分

for i in range(len(remainder), 0, -1):

part = remainder[:i]

if part in self.table:

cells.append(self.table[part].cells[0])

remainder = remainder[i:]

break

# 添加声调

if tone > 0 and str(tone) in self.table:

cells.append(self.table[str(tone)].cells[0])

if not cells:

# 如果没有找到匹配,返回空单元格

cells.append(BrailleCell())

return BrailleCharacter(cells, pinyin_str, f"拼音: {pinyin_str} 声调: {tone}")

class EnglishBrailleTable(BrailleTable):

"""英文盲文对照表(UEB)"""

def __init__(self):

super().__init__(BrailleSystem.ENGLISH_UEB)

self.init_table()

def init_table(self):

"""初始化英文盲文表"""

# 基本字母

letters = {

'a': [1],

'b': [1,2],

'c': [1,4],

'd': [1,4,5],

'e': [1,5],

'f': [1,2,4],

'g': [1,2,4,5],

'h': [1,2,5],

'i': [2,4],

'j': [2,4,5],

'k': [1,3],

'l': [1,2,3],

'm': [1,3,4],

'n': [1,3,4,5],

'o': [1,3,5],

'p': [1,2,3,4],

'q': [1,2,3,4,5],

'r': [1,2,3,5],

's': [2,3,4],

't': [2,3,4,5],

'u': [1,3,6],

'v': [1,2,3,6],

'w': [2,4,5,6],

'x': [1,3,4,6],

'y': [1,3,4,5,6],

'z': [1,3,5,6],

}

for char, dots in letters.items():

upper_char = char.upper()

self.table[char] = BrailleCharacter([BrailleCell(dots)], char)

self.table[upper_char] = BrailleCharacter([BrailleCell([6]), BrailleCell(dots)], upper_char)

# 数字前缀

self.table['#'] = BrailleCharacter([BrailleCell([3,4,5,6])], '#')

# 标点符号

punctuation = {

'.': [2,5,6],

',': [2],

'?': [2,3,6],

'!': [2,3,5],

';': [2,3],

':': [2,5],

'"': [2,3,5,6],

"'": [3],

'(': [2,3,5,6],

')': [3,5,6],

'[': [2,3,5,6],

']': [3,5,6],

'{': [2,3,5,6],

'}': [3,5,6],

'-': [3,6],

'_': [3,6,3,6], # 下划线

}

for char, dots in punctuation.items():

self.table[char] = BrailleCharacter([BrailleCell(dots)], char)

# 常用缩写

contractions = {

'the': [2,3,4,6],

'and': [1,2,3,4,6],

'for': [1,2,3,4,5,6],

'with': [2,3,4,5,6],

'ing': [3,4,6],

'ed': [1,2,4,5,6],

'sh': [1,4,6],

'th': [1,4,5,6],

'wh': [1,5,6],

'ou': [1,2,5,6],

'st': [3,4],

'ar': [3,4,5],

'er': [1,2,4,5,6],

'gh': [1,2,6],

'ow': [2,4,6],

}

for word, dots in contractions.items():

self.contractions[word] = BrailleCharacter([BrailleCell(dots)], word, f"缩写: {word}")

class TextExtractor:

"""文本提取器(支持多种格式)"""

def __init__(self, config: Dict):

"""

初始化文本提取器

Args:

config: 提取器配置

"""

self.config = config

def extract_text(self, filepath: str) -> Tuple[str, Dict]:

"""

提取文本内容

Args:

filepath: 文件路径

Returns:

(文本内容, 元数据)

"""

if not os.path.exists(filepath):

raise FileNotFoundError(f"文件不存在: {filepath}")

ext = os.path.splitext(filepath)[1].lower()

if ext == '.txt':

return self.extract_txt(filepath)

elif ext == '.pdf':

return self.extract_pdf(filepath)

elif ext == '.epub':

return self.extract_epub(filepath)

elif ext in ['.doc', '.docx']:

return self.extract_doc(filepath)

elif ext in ['.html', '.htm']:

return self.extract_html(filepath)

else:

raise ValueError(f"不支持的格式: {ext}")

def extract_txt(self, filepath: str) -> Tuple[str, Dict]:

"""提取纯文本"""

try:

with open(filepath, 'r', encoding='utf-8') as f:

content = f.read()

metadata = {

'format': 'txt',

'encoding': 'utf-8',

'size': len(content),

'chars': len(content),

'lines': content.count('\n') + 1

}

return content, metadata

except UnicodeDecodeError:

# 尝试其他编码

encodings = ['gbk', 'gb2312', 'big5', 'latin-1']

for encoding in encodings:

try:

with open(filepath, 'r', encoding=encoding) as f:

content = f.read()

metadata = {

'format': 'txt',

'encoding': encoding,

'size': len(content),

'chars': len(content),

'lines': content.count('\n') + 1

}

return content, metadata

except:

continue

raise ValueError("无法解码文本文件")

def extract_pdf(self, filepath: str) -> Tuple[str, Dict]:

"""提取PDF文本"""

if not PDF_AVAILABLE:

raise ImportError("PyPDF2未安装,无法处理PDF文件")

try:

text = ""

metadata = {}

with open(filepath, 'rb') as f:

pdf_reader = PyPDF2.PdfReader(f)

# 提取元数据

if pdf_reader.metadata:

metadata = {

'title': pdf_reader.metadata.get('/Title', ''),

'author': pdf_reader.metadata.get('/

如果你觉得这个工具好用,欢迎关注我!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 4:13:48

AI城市管理综合执法系统:让城市治理有“智”更有“度”

传统城管执法常陷“人海战术”困境&#xff1a;人工巡查效率低、夜间漏检多、跨部门协同慢。而AI城市管理综合执法系统&#xff0c;用“技术慧眼智能大脑”重构治理逻辑&#xff0c;把被动响应变成主动预判&#xff0c;让执法既精准又有温度&#xff0c;这背后是多重技术的协同…

作者头像 李华
网站建设 2026/9/2 2:37:03

深度学习计算机毕设之基于python深度学习的餐桌美食识别卷神经网络

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/8/30 5:57:09

强脑科技的核心硬件模组为何选择蓝思量产?

&#x1f4cc; 目录&#x1f527; 98%良品率碾压同行&#xff01;蓝思科技拿下强脑科技核心订单&#xff1a;十年磨一剑的精密制造“变态级”秘籍一、碾压级优势&#xff1a;98%良品率背后&#xff0c;是“灰尘都要登记身份证”的严苛标准&#xff08;一&#xff09;十年合作沉…

作者头像 李华
网站建设 2026/9/1 1:56:04

走进自动洗车房时你可能不知道,背后有套PLC200在掌控全局。今天咱们拆解这个自动洗车系统的控制逻辑,手把手看明白那些梯形图里的“小心机

PLC200控制的自动洗车系统自动洗车电气控制系统带解释的梯形图接线图原理图图纸&#xff0c;io分配&#xff0c;组态画面先说硬件布局&#xff0c;洗车机核心就三件套&#xff1a;红外车辆检测、喷淋电机组、旋转刷系统。IO分配表可以这么记&#xff1a;输入点X0-X3对应启动/急…

作者头像 李华
网站建设 2026/8/29 3:30:43

Z-Image-Edit支持语义分割指导编辑吗?未来方向

Z-Image-Edit 支持语义分割指导编辑吗&#xff1f;未来方向 在当前AI图像生成技术飞速发展的背景下&#xff0c;用户早已不再满足于“生成一张好看但不可控的图”。越来越多的应用场景要求模型不仅能理解语言指令&#xff0c;还能精准地对图像特定区域进行修改——比如把模特身…

作者头像 李华
网站建设 2026/9/1 16:55:03

大语言模型(LLM)入门:人工智能领域的颠覆性突破,系统解析其概念、技术、影响与未来趋势!

作为人工智能领域近十年最具颠覆性的技术突破之一&#xff0c;大语言模型&#xff08;LLM&#xff09;正以其强大的文本理解与生成能力&#xff0c;打破传统自然语言处理的技术边界&#xff0c;渗透到科研、产业及日常生活的多个场景。本文将从概念解析、技术原理、优劣势分析、…

作者头像 李华