yalrashed commited on
Commit
edb392c
·
verified ·
1 Parent(s): 07dd260

Upload creative_analyzer.py

Browse files
Files changed (1) hide show
  1. src/analysis/creative_analyzer.py +293 -0
src/analysis/creative_analyzer.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ import logging
4
+ from tqdm import tqdm
5
+ import requests
6
+ from src.analysis.analysis_cleaner import AnalysisCleaner
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class CreativeAnalyzer:
11
+ def __init__(self):
12
+ # Initialize Claude
13
+ self.api_key = os.getenv("ANTHROPIC_API_KEY")
14
+ if not self.api_key:
15
+ raise ValueError("ANTHROPIC_API_KEY not found")
16
+
17
+ self.api_url = "https://api.anthropic.com/v1/messages"
18
+ self.model = "claude-3-sonnet-20240229"
19
+ self.headers = {
20
+ "x-api-key": self.api_key,
21
+ "anthropic-version": "2023-06-01",
22
+ "content-type": "application/json"
23
+ }
24
+
25
+ # Set chunk size
26
+ self.chunk_size = 6000 # Claude handles larger chunks well
27
+
28
+ def query_claude(self, prompt: str) -> str:
29
+ """Send request to Claude API with proper response handling"""
30
+ try:
31
+ payload = {
32
+ "model": self.model,
33
+ "max_tokens": 4096,
34
+ "messages": [{
35
+ "role": "user",
36
+ "content": prompt
37
+ }]
38
+ }
39
+
40
+ response = requests.post(self.api_url, headers=self.headers, json=payload)
41
+
42
+ if response.status_code == 200:
43
+ response_json = response.json()
44
+ # Get the message content from Claude's response
45
+ if ('content' in response_json and
46
+ isinstance(response_json['content'], list) and
47
+ len(response_json['content']) > 0 and
48
+ 'text' in response_json['content'][0]):
49
+ return response_json['content'][0]['text']
50
+ else:
51
+ logger.error("Invalid response structure")
52
+ logger.error(f"Response: {response_json}")
53
+ return None
54
+ else:
55
+ logger.error(f"API Error: {response.status_code}")
56
+ logger.error(f"Response: {response.text}")
57
+ return None
58
+
59
+ except Exception as e:
60
+ logger.error(f"Error making API request: {str(e)}")
61
+ logger.error("Full error details:", exc_info=True)
62
+ return None
63
+
64
+ def count_tokens(self, text: str) -> int:
65
+ """Estimate token count using simple word-based estimation"""
66
+ words = text.split()
67
+ return int(len(words) * 1.3)
68
+
69
+ def chunk_screenplay(self, text: str) -> list:
70
+ """Split screenplay into chunks with overlap for context"""
71
+ logger.info("Chunking screenplay...")
72
+
73
+ scenes = text.split("\n\n")
74
+ chunks = []
75
+ current_chunk = []
76
+ current_size = 0
77
+ overlap_scenes = 2
78
+
79
+ for scene in scenes:
80
+ scene_size = self.count_tokens(scene)
81
+
82
+ if current_size + scene_size > self.chunk_size and current_chunk:
83
+ overlap = current_chunk[-overlap_scenes:] if len(current_chunk) > overlap_scenes else current_chunk
84
+ chunks.append("\n\n".join(current_chunk))
85
+ current_chunk = overlap + [scene]
86
+ current_size = sum(self.count_tokens(s) for s in current_chunk)
87
+ else:
88
+ current_chunk.append(scene)
89
+ current_size += scene_size
90
+
91
+ if current_chunk:
92
+ chunks.append("\n\n".join(current_chunk))
93
+
94
+ logger.info(f"Split screenplay into {len(chunks)} chunks with {overlap_scenes} scene overlap")
95
+ return chunks
96
+
97
+ def analyze_plot_development(self, chunk: str, previous_plot_points: str = "") -> str:
98
+ prompt = f"""You are a professional screenplay analyst. Building on this previous analysis:
99
+ {previous_plot_points}
100
+
101
+ Continue analyzing the story's progression. Tell me what happens next, focusing on new developments and changes. Reference specific moments from this section but don't repeat what we've covered.
102
+
103
+ Consider:
104
+ - How events build on what came before
105
+ - Their impact on story direction
106
+ - Changes to the narrative
107
+
108
+ Use flowing paragraphs and support with specific examples.
109
+
110
+ Screenplay section to analyze:
111
+ {chunk}"""
112
+
113
+ return self.query_claude(prompt)
114
+
115
+ def analyze_character_arcs(self, chunk: str, plot_context: str, previous_character_dev: str = "") -> str:
116
+ prompt = f"""You are a professional screenplay analyst. Based on these plot developments:
117
+ {plot_context}
118
+
119
+ And previous character analysis:
120
+ {previous_character_dev}
121
+
122
+ Continue analyzing how the characters evolve. Focus on their growth, changes, and key moments from this section. Build on, don't repeat, previous analysis.
123
+
124
+ Consider:
125
+ - Character choices and consequences
126
+ - Relationship dynamics
127
+ - Internal conflicts and growth
128
+
129
+ Use flowing paragraphs with specific examples.
130
+
131
+ Screenplay section to analyze:
132
+ {chunk}"""
133
+
134
+ return self.query_claude(prompt)
135
+
136
+ def analyze_dialogue_progression(self, chunk: str, character_context: str, previous_dialogue: str = "") -> str:
137
+ prompt = f"""You are a professional screenplay analyst. Understanding the character context:
138
+ {character_context}
139
+
140
+ And previous dialogue analysis:
141
+ {previous_dialogue}
142
+
143
+ Analyze the dialogue in this section from a screenwriting perspective. What makes it effective or distinctive?
144
+
145
+ Consider:
146
+ - How dialogue reveals character
147
+ - Subtext and meaning
148
+ - Character voices and patterns
149
+ - Impact on relationships
150
+
151
+ Use specific dialogue examples in flowing paragraphs.
152
+
153
+ Screenplay section to analyze:
154
+ {chunk}"""
155
+
156
+ return self.query_claude(prompt)
157
+
158
+ def analyze_themes(self, chunk: str, plot_context: str, character_context: str) -> str:
159
+ prompt = f"""You are a professional screenplay analyst. Based on these plot developments:
160
+ {plot_context}
161
+
162
+ And character journeys:
163
+ {character_context}
164
+
165
+ Analyze how themes develop in this section. What deeper meanings emerge? How do they connect to previous themes?
166
+
167
+ Consider:
168
+ - Core ideas and messages
169
+ - Symbolic elements
170
+ - How themes connect to character arcs
171
+ - Social or philosophical implications
172
+
173
+ Support with specific examples in flowing paragraphs.
174
+
175
+ Screenplay section to analyze:
176
+ {chunk}"""
177
+
178
+ return self.query_claude(prompt)
179
+
180
+ def analyze_screenplay(self, screenplay_path: Path) -> bool:
181
+ """Main method to generate creative analysis"""
182
+ logger.info("Starting creative analysis")
183
+
184
+ try:
185
+ # Read screenplay
186
+ with open(screenplay_path, 'r', encoding='utf-8') as file:
187
+ screenplay_text = file.read()
188
+
189
+ # Split into chunks
190
+ chunks = self.chunk_screenplay(screenplay_text)
191
+
192
+ # Initialize analyses
193
+ plot_analysis = []
194
+ character_analysis = []
195
+ dialogue_analysis = []
196
+ theme_analysis = []
197
+
198
+ # First Pass: Plot Development
199
+ logger.info("First Pass: Analyzing plot development")
200
+ with tqdm(total=len(chunks), desc="Analyzing plot") as pbar:
201
+ for chunk in chunks:
202
+ result = self.analyze_plot_development(
203
+ chunk,
204
+ "\n\n".join(plot_analysis)
205
+ )
206
+ if result:
207
+ plot_analysis.append(result)
208
+ else:
209
+ logger.error("Failed to get plot analysis")
210
+ return False
211
+ pbar.update(1)
212
+
213
+ # Second Pass: Character Arcs
214
+ logger.info("Second Pass: Analyzing character arcs")
215
+ with tqdm(total=len(chunks), desc="Analyzing characters") as pbar:
216
+ for chunk in chunks:
217
+ result = self.analyze_character_arcs(
218
+ chunk,
219
+ "\n\n".join(plot_analysis),
220
+ "\n\n".join(character_analysis)
221
+ )
222
+ if result:
223
+ character_analysis.append(result)
224
+ else:
225
+ logger.error("Failed to get character analysis")
226
+ return False
227
+ pbar.update(1)
228
+
229
+ # Third Pass: Dialogue Progression
230
+ logger.info("Third Pass: Analyzing dialogue")
231
+ with tqdm(total=len(chunks), desc="Analyzing dialogue") as pbar:
232
+ for chunk in chunks:
233
+ result = self.analyze_dialogue_progression(
234
+ chunk,
235
+ "\n\n".join(character_analysis),
236
+ "\n\n".join(dialogue_analysis)
237
+ )
238
+ if result:
239
+ dialogue_analysis.append(result)
240
+ else:
241
+ logger.error("Failed to get dialogue analysis")
242
+ return False
243
+ pbar.update(1)
244
+
245
+ # Fourth Pass: Thematic Development
246
+ logger.info("Fourth Pass: Analyzing themes")
247
+ with tqdm(total=len(chunks), desc="Analyzing themes") as pbar:
248
+ for chunk in chunks:
249
+ result = self.analyze_themes(
250
+ chunk,
251
+ "\n\n".join(plot_analysis),
252
+ "\n\n".join(character_analysis)
253
+ )
254
+ if result:
255
+ theme_analysis.append(result)
256
+ else:
257
+ logger.error("Failed to get theme analysis")
258
+ return False
259
+ pbar.update(1)
260
+
261
+ # Clean up analyses
262
+ cleaner = AnalysisCleaner()
263
+ cleaned_analyses = {
264
+ 'plot': cleaner.clean_analysis("\n\n".join(plot_analysis)),
265
+ 'character': cleaner.clean_analysis("\n\n".join(character_analysis)),
266
+ 'dialogue': cleaner.clean_analysis("\n\n".join(dialogue_analysis)),
267
+ 'theme': cleaner.clean_analysis("\n\n".join(theme_analysis))
268
+ }
269
+
270
+ # Save Analysis
271
+ output_path = screenplay_path.parent / "creative_analysis.txt"
272
+ with open(output_path, 'w', encoding='utf-8') as f:
273
+ f.write("SCREENPLAY CREATIVE ANALYSIS\n\n")
274
+
275
+ sections = [
276
+ ("PLOT PROGRESSION", cleaned_analyses['plot']),
277
+ ("CHARACTER ARCS", cleaned_analyses['character']),
278
+ ("DIALOGUE PROGRESSION", cleaned_analyses['dialogue']),
279
+ ("THEMATIC DEVELOPMENT", cleaned_analyses['theme'])
280
+ ]
281
+
282
+ for title, content in sections:
283
+ f.write(f"### {title} ###\n\n")
284
+ f.write(content)
285
+ f.write("\n\n")
286
+
287
+ logger.info(f"Analysis saved to: {output_path}")
288
+ return True
289
+
290
+ except Exception as e:
291
+ logger.error(f"Error in creative analysis: {str(e)}")
292
+ logger.error("Full error details:", exc_info=True)
293
+ return False