#!/usr/bin/env python3 """ Generate animated GIFs demonstrating EbookReader functionality. This script creates animated GIFs showcasing: 1. Page navigation (next/previous) 2. Font size adjustment 3. Chapter navigation 4. Bookmark/position management 5. Word highlighting The GIFs are saved to the examples/ directory and can be included in documentation. Usage: python generate_ereader_gifs.py path/to/book.epub [output_dir] Example: python generate_ereader_gifs.py ../tests/data/test.epub ../docs/images """ import sys import os from pathlib import Path from typing import List from pyweblayout_ereader import EbookReader from pyWebLayout.core.highlight import HighlightColor from PIL import Image def create_gif(images: List[Image.Image], output_path: str, duration: int = 800, loop: int = 0): """ Create an animated GIF from a list of PIL Images. Args: images: List of PIL Images to animate output_path: Path where to save the GIF duration: Duration of each frame in milliseconds loop: Number of loops (0 = infinite) """ if not images: print(f"Warning: No images provided for {output_path}") return False try: # Save as animated GIF images[0].save( output_path, save_all=True, append_images=images[1:], duration=duration, loop=loop, optimize=False ) print(f"✓ Created: {output_path} ({len(images)} frames)") return True except Exception as e: print(f"✗ Error creating {output_path}: {e}") return False def generate_page_navigation_gif(reader: EbookReader, output_path: str): """Generate GIF showing page navigation (forward and backward).""" print("\n[1/4] Generating page navigation GIF...") frames = [] # Go to beginning reader.set_font_size(1.0) # Capture 5 pages going forward for i in range(5): page = reader.get_current_page() if page: frames.append(page.copy()) reader.next_page() # Go back to start for _ in range(4): reader.previous_page() # Capture 5 pages going forward again (smoother loop) for i in range(5): page = reader.get_current_page() if page: frames.append(page.copy()) reader.next_page() create_gif(frames, output_path, duration=600) def generate_font_size_gif(reader: EbookReader, output_path: str): """Generate GIF showing font size adjustment.""" print("\n[2/4] Generating font size adjustment GIF...") frames = [] # Reset to beginning and normal font for _ in range(10): reader.previous_page() reader.set_font_size(1.0) # Font sizes to demonstrate font_scales = [0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.3, 1.2, 1.1, 1.0, 0.9, 0.8] for scale in font_scales: page = reader.set_font_size(scale) if page: frames.append(page.copy()) # Reset to normal reader.set_font_size(1.0) create_gif(frames, output_path, duration=500) def generate_chapter_navigation_gif(reader: EbookReader, output_path: str): """Generate GIF showing chapter navigation.""" print("\n[3/4] Generating chapter navigation GIF...") frames = [] # Reset font reader.set_font_size(1.0) # Get chapters chapters = reader.get_chapters() if len(chapters) == 0: print(" Warning: No chapters found, skipping chapter navigation GIF") return # Visit first few chapters (or loop through available chapters) chapter_indices = list(range(min(5, len(chapters)))) # Add some chapters twice for smoother animation for idx in chapter_indices: page = reader.jump_to_chapter(idx) if page: frames.append(page.copy()) # Add a second frame at each chapter for pause effect frames.append(page.copy()) # Go back to first chapter page = reader.jump_to_chapter(0) if page: frames.append(page.copy()) if frames: create_gif(frames, output_path, duration=1000) else: print(" Warning: No frames captured for chapter navigation") def generate_bookmark_gif(reader: EbookReader, output_path: str): """Generate GIF showing bookmark save/load functionality.""" print("\n[4/5] Generating bookmark/position GIF...") frames = [] # Reset font reader.set_font_size(1.0) # Go to beginning for _ in range(20): reader.previous_page() # Capture initial position page = reader.get_current_page() if page: frames.append(page.copy()) frames.append(page.copy()) # Hold frame # Navigate forward a bit for i in range(3): reader.next_page() page = reader.get_current_page() if page: frames.append(page.copy()) # Save this position reader.save_position("demo_bookmark") page = reader.get_current_page() if page: frames.append(page.copy()) frames.append(page.copy()) # Hold frame to show saved position # Navigate away for i in range(5): reader.next_page() page = reader.get_current_page() if page: frames.append(page.copy()) # Hold at distant position page = reader.get_current_page() if page: frames.append(page.copy()) frames.append(page.copy()) # Jump back to bookmark page = reader.load_position("demo_bookmark") if page: frames.append(page.copy()) frames.append(page.copy()) frames.append(page.copy()) # Hold longer to show we're back create_gif(frames, output_path, duration=600) def generate_highlighting_gif(reader: EbookReader, output_path: str): """Generate GIF showing word highlighting functionality.""" print("\n[5/5] Generating word highlighting GIF...") frames = [] # Reset font reader.set_font_size(1.0) # Find a page with actual text content (skip title/cover pages) for _ in range(5): reader.next_page() # Collect text objects from the page with their actual positions from pyWebLayout.concrete.text import Line text_positions = [] # Try to find a page with text max_attempts = 10 for attempt in range(max_attempts): page = reader.manager.get_current_page() text_positions = [] for child in page._children: if isinstance(child, Line): for text_obj in child._text_objects: # Skip empty text if not hasattr(text_obj, '_text') or not text_obj._text or not text_obj._text.strip(): continue # Calculate center of text object, but clamp Y to Line bounds origin = text_obj._origin size = text_obj.size center_x = int(origin[0] + size[0] / 2) center_y = int(origin[1] + size[1] / 2) # Clamp Y to be within Line bounds (avoids the baseline extension issue) line_y_min = int(child._origin[1]) line_y_max = int(child._origin[1] + child._size[1]) clamped_y = max(line_y_min, min(line_y_max - 1, center_y)) text_positions.append((center_x, clamped_y, text_obj._text)) # If we found enough text, use this page if len(text_positions) > 10: print(f" Found page with {len(text_positions)} words") break # Otherwise try next page reader.next_page() if len(text_positions) == 0: print(" Warning: Could not find a page with text after searching") # Capture initial page without highlights page_img = reader.get_current_page(include_highlights=False) if page_img: frames.append(page_img.copy()) frames.append(page_img.copy()) # Hold frame # Use different colors for highlighting colors = [ HighlightColor.YELLOW.value, HighlightColor.GREEN.value, HighlightColor.BLUE.value, HighlightColor.PINK.value, HighlightColor.ORANGE.value, ] # Select a subset of words to highlight (spread across the page) # Take every Nth word to get a good distribution if len(text_positions) > 10: step = len(text_positions) // 5 selected_positions = [text_positions[i * step] for i in range(5) if i * step < len(text_positions)] else: selected_positions = text_positions[:5] highlighted_words = 0 color_names = ['YELLOW', 'GREEN', 'BLUE', 'PINK', 'ORANGE'] print(f"\n Highlighting words:") for i, (x, y, text) in enumerate(selected_positions): color = colors[i % len(colors)] color_name = color_names[i % len(color_names)] # Highlight the word at this position highlight_id = reader.highlight_word(x, y, color=color) if highlight_id: highlighted_words += 1 print(f" [{color_name:6s}] {text}") # Capture page with new highlight page_img = reader.get_current_page(include_highlights=True) if page_img: frames.append(page_img.copy()) # Hold frame briefly to show the new highlight frames.append(page_img.copy()) # If we managed to highlight any words, show the final result if highlighted_words > 0: page_img = reader.get_current_page(include_highlights=True) if page_img: # Hold final frame longer for _ in range(3): frames.append(page_img.copy()) # Clear highlights one by one for highlight in reader.list_highlights(): reader.remove_highlight(highlight.id) page_img = reader.get_current_page(include_highlights=True) if page_img: frames.append(page_img.copy()) # Show final cleared page page_img = reader.get_current_page(include_highlights=False) if page_img: frames.append(page_img.copy()) frames.append(page_img.copy()) print(f" Successfully highlighted {highlighted_words} words") else: print(" Warning: No words found to highlight on current page") if frames: create_gif(frames, output_path, duration=700) else: print(" Warning: No frames captured for highlighting") def main(): """Main function to generate all GIFs.""" if len(sys.argv) < 2: print("Usage: python generate_ereader_gifs.py path/to/book.epub [output_dir]") print("\nExample:") print(" python generate_ereader_gifs.py ../tests/data/test.epub ../docs/images") sys.exit(1) epub_path = sys.argv[1] output_dir = sys.argv[2] if len(sys.argv) > 2 else "." # Validate EPUB path if not os.path.exists(epub_path): print(f"Error: EPUB file not found: {epub_path}") sys.exit(1) # Create output directory os.makedirs(output_dir, exist_ok=True) print("="*70) print(" EbookReader Animated GIF Generator") print("="*70) print(f"\nInput EPUB: {epub_path}") print(f"Output directory: {output_dir}") # Create paths for output GIFs nav_gif = os.path.join(output_dir, "ereader_page_navigation.gif") font_gif = os.path.join(output_dir, "ereader_font_size.gif") chapter_gif = os.path.join(output_dir, "ereader_chapter_navigation.gif") bookmark_gif = os.path.join(output_dir, "ereader_bookmarks.gif") highlight_gif = os.path.join(output_dir, "ereader_highlighting.gif") try: # Create reader with EbookReader(page_size=(600, 800), margin=30) as reader: # Load EPUB print("\nLoading EPUB...") if not reader.load_epub(epub_path): print("Error: Failed to load EPUB file") sys.exit(1) print("✓ EPUB loaded successfully") # Get book info book_info = reader.get_book_info() print(f"\nBook: {book_info['title']}") print(f"Author: {book_info['author']}") print(f"Chapters: {book_info['total_chapters']}") print(f"Blocks: {book_info['total_blocks']}") print("\nGenerating GIFs...") print("-" * 70) # Generate all GIFs generate_page_navigation_gif(reader, nav_gif) generate_font_size_gif(reader, font_gif) generate_chapter_navigation_gif(reader, chapter_gif) generate_bookmark_gif(reader, bookmark_gif) generate_highlighting_gif(reader, highlight_gif) print("\n" + "="*70) print(" Generation Complete!") print("="*70) print("\nGenerated files:") for gif_path in [nav_gif, font_gif, chapter_gif, bookmark_gif, highlight_gif]: if os.path.exists(gif_path): size = os.path.getsize(gif_path) print(f" ✓ {gif_path} ({size/1024:.1f} KB)") print("\nYou can now add these GIFs to your README.md!") except Exception as e: print(f"\nError: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()