""" MatchMe Studio - Project State Management (R2 Persistence) """ import json import logging import uuid from datetime import datetime from typing import Dict, Any, Optional, List from dataclasses import dataclass, asdict, field import config from modules import storage logger = logging.getLogger(__name__) @dataclass class Scene: id: int duration: int = 5 timeline: str = "" keyframe: Dict[str, str] = field(default_factory=dict) camera_movement: str = "" story_beat: str = "" voiceover: str = "" rhythm: Dict[str, Any] = field(default_factory=dict) image_url: str = "" video_url: str = "" @dataclass class Project: id: str = field(default_factory=lambda: str(uuid.uuid4())[:8]) created_at: str = field(default_factory=lambda: datetime.now().isoformat()) status: str = "draft" # draft | analyzing | scripting | imaging | video | rendering | done # Step 0: Input input_mode: str = "" # text | images | video prompt: str = "" image_urls: List[str] = field(default_factory=list) video_url: str = "" asr_text: str = "" # Step 1: Analysis analysis: str = "" questions: List[Dict[str, Any]] = field(default_factory=list) answers: Dict[str, str] = field(default_factory=dict) # Step 2: Script hook: str = "" scenes: List[Dict[str, Any]] = field(default_factory=list) cta: str = "" # Step 6: Final final_video_url: str = "" bgm_url: str = "" def save_project(project: Project) -> str: """Save project state to R2 as JSON.""" data = asdict(project) json_str = json.dumps(data, ensure_ascii=False, indent=2) # Write to temp file temp_path = config.TEMP_DIR / f"project_{project.id}.json" with open(temp_path, "w", encoding="utf-8") as f: f.write(json_str) # Upload to R2 object_name = f"projects/{project.id}.json" s3 = storage.get_s3_client() try: s3.upload_file( str(temp_path), config.R2_BUCKET_NAME, object_name, ExtraArgs={'ContentType': 'application/json'} ) logger.info(f"Project {project.id} saved to R2") return project.id except Exception as e: logger.error(f"Failed to save project: {e}") raise def load_project(project_id: str) -> Optional[Project]: """Load project state from R2.""" object_name = f"projects/{project_id}.json" temp_path = config.TEMP_DIR / f"project_{project_id}.json" s3 = storage.get_s3_client() try: s3.download_file(config.R2_BUCKET_NAME, object_name, str(temp_path)) with open(temp_path, "r", encoding="utf-8") as f: data = json.load(f) # Reconstruct Project project = Project( id=data.get("id", project_id), created_at=data.get("created_at", ""), status=data.get("status", "draft"), input_mode=data.get("input_mode", ""), prompt=data.get("prompt", ""), image_urls=data.get("image_urls", []), video_url=data.get("video_url", ""), asr_text=data.get("asr_text", ""), analysis=data.get("analysis", ""), questions=data.get("questions", []), answers=data.get("answers", {}), hook=data.get("hook", ""), scenes=data.get("scenes", []), cta=data.get("cta", ""), final_video_url=data.get("final_video_url", ""), bgm_url=data.get("bgm_url", "") ) logger.info(f"Project {project_id} loaded from R2") return project except Exception as e: logger.warning(f"Failed to load project {project_id}: {e}") return None def create_project() -> Project: """Create a new project with unique ID.""" project = Project() logger.info(f"Created new project: {project.id}") return project