video_segment_extractor.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 视频时间段提取工具
  5. 功能:从视频文件中提取指定时间段的内容,并保存为图片帧序列
  6. 作者:工程师
  7. """
  8. import cv2
  9. import os
  10. import argparse
  11. import sys
  12. from pathlib import Path
  13. import time
  14. def parse_time_to_seconds(time_str):
  15. """
  16. 将时间字符串转换为秒数
  17. 支持格式:
  18. - 秒数:"30" 或 "30.5"
  19. - 分:秒:"1:30" 或 "1:30.5"
  20. - 时:分:秒:"0:1:30" 或 "0:1:30.5"
  21. """
  22. try:
  23. if ':' not in time_str:
  24. return float(time_str)
  25. parts = time_str.split(':')
  26. if len(parts) == 2: # MM:SS
  27. minutes, seconds = parts
  28. return int(minutes) * 60 + float(seconds)
  29. elif len(parts) == 3: # HH:MM:SS
  30. hours, minutes, seconds = parts
  31. return int(hours) * 3600 + int(minutes) * 60 + float(seconds)
  32. else:
  33. raise ValueError("时间格式不正确")
  34. except ValueError as e:
  35. raise ValueError(f"无法解析时间格式 '{time_str}': {e}")
  36. def extract_video_segment(video_path, start_time, end_time, output_dir, fps=None, prefix="frame"):
  37. """
  38. 从视频中提取指定时间段的帧
  39. Args:
  40. video_path (str): 输入视频文件路径
  41. start_time (str): 开始时间(秒或时:分:秒格式)
  42. end_time (str): 结束时间(秒或时:分:秒格式)
  43. output_dir (str): 输出目录路径
  44. fps (float): 提取帧率,None表示使用原视频帧率
  45. prefix (str): 输出文件名前缀
  46. Returns:
  47. tuple: (成功提取的帧数, 总处理时间)
  48. """
  49. # 检查输入文件
  50. if not os.path.exists(video_path):
  51. raise FileNotFoundError(f"视频文件不存在: {video_path}")
  52. # 创建输出目录
  53. output_path = Path(output_dir)
  54. output_path.mkdir(parents=True, exist_ok=True)
  55. # 解析时间
  56. start_seconds = parse_time_to_seconds(start_time)
  57. end_seconds = parse_time_to_seconds(end_time)
  58. if start_seconds >= end_seconds:
  59. raise ValueError("开始时间必须小于结束时间")
  60. print(f"正在处理视频: {video_path}")
  61. print(f"提取时间段: {start_seconds:.2f}s - {end_seconds:.2f}s")
  62. print(f"输出目录: {output_dir}")
  63. # 打开视频文件
  64. cap = cv2.VideoCapture(video_path)
  65. if not cap.isOpened():
  66. raise RuntimeError(f"无法打开视频文件: {video_path}")
  67. try:
  68. # 获取视频信息
  69. video_fps = cap.get(cv2.CAP_PROP_FPS)
  70. total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
  71. video_duration = total_frames / video_fps
  72. print(f"视频信息: {video_fps:.2f} FPS, {total_frames} 帧, {video_duration:.2f}s")
  73. # 检查时间范围
  74. if end_seconds > video_duration:
  75. print(f"警告: 结束时间 {end_seconds:.2f}s 超过视频长度 {video_duration:.2f}s,将调整为视频结束时间")
  76. end_seconds = video_duration
  77. # 设置提取帧率
  78. extract_fps = fps if fps is not None else video_fps
  79. frame_interval = video_fps / extract_fps if extract_fps <= video_fps else 1
  80. print(f"提取帧率: {extract_fps:.2f} FPS (每 {frame_interval:.2f} 帧提取一帧)")
  81. # 定位到开始时间
  82. start_frame = int(start_seconds * video_fps)
  83. end_frame = int(end_seconds * video_fps)
  84. cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
  85. extracted_count = 0
  86. current_frame = start_frame
  87. next_extract_frame = start_frame
  88. start_time_process = time.time()
  89. print(f"开始提取帧 ({start_frame} - {end_frame})...")
  90. while current_frame <= end_frame:
  91. ret, frame = cap.read()
  92. if not ret:
  93. break
  94. # 检查是否需要提取当前帧
  95. if current_frame >= next_extract_frame:
  96. # 生成输出文件名
  97. output_filename = f"{prefix}_{extracted_count:06d}.jpg"
  98. output_filepath = output_path / output_filename
  99. # 保存帧
  100. success = cv2.imwrite(str(output_filepath), frame)
  101. if success:
  102. extracted_count += 1
  103. if extracted_count % 100 == 0:
  104. progress = (current_frame - start_frame) / (end_frame - start_frame) * 100
  105. print(f"已提取 {extracted_count} 帧 ({progress:.1f}%)")
  106. else:
  107. print(f"警告: 无法保存帧到 {output_filepath}")
  108. # 计算下一个要提取的帧
  109. next_extract_frame += frame_interval
  110. current_frame += 1
  111. process_time = time.time() - start_time_process
  112. print(f"\n提取完成!")
  113. print(f"成功提取 {extracted_count} 帧")
  114. print(f"处理时间: {process_time:.2f}s")
  115. print(f"输出目录: {output_dir}")
  116. return extracted_count, process_time
  117. finally:
  118. cap.release()
  119. def main():
  120. parser = argparse.ArgumentParser(
  121. description="从视频文件中提取指定时间段的帧",
  122. formatter_class=argparse.RawDescriptionHelpFormatter,
  123. epilog="""
  124. 时间格式示例:
  125. 30 - 30秒
  126. 1:30 - 1分30秒
  127. 0:1:30 - 1分30秒
  128. 1:23:45 - 1小时23分45秒
  129. 使用示例:
  130. python video_segment_extractor.py input.mp4 10 60 -o frames
  131. python video_segment_extractor.py input.mp4 0:10 1:00 -o frames --fps 10
  132. """
  133. )
  134. parser.add_argument("video", help="输入视频文件路径")
  135. parser.add_argument("start_time", help="开始时间 (秒数或时:分:秒格式)")
  136. parser.add_argument("end_time", help="结束时间 (秒数或时:分:秒格式)")
  137. parser.add_argument("-o", "--output", default="extracted_frames",
  138. help="输出目录 (默认: extracted_frames)")
  139. parser.add_argument("--fps", type=float,
  140. help="提取帧率 (默认使用原视频帧率)")
  141. parser.add_argument("--prefix", default="frame",
  142. help="输出文件名前缀 (默认: frame)")
  143. args = parser.parse_args()
  144. try:
  145. extracted_count, process_time = extract_video_segment(
  146. args.video,
  147. args.start_time,
  148. args.end_time,
  149. args.output,
  150. args.fps,
  151. args.prefix
  152. )
  153. print(f"\n任务完成! 共提取 {extracted_count} 帧,耗时 {process_time:.2f}s")
  154. except Exception as e:
  155. print(f"错误: {e}", file=sys.stderr)
  156. sys.exit(1)
  157. if __name__ == "__main__":
  158. main()