- app.py: Streamlit UI for video generation workflow - main_flow.py: CLI tool with argparse support - modules/: Business logic modules (script_gen, image_gen, video_gen, composer, etc.) - config.py: Configuration with API keys and paths - requirements.txt: Python dependencies - docs/: System prompt documentation
152 lines
3.9 KiB
Python
152 lines
3.9 KiB
Python
"""
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|