pyPhotoAlbum/examples/basic_usage.py
Duncan Tourolle 46585228fd
Some checks failed
Lint / lint (push) Failing after 2m46s
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
first commit
2025-10-21 22:02:49 +02:00

427 lines
14 KiB
Python

#!/usr/bin/env python3
"""
Basic Usage Example for pyPhotoAlbum
This example demonstrates:
- Creating a new project
- Adding pages with images and text
- Working with the asset manager
- Saving and loading projects
- Basic element manipulation
Based on unit test examples from the pyPhotoAlbum test suite.
"""
import os
import sys
import tempfile
from pathlib import Path
# Add parent directory to path to import pyPhotoAlbum
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
from pyPhotoAlbum.project_serializer import save_to_zip, load_from_zip, get_project_info
from pyPhotoAlbum.pdf_exporter import PDFExporter
def create_sample_image(path: str, color: str = 'blue', size: tuple = (400, 300)):
"""Create a sample image for testing"""
try:
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', size, color=color)
draw = ImageDraw.Draw(img)
# Draw a border
border_width = 10
draw.rectangle(
[(border_width, border_width), (size[0]-border_width, size[1]-border_width)],
outline='white',
width=5
)
# Add some text
text = f"{color.upper()}\n{size[0]}x{size[1]}"
try:
# Try to use a nice font
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36)
except:
# Fallback to default
font = ImageFont.load_default()
# Calculate text position (center)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (size[0] - text_width) // 2
y = (size[1] - text_height) // 2
draw.text((x, y), text, fill='white', font=font)
img.save(path)
print(f"Created sample image: {path}")
return True
except Exception as e:
print(f"Could not create sample image: {e}")
return False
def example_1_create_basic_project():
"""
Example 1: Create a basic project with one page and an image
This demonstrates the fundamental workflow based on test_project.py
"""
print("\n" + "="*60)
print("Example 1: Creating a Basic Project")
print("="*60)
# Create output directory
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
# Create a temporary directory for the project
temp_dir = tempfile.mkdtemp(prefix="photo_album_")
project_folder = os.path.join(temp_dir, "my_album")
print(f"\nProject folder: {project_folder}")
# Create a new project (from test_project.py)
project = Project(name="My First Album", folder_path=project_folder)
project.page_size_mm = (210, 297) # A4 size
project.working_dpi = 300
project.export_dpi = 300
print(f"Created project: {project.name}")
print(f"Page size: {project.page_size_mm[0]}mm x {project.page_size_mm[1]}mm")
print(f"DPI: {project.working_dpi}")
# Create a sample image
image_path = output_dir / "sample_photo.jpg"
create_sample_image(str(image_path), color='blue', size=(800, 600))
# Import the image into the project (from test_project_serialization.py)
imported_path = project.asset_manager.import_asset(str(image_path))
print(f"\nImported asset: {imported_path}")
# Create a page layout (from test_project.py)
layout = PageLayout(width=210, height=297)
# Add an image element (from test_models.py)
image = ImageData(
image_path=imported_path,
x=10.0,
y=10.0,
width=190.0,
height=140.0,
rotation=0,
z_index=0
)
layout.add_element(image)
print(f"Added image at position ({image.position[0]}, {image.position[1]})")
print(f"Image size: {image.size[0]}mm x {image.size[1]}mm")
# Add a text box (from test_models.py)
textbox = TextBoxData(
text_content="My First Photo Album",
font_settings={"family": "Arial", "size": 24, "color": (0, 0, 0)},
alignment="center",
x=10.0,
y=160.0,
width=190.0,
height=30.0
)
layout.add_element(textbox)
print(f"Added text box: '{textbox.text_content}'")
# Create a page and add it to the project
page = Page(layout=layout, page_number=1)
project.add_page(page)
print(f"\nAdded page {page.page_number} to project")
print(f"Total pages: {len(project.pages)}")
print(f"Total elements on page: {len(page.layout.elements)}")
# Save the project (from test_project_serialization.py)
output_path = output_dir / "basic_project.ppz"
print(f"\nSaving project to: {output_path}")
success, error = save_to_zip(project, str(output_path))
if success:
print("Project saved successfully!")
# Get project info without loading (from test_project_serialization.py)
info = get_project_info(str(output_path))
if info:
print(f"\nProject Info:")
print(f" Name: {info['name']}")
print(f" Pages: {info['page_count']}")
print(f" Version: {info['version']}")
print(f" Working DPI: {info['working_dpi']}")
else:
print(f"Error saving project: {error}")
return str(output_path)
def example_2_load_and_modify_project(project_path: str):
"""
Example 2: Load an existing project and add more pages
Based on test_project_serialization.py
"""
print("\n" + "="*60)
print("Example 2: Loading and Modifying a Project")
print("="*60)
# Load the project (from test_project_serialization.py)
print(f"\nLoading project from: {project_path}")
loaded_project, error = load_from_zip(project_path)
if not loaded_project:
print(f"Error loading project: {error}")
return
print(f"Loaded project: {loaded_project.name}")
print(f"Pages: {len(loaded_project.pages)}")
# Create output directory
output_dir = Path(__file__).parent / "output"
# Create more sample images
for i in range(2, 4):
image_path = output_dir / f"sample_photo_{i}.jpg"
colors = ['red', 'green']
create_sample_image(str(image_path), color=colors[i-2], size=(600, 800))
# Import the image
imported_path = loaded_project.asset_manager.import_asset(str(image_path))
# Create a new page with the image (from test_project.py)
layout = PageLayout(width=210, height=297)
image = ImageData(
image_path=imported_path,
x=20.0 + i*5,
y=20.0 + i*5,
width=170.0,
height=230.0
)
layout.add_element(image)
# Add caption
caption = TextBoxData(
text_content=f"Page {i}",
font_settings={"family": "Arial", "size": 18, "color": (0, 0, 0)},
alignment="center",
x=20.0 + i*5,
y=260.0,
width=170.0,
height=25.0
)
layout.add_element(caption)
page = Page(layout=layout, page_number=i)
loaded_project.add_page(page)
print(f"Added page {i} with {len(page.layout.elements)} elements")
# Save the modified project
modified_path = output_dir / "modified_project.ppz"
print(f"\nSaving modified project to: {modified_path}")
success, error = save_to_zip(loaded_project, str(modified_path))
if success:
print("Modified project saved successfully!")
print(f"Total pages: {len(loaded_project.pages)}")
else:
print(f"Error saving: {error}")
return str(modified_path)
def example_3_serialization_roundtrip():
"""
Example 3: Demonstrate serialization/deserialization
Based on test_models.py serialization tests
"""
print("\n" + "="*60)
print("Example 3: Serialization Round-Trip")
print("="*60)
# Create an image element (from test_models.py)
print("\n1. Creating ImageData element...")
original_image = ImageData(
image_path="test.jpg",
x=50.0,
y=60.0,
width=300.0,
height=200.0,
rotation=15.0,
z_index=2,
crop_info=(0.1, 0.1, 0.9, 0.9)
)
print(f" Position: {original_image.position}")
print(f" Size: {original_image.size}")
print(f" Rotation: {original_image.rotation}°")
print(f" Z-index: {original_image.z_index}")
# Serialize (from test_models.py)
print("\n2. Serializing to dictionary...")
data = original_image.serialize()
print(f" Serialized data keys: {list(data.keys())}")
print(f" Type: {data['type']}")
# Deserialize (from test_models.py)
print("\n3. Deserializing from dictionary...")
restored_image = ImageData()
restored_image.deserialize(data)
print(f" Position: {restored_image.position}")
print(f" Size: {restored_image.size}")
print(f" Rotation: {restored_image.rotation}°")
# Verify round-trip
print("\n4. Verifying round-trip...")
assert restored_image.position == original_image.position
assert restored_image.size == original_image.size
assert restored_image.rotation == original_image.rotation
assert restored_image.z_index == original_image.z_index
assert restored_image.crop_info == original_image.crop_info
print(" Round-trip successful!")
# Do the same for TextBoxData (from test_models.py)
print("\n5. Testing TextBoxData serialization...")
font_settings = {"family": "Georgia", "size": 20, "color": (255, 255, 0)}
original_text = TextBoxData(
text_content="Round Trip Test",
font_settings=font_settings,
alignment="center",
x=85.0,
y=95.0,
width=320.0,
height=120.0,
rotation=25.0,
z_index=9
)
data = original_text.serialize()
restored_text = TextBoxData()
restored_text.deserialize(data)
assert restored_text.text_content == original_text.text_content
assert restored_text.alignment == original_text.alignment
assert restored_text.position == original_text.position
print(" TextBoxData round-trip successful!")
def example_4_export_to_pdf():
"""
Example 4: Export a project to PDF
Based on pdf_exporter.py usage
"""
print("\n" + "="*60)
print("Example 4: Export to PDF")
print("="*60)
# Create a simple project
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
temp_dir = tempfile.mkdtemp(prefix="photo_album_pdf_")
project_folder = os.path.join(temp_dir, "pdf_export")
project = Project(name="PDF Export Example", folder_path=project_folder)
project.page_size_mm = (140, 140) # Square format
project.working_dpi = 300
project.export_dpi = 300
print(f"\nCreating project with {project.page_size_mm[0]}x{project.page_size_mm[1]}mm pages")
# Create multiple pages with different colored images
colors = ['red', 'green', 'blue', 'yellow']
for i, color in enumerate(colors, 1):
# Create sample image
image_path = output_dir / f"pdf_sample_{color}.jpg"
create_sample_image(str(image_path), color=color, size=(600, 600))
# Import and add to page
imported_path = project.asset_manager.import_asset(str(image_path))
layout = PageLayout(width=140, height=140)
image = ImageData(
image_path=imported_path,
x=10.0,
y=10.0,
width=120.0,
height=120.0
)
layout.add_element(image)
page = Page(layout=layout, page_number=i)
project.add_page(page)
print(f"Created {len(project.pages)} pages")
# Export to PDF
pdf_path = output_dir / "example_album.pdf"
print(f"\nExporting to PDF: {pdf_path}")
exporter = PDFExporter(project, export_dpi=300)
def progress_callback(current, total):
print(f" Exporting page {current}/{total}...")
success, errors = exporter.export(
output_path=str(pdf_path),
progress_callback=progress_callback
)
if success:
print(f"\nPDF exported successfully!")
print(f"File size: {os.path.getsize(pdf_path) / 1024:.1f} KB")
else:
print(f"\nErrors during export:")
for error in errors:
print(f" - {error}")
def main():
"""Run all examples"""
print("\n" + "="*60)
print("pyPhotoAlbum - Basic Usage Examples")
print("="*60)
print("\nThese examples demonstrate core functionality using")
print("code patterns from the pyPhotoAlbum unit tests.\n")
try:
# Example 1: Create a basic project
project_path = example_1_create_basic_project()
# Example 2: Load and modify
if project_path and os.path.exists(project_path):
example_2_load_and_modify_project(project_path)
# Example 3: Serialization
example_3_serialization_roundtrip()
# Example 4: PDF export
example_4_export_to_pdf()
print("\n" + "="*60)
print("All examples completed successfully!")
print("="*60)
print(f"\nOutput files are in: {Path(__file__).parent / 'output'}")
except Exception as e:
print(f"\nError running examples: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()