Skip to content

Instantly share code, notes, and snippets.

@prohulaelk
Created February 27, 2016 17:57
Show Gist options
  • Save prohulaelk/ffe3444e3bf953548d1c to your computer and use it in GitHub Desktop.
Save prohulaelk/ffe3444e3bf953548d1c to your computer and use it in GitHub Desktop.
#! /usr/bin/python3
# create a gif from a video using ffmpeg and imagemagick.
# both ffmpeg and imagemagick must be installed and added to the PATH env variable for this to work.
# if I were being fancier, I'd make this take command line arguments instead of just setting
# variables inside the `if __name__ == '__main__'` block, but it's intended for personal use only
# so whatever.
import os
import subprocess
import shutil
import re
def get_pngs(folder=None):
# return a list of .png files in a folder, sorted by filename.
if not folder:
folder = os.getcwd()
files = sorted([
os.path.join(folder, f)
for f in os.listdir(folder)
if os.path.isfile(os.path.join(folder, f))
and os.path.splitext(f)[1].lower() == '.png'
])
return files
def export_img_seq(filepath, tempdir, start, end):
# export an image sequence from a video using ffmpeg. `start` and `end` must be time strings
# as supported by ffmpeg, eg. '01:20:30' for 1h 20m and 30s into the video
ffmargs = [
'ffmpeg',
'-i', filepath,
'-ss', start,
'-to', end,
'-f', 'image2',
os.path.join(tempdir, '%04d.png')
]
print(' '.join(ffmargs))
p = subprocess.Popen(ffmargs)
p.wait()
def gif(seq_dir, outfile, delay=4, resize='100%x100%'):
# convert an image sequence into a gif using imagemagick.
# image sequence is assumed to be ordered pngs from export_img_seq(); other image sets might do
# weird things.
# seq_dir and outfile should be self-explanatory. Delay is in 1/100ths of a second,
# so delay=4 = 40ms/frame = 25fps.
# resize can be in pixelsxpixels or percent%xpercent% format
gif_args = [
'convert',
'-delay', str(delay),
'-loop', '0', # loop infinitely
'-resize', resize,
# ordered-dither setting here is kind of magic; it tries to guess a good colour map to use.
# It seems to work pretty well; I don't recommend messing with it too much.
'-ordered-dither', 'o8x8,23', '+map',
os.path.join(seq_dir, '*.png'),
outfile
]
print(' '.join(gif_args))
p = subprocess.Popen(gif_args)
p.wait()
def text_over(
seq_dir,
start_frame,
end_frame,
text,
gravity,
font='Helvetica-Bold',
pointsize='200',
offset='0,0'
):
# overlay text on a set of images inside seq_dir. Again, this sequence is assumed to have come
# from export_img_seq() so I make no guarantees if your sequence did not.
# 'gravity' is imagemagick's setting for text alignment. See their documentation for valid vals.
imgmgk_args = [
'convert',
'', # placeholder for input file
'-fill', 'white',
'-font', font,
'-gravity', gravity,
'-pointsize', str(pointsize),
'-draw', "text {} '{}' ".format(offset, text),
'' # placeholder for output file
]
images = get_pngs(seq_dir)
for i in images[start_frame:end_frame]:
i_args = imgmgk_args[:]
i_args[1] = i # set input file
i_args[-1] = i # set output file
print(' '.join(i_args))
p = subprocess.Popen(i_args) # run the command
p.wait() # wait for imagemagick to finish
def find_timeframe(srt_file, phrase, index=0):
# returns a (start, end) tuple of ffmpeg-compatible times based on the index'th instance of
# a phrase in the srt_file.
srtTime = r'(\d{2}:\d{2}:\d{2}),\d{3}'
reSearch = re.compile(
r'\d\S*\n{} --> {}\S*\n.*{}'.format(srtTime, srtTime, phrase),
re.MULTILINE + re.IGNORECASE
)
matches = reSearch.findall(open(srt_file).read())
if len(matches) == 0:
print('No lines found in subtitle file for {}'.format(phrase))
elif len(matches) == 1:
print('Found one match for "{}" in subtitle file'.format(phrase))
return matches[0]
else:
print('Found {} matches for "{}" in subtitle file:'.format(len(matches), phrase))
for m in matches:
print(m)
print('using match number {}'.format(index+1))
return matches[index]
quit() # quit if we didn't return a valid match
if __name__ == '__main__':
# input file
filepath = '/data/media/videos/movies/James.Bond.GoldenEye.1995.mp4'
filename = os.path.split(filepath)[-1]
# output directories
tempdir = '/data/pictures/temp'
outdir = '/data/pictures/other/homemade_gifs'
prepend = '01' # use this value to set unique names for multiple gifs from the same source file
outfile = os.path.join(outdir, os.path.splitext(filename)[0] + prepend + '.gif')
if not os.path.exists(outdir):
os.path.makedirs(outdir)
tempdir = os.path.join(tempdir, filename, prepend)
if not os.path.exists(tempdir):
os.makedirs(tempdir)
# start and end timestamps to extract. This will be compiled into a regex, so you can enter
# a regular expressions to search. Normal text searches should also work fine, since subtitles
# shouldn't contain anything that would get interpreted oddly as a regex.
# You could also manually set start and end as ffmpeg-compatible timestamps (eg. 00:03:05)
start, end = find_timeframe(
'/data/media/videos/movies/James.Bond.GoldenEye.1995.srt',
'Yes! I am invincible.',
index=0
)
# now make an image sequence from the video
export_img_seq(filepath, tempdir, start, end)
# add text to images in the sequence. The first frame of the gif will be 0.
# I usually set this up after making a textless gif first. Maybe in the future I'll try making
# this occur automatically based off the subtitles.
text_over(tempdir, 0, 21, 'YES!', 'West', pointsize='300', offset='80,0')
text_over(tempdir, 21, 30, 'I', 'South', pointsize='225', offset='0,80')
text_over(tempdir, 30, 36, 'AM', 'South', pointsize='225', offset='0,80')
text_over(tempdir, 36, 64, 'INVINCIBLE!', 'South', pointsize='225', offset='0,80')
# now smoosh the sequence back together into a gif
gif(tempdir, outfile, resize='50%x50%', delay=4)
print('successfully created {}'.format(outfile))
# cleanup, if required
shutil.rmtree(tempdir)
# and now you've got the final gif - for example:
# http://gfycat.com/UnlawfulImpishBluebottle
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment