Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 50 additions & 25 deletions sprites/makesprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import math
import glob
import pipes
from dateutil import relativedelta
import argparse
from distutils.spawn import find_executable
##################################
# Generate tooltip thumbnail images & corresponding WebVTT file for a video (e.g MP4).
# Final product is one *_sprite.jpg file and one *_thumbs.vtt file.
Expand All @@ -24,15 +25,15 @@

#TODO determine optimal number of images/segment distance based on length of video? (so longer videos don't have huge sprites)

USE_SIPS = True #True to use sips if using MacOSX (creates slightly smaller sprites), else set to False to use ImageMagick
THUMB_RATE_SECONDS=45 # every Nth second take a snapshot
THUMB_WIDTH=100 #100-150 is width recommended by JWPlayer; I like smaller files
USE_SIPS = bool(find_executable('sips')) #True to use sips if using MacOSX (creates slightly smaller sprites), else set to False to use ImageMagick
THUMB_RATE_SECONDS=10 # every Nth second take a snapshot
THUMB_WIDTH=320 #100-150 is width recommended by JWPlayer; I like smaller files
SKIP_FIRST=True #True to skip a thumbnail of second 1; often not a useful image, plus JWPlayer doesn't seem to show it anyway, and user knows beginning without needing preview
SPRITE_NAME = "sprite.jpg" #jpg is much smaller than png, so using jpg
VTTFILE_NAME = "thumbs.vtt"
THUMB_OUTDIR = "thumbs"
USE_UNIQUE_OUTDIR = False #true to make a unique timestamped output dir each time, else False to overwrite/replace existing outdir
TIMESYNC_ADJUST = -.5 #set to 1 to not adjust time (gets multiplied by thumbRate); On my machine,ffmpeg snapshots show earlier images than expected timestamp by about 1/2 the thumbRate (for one vid, 10s thumbrate->images were 6s earlier than expected;45->22s early,90->44 sec early)
TIMESYNC_ADJUST = -1 #set to -1 to not adjust time (gets multiplied by thumbRate); On my machine,ffmpeg snapshots show earlier images than expected timestamp by about 1/2 the thumbRate (for one vid, 10s thumbrate->images were 6s earlier than expected;45->22s early,90->44 sec early)
logger = logging.getLogger(sys.argv[0])
logSetup=False

Expand Down Expand Up @@ -80,7 +81,7 @@ def makeOutDir(videofile):
elif os.path.exists(newoutdir) and not USE_UNIQUE_OUTDIR:
#remove previous contents if reusing outdir
files = os.listdir(newoutdir)
print "Removing previous contents of output directory: %s" % newoutdir
print("Removing previous contents of output directory: %s" % newoutdir)
for f in files:
os.unlink(os.path.join(newoutdir,f))
return newoutdir
Expand All @@ -91,7 +92,7 @@ def doCmd(cmd,logger=logger): #execute a shell command and return/print its out
output = None
try:
output = subprocess.check_output(args, stderr=subprocess.STDOUT) #pipe stderr into stdout
except Exception, e:
except Exception as e:
ret = "ERROR [%s] An exception occurred\n%s\n%s" % (datetime.datetime.now(),output,str(e))
logger.error(ret)
raise e #todo ?
Expand Down Expand Up @@ -187,7 +188,7 @@ def makevtt(spritefile,numsegments,coords,gridsize,writefile,thumbRate=None):
end = get_time_str(clipend,adjust=adjust)
clipstart = clipend
clipend += thumbRate
vtt.append("Img %d" % imgnum)
# vtt.append("Img %d" % imgnum)
vtt.append("%s --> %s" % (start,end)) #00:00.000 --> 00:05.000
vtt.append("%s#xywh=%s" % (basefile,xywh))
vtt.append("") #Linebreak
Expand All @@ -201,8 +202,10 @@ def get_time_str(numseconds,adjust=None):
seconds = max(numseconds + adjust, 0) #don't go below 0! can't have a negative timestamp
else:
seconds = numseconds
delta = relativedelta.relativedelta(seconds=seconds)
return "%02d:%02d:%02d.000" % (delta.hours,delta.minutes, delta.seconds)
h = seconds // 3600
m = (seconds - h*3600) // 60
s = (seconds - h*3600 - m*60)
return "%02d:%02d:%02d.000" % (h, m, s)

def get_grid_coordinates(imgnum,gridsize,w,h):
""" given an image number in our sprite, map the coordinates to it in X,Y,W,H format"""
Expand All @@ -221,6 +224,11 @@ def makesprite(outdir,spritefile,coords,gridsize):
cmd = "montage %s/tv*.jpg -tile %s -geometry %s %s" % (pipes.quote(outdir), grid, coords, pipes.quote(spritefile))#if video had more than 144 thumbs, would need to be bigger grid, making it big to cover all our case
doCmd(cmd)

def cleanup(outdir):
"remove the individual thumbs"
cmd = ("rm %s" % ' '.join(map(lambda x: ('"%s"'%x), get_thumb_images(outdir))))
doCmd(cmd)

def writevtt(vttfile,contents):
""" output VTT file """
with open(vttfile,mode="w") as h:
Expand Down Expand Up @@ -260,33 +268,50 @@ def run(task, thumbRate=None):

#convert small files into a single sprite grid
makesprite(outdir,spritefile,coords,gridsize)
cleanup(outdir)

#generate a vtt with coordinates to each image in sprite
makevtt(spritefile,numfiles,coords,gridsize,task.getVTTFile(), thumbRate=thumbRate)

def addLogging():
global logSetup
if not logSetup:
basescript = os.path.splitext(os.path.basename(sys.argv[0]))[0]
LOG_FILENAME = 'logs/%s.%s.log'% (basescript,datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) #new log per job so we can run this program concurrently
#CONSOLE AND FILE LOGGING
print "Writing log to: %s" % LOG_FILENAME
if not os.path.exists('logs'):
os.makedirs('logs')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(LOG_FILENAME)
logger.addHandler(handler)
if LOG_FILENAME:
#FILE LOGGING
print("Writing log to: %s" % LOG_FILENAME)
logs_dir = os.path.split(LOG_FILENAME)[0]
if logs_dir and not os.path.exists(logs_dir):
os.makedirs(logs_dir)
handler = logging.FileHandler(LOG_FILENAME)
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
#CONSOLE LOGGING
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setLevel(logging.WARNING)
logger.addHandler(ch)
logger.setLevel(logging.DEBUG)
logSetup = True #set flag so we don't reset log in same batch


if __name__ == "__main__":
if not len(sys.argv) > 1 :
sys.exit("Please pass the full path or url to the video file for which to create thumbnails.")
if len(sys.argv) == 3:
THUMB_OUTDIR = sys.argv[2]
videofile = sys.argv[1]

parser = argparse.ArgumentParser(description='generate sprites.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-r','--thumb_rate', help='every Nth second take a snapshot.',
default=THUMB_RATE_SECONDS, type=int)
parser.add_argument('-w', '--width', help='width of thum images.',
default=THUMB_WIDTH, type=int)
parser.add_argument('--log_file', help="path to verbose log file.",
default="")
parser.add_argument('videofile', help='full path or url to the video file for which to create thumbnails.')
parser.add_argument('out_dir', help='output directory.', nargs='?', default=THUMB_OUTDIR)
args = parser.parse_args()

THUMB_RATE_SECONDS = args.thumb_rate
THUMB_WIDTH = args.width
THUMB_OUTDIR = args.out_dir
LOG_FILENAME = args.log_file
videofile = args.videofile

task = SpriteTask(videofile)
run(task)