342 lines
11 KiB
Python
Executable File
342 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate demo GIF showing the complete library ↔ reading workflow.
|
|
|
|
This script creates an animated GIF demonstrating:
|
|
1. Library view with multiple books
|
|
2. Selecting a book by tapping
|
|
3. Reading the book (showing 5 pages)
|
|
4. Closing the book (back to library)
|
|
5. Reopening the same book
|
|
6. Auto-resuming at the saved position
|
|
|
|
Usage:
|
|
python generate_library_demo_gif.py path/to/library/directory output.gif
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
# Add parent directory to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from dreader.library import LibraryManager
|
|
from dreader.application import EbookReader
|
|
from dreader.gesture import TouchEvent, GestureType
|
|
|
|
|
|
def add_annotation(image: Image.Image, text: str, position: str = "top") -> Image.Image:
|
|
"""
|
|
Add annotation text to an image.
|
|
|
|
Args:
|
|
image: PIL Image to annotate
|
|
text: Annotation text
|
|
position: "top" or "bottom"
|
|
|
|
Returns:
|
|
New image with annotation
|
|
"""
|
|
# Create a copy
|
|
img = image.copy()
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Try to use a nice font, fall back to default
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
# Get text size
|
|
bbox = draw.textbbox((0, 0), text, font=font)
|
|
text_width = bbox[2] - bbox[0]
|
|
text_height = bbox[3] - bbox[1]
|
|
|
|
# Calculate position
|
|
x = (img.width - text_width) // 2
|
|
if position == "top":
|
|
y = 20
|
|
else:
|
|
y = img.height - text_height - 20
|
|
|
|
# Draw background rectangle
|
|
padding = 10
|
|
draw.rectangle(
|
|
[x - padding, y - padding, x + text_width + padding, y + text_height + padding],
|
|
fill=(0, 0, 0, 200)
|
|
)
|
|
|
|
# Draw text
|
|
draw.text((x, y), text, fill=(255, 255, 255), font=font)
|
|
|
|
return img
|
|
|
|
|
|
def add_tap_indicator(image: Image.Image, x: int, y: int, label: str = "TAP") -> Image.Image:
|
|
"""
|
|
Add a visual tap indicator at coordinates.
|
|
|
|
Args:
|
|
image: PIL Image
|
|
x, y: Tap coordinates
|
|
label: Label text
|
|
|
|
Returns:
|
|
New image with tap indicator
|
|
"""
|
|
img = image.copy()
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Draw circle at tap location
|
|
radius = 30
|
|
draw.ellipse(
|
|
[x - radius, y - radius, x + radius, y + radius],
|
|
outline=(255, 0, 0),
|
|
width=4
|
|
)
|
|
|
|
# Draw crosshair
|
|
cross_size = 10
|
|
draw.line([x - cross_size, y, x + cross_size, y], fill=(255, 0, 0), width=3)
|
|
draw.line([x, y - cross_size, x, y + cross_size], fill=(255, 0, 0), width=3)
|
|
|
|
# Draw label
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
bbox = draw.textbbox((0, 0), label, font=font)
|
|
text_width = bbox[2] - bbox[0]
|
|
text_height = bbox[3] - bbox[1]
|
|
|
|
# Position label above tap
|
|
label_x = x - text_width // 2
|
|
label_y = y - radius - text_height - 10
|
|
|
|
# Background for label
|
|
padding = 5
|
|
draw.rectangle(
|
|
[label_x - padding, label_y - padding,
|
|
label_x + text_width + padding, label_y + text_height + padding],
|
|
fill=(255, 0, 0)
|
|
)
|
|
draw.text((label_x, label_y), label, fill=(255, 255, 255), font=font)
|
|
|
|
return img
|
|
|
|
|
|
def generate_library_demo_gif(library_path: str, output_path: str):
|
|
"""
|
|
Generate the demo GIF.
|
|
|
|
Args:
|
|
library_path: Path to directory containing EPUB files
|
|
output_path: Output GIF file path
|
|
"""
|
|
frames = []
|
|
frame_durations = [] # Duration for each frame in milliseconds
|
|
|
|
print("Generating library demo GIF...")
|
|
print("=" * 70)
|
|
|
|
# ===================================================================
|
|
# FRAME 1: Library view
|
|
# ===================================================================
|
|
print("\n1. Rendering library view...")
|
|
library = LibraryManager(
|
|
library_path=library_path,
|
|
page_size=(800, 1200)
|
|
)
|
|
|
|
books = library.scan_library()
|
|
print(f" Found {len(books)} books")
|
|
|
|
if len(books) == 0:
|
|
print("Error: No books found in library")
|
|
sys.exit(1)
|
|
|
|
library_image = library.render_library()
|
|
annotated = add_annotation(library_image, "📚 My Library - Select a book", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(2000) # Hold for 2 seconds
|
|
|
|
# ===================================================================
|
|
# FRAME 2: Show tap on first book
|
|
# ===================================================================
|
|
print("2. Showing book selection...")
|
|
tap_x, tap_y = 400, 150 # Approximate position of first book
|
|
tap_frame = add_tap_indicator(library_image, tap_x, tap_y, "SELECT BOOK")
|
|
annotated = add_annotation(tap_frame, "📚 Tap to open book", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(1500)
|
|
|
|
# Get the selected book
|
|
selected_book = library.handle_library_tap(tap_x, tap_y)
|
|
if not selected_book:
|
|
selected_book = books[0]['path']
|
|
|
|
print(f" Selected: {selected_book}")
|
|
|
|
# ===================================================================
|
|
# FRAME 3-7: Reading pages
|
|
# ===================================================================
|
|
print("3. Opening book and reading pages...")
|
|
reader = EbookReader(
|
|
page_size=(800, 1200),
|
|
margin=40,
|
|
background_color=(255, 255, 255)
|
|
)
|
|
|
|
reader.load_epub(selected_book)
|
|
book_info = reader.get_book_info()
|
|
print(f" Title: {book_info['title']}")
|
|
|
|
# First page
|
|
page = reader.get_current_page()
|
|
annotated = add_annotation(page, f"📖 {book_info['title']} - Page 1", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(1500)
|
|
|
|
# Turn 4 more pages (total 5 pages)
|
|
for i in range(2, 6):
|
|
print(f" Reading page {i}...")
|
|
reader.next_page()
|
|
page = reader.get_current_page()
|
|
annotated = add_annotation(page, f"📖 Reading - Page {i}", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(1000) # Faster page turns
|
|
|
|
# ===================================================================
|
|
# FRAME 8: Show settings overlay with "Back to Library"
|
|
# ===================================================================
|
|
print("4. Opening settings overlay...")
|
|
settings_overlay = reader.open_settings_overlay()
|
|
if settings_overlay:
|
|
annotated = add_annotation(settings_overlay, "⚙️ Settings - Tap 'Back to Library'", "top")
|
|
# Show where to tap (estimated position of back button)
|
|
tap_frame = add_tap_indicator(annotated, 400, 950, "BACK")
|
|
frames.append(tap_frame)
|
|
frame_durations.append(2000)
|
|
|
|
# ===================================================================
|
|
# FRAME 9: Save position and return to library
|
|
# ===================================================================
|
|
print("5. Saving position and returning to library...")
|
|
# Save current position for resume
|
|
reader.save_position("__auto_resume__")
|
|
pos_info = reader.get_position_info()
|
|
saved_progress = pos_info['progress'] * 100
|
|
print(f" Saved at {saved_progress:.1f}% progress")
|
|
|
|
# Close reader
|
|
reader.close()
|
|
|
|
# Re-render library
|
|
library_image = library.render_library()
|
|
annotated = add_annotation(library_image, "📚 Back to Library (position saved)", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(2000)
|
|
|
|
# ===================================================================
|
|
# FRAME 10: Tap same book again
|
|
# ===================================================================
|
|
print("6. Re-selecting same book...")
|
|
tap_frame = add_tap_indicator(library_image, tap_x, tap_y, "REOPEN")
|
|
annotated = add_annotation(tap_frame, "📚 Tap to reopen book", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(1500)
|
|
|
|
# ===================================================================
|
|
# FRAME 11: Reopen book and auto-resume
|
|
# ===================================================================
|
|
print("7. Reopening book with auto-resume...")
|
|
reader2 = EbookReader(
|
|
page_size=(800, 1200),
|
|
margin=40,
|
|
background_color=(255, 255, 255)
|
|
)
|
|
|
|
reader2.load_epub(selected_book)
|
|
|
|
# Load saved position
|
|
resumed_page = reader2.load_position("__auto_resume__")
|
|
if resumed_page:
|
|
pos_info = reader2.get_position_info()
|
|
progress = pos_info['progress'] * 100
|
|
print(f" ✓ Resumed at {progress:.1f}% progress")
|
|
|
|
annotated = add_annotation(resumed_page, f"✅ Auto-resumed at {progress:.1f}%", "top")
|
|
frames.append(annotated)
|
|
frame_durations.append(3000) # Hold final frame longer
|
|
|
|
reader2.close()
|
|
|
|
# ===================================================================
|
|
# Save GIF
|
|
# ===================================================================
|
|
print("\n8. Saving GIF...")
|
|
print(f" Total frames: {len(frames)}")
|
|
print(f" Output: {output_path}")
|
|
|
|
# Save as GIF with variable durations
|
|
frames[0].save(
|
|
output_path,
|
|
save_all=True,
|
|
append_images=frames[1:],
|
|
duration=frame_durations,
|
|
loop=0, # Loop forever
|
|
optimize=False # Keep quality
|
|
)
|
|
|
|
print(f"\n✓ Demo GIF created: {output_path}")
|
|
print(f" Size: {os.path.getsize(output_path) / 1024 / 1024:.1f} MB")
|
|
|
|
# Cleanup
|
|
library.cleanup()
|
|
|
|
print("\n" + "=" * 70)
|
|
print("Demo complete!")
|
|
print("\nThe GIF demonstrates:")
|
|
print(" 1. Library view with book selection")
|
|
print(" 2. Opening a book and reading 5 pages")
|
|
print(" 3. Settings overlay with 'Back to Library' button")
|
|
print(" 4. Returning to library (with position saved)")
|
|
print(" 5. Reopening the same book")
|
|
print(" 6. Auto-resuming at saved position")
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python generate_library_demo_gif.py path/to/library [output.gif]")
|
|
print("\nExample:")
|
|
print(" python generate_library_demo_gif.py tests/data/library-epub/")
|
|
print(" python generate_library_demo_gif.py tests/data/library-epub/ doc/images/custom_demo.gif")
|
|
sys.exit(1)
|
|
|
|
library_path = sys.argv[1]
|
|
output_path = sys.argv[2] if len(sys.argv) > 2 else "doc/images/library_reading_demo.gif"
|
|
|
|
if not os.path.exists(library_path):
|
|
print(f"Error: Directory not found: {library_path}")
|
|
sys.exit(1)
|
|
|
|
if not os.path.isdir(library_path):
|
|
print(f"Error: Not a directory: {library_path}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
generate_library_demo_gif(library_path, output_path)
|
|
except Exception as e:
|
|
print(f"\nError generating demo: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|