#!/usr/bin/env python """ picfill.py Fills a scene with pictures. Requires ImageMagick 6.2.0+ (GraphicsMagick might also work), Python 2.4.0 or greater, and picinpic.py. Meant as the back-end to the LiVES plugin picfill.script, but can be used as a stand-alone program. Copyright (C) 2005 Marco De la Cruz (marco@reimeika.ca) It's a trivial program, but might as well GPL it, so see: http://www.gnu.org/copyleft/gpl.html for license. See my vids at http://amv.reimeika.ca ! """ # Change this if you want to use a different temp dir, # otherwise use current dir. tempdir='.' # No need to change anything below this. version = '0.0.3' picinpic = 'picinpic.py' identify = 'identify' convert = 'convert' usage = \ """ picfill.py -h picfill.py -V picfill.py [-v|-q] [-m method] [-x] [-e ext] [-C geometry] -k keyframe [-K [-c geometry [-o]]] -f firstframe -F lastframe|-n matrixsize [fill sequence] """ help = \ """ SUMMARY (ver. %s): Fills a scene with sub-images originating from a single picture or a whole scene. OPTIONS: -v be verbose. -q be quiet. -m method composition (overlay) method. Try "picinpic.py -h" for details. Default is 'over'. -x overwrite the original frames. -e ext set a different extension for the resulting file names. Note that the image format does not change. -n integer will fill n^2 frame sequqnce with n^2 sub-images. Alternatively once can specify the value of the last frame, see "-F" below. fill sequence order in which to fill the sub-image matrix e.g. 1,5,2,3,7,6,9,4,8. Numbering is row-wise as follows: .-----------------. | | | | | 1 | 2 | 3 | | | | | |-----------------| | | | | | 4 | 5 | 6 | | | | | |-----------------| | | | | | 7 | 8 | 9 | | | | | `-----------------` Default is 1,2,3,4,5,6,7,8,9...n^2 -k file first key frame (e.g. 00000003.png). -K if this option is selected then frames M (the first key frame) to M+n^2-1 will be used as the n^2 sub-images which will fill the target scene (which must be n^2-frames long). If this options is not specified then the key frame M is split into n^2 rectangles and then assembled over the target scene (which again must be n^2-frames long). Of course, if "-K" is specified then frame M+n^2-1 must exist). -c geometry if "-K" then crop the key frames to 'geometry' (e.g. 704x480+8+0) before resizing and inserting. Must be of the form WxH+X+Y. -C geometry geometry of the frame area which is to be filled. If "-K" is not specified then this will also be the value of "-c". Default is the whole image. Must be of the form WxH+X+Y. -o Setting "-c" may result in the final image not being completely filled. Setting this flag will err on the side of overfilling the final image. -f file first frame (say, N, in which case frame N+n^2-1 must exist). -F file instead on "-n" one can specify the frame at N+n^2-1. Frame filenames are assumed to be in numerical order and of constant length with the same image file extension e.g. 000345.png to 002612.png) in the same directory. The number of key frames must be either one or n^2. The number of frames to be filled is n^2. If no options are given then over composition is used, and the frames are filled in row-wise order from left to right and top to bottom. Resulting frames are named "pf_frame" (original frames are retained). Use "-h" to get help, "-v" for verbose output, and "-q" for no output. Use "-V" to show the version number and exit. Setting "-x" will overwrite the original frame (unless "-e" is used, see below), otherwise the new frames will be named "pf_frame" If you don't overwrite you can always do it later using e.g.: rename pf_0 0 pf_0* With "-e" it is possible to set a different extension for the resulting file name. For example, using "-e new" will create, say: pf_00001000.new Alternatively, you can also use it in combination with "-x" to only change the extension. Note, however, that "-e" will not change the image format, i.e. using "-x -e jpg" on 00001000.png would generate a composed frame called: 00001000.jpg but it will still be a PNG file. EXAMPLES: Fill frames 4990 to 4998 (n^2 = 9) with a scene starting at frame 10 and ending on frame 18 in such a way that the fill starts on the top-right corner and ends in the center progressing in a counter-clockwise fashion: picfill.py -n 3 -k 0010.png -f 4990.png -K -c '336x224+6+6' \ -C '336x224+6+6' 3,6,9,8,7,4,1,2,5 The "-c" will confine the area of the target frames to '336x224+6+6', while the "-C" specifies the geometry of the key frames before insertion. Of course, all frames between '0010.png' and '0010.png' should exist, as well as frames '4990.png' to '4998.png' (an error will be raised otherwise). Setting "-F 4998.png" (instead of "-n 3") in the above example will produce the same result. The above will create 'pf_4990.png'... 'pf_4998.png'. To over write the originals ('4990.png'...) use "-x". To change the extension of the PNG files ('4990.new'...) also add "-e new". Without the "-K" flag above the procedure will remain the same, except for the fact that the key frame '0010.png' will be split into nine quadrants which will be used to fill the scene. """ % version def which(command): """ Finds (or not) a command a la "which" """ command_found = False if command[0] == '/': if os.path.isfile(command) and \ os.access(command, os.X_OK): command_found = True abs_command = command else: path = os.environ.get('PATH', '').split(os.pathsep) for dir in path: abs_command = os.path.join(dir, command) if os.path.isfile(abs_command) and \ os.access(abs_command, os.X_OK): command_found = True break if not command_found: abs_command = '' return abs_command def is_installed(prog): """ See whether "prog" is installed """ wprog = which(prog) if wprog == '': print prog + ': command not found' raise SystemExit else: if verbose: print wprog + ': found' if __name__ == '__main__': import os import sys import getopt import tempfile import shutil import math import glob try: if sys.version_info[0:3] < (2, 4, 0): raise SystemExit except: print 'You need Python 2.4.0 or greater to run me!' raise SystemExit try: (opts, args) = getopt.getopt(sys.argv[1:], \ 'hVqvm:xe:n:k:Kf:c:oC:F:') except: print "Something's wrong. Try the '-h' flag." raise SystemExit opts = dict(opts) if '-v' in opts: verbose = True else: verbose = False if '-q' in opts: quiet = True verbose = False else: quiet = False if not opts: print usage raise SystemExit if '-h' in opts: print usage + help raise SystemExit if '-V' in opts: print 'picfill.py version ' + version raise SystemExit for i in [picinpic, identify, convert]: is_installed(i) firstkeyframe = opts.get('-k', False) firstframe = opts.get('-f', False) if not firstkeyframe or not firstframe: print 'You need to specify a key frame and an initial scene frame.' raise SystemExit if not os.path.isfile(firstkeyframe) or \ not os.path.isfile(firstframe): print firstkeyframe + ' and/or ' + firstframe \ + ' do not exist!' raise SystemExit splitkeyframe = True if '-K' in opts: splitkeyframe = False framedir = os.path.dirname(firstkeyframe) first_keyframe_num = os.path.basename(firstkeyframe).split('.')[0] first_frame_num = os.path.basename(firstframe).split('.')[0] imgext = '.' + os.path.basename(firstframe).split('.')[1] numberlength = len(first_frame_num) matsiz = opts.get('-n', False) lastframe = opts.get('-F', False) if matsiz: try: matsiz = int(matsiz) if matsiz < 1: raise ValueError except: print 'The size of the sub-image matrix must be an integer > 0.' raise SystemExit last_frame_num = str(int(first_frame_num) + matsiz**2 - 1) elif lastframe: if not os.path.isfile(lastframe): print lastframe + ' does not exist!' raise SystemExit last_frame_num = os.path.basename(lastframe).split('.')[0] totframes = int(last_frame_num) - int(first_frame_num) + 1 matsiz = math.sqrt(totframes) if abs(matsiz - int(matsiz)) > 0.00000001: print 'Number of frames (%s) is not equal to n^2.' % totframes raise SystemExit else: matsiz = int(matsiz) else: print 'You must specify either "-n" or "-F".' raise SystemExit if not splitkeyframe: last_keyframe_num = str(int(first_keyframe_num) + matsiz**2 - 1) else: last_keyframe_num = first_keyframe_num if not os.path.isfile(os.path.join(framedir, \ last_frame_num.zfill(numberlength) + \ imgext)): print 'There are not enough frames after firstframe ' + \ 'to complete the sequence (you need %s frames).' % matsiz**2 raise SystemExit if not os.path.isfile(os.path.join(framedir, \ last_keyframe_num.zfill(numberlength) + \ imgext)): print 'There are not enough frames after keyframe ' + \ 'to complete the sequence (you need %s frames).' % matsiz**2 raise SystemExit if len(args) == 1: try: fillsequence = [int(i) for i in args[0].split(',')] except: print 'Invalid fill sequence.' raise SystemExit else: fillsequence = range(1, matsiz**2 + 1) if sorted(fillsequence) != range(1, matsiz**2 + 1): print 'Bad fill sequence.' raise SystemExit # Defaults: method = opts.get('-m', 'over') fillgeom = opts.get('-C', False) cropgeom = opts.get('-c', False) iden = identify + ' -format "%w %h" ' + firstframe geom = os.popen(iden).read().strip().split() [xg0, yg0] = [0, 0] [xgl, ygl] = [int(geom[0]), int(geom[1])] if fillgeom: try: [x0, y0] = [int(i) for i in fillgeom.split('+')[1:]] [xl, yl] = [int(i) for i in fillgeom.split('+')[0].split('x')] if '-' in fillgeom: raise ValueError [xred, yred] = [xl/float(xgl), yl/float(ygl)] except: print 'Invalid -C geometry (must be WxH+X+Y).' raise SystemExit if splitkeyframe: if not quiet: print 'Warning: -K not specified, setting -c to -C.' cropgeom = fillgeom else: [x0, y0] = [xg0, yg0] [xl, yl] = [xgl, ygl] [xred, yred] = [1,1] if cropgeom and splitkeyframe: if not fillgeom: if not quiet: print 'Warning: -K not specified, ignoring -c.' cropgeom = False if cropgeom: cropc = ' '.join(['-crop', cropgeom]) else: cropc = '' if cropgeom: try: [kx0, ky0] = [int(i) for i in cropgeom.split('+')[1:]] [kxl, kyl] = [int(i) for i in cropgeom.split('+')[0].split('x')] except: print 'Invalid -c geometry (must be WxH+X+Y).' raise SystemExit else: [kx0, ky0] = [x0, y0] [kxl, kyl] = [xl, yl] pre = 'pf_' newext = opts.get('-e', False) if newext: newext = '.' + newext if method not in ['over', 'in', 'out', 'atop', 'xor', 'plus', \ 'minus', 'add', 'subtract', 'difference', \ 'bumpmap', 'replace', 'blend', 'displace', \ 'dst_in', 'dst_out', 'dst_atop', 'exclusion', \ 'multiply', 'screen', 'overlay']: print 'Unknown compose method: ' + method raise SystemExit if '-x' in opts: pre = '' if not quiet: print 'picfill.py version ' + version + \ '\nFilling using "' + method + '" method...' # Origin positions (row-wise) Opos = [(i,j) for j in range(y0, y0+yl, int(math.ceil(yl/float(matsiz)))) \ for i in range(x0, x0+xl, int(math.ceil(xl/float(matsiz))))] Opos.insert(0,'dummy') # since we start counting at 1 # Subframe size pipcrop = '' if not splitkeyframe: if cropgeom: pipcrop = ' '.join(['-c', cropgeom]) if '-o' in opts: kresize = '-s %s%%' % ((100*((kxl*kyl)/float(xl*yl)))/matsiz) else: kresize = '-s %s%%' % ((100*((kxl*kyl)/float(max(xred,yred)*xgl*ygl)))/matsiz) else: if '-o' in opts: splitsize = '%sx%s' % (round(kxl/matsiz)+1, round(kyl/matsiz)+1) else: splitsize = '%sx%s' % (round(kxl/matsiz), round(kyl/matsiz)) kresize = '-s 100%' # First create the insertable key frame sequence in a temp dir kframe = 0 ikframe = 1 tkeydir = tempfile.mkdtemp('', 'picfill_ktempdir_', \ os.path.abspath(tempdir)) os.chmod(tkeydir, 0755) for idx in fillsequence: pos = Opos[idx] kpos = '%s+%s' % (pos[0], pos[1]) rt_kfname = str(int(first_keyframe_num) + kframe).zfill(numberlength) kfname = rt_kfname + imgext fp_kfname = os.path.join(framedir, kfname) rt_ikfname = str(ikframe).zfill(numberlength) ikfname = rt_ikfname + imgext fp_ikfname = os.path.join(tkeydir, ikfname) if splitkeyframe: sgeom = '+'.join([splitsize, kpos]) dosplit = ' '.join([convert, \ cropc, \ fp_kfname, \ '-crop', sgeom, \ fp_ikfname]) if verbose: print dosplit os.system(dosplit) else: if verbose: print fp_kfname + ' => ' + fp_ikfname shutil.copy(fp_kfname, fp_ikfname) kframe+=1 ikframe+=1 # Compose insertable key frame plus target frame idx = 0 cumfillseq = [] tfdir = tempfile.mkdtemp('', 'picfill_ftempdir_', \ os.path.abspath(tempdir)) os.chmod(tfdir, 0755) for iframe in range(int(first_frame_num), \ int(last_frame_num) + 1): cumfillseq.append(fillsequence[idx]) ikframe = 1 for jdx in cumfillseq: pos = Opos[jdx] kpos = '%s+%s' % (pos[0], pos[1]) rt_fname = str(iframe).zfill(numberlength) fname = rt_fname + imgext fp_fname = os.path.join(framedir, fname) tfp_fname = os.path.join(tfdir, fname) # Only copy the original frame for the first insert, # will then add sub-images (since we use "-x" below). if ikframe == 1: if verbose: print fp_fname + ' => ' + tfp_fname shutil.copy(fp_fname, tfp_fname) rt_kfname = str(ikframe).zfill(numberlength) kfname = rt_kfname + imgext fp_kfname = os.path.join(tkeydir, kfname) kinsert = ' '.join([picinpic, '-q', \ '-x', \ '-m', method, \ kresize, \ '-p', kpos, \ pipcrop, \ '-k', fp_kfname, \ '-f', tfp_fname]) if verbose: print kinsert os.system(kinsert) ikframe+=1 if newext: fp_prefname = tempfile.mkstemp(imgext, pre, tempdir)[1] else: fp_prefname = os.path.join(framedir, pre + fname) if verbose: print tfp_fname + ' -> ' + fp_prefname shutil.move(tfp_fname, fp_prefname) if newext: fp_prefname_e = \ os.path.join(framedir, pre + rt_fname + newext) shutil.move(fp_prefname, fp_prefname_e) if verbose: print fp_prefname + ' -> ' + fp_prefname_e idx+=1 if not quiet: print 'Deleting temp files...' for f in glob.glob(tkeydir + '/*' + imgext): os.remove(f) for f in glob.glob(tfdir + '/*' + imgext): os.remove(f) os.rmdir(tkeydir) os.rmdir(tfdir) if not quiet: print 'Done!' """ CHANGELOG: 27 Mar 2005 : 0.0.1 : first release. 11 Apr 2005 : 0.0.2 : rounded split sub-image size. added overfill preference to split. 13 Aug 2005 : 0.0.3 : better check for bad geometry. """