Adnan commited on
Commit
519e224
·
verified ·
1 Parent(s): 9832423

Create prompt_parser.py

Browse files
Files changed (1) hide show
  1. prompt_parser.py +240 -0
prompt_parser.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TimeLapseForge — Prompt Parser Module
3
+ Parses GPT JSON output and extracts panel prompts, style guides, and assembly instructions.
4
+ Also provides a quick-generate template for text-only input.
5
+ """
6
+
7
+ import json
8
+ import re
9
+ from typing import List, Dict, Any, Optional, Tuple
10
+
11
+
12
+ class PromptParser:
13
+ """Parses and validates timelapse JSON from GPT output."""
14
+
15
+ @staticmethod
16
+ def clean_json_string(raw: str) -> str:
17
+ """Remove markdown code block wrappers and clean the string."""
18
+ raw = raw.strip()
19
+ # Remove ```json ... ``` or ``` ... ```
20
+ pattern = r"^```(?:json)?\s*\n?(.*?)\n?\s*```$"
21
+ match = re.match(pattern, raw, re.DOTALL)
22
+ if match:
23
+ raw = match.group(1).strip()
24
+ return raw
25
+
26
+ @staticmethod
27
+ def find_json_in_text(text: str) -> str:
28
+ """Find the first valid JSON object in a block of text."""
29
+ # Try to find JSON between curly braces
30
+ depth = 0
31
+ start = None
32
+ for i, char in enumerate(text):
33
+ if char == '{':
34
+ if depth == 0:
35
+ start = i
36
+ depth += 1
37
+ elif char == '}':
38
+ depth -= 1
39
+ if depth == 0 and start is not None:
40
+ return text[start:i + 1]
41
+ return text
42
+
43
+ def parse(self, raw_input: str) -> Dict[str, Any]:
44
+ """
45
+ Parse raw JSON string from GPT output.
46
+ Handles markdown wrappers, nested JSON, and common formatting issues.
47
+ """
48
+ cleaned = self.clean_json_string(raw_input)
49
+
50
+ try:
51
+ data = json.loads(cleaned)
52
+ return {"success": True, "data": data, "error": None}
53
+ except json.JSONDecodeError:
54
+ pass
55
+
56
+ # Try finding JSON within text
57
+ try:
58
+ json_str = self.find_json_in_text(cleaned)
59
+ data = json.loads(json_str)
60
+ return {"success": True, "data": data, "error": None}
61
+ except (json.JSONDecodeError, Exception) as e:
62
+ return {"success": False, "data": None, "error": str(e)}
63
+
64
+ def extract_prompts(self, data: Dict[str, Any]) -> List[Dict[str, str]]:
65
+ """Extract all panel prompts from parsed JSON data."""
66
+ panels = data.get("panels", [])
67
+ prompts = []
68
+
69
+ for panel in panels:
70
+ panel_id = panel.get("panel_id", len(prompts) + 1)
71
+
72
+ # Handle different JSON structures
73
+ img_prompt = panel.get("image_prompt", {})
74
+ if isinstance(img_prompt, dict):
75
+ main_prompt = img_prompt.get("main_prompt", "")
76
+ negative = img_prompt.get("negative_prompt", "")
77
+ style = img_prompt.get("style_suffix", "")
78
+ elif isinstance(img_prompt, str):
79
+ main_prompt = img_prompt
80
+ negative = ""
81
+ style = ""
82
+ else:
83
+ # Fallback: look for prompt directly in panel
84
+ main_prompt = panel.get("prompt", panel.get("description", ""))
85
+ negative = panel.get("negative_prompt", "")
86
+ style = ""
87
+
88
+ # Also check for video_prompt
89
+ vid_prompt = panel.get("video_prompt", {})
90
+ motion_desc = ""
91
+ if isinstance(vid_prompt, dict):
92
+ motion_desc = vid_prompt.get("motion_description", "")
93
+
94
+ prompts.append({
95
+ "panel_id": panel_id,
96
+ "main_prompt": main_prompt,
97
+ "negative_prompt": negative,
98
+ "style_suffix": style,
99
+ "motion_description": motion_desc,
100
+ "panel_title": panel.get("panel_title", f"Panel {panel_id}"),
101
+ "timestamp_label": panel.get("timestamp_label", ""),
102
+ "phase": panel.get("phase_name", panel.get("phase", "")),
103
+ })
104
+
105
+ return prompts
106
+
107
+ def extract_style_guide(self, data: Dict[str, Any]) -> Dict[str, Any]:
108
+ """Extract style guide settings."""
109
+ return data.get("style_guide", {})
110
+
111
+ def extract_assembly_guide(self, data: Dict[str, Any]) -> Dict[str, Any]:
112
+ """Extract video assembly guide."""
113
+ return data.get("video_assembly_guide", {})
114
+
115
+ def extract_metadata(self, data: Dict[str, Any]) -> Dict[str, Any]:
116
+ """Extract project metadata."""
117
+ return data.get("project_metadata", {})
118
+
119
+ def get_summary(self, data: Dict[str, Any]) -> str:
120
+ """Get a human-readable summary of the parsed JSON."""
121
+ meta = self.extract_metadata(data)
122
+ prompts = self.extract_prompts(data)
123
+ phases = data.get("phases", data.get("restoration_phases", []))
124
+
125
+ summary = f"📋 **Project:** {meta.get('project_title', 'Untitled')}\n"
126
+ summary += f"🎬 **Type:** {meta.get('project_type', 'Unknown')}\n"
127
+ summary += f"🖼️ **Total Panels:** {len(prompts)}\n"
128
+ summary += f"📊 **Phases:** {len(phases)}\n"
129
+ summary += f"⏱️ **Timespan:** {meta.get('estimated_real_world_timespan', 'Unknown')}\n\n"
130
+
131
+ summary += "**Panels Preview:**\n"
132
+ for p in prompts[:5]:
133
+ summary += f"- Panel {p['panel_id']}: {p['main_prompt'][:80]}...\n"
134
+ if len(prompts) > 5:
135
+ summary += f"- ... and {len(prompts) - 5} more panels\n"
136
+
137
+ return summary
138
+
139
+
140
+ class QuickGenerator:
141
+ """Generate basic panel JSON from a simple text description."""
142
+
143
+ RESTORATION_PHASES = [
144
+ ("Initial State", 0.0, 0.05, "completely damaged, worn, broken, dirty, neglected"),
145
+ ("Assessment & Cleanup", 0.05, 0.15, "being assessed, initial cleaning, removing loose debris"),
146
+ ("Disassembly & Stripping", 0.15, 0.25, "parts being removed, old paint stripped, exposing bare material"),
147
+ ("Repair & Structural Work", 0.25, 0.45, "active repair, welding, filling dents, replacing broken parts"),
148
+ ("Sanding & Preparation", 0.45, 0.55, "surface sanding, smoothing, preparing for primer"),
149
+ ("Priming", 0.55, 0.65, "primer coat applied, even grey surface, smooth"),
150
+ ("Painting", 0.65, 0.78, "paint being applied, color emerging, wet paint sheen"),
151
+ ("Clear Coat & Finishing", 0.78, 0.88, "clear coat applied, glossy finish, protective layer"),
152
+ ("Reassembly & Detailing", 0.88, 0.95, "parts reinstalled, chrome polished, details perfected"),
153
+ ("Final Reveal", 0.95, 1.0, "fully restored, pristine, gleaming, showroom quality, dramatic lighting"),
154
+ ]
155
+
156
+ CREATION_PHASES = [
157
+ ("Empty Start", 0.0, 0.05, "empty space, blank canvas, raw materials, nothing built yet"),
158
+ ("Foundation & Planning", 0.05, 0.15, "ground preparation, foundation laid, basic framework started"),
159
+ ("Core Structure", 0.15, 0.35, "main structure being built, walls rising, skeleton forming"),
160
+ ("Enclosure & Walls", 0.35, 0.50, "walls completed, roof structure, enclosed space taking shape"),
161
+ ("Systems & Infrastructure", 0.50, 0.62, "wiring, plumbing, mechanical systems being installed"),
162
+ ("Interior Rough Work", 0.62, 0.73, "insulation, drywall, rough interior taking shape"),
163
+ ("Finishing & Details", 0.73, 0.85, "paint, trim, fixtures, flooring being installed"),
164
+ ("Furnishing & Styling", 0.85, 0.95, "furniture placed, decorations added, styled and arranged"),
165
+ ("Completed Reveal", 0.95, 1.0, "fully completed, beautiful, perfect lighting, hero shot"),
166
+ ]
167
+
168
+ def generate(
169
+ self,
170
+ description: str,
171
+ num_panels: int = 20,
172
+ mode: str = "restoration",
173
+ style: str = "photorealistic, 4K, cinematic lighting, consistent camera angle, shot on Sony A7IV"
174
+ ) -> Dict[str, Any]:
175
+ """Generate a complete JSON structure from a text description."""
176
+
177
+ phases = self.RESTORATION_PHASES if mode == "restoration" else self.CREATION_PHASES
178
+ panels = []
179
+
180
+ for i in range(num_panels):
181
+ progress = i / max(num_panels - 1, 1)
182
+
183
+ # Find current phase
184
+ current_phase = phases[-1]
185
+ for phase in phases:
186
+ if phase[1] <= progress <= phase[2]:
187
+ current_phase = phase
188
+ break
189
+
190
+ phase_name, _, _, phase_desc = current_phase
191
+ progress_pct = progress * 100
192
+
193
+ main_prompt = (
194
+ f"{description}, {phase_desc}, "
195
+ f"restoration progress {progress_pct:.0f}% complete, "
196
+ f"detailed textures visible, consistent environment, "
197
+ f"same camera angle throughout, {style}"
198
+ )
199
+
200
+ negative = (
201
+ "blurry, low quality, cartoon, anime, drawing, sketch, text, watermark, "
202
+ "inconsistent angle, different background, teleportation, sudden changes, "
203
+ "extra limbs, deformed, ugly, duplicate"
204
+ )
205
+
206
+ panels.append({
207
+ "panel_id": i + 1,
208
+ "phase_name": phase_name,
209
+ "panel_title": f"{phase_name} — {progress_pct:.0f}%",
210
+ "timestamp_label": f"Step {i + 1}/{num_panels}",
211
+ "image_prompt": {
212
+ "main_prompt": main_prompt,
213
+ "negative_prompt": negative,
214
+ "style_suffix": style
215
+ },
216
+ "video_prompt": {
217
+ "motion_description": f"Subtle ambient motion, {phase_desc}",
218
+ "camera_motion": "static, locked tripod"
219
+ }
220
+ })
221
+
222
+ return {
223
+ "project_metadata": {
224
+ "project_title": f"Timelapse: {description[:50]}",
225
+ "project_type": mode.upper(),
226
+ "total_panels": num_panels,
227
+ "user_original_command": description
228
+ },
229
+ "style_guide": {
230
+ "art_style": "photorealistic",
231
+ "camera": {"type": "fixed-tripod", "focal_length": "35mm"},
232
+ "lighting": {"primary_source": "natural daylight"}
233
+ },
234
+ "panels": panels,
235
+ "video_assembly_guide": {
236
+ "recommended_fps": 24,
237
+ "frame_hold_duration": 2,
238
+ "total_estimated_duration": f"{num_panels * 2} seconds"
239
+ }
240
+ }