Created
February 27, 2016 17:57
-
-
Save prohulaelk/ffe3444e3bf953548d1c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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