Gaurav vashistha commited on
Commit
ef27e6d
·
1 Parent(s): a8c659a

feat: integrate Veo and GCS download

Browse files
Files changed (6) hide show
  1. agent.py +151 -83
  2. check_genai.py +5 -0
  3. check_genai_help.py +7 -0
  4. check_genai_models.py +7 -0
  5. requirements.txt +4 -0
  6. server.py +9 -14
agent.py CHANGED
@@ -4,13 +4,27 @@ import shutil
4
  import requests
5
  import tempfile
6
  import logging
 
7
  from typing import TypedDict, Optional
8
  from langgraph.graph import StateGraph, END
 
9
  from google import genai
 
 
 
10
  from groq import Groq
11
  from gradio_client import Client, handle_file
12
  from dotenv import load_dotenv
13
 
 
 
 
 
 
 
 
 
 
14
  # Load environment variables
15
  load_dotenv()
16
 
@@ -32,6 +46,9 @@ class ContinuityState(TypedDict):
32
  # --- HELPER FUNCTIONS ---
33
  def download_to_temp(url):
34
  logger.info(f"Downloading: {url}")
 
 
 
35
  resp = requests.get(url, stream=True)
36
  resp.raise_for_status()
37
  suffix = os.path.splitext(url.split("/")[-1])[1] or ".mp4"
@@ -39,46 +56,63 @@ def download_to_temp(url):
39
  shutil.copyfileobj(resp.raw, f)
40
  return f.name
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  # --- NODE 1: ANALYST ---
43
  def analyze_videos(state: ContinuityState) -> dict:
44
  logger.info("--- 🧐 Analyst Node (Director) ---")
45
-
46
  video_a_url = state['video_a_url']
47
  video_c_url = state['video_c_url']
48
-
49
  # 1. Prepare Files
50
  try:
51
  path_a = state.get('video_a_local_path')
52
  if not path_a:
53
- path_a = download_to_temp(video_a_url)
54
-
55
  path_c = state.get('video_c_local_path')
56
  if not path_c:
57
- path_c = download_to_temp(video_c_url)
58
  except Exception as e:
59
  logger.error(f"Download failed: {e}")
60
  return {"scene_analysis": "Error downloading", "veo_prompt": "Smooth cinematic transition"}
61
 
62
  # 2. Try Gemini 2.0 (With Retry)
 
63
  client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
64
  transition_prompt = None
65
-
66
  retries = 3
67
  for attempt in range(retries):
68
  try:
69
  logger.info(f"Uploading videos to Gemini... (Attempt {attempt+1})")
70
  file_a = client.files.upload(file=path_a)
71
  file_c = client.files.upload(file=path_c)
72
-
73
  prompt_text = """
74
  You are a film director.
75
  Analyze the motion, lighting, and subject of the first video (Video A) and the second video (Video C).
76
  Write a detailed visual prompt for a 2-second video (Video B) that smoothly transitions from the end of A to the start of C.
77
  Target Output: A single concise descriptive paragraph for the video generation model.
78
  """
79
-
80
  logger.info("Generating transition prompt...")
81
- # Using 2.0 Flash as per your logs (or 1.5-flash if preferred)
82
  response = client.models.generate_content(
83
  model="gemini-2.0-flash-exp",
84
  contents=[prompt_text, file_a, file_c]
@@ -86,7 +120,6 @@ def analyze_videos(state: ContinuityState) -> dict:
86
  transition_prompt = response.text
87
  logger.info(f"Generated Prompt: {transition_prompt}")
88
  break # Success
89
-
90
  except Exception as e:
91
  if "429" in str(e) or "RESOURCE_EXHAUSTED" in str(e):
92
  wait = 30 * (attempt + 1)
@@ -101,14 +134,10 @@ def analyze_videos(state: ContinuityState) -> dict:
101
  logger.info("Switching to Llama 3.2 (Groq) Fallback...")
102
  try:
103
  groq_client = Groq(api_key=os.environ["GROQ_API_KEY"])
104
- # We can't easily send videos, so we generate a prompt based on general best practices
105
- fallback_prompt = "Create a smooth, cinematic visual transition that bridges two scenes with matching lighting and motion blur."
106
-
107
  completion = groq_client.chat.completions.create(
108
  model="llama-3.2-11b-vision-preview",
109
- messages=[
110
- {"role": "user", "content": f"Refine this into a video generation prompt: {fallback_prompt}"}
111
- ]
112
  )
113
  transition_prompt = completion.choices[0].message.content
114
  except Exception as e:
@@ -125,94 +154,107 @@ def analyze_videos(state: ContinuityState) -> dict:
125
  # --- NODE 2: GENERATOR ---
126
  def generate_video(state: ContinuityState) -> dict:
127
  logger.info("--- 🎥 Generator Node ---")
128
-
129
  prompt = state.get('veo_prompt', "")
130
  path_a = state.get('video_a_local_path')
131
  path_c = state.get('video_c_local_path')
132
-
133
  if not path_a or not path_c:
134
  return {"generated_video_url": "Error: Missing local video paths"}
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  try:
137
- # Extract Frames (simplified for brevity, ensuring libraries are imported)
138
  import cv2
139
  from PIL import Image
140
 
141
- def get_frame(video_path, location="last"):
142
  cap = cv2.VideoCapture(video_path)
143
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
144
- if location == "last": cap.set(cv2.CAP_PROP_POS_FRAMES, total_frames - 1)
145
- else: cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
146
  ret, frame = cap.read()
147
  cap.release()
148
- if ret: return Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
149
- raise ValueError(f"Could not extract frame from {video_path}")
 
150
 
151
- logger.info("Extracting frames...")
152
- img_start = get_frame(path_a, "last")
153
- img_end = get_frame(path_c, "first")
154
 
155
  with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f_start:
156
  img_start.save(f_start, format="PNG")
157
  start_path = f_start.name
158
 
159
- with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f_end:
160
- img_end.save(f_end, format="PNG")
161
- end_path = f_end.name
162
-
163
- # --- ATTEMPT 1: WAN 2.2 ---
164
- try:
165
- logger.info("Initializing Wan Client...")
166
- client = Client("multimodalart/wan-2-2-first-last-frame")
167
-
168
- logger.info(f"Generating with Wan 2.2... Prompt: {prompt[:30]}...")
169
- result = client.predict(
170
- start_image_pil=handle_file(start_path),
171
- end_image_pil=handle_file(end_path),
172
- prompt=prompt,
173
- negative_prompt="blurry, distorted, low quality, static",
174
- duration_seconds=2.1,
175
- steps=20,
176
- guidance_scale=5.0,
177
- guidance_scale_2=5.0,
178
- seed=42,
179
- randomize_seed=True,
180
- api_name="/generate_video"
181
- )
182
- # Handle Wan output format
183
- video_out = result[0]
184
- if isinstance(video_out, dict) and 'video' in video_out:
185
- return {"generated_video_url": video_out['video']}
186
- elif isinstance(video_out, str) and os.path.exists(video_out):
187
- return {"generated_video_url": video_out}
188
-
189
- except Exception as e:
190
- logger.warning(f"⚠️ Wan 2.2 Failed: {e}")
191
-
192
- # --- ATTEMPT 2: SVD FALLBACK ---
193
- logger.info("🔄 Switching to SVD Fallback...")
194
- try:
195
- # FIXED REPO ID
196
- client = Client("multimodalart/stable-video-diffusion")
197
-
198
- # SVD uses one image, we'll use the start frame
199
- result = client.predict(
200
- handle_file(start_path),
201
- 0.0, 0.0, 1, 25, # resized_width, resized_height, motion_bucket_id, fps
202
- api_name="/predict"
203
- )
204
- logger.info(f"✅ SVD Generated: {result}")
205
- return {"generated_video_url": result} # SVD usually returns path string
206
-
207
- except Exception as e:
208
- logger.error(f"❌ All Generators Failed. Error: {e}")
209
- return {"generated_video_url": f"Error: {str(e)}"}
210
-
211
  except Exception as e:
212
- logger.error(f"Error in Generator Setup: {e}")
213
  return {"generated_video_url": f"Error: {str(e)}"}
214
 
215
-
216
  # Graph Construction
217
  workflow = StateGraph(ContinuityState)
218
  workflow.add_node("analyst", analyze_videos)
@@ -220,4 +262,30 @@ workflow.add_node("generator", generate_video)
220
  workflow.set_entry_point("analyst")
221
  workflow.add_edge("analyst", "generator")
222
  workflow.add_edge("generator", END)
223
- app = workflow.compile()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import requests
5
  import tempfile
6
  import logging
7
+ import json
8
  from typing import TypedDict, Optional
9
  from langgraph.graph import StateGraph, END
10
+ # Unified SDK for both Analyst (Gemini) and Generator (Veo)
11
  from google import genai
12
+ from google.genai import types
13
+ from google.cloud import storage # Required for downloading Veo output
14
+
15
  from groq import Groq
16
  from gradio_client import Client, handle_file
17
  from dotenv import load_dotenv
18
 
19
+ # --- AUTH SETUP FOR HUGGING FACE ---
20
+ if "GCP_CREDENTIALS_JSON" in os.environ:
21
+ # logger is not defined yet, using print
22
+ print("🔐 Found GCP Credentials Secret. Setting up auth...")
23
+ creds_path = "gcp_credentials.json"
24
+ with open(creds_path, "w") as f:
25
+ f.write(os.environ["GCP_CREDENTIALS_JSON"])
26
+ os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = creds_path
27
+
28
  # Load environment variables
29
  load_dotenv()
30
 
 
46
  # --- HELPER FUNCTIONS ---
47
  def download_to_temp(url):
48
  logger.info(f"Downloading: {url}")
49
+ if os.path.exists(url):
50
+ return url
51
+
52
  resp = requests.get(url, stream=True)
53
  resp.raise_for_status()
54
  suffix = os.path.splitext(url.split("/")[-1])[1] or ".mp4"
 
56
  shutil.copyfileobj(resp.raw, f)
57
  return f.name
58
 
59
+ def download_blob(gcs_uri, destination_file_name):
60
+ """Downloads a blob from the bucket."""
61
+ # gcs_uri format: gs://bucket-name/path/to/object
62
+ if not gcs_uri.startswith("gs://"):
63
+ raise ValueError(f"Invalid GCS URI: {gcs_uri}")
64
+
65
+ parts = gcs_uri[5:].split("/", 1)
66
+ bucket_name = parts[0]
67
+ source_blob_name = parts[1]
68
+
69
+ storage_client = storage.Client()
70
+ bucket = storage_client.bucket(bucket_name)
71
+ blob = bucket.blob(source_blob_name)
72
+ blob.download_to_filename(destination_file_name)
73
+
74
+ logger.info(f"Downloaded storage object {gcs_uri} to local file {destination_file_name}.")
75
+
76
  # --- NODE 1: ANALYST ---
77
  def analyze_videos(state: ContinuityState) -> dict:
78
  logger.info("--- 🧐 Analyst Node (Director) ---")
79
+
80
  video_a_url = state['video_a_url']
81
  video_c_url = state['video_c_url']
82
+
83
  # 1. Prepare Files
84
  try:
85
  path_a = state.get('video_a_local_path')
86
  if not path_a:
87
+ path_a = download_to_temp(video_a_url)
88
+
89
  path_c = state.get('video_c_local_path')
90
  if not path_c:
91
+ path_c = download_to_temp(video_c_url)
92
  except Exception as e:
93
  logger.error(f"Download failed: {e}")
94
  return {"scene_analysis": "Error downloading", "veo_prompt": "Smooth cinematic transition"}
95
 
96
  # 2. Try Gemini 2.0 (With Retry)
97
+ # Standard Client for Gemini (API Key based)
98
  client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
99
  transition_prompt = None
 
100
  retries = 3
101
  for attempt in range(retries):
102
  try:
103
  logger.info(f"Uploading videos to Gemini... (Attempt {attempt+1})")
104
  file_a = client.files.upload(file=path_a)
105
  file_c = client.files.upload(file=path_c)
106
+
107
  prompt_text = """
108
  You are a film director.
109
  Analyze the motion, lighting, and subject of the first video (Video A) and the second video (Video C).
110
  Write a detailed visual prompt for a 2-second video (Video B) that smoothly transitions from the end of A to the start of C.
111
  Target Output: A single concise descriptive paragraph for the video generation model.
112
  """
113
+
114
  logger.info("Generating transition prompt...")
115
+ # Using 2.0 Flash Exp or falling back to 1.5 Flash if needed
116
  response = client.models.generate_content(
117
  model="gemini-2.0-flash-exp",
118
  contents=[prompt_text, file_a, file_c]
 
120
  transition_prompt = response.text
121
  logger.info(f"Generated Prompt: {transition_prompt}")
122
  break # Success
 
123
  except Exception as e:
124
  if "429" in str(e) or "RESOURCE_EXHAUSTED" in str(e):
125
  wait = 30 * (attempt + 1)
 
134
  logger.info("Switching to Llama 3.2 (Groq) Fallback...")
135
  try:
136
  groq_client = Groq(api_key=os.environ["GROQ_API_KEY"])
137
+ fallback_prompt = "Create a smooth, cinematic visual transition that bridges two scenes."
 
 
138
  completion = groq_client.chat.completions.create(
139
  model="llama-3.2-11b-vision-preview",
140
+ messages=[{"role": "user", "content": f"Refine this into a video prompt: {fallback_prompt}"}]
 
 
141
  )
142
  transition_prompt = completion.choices[0].message.content
143
  except Exception as e:
 
154
  # --- NODE 2: GENERATOR ---
155
  def generate_video(state: ContinuityState) -> dict:
156
  logger.info("--- 🎥 Generator Node ---")
157
+
158
  prompt = state.get('veo_prompt', "")
159
  path_a = state.get('video_a_local_path')
160
  path_c = state.get('video_c_local_path')
 
161
  if not path_a or not path_c:
162
  return {"generated_video_url": "Error: Missing local video paths"}
163
 
164
+ # --- ATTEMPT 1: GOOGLE VEO (VIA UNIFIED GENAI SDK) ---
165
+ try:
166
+ logger.info("⚡ Initializing Google Veo (Unified SDK)...")
167
+ project_id = os.getenv("GCP_PROJECT_ID")
168
+ location = os.getenv("GCP_LOCATION", "us-central1")
169
+
170
+ if project_id:
171
+ # Initialize Vertex AI Client via genai
172
+ client = genai.Client(
173
+ vertexai=True,
174
+ project=project_id,
175
+ location=location
176
+ )
177
+
178
+ logger.info(f"Generating with Veo... Prompt: {prompt[:30]}...")
179
+
180
+ # Submit Generation Operation
181
+ operation = client.models.generate_videos(
182
+ model='veo-2.0-generate-001',
183
+ prompt=prompt,
184
+ config=types.GenerateVideosConfig(
185
+ number_of_videos=1,
186
+ )
187
+ )
188
+
189
+ # Polling Loop
190
+ logger.info(f"Waiting for Veo operation {operation.name}...")
191
+ while not operation.done:
192
+ time.sleep(10)
193
+ operation = client.operations.get(operation.name)
194
+ logger.info("...still generating...")
195
+
196
+ # Handle Result
197
+ if operation.result and operation.result.generated_videos:
198
+ video_result = operation.result.generated_videos[0]
199
+
200
+ # Check if we have a GCS URI (Typical for Veo)
201
+ if hasattr(video_result.video, 'uri') and video_result.video.uri:
202
+ gcs_uri = video_result.video.uri
203
+ logger.info(f"Veo output saved to GCS: {gcs_uri}")
204
+
205
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as f:
206
+ local_path = f.name
207
+
208
+ download_blob(gcs_uri, local_path)
209
+ logger.info(f"✅ Veo Video Downloaded: {local_path}")
210
+ return {"generated_video_url": local_path}
211
+ else:
212
+ logger.warning("Veo operation completed but no URI found.")
213
+ else:
214
+ logger.warning("Veo operation completed with no result.")
215
+
216
+ else:
217
+ logger.warning("⚠️ GCP_PROJECT_ID not set. Skipping Veo.")
218
+
219
+ except Exception as e:
220
+ logger.warning(f"⚠️ Veo Failed: {e}")
221
+ # Fallback to SVD below
222
+
223
+ # --- ATTEMPT 2: SVD FALLBACK (Free) ---
224
+ logger.info("🔄 Switching to SVD Fallback...")
225
  try:
 
226
  import cv2
227
  from PIL import Image
228
 
229
+ def get_frame(video_path):
230
  cap = cv2.VideoCapture(video_path)
 
 
 
231
  ret, frame = cap.read()
232
  cap.release()
233
+ if ret:
234
+ return Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
235
+ return None
236
 
237
+ img_start = get_frame(path_a)
238
+ if img_start is None:
239
+ raise ValueError("Could not read start frame for SVD")
240
 
241
  with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f_start:
242
  img_start.save(f_start, format="PNG")
243
  start_path = f_start.name
244
 
245
+ client = Client("multimodalart/stable-video-diffusion")
246
+ result = client.predict(
247
+ handle_file(start_path),
248
+ 0.0, 0.0, 1, 25,
249
+ api_name="/predict"
250
+ )
251
+ logger.info(f" SVD Generated: {result}")
252
+ return {"generated_video_url": result}
253
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  except Exception as e:
255
+ logger.error(f" All Generators Failed. Error: {e}")
256
  return {"generated_video_url": f"Error: {str(e)}"}
257
 
 
258
  # Graph Construction
259
  workflow = StateGraph(ContinuityState)
260
  workflow.add_node("analyst", analyze_videos)
 
262
  workflow.set_entry_point("analyst")
263
  workflow.add_edge("analyst", "generator")
264
  workflow.add_edge("generator", END)
265
+ app = workflow.compile()
266
+
267
+ # --- SERVER COMPATIBILITY WRAPPERS ---
268
+ def analyze_only(state_or_path_a, path_c=None):
269
+ # Handle direct server call format (path_a, path_c)
270
+ if isinstance(state_or_path_a, str) and path_c:
271
+ state = {
272
+ "video_a_url": "local",
273
+ "video_c_url": "local",
274
+ "video_a_local_path": state_or_path_a,
275
+ "video_c_local_path": path_c
276
+ }
277
+ else:
278
+ state = state_or_path_a if isinstance(state_or_path_a, dict) else state_or_path_a.dict()
279
+
280
+ result = analyze_videos(state)
281
+ return {"prompt": result.get("scene_analysis"), "status": "success"}
282
+
283
+ def generate_only(prompt, path_a, path_c):
284
+ state = {
285
+ "video_a_url": "local",
286
+ "video_c_url": "local",
287
+ "video_a_local_path": path_a,
288
+ "video_c_local_path": path_c,
289
+ "veo_prompt": prompt
290
+ }
291
+ return generate_video(state)
check_genai.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from google import genai
2
+ try:
3
+ print("Client methods:", dir(genai.Client))
4
+ except Exception as e:
5
+ print(e)
check_genai_help.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from google import genai
2
+ import os
3
+ try:
4
+ client = genai.Client(api_key="TEST")
5
+ print(help(client.models.generate_videos))
6
+ except Exception as e:
7
+ print(e)
check_genai_models.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from google import genai
2
+ import os
3
+ try:
4
+ client = genai.Client(api_key="TEST")
5
+ print("models methods:", dir(client.models))
6
+ except Exception as e:
7
+ print(e)
requirements.txt CHANGED
@@ -14,3 +14,7 @@ groq
14
  numpy
15
  gradio
16
  google-generativeai
 
 
 
 
 
14
  numpy
15
  gradio
16
  google-generativeai
17
+
18
+ google-cloud-aiplatform
19
+
20
+ google-cloud-storage
server.py CHANGED
@@ -6,7 +6,8 @@ import uvicorn
6
  import os
7
  import shutil
8
  import uuid
9
- from continuity_agent.agent import analyze_only, generate_only
 
10
 
11
  app = FastAPI(title="Continuity", description="AI Video Bridging Service")
12
 
@@ -24,6 +25,7 @@ app.mount("/outputs", StaticFiles(directory=OUTPUT_DIR), name="outputs")
24
 
25
  @app.get("/")
26
  async def read_root():
 
27
  return FileResponse("stitch_continuity_dashboard/code.html")
28
 
29
  @app.post("/analyze")
@@ -38,18 +40,18 @@ async def analyze_endpoint(
38
 
39
  path_a = os.path.join(OUTPUT_DIR, f"{request_id}_a{ext_a}")
40
  path_c = os.path.join(OUTPUT_DIR, f"{request_id}_c{ext_c}")
41
-
42
  with open(path_a, "wb") as buffer:
43
  shutil.copyfileobj(video_a.file, buffer)
44
  with open(path_c, "wb") as buffer:
45
  shutil.copyfileobj(video_c.file, buffer)
46
-
47
- # Call Agent
48
- result = analyze_only(os.path.abspath(path_a), os.path.abspath(path_c))
49
 
 
 
 
50
  if result.get("status") == "error":
51
  raise HTTPException(status_code=500, detail=result.get("detail"))
52
-
53
  return {
54
  "prompt": result["prompt"],
55
  "video_a_path": os.path.abspath(path_a),
@@ -68,7 +70,7 @@ async def generate_endpoint(
68
  try:
69
  if not os.path.exists(video_a_path) or not os.path.exists(video_c_path):
70
  raise HTTPException(status_code=400, detail="Video files not found on server.")
71
-
72
  # Call Agent
73
  result = generate_only(prompt, video_a_path, video_c_path)
74
  gen_path = result.get("generated_video_url")
@@ -76,16 +78,12 @@ async def generate_endpoint(
76
  if not gen_path or "Error" in gen_path:
77
  raise HTTPException(status_code=500, detail=f"Generation failed: {gen_path}")
78
 
79
- # Move final file to output dir if it's not already there (SVD might return temp path)
80
  final_filename = f"{uuid.uuid4()}_bridge.mp4"
81
  final_output_path = os.path.join(OUTPUT_DIR, final_filename)
82
 
83
- # If gen_path is a URL (some providers), we might need to handle differently
84
- # But our agent functions return local paths (SVD) or temp paths (Wan)
85
  if os.path.exists(gen_path):
86
  shutil.move(gen_path, final_output_path)
87
  else:
88
- # Assume it's an error message or invalid
89
  raise HTTPException(status_code=500, detail="Generated file missing.")
90
 
91
  return {"video_url": f"/outputs/{final_filename}"}
@@ -93,9 +91,6 @@ async def generate_endpoint(
93
  except Exception as e:
94
  print(f"Server Error (Generate): {e}")
95
  raise HTTPException(status_code=500, detail=str(e))
96
- except Exception as e:
97
- print(f"Server Error: {e}")
98
- raise HTTPException(status_code=500, detail=str(e))
99
 
100
  if __name__ == "__main__":
101
  uvicorn.run("server:app", host="0.0.0.0", port=7860, reload=False)
 
6
  import os
7
  import shutil
8
  import uuid
9
+ # FIXED IMPORT: Importing from root agent.py instead of continuity_agent
10
+ from agent import analyze_only, generate_only
11
 
12
  app = FastAPI(title="Continuity", description="AI Video Bridging Service")
13
 
 
25
 
26
  @app.get("/")
27
  async def read_root():
28
+ # Serve the dashboard HTML
29
  return FileResponse("stitch_continuity_dashboard/code.html")
30
 
31
  @app.post("/analyze")
 
40
 
41
  path_a = os.path.join(OUTPUT_DIR, f"{request_id}_a{ext_a}")
42
  path_c = os.path.join(OUTPUT_DIR, f"{request_id}_c{ext_c}")
43
+
44
  with open(path_a, "wb") as buffer:
45
  shutil.copyfileobj(video_a.file, buffer)
46
  with open(path_c, "wb") as buffer:
47
  shutil.copyfileobj(video_c.file, buffer)
 
 
 
48
 
49
+ # Call Agent with local paths
50
+ result = analyze_only(os.path.abspath(path_a), os.path.abspath(path_c))
51
+
52
  if result.get("status") == "error":
53
  raise HTTPException(status_code=500, detail=result.get("detail"))
54
+
55
  return {
56
  "prompt": result["prompt"],
57
  "video_a_path": os.path.abspath(path_a),
 
70
  try:
71
  if not os.path.exists(video_a_path) or not os.path.exists(video_c_path):
72
  raise HTTPException(status_code=400, detail="Video files not found on server.")
73
+
74
  # Call Agent
75
  result = generate_only(prompt, video_a_path, video_c_path)
76
  gen_path = result.get("generated_video_url")
 
78
  if not gen_path or "Error" in gen_path:
79
  raise HTTPException(status_code=500, detail=f"Generation failed: {gen_path}")
80
 
 
81
  final_filename = f"{uuid.uuid4()}_bridge.mp4"
82
  final_output_path = os.path.join(OUTPUT_DIR, final_filename)
83
 
 
 
84
  if os.path.exists(gen_path):
85
  shutil.move(gen_path, final_output_path)
86
  else:
 
87
  raise HTTPException(status_code=500, detail="Generated file missing.")
88
 
89
  return {"video_url": f"/outputs/{final_filename}"}
 
91
  except Exception as e:
92
  print(f"Server Error (Generate): {e}")
93
  raise HTTPException(status_code=500, detail=str(e))
 
 
 
94
 
95
  if __name__ == "__main__":
96
  uvicorn.run("server:app", host="0.0.0.0", port=7860, reload=False)