The rebuilt editor never exposed character creation/assignment, so the feature was vestigial: dead frontend plumbing plus generation branches that only fired for legacy/imported projects. Removed it wholesale per decision. Backend: - Deleted movie_editor/backend/characters.py and the /library/characters routes. - timeline.py: dropped normalize_character_bible / build_characters_anchor / _load_character_map / _scene_character_prefix / character_media_refs_for_project and friends. effective_anchor/effective_negative_prompt now return the project's manual text; resolve_scene_identity_pin(cs) returns the project-level identity pin only. Removed Scene.character_ids and Project.character_bible fields and the character_ids fingerprint key. - Continuity auto-guides keep working off continuity_settings.identity_pin_ref. Frontend: - store.js: removed state.characters, the character preview-key plumbing, load/save/deleteCharacter, sceneCharacterIds/toggleSceneCharacter, character_ids in patch/archive paths and exports. - timeline.js: dropped clip-char badge rendering; api.js: dropped character routes; view_bus.js: dropped chars fingerprint; tour.js: dropped demo characters + copy; styles.css: dropped .clip-chars/.clip-char. Tests: removed test_characters.py and the character-bible timeline tests; added manual-anchor / manual-negative / project-identity-pin tests. 133 passed, 4 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
1.8 KiB
Python
69 lines
1.8 KiB
Python
"""On-disk JSON project store. One file per project, keyed by id.
|
|
|
|
Deliberately simple and synchronous — projects are small. This is the persistence
|
|
seam that Phase 2+ extends with cached per-scene latents/assets.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
from typing import Optional
|
|
|
|
from . import config
|
|
from .timeline import Project, Scene
|
|
|
|
|
|
def _path(project_id: str):
|
|
return config.PROJECTS_DIR / f"{project_id}.json"
|
|
|
|
|
|
def list_projects() -> list[dict]:
|
|
config.ensure_dirs()
|
|
out: list[dict] = []
|
|
for p in sorted(config.PROJECTS_DIR.glob("*.json")):
|
|
try:
|
|
d = json.loads(p.read_text())
|
|
except (json.JSONDecodeError, OSError):
|
|
continue
|
|
out.append({
|
|
"id": d.get("id", p.stem),
|
|
"name": d.get("name", p.stem),
|
|
"scene_count": len(d.get("scenes", [])),
|
|
"updated_at": d.get("updated_at", 0),
|
|
})
|
|
out.sort(key=lambda x: x.get("updated_at", 0), reverse=True)
|
|
return out
|
|
|
|
|
|
def get(project_id: str) -> Optional[Project]:
|
|
p = _path(project_id)
|
|
if not p.exists():
|
|
return None
|
|
return Project.from_dict(json.loads(p.read_text()))
|
|
|
|
|
|
def save(project: Project) -> Project:
|
|
config.ensure_dirs()
|
|
project.updated_at = time.time()
|
|
path = _path(project.id)
|
|
payload = json.dumps(project.to_dict(), indent=2)
|
|
tmp = path.with_suffix(".json.tmp")
|
|
tmp.write_text(payload)
|
|
tmp.replace(path)
|
|
return project
|
|
|
|
|
|
def create(name: str = "Untitled") -> Project:
|
|
project = Project(name=name)
|
|
project.anchor = "Optional: describe the subjects or world shared by all scenes"
|
|
project.scenes = [Scene(text="Describe your first scene here…")]
|
|
return save(project)
|
|
|
|
|
|
def delete(project_id: str) -> bool:
|
|
p = _path(project_id)
|
|
if p.exists():
|
|
p.unlink()
|
|
return True
|
|
return False
|