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:
Tony Zhang
2025-12-12 19:18:27 +08:00
commit 33a165a615
34 changed files with 12012 additions and 0 deletions

151
modules/project.py Normal file
View 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