Skip to content

Instantly share code, notes, and snippets.

@xvdp
Last active July 20, 2023 01:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xvdp/ec64daaa6fa381d8ba02342801a51b37 to your computer and use it in GitHub Desktop.
Save xvdp/ec64daaa6fa381d8ba02342801a51b37 to your computer and use it in GitHub Desktop.
Simple solution to show frame number and frame time in VLC or other video players.
""" @xvdp
Simple solution to Show frame number and frame time with VLC or other video players
Make a subtitle file
caveats:
only tested in linux
requires ffmpeg / only tested with ffmpeg 4.3
only tested with limited number of .mov and .mkv files
it may fail if ffprobe reports nb_frames, DURATION or frame_rate with keys not parsed here.
ffplay -i my_video.mp4 -vf "subtitles=my_video_subtitles.srt"
"""
import sys
import os
import os.path as osp
import json
def frame_subtitles(filename, stream=0):
""" creates frame number and frame time from video
"""
nb_frames, frame_rate = ffmpeg_stats(filename, stream)
make_subtitle(filename, nb_frames, frame_rate, stream)
def make_subtitle(filename, nb_frames, frame_rate, stream=0):
"""
nb_frames and some form of frame_rate you can get from ffprobe
depending on how video was recorded there may be an avg_frame_rate and DURATION pr...
"""
fname = osp.abspath(filename)
assert osp.isfile(fname), f"file not found {fname}"
name = f"{osp.splitext(fname)[0]}_frames.srt"
print(f"Making Frame / Frame time subtitle file {filename} stream [{stream}] -> {name}")
sub = ""
last_frame = frame_to_time(0, frame_rate)
for i in range(nb_frames):
next_frame = frame_to_time(i+1, frame_rate)
sub += f"{i+1}\n{last_frame} --> {next_frame}\n\t{i+1}\t{last_frame}\n\n"
last_frame = next_frame
with open(name, 'w', encoding='utf8') as _fi:
_fi.write(sub)
def ffmpeg_stats(filename, stream=0):
""" return (nb_frames, frame_rate) from video filename
"""
_cmd = f"ffprobe -v quiet -print_format json -show_format -show_streams {filename}"
with os.popen(_cmd) as _fi:
stats = json.loads(_fi.read())
if 'streams' in stats:
videos = [s for s in stats['streams'] if s['codec_type'] == 'video']
assert videos is not None, f'no video streams found in {filename}'
if len(videos) > 1:
print(f"video {filename} cotains {len(videos)} streams, processing stream={stream}")
_stats = videos[stream]
_frame_rate_keys = [k for k in _stats if 'frame_rate' in k]
assert len(_frame_rate_keys), f"no frame_rate keys found in {filename}\n{json.dumps(_stats)}"
frame_rate = eval(_stats[_frame_rate_keys[0]])
if 'nb_frames' in _stats:
nb_frames = eval(_stats['nb_frames'])
elif 'tags' in _stats and 'DURATION' in _stats['tags']:
duration = _stats['tags']['DURATION']
seconds = sum(x * float(t) for x, t in zip([3600, 60, 1], duration.split(":")))
nb_frames = round(seconds * frame_rate)
else:
print(f"neither 'nb_frames' and 'DURATION' found in stream\{json.dumps(_stats)}")
return None
return nb_frames, frame_rate
def frame_to_time(frame=0, frame_rate=30):
"""convert frame number to time"""
t = frame/frame_rate
_t = int(t)
return f"{(_t//3600)%24:02d}:{(_t//60)%60:02d}:{_t%60:02d}.{(int((t - _t)*1000)):03d}"
if __name__ == "__main__":
assert len(sys.argv) > 1, "usage: $ python frame_subtitles.py <video_file>"
frame_subtitles(sys.argv[1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment