我用我编写的Android5.2应用程序从平板电脑的前摄像头录制了几段视频。我已经为每个视频存储了以毫秒为单位的开始时间戳(Unix时间)。
不幸的是,每个视频都有不同的帧(范围从20到30)。有了OpenCV,我就可以获得每个视频的框架:
import cv2
video = cv2.VideoCapture(videoFile)
fps = video.get(cv2.CAP_PROP_FPS)这很好,理论上我可以为视频中的每一帧添加1000/fps (由于毫秒)。但这假设框架在整个录音过程中保持稳定。我不知道是不是这样。
Python中是否有可能获得视频中独立于框架的每一帧的时间戳(毫秒)?
发布于 2022-10-08 16:33:07
我用多个库做了一些测试。
import av
import cv2
import json
import subprocess
import time
from decimal import Decimal
from decord import VideoReader
from ffms2 import VideoSource
from moviepy.editor import VideoFileClip
from typing import List
def with_movie_py(video: str) -> List[int]:
"""
Link: https://pypi.org/project/moviepy/
My comments:
The timestamps I get are not good compared to gMKVExtractGUI or ffms2. (I only tried with VFR video)
Parameters:
video (str): Video path
Returns:
List of timestamps in ms
"""
vid = VideoFileClip(video)
timestamps = [
round(tstamp * 1000) for tstamp, frame in vid.iter_frames(with_times=True)
]
return timestamps
def with_cv2(video: str) -> List[int]:
"""
Link: https://pypi.org/project/opencv-python/
My comments:
I don't know why, but the last 4 or 5 timestamps are equal to 0 when they should not.
Also, cv2 is slow. It took my computer 132 seconds to process the video.
Parameters:
video (str): Video path
Returns:
List of timestamps in ms
"""
timestamps = []
cap = cv2.VideoCapture(video)
while cap.isOpened():
frame_exists, curr_frame = cap.read()
if frame_exists:
timestamps.append(round(cap.get(cv2.CAP_PROP_POS_MSEC)))
else:
break
cap.release()
return timestamps
def with_pyffms2(video: str) -> List[int]:
"""
Link: https://pypi.org/project/ffms2/
My comments:
Works really well, but it doesn't install ffms2 automatically, so you need to do it by yourself.
The easiest way is to install Vapoursynth and use it to install ffms2.
Also, the library doesn't seems to be really maintained.
Parameters:
video (str): Video path
Returns:
List of timestamps in ms
"""
video_source = VideoSource(video)
# You can also do: video_source.track.timecodes
timestamps = [
int(
(frame.PTS * video_source.track.time_base.numerator)
/ video_source.track.time_base.denominator
)
for frame in video_source.track.frame_info_list
]
return timestamps
def with_decord(video: str) -> List[int]:
"""
Link: https://github.com/dmlc/decord
My comments:
Works really well, but it seems to only work with mkv and mp4 file.
Mp4 file can have a +- 1 ms difference with ffms2, but it is acceptable.
Parameters:
video (str): Video path
Returns:
List of timestamps in ms
"""
vr = VideoReader(video)
timestamps = vr.get_frame_timestamp(range(len(vr)))
timestamps = (timestamps[:, 0] * 1000).round().astype(int).tolist()
return timestamps
def with_pyav(video: str) -> List[int]:
"""
Link: https://pypi.org/project/av/
My comments:
I don't know why, but sometimes it simply doesn't work with certain video.
Also, I tested with different mp4 file and sometimes it take 8 seconds and sometimes 117 seconds.
Parameters:
video (str): Video path
Returns:
List of timestamps in ms
"""
container = av.open(video)
video = container.streams.video[0]
av_timestamps = [
int(frame.pts * video.time_base * 1000) for frame in container.decode(video)
]
container.close()
return av_timestamps
def with_ffprobe(video_path: str, index: int = 0) -> List[int]:
"""
Link: https://ffmpeg.org/ffprobe.html
My comments:
Works really well, but the user need to have FFMpeg in his environment variables.
Parameters:
video (str): Video path
index (int): Index of the stream of the video
Returns:
List of timestamps in ms
"""
def get_pts(packets) -> List[int]:
pts: List[int] = []
for packet in packets:
pts.append(int(Decimal(packet["pts_time"]) * 1000))
pts.sort()
return pts
cmd = f'ffprobe -select_streams {index} -show_entries packet=pts_time:stream=codec_type "{video_path}" -print_format json'
ffprobeOutput = subprocess.run(cmd, capture_output=True, text=True)
ffprobeOutput = json.loads(ffprobeOutput.stdout)
if len(ffprobeOutput) == 0:
raise Exception(
f"The file {video_path} is not a video file or the file does not exist."
)
if len(ffprobeOutput["streams"]) == 0:
raise ValueError(f"The index {index} is not in the file {video_path}.")
if ffprobeOutput["streams"][0]["codec_type"] != "video":
raise ValueError(
f'The index {index} is not a video stream. It is an {ffprobeOutput["streams"][0]["codec_type"]} stream.'
)
return get_pts(ffprobeOutput["packets"])
def main():
video = r"WRITE_YOUR_VIDEO_PATH"
start = time.process_time()
movie_py_timestamps = with_movie_py(video)
print(f"With Movie py {time.process_time() - start} seconds")
start = time.process_time()
cv2_timestamps = with_cv2(video)
print(f"With cv2 {time.process_time() - start} seconds")
start = time.process_time()
ffms2_timestamps = with_pyffms2(video)
print(f"With ffms2 {time.process_time() - start} seconds")
start = time.process_time()
decord_timestamps = with_decord(video)
print(f"With decord {time.process_time() - start} seconds")
start = time.process_time()
av_timestamps = with_pyav(video)
print(f"With av {time.process_time() - start} seconds")
start = time.process_time()
ffprobe_timestamps = with_ffprobe(video)
print(f"With ffprobe {time.process_time() - start} seconds")
if __name__ == "__main__":
main()以下是获得24分钟的mkv时间戳所需的时间。
With Movie py 0.65625 seconds
With cv2 2.4375 seconds
With ffms2 0.0625 seconds
With decord 0.03125 seconds
With av 1.28125 seconds
With ffprobe 0.0 secondshttps://stackoverflow.com/questions/47743246
复制相似问题