ACloudCenter commited on
Commit
29b2e23
·
1 Parent(s): bed519a

Better speaker casting and strip stage directions

Browse files
Files changed (1) hide show
  1. app.py +58 -0
app.py CHANGED
@@ -145,6 +145,20 @@ STYLE:
145
  - Speakers should reference what the other person said, react naturally, and build on previous points
146
  - Include personality — people joke, digress slightly, use analogies, get passionate about topics
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  FORMAT RULES:
149
  - Start with a title on the FIRST LINE in this format: "Title: Your Script Title Here"
150
  - Then a blank line, then the dialogue
@@ -155,6 +169,44 @@ FORMAT RULES:
155
  - Output ONLY the title and script — no stage directions, no commentary, no preamble"""
156
 
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  def generate_script_from_prompt(prompt: str) -> tuple[list[dict], int, str]:
159
  """Returns (turns, num_speakers, title)."""
160
  system = SCRIPT_SYSTEM_PROMPT.format(max_words=SCRIPT_MAX_WORDS)
@@ -176,6 +228,12 @@ def generate_script_from_prompt(prompt: str) -> tuple[list[dict], int, str]:
176
  raw = "\n".join(lines[1:])
177
 
178
  turns = parse_script_to_turns(raw)
 
 
 
 
 
 
179
  turns = turns[:MAX_TURNS]
180
  total_words = sum(len(t["text"].split()) for t in turns)
181
  while total_words > MAX_SCRIPT_WORDS and turns:
 
145
  - Speakers should reference what the other person said, react naturally, and build on previous points
146
  - Include personality — people joke, digress slightly, use analogies, get passionate about topics
147
 
148
+ CASTING (IMPORTANT):
149
+ - Before writing, identify EVERY character in the scenario — including any who enter, interrupt, or arrive later (parents, bosses, narrators, bystanders, etc.)
150
+ - If the prompt mentions someone at all, they get their own Speaker number (up to 4 max)
151
+ - Example: "Two kids argue until their mom walks in" = 3 speakers, not 2
152
+ - Example: "A detective interviews a suspect while a lawyer objects" = 3 speakers
153
+ - Assign Speaker numbers in order of first appearance
154
+
155
+ STRICT NO-NO's (VibeVoice reads these LITERALLY as spoken words — never use them):
156
+ - NO bracketed stage directions: [whispering], [sighs], [laughs], [door slams], [pause], [music], etc.
157
+ - NO parenthetical emotion cues: (softly), (angrily), (laughing), (sarcastically), etc.
158
+ - NO asterisk actions: *laughs*, *sighs*, *door opens*, etc.
159
+ - NO scene headings, sound effects, or narration lines
160
+ - Convey emotion through WORD CHOICE and natural speech only (e.g., actually type "hahaha" or "ugh" or "whoa" as part of the dialogue itself)
161
+
162
  FORMAT RULES:
163
  - Start with a title on the FIRST LINE in this format: "Title: Your Script Title Here"
164
  - Then a blank line, then the dialogue
 
169
  - Output ONLY the title and script — no stage directions, no commentary, no preamble"""
170
 
171
 
172
+ # Strip bracketed stage directions, parenthetical cues, and asterisk actions.
173
+ # VibeVoice reads these literally, so we defensively remove them even if the LLM sneaks them in.
174
+ _STAGE_DIRECTION_PATTERNS = [
175
+ re.compile(r"\[[^\]]*\]"), # [whispering], [sighs], [door slams]
176
+ re.compile(r"\*[^*\n]+\*"), # *laughs*, *sighs*
177
+ ]
178
+ # Common parenthetical emotion/action cues — only strip short ones that look like directions,
179
+ # not legitimate asides like "(which, by the way, is huge)".
180
+ _PAREN_CUE_WORDS = {
181
+ "softly", "angrily", "laughing", "laughs", "sighs", "sighing", "whispers", "whispering",
182
+ "shouts", "shouting", "sarcastically", "sarcastic", "nervously", "excitedly",
183
+ "quietly", "loudly", "pauses", "pause", "crying", "sobbing", "giggling", "chuckling",
184
+ "sternly", "coldly", "warmly", "mockingly", "sadly", "happily", "angry", "sad",
185
+ "clears throat", "beat", "aside", "muttering", "mutters", "groans", "groaning",
186
+ }
187
+ _PAREN_PATTERN = re.compile(r"\(([^)\n]{1,40})\)")
188
+
189
+
190
+ def sanitize_dialogue(text: str) -> str:
191
+ """Remove stage directions VibeVoice would read as literal words."""
192
+ for pat in _STAGE_DIRECTION_PATTERNS:
193
+ text = pat.sub("", text)
194
+
195
+ def _paren_filter(m):
196
+ inside = m.group(1).strip().lower().rstrip(".!?")
197
+ if inside in _PAREN_CUE_WORDS:
198
+ return ""
199
+ # Also strip single-word parentheticals ending in -ly (adverbs)
200
+ if " " not in inside and inside.endswith("ly"):
201
+ return ""
202
+ return m.group(0) # keep legitimate asides
203
+
204
+ text = _PAREN_PATTERN.sub(_paren_filter, text)
205
+ # Collapse whitespace the stripping may have introduced
206
+ text = re.sub(r"\s{2,}", " ", text).strip()
207
+ return text
208
+
209
+
210
  def generate_script_from_prompt(prompt: str) -> tuple[list[dict], int, str]:
211
  """Returns (turns, num_speakers, title)."""
212
  system = SCRIPT_SYSTEM_PROMPT.format(max_words=SCRIPT_MAX_WORDS)
 
228
  raw = "\n".join(lines[1:])
229
 
230
  turns = parse_script_to_turns(raw)
231
+ # Scrub stage directions from each turn, drop any turn that becomes empty
232
+ turns = [
233
+ {"speaker": t["speaker"], "text": sanitize_dialogue(t["text"])}
234
+ for t in turns
235
+ ]
236
+ turns = [t for t in turns if t["text"].strip()]
237
  turns = turns[:MAX_TURNS]
238
  total_words = sum(len(t["text"].split()) for t in turns)
239
  while total_words > MAX_SCRIPT_WORDS and turns: