Qué es EPUB 3
EPUB (Electronic Publication) es el estándar abierto de la industria editorial para libros electrónicos. Desarrollado por el W3C e IDPf, EPUB 3 es el sucesor de EPUB 2 y soporta:
- HTML5 y CSS3 — diseño visual completo
- JavaScript — libros interactivos y educativos
- MathML y SVG — fórmulas matemáticas y gráficos vectoriales
- Audio y video — multimedia embebido
- Accesibilidad WCAG — ARIA, sincronización de texto y audio
Internamente, un EPUB es un archivo ZIP con una estructura de directorios definida que contiene XHTML, CSS, imágenes y archivos de metadatos XML.
Anatomía de un EPUB
mi_libro.epub (ZIP)
├── mimetype ← "application/epub+zip" sin comprimir
├── META-INF/
│ └── container.xml ← apunta al OPF raíz
└── OEBPS/ (o cualquier nombre)
├── content.opf ← metadatos + lista de archivos
├── toc.ncx ← tabla de contenidos EPUB 2 (compatibilidad)
├── nav.xhtml ← tabla de contenidos EPUB 3
├── capitulo01.xhtml ← contenido
├── capitulo02.xhtml
├── estilos/
│ └── libro.css
└── imagenes/
└── portada.jpg
Instalación
pip install ebooklib Pillow lxml
Crear un EPUB desde cero con ebooklib
from ebooklib import epub
from pathlib import Path
def crear_libro_basico(titulo, autor, capitulos, archivo_salida):
"""
Crea un EPUB 3 completo desde una lista de capítulos.
capitulos = [{'titulo': str, 'contenido': str (HTML)}, ...]
"""
libro = epub.EpubBook()
# ── Metadatos ──────────────────────────────────────────────────────────
libro.set_identifier(f'urn:isbn:{titulo.lower().replace(" ", "-")}-001')
libro.set_title(titulo)
libro.set_language('es')
libro.add_author(autor)
libro.add_metadata('DC', 'publisher', 'KaijuConverter Books')
libro.add_metadata('DC', 'rights', 'Copyright 2024')
# ── CSS ────────────────────────────────────────────────────────────────
css_contenido = '''
body {
font-family: Georgia, "Times New Roman", serif;
font-size: 1em;
line-height: 1.6;
margin: 0 auto;
max-width: 600px;
padding: 1em 2em;
color: #333;
}
h1 { font-size: 2em; color: #1a1a2e; border-bottom: 2px solid #4361ee; }
h2 { font-size: 1.5em; color: #16213e; margin-top: 1.5em; }
p { text-indent: 1.5em; margin: 0.5em 0; }
code {
font-family: "Courier New", monospace;
background: #f4f4f4;
padding: 0.1em 0.4em;
border-radius: 3px;
}
pre code {
display: block;
padding: 1em;
overflow-x: auto;
line-height: 1.4;
}
'''
css = epub.EpubItem(
uid='estilo_principal',
file_name='estilos/libro.css',
media_type='text/css',
content=css_contenido
)
libro.add_item(css)
# ── Capítulos ──────────────────────────────────────────────────────────
items_epub = []
spine_items = ['nav']
for i, cap in enumerate(capitulos, 1):
cap_epub = epub.EpubHtml(
title=cap['titulo'],
file_name=f'capitulo{i:02d}.xhtml',
lang='es'
)
cap_epub.content = f'''<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="es">
<head>
<title>{cap["titulo"]}</title>
<link rel="stylesheet" href="estilos/libro.css" type="text/css"/>
</head>
<body>
<h1>{cap["titulo"]}</h1>
{cap["contenido"]}
</body>
</html>'''
cap_epub.add_item(css)
libro.add_item(cap_epub)
items_epub.append(cap_epub)
spine_items.append(cap_epub)
# ── Tabla de contenidos ────────────────────────────────────────────────
libro.toc = [epub.Link(item.file_name, item.title, item.id)
for item in items_epub]
# ── Items necesarios ───────────────────────────────────────────────────
libro.add_item(epub.EpubNcx())
libro.add_item(epub.EpubNav())
# ── Spine (orden de lectura) ───────────────────────────────────────────
libro.spine = spine_items
# ── Guardar ───────────────────────────────────────────────────────────
epub.write_epub(archivo_salida, libro)
tamaño = Path(archivo_salida).stat().st_size / 1024
print(f"EPUB creado: {archivo_salida} ({tamaño:.1f} KB)")
return archivo_salida
# Ejemplo básico
capitulos = [
{
'titulo': 'Introducción',
'contenido': '''
<p>Bienvenido a este libro de ejemplo creado con Python y ebooklib.</p>
<p>En este capítulo aprenderás los fundamentos del formato EPUB 3.</p>
'''
},
{
'titulo': 'Capítulo 1: Fundamentos',
'contenido': '''
<p>El formato EPUB se basa en tecnologías web estándar: HTML, CSS y SVG.</p>
<p>Cada capítulo es un archivo XHTML independiente dentro del contenedor ZIP.</p>
<h2>¿Por qué EPUB?</h2>
<p>EPUB es el estándar abierto, sin DRM por defecto y soportado en todos los
lectores modernos excepto Kindle (que usa MOBI/AZW).</p>
'''
},
]
crear_libro_basico('Mi Primer eBook', 'Pablo Autor', capitulos, 'mi_ebook.epub')
Añadir portada e imágenes
import io
from PIL import Image, ImageDraw
def generar_portada_epub(titulo, autor, ancho=1400, alto=2100):
"""Genera una portada profesional con Pillow."""
img = Image.new('RGB', (ancho, alto), color='#1a1a2e')
d = ImageDraw.Draw(img)
# Marco decorativo
for margen in [20, 30, 40]:
d.rectangle([margen, margen, ancho-margen, alto-margen],
outline='#4361ee', width=2)
# Título (texto centrado)
d.text((ancho//2, alto//3), titulo, fill='#ffffff',
anchor='mm', font_size=80)
d.text((ancho//2, alto//3 + 120), autor, fill='#4cc9f0',
anchor='mm', font_size=48)
# Guardar en memoria
buf = io.BytesIO()
img.save(buf, 'JPEG', quality=95)
return buf.getvalue()
def crear_epub_con_portada(titulo, autor, capitulos, salida):
"""EPUB con portada autogenerada."""
libro = epub.EpubBook()
libro.set_title(titulo)
libro.set_language('es')
libro.add_author(autor)
# Portada
datos_portada = generar_portada_epub(titulo, autor)
portada_item = epub.EpubItem(
uid='portada',
file_name='imagenes/portada.jpg',
media_type='image/jpeg',
content=datos_portada
)
libro.add_item(portada_item)
libro.set_cover('imagenes/portada.jpg', datos_portada)
# Página de portada
pagina_portada = epub.EpubHtml(
title='Portada',
file_name='portada.xhtml'
)
pagina_portada.content = '''<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Portada</title></head>
<body>
<div style="text-align:center;padding:2em;">
<img src="imagenes/portada.jpg" alt="Portada"
style="max-width:100%;height:auto;"/>
</div>
</body>
</html>'''
libro.add_item(pagina_portada)
# Capítulos (simplificado)
caps = []
for i, cap in enumerate(capitulos, 1):
c = epub.EpubHtml(title=cap['titulo'],
file_name=f'cap{i:02d}.xhtml')
c.content = f'<html><body><h1>{cap["titulo"]}</h1>{cap["contenido"]}</body></html>'
libro.add_item(c)
caps.append(c)
libro.toc = [epub.Link(c.file_name, c.title, c.id) for c in caps]
libro.spine = ['cover', pagina_portada] + caps
libro.add_item(epub.EpubNcx())
libro.add_item(epub.EpubNav())
epub.write_epub(salida, libro)
print(f"EPUB con portada: {salida}")
Convertir texto plano a EPUB
import re
def txt_a_epub(ruta_txt, titulo, autor, salida=None):
"""
Convierte un archivo .txt a EPUB.
Detecta capítulos por líneas que empiezan con 'Capítulo', 'Chapter' o numeración.
"""
texto = Path(ruta_txt).read_text(encoding='utf-8')
# Dividir en capítulos
patron_cap = re.compile(
r'^(cap[íi]tulo\s+\d+|chapter\s+\d+|\d+\.\s+\w)',
re.IGNORECASE | re.MULTILINE
)
partes = patron_cap.split(texto)
# Agrupar: título del capítulo + contenido
capitulos = []
if len(partes) > 1:
for i in range(1, len(partes), 2):
titulo_cap = partes[i].strip()
contenido = partes[i + 1].strip() if i + 1 < len(partes) else ''
# Convertir saltos de línea en párrafos HTML
parrafos = contenido.split('\n\n')
html = ''.join(f'<p>{p.strip()}</p>' for p in parrafos if p.strip())
capitulos.append({'titulo': titulo_cap, 'contenido': html})
else:
# Sin capítulos detectados → un solo capítulo
parrafos = texto.split('\n\n')
html = ''.join(f'<p>{p.strip()}</p>' for p in parrafos if p.strip())
capitulos = [{'titulo': titulo, 'contenido': html}]
if salida is None:
salida = Path(ruta_txt).with_suffix('.epub')
crear_libro_basico(titulo, autor, capitulos, str(salida))
print(f"Convertido: {ruta_txt} → {salida} ({len(capitulos)} capítulos)")
return str(salida)
# Uso
txt_a_epub('novela.txt', 'Mi Novela', 'El Autor', 'novela.epub')
Convertir Markdown a EPUB
def md_a_epub(ruta_md, titulo, autor, salida=None):
"""Convierte Markdown a EPUB usando python-markdown."""
try:
import markdown
except ImportError:
raise ImportError("pip install markdown")
texto_md = Path(ruta_md).read_text(encoding='utf-8')
# Dividir por encabezados H1 para crear capítulos
bloques = re.split(r'^# (.+)$', texto_md, flags=re.MULTILINE)
capitulos = []
if len(bloques) > 1:
for i in range(1, len(bloques), 2):
titulo_cap = bloques[i].strip()
contenido_md = bloques[i + 1] if i + 1 < len(bloques) else ''
html = markdown.markdown(
contenido_md,
extensions=['tables', 'fenced_code', 'footnotes']
)
capitulos.append({'titulo': titulo_cap, 'contenido': html})
else:
html = markdown.markdown(texto_md, extensions=['tables', 'fenced_code'])
capitulos = [{'titulo': titulo, 'contenido': html}]
if salida is None:
salida = Path(ruta_md).with_suffix('.epub')
crear_libro_basico(titulo, autor, capitulos, str(salida))
print(f"Markdown → EPUB: {salida} ({len(capitulos)} capítulos)")
return str(salida)
# Uso
md_a_epub('documentacion.md', 'Guía Técnica', 'DevTeam', 'guia.epub')
Leer y editar EPUBs existentes
from ebooklib import epub, ITEM_DOCUMENT
def info_epub(ruta):
"""Muestra metadatos de un EPUB."""
libro = epub.read_epub(ruta)
titulo = libro.get_metadata('DC', 'title')
autor = libro.get_metadata('DC', 'creator')
idioma = libro.get_metadata('DC', 'language')
print(f"Título: {titulo[0][0] if titulo else 'N/A'}")
print(f"Autor: {autor[0][0] if autor else 'N/A'}")
print(f"Idioma: {idioma[0][0] if idioma else 'N/A'}")
# Contar documentos
docs = list(libro.get_items_of_type(ITEM_DOCUMENT))
print(f"Documentos XHTML: {len(docs)}")
return libro
def extraer_texto_epub(ruta):
"""Extrae todo el texto legible de un EPUB."""
try:
from bs4 import BeautifulSoup
except ImportError:
raise ImportError("pip install beautifulsoup4")
libro = epub.read_epub(ruta)
textos = []
for item in libro.get_items_of_type(ITEM_DOCUMENT):
sopa = BeautifulSoup(item.content, 'html.parser')
texto = sopa.get_text(separator='\n', strip=True)
if texto:
textos.append(texto)
texto_completo = '\n\n'.join(textos)
palabras = len(texto_completo.split())
print(f"Texto extraído: {palabras:,} palabras")
return texto_completo
# Uso
info_epub('mi_ebook.epub')
texto = extraer_texto_epub('novela.epub')
Validación y compatibilidad
import subprocess
import shutil
def validar_epub(ruta_epub):
"""Valida un EPUB con EPUBCheck (requiere Java)."""
if not shutil.which('java'):
print("Java no disponible — validación omitida")
return True
# Descargar epubcheck.jar de: https://github.com/w3c/epubcheck
jar = 'epubcheck.jar'
if not Path(jar).exists():
print(f"EPUBCheck no encontrado: {jar}")
return None
result = subprocess.run(
['java', '-jar', jar, ruta_epub],
capture_output=True, text=True
)
if 'No errors' in result.stdout:
print(f"✅ EPUB válido: {ruta_epub}")
return True
else:
print(f"❌ Errores encontrados:\n{result.stdout}")
return False
Lectores recomendados por plataforma
| Plataforma | Mejor lector EPUB |
|---|---|
| Windows | Calibre, Sumatra PDF |
| macOS | Books (Apple), Calibre |
| Android | Moon+ Reader, ReadEra |
| iOS | Books (Apple), Marvin |
| Linux | Foliate, Calibre |
| eReaders | Kobo, PocketBook (nativos) |
| Kindle | ❌ No soporta EPUB — convertir a MOBI/AZW3 |
Conclusión
EPUB 3 es el estándar abierto para eBooks con soporte HTML5 completo. Python con ebooklib permite crear EPUBs desde cero, convertir texto plano o Markdown, y extraer contenido de EPUBs existentes. Para publicación profesional, combina siempre la creación con EPUBCheck para garantizar compatibilidad universal, y añade metadatos Dublin Core completos para una mejor distribución en plataformas editoriales.
Conversiones relacionadas
Conversiones de documento que siguen este tema: