XD-MU commited on
Commit
2119db4
·
verified ·
1 Parent(s): fdc38da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -45
app.py CHANGED
@@ -546,23 +546,112 @@ def parse_script_nodes(script_text: str) -> List[str]:
546
 
547
 
548
  def extract_last_frame(video_path: str, output_path: str) -> Optional[str]:
549
- """提取视频最后一帧作为参考图"""
 
 
550
  if cv2 is None:
 
551
  return None
552
 
553
- cap = cv2.VideoCapture(video_path)
554
- if not cap.isOpened():
555
  return None
556
 
557
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
558
- cap.set(cv2.CAP_PROP_POS_FRAMES, max(total_frames - 1, 0))
559
- ret, frame = cap.read()
560
- cap.release()
 
 
561
 
562
- if ret:
563
- cv2.imwrite(output_path, frame)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  return output_path
565
- return None
 
 
 
 
 
566
 
567
 
568
  def stitch_videos(video_paths: List[str], output_path: str):
@@ -685,7 +774,7 @@ def run_video_generation_pipeline(
685
  if i < len(nodes) - 1:
686
  frame_path = os.path.join(output_dir, f"ref_{idx:02d}.png")
687
  last_frame_path = extract_last_frame(video_filename, frame_path)
688
-
689
  yield generated_videos, None, f"✅ 分镜 {idx} 完成"
690
 
691
  # 拼接视频
@@ -727,39 +816,6 @@ def get_demo_path(filename):
727
  return filename if os.path.exists(filename) else None
728
 
729
 
730
- # # 构建 Gradio 界面
731
- # with gr.Blocks(title="AI 剧本视频工厂") as demo:
732
- # gr.Markdown("# 🎬 ScriptAgent & Sora/Veo 视频生成工坊")
733
-
734
- # with gr.Tabs():
735
- # # --- TAB 1: 剧本创作 ---
736
- # with gr.Tab("📝 第一步:剧本创作"):
737
- # with gr.Row():
738
- # with gr.Column():
739
- # llm_input = gr.Textbox(
740
- # label="剧情输入",
741
- # placeholder="主角:你在做什么?...",
742
- # lines=6
743
- # )
744
- # llm_btn = gr.Button("生成/续写剧本", variant="primary")
745
-
746
- # with gr.Column():
747
- # llm_output = gr.Textbox(
748
- # label="生成的剧本",
749
- # lines=10,
750
- # interactive=True
751
- # )
752
- # to_video_btn = gr.Button("⬇️ 发送到视频生成", variant="secondary")
753
-
754
- # gr.Examples(
755
- # [[
756
- # "主角:你在做什么?指引图是不能随便篡改的。\n"
757
- # "蒋前:篡改指引图?不不不,你误会了。\n"
758
- # "蒋前:我才加入风物家没多久,哪有这个本事能篡改它..."
759
- # ]],
760
- # inputs=llm_input
761
- # )
762
-
763
  with gr.Blocks(title="AI 剧本视频工厂") as demo:
764
  gr.Markdown("# 🎬 ScriptAgent & Sora/Veo 视频生成工坊 ")
765
 
@@ -1024,4 +1080,4 @@ print(response.choices[0].message.content)'''
1024
 
1025
  if __name__ == "__main__":
1026
  demo.queue()
1027
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
546
 
547
 
548
  def extract_last_frame(video_path: str, output_path: str) -> Optional[str]:
549
+ """提取视频最后一帧(OpenCV优化版 - 精简)"""
550
+ import time
551
+
552
  if cv2 is None:
553
+ LOGGER.warning("OpenCV 不可用")
554
  return None
555
 
556
+ if not os.path.exists(video_path):
557
+ LOGGER.error(f"视频文件不存在: {video_path}")
558
  return None
559
 
560
+ # === 步骤1: 等待文件写入稳定 ===
561
+ max_wait = 30
562
+ check_interval = 1.0
563
+ stable_count = 0
564
+ required_stable = 3
565
+ last_size = 0
566
 
567
+ LOGGER.info("⏳ 等待文件写入完成...")
568
+ for i in range(int(max_wait / check_interval)):
569
+ try:
570
+ current_size = os.path.getsize(video_path)
571
+ except OSError:
572
+ time.sleep(check_interval)
573
+ continue
574
+
575
+ if current_size == 0:
576
+ time.sleep(check_interval)
577
+ continue
578
+
579
+ if current_size == last_size:
580
+ stable_count += 1
581
+ if stable_count >= required_stable:
582
+ LOGGER.info(f"✅ 文件稳定: {current_size / 1024 / 1024:.2f} MB")
583
+ break
584
+ else:
585
+ stable_count = 0
586
+
587
+ last_size = current_size
588
+ time.sleep(check_interval)
589
+
590
+ # 额外等待确保文件系统同步
591
+ time.sleep(2.0)
592
+
593
+ # === 步骤2: OpenCV 读取视频 ===
594
+ capture = cv2.VideoCapture(video_path)
595
+ if not capture.isOpened():
596
+ LOGGER.error("OpenCV 无法打开视频")
597
+ return None
598
+
599
+ try:
600
+ total_frames = int(capture.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
601
+ fps = capture.get(cv2.CAP_PROP_FPS) or 30
602
+
603
+ if total_frames <= 0:
604
+ LOGGER.error("视频帧数为 0")
605
+ return None
606
+
607
+ LOGGER.info(f"📹 视频信息: {total_frames} 帧, {fps:.2f} FPS")
608
+
609
+ # === 步骤3: 多候选帧策略(避免黑帧/损坏帧)===
610
+ candidates = [
611
+ total_frames - 1, # 最后一帧
612
+ total_frames - 2, # 倒数第2帧
613
+ total_frames - 5, # 倒数第5帧
614
+ max(0, int(total_frames * 0.95)) # 95%位置
615
+ ]
616
+
617
+ frame = None
618
+ used_index = -1
619
+
620
+ for candidate_idx in candidates:
621
+ candidate_idx = max(0, min(candidate_idx, total_frames - 1))
622
+ capture.set(cv2.CAP_PROP_POS_FRAMES, candidate_idx)
623
+ success, temp_frame = capture.read()
624
+
625
+ if success and temp_frame is not None and temp_frame.size > 0:
626
+ # 检查亮度(排除黑屏)
627
+ gray = cv2.cvtColor(temp_frame, cv2.COLOR_BGR2GRAY)
628
+ brightness = gray.mean()
629
+
630
+ if brightness > 5: # 亮度阈值
631
+ frame = temp_frame
632
+ used_index = candidate_idx
633
+ LOGGER.info(f"✅ 提取第 {used_index}/{total_frames} 帧(亮度: {brightness:.1f})")
634
+ break
635
+
636
+ # === 步骤4: 保存图片 ===
637
+ if frame is None:
638
+ LOGGER.error("所有候选帧均无效")
639
+ return None
640
+
641
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
642
+ if not cv2.imwrite(output_path, frame):
643
+ LOGGER.error("保存图片失败")
644
+ return None
645
+
646
+ file_size = os.path.getsize(output_path)
647
+ LOGGER.info(f"💾 参考帧已保存: {os.path.basename(output_path)} ({file_size / 1024:.1f} KB)")
648
  return output_path
649
+
650
+ except Exception as e:
651
+ LOGGER.error(f"提取帧时出错: {e}")
652
+ return None
653
+ finally:
654
+ capture.release()
655
 
656
 
657
  def stitch_videos(video_paths: List[str], output_path: str):
 
774
  if i < len(nodes) - 1:
775
  frame_path = os.path.join(output_dir, f"ref_{idx:02d}.png")
776
  last_frame_path = extract_last_frame(video_filename, frame_path)
777
+
778
  yield generated_videos, None, f"✅ 分镜 {idx} 完成"
779
 
780
  # 拼接视频
 
816
  return filename if os.path.exists(filename) else None
817
 
818
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
  with gr.Blocks(title="AI 剧本视频工厂") as demo:
820
  gr.Markdown("# 🎬 ScriptAgent & Sora/Veo 视频生成工坊 ")
821
 
 
1080
 
1081
  if __name__ == "__main__":
1082
  demo.queue()
1083
+ demo.launch(server_name="0.0.0.0", server_port=7860)