From 5da6cc9a96100d476ec0819950eb01da43e8a3ea Mon Sep 17 00:00:00 2001 From: "mur.at ML group" Date: Tue, 3 Mar 2020 13:39:52 +0100 Subject: [PATCH 1/5] more useful default settings and eleminating the cue identifierers * the presence of the 'sips' executable is now autodetected to let it work out of the box on non-mac machines * TIMESYNC_ADJUST isn't neccessary on more recent ffmpeg executables. if not set to -1 the time stamps will not start with 0:00.000, which is not accepted by some applicationes (see: https://github.com/matvp91/indigo-player/issues/56) * explicit cue identifierers in the output don't make much sense and also tend to be incompatible. (e.g. in case of indigo-player) --- sprites/makesprites.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sprites/makesprites.py b/sprites/makesprites.py index 35b8457..2848fed 100644 --- a/sprites/makesprites.py +++ b/sprites/makesprites.py @@ -7,6 +7,7 @@ import math import glob import pipes +from distutils.spawn import find_executable from dateutil import relativedelta ################################## # Generate tooltip thumbnail images & corresponding WebVTT file for a video (e.g MP4). @@ -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 @@ -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 From ba0f9359b8af0c4bea19cb34ab1c9bee619da389 Mon Sep 17 00:00:00 2001 From: "mur.at ML group" Date: Tue, 3 Mar 2020 13:58:55 +0100 Subject: [PATCH 2/5] python3 compatibility --- sprites/makesprites.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sprites/makesprites.py b/sprites/makesprites.py index 2848fed..cce35bf 100644 --- a/sprites/makesprites.py +++ b/sprites/makesprites.py @@ -81,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 @@ -92,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 ? @@ -271,7 +271,7 @@ def addLogging(): 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 + print("Writing log to: %s" % LOG_FILENAME) if not os.path.exists('logs'): os.makedirs('logs') logger.setLevel(logging.DEBUG) From d630f183bd7c5a013ef560e1ff07d8e7a62568e6 Mon Sep 17 00:00:00 2001 From: "mur.at ML group" Date: Tue, 3 Mar 2020 16:23:24 +0100 Subject: [PATCH 3/5] argparser and eliminating dateutil dependency --- sprites/makesprites.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/sprites/makesprites.py b/sprites/makesprites.py index cce35bf..91d8212 100644 --- a/sprites/makesprites.py +++ b/sprites/makesprites.py @@ -7,8 +7,8 @@ import math import glob import pipes +import argparse from distutils.spawn import find_executable -from dateutil import relativedelta ################################## # 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. @@ -202,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""" @@ -284,10 +286,21 @@ def addLogging(): 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('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 + videofile = args.videofile + task = SpriteTask(videofile) run(task) From 0bfaf51b501772d392923d08f1361a183d4389dd Mon Sep 17 00:00:00 2001 From: "mur.at ML group" Date: Tue, 3 Mar 2020 17:20:00 +0100 Subject: [PATCH 4/5] cleanup -- i.e. remove individual thumbs --- sprites/makesprites.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sprites/makesprites.py b/sprites/makesprites.py index 91d8212..6e943ea 100644 --- a/sprites/makesprites.py +++ b/sprites/makesprites.py @@ -224,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: @@ -263,6 +268,7 @@ 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) From e18e3d98cd1db9606ac59d95db7abdd2c0b7443b Mon Sep 17 00:00:00 2001 From: "mur.at ML group" Date: Tue, 3 Mar 2020 18:13:02 +0100 Subject: [PATCH 5/5] less verbose output resp. log handling --- sprites/makesprites.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/sprites/makesprites.py b/sprites/makesprites.py index 6e943ea..666caf3 100644 --- a/sprites/makesprites.py +++ b/sprites/makesprites.py @@ -276,18 +276,20 @@ def run(task, thumbRate=None): 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 @@ -299,6 +301,8 @@ def addLogging(): 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() @@ -306,6 +310,7 @@ def addLogging(): 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)