pyPhotoAlbum/pyPhotoAlbum/project_serializer.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

155 lines
5.3 KiB
Python

"""
Project serialization to/from ZIP files for pyPhotoAlbum
"""
import os
import json
import zipfile
import shutil
import tempfile
from typing import Optional, Tuple
from pathlib import Path
from pyPhotoAlbum.project import Project
# Version for serialization format - increment when making breaking changes
SERIALIZATION_VERSION = "1.0"
def save_to_zip(project: Project, zip_path: str) -> Tuple[bool, Optional[str]]:
"""
Save a project to a ZIP file, including all assets.
Args:
project: The Project instance to save
zip_path: Path where the ZIP file should be created
Returns:
Tuple of (success: bool, error_message: Optional[str])
"""
try:
# Ensure .ppz extension
if not zip_path.lower().endswith('.ppz'):
zip_path += '.ppz'
# Serialize project to dictionary
project_data = project.serialize()
# Add version information
project_data['serialization_version'] = SERIALIZATION_VERSION
# Create ZIP file
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
# Write project.json
project_json = json.dumps(project_data, indent=2)
zipf.writestr('project.json', project_json)
# Add all files from the assets folder
assets_folder = project.asset_manager.assets_folder
if os.path.exists(assets_folder):
for root, dirs, files in os.walk(assets_folder):
for file in files:
file_path = os.path.join(root, file)
# Store with relative path from project folder
arcname = os.path.relpath(file_path, project.folder_path)
zipf.write(file_path, arcname)
print(f"Project saved to {zip_path}")
return True, None
except Exception as e:
error_msg = f"Error saving project: {str(e)}"
print(error_msg)
return False, error_msg
def load_from_zip(zip_path: str, extract_to: Optional[str] = None) -> Tuple[Optional[Project], Optional[str]]:
"""
Load a project from a ZIP file.
Args:
zip_path: Path to the ZIP file to load
extract_to: Optional directory to extract to. If None, uses a directory
based on the ZIP filename in ./projects/
Returns:
Tuple of (project: Optional[Project], error_message: Optional[str])
"""
try:
if not os.path.exists(zip_path):
return None, f"ZIP file not found: {zip_path}"
# Determine extraction directory
if extract_to is None:
# Extract to ./projects/{zipname}/
zip_basename = os.path.splitext(os.path.basename(zip_path))[0]
extract_to = os.path.join("./projects", zip_basename)
# Create extraction directory
os.makedirs(extract_to, exist_ok=True)
# Extract ZIP contents
with zipfile.ZipFile(zip_path, 'r') as zipf:
zipf.extractall(extract_to)
# Load project.json
project_json_path = os.path.join(extract_to, 'project.json')
if not os.path.exists(project_json_path):
return None, "Invalid project file: project.json not found"
with open(project_json_path, 'r') as f:
project_data = json.load(f)
# Check version compatibility
version = project_data.get('serialization_version', '1.0')
if version != SERIALIZATION_VERSION:
print(f"Warning: Loading project with version {version}, current version is {SERIALIZATION_VERSION}")
# Create new project
project_name = project_data.get('name', 'Untitled Project')
project = Project(name=project_name, folder_path=extract_to)
# Deserialize project data
project.deserialize(project_data)
# Update folder path to extraction location
project.folder_path = extract_to
project.asset_manager.project_folder = extract_to
project.asset_manager.assets_folder = os.path.join(extract_to, "assets")
print(f"Project loaded from {zip_path} to {extract_to}")
return project, None
except Exception as e:
error_msg = f"Error loading project: {str(e)}"
print(error_msg)
return None, error_msg
def get_project_info(zip_path: str) -> Optional[dict]:
"""
Get basic information about a project without fully loading it.
Args:
zip_path: Path to the ZIP file
Returns:
Dictionary with project info, or None if error
"""
try:
with zipfile.ZipFile(zip_path, 'r') as zipf:
# Read project.json
project_json = zipf.read('project.json').decode('utf-8')
project_data = json.loads(project_json)
return {
'name': project_data.get('name', 'Unknown'),
'version': project_data.get('serialization_version', 'Unknown'),
'page_count': len(project_data.get('pages', [])),
'page_size_mm': project_data.get('page_size_mm', (0, 0)),
'working_dpi': project_data.get('working_dpi', 300),
}
except Exception as e:
print(f"Error reading project info: {e}")
return None