Skip to content

Instantly share code, notes, and snippets.

@timbennett
Last active June 11, 2023 00:36
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save timbennett/9c1614281797ce620c6069476d39c7b8 to your computer and use it in GitHub Desktop.
Save timbennett/9c1614281797ce620c6069476d39c7b8 to your computer and use it in GitHub Desktop.
Create 'long exposure' images from a video file.
# This script takes a video's individual frames and overlays them to produce a composite "long exposure" image.
#
# Usage: python expose.py video.mp4 width height
#
# IMPORTANT: you'll need to edit this script to set the value of 'alpha' appropriate for your video's length and desired brightness.
from __future__ import division
import subprocess as sp
import numpy
from PIL import Image, ImageDraw
import re
import time
import sys
# Alpha is the opacity of each frame. I've had success with values of 5/n to 36/n, where n
# is the number of frames in your video. You'll have to experiment. Sample values:
# 72 frame video: 0.069 (5/72)
# 12286 frame video: 0.0003 (36/12286)
alpha = 0.01
print("Default value for alpha: {}".format(alpha))
# Command you use to launch ffmpeg. In Windows you might need to use FFMPEG_BIN="ffmpeg.exe"; Linux/OSX should be OK.
FFMPEG_BIN = "ffmpeg"
# Timestamp so you can see how long it took
start_time = "Script started at " + time.strftime("%H:%M:%S")
print(start_time)
# optional starting time hh:mm:ss.ff; default value set to 00:00:00.0
hh = "%02d" % (0,)
mm = ":%02d" % (0,)
ss = ":%02d" % (0,)
ff = ".0"
print "Timestamp for first frame: "+hh+mm+ss+ff
# input file (first argument)
filename = str(sys.argv[1])
width = int(sys.argv[2])
height = int(sys.argv[3])
# output image file (same as input file, with non-alphanums stripped):
outfilename = re.sub(r'\W+', '', filename) + ".png"
print("Filename: {}".format(filename))
print("Dimensions: {},{}".format(width,height))
###
### This section: credit to http://zulko.github.io/blog/2013/09/27/read-and-write-video-frames-in-python-using-ffmpeg/
# Open the video file.
command = [ FFMPEG_BIN,
'-threads', '4',
'-ss', hh+mm+ss,
'-i', filename,
'-f', 'image2pipe',
# '-filter:v', 'setpts=PTS/8', # optional; take every 8th frame; offers some performance improvement
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo', '-']
pipe = sp.Popen(command, stdout = sp.PIPE, bufsize=10**8)
# create a numpy array from the first video frame; we'll add subsequent frames on top
frame = numpy.fromstring(pipe.stdout.read(width*height*3), dtype='uint8').reshape((height,width,3)) * alpha
# x is optional; you might want to use it to break the 'while' loop in testing
x=0
# stack more frames on top
while pipe.stdout.read(width*height*3):
try:
# read the next frame from the pipe
nextframe = numpy.fromstring(pipe.stdout.read(width*height*3), dtype='uint8').reshape((height,width,3)) * alpha
# add the current frame to the composite frame; doing it this way prevents 8-bit values rolling over from 255 to 0
# (from http://stackoverflow.com/questions/29611185/avoid-overflow-when-adding-numpy-arrays)
nextframe = 255 - nextframe
numpy.putmask(frame, nextframe < frame, nextframe)
frame += 255 - nextframe
except:
print("No more frames to process (or error occurred). Number of frames processed:", x)
x += 1
frame = frame.astype('uint8') # convert frame pixel values to correct dtype before saving
im = Image.fromarray(frame)
im.save("output.png")
print(start_time)
print("Script finished at {}".format(time.strftime("%H:%M:%S")))
print("Total frames processed: {}".format(x))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment