rairo commited on
Commit
022e9d4
Β·
verified Β·
1 Parent(s): d072a75

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -60
app.py CHANGED
@@ -251,83 +251,111 @@ def generate_image_from_prompt(prompt, style):
251
  st.warning(f"Illustrative image generation failed: {e}. Using placeholder.")
252
  return Image.new("RGB", (WIDTH, HEIGHT), color=(230, 230, 230))
253
 
 
254
  # ─────────────────────────────────────────────────────────────────────────────
255
- # REPORT GENERATION (UNCHANGED)
256
  # ─────────────────────────────────────────────────────────────────────────────
257
-
258
- report_prompt = (
259
- "You are a senior business analyst. Write an executive-level Markdown report "
260
- "with insights & recommendations.\n"
261
- "When you need a visual, insert a tag like\n"
262
- '<generate_chart: "pie | sales by region">\n'
263
- "(chart_type **first**, pipe, then short description). "
264
- "Valid chart_type values: line, bar, scatter, pie, hist.\n"
265
- f"Data Context: {json.dumps(ctx_dict, indent=2)}"
266
- )
267
-
268
  def generate_report_assets(key, buf, name, ctx):
269
  df, err = load_dataframe_safely(buf, name)
270
- if err: st.error(err); return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash",
273
- google_api_key=API_KEY, temperature=0.1)
274
- ctx_dict = {"shape": df.shape, "columns": list(df.columns),
275
- "user_ctx": ctx or "General business analysis"}
276
  md = llm.invoke(report_prompt).content
277
 
278
- # Replace tags with static charts
279
  chart_descs = extract_chart_tags(md)[:MAX_CHARTS]
280
  charts = {}
281
  if chart_descs:
282
- ag = create_pandas_dataframe_agent(llm=llm, df=df, verbose=False, allow_dangerous_code=True)
 
 
283
  for d in chart_descs:
284
  with st.spinner(f"Generating chart: {d}"):
285
  with plt.ioff():
286
  try:
287
- ag.run(f"Create a {d} with Matplotlib and save."); fig = plt.gcf()
 
288
  if fig.axes:
289
  p = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.png"
290
  fig.savefig(p, dpi=300, bbox_inches="tight", facecolor="white")
291
  charts[d] = str(p)
292
  plt.close("all")
293
- except: plt.close("all")
 
294
 
295
  preview = repl_tags(
296
- md, charts,
297
- lambda p: f'<img src="data:image/png;base64,{base64.b64encode(Path(p).read_bytes()).decode()}" '
298
- f'style="max-width:100%;">'
 
299
  )
300
  pdf = build_pdf(md, charts)
301
- return {"type": "report", "preview": preview, "pdf": pdf, "report_md": md, "key": key}
 
 
 
 
 
 
 
 
302
 
303
  # ─────────────────────────────────────────────────────────────────────────────
304
- # VIDEO GENERATION (ANIMATED CHARTS)
305
  # ─────────────────────────────────────────────────────────────────────────────
306
- # ── Video script prompt ───────────────────────────────────
307
- story_prompt = (
308
- f"Create a script for a short business video with exactly {VIDEO_SCENES} scenes.\n"
309
- "For each scene:\n"
310
- "1. Provide 1–2 sentences of narration.\n"
311
- '2. If a visual is useful, add <generate_chart: "bar | monthly revenue"> '
312
- "(chart_type first).\n"
313
- "3. Separate scenes with [SCENE_BREAK].\n"
314
- f"Data Context: {json.dumps(ctx_dict, indent=2)}"
315
- )
316
-
317
  def generate_video_assets(key, buf, name, ctx, style, animate_charts=True):
318
- # FFmpeg presence
319
  try:
320
  subprocess.run(["ffmpeg", "-version"], check=True, capture_output=True)
321
  except Exception:
322
- st.error("πŸ”΄ FFmpeg not available β€” cannot render video."); return None
 
323
 
324
  df, err = load_dataframe_safely(buf, name)
325
- if err: st.error(err); return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
- llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash",
328
- google_api_key=API_KEY, temperature=0.2)
329
- ctx_dict = {"shape": df.shape, "columns": list(df.columns),
330
- "user_ctx": ctx or "General business analysis"}
331
  script = llm.invoke(story_prompt).content
332
  scenes = [s.strip() for s in script.split("[SCENE_BREAK]") if s.strip()]
333
 
@@ -335,45 +363,66 @@ def generate_video_assets(key, buf, name, ctx, style, animate_charts=True):
335
 
336
  for idx, scene in enumerate(scenes[:VIDEO_SCENES]):
337
  st.progress((idx + 1) / VIDEO_SCENES, text=f"Processing Scene {idx+1}/{VIDEO_SCENES}…")
 
338
  chart_tags = extract_chart_tags(scene)
339
- narrative = repl_tags(scene, {}, lambda _: "").strip()
340
 
341
- # Audio
342
  audio_bytes, _ = deepgram_tts(narrative)
343
  audio_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp3"
344
- (audio_path.write_bytes(audio_bytes) if audio_bytes else None)
 
345
  duration = get_audio_duration(str(audio_path)) if audio_bytes else 5.0
346
- audio_parts.append(str(audio_path)); temps.append(audio_path)
 
347
 
348
- # Video
349
  if chart_tags and animate_charts:
350
  clip_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
351
  animate_chart(chart_tags[0], df, duration, clip_path, FPS)
352
- video_parts.append(str(clip_path)); temps.append(clip_path)
 
353
  else:
354
- # illustrative image fade
355
  img = generate_image_from_prompt(narrative, style)
356
  png_tmp = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.png"
357
- img.save(png_tmp); temps.append(png_tmp)
 
358
  clip_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
359
  animate_image_fade(
360
  cv2.cvtColor(np.array(img.resize((WIDTH, HEIGHT))), cv2.COLOR_RGB2BGR),
361
- duration, clip_path, FPS
 
 
362
  )
363
- video_parts.append(str(clip_path)); temps.append(clip_path)
 
364
 
365
- # Concatenate media
366
  silent_vid = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
367
  concat_media(video_parts, silent_vid, "video")
368
- audio_mix = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp3"
369
  concat_media(audio_parts, audio_mix, "audio")
370
 
371
- final_vid = Path(tempfile.gettempdir()) / f"{key}.mp4"
372
  subprocess.run(
373
- ["ffmpeg", "-y", "-i", str(silent_vid), "-i", str(audio_mix),
374
- "-c:v", "copy", "-c:a", "aac", "-shortest", str(final_vid)],
375
- check=True, capture_output=True
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  )
 
377
  return {"type": "video", "video_path": str(final_vid), "key": key}
378
 
379
  # ─────────────────────────────────────────────────────────────────────────────
 
251
  st.warning(f"Illustrative image generation failed: {e}. Using placeholder.")
252
  return Image.new("RGB", (WIDTH, HEIGHT), color=(230, 230, 230))
253
 
254
+
255
  # ─────────────────────────────────────────────────────────────────────────────
256
+ # REPORT GENERATION (unchanged models – prompt now local)
257
  # ─────────────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
258
  def generate_report_assets(key, buf, name, ctx):
259
  df, err = load_dataframe_safely(buf, name)
260
+ if err:
261
+ st.error(err)
262
+ return None
263
+
264
+ llm = ChatGoogleGenerativeAI(
265
+ model="gemini-2.0-flash", google_api_key=API_KEY, temperature=0.1
266
+ )
267
+
268
+ # build context dict **after** df exists
269
+ ctx_dict = {
270
+ "shape": df.shape,
271
+ "columns": list(df.columns),
272
+ "user_ctx": ctx or "General business analysis",
273
+ }
274
+
275
+ report_prompt = (
276
+ "You are a senior business analyst. Write an executive-level Markdown report "
277
+ "with insights & recommendations.\n"
278
+ 'When you need a visual, insert a tag like <generate_chart: "pie | sales by region"> '
279
+ "(chart_type first, then a description). "
280
+ "Valid chart_type values: line, bar, scatter, pie, hist.\n"
281
+ f"Data Context: {json.dumps(ctx_dict, indent=2)}"
282
+ )
283
 
 
 
 
 
284
  md = llm.invoke(report_prompt).content
285
 
286
+ # ------------------------------------------------------------------ charts
287
  chart_descs = extract_chart_tags(md)[:MAX_CHARTS]
288
  charts = {}
289
  if chart_descs:
290
+ agent = create_pandas_dataframe_agent(
291
+ llm=llm, df=df, verbose=False, allow_dangerous_code=True
292
+ )
293
  for d in chart_descs:
294
  with st.spinner(f"Generating chart: {d}"):
295
  with plt.ioff():
296
  try:
297
+ agent.run(f"Create a {d} with Matplotlib and save.")
298
+ fig = plt.gcf()
299
  if fig.axes:
300
  p = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.png"
301
  fig.savefig(p, dpi=300, bbox_inches="tight", facecolor="white")
302
  charts[d] = str(p)
303
  plt.close("all")
304
+ except Exception:
305
+ plt.close("all")
306
 
307
  preview = repl_tags(
308
+ md,
309
+ charts,
310
+ lambda p: f'<img src="data:image/png;base64,'
311
+ f'{base64.b64encode(Path(p).read_bytes()).decode()}">'
312
  )
313
  pdf = build_pdf(md, charts)
314
+
315
+ return {
316
+ "type": "report",
317
+ "preview": preview,
318
+ "pdf": pdf,
319
+ "report_md": md,
320
+ "key": key,
321
+ }
322
+
323
 
324
  # ─────────────────────────────────────────────────────────────────────────────
325
+ # VIDEO GENERATION (animated charts – prompt now local)
326
  # ─────────────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
327
  def generate_video_assets(key, buf, name, ctx, style, animate_charts=True):
 
328
  try:
329
  subprocess.run(["ffmpeg", "-version"], check=True, capture_output=True)
330
  except Exception:
331
+ st.error("πŸ”΄ FFmpeg not available β€” cannot render video.")
332
+ return None
333
 
334
  df, err = load_dataframe_safely(buf, name)
335
+ if err:
336
+ st.error(err)
337
+ return None
338
+
339
+ llm = ChatGoogleGenerativeAI(
340
+ model="gemini-2.0-flash", google_api_key=API_KEY, temperature=0.2
341
+ )
342
+
343
+ ctx_dict = {
344
+ "shape": df.shape,
345
+ "columns": list(df.columns),
346
+ "user_ctx": ctx or "General business analysis",
347
+ }
348
+
349
+ story_prompt = (
350
+ f"Create a script for a short business video with exactly {VIDEO_SCENES} scenes.\n"
351
+ "For each scene:\n"
352
+ "1. Provide 1–2 sentences of narration.\n"
353
+ '2. If a visual is helpful, add <generate_chart: "bar | monthly revenue"> '
354
+ "(chart_type first).\n"
355
+ "3. Separate scenes with [SCENE_BREAK].\n"
356
+ f"Data Context: {json.dumps(ctx_dict, indent=2)}"
357
+ )
358
 
 
 
 
 
359
  script = llm.invoke(story_prompt).content
360
  scenes = [s.strip() for s in script.split("[SCENE_BREAK]") if s.strip()]
361
 
 
363
 
364
  for idx, scene in enumerate(scenes[:VIDEO_SCENES]):
365
  st.progress((idx + 1) / VIDEO_SCENES, text=f"Processing Scene {idx+1}/{VIDEO_SCENES}…")
366
+
367
  chart_tags = extract_chart_tags(scene)
368
+ narrative = repl_tags(scene, {}, lambda _: "").strip()
369
 
370
+ # ---------------- audio -------------------------------------------
371
  audio_bytes, _ = deepgram_tts(narrative)
372
  audio_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp3"
373
+ if audio_bytes:
374
+ audio_path.write_bytes(audio_bytes)
375
  duration = get_audio_duration(str(audio_path)) if audio_bytes else 5.0
376
+ audio_parts.append(str(audio_path))
377
+ temps.append(audio_path)
378
 
379
+ # ---------------- visual ------------------------------------------
380
  if chart_tags and animate_charts:
381
  clip_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
382
  animate_chart(chart_tags[0], df, duration, clip_path, FPS)
383
+ video_parts.append(str(clip_path))
384
+ temps.append(clip_path)
385
  else:
 
386
  img = generate_image_from_prompt(narrative, style)
387
  png_tmp = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.png"
388
+ img.save(png_tmp)
389
+ temps.append(png_tmp)
390
  clip_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
391
  animate_image_fade(
392
  cv2.cvtColor(np.array(img.resize((WIDTH, HEIGHT))), cv2.COLOR_RGB2BGR),
393
+ duration,
394
+ clip_path,
395
+ FPS,
396
  )
397
+ video_parts.append(str(clip_path))
398
+ temps.append(clip_path)
399
 
400
+ # -------------- concatenate ----------------------------------------------
401
  silent_vid = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
402
  concat_media(video_parts, silent_vid, "video")
403
+ audio_mix = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp3"
404
  concat_media(audio_parts, audio_mix, "audio")
405
 
406
+ final_vid = Path(tempfile.gettempdir()) / f"{key}.mp4"
407
  subprocess.run(
408
+ [
409
+ "ffmpeg",
410
+ "-y",
411
+ "-i",
412
+ str(silent_vid),
413
+ "-i",
414
+ str(audio_mix),
415
+ "-c:v",
416
+ "copy",
417
+ "-c:a",
418
+ "aac",
419
+ "-shortest",
420
+ str(final_vid),
421
+ ],
422
+ check=True,
423
+ capture_output=True,
424
  )
425
+
426
  return {"type": "video", "video_path": str(final_vid), "key": key}
427
 
428
  # ─────────────────────────────────────────────────────────────────────────────