plotagon-director 0.9.2__tar.gz → 0.10.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/PKG-INFO +23 -2
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/README.md +22 -1
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/__init__.py +3 -3
- plotagon_director-0.10.1/plotagon_director/casting.py +135 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/cli.py +41 -4
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/archive.py +5 -1
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/decompiler.py +26 -7
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/plotdoc.py +1 -1
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/validator.py +25 -1
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/mcp_server.py +17 -0
- plotagon_director-0.10.1/plotagon_director/schemas/project.schema.json +217 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/PKG-INFO +23 -2
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/SOURCES.txt +1 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/pyproject.toml +57 -52
- plotagon_director-0.9.2/plotagon_director/schemas/project.schema.json +0 -105
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/LICENSE +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/audio/__init__.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/audio/phonemes.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/audio/tts.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/__init__.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/diffplot.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/core/doctor.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/http_api.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/library/__init__.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/library/builder.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/library/data/library.json +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/storyteller.py +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/dependency_links.txt +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/entry_points.txt +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/requires.txt +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/top_level.txt +0 -0
- {plotagon_director-0.9.2 → plotagon_director-0.10.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotagon-director
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Summary: Génération de films Plotagon Studio Desktop (.plot) avec voix Edge TTS et synchro labiale
|
|
5
5
|
Author: Mohamed Amine Ben Mallessa
|
|
6
6
|
License: MIT
|
|
@@ -90,6 +90,13 @@ plotagon-director batch scenarios/ sorties/ # tous les .yaml d'un dossier
|
|
|
90
90
|
|
|
91
91
|
# Importer mon-film.plot dans Plotagon Studio (menu import)
|
|
92
92
|
|
|
93
|
+
# Personnages custom (créés dans le créateur de Plotagon Studio)
|
|
94
|
+
plotagon-director casting add export.plot # récolte les persos d'un export
|
|
95
|
+
plotagon-director casting list [--json] # registre local (./casting)
|
|
96
|
+
# puis dans le cast du yaml : `custom: Leo` au lieu de `character: ...`
|
|
97
|
+
# (clé racine `casting_dir:` optionnelle ; défaut ./casting, sinon
|
|
98
|
+
# variable PLOTAGON_CASTING_DIR) — voix TTS et synchro labiale incluses
|
|
99
|
+
|
|
93
100
|
# Analyse / rétro-ingénierie
|
|
94
101
|
plotagon-director parse export.plot # JSON brut d'un .plot
|
|
95
102
|
plotagon-director decompile export.plot film.yaml # .plot -> YAML éditable
|
|
@@ -165,6 +172,8 @@ Bibliothèque embarquée : 205 scènes (953 positions), 53 personnages,
|
|
|
165
172
|
- `plotagon_director/audio/` — Edge TTS → WAV conforme (cache + parallèle),
|
|
166
173
|
phonèmes/lip-sync
|
|
167
174
|
- `plotagon_director/library/` — builder du catalogue + `data/library.json`
|
|
175
|
+
- `plotagon_director/casting.py` — registre des personnages custom
|
|
176
|
+
(récolte des `.pcd` d'un export, résolution du cast `custom:`)
|
|
168
177
|
- `plotagon_director/storyteller.py` — pipeline vidéo/transcription → .plot
|
|
169
178
|
(`plotagon-storyteller`)
|
|
170
179
|
- `plotagon_director/mcp_server.py` / `http_api.py` — serveur MCP
|
|
@@ -179,6 +188,18 @@ Bibliothèque embarquée : 205 scènes (953 positions), 53 personnages,
|
|
|
179
188
|
- `plotagon-director/` — le skill principal (+ tests du package)
|
|
180
189
|
- `plotagon-storyteller/` — pipeline Storyteller
|
|
181
190
|
(+ `scripts/transcribe_and_build.py`, tests)
|
|
191
|
+
- `plotagon-showrunner/` — créer une SÉRIE : bible (concept, casting,
|
|
192
|
+
relations, world state) → épisodes cohérents (gabarits + exemple
|
|
193
|
+
complet « Flash Info »)
|
|
194
|
+
- `plotagon-casting-designer/` — personnages custom : description →
|
|
195
|
+
recette (catalogue des 592 pièces embarqué) → création automatique en
|
|
196
|
+
lot dans l'appli (injection UI) → récolte `casting add`
|
|
197
|
+
- `plotagon-script-doctor/` — passe de révision d'un film existant
|
|
198
|
+
(rythme, expressions, caméras, punch-up)
|
|
199
|
+
- `plotagon-adaptateur/` — texte (article, thread, nouvelle) → film
|
|
200
|
+
dialogué (complément du storyteller, qui part d'une vidéo)
|
|
201
|
+
- `plotagon-chaine/` — chaîne TV complète : identité, grille de
|
|
202
|
+
plusieurs émissions, habillage, crossovers (orchestre showrunner)
|
|
182
203
|
- `plotagon-director-cloud/` — variante claude.ai (sandbox + PyPI, sans
|
|
183
204
|
voix) ; voir son `README.md` pour générer le zip d'upload
|
|
184
205
|
- `projects/` — films d'exemple complets (YAML + sous-titres)
|
|
@@ -193,7 +214,7 @@ Le projet expose plusieurs surfaces pour les agents :
|
|
|
193
214
|
- **Serveur MCP** : `pip install plotagon-director[mcp]` puis
|
|
194
215
|
`plotagon-director mcp` (stdio). Outils : search_library, list_category,
|
|
195
216
|
scene_positions, list_voices, validate_project, preview_durations,
|
|
196
|
-
build_film, storytell. Compatible Claude Desktop/Code, Cursor, VS Code,
|
|
217
|
+
build_film, storytell, casting_add, casting_list. Compatible Claude Desktop/Code, Cursor, VS Code,
|
|
197
218
|
Gemini CLI... Exemple de config client :
|
|
198
219
|
`{"command": "plotagon-director", "args": ["mcp"]}`
|
|
199
220
|
- **API HTTP + OpenAPI** : `pip install plotagon-director[http]` puis
|
|
@@ -51,6 +51,13 @@ plotagon-director batch scenarios/ sorties/ # tous les .yaml d'un dossier
|
|
|
51
51
|
|
|
52
52
|
# Importer mon-film.plot dans Plotagon Studio (menu import)
|
|
53
53
|
|
|
54
|
+
# Personnages custom (créés dans le créateur de Plotagon Studio)
|
|
55
|
+
plotagon-director casting add export.plot # récolte les persos d'un export
|
|
56
|
+
plotagon-director casting list [--json] # registre local (./casting)
|
|
57
|
+
# puis dans le cast du yaml : `custom: Leo` au lieu de `character: ...`
|
|
58
|
+
# (clé racine `casting_dir:` optionnelle ; défaut ./casting, sinon
|
|
59
|
+
# variable PLOTAGON_CASTING_DIR) — voix TTS et synchro labiale incluses
|
|
60
|
+
|
|
54
61
|
# Analyse / rétro-ingénierie
|
|
55
62
|
plotagon-director parse export.plot # JSON brut d'un .plot
|
|
56
63
|
plotagon-director decompile export.plot film.yaml # .plot -> YAML éditable
|
|
@@ -126,6 +133,8 @@ Bibliothèque embarquée : 205 scènes (953 positions), 53 personnages,
|
|
|
126
133
|
- `plotagon_director/audio/` — Edge TTS → WAV conforme (cache + parallèle),
|
|
127
134
|
phonèmes/lip-sync
|
|
128
135
|
- `plotagon_director/library/` — builder du catalogue + `data/library.json`
|
|
136
|
+
- `plotagon_director/casting.py` — registre des personnages custom
|
|
137
|
+
(récolte des `.pcd` d'un export, résolution du cast `custom:`)
|
|
129
138
|
- `plotagon_director/storyteller.py` — pipeline vidéo/transcription → .plot
|
|
130
139
|
(`plotagon-storyteller`)
|
|
131
140
|
- `plotagon_director/mcp_server.py` / `http_api.py` — serveur MCP
|
|
@@ -140,6 +149,18 @@ Bibliothèque embarquée : 205 scènes (953 positions), 53 personnages,
|
|
|
140
149
|
- `plotagon-director/` — le skill principal (+ tests du package)
|
|
141
150
|
- `plotagon-storyteller/` — pipeline Storyteller
|
|
142
151
|
(+ `scripts/transcribe_and_build.py`, tests)
|
|
152
|
+
- `plotagon-showrunner/` — créer une SÉRIE : bible (concept, casting,
|
|
153
|
+
relations, world state) → épisodes cohérents (gabarits + exemple
|
|
154
|
+
complet « Flash Info »)
|
|
155
|
+
- `plotagon-casting-designer/` — personnages custom : description →
|
|
156
|
+
recette (catalogue des 592 pièces embarqué) → création automatique en
|
|
157
|
+
lot dans l'appli (injection UI) → récolte `casting add`
|
|
158
|
+
- `plotagon-script-doctor/` — passe de révision d'un film existant
|
|
159
|
+
(rythme, expressions, caméras, punch-up)
|
|
160
|
+
- `plotagon-adaptateur/` — texte (article, thread, nouvelle) → film
|
|
161
|
+
dialogué (complément du storyteller, qui part d'une vidéo)
|
|
162
|
+
- `plotagon-chaine/` — chaîne TV complète : identité, grille de
|
|
163
|
+
plusieurs émissions, habillage, crossovers (orchestre showrunner)
|
|
143
164
|
- `plotagon-director-cloud/` — variante claude.ai (sandbox + PyPI, sans
|
|
144
165
|
voix) ; voir son `README.md` pour générer le zip d'upload
|
|
145
166
|
- `projects/` — films d'exemple complets (YAML + sous-titres)
|
|
@@ -154,7 +175,7 @@ Le projet expose plusieurs surfaces pour les agents :
|
|
|
154
175
|
- **Serveur MCP** : `pip install plotagon-director[mcp]` puis
|
|
155
176
|
`plotagon-director mcp` (stdio). Outils : search_library, list_category,
|
|
156
177
|
scene_positions, list_voices, validate_project, preview_durations,
|
|
157
|
-
build_film, storytell. Compatible Claude Desktop/Code, Cursor, VS Code,
|
|
178
|
+
build_film, storytell, casting_add, casting_list. Compatible Claude Desktop/Code, Cursor, VS Code,
|
|
158
179
|
Gemini CLI... Exemple de config client :
|
|
159
180
|
`{"command": "plotagon-director", "args": ["mcp"]}`
|
|
160
181
|
- **API HTTP + OpenAPI** : `pip install plotagon-director[http]` puis
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"""plotagon_director — generation de films Plotagon Studio Desktop (.plot)."""
|
|
2
|
-
|
|
3
|
-
__version__ = "0.
|
|
1
|
+
"""plotagon_director — generation de films Plotagon Studio Desktop (.plot)."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.10.1"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Personnages custom : registre local et integration au build.
|
|
2
|
+
|
|
3
|
+
Un personnage custom est cree dans le createur de Plotagon Studio puis
|
|
4
|
+
exporte dans un .plot : l'archive contient "customcharacters/<Nom>_<guid>.pcd"
|
|
5
|
+
(recette JSON "TmpL" + geometrie 3D bakee) et sa vignette .png. Le registre
|
|
6
|
+
local (un dossier + registry.json) conserve ces fichiers pour les reutiliser
|
|
7
|
+
dans les films generes : il suffit de referencer le GUID dans les
|
|
8
|
+
instructions et d'embarquer les fichiers dans l'archive.
|
|
9
|
+
|
|
10
|
+
Resolution du dossier de casting (premier trouve) :
|
|
11
|
+
1. cle `casting_dir` du project.yaml (absolue, ou relative au yaml) ;
|
|
12
|
+
2. dossier `casting/` a cote du project.yaml ;
|
|
13
|
+
3. variable d'environnement PLOTAGON_CASTING_DIR.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import zipfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
REGISTRY_NAME = "registry.json"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_recipe(pcd_bytes):
|
|
25
|
+
"""Extrait la recette JSON (chunk TmpL) d'un fichier .pcd."""
|
|
26
|
+
start = pcd_bytes.find(b"{")
|
|
27
|
+
if start < 0:
|
|
28
|
+
raise ValueError("recette TmpL introuvable dans le .pcd")
|
|
29
|
+
depth = 0
|
|
30
|
+
for i in range(start, len(pcd_bytes)):
|
|
31
|
+
char = pcd_bytes[i:i + 1]
|
|
32
|
+
if char == b"{":
|
|
33
|
+
depth += 1
|
|
34
|
+
elif char == b"}":
|
|
35
|
+
depth -= 1
|
|
36
|
+
if depth == 0:
|
|
37
|
+
return json.loads(pcd_bytes[start:i + 1])
|
|
38
|
+
raise ValueError("recette TmpL tronquee dans le .pcd")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def casting_dir(data=None, project_path=None):
|
|
42
|
+
"""Retourne le dossier de casting applicable (Path) ou None."""
|
|
43
|
+
if isinstance(data, dict) and data.get("casting_dir"):
|
|
44
|
+
base = Path(project_path).parent if project_path else Path(".")
|
|
45
|
+
path = Path(data["casting_dir"])
|
|
46
|
+
return path if path.is_absolute() else base / path
|
|
47
|
+
if project_path:
|
|
48
|
+
local = Path(project_path).parent / "casting"
|
|
49
|
+
if local.is_dir():
|
|
50
|
+
return local
|
|
51
|
+
env = os.environ.get("PLOTAGON_CASTING_DIR")
|
|
52
|
+
if env:
|
|
53
|
+
return Path(env)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_registry(directory):
|
|
58
|
+
"""Charge registry.json d'un dossier de casting -> {nom: entree}."""
|
|
59
|
+
if not directory:
|
|
60
|
+
return {}
|
|
61
|
+
path = Path(directory) / REGISTRY_NAME
|
|
62
|
+
if not path.is_file():
|
|
63
|
+
return {}
|
|
64
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
65
|
+
return json.load(f)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def harvest(plot_path, directory):
|
|
69
|
+
"""Recolte les personnages custom d'un .plot exporte vers le registre.
|
|
70
|
+
|
|
71
|
+
Copie les customcharacters/*.pcd|png dans `directory` et met a jour
|
|
72
|
+
registry.json. Retourne la liste des noms ajoutes/mis a jour.
|
|
73
|
+
"""
|
|
74
|
+
directory = Path(directory)
|
|
75
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
registry = load_registry(directory)
|
|
77
|
+
added = []
|
|
78
|
+
with zipfile.ZipFile(plot_path) as zf:
|
|
79
|
+
for name in zf.namelist():
|
|
80
|
+
if not name.startswith("customcharacters/"):
|
|
81
|
+
continue
|
|
82
|
+
filename = name.split("/", 1)[1]
|
|
83
|
+
payload = zf.read(name)
|
|
84
|
+
(directory / filename).write_bytes(payload)
|
|
85
|
+
if filename.endswith(".pcd"):
|
|
86
|
+
recipe = parse_recipe(payload)
|
|
87
|
+
registry[recipe["name"]] = {
|
|
88
|
+
"id": recipe["id"],
|
|
89
|
+
"gender": recipe.get("gender", ""),
|
|
90
|
+
"pcd": filename,
|
|
91
|
+
"png": filename[:-4] + ".png",
|
|
92
|
+
"definition": recipe.get("definition", {}),
|
|
93
|
+
}
|
|
94
|
+
added.append(recipe["name"])
|
|
95
|
+
with open(directory / REGISTRY_NAME, "w", encoding="utf-8") as f:
|
|
96
|
+
json.dump(registry, f, indent=1, ensure_ascii=False)
|
|
97
|
+
return added
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def apply_casting(data, project_path=None):
|
|
101
|
+
"""Resout les membres du cast declares avec `custom: <Nom>`.
|
|
102
|
+
|
|
103
|
+
Mutations en place : `character` devient le GUID du personnage et `name`
|
|
104
|
+
prend le nom du registre par defaut. Retourne les fichiers a embarquer
|
|
105
|
+
dans l'archive : {chemin_zip: bytes}. Leve ValueError si un nom est
|
|
106
|
+
introuvable dans le registre.
|
|
107
|
+
"""
|
|
108
|
+
members = [m for m in (data or {}).get("cast", [])
|
|
109
|
+
if isinstance(m, dict) and m.get("custom")]
|
|
110
|
+
if not members:
|
|
111
|
+
return {}
|
|
112
|
+
directory = casting_dir(data, project_path)
|
|
113
|
+
registry = load_registry(directory)
|
|
114
|
+
if not registry:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
"Cast custom mais aucun registre trouve (casting_dir, dossier "
|
|
117
|
+
"casting/ a cote du yaml, ou PLOTAGON_CASTING_DIR). Recoltez "
|
|
118
|
+
"d'abord un export: plotagon-director casting add <export.plot>"
|
|
119
|
+
)
|
|
120
|
+
extra_files = {}
|
|
121
|
+
for member in members:
|
|
122
|
+
name = member["custom"]
|
|
123
|
+
entry = registry.get(name)
|
|
124
|
+
if entry is None:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
"Personnage custom '%s' absent du registre %s (disponibles: "
|
|
127
|
+
"%s)." % (name, directory, ", ".join(sorted(registry)))
|
|
128
|
+
)
|
|
129
|
+
member["character"] = entry["id"]
|
|
130
|
+
member.setdefault("name", name)
|
|
131
|
+
for key in ("pcd", "png"):
|
|
132
|
+
filename = entry[key]
|
|
133
|
+
path = Path(directory) / filename
|
|
134
|
+
extra_files["customcharacters/" + filename] = path.read_bytes()
|
|
135
|
+
return extra_files
|
|
@@ -108,7 +108,10 @@ def do_build(project_path, output, no_voices=False, strict=False, srt=None,
|
|
|
108
108
|
errors, warnings = validate_project(project_path)
|
|
109
109
|
if errors or (strict and warnings):
|
|
110
110
|
print_report(errors, warnings, strict)
|
|
111
|
-
|
|
111
|
+
from .casting import apply_casting
|
|
112
|
+
data = load_project(project_path)
|
|
113
|
+
extra_files = apply_casting(data, project_path)
|
|
114
|
+
plotdoc, voice_jobs = build_plotdoc(data, load_library())
|
|
112
115
|
|
|
113
116
|
media = {}
|
|
114
117
|
durations = {}
|
|
@@ -137,9 +140,10 @@ def do_build(project_path, output, no_voices=False, strict=False, srt=None,
|
|
|
137
140
|
instr["parameters"]["isRecorded"] = False
|
|
138
141
|
instr["parameters"]["playRecording"] = False
|
|
139
142
|
|
|
140
|
-
write_plot(plotdoc, output, media)
|
|
141
|
-
print("OK %s (%d instructions, %d voix)" % (
|
|
142
|
-
output, len(plotdoc["contents"]["instructions"]), len(media)
|
|
143
|
+
write_plot(plotdoc, output, media, extra_files=extra_files)
|
|
144
|
+
print("OK %s (%d instructions, %d voix%s)" % (
|
|
145
|
+
output, len(plotdoc["contents"]["instructions"]), len(media),
|
|
146
|
+
", %d perso(s) custom" % (len(extra_files) // 2) if extra_files else "",
|
|
143
147
|
))
|
|
144
148
|
|
|
145
149
|
if srt:
|
|
@@ -346,6 +350,19 @@ def main():
|
|
|
346
350
|
"preview", help="durees estimees d'un project.yaml sans builder")
|
|
347
351
|
pprev.add_argument("project")
|
|
348
352
|
|
|
353
|
+
pcast = sub.add_parser(
|
|
354
|
+
"casting",
|
|
355
|
+
help="registre des personnages custom (crees dans l'appli)")
|
|
356
|
+
cast_sub = pcast.add_subparsers(dest="casting_command", required=True)
|
|
357
|
+
pcadd = cast_sub.add_parser(
|
|
358
|
+
"add", help="recolte les personnages custom d'un .plot exporte")
|
|
359
|
+
pcadd.add_argument("plot")
|
|
360
|
+
pcadd.add_argument("--dir", dest="directory", default="casting",
|
|
361
|
+
help="dossier du registre (defaut: ./casting)")
|
|
362
|
+
pclist = cast_sub.add_parser("list", help="liste le registre de casting")
|
|
363
|
+
pclist.add_argument("--dir", dest="directory", default="casting")
|
|
364
|
+
pclist.add_argument("--json", action="store_true", dest="as_json")
|
|
365
|
+
|
|
349
366
|
sub.add_parser(
|
|
350
367
|
"storytell", add_help=False,
|
|
351
368
|
help="pipeline video/transcription -> .plot (alias plotagon-storyteller)")
|
|
@@ -463,6 +480,26 @@ def main():
|
|
|
463
480
|
elif args.command == "preview":
|
|
464
481
|
do_preview(args.project)
|
|
465
482
|
|
|
483
|
+
elif args.command == "casting":
|
|
484
|
+
from .casting import harvest, load_registry
|
|
485
|
+
if args.casting_command == "add":
|
|
486
|
+
added = harvest(args.plot, args.directory)
|
|
487
|
+
if added:
|
|
488
|
+
print("OK %d personnage(s) dans %s : %s" % (
|
|
489
|
+
len(added), args.directory, ", ".join(added)))
|
|
490
|
+
else:
|
|
491
|
+
print("Aucun personnage custom dans %s." % args.plot)
|
|
492
|
+
elif args.casting_command == "list":
|
|
493
|
+
registry = load_registry(args.directory)
|
|
494
|
+
if args.as_json:
|
|
495
|
+
print(json.dumps(registry, indent=1, ensure_ascii=False))
|
|
496
|
+
elif not registry:
|
|
497
|
+
print("Registre vide ou absent (%s)." % args.directory)
|
|
498
|
+
else:
|
|
499
|
+
for name, entry in sorted(registry.items()):
|
|
500
|
+
print("%-16s %-7s %s" % (name, entry.get("gender", "?"),
|
|
501
|
+
entry["id"]))
|
|
502
|
+
|
|
466
503
|
elif args.command == "say":
|
|
467
504
|
from .audio.tts import synthesize_wav
|
|
468
505
|
wav = synthesize_wav(args.text, args.voice)
|
|
@@ -9,10 +9,12 @@ import json
|
|
|
9
9
|
import zipfile
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def write_plot(plotdoc, out_path, media=None):
|
|
12
|
+
def write_plot(plotdoc, out_path, media=None, extra_files=None):
|
|
13
13
|
"""Ecrit un .plot.
|
|
14
14
|
|
|
15
15
|
media : dict {guid_instruction: {"wav": bytes, "phonemes": bytes}}.
|
|
16
|
+
extra_files : dict {chemin_dans_le_zip: bytes} — p. ex. les
|
|
17
|
+
customcharacters/*.pcd|png des personnages custom.
|
|
16
18
|
"""
|
|
17
19
|
inner_name = "%s.plotdoc" % plotdoc["id"]
|
|
18
20
|
payload = json.dumps(plotdoc, ensure_ascii=False, indent=2)
|
|
@@ -23,6 +25,8 @@ def write_plot(plotdoc, out_path, media=None):
|
|
|
23
25
|
zf.writestr(base + ".wav", files["wav"])
|
|
24
26
|
if files.get("phonemes"):
|
|
25
27
|
zf.writestr(base + ".phonemes", files["phonemes"])
|
|
28
|
+
for name, payload_bytes in (extra_files or {}).items():
|
|
29
|
+
zf.writestr(name, payload_bytes)
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def read_plot(path):
|
|
@@ -7,6 +7,10 @@ conserves bruts dans `unknown_instructions` (rien n'est perdu).
|
|
|
7
7
|
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
|
+
_GUID_RE = re.compile(
|
|
11
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I
|
|
12
|
+
)
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
def _slug(text):
|
|
12
16
|
s = re.sub(r"[^a-z0-9]+", "", str(text).lower())
|
|
@@ -16,7 +20,9 @@ def _slug(text):
|
|
|
16
20
|
def _ref_for(character_id, display_name, cast):
|
|
17
21
|
"""Retourne un ref stable pour un personnage (meme id -> meme ref)."""
|
|
18
22
|
for ref, member in cast.items():
|
|
19
|
-
if member
|
|
23
|
+
if member.get("character") == character_id or (
|
|
24
|
+
member.get("custom") and member["_guid"] == character_id
|
|
25
|
+
):
|
|
20
26
|
return ref
|
|
21
27
|
base = _slug(display_name or character_id.split(".")[-1])
|
|
22
28
|
ref = base
|
|
@@ -24,11 +30,20 @@ def _ref_for(character_id, display_name, cast):
|
|
|
24
30
|
while ref in cast:
|
|
25
31
|
ref = "%s%d" % (base, n)
|
|
26
32
|
n += 1
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if _GUID_RE.match(character_id):
|
|
34
|
+
# personnage custom (GUID) : emettre `custom: <Nom>` — rebuildable
|
|
35
|
+
# avec un registre de casting contenant ce personnage (casting add)
|
|
36
|
+
cast[ref] = {
|
|
37
|
+
"ref": ref,
|
|
38
|
+
"custom": display_name or character_id,
|
|
39
|
+
"_guid": character_id,
|
|
40
|
+
}
|
|
41
|
+
else:
|
|
42
|
+
cast[ref] = {
|
|
43
|
+
"ref": ref,
|
|
44
|
+
"character": character_id,
|
|
45
|
+
"name": display_name or character_id.split(".")[-1].capitalize(),
|
|
46
|
+
}
|
|
32
47
|
return ref
|
|
33
48
|
|
|
34
49
|
|
|
@@ -110,9 +125,13 @@ def plotdoc_to_project(plotdoc):
|
|
|
110
125
|
else:
|
|
111
126
|
unknown.append(instr)
|
|
112
127
|
|
|
128
|
+
members = []
|
|
129
|
+
for member in cast.values():
|
|
130
|
+
member.pop("_guid", None)
|
|
131
|
+
members.append(member)
|
|
113
132
|
project = {
|
|
114
133
|
"project": {"title": plotdoc.get("name", "untitled")},
|
|
115
|
-
"cast":
|
|
134
|
+
"cast": members,
|
|
116
135
|
"scenes": scenes,
|
|
117
136
|
}
|
|
118
137
|
if unknown:
|
|
@@ -110,7 +110,7 @@ def build_plotdoc(data, library=None):
|
|
|
110
110
|
)
|
|
111
111
|
instructions.append({"type": "scene", "parameters": params})
|
|
112
112
|
|
|
113
|
-
# musique de scene —
|
|
113
|
+
# musique de scene — CONFIRME par imports reels dans l'appli
|
|
114
114
|
if scene.get("music"):
|
|
115
115
|
params = _base_params()
|
|
116
116
|
params["music"] = _entry(scene["music"], scene.get("music_name"))
|
|
@@ -59,6 +59,9 @@ def validate_project(path):
|
|
|
59
59
|
if not scenes:
|
|
60
60
|
errors.append("No scenes defined.")
|
|
61
61
|
|
|
62
|
+
from ..casting import casting_dir, load_registry
|
|
63
|
+
registry = load_registry(casting_dir(data, path))
|
|
64
|
+
|
|
62
65
|
refs = {}
|
|
63
66
|
for member in cast:
|
|
64
67
|
if not isinstance(member, dict) or not member.get("ref"):
|
|
@@ -68,7 +71,28 @@ def validate_project(path):
|
|
|
68
71
|
errors.append("Duplicate cast ref '%s'." % member["ref"])
|
|
69
72
|
refs[member["ref"]] = member
|
|
70
73
|
character = member.get("character")
|
|
71
|
-
|
|
74
|
+
custom = member.get("custom")
|
|
75
|
+
if custom:
|
|
76
|
+
if character:
|
|
77
|
+
errors.append(
|
|
78
|
+
"Cast '%s': 'character' et 'custom' sont exclusifs."
|
|
79
|
+
% member["ref"]
|
|
80
|
+
)
|
|
81
|
+
elif not registry:
|
|
82
|
+
errors.append(
|
|
83
|
+
"Cast '%s': personnage custom '%s' mais aucun registre "
|
|
84
|
+
"de casting trouve (casting_dir, dossier casting/ a cote "
|
|
85
|
+
"du yaml, ou PLOTAGON_CASTING_DIR)."
|
|
86
|
+
% (member["ref"], custom)
|
|
87
|
+
)
|
|
88
|
+
elif custom not in registry:
|
|
89
|
+
errors.append(
|
|
90
|
+
"Cast '%s': personnage custom '%s' absent du registre "
|
|
91
|
+
"(disponibles: %s).%s"
|
|
92
|
+
% (member["ref"], custom, ", ".join(sorted(registry)),
|
|
93
|
+
_did_you_mean(custom, registry))
|
|
94
|
+
)
|
|
95
|
+
elif not character:
|
|
72
96
|
errors.append("Cast '%s': missing character id." % member["ref"])
|
|
73
97
|
elif library and character not in library["characters"]:
|
|
74
98
|
errors.append(
|
|
@@ -122,6 +122,23 @@ def create_server():
|
|
|
122
122
|
"errors": _structured(errors),
|
|
123
123
|
"warnings": _structured(warnings)}
|
|
124
124
|
|
|
125
|
+
@server.tool()
|
|
126
|
+
def casting_add(plot_path: str, directory: str) -> dict:
|
|
127
|
+
"""Recolte les personnages custom d'un .plot exporte depuis Plotagon
|
|
128
|
+
Studio vers un registre de casting local (dossier + registry.json).
|
|
129
|
+
Ils deviennent utilisables dans le cast via `custom: <Nom>` (avec
|
|
130
|
+
`casting_dir` absolu dans le project.yaml)."""
|
|
131
|
+
from .casting import harvest
|
|
132
|
+
return {"added": harvest(plot_path, directory)}
|
|
133
|
+
|
|
134
|
+
@server.tool()
|
|
135
|
+
def casting_list(directory: str) -> dict:
|
|
136
|
+
"""Liste le registre de casting local {nom: {id, gender, ...}}."""
|
|
137
|
+
from .casting import load_registry
|
|
138
|
+
registry = load_registry(directory)
|
|
139
|
+
return {name: {"id": e["id"], "gender": e.get("gender", "")}
|
|
140
|
+
for name, e in registry.items()}
|
|
141
|
+
|
|
125
142
|
@server.tool()
|
|
126
143
|
def preview_durations(yaml_text: str) -> dict:
|
|
127
144
|
"""Durees estimees par replique et totale d'un project.yaml, sans
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/mohamed-amine-ben-mallessa/plotagon-director/blob/main/schemas/project.schema.json",
|
|
4
|
+
"title": "Plotagon Director project",
|
|
5
|
+
"description": "Scenario YAML pour plotagon-director. Les IDs (scene, character, expression, music, action, position) doivent exister dans la bibliotheque embarquee : les valider avec `plotagon-director validate --strict` ou explorer avec `plotagon-director search/list/positions`.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"project",
|
|
9
|
+
"cast",
|
|
10
|
+
"scenes"
|
|
11
|
+
],
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"properties": {
|
|
14
|
+
"project": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": [
|
|
17
|
+
"title"
|
|
18
|
+
],
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"properties": {
|
|
21
|
+
"title": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"minLength": 1
|
|
24
|
+
},
|
|
25
|
+
"language": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Locale BCP 47, ex. fr-FR, en-US",
|
|
28
|
+
"pattern": "^[a-z]{2}(-[A-Z]{2})?$",
|
|
29
|
+
"default": "fr-FR"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"cast_file": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Chemin (relatif au project.yaml) d'un cast partage ; fusionne avant le cast local"
|
|
36
|
+
},
|
|
37
|
+
"cast": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"required": [
|
|
42
|
+
"ref"
|
|
43
|
+
],
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"properties": {
|
|
46
|
+
"ref": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"minLength": 1,
|
|
49
|
+
"description": "Identifiant local reference par scenes[].actors et dialogue[].actor"
|
|
50
|
+
},
|
|
51
|
+
"character": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"pattern": "^[a-z0-9]+\\.[a-z0-9]+$",
|
|
54
|
+
"description": "ID bibliotheque, ex. office.steve (53 disponibles : `plotagon-director list characters`)"
|
|
55
|
+
},
|
|
56
|
+
"name": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Nom affiche dans le film"
|
|
59
|
+
},
|
|
60
|
+
"voice": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"pattern": "^[a-z]{2}-[A-Z]{2}-[A-Za-z]+Neural$",
|
|
63
|
+
"description": "Voix Edge TTS, ex. fr-FR-HenriNeural (docs/VOICES.md)"
|
|
64
|
+
},
|
|
65
|
+
"rate": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"pattern": "^[+-]\\d+%$",
|
|
68
|
+
"description": "Debit TTS, ex. +10%"
|
|
69
|
+
},
|
|
70
|
+
"pitch": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"pattern": "^[+-]\\d+Hz$",
|
|
73
|
+
"description": "Hauteur TTS, ex. -5Hz"
|
|
74
|
+
},
|
|
75
|
+
"custom": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Nom d'un personnage custom du registre de casting (exclusif avec `character`). Cree dans le createur de Plotagon Studio puis recolte via `plotagon-director casting add`."
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"anyOf": [
|
|
81
|
+
{
|
|
82
|
+
"required": [
|
|
83
|
+
"character"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"required": [
|
|
88
|
+
"custom"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"scenes": {
|
|
95
|
+
"type": "array",
|
|
96
|
+
"minItems": 1,
|
|
97
|
+
"items": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"required": [
|
|
100
|
+
"scene"
|
|
101
|
+
],
|
|
102
|
+
"additionalProperties": false,
|
|
103
|
+
"properties": {
|
|
104
|
+
"scene": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"pattern": "^[a-z0-9]+\\.[a-z0-9]+$",
|
|
107
|
+
"description": "ID bibliotheque, ex. office.ceooffice (205 disponibles)"
|
|
108
|
+
},
|
|
109
|
+
"music": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"pattern": "^music\\.[a-z0-9]+$",
|
|
112
|
+
"description": "38 pistes + music.stopmusic"
|
|
113
|
+
},
|
|
114
|
+
"actors": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"maxItems": 2,
|
|
117
|
+
"items": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"required": [
|
|
120
|
+
"ref"
|
|
121
|
+
],
|
|
122
|
+
"additionalProperties": false,
|
|
123
|
+
"properties": {
|
|
124
|
+
"ref": {
|
|
125
|
+
"type": "string"
|
|
126
|
+
},
|
|
127
|
+
"position": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"description": "Slot propre a la scene (`plotagon-director positions <scene>`) ; omis = premier slot"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"dialogue": {
|
|
135
|
+
"type": "array",
|
|
136
|
+
"items": {
|
|
137
|
+
"oneOf": [
|
|
138
|
+
{
|
|
139
|
+
"type": "object",
|
|
140
|
+
"required": [
|
|
141
|
+
"actor",
|
|
142
|
+
"text"
|
|
143
|
+
],
|
|
144
|
+
"additionalProperties": false,
|
|
145
|
+
"properties": {
|
|
146
|
+
"actor": {
|
|
147
|
+
"type": "string"
|
|
148
|
+
},
|
|
149
|
+
"expression": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"default": "neutral",
|
|
152
|
+
"description": "88 expressions (`plotagon-director list expressions`)"
|
|
153
|
+
},
|
|
154
|
+
"text": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"description": "Vide accepte (mime sans parole)"
|
|
157
|
+
},
|
|
158
|
+
"camera": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"enum": [
|
|
161
|
+
"default",
|
|
162
|
+
"establishing shot",
|
|
163
|
+
"in dialogue mode",
|
|
164
|
+
"in interaction mode",
|
|
165
|
+
"moving right",
|
|
166
|
+
"moving left",
|
|
167
|
+
"moving forward",
|
|
168
|
+
"moving backward",
|
|
169
|
+
"wide shot",
|
|
170
|
+
"still"
|
|
171
|
+
],
|
|
172
|
+
"description": "'in dialogue/interaction mode' exigent 2 acteurs ; 'moving *' exigent camera_position ET camera_target"
|
|
173
|
+
},
|
|
174
|
+
"camera_position": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"description": "ex. EstablishingLocation"
|
|
177
|
+
},
|
|
178
|
+
"camera_target": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"description": "Slot de position de la scene"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"type": "object",
|
|
186
|
+
"required": [
|
|
187
|
+
"actor",
|
|
188
|
+
"action",
|
|
189
|
+
"target"
|
|
190
|
+
],
|
|
191
|
+
"additionalProperties": false,
|
|
192
|
+
"properties": {
|
|
193
|
+
"actor": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
},
|
|
196
|
+
"action": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"description": "15 interactions a 2 personnages (`plotagon-director list actions`)"
|
|
199
|
+
},
|
|
200
|
+
"target": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"description": "ref de l'autre acteur"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"casting_dir": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "Dossier du registre de personnages custom (registry.json + .pcd/.png recoltes via `plotagon-director casting add <export.plot>`). Absolu, ou relatif au project.yaml. Defauts : dossier casting/ a cote du yaml, puis variable PLOTAGON_CASTING_DIR."
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotagon-director
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Summary: Génération de films Plotagon Studio Desktop (.plot) avec voix Edge TTS et synchro labiale
|
|
5
5
|
Author: Mohamed Amine Ben Mallessa
|
|
6
6
|
License: MIT
|
|
@@ -90,6 +90,13 @@ plotagon-director batch scenarios/ sorties/ # tous les .yaml d'un dossier
|
|
|
90
90
|
|
|
91
91
|
# Importer mon-film.plot dans Plotagon Studio (menu import)
|
|
92
92
|
|
|
93
|
+
# Personnages custom (créés dans le créateur de Plotagon Studio)
|
|
94
|
+
plotagon-director casting add export.plot # récolte les persos d'un export
|
|
95
|
+
plotagon-director casting list [--json] # registre local (./casting)
|
|
96
|
+
# puis dans le cast du yaml : `custom: Leo` au lieu de `character: ...`
|
|
97
|
+
# (clé racine `casting_dir:` optionnelle ; défaut ./casting, sinon
|
|
98
|
+
# variable PLOTAGON_CASTING_DIR) — voix TTS et synchro labiale incluses
|
|
99
|
+
|
|
93
100
|
# Analyse / rétro-ingénierie
|
|
94
101
|
plotagon-director parse export.plot # JSON brut d'un .plot
|
|
95
102
|
plotagon-director decompile export.plot film.yaml # .plot -> YAML éditable
|
|
@@ -165,6 +172,8 @@ Bibliothèque embarquée : 205 scènes (953 positions), 53 personnages,
|
|
|
165
172
|
- `plotagon_director/audio/` — Edge TTS → WAV conforme (cache + parallèle),
|
|
166
173
|
phonèmes/lip-sync
|
|
167
174
|
- `plotagon_director/library/` — builder du catalogue + `data/library.json`
|
|
175
|
+
- `plotagon_director/casting.py` — registre des personnages custom
|
|
176
|
+
(récolte des `.pcd` d'un export, résolution du cast `custom:`)
|
|
168
177
|
- `plotagon_director/storyteller.py` — pipeline vidéo/transcription → .plot
|
|
169
178
|
(`plotagon-storyteller`)
|
|
170
179
|
- `plotagon_director/mcp_server.py` / `http_api.py` — serveur MCP
|
|
@@ -179,6 +188,18 @@ Bibliothèque embarquée : 205 scènes (953 positions), 53 personnages,
|
|
|
179
188
|
- `plotagon-director/` — le skill principal (+ tests du package)
|
|
180
189
|
- `plotagon-storyteller/` — pipeline Storyteller
|
|
181
190
|
(+ `scripts/transcribe_and_build.py`, tests)
|
|
191
|
+
- `plotagon-showrunner/` — créer une SÉRIE : bible (concept, casting,
|
|
192
|
+
relations, world state) → épisodes cohérents (gabarits + exemple
|
|
193
|
+
complet « Flash Info »)
|
|
194
|
+
- `plotagon-casting-designer/` — personnages custom : description →
|
|
195
|
+
recette (catalogue des 592 pièces embarqué) → création automatique en
|
|
196
|
+
lot dans l'appli (injection UI) → récolte `casting add`
|
|
197
|
+
- `plotagon-script-doctor/` — passe de révision d'un film existant
|
|
198
|
+
(rythme, expressions, caméras, punch-up)
|
|
199
|
+
- `plotagon-adaptateur/` — texte (article, thread, nouvelle) → film
|
|
200
|
+
dialogué (complément du storyteller, qui part d'une vidéo)
|
|
201
|
+
- `plotagon-chaine/` — chaîne TV complète : identité, grille de
|
|
202
|
+
plusieurs émissions, habillage, crossovers (orchestre showrunner)
|
|
182
203
|
- `plotagon-director-cloud/` — variante claude.ai (sandbox + PyPI, sans
|
|
183
204
|
voix) ; voir son `README.md` pour générer le zip d'upload
|
|
184
205
|
- `projects/` — films d'exemple complets (YAML + sous-titres)
|
|
@@ -193,7 +214,7 @@ Le projet expose plusieurs surfaces pour les agents :
|
|
|
193
214
|
- **Serveur MCP** : `pip install plotagon-director[mcp]` puis
|
|
194
215
|
`plotagon-director mcp` (stdio). Outils : search_library, list_category,
|
|
195
216
|
scene_positions, list_voices, validate_project, preview_durations,
|
|
196
|
-
build_film, storytell. Compatible Claude Desktop/Code, Cursor, VS Code,
|
|
217
|
+
build_film, storytell, casting_add, casting_list. Compatible Claude Desktop/Code, Cursor, VS Code,
|
|
197
218
|
Gemini CLI... Exemple de config client :
|
|
198
219
|
`{"command": "plotagon-director", "args": ["mcp"]}`
|
|
199
220
|
- **API HTTP + OpenAPI** : `pip install plotagon-director[http]` puis
|
|
@@ -1,52 +1,57 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=68"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "plotagon-director"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Génération de films Plotagon Studio Desktop (.plot) avec voix Edge TTS et synchro labiale"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
license = { text = "MIT" }
|
|
11
|
-
authors = [{ name = "Mohamed Amine Ben Mallessa" }]
|
|
12
|
-
requires-python = ">=3.10"
|
|
13
|
-
keywords = ["plotagon", "tts", "video", "animation"]
|
|
14
|
-
classifiers = [
|
|
15
|
-
"Programming Language :: Python :: 3",
|
|
16
|
-
"License :: OSI Approved :: MIT License",
|
|
17
|
-
"Operating System :: OS Independent",
|
|
18
|
-
]
|
|
19
|
-
dependencies = [
|
|
20
|
-
"pyyaml",
|
|
21
|
-
"edge-tts",
|
|
22
|
-
"soundfile",
|
|
23
|
-
"numpy",
|
|
24
|
-
"truststore",
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
[project.optional-dependencies]
|
|
28
|
-
library = ["UnityPy"]
|
|
29
|
-
storyteller = ["yt-dlp", "yapsnap"]
|
|
30
|
-
mcp = ["mcp"]
|
|
31
|
-
http = ["fastapi", "uvicorn"]
|
|
32
|
-
dev = ["pytest", "UnityPy", "mcp", "fastapi", "uvicorn", "httpx", "jsonschema"]
|
|
33
|
-
|
|
34
|
-
[project.urls]
|
|
35
|
-
LinkedIn = "https://www.linkedin.com/in/benmallessa/"
|
|
36
|
-
|
|
37
|
-
[project.scripts]
|
|
38
|
-
plotagon-director = "plotagon_director.cli:main"
|
|
39
|
-
plotagon-storyteller = "plotagon_director.storyteller:main"
|
|
40
|
-
|
|
41
|
-
[tool.pytest.ini_options]
|
|
42
|
-
testpaths = [
|
|
43
|
-
"skills/plotagon-director/tests",
|
|
44
|
-
"skills/plotagon-storyteller/tests",
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "plotagon-director"
|
|
7
|
+
version = "0.10.1"
|
|
8
|
+
description = "Génération de films Plotagon Studio Desktop (.plot) avec voix Edge TTS et synchro labiale"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Mohamed Amine Ben Mallessa" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["plotagon", "tts", "video", "animation"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"pyyaml",
|
|
21
|
+
"edge-tts",
|
|
22
|
+
"soundfile",
|
|
23
|
+
"numpy",
|
|
24
|
+
"truststore",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
library = ["UnityPy"]
|
|
29
|
+
storyteller = ["yt-dlp", "yapsnap"]
|
|
30
|
+
mcp = ["mcp"]
|
|
31
|
+
http = ["fastapi", "uvicorn"]
|
|
32
|
+
dev = ["pytest", "UnityPy", "mcp", "fastapi", "uvicorn", "httpx", "jsonschema"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
LinkedIn = "https://www.linkedin.com/in/benmallessa/"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
plotagon-director = "plotagon_director.cli:main"
|
|
39
|
+
plotagon-storyteller = "plotagon_director.storyteller:main"
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
testpaths = [
|
|
43
|
+
"skills/plotagon-director/tests",
|
|
44
|
+
"skills/plotagon-storyteller/tests",
|
|
45
|
+
"skills/plotagon-casting-designer/tests",
|
|
46
|
+
"skills/plotagon-showrunner/tests",
|
|
47
|
+
"skills/plotagon-script-doctor/tests",
|
|
48
|
+
"skills/plotagon-adaptateur/tests",
|
|
49
|
+
"skills/plotagon-chaine/tests",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
include = ["plotagon_director*"]
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
"plotagon_director.library" = ["data/*.json"]
|
|
57
|
+
"plotagon_director" = ["schemas/*.json"]
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/mohamed-amine-ben-mallessa/plotagon-director/blob/main/schemas/project.schema.json",
|
|
4
|
-
"title": "Plotagon Director project",
|
|
5
|
-
"description": "Scenario YAML pour plotagon-director. Les IDs (scene, character, expression, music, action, position) doivent exister dans la bibliotheque embarquee : les valider avec `plotagon-director validate --strict` ou explorer avec `plotagon-director search/list/positions`.",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"required": ["project", "cast", "scenes"],
|
|
8
|
-
"additionalProperties": false,
|
|
9
|
-
"properties": {
|
|
10
|
-
"project": {
|
|
11
|
-
"type": "object",
|
|
12
|
-
"required": ["title"],
|
|
13
|
-
"additionalProperties": false,
|
|
14
|
-
"properties": {
|
|
15
|
-
"title": {"type": "string", "minLength": 1},
|
|
16
|
-
"language": {
|
|
17
|
-
"type": "string",
|
|
18
|
-
"description": "Locale BCP 47, ex. fr-FR, en-US",
|
|
19
|
-
"pattern": "^[a-z]{2}(-[A-Z]{2})?$",
|
|
20
|
-
"default": "fr-FR"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"cast_file": {
|
|
25
|
-
"type": "string",
|
|
26
|
-
"description": "Chemin (relatif au project.yaml) d'un cast partage ; fusionne avant le cast local"
|
|
27
|
-
},
|
|
28
|
-
"cast": {
|
|
29
|
-
"type": "array",
|
|
30
|
-
"items": {
|
|
31
|
-
"type": "object",
|
|
32
|
-
"required": ["ref", "character"],
|
|
33
|
-
"additionalProperties": false,
|
|
34
|
-
"properties": {
|
|
35
|
-
"ref": {"type": "string", "minLength": 1, "description": "Identifiant local reference par scenes[].actors et dialogue[].actor"},
|
|
36
|
-
"character": {"type": "string", "pattern": "^[a-z0-9]+\\.[a-z0-9]+$", "description": "ID bibliotheque, ex. office.steve (53 disponibles : `plotagon-director list characters`)"},
|
|
37
|
-
"name": {"type": "string", "description": "Nom affiche dans le film"},
|
|
38
|
-
"voice": {"type": "string", "pattern": "^[a-z]{2}-[A-Z]{2}-[A-Za-z]+Neural$", "description": "Voix Edge TTS, ex. fr-FR-HenriNeural (docs/VOICES.md)"},
|
|
39
|
-
"rate": {"type": "string", "pattern": "^[+-]\\d+%$", "description": "Debit TTS, ex. +10%"},
|
|
40
|
-
"pitch": {"type": "string", "pattern": "^[+-]\\d+Hz$", "description": "Hauteur TTS, ex. -5Hz"}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"scenes": {
|
|
45
|
-
"type": "array",
|
|
46
|
-
"minItems": 1,
|
|
47
|
-
"items": {
|
|
48
|
-
"type": "object",
|
|
49
|
-
"required": ["scene"],
|
|
50
|
-
"additionalProperties": false,
|
|
51
|
-
"properties": {
|
|
52
|
-
"scene": {"type": "string", "pattern": "^[a-z0-9]+\\.[a-z0-9]+$", "description": "ID bibliotheque, ex. office.ceooffice (205 disponibles)"},
|
|
53
|
-
"music": {"type": "string", "pattern": "^music\\.[a-z0-9]+$", "description": "38 pistes + music.stopmusic"},
|
|
54
|
-
"actors": {
|
|
55
|
-
"type": "array",
|
|
56
|
-
"maxItems": 2,
|
|
57
|
-
"items": {
|
|
58
|
-
"type": "object",
|
|
59
|
-
"required": ["ref"],
|
|
60
|
-
"additionalProperties": false,
|
|
61
|
-
"properties": {
|
|
62
|
-
"ref": {"type": "string"},
|
|
63
|
-
"position": {"type": "string", "description": "Slot propre a la scene (`plotagon-director positions <scene>`) ; omis = premier slot"}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"dialogue": {
|
|
68
|
-
"type": "array",
|
|
69
|
-
"items": {
|
|
70
|
-
"oneOf": [
|
|
71
|
-
{
|
|
72
|
-
"type": "object",
|
|
73
|
-
"required": ["actor", "text"],
|
|
74
|
-
"additionalProperties": false,
|
|
75
|
-
"properties": {
|
|
76
|
-
"actor": {"type": "string"},
|
|
77
|
-
"expression": {"type": "string", "default": "neutral", "description": "88 expressions (`plotagon-director list expressions`)"},
|
|
78
|
-
"text": {"type": "string", "description": "Vide accepte (mime sans parole)"},
|
|
79
|
-
"camera": {
|
|
80
|
-
"type": "string",
|
|
81
|
-
"enum": ["default", "establishing shot", "in dialogue mode", "in interaction mode", "moving right", "moving left", "moving forward", "moving backward", "wide shot", "still"],
|
|
82
|
-
"description": "'in dialogue/interaction mode' exigent 2 acteurs ; 'moving *' exigent camera_position ET camera_target"
|
|
83
|
-
},
|
|
84
|
-
"camera_position": {"type": "string", "description": "ex. EstablishingLocation"},
|
|
85
|
-
"camera_target": {"type": "string", "description": "Slot de position de la scene"}
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"type": "object",
|
|
90
|
-
"required": ["actor", "action", "target"],
|
|
91
|
-
"additionalProperties": false,
|
|
92
|
-
"properties": {
|
|
93
|
-
"actor": {"type": "string"},
|
|
94
|
-
"action": {"type": "string", "description": "15 interactions a 2 personnages (`plotagon-director list actions`)"},
|
|
95
|
-
"target": {"type": "string", "description": "ref de l'autre acteur"}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director/library/data/library.json
RENAMED
|
File without changes
|
|
File without changes
|
{plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/requires.txt
RENAMED
|
File without changes
|
{plotagon_director-0.9.2 → plotagon_director-0.10.1}/plotagon_director.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|