@fifine/aim-studio 0.0.2 → 0.0.3
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.
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +123 -101
- package/dist/commands/init.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +91 -65
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/templates/aim/scripts/common/paths.py +3 -2
- package/dist/templates/aim/scripts/export.py +427 -0
- package/dist/templates/claude/commands/aim/export.md +89 -115
- package/dist/templates/claude/commands/aim/onboard.md +10 -8
- package/dist/templates/claude/commands/aim/start.md +104 -37
- package/dist/templates/claude/hooks/inject-subagent-context.py +6 -10
- package/dist/templates/claude/hooks/session-start.py +134 -24
- package/dist/templates/markdown/index.d.ts +5 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +6 -0
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/spec/cli/directory-structure.md.txt +71 -0
- package/dist/templates/markdown/spec/cli/error-handling.md.txt +91 -0
- package/dist/templates/markdown/spec/cli/index.md.txt +37 -0
- package/dist/templates/markdown/spec/cli/options-flags.md.txt +71 -0
- package/dist/templates/markdown/spec/cli/output-formatting.md.txt +93 -0
- package/dist/utils/project-detector.d.ts +1 -1
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +20 -0
- package/dist/utils/project-detector.js.map +1 -1
- package/package.json +1 -1
- package/dist/templates/claude/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/claude/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/claude/commands/trellis/break-loop.md +0 -125
- package/dist/templates/claude/commands/trellis/check-backend.md +0 -13
- package/dist/templates/claude/commands/trellis/check-cross-layer.md +0 -153
- package/dist/templates/claude/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/claude/commands/trellis/create-command.md +0 -154
- package/dist/templates/claude/commands/trellis/finish-work.md +0 -129
- package/dist/templates/claude/commands/trellis/integrate-skill.md +0 -219
- package/dist/templates/claude/commands/trellis/onboard.md +0 -358
- package/dist/templates/claude/commands/trellis/parallel.md +0 -193
- package/dist/templates/claude/commands/trellis/record-session.md +0 -62
- package/dist/templates/claude/commands/trellis/start.md +0 -280
- package/dist/templates/claude/commands/trellis/update-spec.md +0 -285
- package/dist/templates/trellis/gitignore.txt +0 -29
- package/dist/templates/trellis/index.d.ts +0 -49
- package/dist/templates/trellis/index.d.ts.map +0 -1
- package/dist/templates/trellis/index.js +0 -92
- package/dist/templates/trellis/index.js.map +0 -1
- package/dist/templates/trellis/scripts/__init__.py +0 -5
- package/dist/templates/trellis/scripts/add_session.py +0 -392
- package/dist/templates/trellis/scripts/common/__init__.py +0 -80
- package/dist/templates/trellis/scripts/common/cli_adapter.py +0 -435
- package/dist/templates/trellis/scripts/common/developer.py +0 -190
- package/dist/templates/trellis/scripts/common/git_context.py +0 -383
- package/dist/templates/trellis/scripts/common/paths.py +0 -347
- package/dist/templates/trellis/scripts/common/phase.py +0 -253
- package/dist/templates/trellis/scripts/common/registry.py +0 -366
- package/dist/templates/trellis/scripts/common/task_queue.py +0 -255
- package/dist/templates/trellis/scripts/common/task_utils.py +0 -178
- package/dist/templates/trellis/scripts/common/worktree.py +0 -219
- package/dist/templates/trellis/scripts/create_bootstrap.py +0 -290
- package/dist/templates/trellis/scripts/get_context.py +0 -16
- package/dist/templates/trellis/scripts/get_developer.py +0 -26
- package/dist/templates/trellis/scripts/init_developer.py +0 -51
- package/dist/templates/trellis/scripts/multi_agent/__init__.py +0 -5
- package/dist/templates/trellis/scripts/multi_agent/cleanup.py +0 -403
- package/dist/templates/trellis/scripts/multi_agent/create_pr.py +0 -329
- package/dist/templates/trellis/scripts/multi_agent/plan.py +0 -233
- package/dist/templates/trellis/scripts/multi_agent/start.py +0 -461
- package/dist/templates/trellis/scripts/multi_agent/status.py +0 -817
- package/dist/templates/trellis/scripts/task.py +0 -1056
- package/dist/templates/trellis/scripts-shell-archive/add-session.sh +0 -384
- package/dist/templates/trellis/scripts-shell-archive/common/developer.sh +0 -129
- package/dist/templates/trellis/scripts-shell-archive/common/git-context.sh +0 -263
- package/dist/templates/trellis/scripts-shell-archive/common/paths.sh +0 -208
- package/dist/templates/trellis/scripts-shell-archive/common/phase.sh +0 -150
- package/dist/templates/trellis/scripts-shell-archive/common/registry.sh +0 -247
- package/dist/templates/trellis/scripts-shell-archive/common/task-queue.sh +0 -142
- package/dist/templates/trellis/scripts-shell-archive/common/task-utils.sh +0 -151
- package/dist/templates/trellis/scripts-shell-archive/common/worktree.sh +0 -128
- package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +0 -299
- package/dist/templates/trellis/scripts-shell-archive/get-context.sh +0 -7
- package/dist/templates/trellis/scripts-shell-archive/get-developer.sh +0 -15
- package/dist/templates/trellis/scripts-shell-archive/init-developer.sh +0 -34
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/cleanup.sh +0 -396
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/create-pr.sh +0 -241
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/plan.sh +0 -207
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/start.sh +0 -317
- package/dist/templates/trellis/scripts-shell-archive/multi-agent/status.sh +0 -828
- package/dist/templates/trellis/scripts-shell-archive/task.sh +0 -1204
- package/dist/templates/trellis/tasks/.gitkeep +0 -0
- package/dist/templates/trellis/workflow.md +0 -416
- package/dist/templates/trellis/worktree.yaml +0 -47
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Export script for generating Seedance-ready prompts
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python export.py --ep 1 # Export episode 1
|
|
8
|
+
python export.py --ep 1-3 # Export episodes 1-3
|
|
9
|
+
python export.py --all # Export all episodes
|
|
10
|
+
python export.py --scene 1 # Export scene 1 of current episode
|
|
11
|
+
python export.py --format seedance # Seedance format (default)
|
|
12
|
+
python export.py --format simple # Simple format for quick copy
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
|
|
22
|
+
# Add parent directory to path for imports
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
24
|
+
from common.paths import get_project_root, get_tasks_dir
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def read_file(file_path: Path) -> str:
|
|
28
|
+
"""Read file content."""
|
|
29
|
+
try:
|
|
30
|
+
return file_path.read_text(encoding="utf-8")
|
|
31
|
+
except (FileNotFoundError, PermissionError):
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_character_prompt(character_name: str, spec_dir: Path) -> str:
|
|
36
|
+
"""Generate character prompt for Seedance."""
|
|
37
|
+
character_file = spec_dir / "story" / "character.md"
|
|
38
|
+
if not character_file.exists():
|
|
39
|
+
return ""
|
|
40
|
+
|
|
41
|
+
content = read_file(character_file)
|
|
42
|
+
|
|
43
|
+
# Extract character section
|
|
44
|
+
lines = content.split("\n")
|
|
45
|
+
in_character = False
|
|
46
|
+
character_lines = []
|
|
47
|
+
|
|
48
|
+
for line in lines:
|
|
49
|
+
if f"## {character_name}" in line or f"## {character_name}:" in line:
|
|
50
|
+
in_character = True
|
|
51
|
+
continue
|
|
52
|
+
if in_character:
|
|
53
|
+
if line.startswith("## "):
|
|
54
|
+
break
|
|
55
|
+
character_lines.append(line)
|
|
56
|
+
|
|
57
|
+
if character_lines:
|
|
58
|
+
# Extract key information: appearance, personality, clothing
|
|
59
|
+
prompt_parts = []
|
|
60
|
+
|
|
61
|
+
# Extract appearance
|
|
62
|
+
for line in character_lines:
|
|
63
|
+
if "外观" in line or "外貌" in line or "Appearance" in line:
|
|
64
|
+
prompt_parts.append(line.split(":", 1)[-1].strip())
|
|
65
|
+
|
|
66
|
+
if "服装" in line or "Clothing" in line:
|
|
67
|
+
prompt_parts.append(line.split(":", 1)[-1].strip())
|
|
68
|
+
|
|
69
|
+
return ", ".join(prompt_parts) if prompt_parts else ""
|
|
70
|
+
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_world_prompt(spec_dir: Path) -> str:
|
|
75
|
+
"""Generate world prompt for Seedance."""
|
|
76
|
+
world_file = spec_dir / "story" / "world.md"
|
|
77
|
+
if not world_file.exists():
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
content = read_file(world_file)
|
|
81
|
+
|
|
82
|
+
# Extract style keywords
|
|
83
|
+
keywords = []
|
|
84
|
+
|
|
85
|
+
# Look for style/tone keywords
|
|
86
|
+
for line in content.split("\n"):
|
|
87
|
+
if "风格" in line or "Style" in line or "视觉" in line or "Visual" in line:
|
|
88
|
+
# Extract keywords after colon
|
|
89
|
+
if ":" in line:
|
|
90
|
+
keywords.append(line.split(":", 1)[-1].strip())
|
|
91
|
+
|
|
92
|
+
return ", ".join(keywords)[:200] if keywords else "cinematic, high quality"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def parse_scene_file(scene_path: Path) -> dict:
|
|
96
|
+
"""Parse scene file and extract Seedance-relevant content."""
|
|
97
|
+
content = read_file(scene_path)
|
|
98
|
+
|
|
99
|
+
if not content:
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
scene_data = {
|
|
103
|
+
"content": content,
|
|
104
|
+
"has_seedance_format": False,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Check for Seedance 2.0 format (六要素)
|
|
108
|
+
required_elements = ["主体描述", "动作", "环境", "镜头", "音频", "风格"]
|
|
109
|
+
if any(elem in content for elem in required_elements):
|
|
110
|
+
scene_data["has_seedance_format"] = True
|
|
111
|
+
|
|
112
|
+
return scene_data
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def generate_seedance_prompt(
|
|
116
|
+
scene_content: str,
|
|
117
|
+
character_prompts: dict,
|
|
118
|
+
world_prompt: str,
|
|
119
|
+
scene_name: str,
|
|
120
|
+
previous_context: str = "",
|
|
121
|
+
) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Generate a clean, copy-paste ready prompt for Seedance.
|
|
124
|
+
|
|
125
|
+
Format: Pure text, no markdown, ready for video generation.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
# Extract key information from scene content
|
|
129
|
+
lines = scene_content.split("\n")
|
|
130
|
+
|
|
131
|
+
output = []
|
|
132
|
+
|
|
133
|
+
# Scene header
|
|
134
|
+
output.append(f"=== {scene_name} ===")
|
|
135
|
+
output.append("")
|
|
136
|
+
|
|
137
|
+
# Character prompts
|
|
138
|
+
if character_prompts:
|
|
139
|
+
output.append("[CHARACTERS]")
|
|
140
|
+
for name, prompt in character_prompts.items():
|
|
141
|
+
if prompt:
|
|
142
|
+
output.append(f"{name}: {prompt}")
|
|
143
|
+
output.append("")
|
|
144
|
+
|
|
145
|
+
# Environment
|
|
146
|
+
output.append("[ENVIRONMENT]")
|
|
147
|
+
# Extract environment description
|
|
148
|
+
for line in lines:
|
|
149
|
+
if "环境" in line or "场景" in line:
|
|
150
|
+
env_text = line.split(":", 1)[-1].strip() if ":" in line else line
|
|
151
|
+
if env_text:
|
|
152
|
+
output.append(env_text)
|
|
153
|
+
break
|
|
154
|
+
output.append("")
|
|
155
|
+
|
|
156
|
+
# Previous context
|
|
157
|
+
if previous_context:
|
|
158
|
+
output.append("[PREVIOUS CONTEXT]")
|
|
159
|
+
output.append(previous_context[:200])
|
|
160
|
+
output.append("")
|
|
161
|
+
|
|
162
|
+
# Main content - extract the actual scene description
|
|
163
|
+
output.append("[SCENE]")
|
|
164
|
+
|
|
165
|
+
# Extract main scene content (skip headers and metadata)
|
|
166
|
+
in_main_content = False
|
|
167
|
+
for line in lines:
|
|
168
|
+
# Skip metadata lines
|
|
169
|
+
if any(marker in line for marker in ["#", "##", "---", "**", "日期", "时间", "时长"]):
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Skip empty lines at start
|
|
173
|
+
if not in_main_content and not line.strip():
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# Start main content
|
|
177
|
+
if line.strip() and not in_main_content:
|
|
178
|
+
in_main_content = True
|
|
179
|
+
|
|
180
|
+
if in_main_content:
|
|
181
|
+
# Clean the line - remove markdown
|
|
182
|
+
cleaned_line = line.strip()
|
|
183
|
+
cleaned_line = cleaned_line.replace("**", "").replace("*", "")
|
|
184
|
+
cleaned_line = cleaned_line.replace("[", "").replace("]", "")
|
|
185
|
+
|
|
186
|
+
if cleaned_line:
|
|
187
|
+
output.append(cleaned_line)
|
|
188
|
+
|
|
189
|
+
output.append("")
|
|
190
|
+
|
|
191
|
+
# Style prompt
|
|
192
|
+
if world_prompt:
|
|
193
|
+
output.append("[STYLE]")
|
|
194
|
+
output.append(world_prompt)
|
|
195
|
+
|
|
196
|
+
# Return as pure text
|
|
197
|
+
return "\n".join(output)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def generate_simple_prompt(scene_content: str, scene_name: str) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Generate a simple, minimal prompt for quick copy.
|
|
203
|
+
Just the essential scene description.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
lines = scene_content.split("\n")
|
|
207
|
+
output = []
|
|
208
|
+
|
|
209
|
+
output.append(f"Scene: {scene_name}")
|
|
210
|
+
output.append("")
|
|
211
|
+
|
|
212
|
+
# Extract just the dialogue and action
|
|
213
|
+
for line in lines:
|
|
214
|
+
# Skip headers and metadata
|
|
215
|
+
if line.startswith("#") or line.startswith("##"):
|
|
216
|
+
continue
|
|
217
|
+
if "日期" in line or "时间" in line or "时长" in line:
|
|
218
|
+
continue
|
|
219
|
+
if "主体描述" in line or "动作" in line:
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
# Clean and add
|
|
223
|
+
cleaned = line.strip().replace("**", "").replace("*", "")
|
|
224
|
+
if cleaned:
|
|
225
|
+
output.append(cleaned)
|
|
226
|
+
|
|
227
|
+
return "\n".join(output)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def find_episodes(tasks_dir: Path) -> list:
|
|
231
|
+
"""Find all episode directories."""
|
|
232
|
+
episodes = []
|
|
233
|
+
|
|
234
|
+
if not tasks_dir.exists():
|
|
235
|
+
return episodes
|
|
236
|
+
|
|
237
|
+
for item in tasks_dir.iterdir():
|
|
238
|
+
if item.is_dir() and "EP" in item.name.upper():
|
|
239
|
+
episodes.append((item.name, item))
|
|
240
|
+
|
|
241
|
+
# Sort by episode number
|
|
242
|
+
episodes.sort(key=lambda x: x[0])
|
|
243
|
+
return episodes
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def find_scenes(episode_dir: Path) -> list:
|
|
247
|
+
"""Find all scene files in an episode directory."""
|
|
248
|
+
scenes = []
|
|
249
|
+
|
|
250
|
+
if not episode_dir.exists():
|
|
251
|
+
return scenes
|
|
252
|
+
|
|
253
|
+
for item in episode_dir.iterdir():
|
|
254
|
+
if item.is_file() and (item.suffix == ".md" or item.suffix == ".txt"):
|
|
255
|
+
if "task.json" in item.name or "prd" in item.name:
|
|
256
|
+
continue
|
|
257
|
+
scenes.append(item)
|
|
258
|
+
|
|
259
|
+
scenes.sort(key=lambda x: x.name)
|
|
260
|
+
return scenes
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def export_episode(
|
|
264
|
+
episode_name: str,
|
|
265
|
+
episode_dir: Path,
|
|
266
|
+
spec_dir: Path,
|
|
267
|
+
output_dir: Path,
|
|
268
|
+
format_type: str = "seedance",
|
|
269
|
+
) -> list:
|
|
270
|
+
"""Export a single episode."""
|
|
271
|
+
|
|
272
|
+
# Get character and world prompts
|
|
273
|
+
world_prompt = get_world_prompt(spec_dir)
|
|
274
|
+
|
|
275
|
+
# Find scenes
|
|
276
|
+
scenes = find_scenes(episode_dir)
|
|
277
|
+
|
|
278
|
+
output_files = []
|
|
279
|
+
|
|
280
|
+
for scene_file in scenes:
|
|
281
|
+
scene_name = scene_file.stem
|
|
282
|
+
content = read_file(scene_file)
|
|
283
|
+
|
|
284
|
+
# Get character prompts for this scene (simplified - use all characters)
|
|
285
|
+
character_prompts = {}
|
|
286
|
+
character_file = spec_dir / "story" / "character.md"
|
|
287
|
+
if character_file.exists():
|
|
288
|
+
char_content = read_file(character_file)
|
|
289
|
+
# Extract character names from ## headers
|
|
290
|
+
for line in char_content.split("\n"):
|
|
291
|
+
if line.startswith("## ") and not line.startswith("###"):
|
|
292
|
+
char_name = line.replace("##", "").strip()
|
|
293
|
+
# Generate prompt for this character
|
|
294
|
+
char_prompt = get_character_prompt(char_name, spec_dir)
|
|
295
|
+
if char_prompt:
|
|
296
|
+
character_prompts[char_name] = char_prompt
|
|
297
|
+
|
|
298
|
+
# Generate prompt based on format
|
|
299
|
+
if format_type == "simple":
|
|
300
|
+
prompt = generate_simple_prompt(content, scene_name)
|
|
301
|
+
else:
|
|
302
|
+
prompt = generate_seedance_prompt(
|
|
303
|
+
content,
|
|
304
|
+
character_prompts,
|
|
305
|
+
world_prompt,
|
|
306
|
+
scene_name,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Write to output directory
|
|
310
|
+
output_file = output_dir / f"{episode_name}_{scene_name}.txt"
|
|
311
|
+
output_file.write_text(prompt, encoding="utf-8")
|
|
312
|
+
output_files.append(output_file)
|
|
313
|
+
|
|
314
|
+
return output_files
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def main():
|
|
318
|
+
parser = argparse.ArgumentParser(
|
|
319
|
+
description="Export script for Seedance video generation"
|
|
320
|
+
)
|
|
321
|
+
parser.add_argument(
|
|
322
|
+
"--ep",
|
|
323
|
+
type=str,
|
|
324
|
+
help="Episode number(s). Examples: 1, 1-3, all",
|
|
325
|
+
)
|
|
326
|
+
parser.add_argument(
|
|
327
|
+
"--scene",
|
|
328
|
+
type=int,
|
|
329
|
+
help="Scene number within episode",
|
|
330
|
+
)
|
|
331
|
+
parser.add_argument(
|
|
332
|
+
"--format",
|
|
333
|
+
type=str,
|
|
334
|
+
default="seedance",
|
|
335
|
+
choices=["seedance", "simple"],
|
|
336
|
+
help="Export format: seedance (full) or simple (minimal)",
|
|
337
|
+
)
|
|
338
|
+
parser.add_argument(
|
|
339
|
+
"--output",
|
|
340
|
+
type=str,
|
|
341
|
+
default="export",
|
|
342
|
+
help="Output directory name",
|
|
343
|
+
)
|
|
344
|
+
parser.add_argument(
|
|
345
|
+
"--open",
|
|
346
|
+
action="store_true",
|
|
347
|
+
help="Open output directory after export",
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
args = parser.parse_args()
|
|
351
|
+
|
|
352
|
+
# Get project paths
|
|
353
|
+
project_root = get_project_root()
|
|
354
|
+
tasks_dir = get_tasks_dir(project_root)
|
|
355
|
+
spec_dir = project_root / ".aim-studio" / "spec"
|
|
356
|
+
|
|
357
|
+
# Check if story project
|
|
358
|
+
if not (spec_dir / "story").exists():
|
|
359
|
+
print("Error: This is not a story project. No spec/story/ directory found.")
|
|
360
|
+
sys.exit(1)
|
|
361
|
+
|
|
362
|
+
# Create output directory
|
|
363
|
+
output_dir = project_root / args.output
|
|
364
|
+
output_dir.mkdir(exist_ok=True)
|
|
365
|
+
|
|
366
|
+
print(f"Exporting to: {output_dir}")
|
|
367
|
+
print(f"Format: {args.format}")
|
|
368
|
+
print("")
|
|
369
|
+
|
|
370
|
+
exported_files = []
|
|
371
|
+
|
|
372
|
+
# Determine which episodes to export
|
|
373
|
+
if args.ep == "all":
|
|
374
|
+
episodes = find_episodes(tasks_dir)
|
|
375
|
+
for ep_name, ep_dir in episodes:
|
|
376
|
+
print(f"Exporting {ep_name}...")
|
|
377
|
+
files = export_episode(ep_name, ep_dir, spec_dir, output_dir, args.format)
|
|
378
|
+
exported_files.extend(files)
|
|
379
|
+
elif "-" in str(args.ep):
|
|
380
|
+
# Range like 1-3
|
|
381
|
+
start, end = map(int, args.ep.split("-"))
|
|
382
|
+
for ep_num in range(start, end + 1):
|
|
383
|
+
ep_name = f"EP{ep_num:02d}"
|
|
384
|
+
ep_dir = tasks_dir / ep_name
|
|
385
|
+
if ep_dir.exists():
|
|
386
|
+
print(f"Exporting {ep_name}...")
|
|
387
|
+
files = export_episode(ep_name, ep_dir, spec_dir, output_dir, args.format)
|
|
388
|
+
exported_files.extend(files)
|
|
389
|
+
elif args.ep:
|
|
390
|
+
# Single episode
|
|
391
|
+
ep_num = int(args.ep)
|
|
392
|
+
ep_name = f"EP{ep_num:02d}"
|
|
393
|
+
ep_dir = tasks_dir / ep_name
|
|
394
|
+
|
|
395
|
+
if ep_dir.exists():
|
|
396
|
+
print(f"Exporting {ep_name}...")
|
|
397
|
+
exported_files = export_episode(ep_name, ep_dir, spec_dir, output_dir, args.format)
|
|
398
|
+
else:
|
|
399
|
+
print(f"Error: Episode {ep_name} not found in {tasks_dir}")
|
|
400
|
+
sys.exit(1)
|
|
401
|
+
else:
|
|
402
|
+
print("Error: Please specify --ep or --all")
|
|
403
|
+
parser.print_help()
|
|
404
|
+
sys.exit(1)
|
|
405
|
+
|
|
406
|
+
print("")
|
|
407
|
+
print(f"Exported {len(exported_files)} files:")
|
|
408
|
+
for f in exported_files:
|
|
409
|
+
print(f" - {f.relative_to(project_root)}")
|
|
410
|
+
|
|
411
|
+
# Optionally open output directory
|
|
412
|
+
if args.open:
|
|
413
|
+
import subprocess
|
|
414
|
+
|
|
415
|
+
if sys.platform == "win32":
|
|
416
|
+
os.startfile(output_dir)
|
|
417
|
+
elif sys.platform == "darwin":
|
|
418
|
+
subprocess.run(["open", str(output_dir)])
|
|
419
|
+
else:
|
|
420
|
+
subprocess.run(["xdg-open", str(output_dir)])
|
|
421
|
+
|
|
422
|
+
print("")
|
|
423
|
+
print("Done! Files are ready for Seedance.")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
if __name__ == "__main__":
|
|
427
|
+
main()
|
|
@@ -1,187 +1,161 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: export
|
|
3
|
-
description: 导出剧本用于AI
|
|
3
|
+
description: 导出剧本用于AI视频生成(纯文本格式)
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 导出剧本
|
|
7
7
|
|
|
8
|
-
将剧本导出为可直接粘贴到 AI 视频生成工具(如 Seedance
|
|
8
|
+
将剧本导出为可直接粘贴到 AI 视频生成工具(如 Seedance)的**纯文本格式**。
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 核心功能
|
|
13
|
+
|
|
14
|
+
本命令生成**不带任何 Markdown 符号**的纯文本文件,可直接复制粘贴到 Seedance 的视频生成输入框。
|
|
15
|
+
|
|
16
|
+
---
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
## 使用方法
|
|
19
|
+
|
|
20
|
+
### 基本用法
|
|
13
21
|
|
|
14
22
|
```bash
|
|
15
|
-
#
|
|
16
|
-
/
|
|
23
|
+
# 导出第1集
|
|
24
|
+
python3 .aim-studio/scripts/export.py --ep 1
|
|
17
25
|
|
|
18
26
|
# 导出多集
|
|
19
|
-
/
|
|
27
|
+
python3 .aim-studio/scripts/export.py --ep 1-3
|
|
28
|
+
|
|
29
|
+
# 导出全部集数
|
|
30
|
+
python3 .aim-studio/scripts/export.py --all
|
|
20
31
|
|
|
21
|
-
#
|
|
22
|
-
/
|
|
32
|
+
# 导出后打开文件夹
|
|
33
|
+
python3 .aim-studio/scripts/export.py --ep 1 --open
|
|
23
34
|
```
|
|
24
35
|
|
|
25
|
-
###
|
|
36
|
+
### 导出格式
|
|
26
37
|
|
|
27
|
-
| 格式 |
|
|
38
|
+
| 格式 | 说明 | 用途 |
|
|
28
39
|
|------|------|------|
|
|
29
|
-
| `seedance` |
|
|
30
|
-
| `
|
|
31
|
-
| `review` | 预览检查 | 带注释格式,便于检查 |
|
|
32
|
-
| `text` | 纯文本发布 | 去除备注,统一标点 |
|
|
40
|
+
| `seedance` | 完整格式,包含角色、环境、上下文(默认) | 推荐用于 Seedance |
|
|
41
|
+
| `simple` | 极简格式,只有场景描述和对话 | 快速复制 |
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
```bash
|
|
44
|
+
# 使用简单格式
|
|
45
|
+
python3 .aim-studio/scripts/export.py --ep 1 --format simple
|
|
46
|
+
```
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 输出文件
|
|
51
|
+
|
|
52
|
+
导出会生成纯文本文件到 `export/` 目录:
|
|
37
53
|
|
|
38
54
|
```
|
|
39
55
|
export/
|
|
40
|
-
├──
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
│ └── characters.txt # 本集角色参考
|
|
45
|
-
├── EP02/
|
|
46
|
-
│ └── ...
|
|
47
|
-
└── global_context.txt # 全局设定(角色、世界观)
|
|
56
|
+
├── EP01_场景1.txt # 可直接粘贴
|
|
57
|
+
├── EP01_场景2.txt # 可直接粘贴
|
|
58
|
+
├── EP02_场景1.txt
|
|
59
|
+
└── ...
|
|
48
60
|
```
|
|
49
61
|
|
|
50
62
|
---
|
|
51
63
|
|
|
52
|
-
## Seedance
|
|
53
|
-
|
|
54
|
-
### 单场次 Prompt 模板
|
|
64
|
+
## Seedance 纯文本格式
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
导出的文件内容示例(无任何 Markdown 符号):
|
|
57
67
|
|
|
58
68
|
```
|
|
59
|
-
|
|
60
|
-
[角色名]:[外貌描述],[性格关键词]
|
|
61
|
-
(如有多位角色,逐一列出)
|
|
69
|
+
=== 场景1: 青云峰主殿 ===
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
[CHARACTERS]
|
|
72
|
+
沈安在: 白衣中年男子, 两鬓霜白, 面容俊俏
|
|
73
|
+
慕容天: 少年身形, 目光坚定
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
[ENVIRONMENT]
|
|
76
|
+
青云峰主殿: 破旧大殿, 阳光洒落, 萧条冷清
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
[PREVIOUS CONTEXT]
|
|
79
|
+
慕容天决定下山离开
|
|
71
80
|
|
|
72
|
-
[
|
|
81
|
+
[SCENE]
|
|
82
|
+
场次 1: 青云峰主殿内 - 清晨 - 萧条落寞 - 30秒
|
|
73
83
|
|
|
74
|
-
|
|
84
|
+
白衣中年沈安在瘫坐在太师椅上,神情苦涩。
|
|
75
85
|
|
|
76
|
-
|
|
86
|
+
慕容天:(低沉) 师父,弟子不孝,今日便要下山!
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
【视觉风格】
|
|
81
|
-
[风格关键词,如:xianxia, fantasy, cinematic lighting]
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 示例输出
|
|
88
|
+
沈安在听到声音,却懒得出门。镜头推进他的脸,苦涩中带着自嘲。
|
|
85
89
|
|
|
90
|
+
[STYLE]
|
|
91
|
+
xianxia, chinese fantasy, cinematic lighting, melancholic atmosphere
|
|
86
92
|
```
|
|
87
|
-
【角色设定】
|
|
88
|
-
沈安在:白衣中年男子,两鬓霜白,面容俊俏,气质高深莫测。性格:腹黑、护短、爱演。
|
|
89
|
-
慕容天:少年身形,目光从落寞到坚定。性格:勤奋、尊师重道、意志坚定。
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
青云峰主殿:破旧的大殿,阳光透过残破窗棂洒落,萧条冷清的氛围。
|
|
94
|
+
---
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
慕容天决定下山离开,沈安在内心吐槽自己的穿越遭遇。
|
|
96
|
+
## 快速使用流程
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
场次 1:青云峰主殿内 - 清晨 - 萧条落寞 - 30秒
|
|
98
|
+
### 步骤 1:导出
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
```bash
|
|
101
|
+
python3 .aim-studio/scripts/export.py --ep 1 --open
|
|
102
|
+
```
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
### 步骤 2:复制
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
打开 `export/` 文件夹,选择对应场次的 `.txt` 文件,**直接全选复制**。
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
### 步骤 3:粘贴到 Seedance
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
xianxia, chinese fantasy, cinematic lighting, melancholic atmosphere
|
|
110
|
-
```
|
|
110
|
+
将复制的内容**直接粘贴**到 Seedance 的 prompt 输入框中,点击生成。
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
114
|
-
##
|
|
114
|
+
## 保持一致性的技巧
|
|
115
115
|
|
|
116
|
-
### 1.
|
|
116
|
+
### 1. 角色描述一致性
|
|
117
117
|
|
|
118
|
-
`
|
|
119
|
-
- 核心角色设定(外貌、性格、说话风格)
|
|
120
|
-
- 世界观设定(力量体系、地理环境)
|
|
121
|
-
- 视觉风格关键词
|
|
118
|
+
每次导出时,脚本会自动从 `spec/story/character.md` 提取角色信息。确保角色设定文件中的描述保持一致。
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
- 首次使用时,先粘贴全局上下文
|
|
125
|
-
- 或将全局上下文保存为 Seedance 的"项目设定"
|
|
120
|
+
### 2. 视觉风格关键词
|
|
126
121
|
|
|
127
|
-
|
|
122
|
+
在世界观文件 `spec/story/world.md` 中定义统一的视觉风格关键词,导出会自动包含这些关键词。
|
|
128
123
|
|
|
129
|
-
|
|
130
|
-
- 本集角色状态快照
|
|
131
|
-
- 本集关键事件摘要
|
|
132
|
-
- 上一集结尾状态
|
|
124
|
+
### 3. 上下文衔接
|
|
133
125
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
每集的 `characters.txt` 包含:
|
|
137
|
-
- 本集出场角色的完整设定
|
|
138
|
-
- SD Prompt 片段
|
|
139
|
-
- 服装变化记录
|
|
126
|
+
每集导出时会包含 `[PREVIOUS CONTEXT]` 部分,帮助 Seedance 理解剧情连续性。
|
|
140
127
|
|
|
141
128
|
---
|
|
142
129
|
|
|
143
|
-
##
|
|
130
|
+
## 常见问题
|
|
144
131
|
|
|
145
|
-
|
|
146
|
-
# 交互式导出
|
|
147
|
-
/aim:export
|
|
132
|
+
### Q: 导出文件有乱码?
|
|
148
133
|
|
|
149
|
-
|
|
150
|
-
/aim:export --ep 1 --format seedance
|
|
134
|
+
确保使用 `--format seedance`(默认)格式,这是为 Seedance 优化的纯文本格式。
|
|
151
135
|
|
|
152
|
-
|
|
153
|
-
/aim:export --ep 1 --format text
|
|
136
|
+
### Q: 如何只导出一个场景?
|
|
154
137
|
|
|
155
|
-
|
|
156
|
-
/aim:export --ep 1 --open
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
138
|
+
目前脚本按集导出。如果需要单场景,可以手动编辑导出的文件。
|
|
160
139
|
|
|
161
|
-
|
|
140
|
+
### Q: 导出的内容太长?
|
|
162
141
|
|
|
163
|
-
|
|
164
|
-
- [ ] 角色设定是否完整?
|
|
165
|
-
- [ ] 前情提要是否准确?
|
|
166
|
-
- [ ] 视觉风格是否一致?
|
|
167
|
-
- [ ] 时长是否符合配置?
|
|
142
|
+
Seedance 对 prompt 长度有限制。可以使用 `--format simple` 获得更简洁的版本。
|
|
168
143
|
|
|
169
144
|
---
|
|
170
145
|
|
|
171
|
-
##
|
|
146
|
+
## 与其他命令配合
|
|
172
147
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
148
|
+
```
|
|
149
|
+
1. /aim:story → 创作剧本
|
|
150
|
+
2. /aim:check-story → 检查剧情一致性
|
|
151
|
+
3. /aim:export --ep 1 → 导出第1集
|
|
152
|
+
4. 复制到 Seedance → 生成视频
|
|
153
|
+
```
|
|
177
154
|
|
|
178
155
|
---
|
|
179
156
|
|
|
180
|
-
##
|
|
181
|
-
|
|
182
|
-
如需导出为传统发布格式,直接读取当前剧本并整理为适合发布的格式:
|
|
183
|
-
- 去除构建过程中的备注
|
|
184
|
-
- 统一标点符号
|
|
185
|
-
- 优化段落间距
|
|
157
|
+
## 注意事项
|
|
186
158
|
|
|
187
|
-
|
|
159
|
+
1. **纯文本格式**:导出的文件不包含任何 `#`、`*`、`[]` 等 Markdown 符号
|
|
160
|
+
2. **字符编码**:文件使用 UTF-8 编码,确保 Seedance 能正确识别
|
|
161
|
+
3. **首次使用**:建议先导出单集测试,确认格式符合预期
|