feat: video-flow initial commit
- 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
This commit is contained in:
151
modules/project.py
Normal file
151
modules/project.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user