| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 视频时间段提取工具
- 功能:从视频文件中提取指定时间段的内容,并保存为图片帧序列
- 作者:工程师
- """
- import cv2
- import os
- import argparse
- import sys
- from pathlib import Path
- import time
- def parse_time_to_seconds(time_str):
- """
- 将时间字符串转换为秒数
- 支持格式:
- - 秒数:"30" 或 "30.5"
- - 分:秒:"1:30" 或 "1:30.5"
- - 时:分:秒:"0:1:30" 或 "0:1:30.5"
- """
- try:
- if ':' not in time_str:
- return float(time_str)
-
- parts = time_str.split(':')
- if len(parts) == 2: # MM:SS
- minutes, seconds = parts
- return int(minutes) * 60 + float(seconds)
- elif len(parts) == 3: # HH:MM:SS
- hours, minutes, seconds = parts
- return int(hours) * 3600 + int(minutes) * 60 + float(seconds)
- else:
- raise ValueError("时间格式不正确")
- except ValueError as e:
- raise ValueError(f"无法解析时间格式 '{time_str}': {e}")
- def extract_video_segment(video_path, start_time, end_time, output_dir, fps=None, prefix="frame"):
- """
- 从视频中提取指定时间段的帧
-
- Args:
- video_path (str): 输入视频文件路径
- start_time (str): 开始时间(秒或时:分:秒格式)
- end_time (str): 结束时间(秒或时:分:秒格式)
- output_dir (str): 输出目录路径
- fps (float): 提取帧率,None表示使用原视频帧率
- prefix (str): 输出文件名前缀
-
- Returns:
- tuple: (成功提取的帧数, 总处理时间)
- """
- # 检查输入文件
- if not os.path.exists(video_path):
- raise FileNotFoundError(f"视频文件不存在: {video_path}")
-
- # 创建输出目录
- output_path = Path(output_dir)
- output_path.mkdir(parents=True, exist_ok=True)
-
- # 解析时间
- start_seconds = parse_time_to_seconds(start_time)
- end_seconds = parse_time_to_seconds(end_time)
-
- if start_seconds >= end_seconds:
- raise ValueError("开始时间必须小于结束时间")
-
- print(f"正在处理视频: {video_path}")
- print(f"提取时间段: {start_seconds:.2f}s - {end_seconds:.2f}s")
- print(f"输出目录: {output_dir}")
-
- # 打开视频文件
- cap = cv2.VideoCapture(video_path)
- if not cap.isOpened():
- raise RuntimeError(f"无法打开视频文件: {video_path}")
-
- try:
- # 获取视频信息
- video_fps = cap.get(cv2.CAP_PROP_FPS)
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
- video_duration = total_frames / video_fps
-
- print(f"视频信息: {video_fps:.2f} FPS, {total_frames} 帧, {video_duration:.2f}s")
-
- # 检查时间范围
- if end_seconds > video_duration:
- print(f"警告: 结束时间 {end_seconds:.2f}s 超过视频长度 {video_duration:.2f}s,将调整为视频结束时间")
- end_seconds = video_duration
-
- # 设置提取帧率
- extract_fps = fps if fps is not None else video_fps
- frame_interval = video_fps / extract_fps if extract_fps <= video_fps else 1
-
- print(f"提取帧率: {extract_fps:.2f} FPS (每 {frame_interval:.2f} 帧提取一帧)")
-
- # 定位到开始时间
- start_frame = int(start_seconds * video_fps)
- end_frame = int(end_seconds * video_fps)
- cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
-
- extracted_count = 0
- current_frame = start_frame
- next_extract_frame = start_frame
- start_time_process = time.time()
-
- print(f"开始提取帧 ({start_frame} - {end_frame})...")
-
- while current_frame <= end_frame:
- ret, frame = cap.read()
- if not ret:
- break
-
- # 检查是否需要提取当前帧
- if current_frame >= next_extract_frame:
- # 生成输出文件名
- output_filename = f"{prefix}_{extracted_count:06d}.jpg"
- output_filepath = output_path / output_filename
-
- # 保存帧
- success = cv2.imwrite(str(output_filepath), frame)
- if success:
- extracted_count += 1
- if extracted_count % 100 == 0:
- progress = (current_frame - start_frame) / (end_frame - start_frame) * 100
- print(f"已提取 {extracted_count} 帧 ({progress:.1f}%)")
- else:
- print(f"警告: 无法保存帧到 {output_filepath}")
-
- # 计算下一个要提取的帧
- next_extract_frame += frame_interval
-
- current_frame += 1
-
- process_time = time.time() - start_time_process
-
- print(f"\n提取完成!")
- print(f"成功提取 {extracted_count} 帧")
- print(f"处理时间: {process_time:.2f}s")
- print(f"输出目录: {output_dir}")
-
- return extracted_count, process_time
-
- finally:
- cap.release()
- def main():
- parser = argparse.ArgumentParser(
- description="从视频文件中提取指定时间段的帧",
- formatter_class=argparse.RawDescriptionHelpFormatter,
- epilog="""
- 时间格式示例:
- 30 - 30秒
- 1:30 - 1分30秒
- 0:1:30 - 1分30秒
- 1:23:45 - 1小时23分45秒
-
- 使用示例:
- python video_segment_extractor.py input.mp4 10 60 -o frames
- python video_segment_extractor.py input.mp4 0:10 1:00 -o frames --fps 10
- """
- )
-
- parser.add_argument("video", help="输入视频文件路径")
- parser.add_argument("start_time", help="开始时间 (秒数或时:分:秒格式)")
- parser.add_argument("end_time", help="结束时间 (秒数或时:分:秒格式)")
- parser.add_argument("-o", "--output", default="extracted_frames",
- help="输出目录 (默认: extracted_frames)")
- parser.add_argument("--fps", type=float,
- help="提取帧率 (默认使用原视频帧率)")
- parser.add_argument("--prefix", default="frame",
- help="输出文件名前缀 (默认: frame)")
-
- args = parser.parse_args()
-
- try:
- extracted_count, process_time = extract_video_segment(
- args.video,
- args.start_time,
- args.end_time,
- args.output,
- args.fps,
- args.prefix
- )
-
- print(f"\n任务完成! 共提取 {extracted_count} 帧,耗时 {process_time:.2f}s")
-
- except Exception as e:
- print(f"错误: {e}", file=sys.stderr)
- sys.exit(1)
- if __name__ == "__main__":
- main()
|