burtenshaw commited on
Commit
de93716
·
1 Parent(s): 44176d4

add progress updates

Browse files
Files changed (1) hide show
  1. app/app.py +122 -47
app/app.py CHANGED
@@ -278,14 +278,16 @@ def load_css(css_filename="style.scss"):
278
  # --- Gradio Workflow Functions ---
279
 
280
 
281
- def step1_fetch_and_generate_presentation(url, progress=gr.Progress(track_tqdm=True)):
 
 
282
  """Fetches content, generates presentation markdown, prepares editor, and copies template. Uses caching based on URL."""
283
  if not url:
284
  raise gr.Error("Please enter a URL.")
285
  logger.info(f"Step 1: Fetching & Generating for {url}")
286
 
287
  status_update = f"Starting Step 1: Fetching content from {url}..."
288
- yield status_update
289
 
290
  # --- Cache Check ---
291
  try:
@@ -338,11 +340,11 @@ def step1_fetch_and_generate_presentation(url, progress=gr.Progress(track_tqdm=T
338
  status_update = (
339
  "Loaded presentation from cache. Preparing editor..."
340
  )
341
- yield status_update
342
  logger.info(f"Using cached data for {len(slides_data)} slides.")
343
  # Return updates for the UI state and controls
344
  yield (
345
- status_update, # Keep status
346
  temp_dir,
347
  md_path,
348
  slides_data,
@@ -368,14 +370,14 @@ def step1_fetch_and_generate_presentation(url, progress=gr.Progress(track_tqdm=T
368
  raise gr.Error("LLM Client not initialized. Check API Key.")
369
 
370
  status_update = "Fetching webpage content..."
371
- yield status_update
372
  web_content = fetch_webpage_content(url)
373
  if not web_content:
374
  raise gr.Error("Failed to fetch or parse content from the URL.")
375
 
376
  progress(0.3, desc="Generating presentation with LLM...")
377
  status_update = "Generating presentation with LLM..."
378
- yield status_update
379
  try:
380
  presentation_md = generate_presentation_with_llm(
381
  hf_client, LLM_MODEL, PRESENTATION_PROMPT, web_content, url
@@ -400,7 +402,7 @@ def step1_fetch_and_generate_presentation(url, progress=gr.Progress(track_tqdm=T
400
 
401
  progress(0.7, desc="Parsing presentation slides...")
402
  status_update = "Parsing presentation slides..."
403
- yield status_update
404
  slides_data = parse_presentation_markdown(presentation_md)
405
  if not slides_data:
406
  logger.error("Parsing markdown resulted in zero slides.")
@@ -444,12 +446,12 @@ def step1_fetch_and_generate_presentation(url, progress=gr.Progress(track_tqdm=T
444
 
445
  progress(0.9, desc="Preparing editor...")
446
  status_update = "Generated presentation. Preparing editor..."
447
- yield status_update
448
  logger.info(f"Prepared data for {len(slides_data)} slides.")
449
 
450
  # Return updates for the UI state and controls
451
  yield (
452
- status_update, # Keep status
453
  temp_dir,
454
  md_path,
455
  slides_data,
@@ -461,10 +463,10 @@ def step1_fetch_and_generate_presentation(url, progress=gr.Progress(track_tqdm=T
461
  except Exception as e:
462
  logger.error(f"Error in step 1 (fetch/generate): {e}", exc_info=True)
463
  status_update = f"Error during presentation setup: {e}"
464
- yield status_update # Yield error status
465
  # Need to yield the correct number of outputs even on error to avoid issues
466
  yield (
467
- status_update,
468
  None,
469
  None,
470
  [],
@@ -480,6 +482,7 @@ def step2_build_slides(
480
  state_temp_dir,
481
  state_md_path,
482
  state_slides_data,
 
483
  *editors,
484
  progress=gr.Progress(track_tqdm=True),
485
  ):
@@ -487,6 +490,8 @@ def step2_build_slides(
487
  if not all([state_temp_dir, state_md_path, state_slides_data]):
488
  raise gr.Error("Session state missing.")
489
  logger.info("Step 2: Building Slides (PDF + Images)")
 
 
490
 
491
  num_slides = len(state_slides_data)
492
  MAX_SLIDES = 20
@@ -499,6 +504,8 @@ def step2_build_slides(
499
  raise gr.Error("Editor input mismatch.")
500
 
501
  progress(0.1, desc="Saving edited markdown...")
 
 
502
  updated_slides = []
503
  for i in range(num_slides):
504
  updated_slides.append(
@@ -510,20 +517,54 @@ def step2_build_slides(
510
  f.write(updated_md)
511
  logger.info(f"Saved edited markdown: {state_md_path}")
512
  except IOError as e:
 
 
 
 
 
 
 
 
 
 
513
  raise gr.Error(f"Failed to save markdown: {e}")
514
 
515
  progress(0.3, desc="Generating PDF...")
 
 
516
  pdf_output_path = os.path.join(state_temp_dir, "presentation.pdf")
517
  try:
518
  generated_pdf_path = generate_pdf_from_markdown(state_md_path, pdf_output_path)
519
  if not generated_pdf_path:
520
  raise gr.Error("PDF generation failed (check logs).")
521
  except gr.Error as e:
522
- raise e # Re-raise
 
 
 
 
 
 
 
 
 
 
523
  except Exception as e:
524
- raise gr.Error(f"Unexpected PDF Error: {e}")
 
 
 
 
 
 
 
 
 
 
525
 
526
  progress(0.7, desc="Converting PDF to images...")
 
 
527
  pdf_images = []
528
  try:
529
  pdf_images = convert_pdf_to_images(
@@ -535,9 +576,21 @@ def step2_build_slides(
535
  if len(pdf_images) != num_slides:
536
  warning_msg = f"Warning: PDF page count ({len(pdf_images)}) != slide count ({num_slides}). Images might mismatch."
537
  gr.Warning(warning_msg)
 
538
  # Pad or truncate? For now, just return what we have, UI update logic handles MAX_SLIDES
539
  except Exception as e:
540
  logger.error(f"Error converting PDF to images: {e}", exc_info=True)
 
 
 
 
 
 
 
 
 
 
 
541
  raise gr.Error(f"Failed to convert PDF to images: {e}")
542
 
543
  info_msg = f"Built {len(pdf_images)} slide images. Ready for Step 3."
@@ -545,7 +598,7 @@ def step2_build_slides(
545
  progress(1.0, desc="Slide build complete.")
546
  status_update = f"Step 2 Complete: {info_msg}"
547
  yield (
548
- status_update,
549
  generated_pdf_path,
550
  pdf_images, # Return the list of image paths
551
  gr.update(visible=True), # btn_generate_audio
@@ -556,18 +609,19 @@ def step2_build_slides(
556
 
557
  def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
558
  """Generates audio files for the speaker notes using edited content."""
559
- # Args structure:
560
  # args[0]: state_temp_dir
561
  # args[1]: state_md_path
562
- # args[2]: original_slides_data (list of dicts, used to get count)
563
- # args[3] : status_textbox (This index needs adjustment if adding more inputs)
564
- # args[4 : 4 + MAX_SLIDES]: values from all_code_editors
565
- # args[4 + MAX_SLIDES :]: values from all_notes_textboxes
566
 
567
  state_temp_dir = args[0]
568
  state_md_path = args[1]
569
  original_slides_data = args[2]
570
- # editors = args[3:] # noqa F841 - Keeping for potential future use or clarity of arg structure # Old slicing
 
 
571
  num_slides = len(original_slides_data)
572
  if num_slides == 0:
573
  logger.error("Step 3 (Audio) called with zero slides data.")
@@ -575,9 +629,9 @@ def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
575
 
576
  MAX_SLIDES = 20 # Ensure this matches UI definition
577
  # --- Adjust indices based on adding status_textbox input ---
578
- # Assuming status_textbox is now at index 3
579
- code_editors_start_index = 4 # Was 3
580
- notes_textboxes_start_index = 4 + MAX_SLIDES # Was 3 + MAX_SLIDES
581
 
582
  # Slice the *actual* edited values based on num_slides
583
  edited_contents = args[
@@ -600,6 +654,9 @@ def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
600
  )
601
 
602
  logger.info(f"Processing {num_slides} slides for audio generation.")
 
 
 
603
  audio_dir = os.path.join(state_temp_dir, "audio")
604
  os.makedirs(audio_dir, exist_ok=True)
605
 
@@ -607,6 +664,8 @@ def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
607
  # This might be redundant if users don't edit notes between PDF and Audio steps,
608
  # but ensures the audio matches the *latest* notes displayed.
609
  progress(0.1, desc="Saving latest notes...")
 
 
610
  updated_slides_data = []
611
  for i in range(num_slides):
612
  updated_slides_data.append(
@@ -623,11 +682,20 @@ def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
623
  f.write(updated_markdown)
624
  logger.info(f"Updated presentation markdown before audio gen: {state_md_path}")
625
  except IOError as e:
626
- logger.error(f"Failed to save updated markdown before audio gen: {e}")
627
  warning_msg = f"Warning: Could not save latest notes to markdown file: {e}"
628
  gr.Warning(warning_msg)
 
 
629
  status_update = f"Warning: {warning_msg}"
630
- yield status_update # Yield status with warning
 
 
 
 
 
 
 
 
631
 
632
  generated_audio_paths = ["" for _ in range(num_slides)]
633
  audio_generation_failed = False
@@ -641,6 +709,9 @@ def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
641
  progress_val,
642
  desc=f"Audio slide {slide_num}/{num_slides}",
643
  )
 
 
 
644
  output_file_path = Path(audio_dir) / f"{slide_num}.wav"
645
  if not note_text or not note_text.strip():
646
  try: # Generate silence
@@ -704,7 +775,7 @@ def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
704
 
705
  # Return tuple including status update + original outputs
706
  yield (
707
- status_update,
708
  audio_dir,
709
  gr.update(visible=True), # btn_generate_video
710
  gr.update(visible=False), # btn_generate_audio
@@ -717,6 +788,7 @@ def step4_generate_video(
717
  state_temp_dir,
718
  state_audio_dir,
719
  state_pdf_path, # Use PDF path from state
 
720
  progress=gr.Progress(track_tqdm=True),
721
  ):
722
  """Generates the final video using PDF images and audio files."""
@@ -730,6 +802,9 @@ def step4_generate_video(
730
  )
731
 
732
  video_output_path = os.path.join(state_temp_dir, "final_presentation.mp4")
 
 
 
733
  progress(0.1, desc="Preparing video components...")
734
  pdf_images = [] # Initialize to ensure cleanup happens
735
  try:
@@ -738,13 +813,14 @@ def step4_generate_video(
738
  if not audio_files:
739
  warning_msg = f"Warning: No WAV files found in {state_audio_dir}. Video might lack audio."
740
  logger.warning(warning_msg)
741
- status_update = f"Warning: {warning_msg}"
742
- yield status_update
743
  # Decide whether to proceed with silent video or error out
744
  # raise gr.Error(f"No audio files found in {state_audio_dir}")
745
 
746
  # Convert PDF to images
747
  progress(0.2, desc="Converting PDF to images...")
 
 
748
  pdf_images = convert_pdf_to_images(state_pdf_path, dpi=150)
749
  if not pdf_images:
750
  raise gr.Error(f"Failed to convert PDF ({state_pdf_path}) to images.")
@@ -754,10 +830,14 @@ def step4_generate_video(
754
  if len(pdf_images) != len(audio_files):
755
  warning_msg = f"Warning: Mismatch: {len(pdf_images)} PDF pages vs {len(audio_files)} audio files. Video clips might have incorrect durations or missing audio."
756
  logger.warning(warning_msg)
757
- status_update = f"Warning: {warning_msg}"
758
- yield status_update
 
759
 
760
  progress(0.5, desc="Creating individual video clips...")
 
 
 
761
  buffer_seconds = 1.0
762
  output_fps = 10
763
  video_clips = create_video_clips(
@@ -768,11 +848,17 @@ def step4_generate_video(
768
  raise gr.Error("Failed to create any video clips.")
769
 
770
  progress(0.8, desc="Concatenating clips...")
 
 
 
771
  concatenate_clips(video_clips, video_output_path, output_fps)
772
 
773
  logger.info(f"Video concatenation complete: {video_output_path}")
774
 
775
  progress(0.95, desc="Cleaning up temp images...")
 
 
 
776
  cleanup_temp_files(pdf_images) # Pass the list of image paths
777
 
778
  except Exception as e:
@@ -780,11 +866,8 @@ def step4_generate_video(
780
  cleanup_temp_files(pdf_images)
781
  logger.error(f"Video generation failed: {e}", exc_info=True)
782
  status_update = f"Video generation failed: {e}"
783
- yield (
784
- status_update,
785
- gr.update(),
786
- gr.update(visible=True),
787
- ) # Keep button visible
788
  raise gr.Error(f"Video generation failed: {e}")
789
 
790
  info_msg = f"Video generated: {os.path.basename(video_output_path)}"
@@ -794,7 +877,7 @@ def step4_generate_video(
794
 
795
  # Return tuple including status update
796
  yield (
797
- status_update,
798
  gr.update(value=video_output_path, visible=True), # video_output
799
  gr.update(visible=False), # btn_generate_video
800
  )
@@ -1044,7 +1127,7 @@ with gr.Blocks(
1044
  ]
1045
  btn_fetch_generate.click(
1046
  fn=step1_fetch_and_generate_presentation,
1047
- inputs=[input_url],
1048
  outputs=step1_outputs,
1049
  show_progress="full",
1050
  ).then(
@@ -1083,7 +1166,7 @@ with gr.Blocks(
1083
 
1084
  # Step 2 Click Handler
1085
  step2_inputs = (
1086
- [state_temp_dir, state_md_path, state_slides_data]
1087
  + all_code_editors
1088
  + all_notes_textboxes
1089
  )
@@ -1119,18 +1202,11 @@ with gr.Blocks(
1119
  ).then(lambda: gr.update(selected=2), outputs=tabs_widget) # Switch to Tab 3
1120
 
1121
  # Step 3 Click Handler
1122
- # Need to add status_textbox to inputs for step 3
1123
  step3_inputs = (
1124
- [
1125
- state_temp_dir,
1126
- state_md_path,
1127
- state_slides_data,
1128
- status_textbox,
1129
- ] # Added status_textbox
1130
  + all_code_editors
1131
  + all_notes_textboxes
1132
  )
1133
- # Need to add status_textbox to outputs for step 3
1134
  step3_outputs = (
1135
  [
1136
  status_textbox, # Added status output
@@ -1156,7 +1232,6 @@ with gr.Blocks(
1156
  )
1157
 
1158
  # Step 4 Click Handler
1159
- # Add status_textbox to inputs and outputs
1160
  step4_inputs = [state_temp_dir, state_audio_dir, state_pdf_path, status_textbox]
1161
  step4_outputs = [
1162
  status_textbox, # Added status output
 
278
  # --- Gradio Workflow Functions ---
279
 
280
 
281
+ def step1_fetch_and_generate_presentation(
282
+ url, status_textbox, progress=gr.Progress(track_tqdm=True)
283
+ ):
284
  """Fetches content, generates presentation markdown, prepares editor, and copies template. Uses caching based on URL."""
285
  if not url:
286
  raise gr.Error("Please enter a URL.")
287
  logger.info(f"Step 1: Fetching & Generating for {url}")
288
 
289
  status_update = f"Starting Step 1: Fetching content from {url}..."
290
+ yield {status_textbox: gr.update(value=status_update)}
291
 
292
  # --- Cache Check ---
293
  try:
 
340
  status_update = (
341
  "Loaded presentation from cache. Preparing editor..."
342
  )
343
+ yield {status_textbox: gr.update(value=status_update)}
344
  logger.info(f"Using cached data for {len(slides_data)} slides.")
345
  # Return updates for the UI state and controls
346
  yield (
347
+ gr.update(value=status_update),
348
  temp_dir,
349
  md_path,
350
  slides_data,
 
370
  raise gr.Error("LLM Client not initialized. Check API Key.")
371
 
372
  status_update = "Fetching webpage content..."
373
+ yield {status_textbox: gr.update(value=status_update)}
374
  web_content = fetch_webpage_content(url)
375
  if not web_content:
376
  raise gr.Error("Failed to fetch or parse content from the URL.")
377
 
378
  progress(0.3, desc="Generating presentation with LLM...")
379
  status_update = "Generating presentation with LLM..."
380
+ yield {status_textbox: gr.update(value=status_update)}
381
  try:
382
  presentation_md = generate_presentation_with_llm(
383
  hf_client, LLM_MODEL, PRESENTATION_PROMPT, web_content, url
 
402
 
403
  progress(0.7, desc="Parsing presentation slides...")
404
  status_update = "Parsing presentation slides..."
405
+ yield {status_textbox: gr.update(value=status_update)}
406
  slides_data = parse_presentation_markdown(presentation_md)
407
  if not slides_data:
408
  logger.error("Parsing markdown resulted in zero slides.")
 
446
 
447
  progress(0.9, desc="Preparing editor...")
448
  status_update = "Generated presentation. Preparing editor..."
449
+ yield {status_textbox: gr.update(value=status_update)}
450
  logger.info(f"Prepared data for {len(slides_data)} slides.")
451
 
452
  # Return updates for the UI state and controls
453
  yield (
454
+ gr.update(value=status_update),
455
  temp_dir,
456
  md_path,
457
  slides_data,
 
463
  except Exception as e:
464
  logger.error(f"Error in step 1 (fetch/generate): {e}", exc_info=True)
465
  status_update = f"Error during presentation setup: {e}"
466
+ yield {status_textbox: gr.update(value=status_update)}
467
  # Need to yield the correct number of outputs even on error to avoid issues
468
  yield (
469
+ gr.update(value=status_update),
470
  None,
471
  None,
472
  [],
 
482
  state_temp_dir,
483
  state_md_path,
484
  state_slides_data,
485
+ status_textbox,
486
  *editors,
487
  progress=gr.Progress(track_tqdm=True),
488
  ):
 
490
  if not all([state_temp_dir, state_md_path, state_slides_data]):
491
  raise gr.Error("Session state missing.")
492
  logger.info("Step 2: Building Slides (PDF + Images)")
493
+ status_update = "Starting Step 2: Building slides..."
494
+ yield {status_textbox: gr.update(value=status_update)}
495
 
496
  num_slides = len(state_slides_data)
497
  MAX_SLIDES = 20
 
504
  raise gr.Error("Editor input mismatch.")
505
 
506
  progress(0.1, desc="Saving edited markdown...")
507
+ status_update = "Saving edited markdown..."
508
+ yield {status_textbox: gr.update(value=status_update)}
509
  updated_slides = []
510
  for i in range(num_slides):
511
  updated_slides.append(
 
517
  f.write(updated_md)
518
  logger.info(f"Saved edited markdown: {state_md_path}")
519
  except IOError as e:
520
+ status_update = f"Failed to save markdown: {e}"
521
+ yield {status_textbox: gr.update(value=status_update)}
522
+ yield (
523
+ gr.update(value=status_update),
524
+ None,
525
+ [],
526
+ gr.update(),
527
+ gr.update(),
528
+ gr.update(),
529
+ )
530
  raise gr.Error(f"Failed to save markdown: {e}")
531
 
532
  progress(0.3, desc="Generating PDF...")
533
+ status_update = "Generating PDF..."
534
+ yield {status_textbox: gr.update(value=status_update)}
535
  pdf_output_path = os.path.join(state_temp_dir, "presentation.pdf")
536
  try:
537
  generated_pdf_path = generate_pdf_from_markdown(state_md_path, pdf_output_path)
538
  if not generated_pdf_path:
539
  raise gr.Error("PDF generation failed (check logs).")
540
  except gr.Error as e:
541
+ status_update = f"PDF Generation Error: {e}"
542
+ yield {status_textbox: gr.update(value=status_update)}
543
+ yield (
544
+ gr.update(value=status_update),
545
+ None,
546
+ [],
547
+ gr.update(),
548
+ gr.update(visible=True),
549
+ gr.update(),
550
+ )
551
+ raise e
552
  except Exception as e:
553
+ status_update = f"Unexpected PDF Error: {e}"
554
+ yield {status_textbox: gr.update(value=status_update)}
555
+ yield (
556
+ gr.update(value=status_update),
557
+ None,
558
+ [],
559
+ gr.update(),
560
+ gr.update(visible=True),
561
+ gr.update(),
562
+ )
563
+ raise gr.Error(f"Unexpected error generating PDF: {e}")
564
 
565
  progress(0.7, desc="Converting PDF to images...")
566
+ status_update = "Converting PDF to images..."
567
+ yield {status_textbox: gr.update(value=status_update)}
568
  pdf_images = []
569
  try:
570
  pdf_images = convert_pdf_to_images(
 
576
  if len(pdf_images) != num_slides:
577
  warning_msg = f"Warning: PDF page count ({len(pdf_images)}) != slide count ({num_slides}). Images might mismatch."
578
  gr.Warning(warning_msg)
579
+ status_update += f" ({warning_msg})"
580
  # Pad or truncate? For now, just return what we have, UI update logic handles MAX_SLIDES
581
  except Exception as e:
582
  logger.error(f"Error converting PDF to images: {e}", exc_info=True)
583
+ status_update = f"Failed to convert PDF to images: {e}"
584
+ yield {status_textbox: gr.update(value=status_update)}
585
+ yield (
586
+ gr.update(value=status_update),
587
+ generated_pdf_path,
588
+ [],
589
+ gr.update(),
590
+ gr.update(visible=True),
591
+ gr.update(value=generated_pdf_path, visible=True),
592
+ )
593
+ # Proceed without images? Or raise error? Let's raise.
594
  raise gr.Error(f"Failed to convert PDF to images: {e}")
595
 
596
  info_msg = f"Built {len(pdf_images)} slide images. Ready for Step 3."
 
598
  progress(1.0, desc="Slide build complete.")
599
  status_update = f"Step 2 Complete: {info_msg}"
600
  yield (
601
+ gr.update(value=status_update),
602
  generated_pdf_path,
603
  pdf_images, # Return the list of image paths
604
  gr.update(visible=True), # btn_generate_audio
 
609
 
610
  def step3_generate_audio(*args, progress=gr.Progress(track_tqdm=True)):
611
  """Generates audio files for the speaker notes using edited content."""
612
+ # Args structure adjustment:
613
  # args[0]: state_temp_dir
614
  # args[1]: state_md_path
615
+ # args[2]: original_slides_data
616
+ # args[3]: status_textbox <- Now passed explicitly
617
+ # args[4...]: editors
 
618
 
619
  state_temp_dir = args[0]
620
  state_md_path = args[1]
621
  original_slides_data = args[2]
622
+ status_textbox = args[3]
623
+
624
+ # editors = args[3:] # Old slicing
625
  num_slides = len(original_slides_data)
626
  if num_slides == 0:
627
  logger.error("Step 3 (Audio) called with zero slides data.")
 
629
 
630
  MAX_SLIDES = 20 # Ensure this matches UI definition
631
  # --- Adjust indices based on adding status_textbox input ---
632
+ # Start editors from index 4 now
633
+ code_editors_start_index = 4
634
+ notes_textboxes_start_index = 4 + MAX_SLIDES
635
 
636
  # Slice the *actual* edited values based on num_slides
637
  edited_contents = args[
 
654
  )
655
 
656
  logger.info(f"Processing {num_slides} slides for audio generation.")
657
+ status_update = "Starting Step 3: Generating audio..."
658
+ yield {status_textbox: gr.update(value=status_update)}
659
+
660
  audio_dir = os.path.join(state_temp_dir, "audio")
661
  os.makedirs(audio_dir, exist_ok=True)
662
 
 
664
  # This might be redundant if users don't edit notes between PDF and Audio steps,
665
  # but ensures the audio matches the *latest* notes displayed.
666
  progress(0.1, desc="Saving latest notes...")
667
+ status_update = "Saving latest notes..."
668
+ yield {status_textbox: gr.update(value=status_update)}
669
  updated_slides_data = []
670
  for i in range(num_slides):
671
  updated_slides_data.append(
 
682
  f.write(updated_markdown)
683
  logger.info(f"Updated presentation markdown before audio gen: {state_md_path}")
684
  except IOError as e:
 
685
  warning_msg = f"Warning: Could not save latest notes to markdown file: {e}"
686
  gr.Warning(warning_msg)
687
+ status_update += f" ({warning_msg})"
688
+ yield {status_textbox: gr.update(value=status_update)}
689
  status_update = f"Warning: {warning_msg}"
690
+ yield (
691
+ gr.update(value=status_update),
692
+ None,
693
+ [],
694
+ gr.update(),
695
+ gr.update(visible=True),
696
+ gr.update(),
697
+ )
698
+ raise gr.Error(f"Failed to save updated markdown before audio gen: {e}")
699
 
700
  generated_audio_paths = ["" for _ in range(num_slides)]
701
  audio_generation_failed = False
 
709
  progress_val,
710
  desc=f"Audio slide {slide_num}/{num_slides}",
711
  )
712
+ status_update = f"Generating audio for slide {slide_num}/{num_slides}..."
713
+ yield {status_textbox: gr.update(value=status_update)}
714
+
715
  output_file_path = Path(audio_dir) / f"{slide_num}.wav"
716
  if not note_text or not note_text.strip():
717
  try: # Generate silence
 
775
 
776
  # Return tuple including status update + original outputs
777
  yield (
778
+ gr.update(value=status_update),
779
  audio_dir,
780
  gr.update(visible=True), # btn_generate_video
781
  gr.update(visible=False), # btn_generate_audio
 
788
  state_temp_dir,
789
  state_audio_dir,
790
  state_pdf_path, # Use PDF path from state
791
+ status_textbox,
792
  progress=gr.Progress(track_tqdm=True),
793
  ):
794
  """Generates the final video using PDF images and audio files."""
 
802
  )
803
 
804
  video_output_path = os.path.join(state_temp_dir, "final_presentation.mp4")
805
+ status_update = "Starting Step 4: Generating video..."
806
+ yield {status_textbox: gr.update(value=status_update)}
807
+
808
  progress(0.1, desc="Preparing video components...")
809
  pdf_images = [] # Initialize to ensure cleanup happens
810
  try:
 
813
  if not audio_files:
814
  warning_msg = f"Warning: No WAV files found in {state_audio_dir}. Video might lack audio."
815
  logger.warning(warning_msg)
816
+ status_update += f" ({warning_msg})"
 
817
  # Decide whether to proceed with silent video or error out
818
  # raise gr.Error(f"No audio files found in {state_audio_dir}")
819
 
820
  # Convert PDF to images
821
  progress(0.2, desc="Converting PDF to images...")
822
+ status_update = "Converting PDF back to images for video..."
823
+ yield {status_textbox: gr.update(value=status_update)}
824
  pdf_images = convert_pdf_to_images(state_pdf_path, dpi=150)
825
  if not pdf_images:
826
  raise gr.Error(f"Failed to convert PDF ({state_pdf_path}) to images.")
 
830
  if len(pdf_images) != len(audio_files):
831
  warning_msg = f"Warning: Mismatch: {len(pdf_images)} PDF pages vs {len(audio_files)} audio files. Video clips might have incorrect durations or missing audio."
832
  logger.warning(warning_msg)
833
+ status_update += f" ({warning_msg})"
834
+ # yield status_update # Old yield
835
+ yield {status_textbox: gr.update(value=status_update)}
836
 
837
  progress(0.5, desc="Creating individual video clips...")
838
+ status_update = "Creating individual video clips..."
839
+ # yield status_update # Old yield
840
+ yield {status_textbox: gr.update(value=status_update)}
841
  buffer_seconds = 1.0
842
  output_fps = 10
843
  video_clips = create_video_clips(
 
848
  raise gr.Error("Failed to create any video clips.")
849
 
850
  progress(0.8, desc="Concatenating clips...")
851
+ status_update = "Concatenating clips into final video..."
852
+ # yield status_update # Old yield
853
+ yield {status_textbox: gr.update(value=status_update)}
854
  concatenate_clips(video_clips, video_output_path, output_fps)
855
 
856
  logger.info(f"Video concatenation complete: {video_output_path}")
857
 
858
  progress(0.95, desc="Cleaning up temp images...")
859
+ status_update = "Cleaning up temporary image files..."
860
+ # yield status_update # Old yield
861
+ yield {status_textbox: gr.update(value=status_update)}
862
  cleanup_temp_files(pdf_images) # Pass the list of image paths
863
 
864
  except Exception as e:
 
866
  cleanup_temp_files(pdf_images)
867
  logger.error(f"Video generation failed: {e}", exc_info=True)
868
  status_update = f"Video generation failed: {e}"
869
+ yield {status_textbox: gr.update(value=status_update)}
870
+ yield (gr.update(value=status_update), gr.update(), gr.update(visible=True))
 
 
 
871
  raise gr.Error(f"Video generation failed: {e}")
872
 
873
  info_msg = f"Video generated: {os.path.basename(video_output_path)}"
 
877
 
878
  # Return tuple including status update
879
  yield (
880
+ gr.update(value=status_update),
881
  gr.update(value=video_output_path, visible=True), # video_output
882
  gr.update(visible=False), # btn_generate_video
883
  )
 
1127
  ]
1128
  btn_fetch_generate.click(
1129
  fn=step1_fetch_and_generate_presentation,
1130
+ inputs=[input_url, status_textbox],
1131
  outputs=step1_outputs,
1132
  show_progress="full",
1133
  ).then(
 
1166
 
1167
  # Step 2 Click Handler
1168
  step2_inputs = (
1169
+ [state_temp_dir, state_md_path, state_slides_data, status_textbox]
1170
  + all_code_editors
1171
  + all_notes_textboxes
1172
  )
 
1202
  ).then(lambda: gr.update(selected=2), outputs=tabs_widget) # Switch to Tab 3
1203
 
1204
  # Step 3 Click Handler
 
1205
  step3_inputs = (
1206
+ [state_temp_dir, state_md_path, state_slides_data, status_textbox]
 
 
 
 
 
1207
  + all_code_editors
1208
  + all_notes_textboxes
1209
  )
 
1210
  step3_outputs = (
1211
  [
1212
  status_textbox, # Added status output
 
1232
  )
1233
 
1234
  # Step 4 Click Handler
 
1235
  step4_inputs = [state_temp_dir, state_audio_dir, state_pdf_path, status_textbox]
1236
  step4_outputs = [
1237
  status_textbox, # Added status output