CoolFace
Apppublic

uxoxo/eb2ab

sourceHugging Faceapache-2.0updated 11mo agoView on Hugging Face
0likes
ebook_extractor.py338 linesDownload Raw Back to lib
1#!/usr/bin/env python32"""3Ebook Text Extractor - Extract text and metadata from multiple ebook formats4 5Supports: EPUB, PDF, TXT, HTML6Returns: Structured metadata and chapter content7 8Author: Claude Code9Date: 2025-10-1810"""11 12import os13import re14from pathlib import Path15from dataclasses import dataclass16from typing import List, Tuple, Optional17 18# Import with fallbacks19try:20    import ebooklib21    from ebooklib import epub22    HAS_EBOOKLIB = True23except ImportError:24    HAS_EBOOKLIB = False25 26try:27    from bs4 import BeautifulSoup28    HAS_BS4 = True29except ImportError:30    HAS_BS4 = False31 32try:33    import pymupdf4llm34    HAS_PYMUPDF = True35except ImportError:36    HAS_PYMUPDF = False37 38 39@dataclass40class EbookMetadata:41    """Metadata extracted from ebook"""42    title: str43    author: str44    format: str45    word_count: int46    character_count: int47    chapter_count: int48 49 50@dataclass51class ChapterContent:52    """Content of a single chapter"""53    title: str54    text: str55    chapter_number: int56    word_count: int57 58 59def extract_ebook_text(ebook_path: str) -> Tuple[EbookMetadata, List[ChapterContent]]:60    """61    Extract text and metadata from an ebook file62 63    Args:64        ebook_path: Path to ebook file65 66    Returns:67        Tuple of (metadata, chapters)68 69    Raises:70        ValueError: If format is unsupported71        FileNotFoundError: If file doesn't exist72    """73 74    if not os.path.exists(ebook_path):75        raise FileNotFoundError(f"Ebook file not found: {ebook_path}")76 77    # Detect format78    ext = Path(ebook_path).suffix.lower()79 80    if ext == '.epub':81        return _extract_epub(ebook_path)82    elif ext == '.pdf':83        return _extract_pdf(ebook_path)84    elif ext in ['.txt', '.html', '.htm']:85        return _extract_text(ebook_path, ext)86    else:87        raise ValueError(88            f"Unsupported format: {ext}\n"89            f"Supported: .epub, .pdf, .txt, .html"90        )91 92 93def _extract_epub(ebook_path: str) -> Tuple[EbookMetadata, List[ChapterContent]]:94    """Extract text from EPUB file"""95 96    if not HAS_EBOOKLIB:97        raise ImportError("ebooklib required for EPUB. Install with: pip install ebooklib")98 99    if not HAS_BS4:100        raise ImportError("beautifulsoup4 required for EPUB. Install with: pip install beautifulsoup4")101 102    try:103        book = epub.read_epub(ebook_path)104    except Exception as e:105        raise ValueError(f"Failed to read EPUB file: {e}")106 107    # Extract metadata108    title = book.get_metadata('DC', 'title')109    title = title[0][0] if title else Path(ebook_path).stem110 111    author = book.get_metadata('DC', 'creator')112    author = author[0][0] if author else "Unknown"113 114    # Extract chapters115    chapters = []116    chapter_num = 0117 118    for item in book.get_items():119        if item.get_type() == ebooklib.ITEM_DOCUMENT:120            # Parse HTML content121            soup = BeautifulSoup(item.get_content(), 'html.parser')122 123            # Extract text124            text = soup.get_text()125            text = _clean_text(text)126 127            if len(text.strip()) < 50:  # Skip very short sections128                continue129 130            chapter_num += 1131 132            # Try to extract chapter title133            chapter_title = f"Chapter {chapter_num}"134            h_tags = soup.find_all(['h1', 'h2', 'h3'])135            if h_tags:136                chapter_title = h_tags[0].get_text().strip()137 138            word_count = len(text.split())139 140            chapters.append(ChapterContent(141                title=chapter_title,142                text=text,143                chapter_number=chapter_num,144                word_count=word_count145            ))146 147    # Calculate totals148    total_words = sum(c.word_count for c in chapters)149    total_chars = sum(len(c.text) for c in chapters)150 151    metadata = EbookMetadata(152        title=title,153        author=author,154        format="EPUB",155        word_count=total_words,156        character_count=total_chars,157        chapter_count=len(chapters)158    )159 160    return metadata, chapters161 162 163def _extract_pdf(ebook_path: str) -> Tuple[EbookMetadata, List[ChapterContent]]:164    """Extract text from PDF file"""165 166    if not HAS_PYMUPDF:167        raise ImportError(168            "pymupdf4llm required for PDF extraction. "169            "Install with: pip install pymupdf4llm"170        )171 172    try:173        # Extract markdown from PDF174        md_text = pymupdf4llm.to_markdown(ebook_path)175    except Exception as e:176        raise ValueError(f"Failed to extract PDF: {e}")177 178    # Split into chapters (look for markdown headers)179    chapters = []180    current_chapter = []181    current_title = "Chapter 1"182    chapter_num = 0183 184    for line in md_text.split('\n'):185        # Check for chapter markers (# headers)186        if re.match(r'^#+\s+', line):187            # Save previous chapter if exists188            if current_chapter:189                chapter_num += 1190                text = '\n'.join(current_chapter)191                text = _clean_text(text)192 193                chapters.append(ChapterContent(194                    title=current_title,195                    text=text,196                    chapter_number=chapter_num,197                    word_count=len(text.split())198                ))199                current_chapter = []200 201            # Start new chapter202            current_title = line.lstrip('#').strip()203        else:204            current_chapter.append(line)205 206    # Add final chapter207    if current_chapter:208        chapter_num += 1209        text = '\n'.join(current_chapter)210        text = _clean_text(text)211 212        chapters.append(ChapterContent(213            title=current_title,214            text=text,215            chapter_number=chapter_num,216            word_count=len(text.split())217        ))218 219    # If no chapters detected, treat whole doc as one chapter220    if not chapters:221        text = _clean_text(md_text)222        chapters = [ChapterContent(223            title="Full Document",224            text=text,225            chapter_number=1,226            word_count=len(text.split())227        )]228 229    # Calculate totals230    total_words = sum(c.word_count for c in chapters)231    total_chars = sum(len(c.text) for c in chapters)232 233    title = Path(ebook_path).stem234 235    metadata = EbookMetadata(236        title=title,237        author="Unknown",238        format="PDF",239        word_count=total_words,240        character_count=total_chars,241        chapter_count=len(chapters)242    )243 244    return metadata, chapters245 246 247def _extract_text(ebook_path: str, ext: str) -> Tuple[EbookMetadata, List[ChapterContent]]:248    """Extract text from TXT or HTML file"""249 250    try:251        with open(ebook_path, 'r', encoding='utf-8') as f:252            content = f.read()253    except UnicodeDecodeError:254        # Try with different encoding255        with open(ebook_path, 'r', encoding='latin-1') as f:256            content = f.read()257 258    # Parse HTML if needed259    if ext in ['.html', '.htm']:260        if not HAS_BS4:261            raise ImportError("beautifulsoup4 required for HTML. Install with: pip install beautifulsoup4")262 263        soup = BeautifulSoup(content, 'html.parser')264        content = soup.get_text()265 266    content = _clean_text(content)267 268    # Try to detect chapters (look for "Chapter" markers)269    chapter_pattern = r'(?:^|\n)(?:Chapter|CHAPTER|Ch\.?)\s+(\d+|[IVXLCDM]+)'270    chapter_splits = list(re.finditer(chapter_pattern, content))271 272    chapters = []273 274    if chapter_splits:275        # Split by detected chapters276        for i, match in enumerate(chapter_splits):277            start = match.start()278            end = chapter_splits[i+1].start() if i+1 < len(chapter_splits) else len(content)279 280            chapter_text = content[start:end]281            chapter_title = match.group(0).strip()282            chapter_text = _clean_text(chapter_text)283 284            chapters.append(ChapterContent(285                title=chapter_title,286                text=chapter_text,287                chapter_number=i+1,288                word_count=len(chapter_text.split())289            ))290    else:291        # No chapters detected, treat as single chapter292        chapters = [ChapterContent(293            title="Full Text",294            text=content,295            chapter_number=1,296            word_count=len(content.split())297        )]298 299    # Calculate totals300    total_words = sum(c.word_count for c in chapters)301    total_chars = sum(len(c.text) for c in chapters)302 303    title = Path(ebook_path).stem304    format_name = ext.upper().lstrip('.')305 306    metadata = EbookMetadata(307        title=title,308        author="Unknown",309        format=format_name,310        word_count=total_words,311        character_count=total_chars,312        chapter_count=len(chapters)313    )314 315    return metadata, chapters316 317 318def _clean_text(text: str) -> str:319    """Clean extracted text"""320 321    # Remove excessive whitespace322    text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text)323 324    # Remove page numbers (common pattern)325    text = re.sub(r'\n\s*\d+\s*\n', '\n', text)326 327    # Normalize spaces328    text = re.sub(r'[ \t]+', ' ', text)329 330    # Remove leading/trailing whitespace331    text = text.strip()332 333    return text334 335 336# Export main function337__all__ = ['extract_ebook_text', 'EbookMetadata', 'ChapterContent']338