""" Project storage service for saving and loading Chatterbox TTS projects. """ import json import os import asyncio from pathlib import Path from typing import List, Optional from datetime import datetime from models import DialogProject, DialogLine class ProjectStorage: """Handles saving and loading projects to/from JSON files.""" def __init__(self, storage_dir: str = "projects"): self.storage_dir = Path(storage_dir) self.storage_dir.mkdir(exist_ok=True) async def save_project(self, project: DialogProject) -> bool: """Save a project to a JSON file.""" try: project_file = self.storage_dir / f"{project.id}.json" # Convert to dict and ensure timestamps are strings project_data = project.dict() project_data["last_modified"] = datetime.now().isoformat() # Ensure created_at is set if not already if not project_data.get("created_at"): project_data["created_at"] = datetime.now().isoformat() with open(project_file, 'w', encoding='utf-8') as f: json.dump(project_data, f, indent=2, ensure_ascii=False) return True except Exception as e: print(f"Error saving project {project.id}: {e}") return False async def load_project(self, project_id: str) -> Optional[DialogProject]: """Load a project from a JSON file.""" try: project_file = self.storage_dir / f"{project_id}.json" if not project_file.exists(): return None with open(project_file, 'r', encoding='utf-8') as f: project_data = json.load(f) # Validate that audio files still exist for line in project_data.get("lines", []): if line.get("audio_url"): audio_path = Path("dialog_output") / line["audio_url"].split("/")[-1] if not audio_path.exists(): line["audio_url"] = None line["status"] = "pending" return DialogProject(**project_data) except Exception as e: print(f"Error loading project {project_id}: {e}") return None async def list_projects(self) -> List[dict]: """List all saved projects with metadata.""" projects = [] for project_file in self.storage_dir.glob("*.json"): try: with open(project_file, 'r', encoding='utf-8') as f: project_data = json.load(f) projects.append({ "id": project_data["id"], "name": project_data["name"], "created_at": project_data.get("created_at"), "last_modified": project_data.get("last_modified"), "line_count": len(project_data.get("lines", [])), "has_audio": any(line.get("audio_url") for line in project_data.get("lines", [])) }) except Exception as e: print(f"Error reading project file {project_file}: {e}") continue # Sort by last modified (most recent first) projects.sort(key=lambda x: x.get("last_modified", ""), reverse=True) return projects async def delete_project(self, project_id: str) -> bool: """Delete a saved project.""" try: project_file = self.storage_dir / f"{project_id}.json" if project_file.exists(): project_file.unlink() return True return False except Exception as e: print(f"Error deleting project {project_id}: {e}") return False async def project_exists(self, project_id: str) -> bool: """Check if a project exists in storage.""" project_file = self.storage_dir / f"{project_id}.json" return project_file.exists() # Global storage instance project_storage = ProjectStorage()