#!/bin/bash
# gempub-converter.sh
# Conversor EPUB to Gempub
# Uso: gempub-converter.sh -e archivo.epub | -c carpeta
set -e
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Funciones de log
log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
# Mostrar ayuda
show_help() {
cat << EOF
Uso: $0 [OPCIÓN]... [ARCHIVO|CARPETA]
Conversor EPUB to Gempub
Opciones:
-e, --extraer ARCHIVO.epub Extrae y convierte un EPUB a estructura Gempub
-c, --crear CARPETA Crea archivo .gpub desde carpeta Gempub
-h, --help Muestra esta ayuda
Ejemplos:
$0 -e libro.epub
$0 -c mi_libro_gempub/
EOF
}
# Función para verificar si un capítulo está completamente en blanco
es_capitulo_en_blanco() {
local archivo="$1"
if [ ! -f "$archivo" ] || [ ! -s "$archivo" ]; then
return 0 # Está completamente vacío
fi
# Contar caracteres no espaciales
local caracteres_utiles=$(cat "$archivo" | tr -d '[:space:]' | wc -c)
# Considerar en blanco si tiene menos de 2 caracteres útiles
if [ "$caracteres_utiles" -lt 2 ]; then
return 0 # Está en blanco
else
return 1 # Tiene contenido
fi
}
# Función para comprimir imagen si es grande
comprimir_imagen() {
local img_file="$1"
local dest_dir="$2"
# Verificar si la imagen existe
if [ ! -f "$img_file" ]; then
log_warning "Imagen no encontrada: $img_file"
return 1
fi
# Obtener información de la imagen
local file_size=$(stat -f%z "$img_file" 2>/dev/null || stat -c%s "$img_file" 2>/dev/null)
local file_name=$(basename "$img_file")
local file_ext=$(echo "$file_name" | rev | cut -d. -f1 | rev | tr '[:upper:]' '[:lower:]')
local file_base=$(basename "$file_name" ".$file_ext")
# Si la imagen es menor a 1MB, copiar sin comprimir
if [ "$file_size" -lt 1048576 ]; then
cp "$img_file" "$dest_dir/$file_name"
echo "$file_name" # SOLO el nombre del archivo por stdout
return 0
fi
# Verificar si tenemos herramientas de conversión
if ! command -v convert &> /dev/null && ! command -v ffmpeg &> /dev/null; then
log_warning "No hay herramientas de conversión (ImageMagick/ffmpeg), copiando sin comprimir"
cp "$img_file" "$dest_dir/$file_name"
echo "$file_name" # SOLO el nombre del archivo por stdout
return 0
fi
# Para imágenes grandes (>1MB), convertir a JPEG
local jpg_name="${file_base}.jpg"
local jpg_path="$dest_dir/$jpg_name"
log_info "Comprimiendo imagen grande: $file_name ($((file_size/1024/1024))MB) → $jpg_name"
# Intentar con ImageMagick (convert) primero
local conversion_exitcode=1
if command -v convert &> /dev/null; then
if convert "$img_file" -quality 85 -strip "$jpg_path" 2>/dev/null; then
conversion_exitcode=0
fi
fi
# Fallback a ffmpeg si ImageMagick falló
if [ $conversion_exitcode -ne 0 ] && command -v ffmpeg &> /dev/null; then
if ffmpeg -i "$img_file" -q:v 5 -y "$jpg_path" 2>/dev/null; then
conversion_exitcode=0
fi
fi
# Verificar si la conversión fue exitosa
if [ $conversion_exitcode -eq 0 ] && [ -f "$jpg_path" ]; then
local new_size=$(stat -f%z "$jpg_path" 2>/dev/null || stat -c%s "$jpg_path" 2>/dev/null)
local reduction=$((100 - (new_size * 100 / file_size)))
log_success "Comprimida: $file_name → $jpg_name (${reduction}% reducción)"
echo "$jpg_name" # SOLO el nombre del archivo por stdout
return 0
else
log_warning "Falló la conversión, copiando original"
cp "$img_file" "$dest_dir/$file_name"
echo "$file_name" # SOLO el nombre del archivo por stdout
return 1
fi
}
# Función para actualizar referencias a imágenes en archivos .gmi
actualizar_referencias_imagenes() {
local work_dir="$1"
local old_name="$2"
local new_name="$3"
# Actualizar en todos los archivos .gmi
find "$work_dir" -name "*.gmi" -type f | while read -r gmi_file; do
if grep -q "$old_name" "$gmi_file"; then
sed -i.tmp "s|$old_name|$new_name|g" "$gmi_file"
rm -f "$gmi_file.tmp"
log_info "Actualizada referencia en $(basename "$gmi_file"): $old_name → $new_name"
fi
done
# Actualizar en metadata.txt si es la portada
if grep -q "cover:.*$old_name" "$work_dir/metadata.txt"; then
sed -i.tmp "s|cover:.*$old_name|cover: imagenes/$new_name|g" "$work_dir/metadata.txt"
rm -f "$work_dir/metadata.txt.tmp"
log_info "Actualizada portada en metadata.txt: $old_name → $new_name"
fi
}
# Función para limpiar texto para Gemtext
clean_text_for_gemtext() {
local text="$1"
# Eliminar caracteres problemáticos pero mantener acentos y caracteres especiales válidos
echo "$text" | sed -e 's/[“]/\"/g' -e 's/[”]/\"/g' -e 's/[‘]/'"'"'/g' -e 's/[’]/'"'"'/g' -e 's/…/.../g' -e 's/–/-/g' -e 's/—/--/g'
}
# Función para obtener primera línea de un archivo (limpia)
get_first_line_clean() {
local file="$1"
if [ ! -f "$file" ] || [ ! -s "$file" ]; then
echo "Capítulo"
return
fi
# Obtener primera línea no vacía y limpiarla
local first_line=$(grep -v '^[[:space:]]*$' "$file" | head -1 | sed 's/^[#]*[[:space:]]*//' | head -c 200)
if [ -z "$first_line" ]; then
echo "Capítulo"
else
echo "$first_line"
fi
}
# Función para verificar si es capítulo "subtítulo"
is_subtitle_chapter() {
local file="$1"
if [ ! -f "$file" ] || [ ! -s "$file" ]; then
return 1
fi
# Contar líneas no vacías
local non_empty_lines=$(grep -v '^[[:space:]]*$' "$file" | wc -l)
local content_length=$(cat "$file" | wc -c)
# Es subtítulo si: 1 línea no vacía Y menos de 200 caracteres
if [ "$non_empty_lines" -eq 1 ] && [ "$content_length" -lt 200 ]; then
return 0
else
return 1
fi
}
# Función para extraer texto alternativo de imagen HTML
extraer_alt_text() {
local html_line="$1"
# Extraer texto alternativo de la etiqueta img
local alt_text=$(echo "$html_line" | sed -n 's/.*alt="\([^"]*\)".*/\1/pI')
if [ -z "$alt_text" ]; then
# Intentar con comillas simples
alt_text=$(echo "$html_line" | sed -n "s/.*alt='\([^']*\)'.*/\1/pI")
fi
if [ -z "$alt_text" ]; then
# Usar nombre del archivo como fallback (mejorado)
local img_src=$(extraer_img_src "$html_line")
if [ -n "$img_src" ]; then
alt_text=$(basename "$img_src" | sed 's/\.[^.]*$//' | tr '_-' ' ' | sed 's/\([a-z]\)\([A-Z]\)/\1 \2/g')
# Capitalizar primera letra
alt_text=$(echo "$alt_text" | sed 's/^./\U&/')
else
alt_text="Imagen"
fi
fi
# Limpiar el texto alternativo
alt_text=$(echo "$alt_text" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo "$alt_text"
}
# Función para extraer src de imagen HTML
extraer_img_src() {
local html_line="$1"
# Extraer src de la etiqueta img
local img_src=$(echo "$html_line" | sed -n 's/.*src="\([^"]*\)".*/\1/pI')
if [ -z "$img_src" ]; then
# Intentar con comillas simples
img_src=$(echo "$html_line" | sed -n "s/.*src='\([^']*\)'.*/\1/pI")
fi
echo "$img_src"
}
# Función para convertir HTML/XHTML a Gemtext (VERSIÓN MEJORADA Y ROBUSTA)
html_to_gemtext() {
local html_file="$1"
local gemtext_file="$2"
log_info "Procesando: $(basename "$html_file")"
# Verificar que el archivo existe y no está vacío
if [ ! -f "$html_file" ] || [ ! -s "$html_file" ]; then
log_warning "Archivo no encontrado o vacío: $html_file"
echo "# Archivo no disponible" > "$gemtext_file"
return 1
fi
# Archivo temporal para el procesamiento
local temp_file="$gemtext_file.tmp"
# PRIMERO: Extraer y procesar todas las imágenes del HTML
local image_count=0
declare -a image_lines
declare -a image_replacements
# Leer el HTML y encontrar todas las líneas con imágenes
while IFS= read -r line; do
if echo "$line" | grep -qi '
/dev/null; then
if pandoc "$html_file" -f html -t plain --wrap=none 2>/dev/null > "$temp_file"; then
log_info "Usando pandoc para conversión HTML→texto"
else
# Fallback a conversión básica si pandoc falla
log_warning "Pandoc falló, usando conversión básica"
cat "$html_file" | sed -e 's/<[^>]*>//g' -e 's/ / /g' -e 's/&/\&/g' > "$temp_file"
fi
else
# Conversión básica con sed
cat "$html_file" | sed -e 's/<[^>]*>//g' -e 's/ / /g' -e 's/&/\&/g' > "$temp_file"
fi
# TERCERO: Aplicar limpieza al texto
clean_text_for_gemtext "$(cat "$temp_file")" > "$temp_file.clean"
mv "$temp_file.clean" "$temp_file"
# CUARTO: Si hay imágenes, crear un archivo final combinando texto e imágenes
if [ $image_count -gt 0 ]; then
local final_content=""
local line_num=1
# Leer el HTML original línea por línea para mantener el orden correcto
while IFS= read -r original_line; do
local image_found=0
# Verificar si esta línea es una imagen que debemos reemplazar
for ((i=0; i/dev/null || true)
if [ -n "$text_line" ] && [ ! "$text_line" = "..." ]; then
final_content="${final_content}${text_line}"$'\n'
fi
line_num=$((line_num + 1))
fi
done < "$html_file"
# Escribir el contenido final
echo "$final_content" > "$gemtext_file"
else
# Si no hay imágenes, usar directamente el texto convertido
mv "$temp_file" "$gemtext_file"
fi
# Limpiar archivos temporales
rm -f "$temp_file" "$temp_file.clean"
# QUINTO: Post-procesamiento para asegurar formato Gemtext válido
{
# Asegurar que el archivo termina con nueva línea
cat "$gemtext_file"
echo
} > "$gemtext_file.tmp"
mv "$gemtext_file.tmp" "$gemtext_file"
# Eliminar líneas completamente vacías consecutivas (máximo 2 líneas vacías seguidas)
sed -i.tmp '/^[[:space:]]*$/{N;/^\n$/d;}' "$gemtext_file"
rm -f "$gemtext_file.tmp"
# Si el archivo resultante está vacío, poner contenido mínimo
if [ ! -s "$gemtext_file" ]; then
echo "# Capítulo" > "$gemtext_file"
echo "" >> "$gemtext_file"
echo "Contenido no disponible en este capítulo." >> "$gemtext_file"
fi
# Contar imágenes procesadas en el archivo final
local final_image_count=$(grep -c '=> imagenes/' "$gemtext_file" || true)
if [ $final_image_count -gt 0 ]; then
log_success "Convertido con $final_image_count imagen(es): $(basename "$gemtext_file")"
else
log_success "Convertido: $(basename "$gemtext_file")"
fi
}
# Función para extraer metadatos de OPF
extract_metadata_from_opf() {
local opf_file="$1"
local metadata_file="$2"
log_info "Extrayendo metadatos de: $(basename "$opf_file")"
# Valores por defecto
local title=""
local author=""
local language=""
local description=""
local published=""
local publishDate=""
local revisionDate=""
local copyright=""
local license=""
local publisher=""
local identifier=""
local subject=""
local cover_image=""
# Extraer metadatos del OPF usando métodos más robustos
if [ -f "$opf_file" ]; then
local opf_content=$(cat "$opf_file")
# EXTRAER TÍTULO - múltiples métodos
title=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
if [ -z "$title" ]; then
title=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
fi
# EXTRAER AUTOR - métodos más agresivos
author=$(echo "$opf_content" | grep -o ']*>[^<]*' | sed 's/]*>//' | head -1)
if [ -z "$author" ]; then
author=$(echo "$opf_content" | grep -o ']*>[^<]*' | sed 's/]*>//' | head -1)
fi
if [ -z "$author" ]; then
# Buscar en cualquier tag que contenga "creator"
author=$(echo "$opf_content" | grep -i 'creator' | grep -o '>[^<]*<' | sed 's/^>//;s/<$//' | head -1)
fi
# EXTRAER DESCRIPCIÓN
description=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
# EXTRAER IDIOMA
language=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
# EXTRAER FECHA - manejar diferentes formatos
local date_line=$(echo "$opf_content" | grep -o '[^<]*' | head -1 | sed 's///')
if [ -n "$date_line" ]; then
# Extraer YYYY-MM-DD si existe
if [[ "$date_line" =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}) ]]; then
publishDate="${BASH_REMATCH[1]}"
published=$(echo "$publishDate" | cut -d'-' -f1)
# O solo el año
elif [[ "$date_line" =~ ([0-9]{4}) ]]; then
published="${BASH_REMATCH[1]}"
fi
fi
# EXTRAER SUBJECT/TEMA
subject=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
# EXTRAER IDENTIFICADOR
identifier=$(echo "$opf_content" | grep -o ']*>[^<]*' | sed 's/]*>//' | head -1)
# BUSCAR PORTADA en meta tags
cover_image=$(echo "$opf_content" | grep -i 'meta.*name="cover"' | grep -o 'content="[^"]*"' | sed 's/content="//;s/"//')
# EXTRAER COPYRIGHT si existe
copyright=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
# EXTRAER EDITOR
publisher=$(echo "$opf_content" | grep -o '[^<]*' | sed 's///' | head -1)
# DEBUG: Mostrar lo que se encontró
log_info "Encontrado - Título: '$title', Autor: '$author', Descripción: '$description'"
fi
# Limpiar valores (eliminar espacios y caracteres extraños)
title=$(echo "$title" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/&/&/g; s/<//g; s/"/"/g' | head -c 200)
author=$(echo "$author" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/&/&/g; s/<//g; s/"/"/g' | head -c 150)
description=$(echo "$description" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/&/&/g; s/<//g; s/"/"/g' | head -c 300)
subject=$(echo "$subject" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | head -c 100)
copyright=$(echo "$copyright" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | head -c 100)
# Crear archivo de metadatos Gempub
{
echo "title: ${title:-Sin título}"
echo "gpubVersion: 1.0.0"
# Solo añadir campos que tienen contenido
if [ -n "$author" ] && [ "$author" != "Sin título" ]; then
echo "author: $author"
fi
if [ -n "$language" ] && [ "$language" != "Sin título" ]; then
echo "language: $language"
fi
if [ -n "$description" ] && [ "$description" != "Sin título" ]; then
echo "description: $description"
fi
if [ -n "$published" ]; then
echo "published: $published"
fi
if [ -n "$publishDate" ]; then
echo "publishDate: $publishDate"
fi
if [ -n "$subject" ] && [ "$subject" != "Sin título" ]; then
echo "subject: $subject"
fi
if [ -n "$identifier" ] && [ "$identifier" != "Sin título" ]; then
echo "identifier: $identifier"
fi
if [ -n "$copyright" ] && [ "$copyright" != "Sin título" ]; then
echo "copyright: $copyright"
fi
if [ -n "$publisher" ] && [ "$publisher" != "Sin título" ]; then
echo "publisher: $publisher"
fi
# Añadir créditos de conversión
echo "version: Convertido a Gempub usando gempub-converter.sh"
# Añadir charset por defecto
echo "charset: UTF-8"
} > "$metadata_file"
# Verificar que se escribieron los campos críticos
if grep -q "author:" "$metadata_file"; then
log_success "Metadatos extraídos: '$title' por '$author'"
else
log_success "Metadatos extraídos: '$title' (autor no encontrado)"
fi
}
# Función para encontrar archivo OPF
find_opf_file() {
local epub_dir="$1"
# Buscar en META-INF/container.xml primero
local container_file="$epub_dir/META-INF/container.xml"
if [ -f "$container_file" ]; then
log_info "Buscando OPF en container.xml"
# Método más robusto para extraer la ruta
local opf_path=$(grep -o 'full-path="[^"]*"' "$container_file" | head -1 | sed 's/full-path="//' | sed 's/"//')
if [ -n "$opf_path" ] && [ -f "$epub_dir/$opf_path" ]; then
echo "$epub_dir/$opf_path"
return 0
fi
fi
# Buscar cualquier archivo .opf en el EPUB
log_info "Buscando archivos OPF..."
local opf_files=$(find "$epub_dir" -name "*.opf" -type f)
if [ -n "$opf_files" ]; then
local first_opf=$(echo "$opf_files" | head -1)
log_info "Usando OPF encontrado: $(basename "$first_opf")"
echo "$first_opf"
return 0
fi
log_warning "No se encontró archivo OPF"
return 1
}
# Función para encontrar archivos de contenido
find_content_files() {
local epub_dir="$1"
local opf_file="$2"
log_info "Buscando archivos de contenido..."
# Si tenemos OPF, buscar en el manifest
if [ -f "$opf_file" ] && command -v xmllint &> /dev/null; then
local content_dir=$(dirname "$opf_file")
# Extraer items del manifest
local manifest_items=$(xmllint --xpath '//*[local-name()="item"]/@href' "$opf_file" 2>/dev/null | \
sed 's/href="/\n/g' | sed 's/"/\n/g' | grep -v '^$' | grep -E '\.(x?html|htm)$')
if [ -n "$manifest_items" ]; then
local files=""
for item in $manifest_items; do
local full_path="$content_dir/$item"
if [ -f "$full_path" ]; then
files="$files$full_path"$'\n'
fi
done
echo "$files" | grep -v '^$'
return 0
fi
fi
# Fallback: buscar todos los archivos HTML en el EPUB
log_info "Buscando archivos HTML en todo el EPUB..."
find "$epub_dir" -type f \( -name "*.html" -o -name "*.xhtml" -o -name "*.htm" \) | \
grep -v -E '(nav|toc|cover|titlepage|copyright|frontmatter|backmatter)' | \
sort
}
# Función para extraer EPUB (CON LÓGICA CORRECTA DE CAPÍTULOS)
extract_epub() {
local epub_file="$1"
if [ ! -f "$epub_file" ]; then
log_error "Archivo EPUB no encontrado: $epub_file"
exit 1
fi
local base_name=$(basename "$epub_file" .epub)
local base_name=$(basename "$base_name" .EPUB)
# Limpiar nombre para directorio
local clean_name=$(echo "$base_name" | sed 's/[^a-zA-Z0-9áéíóúÁÉÍÓÚñÑ -]//g')
local work_dir="${clean_name}_gempub"
log_info "Creando directorio de trabajo: $work_dir"
mkdir -p "$work_dir"
log_info "Extrayendo EPUB: $(basename "$epub_file")"
if ! unzip -q "$epub_file" -d "$work_dir/epub_content" 2>/dev/null; then
log_error "Error al extraer el EPUB"
exit 1
fi
# Encontrar y procesar metadatos
local opf_file=$(find_opf_file "$work_dir/epub_content")
if [ -n "$opf_file" ] && [ -f "$opf_file" ]; then
extract_metadata_from_opf "$opf_file" "$work_dir/metadata.txt"
else
log_warning "No se encontró archivo OPF, usando metadatos por defecto"
cat > "$work_dir/metadata.txt" << EOF
title: $clean_name
gpubVersion: 1.0.0
version: Convertido a Gempub usando gempub-converter.sh
charset: UTF-8
EOF
fi
# Buscar archivos de contenido
local html_files=$(find_content_files "$work_dir/epub_content" "$opf_file")
if [ -z "$html_files" ]; then
log_error "No se encontraron archivos de contenido HTML"
exit 1
fi
# Convertir contenido a Gemtext
local chapter_count=0
local book_title=$(grep "^title:" "$work_dir/metadata.txt" | cut -d: -f2- | sed 's/^[[:space:]]*//')
local index_content="# ${book_title:-Índice del Libro}\n\n"
# Primero convertir todos los archivos
declare -a gemtext_files
while IFS= read -r html_file; do
if [ ! -f "$html_file" ] || [ ! -s "$html_file" ]; then
log_warning "Archivo no válido, saltando: $html_file"
continue
fi
chapter_count=$((chapter_count + 1))
local gemtext_file="$work_dir/capitulo${chapter_count}.gmi"
html_to_gemtext "$html_file" "$gemtext_file"
gemtext_files[$chapter_count]="$gemtext_file"
done <<< "$html_files"
# PROCESAR CAPÍTULOS CON LÓGICA CORRECTA
log_info "Procesando estructura de capítulos..."
local capitulos_eliminados=0
local capitulos_subtitulo=0
local nuevo_numero=0
declare -a gemtext_files_finales
for ((i=1; i<=chapter_count; i++)); do
local gemtext_file="${gemtext_files[$i]}"
# 1. ELIMINAR completamente vacíos (no aparecen en índice)
if es_capitulo_en_blanco "$gemtext_file"; then
log_warning "Eliminando capítulo completamente en blanco: capitulo${i}.gmi"
rm -f "$gemtext_file"
capitulos_eliminados=$((capitulos_eliminados + 1))
continue
fi
# 2. DETECTAR subtítulos (1 línea, <200 chars) → ## en índice + ELIMINAR archivo
if is_subtitle_chapter "$gemtext_file"; then
local contenido=$(get_first_line_clean "$gemtext_file")
index_content="${index_content}## $contenido\n\n"
log_info "Identificado como subtítulo: $contenido"
rm -f "$gemtext_file" # ELIMINAR el archivo
capitulos_subtitulo=$((capitulos_subtitulo + 1))
continue
fi
# 3. CAPÍTULOS NORMALES → => en índice con primera línea
nuevo_numero=$((nuevo_numero + 1))
local nuevo_nombre="$work_dir/capitulo${nuevo_numero}.gmi"
# Renombrar si es necesario (para mantener numeración secuencial)
if [ "$gemtext_file" != "$nuevo_nombre" ]; then
mv "$gemtext_file" "$nuevo_nombre"
fi
gemtext_files_finales[$nuevo_numero]="$nuevo_nombre"
local first_line=$(get_first_line_clean "$nuevo_nombre")
index_content="${index_content}=> capitulo${nuevo_numero}.gmi $first_line\n"
done
local total_capitulos_finales=$nuevo_numero
# Crear archivo índice
echo -e "$index_content" > "$work_dir/index.gmi"
# Buscar y procesar imágenes
log_info "Buscando y procesando imágenes..."
local image_files=$(find "$work_dir/epub_content" -type f \( -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" -o -name "*.bmp" -o -name "*.gif" \) 2>/dev/null | head -20)
if [ -n "$image_files" ]; then
mkdir -p "$work_dir/imagenes"
local image_count=0
while IFS= read -r img_file; do
image_count=$((image_count + 1))
# Capturar SOLO el nombre del archivo, sin logs
local final_name
final_name=$(comprimir_imagen "$img_file" "$work_dir/imagenes")
# Limpiar cualquier código de color que pueda haber escapado
final_name=$(echo "$final_name" | sed 's/\x1b\[[0-9;]*m//g' | tr -d '\n')
# Actualizar referencias en archivos .gmi si el nombre cambió
local original_name=$(basename "$img_file")
if [ "$final_name" != "$original_name" ]; then
actualizar_referencias_imagenes "$work_dir" "$original_name" "$final_name"
fi
# Si es la primera imagen, establecer como portada
if [ $image_count -eq 1 ]; then
# Asegurarse de que el campo cover esté limpio
if grep -q "^cover:" "$work_dir/metadata.txt"; then
# Si ya existe, actualizarlo
sed -i.tmp "s|^cover:.*|cover: imagenes/$final_name|" "$work_dir/metadata.txt"
rm -f "$work_dir/metadata.txt.tmp"
else
# Si no existe, añadirlo
echo "cover: imagenes/$final_name" >> "$work_dir/metadata.txt"
fi
log_info "Imagen de portada establecida: imagenes/$final_name"
fi
done <<< "$image_files"
log_success "Procesadas $image_count imágenes"
else
log_warning "No se encontraron imágenes"
fi
# Añadir índice al metadata si no está presente
if ! grep -q "^index:" "$work_dir/metadata.txt"; then
echo "index: index.gmi" >> "$work_dir/metadata.txt"
fi
# Limpiar contenido EPUB extraído
rm -rf "$work_dir/epub_content"
# Mostrar resumen final
log_success "=== EXTRACCIÓN COMPLETADA ==="
log_success "Directorio: $work_dir"
log_success "Capítulos originales: $chapter_count"
log_success "Capítulos eliminados (en blanco): $capitulos_eliminados"
log_success "Capítulos como subtítulos: $capitulos_subtitulo"
log_success "Capítulos finales con contenido: $total_capitulos_finales"
log_success "Metadatos: metadata.txt"
log_success "Índice: index.gmi"
echo
log_info "Para crear el Gempub, ejecuta:"
log_info " $0 -c '$work_dir'"
}
# Función para validar estructura Gempub
validate_gempub_structure() {
local gempub_dir="$1"
log_info "Validando estructura Gempub..."
local errors=0
local warnings=0
# Verificar archivos obligatorios
if [ ! -f "$gempub_dir/metadata.txt" ]; then
log_error "Falta: metadata.txt"
errors=$((errors + 1))
else
# Verificar metadatos obligatorios
if ! grep -q "title:" "$gempub_dir/metadata.txt"; then
log_error "metadata.txt falta 'title'"
errors=$((errors + 1))
fi
if ! grep -q "gpubVersion:" "$gempub_dir/metadata.txt"; then
log_error "metadata.txt falta 'gpubVersion'"
errors=$((errors + 1))
fi
fi
# Verificar archivo índice
local index_path="$gempub_dir/index.gmi"
if [ -f "$gempub_dir/metadata.txt" ]; then
local custom_index=$(grep "^index:" "$gempub_dir/metadata.txt" | cut -d: -f2- | sed 's/^[ \t]*//')
if [ -n "$custom_index" ]; then
index_path="$gempub_dir/$custom_index"
fi
fi
if [ ! -f "$index_path" ]; then
log_error "Falta archivo índice: $index_path"
errors=$((errors + 1))
else
# Validar enlaces en índice
local remote_links=$(grep -E '=> https?://' "$index_path" | grep -v '^#' || true)
if [ -n "$remote_links" ]; then
log_warning "Enlaces remotos en índice (deberían ser locales):"
echo "$remote_links"
warnings=$((warnings + 1))
fi
fi
# Verificar archivos .gmi
local gmi_files=$(find "$gempub_dir" -name "*.gmi" | wc -l)
if [ "$gmi_files" -eq 0 ]; then
log_error "No se encontraron archivos .gmi"
errors=$((errors + 1))
fi
# Verificar imágenes sin alt-text (según especificación Gempub)
local images_no_alt=$(find "$gempub_dir" -name "*.gmi" -exec grep -H '=>.*\.\(jpg\|jpeg\|png\|gif\)[[:space:]]*$' {} \; 2>/dev/null || true)
if [ -n "$images_no_alt" ]; then
log_warning "Imágenes sin texto alternativo (viola especificación Gempub):"
echo "$images_no_alt"
warnings=$((warnings + 1))
fi
# Resumen de validación
if [ $errors -eq 0 ]; then
log_success "Validación completada: $warnings advertencias, 0 errores"
return 0
else
log_error "Validación fallida: $warnings advertencias, $errors errores"
return 1
fi
}
# Función para crear Gempub
create_gempub() {
local gempub_dir="$1"
if [ ! -d "$gempub_dir" ]; then
log_error "Directorio no encontrado: $gempub_dir"
exit 1
fi
# Validar estructura
if ! validate_gempub_structure "$gempub_dir"; then
log_error "No se puede crear Gempub - estructura inválida"
exit 1
fi
local base_name=$(basename "$gempub_dir" _gempub)
local gempub_file="${base_name}.gpub"
log_info "Creando Gempub: $gempub_file"
# Crear archivo ZIP con compresión máxima
if (cd "$gempub_dir" && zip -qr -9 "../$gempub_file" .); then
log_success "Gempub creado exitosamente: $gempub_file"
# Mostrar información del archivo
local file_size=$(du -h "$gempub_file" | cut -f1)
local file_count=$(zipinfo -1 "$gempub_file" | wc -l)
log_info " - Tamaño: $file_size"
log_info " - Archivos: $file_count"
else
log_error "Error al crear el archivo Gempub"
exit 1
fi
}
# Procesar argumentos
if [ $# -eq 0 ]; then
show_help
exit 1
fi
case "$1" in
-e|--extraer)
if [ $# -ne 2 ]; then
log_error "Se requiere archivo EPUB para extraer"
show_help
exit 1
fi
extract_epub "$2"
;;
-c|--crear)
if [ $# -ne 2 ]; then
log_error "Se requiere directorio para crear Gempub"
show_help
exit 1
fi
create_gempub "$2"
;;
-h|--help)
show_help
;;
*)
log_error "Opción no reconocida: $1"
show_help
exit 1
;;
esac