Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -536,18 +536,6 @@ class ScreenplayDatabase:
|
|
536 |
|
537 |
return theme_id
|
538 |
|
539 |
-
@staticmethod
|
540 |
-
def get_stages(session_id: str) -> List[Dict]:
|
541 |
-
"""Get all stages for a session"""
|
542 |
-
with ScreenplayDatabase.get_db() as conn:
|
543 |
-
rows = conn.cursor().execute(
|
544 |
-
'''SELECT * FROM screenplay_stages
|
545 |
-
WHERE session_id = ?
|
546 |
-
ORDER BY stage_number''',
|
547 |
-
(session_id,)
|
548 |
-
).fetchall()
|
549 |
-
return [dict(row) for row in rows]
|
550 |
-
|
551 |
class WebSearchIntegration:
|
552 |
"""Web search functionality for screenplay research"""
|
553 |
def __init__(self):
|
@@ -1481,8 +1469,6 @@ Provide specific solutions for each issue."""
|
|
1481 |
raise Exception(f"LLM Call Failed: {full_content}")
|
1482 |
return full_content
|
1483 |
|
1484 |
-
|
1485 |
-
|
1486 |
def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
|
1487 |
language: str) -> Generator[str, None, None]:
|
1488 |
try:
|
@@ -1632,8 +1618,6 @@ Provide specific solutions for each issue."""
|
|
1632 |
logger.error(traceback.format_exc())
|
1633 |
yield f"❌ Unexpected error: {str(e)}"
|
1634 |
|
1635 |
-
|
1636 |
-
|
1637 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
1638 |
"""Role-specific system prompts"""
|
1639 |
|
@@ -1724,9 +1708,7 @@ You provide feedback that's critical yet encouraging."""
|
|
1724 |
|
1725 |
return base_prompts.get(language, base_prompts["English"])
|
1726 |
|
1727 |
-
|
1728 |
-
|
1729 |
-
# --- Main process ---
|
1730 |
def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
|
1731 |
language: str, session_id: Optional[str] = None
|
1732 |
) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
|
@@ -1886,6 +1868,20 @@ You provide feedback that's critical yet encouraging."""
|
|
1886 |
|
1887 |
return "\n\n---\n\n".join(previous) if previous else ""
|
1888 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1889 |
|
1890 |
def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
|
1891 |
"""Parse character profile from content"""
|
@@ -1903,10 +1899,10 @@ You provide feedback that's critical yet encouraging."""
|
|
1903 |
|
1904 |
for pattern in name_patterns:
|
1905 |
name_match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)
|
1906 |
-
if name_match:
|
1907 |
extracted_name = name_match.group(1).strip()
|
1908 |
# Remove markdown and extra characters
|
1909 |
-
extracted_name = re.sub(r'[*:\s]
|
1910 |
extracted_name = re.sub(r'^[*:\s]+', '', extracted_name)
|
1911 |
if extracted_name and len(extracted_name) > 1:
|
1912 |
name = extracted_name
|
@@ -1918,8 +1914,9 @@ You provide feedback that's critical yet encouraging."""
|
|
1918 |
patterns = [patterns]
|
1919 |
|
1920 |
for pattern in patterns:
|
|
|
1921 |
match = re.search(rf'{pattern}[:\s]*([^\n*]+?)(?=\n|$)', content, re.IGNORECASE | re.DOTALL)
|
1922 |
-
if match:
|
1923 |
value = match.group(1).strip()
|
1924 |
# Clean up the value
|
1925 |
value = re.sub(r'^[-*•:\s]+', '', value)
|
@@ -1929,7 +1926,7 @@ You provide feedback that's critical yet encouraging."""
|
|
1929 |
return value
|
1930 |
return ""
|
1931 |
|
1932 |
-
# Extract all fields
|
1933 |
profile = CharacterProfile(
|
1934 |
name=name,
|
1935 |
role=role,
|
@@ -1975,166 +1972,211 @@ You provide feedback that's critical yet encouraging."""
|
|
1975 |
logger.debug(f"Parsed character: {profile.name}")
|
1976 |
return profile
|
1977 |
|
1978 |
-
def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
|
1979 |
-
"""Extract field value from content with improved parsing"""
|
1980 |
-
# Use word boundary to prevent partial matches
|
1981 |
-
pattern = rf'\b{field_pattern}\b[:\s]*([^\n]+?)(?=\n|$)'
|
1982 |
-
match = re.search(pattern, content, re.IGNORECASE)
|
1983 |
-
if match:
|
1984 |
-
value = match.group(1).strip()
|
1985 |
-
# Remove markdown formatting
|
1986 |
-
value = re.sub(r'\*+', '', value)
|
1987 |
-
# Remove list markers
|
1988 |
-
value = re.sub(r'^\s*[-•*]\s*', '', value)
|
1989 |
-
# Remove trailing punctuation
|
1990 |
-
value = re.sub(r'[,.:;]$', '', value)
|
1991 |
-
return value.strip()
|
1992 |
-
return None
|
1993 |
-
|
1994 |
def _extract_personality_traits(self, content: str) -> List[str]:
|
1995 |
-
|
1996 |
-
|
1997 |
-
|
1998 |
-
|
1999 |
-
|
2000 |
-
|
2001 |
-
|
2002 |
-
|
2003 |
-
|
2004 |
-
|
2005 |
-
|
2006 |
-
|
2007 |
-
|
2008 |
-
|
2009 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2010 |
|
2011 |
def _process_character_content(self, content: str):
|
2012 |
-
|
2013 |
-
|
2014 |
-
|
2015 |
-
|
2016 |
-
|
2017 |
-
|
2018 |
-
|
2019 |
-
|
2020 |
-
|
2021 |
-
|
2022 |
-
|
2023 |
-
|
2024 |
-
|
2025 |
-
|
2026 |
-
|
2027 |
-
|
2028 |
-
|
2029 |
-
|
2030 |
-
|
2031 |
-
|
2032 |
-
|
2033 |
-
|
2034 |
-
|
2035 |
-
|
2036 |
-
|
2037 |
-
|
2038 |
-
|
2039 |
-
|
2040 |
-
|
2041 |
-
|
2042 |
-
|
2043 |
-
|
2044 |
-
|
2045 |
-
|
2046 |
-
|
2047 |
-
|
2048 |
-
|
2049 |
-
|
2050 |
-
|
2051 |
-
|
2052 |
-
|
2053 |
-
|
2054 |
-
|
2055 |
-
|
2056 |
-
|
2057 |
-
|
2058 |
-
|
2059 |
-
|
2060 |
-
|
2061 |
-
|
2062 |
-
|
2063 |
-
|
2064 |
-
|
2065 |
-
|
2066 |
-
|
|
|
|
|
2067 |
|
2068 |
def _extract_section(self, content: str, section_pattern: str) -> str:
|
2069 |
-
|
2070 |
-
|
2071 |
-
|
2072 |
-
|
2073 |
-
|
2074 |
-
|
2075 |
-
|
2076 |
-
|
2077 |
-
|
2078 |
-
|
2079 |
-
|
2080 |
-
|
2081 |
-
|
2082 |
-
|
2083 |
-
|
2084 |
-
|
2085 |
-
|
2086 |
-
|
2087 |
-
|
2088 |
-
title_patterns = [
|
2089 |
-
r'(?:TITLE|제목)[:\s]*\*?\*?([^\n*]+)\*?\*?',
|
2090 |
-
r'\*\*(?:TITLE|제목)\*\*[:\s]*([^\n]+)',
|
2091 |
-
r'Title[:\s]*([^\n]+)'
|
2092 |
-
]
|
2093 |
-
|
2094 |
-
for pattern in title_patterns:
|
2095 |
-
title_match = re.search(pattern, content, re.IGNORECASE)
|
2096 |
-
if title_match:
|
2097 |
-
self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
|
2098 |
-
break
|
2099 |
-
|
2100 |
-
# Extract logline with various formats
|
2101 |
-
logline_patterns = [
|
2102 |
-
r'(?:LOGLINE|로그라인)[:\s]*\*?\*?([^\n]+)',
|
2103 |
-
r'\*\*(?:LOGLINE|로그라인)\*\*[:\s]*([^\n]+)',
|
2104 |
-
r'Logline[:\s]*([^\n]+)'
|
2105 |
-
]
|
2106 |
-
|
2107 |
-
for pattern in logline_patterns:
|
2108 |
-
logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2109 |
-
if logline_match:
|
2110 |
-
# Get full logline (might be multi-line)
|
2111 |
-
logline_text = logline_match.group(1).strip()
|
2112 |
-
# Continue reading if it's incomplete
|
2113 |
-
if not logline_text.endswith('.'):
|
2114 |
-
next_lines = content[logline_match.end():].split('\n')
|
2115 |
-
for line in next_lines[:3]: # Check next 3 lines
|
2116 |
-
if line.strip() and not re.match(r'^[A-Z가-힣\d]', line.strip()):
|
2117 |
-
logline_text += ' ' + line.strip()
|
2118 |
-
else:
|
2119 |
-
break
|
2120 |
-
self.screenplay_tracker.screenplay_bible.logline = logline_text
|
2121 |
-
break
|
2122 |
-
|
2123 |
-
# Extract genre
|
2124 |
-
genre_match = re.search(r'(?:Primary Genre|주 장르)[:\s]*([^\n]+)', content, re.IGNORECASE)
|
2125 |
-
if genre_match:
|
2126 |
-
self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
|
2127 |
-
|
2128 |
-
# Save to database
|
2129 |
-
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2130 |
-
self.screenplay_tracker.screenplay_bible)
|
2131 |
-
|
2132 |
-
except Exception as e:
|
2133 |
-
logger.error(f"Error processing producer content: {e}")
|
2134 |
-
|
2135 |
-
|
2136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2138 |
|
2139 |
def _process_story_content(self, content: str):
|
2140 |
"""Process story developer output"""
|
@@ -2149,22 +2191,6 @@ You provide feedback that's critical yet encouraging."""
|
|
2149 |
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2150 |
self.screenplay_tracker.screenplay_bible)
|
2151 |
|
2152 |
-
def _process_character_content(self, content: str):
|
2153 |
-
"""Process character designer output"""
|
2154 |
-
# Extract protagonist
|
2155 |
-
protagonist_section = self._extract_section(content, "PROTAGONIST|주인공")
|
2156 |
-
if protagonist_section:
|
2157 |
-
protagonist = self._parse_character_profile(protagonist_section, "protagonist")
|
2158 |
-
self.screenplay_tracker.add_character(protagonist)
|
2159 |
-
ScreenplayDatabase.save_character(self.current_session_id, protagonist)
|
2160 |
-
|
2161 |
-
# Extract antagonist
|
2162 |
-
antagonist_section = self._extract_section(content, "ANTAGONIST|적대자")
|
2163 |
-
if antagonist_section:
|
2164 |
-
antagonist = self._parse_character_profile(antagonist_section, "antagonist")
|
2165 |
-
self.screenplay_tracker.add_character(antagonist)
|
2166 |
-
ScreenplayDatabase.save_character(self.current_session_id, antagonist)
|
2167 |
-
|
2168 |
def _process_scene_content(self, content: str):
|
2169 |
"""Process scene planner output"""
|
2170 |
# Parse scene breakdown
|
@@ -2193,44 +2219,6 @@ You provide feedback that's critical yet encouraging."""
|
|
2193 |
self.screenplay_tracker.add_scene(scene)
|
2194 |
ScreenplayDatabase.save_scene(self.current_session_id, scene)
|
2195 |
|
2196 |
-
def _extract_section(self, content: str, section_pattern: str) -> str:
|
2197 |
-
"""Extract section from content"""
|
2198 |
-
pattern = rf'(?:{section_pattern})[:\s]*(.+?)(?=\n(?:[A-Z]{{2,}}|[가-힣]{{2,}}):|\Z)'
|
2199 |
-
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2200 |
-
return match.group(1).strip() if match else ""
|
2201 |
-
|
2202 |
-
def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
|
2203 |
-
"""Parse character profile from content"""
|
2204 |
-
# Extract character details using regex or string parsing
|
2205 |
-
name = self._extract_field(content, "Name|이름") or f"Character_{role}"
|
2206 |
-
|
2207 |
-
return CharacterProfile(
|
2208 |
-
name=name,
|
2209 |
-
role=role,
|
2210 |
-
archetype=self._extract_field(content, "Archetype|아크타입") or "",
|
2211 |
-
want=self._extract_field(content, "WANT|외적 목표") or "",
|
2212 |
-
need=self._extract_field(content, "NEED|내적 필요") or "",
|
2213 |
-
backstory=self._extract_field(content, "Backstory|백스토리") or "",
|
2214 |
-
personality=[], # Would be parsed from content
|
2215 |
-
speech_pattern=self._extract_field(content, "Speech|말투") or "",
|
2216 |
-
character_arc=self._extract_field(content, "Arc|아크") or ""
|
2217 |
-
)
|
2218 |
-
|
2219 |
-
def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
|
2220 |
-
"""Extract field value from content with improved parsing"""
|
2221 |
-
# More flexible pattern that handles various formats
|
2222 |
-
pattern = rf'{field_pattern}(.+?)(?=\n[A-Z가-힣]|$)'
|
2223 |
-
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2224 |
-
if match:
|
2225 |
-
value = match.group(1).strip()
|
2226 |
-
# Remove markdown formatting if present
|
2227 |
-
value = re.sub(r'\*\*', '', value)
|
2228 |
-
value = re.sub(r'^\s*[-•]\s*', '', value)
|
2229 |
-
# Remove trailing punctuation
|
2230 |
-
value = re.sub(r'[,.:;]$', '', value)
|
2231 |
-
return value.strip()
|
2232 |
-
return None
|
2233 |
-
|
2234 |
# --- Utility functions ---
|
2235 |
def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
|
2236 |
"""Generate random screenplay theme"""
|
@@ -2421,7 +2409,7 @@ def format_screenplay_display(screenplay_text: str) -> str:
|
|
2421 |
|
2422 |
# Format scene headings
|
2423 |
formatted_text = re.sub(
|
2424 |
-
r'^(INT\.|EXT\.)(.*?)
|
2425 |
r'**\1\2**',
|
2426 |
screenplay_text,
|
2427 |
flags=re.MULTILINE
|
@@ -2429,7 +2417,7 @@ def format_screenplay_display(screenplay_text: str) -> str:
|
|
2429 |
|
2430 |
# Format character names (all caps on their own line)
|
2431 |
formatted_text = re.sub(
|
2432 |
-
r'^([A-Z][A-Z\s]+)
|
2433 |
r'**\1**',
|
2434 |
formatted_text,
|
2435 |
flags=re.MULTILINE
|
@@ -2897,4 +2885,4 @@ if __name__ == "__main__":
|
|
2897 |
server_port=7860,
|
2898 |
share=False,
|
2899 |
debug=True
|
2900 |
-
)
|
|
|
536 |
|
537 |
return theme_id
|
538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
539 |
class WebSearchIntegration:
|
540 |
"""Web search functionality for screenplay research"""
|
541 |
def __init__(self):
|
|
|
1469 |
raise Exception(f"LLM Call Failed: {full_content}")
|
1470 |
return full_content
|
1471 |
|
|
|
|
|
1472 |
def call_llm_streaming(self, messages: List[Dict[str, str]], role: str,
|
1473 |
language: str) -> Generator[str, None, None]:
|
1474 |
try:
|
|
|
1618 |
logger.error(traceback.format_exc())
|
1619 |
yield f"❌ Unexpected error: {str(e)}"
|
1620 |
|
|
|
|
|
1621 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
1622 |
"""Role-specific system prompts"""
|
1623 |
|
|
|
1708 |
|
1709 |
return base_prompts.get(language, base_prompts["English"])
|
1710 |
|
1711 |
+
# --- Main process ---
|
|
|
|
|
1712 |
def process_screenplay_stream(self, query: str, screenplay_type: str, genre: str,
|
1713 |
language: str, session_id: Optional[str] = None
|
1714 |
) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
|
|
|
1868 |
|
1869 |
return "\n\n---\n\n".join(previous) if previous else ""
|
1870 |
|
1871 |
+
def _extract_field(self, content: str, field_pattern: str) -> Optional[str]:
|
1872 |
+
"""Extract field value from content with improved parsing"""
|
1873 |
+
# More flexible pattern that handles various formats
|
1874 |
+
pattern = rf'{field_pattern}[:\s]*([^\n]+?)(?=\n[A-Z가-힣]|$)'
|
1875 |
+
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
1876 |
+
if match and match.group(1): # Check if group(1) exists and is not None
|
1877 |
+
value = match.group(1).strip()
|
1878 |
+
# Remove markdown formatting if present
|
1879 |
+
value = re.sub(r'\*\*', '', value)
|
1880 |
+
value = re.sub(r'^\s*[-•]\s*', '', value)
|
1881 |
+
# Remove trailing punctuation
|
1882 |
+
value = re.sub(r'[,.:;], '', value)
|
1883 |
+
return value.strip() if value else None
|
1884 |
+
return None
|
1885 |
|
1886 |
def _parse_character_profile(self, content: str, role: str) -> CharacterProfile:
|
1887 |
"""Parse character profile from content"""
|
|
|
1899 |
|
1900 |
for pattern in name_patterns:
|
1901 |
name_match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)
|
1902 |
+
if name_match and name_match.group(1):
|
1903 |
extracted_name = name_match.group(1).strip()
|
1904 |
# Remove markdown and extra characters
|
1905 |
+
extracted_name = re.sub(r'[*:\s]+, '', extracted_name)
|
1906 |
extracted_name = re.sub(r'^[*:\s]+', '', extracted_name)
|
1907 |
if extracted_name and len(extracted_name) > 1:
|
1908 |
name = extracted_name
|
|
|
1914 |
patterns = [patterns]
|
1915 |
|
1916 |
for pattern in patterns:
|
1917 |
+
# Improved pattern with better capturing groups
|
1918 |
match = re.search(rf'{pattern}[:\s]*([^\n*]+?)(?=\n|$)', content, re.IGNORECASE | re.DOTALL)
|
1919 |
+
if match and match.group(1):
|
1920 |
value = match.group(1).strip()
|
1921 |
# Clean up the value
|
1922 |
value = re.sub(r'^[-*•:\s]+', '', value)
|
|
|
1926 |
return value
|
1927 |
return ""
|
1928 |
|
1929 |
+
# Extract all fields with safer extraction
|
1930 |
profile = CharacterProfile(
|
1931 |
name=name,
|
1932 |
role=role,
|
|
|
1972 |
logger.debug(f"Parsed character: {profile.name}")
|
1973 |
return profile
|
1974 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1975 |
def _extract_personality_traits(self, content: str) -> List[str]:
|
1976 |
+
"""Extract personality traits from content"""
|
1977 |
+
traits = []
|
1978 |
+
# Look for personality section with multiple pattern options
|
1979 |
+
personality_patterns = [
|
1980 |
+
r"(?:Personality|성격 특성|성격)[:\s]*([^\n]+(?:\n(?![\w가-힣]+:)[^\n]+)*)",
|
1981 |
+
r"성격[:\s]*(?:\n?[-•*]\s*[^\n]+)+"
|
1982 |
+
]
|
1983 |
+
|
1984 |
+
for pattern in personality_patterns:
|
1985 |
+
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
1986 |
+
if match and match.group(1):
|
1987 |
+
personality_section = match.group(1)
|
1988 |
+
# Extract individual traits (usually listed)
|
1989 |
+
trait_lines = personality_section.split('\n')
|
1990 |
+
for line in trait_lines:
|
1991 |
+
line = line.strip()
|
1992 |
+
if line and not line.endswith(':'):
|
1993 |
+
# Remove list markers
|
1994 |
+
trait = re.sub(r'^\s*[-•*]\s*', '', line)
|
1995 |
+
trait = re.sub(r'^\d+\.\s*', '', trait) # Remove numbered lists
|
1996 |
+
if trait and len(trait) > 2: # Skip very short entries
|
1997 |
+
traits.append(trait)
|
1998 |
+
if traits: # If we found traits, stop looking
|
1999 |
+
break
|
2000 |
+
|
2001 |
+
return traits[:5] # Limit to 5 traits
|
2002 |
|
2003 |
def _process_character_content(self, content: str):
|
2004 |
+
"""Process character designer output with better error handling"""
|
2005 |
+
try:
|
2006 |
+
# Extract protagonist
|
2007 |
+
protagonist_section = self._extract_section(content, r"(?:PROTAGONIST|주인공)")
|
2008 |
+
if protagonist_section:
|
2009 |
+
try:
|
2010 |
+
protagonist = self._parse_character_profile(protagonist_section, "protagonist")
|
2011 |
+
self.screenplay_tracker.add_character(protagonist)
|
2012 |
+
ScreenplayDatabase.save_character(self.current_session_id, protagonist)
|
2013 |
+
except Exception as e:
|
2014 |
+
logger.error(f"Error parsing protagonist: {e}")
|
2015 |
+
# Create a default protagonist to continue
|
2016 |
+
protagonist = CharacterProfile(
|
2017 |
+
name="Protagonist",
|
2018 |
+
role="protagonist",
|
2019 |
+
archetype="Hero",
|
2020 |
+
want="To achieve goal",
|
2021 |
+
need="To grow",
|
2022 |
+
backstory="Unknown",
|
2023 |
+
personality=["Determined"],
|
2024 |
+
speech_pattern="Normal",
|
2025 |
+
character_arc="Growth"
|
2026 |
+
)
|
2027 |
+
self.screenplay_tracker.add_character(protagonist)
|
2028 |
+
|
2029 |
+
# Extract antagonist
|
2030 |
+
antagonist_section = self._extract_section(content, r"(?:ANTAGONIST|적대자)")
|
2031 |
+
if antagonist_section:
|
2032 |
+
try:
|
2033 |
+
antagonist = self._parse_character_profile(antagonist_section, "antagonist")
|
2034 |
+
self.screenplay_tracker.add_character(antagonist)
|
2035 |
+
ScreenplayDatabase.save_character(self.current_session_id, antagonist)
|
2036 |
+
except Exception as e:
|
2037 |
+
logger.error(f"Error parsing antagonist: {e}")
|
2038 |
+
# Create a default antagonist to continue
|
2039 |
+
antagonist = CharacterProfile(
|
2040 |
+
name="Antagonist",
|
2041 |
+
role="antagonist",
|
2042 |
+
archetype="Villain",
|
2043 |
+
want="To stop protagonist",
|
2044 |
+
need="Power",
|
2045 |
+
backstory="Unknown",
|
2046 |
+
personality=["Ruthless"],
|
2047 |
+
speech_pattern="Menacing",
|
2048 |
+
character_arc="Downfall"
|
2049 |
+
)
|
2050 |
+
self.screenplay_tracker.add_character(antagonist)
|
2051 |
+
|
2052 |
+
# Extract supporting characters
|
2053 |
+
supporting_section = self._extract_section(content, r"(?:SUPPORTING CAST|조력자들)")
|
2054 |
+
if supporting_section:
|
2055 |
+
# Parse multiple supporting characters
|
2056 |
+
self._parse_supporting_characters(supporting_section)
|
2057 |
+
|
2058 |
+
except Exception as e:
|
2059 |
+
logger.error(f"Error processing character content: {e}")
|
2060 |
+
# Continue with default values rather than failing
|
2061 |
|
2062 |
def _extract_section(self, content: str, section_pattern: str) -> str:
|
2063 |
+
"""Extract section from content with improved pattern matching"""
|
2064 |
+
# More flexible section extraction
|
2065 |
+
patterns = [
|
2066 |
+
# Pattern 1: Section header followed by content until next major section
|
2067 |
+
rf'{section_pattern}[:\s]*\n?(.*?)(?=\n\n[A-Z가-힣]{{2,}}[:\s]|\n\n\d+\.|$)',
|
2068 |
+
# Pattern 2: Section header with content until next section (alternative)
|
2069 |
+
rf'{section_pattern}.*?\n((?:.*\n)*?)(?=\n[A-Z가-힣]{{2,}}:|$)',
|
2070 |
+
# Pattern 3: More flexible pattern for Korean text
|
2071 |
+
rf'{section_pattern}[:\s]*\n?((?:[^\n]+\n?)*?)(?=\n\n|\Z)'
|
2072 |
+
]
|
2073 |
+
|
2074 |
+
for pattern in patterns:
|
2075 |
+
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2076 |
+
if match and match.group(1):
|
2077 |
+
section_content = match.group(1).strip()
|
2078 |
+
if section_content: # Only return if we got actual content
|
2079 |
+
return section_content
|
2080 |
+
|
2081 |
+
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2082 |
|
2083 |
+
def _parse_supporting_characters(self, content: str):
|
2084 |
+
"""Parse supporting characters from content"""
|
2085 |
+
# Split by character markers (numbers or bullets)
|
2086 |
+
char_sections = re.split(r'\n(?:\d+\.|[-•*])\s*', content)
|
2087 |
+
|
2088 |
+
for i, section in enumerate(char_sections[1:], 1): # Skip first empty split
|
2089 |
+
if section.strip():
|
2090 |
+
try:
|
2091 |
+
# Try multiple name extraction patterns
|
2092 |
+
name = None
|
2093 |
+
name_patterns = [
|
2094 |
+
r"(?:이름|Name)[:\s]*([^,\n]+)",
|
2095 |
+
r"^([^:\n]+?)(?:\s*[-–]\s*|:\s*)", # Name at start before dash or colon
|
2096 |
+
r"^([가-힣A-Za-z\s]+?)(?:\s*\(|$)" # Korean/English name before parenthesis
|
2097 |
+
]
|
2098 |
+
|
2099 |
+
for pattern in name_patterns:
|
2100 |
+
name_match = re.search(pattern, section.strip(), re.IGNORECASE)
|
2101 |
+
if name_match and name_match.group(1):
|
2102 |
+
name = name_match.group(1).strip()
|
2103 |
+
if name and len(name) > 1:
|
2104 |
+
break
|
2105 |
+
|
2106 |
+
if not name:
|
2107 |
+
name = f"Supporting_{i}"
|
2108 |
+
|
2109 |
+
role_desc = self._extract_field(section, r"(?:Role|역할)[:\s]*") or "supporting"
|
2110 |
+
|
2111 |
+
character = CharacterProfile(
|
2112 |
+
name=name,
|
2113 |
+
role="supporting",
|
2114 |
+
archetype=role_desc,
|
2115 |
+
want="",
|
2116 |
+
need="",
|
2117 |
+
backstory=self._extract_field(section, r"(?:Backstory|백스토리)[:\s]*") or "",
|
2118 |
+
personality=[],
|
2119 |
+
speech_pattern="",
|
2120 |
+
character_arc=""
|
2121 |
+
)
|
2122 |
+
|
2123 |
+
self.screenplay_tracker.add_character(character)
|
2124 |
+
ScreenplayDatabase.save_character(self.current_session_id, character)
|
2125 |
+
|
2126 |
+
except Exception as e:
|
2127 |
+
logger.warning(f"Error parsing supporting character {i}: {e}")
|
2128 |
+
continue
|
2129 |
|
2130 |
+
def _process_producer_content(self, content: str):
|
2131 |
+
"""Process producer output with better extraction"""
|
2132 |
+
try:
|
2133 |
+
# Extract title with various formats
|
2134 |
+
title_patterns = [
|
2135 |
+
r'(?:TITLE|제목)[:\s]*\*?\*?([^\n*]+)\*?\*?',
|
2136 |
+
r'\*\*(?:TITLE|제목)\*\*[:\s]*([^\n]+)',
|
2137 |
+
r'Title[:\s]*([^\n]+)'
|
2138 |
+
]
|
2139 |
+
|
2140 |
+
for pattern in title_patterns:
|
2141 |
+
title_match = re.search(pattern, content, re.IGNORECASE)
|
2142 |
+
if title_match:
|
2143 |
+
self.screenplay_tracker.screenplay_bible.title = title_match.group(1).strip()
|
2144 |
+
break
|
2145 |
+
|
2146 |
+
# Extract logline with various formats
|
2147 |
+
logline_patterns = [
|
2148 |
+
r'(?:LOGLINE|로그라인)[:\s]*\*?\*?([^\n]+)',
|
2149 |
+
r'\*\*(?:LOGLINE|로그라인)\*\*[:\s]*([^\n]+)',
|
2150 |
+
r'Logline[:\s]*([^\n]+)'
|
2151 |
+
]
|
2152 |
+
|
2153 |
+
for pattern in logline_patterns:
|
2154 |
+
logline_match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
|
2155 |
+
if logline_match:
|
2156 |
+
# Get full logline (might be multi-line)
|
2157 |
+
logline_text = logline_match.group(1).strip()
|
2158 |
+
# Continue reading if it's incomplete
|
2159 |
+
if not logline_text.endswith('.'):
|
2160 |
+
next_lines = content[logline_match.end():].split('\n')
|
2161 |
+
for line in next_lines[:3]: # Check next 3 lines
|
2162 |
+
if line.strip() and not re.match(r'^[A-Z가-힣\d]', line.strip()):
|
2163 |
+
logline_text += ' ' + line.strip()
|
2164 |
+
else:
|
2165 |
+
break
|
2166 |
+
self.screenplay_tracker.screenplay_bible.logline = logline_text
|
2167 |
+
break
|
2168 |
+
|
2169 |
+
# Extract genre
|
2170 |
+
genre_match = re.search(r'(?:Primary Genre|주 장르)[:\s]*([^\n]+)', content, re.IGNORECASE)
|
2171 |
+
if genre_match:
|
2172 |
+
self.screenplay_tracker.screenplay_bible.genre = genre_match.group(1).strip()
|
2173 |
+
|
2174 |
+
# Save to database
|
2175 |
+
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2176 |
+
self.screenplay_tracker.screenplay_bible)
|
2177 |
+
|
2178 |
+
except Exception as e:
|
2179 |
+
logger.error(f"Error processing producer content: {e}")
|
2180 |
|
2181 |
def _process_story_content(self, content: str):
|
2182 |
"""Process story developer output"""
|
|
|
2191 |
ScreenplayDatabase.save_screenplay_bible(self.current_session_id,
|
2192 |
self.screenplay_tracker.screenplay_bible)
|
2193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2194 |
def _process_scene_content(self, content: str):
|
2195 |
"""Process scene planner output"""
|
2196 |
# Parse scene breakdown
|
|
|
2219 |
self.screenplay_tracker.add_scene(scene)
|
2220 |
ScreenplayDatabase.save_scene(self.current_session_id, scene)
|
2221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2222 |
# --- Utility functions ---
|
2223 |
def generate_random_screenplay_theme(screenplay_type: str, genre: str, language: str) -> str:
|
2224 |
"""Generate random screenplay theme"""
|
|
|
2409 |
|
2410 |
# Format scene headings
|
2411 |
formatted_text = re.sub(
|
2412 |
+
r'^(INT\.|EXT\.)(.*?),
|
2413 |
r'**\1\2**',
|
2414 |
screenplay_text,
|
2415 |
flags=re.MULTILINE
|
|
|
2417 |
|
2418 |
# Format character names (all caps on their own line)
|
2419 |
formatted_text = re.sub(
|
2420 |
+
r'^([A-Z][A-Z\s]+),
|
2421 |
r'**\1**',
|
2422 |
formatted_text,
|
2423 |
flags=re.MULTILINE
|
|
|
2885 |
server_port=7860,
|
2886 |
share=False,
|
2887 |
debug=True
|
2888 |
+
)
|