按照指定时间裁剪视频通常有如下三种方法:
- 使用 moviepy 库的 clip.subclip(start_time, stop_time) 方法实现:Clips transformations and effects — MoviePy 1.0.2 documentation (zulko.github.io)
- 使用 opencv-python 库的 cap.set(cv2.CAP_PROP_POS_MSEC, float(start_time * MILLISECONDS))方法实现:Reading and Writing Images and Video — OpenCV 2.4.13.7 documentation
- 使用 ffmpeg-python 库的 trim 方法实现:ffmpeg-python: Python bindings for FFmpeg — ffmpeg-python documentation (kkroening.github.io)
但对于从监控等视频流保存的视频文件来说,可能存在文件的起始时间戳不准确的情况。即第一帧视频数据对应的时间戳不是 00:00:00 。这种情况下,上述方法的前两种将无法正确处理(第三种方法未尝试)。因此,我采用了一种更笨(速度更慢,但更直观)的做法。即逐帧读取视频,通过判断各帧与第一帧的相对时间来确定是否保留当前帧,由此规避了首帧时间戳错误的问题。程序片段记录如下:
import cv2 def clip_video_with_opencv(src_path:str, dst_path:str, start_time:str, stop_time:str): """ Clip the video with OpenCV for the specified time period (Force the start timestamp of the video to `00:00:00`) This function is time consuming. If the start timestamp of the video is correct, you can use the `cap.set()` function to jump to the start time of the target clip directly, or use the `moviepy` module instead. Args: `src_path` (str): Source video path. The path of the video to be clipped `dst_path` (str): Destination video path. Storage path of the clipped video `start_time` (str): Time to start clipping. The format is `hh:mm:ss` `stop_time` (str): Time to stop clipping. `None` if the stop time is the end of the video. The format is `hh:mm:ss` """ th, tm, ts = map(int, start_time.split(":")) start_time = th*3600 + tm*60 + ts # Seconds if stop_time: th, tm, ts = map(int, stop_time.split(":")) stop_time = th*3600 + tm*60 + ts # Seconds ### Prepare to load video data in_cap = cv2.VideoCapture(src_path) frame_total = int(in_cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = int(in_cap.get(cv2.CAP_PROP_FPS)) # fourcc_int = int(in_cap.get(cv2.CAP_PROP_FOURCC)) frame_width, frame_height = int(in_cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(in_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) ### Prepare to save video clip # fourcc_str = "{}{}{}{}".format(chr(fourcc_int&255), chr((fourcc_int>>8)&255), chr((fourcc_int>>16)&255), chr((fourcc_int>>24)&255)) # fourcc = cv2.VideoWriter_fourcc(*fourcc_str) fourcc = cv2.VideoWriter_fourcc(*'mp4v') out_cap = cv2.VideoWriter(dst_path, fourcc, fps, (frame_width, frame_height)) ### Frame-by-frame processing frame_idx = 0 start_ts = None while in_cap.isOpened(): success = in_cap.grab() # !!! Load a frame of data but do not decode it (decoding is time consuming) if not success: break video_ts = int(round(int(in_cap.get(cv2.CAP_PROP_POS_MSEC))/1000)) # Seconds if start_ts is None: start_ts = video_ts video_ts -= start_ts # Force the start timestamp of the video to 00:00:00 th, tm, ts = video_ts//3600, (video_ts%3600)//60, video_ts%3600%60 if video_ts < start_time: # Skip the video frames before the target clip print("{}/{}: {:0>2d}:{:0>2d}:{:0>2d} [skip]".format(frame_idx+1, frame_total, th, tm, ts), end="\r") elif video_ts >= start_time and (stop_time is None or video_ts <= stop_time): # Save video frames of the target clip print("{}/{}: {:0>2d}:{:0>2d}:{:0>2d} [write]".format(frame_idx+1, frame_total, th, tm, ts), end="\r") success, frame = in_cap.retrieve() # !!! Decode the frame data. Only the video frames of the target clip are decoded if not success: break out_cap.write(frame) else: break frame_idx += 1 in_cap.release() out_cap.release() print("{}/{}: {:0>2d}:{:0>2d}:{:0>2d} [done]".format(frame_idx, frame_total, th, tm, ts))