Migration of application to own repo
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user