#!/usr/bin/env python """ picinpic.py Inserts a floating picture (or scene) within another scene. Requires ImageMagick 6.2.0+ (GraphicsMagick might also work) and Python 2.3.0 or greater. Meant as the back-end to the LiVES plugin sceneinscene.script, but can be used as a stand-alone program. For examples of the compose methods see: http://www.cit.gu.edu.au/~anthony/graphics/imagick6/compose/tables/ 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.8' convert = 'convert' resize = '-resize' geometry = '-geometry' compose = '-compose' composite = '-composite' crop = '-crop' d8 = '-depth 8' usage = \ """ picinpic.py -h picinpic.py -V picinpic.py [-v|-q] [-m method] [-s initialsize [-S finalsize]] [-p initialposition [-P finalposition]] [-x] [-e ext] [-c geometry] -k firstkeyframe [-K lastkeyframe] [-l] -f firstframe [-F lastframe] """ help = \ """ SUMMARY (ver. %s): Inserts a picture (or a scene) within another picture (or scene of the same length). OPTIONS: -v be verbose. -q be quiet. -m method composition (overlay) method. May be one of the following: over in out atop xor plus minus add subtract difference bumpmap replace blend displace dst_in dst_out dst_atop exclusion multiply screen overlay Default is 'over'. -s size initial sub-image size (e.g. 10%%). Defaults to 20%%. -S size final sub-image size. Defaults to initial sub-image size. -p position initial sub-image position (top left corner). Must be of the form 'X+Y'. Defaults to '0+0'. -P position final sub-image position. Defaults to initial sub-image position. -x overwrite the original frames. -e ext set a different extension for the resulting file names. Note that the image format does not change. -c geometry crop the keyframes to 'geometry' (e.g. 100x50+10+10) before inserting (precedes resizing). Must be of the form WxH+X+Y. -l limit displacement so that the image never moves beyond the final position. -k file first key frame (e.g. 00000003.png). -K file final key frame. Defaults to first key frame. -f file first frame. -F file last frame. Defaults to first frame. 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), but key frames do not necessarily have to be in the same directory as the target frames. The number of key frames must be either one or match the number of frames (into which they will be placed). If no options are given then over composition is used, and sets the sub-frame size to 20%% at '0+0'. Resulting frames are named "pip_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 "pip_frame" If you don't overwrite you can always do it later using e.g.: rename pip_0 0 pip_0* With "-e" it is possible to set a different extension for the resulting file name. For example, using "-e new" will create, say: pip_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: Insert the scene shown in frames 1 to 10 into frames 11 to 20 in such a way that it starts at the top left corner and moves towards the center while becoming smaller: picinpic.py -s 20%% -S 5%% -p 0+0 -P 100+100 \ -k 001.png -K 010.png -f 011.png -F 020.png The above will create 'pip_011.png'... 'pip_020.png'. To over write the originals ('011.png'...) use "-x". To change the extension of the PNG files ('011.new'...) also add "-e new". """ % version if __name__ == '__main__': import os import sys import getopt import tempfile import shutil import math try: if sys.version_info[0:3] < (2, 3, 0): raise SystemExit except: print 'You need Python 2.3.0 or greater to run me!' raise SystemExit (std_in, std_out_err) = os.popen4(convert) std_in.close() status = std_out_err.read() if not ('ImageMagick' or 'GraphicsMagick') in status.split(): print '(Image|Graphics)Magick\'s "convert" command does ' + \ 'not seem to work...' print 'I get: ' + status.strip() raise SystemExit try: (opts, args) = getopt.getopt(sys.argv[1:], \ 'hVqvm:s:S:p:P:xe:k:K:f:F:c:l') 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 # verbose = True if not opts: print usage raise SystemExit if '-h' in opts: print usage + help raise SystemExit if '-V' in opts: print 'picinpic.py version ' + version raise SystemExit if '-l' in opts: confine = True else: confine = False firstkeyframe = opts.get('-k', False) lastkeyframe = opts.get('-K', firstkeyframe) firstframe = opts.get('-f', False) lastframe = opts.get('-F', firstframe) if not firstkeyframe or not firstframe: print 'You need to specify at least two frames.' raise SystemExit if not os.path.isfile(firstkeyframe) or \ not os.path.isfile(firstframe): print firstkeyframe + ' and/or ' + firstframe \ + ' do not exist!' raise SystemExit kframedir = os.path.dirname(firstkeyframe) framedir = os.path.dirname(firstframe) first_keyframe_num = os.path.basename(firstkeyframe).split('.')[0] last_keyframe_num = os.path.basename(lastkeyframe).split('.')[0] first_frame_num = os.path.basename(firstframe).split('.')[0] last_frame_num = os.path.basename(lastframe).split('.')[0] imgext = '.' + os.path.basename(firstframe).split('.')[1] numberlength = len(first_frame_num) totalsteps = int(last_frame_num) - int(first_frame_num) if first_keyframe_num != last_keyframe_num: sceneinscene = True if (int(last_frame_num) - int(first_frame_num)) != \ (int(last_keyframe_num) - int(first_keyframe_num)): print 'The number of key frames (if more than one) must ' + \ 'match the number of frames.' raise SystemExit else: sceneinscene = False # Defaults: method = opts.get('-m', 'over') cropgeom = opts.get('-c', False) if cropgeom: try: # xd, yd are dummy [xd, yd] = [int(i) for i in cropgeom.split('+')[1:]] [xd, yd] = [int(i) for i in cropgeom.split('+')[0].split('x')] if '-' in cropgeom: raise ValueError except: print 'Invalid -c geometry (must be WxH+X+Y).' raise SystemExit cropc = ' '.join([crop, cropgeom]) else: cropc = '' pre = 'pip_' size0 = opts.get('-s', '20%') sizef = opts.get('-S', size0) if float(size0[:-1]) < 0 or float(sizef[:-1]) < 0: print 'Resize percentage must be positive.' raise SystemExit pos0 = opts.get('-p', '0+0') posf = opts.get('-P', pos0) 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 'picinpic.py version ' + version + \ '\nComposing using "' + method + '" method...' try: size = size0 if size0 != sizef: sizestep = (float(sizef[:-1]) - float(size0[:-1]))/totalsteps else: sizestep = 0 except: print 'Unable to calculate size step.' raise SystemExit try: [x0, y0] = [int(xi) for xi in pos0.split('+')] [xf, yf] = [int(xi) for xi in posf.split('+')] pos = '+' + pos0 if pos0 != posf: dx, dy = cmp(xf-x0,0), cmp(yf-y0,0) xstep = dx*int(math.ceil(abs(xf - x0)/float(totalsteps))) ystep = dy*int(math.ceil(abs(yf - y0)/float(totalsteps))) else: xstep = 0 ystep = 0 except: print 'Unable to calculate position step.' raise SystemExit kframe = 0 for iframe in range(int(first_frame_num), \ int(last_frame_num) + 1): rt_fname = str(iframe).zfill(numberlength) fname = rt_fname + imgext fp_fname = os.path.join(framedir, fname) rt_kfname = str(int(first_keyframe_num) + kframe).zfill(numberlength) kfname = rt_kfname + imgext fp_kfname = os.path.join(kframedir, kfname) if sceneinscene: kframe+=1 if newext: fp_prefname = tempfile.mkstemp(imgext, pre, tempdir)[1] else: fp_prefname = os.path.join(framedir, pre + fname) finsert = ' '.join(['\(', fp_kfname, cropc, resize, size, '\)']) docompose = ' '.join([convert, \ fp_fname, \ finsert, \ geometry, pos, \ compose, method, \ d8, composite, fp_prefname]) size = str(float(size[:-1]) + sizestep) + '%' [xp, yp] = [int(xi) for xi in pos.split('+')[1:]] # This is needed because there's a minimum d displacement of 1 # pixel, but if the number N of frames is such that d*N > posf-pos0 # then the sub-image will just keep moving. if confine: if (abs(xp-x0) >= abs(xf-x0)): xp = xf xstep = 0 if (abs(yp-y0) >= abs(yf-y0)): yp = yf ystep = 0 # If scrolling is not confined and since we take "abs" # (e.g. +-5+5 is not a valid position) the sub-image will # "bounce" once it reaches the final position. pos = '+' + str(abs(xp + xstep)) + '+' + str(abs(yp + ystep)) if verbose: print docompose os.system(docompose) 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 if not quiet: print 'Done!' """ CHANGELOG: 08 Mar 2005 : 0.0.1 : first release. 09 Mar 2005 : 0.0.2 : minor internal code cleanups. fixed error when pos0 = posf. 10 Mar 2005 : 0.0.3 : fixed displacement limit. fixed step calculation. 23 Mar 2005 : 0.0.4 : use env python (hopefully >= 2.3). fixed comment. change "cp_" prefix to "pip_". key frames and target frames no longer have to be in the same directory. 28 Mar 2005 : 0.0.5 : added "-l" (confine) flag. 12 Apr 2005 : 0.0.6 : fixed step size calculation. 13 Apr 2005 : 0.0.7 : fixed position/confine calculation. 13 Aug 2005 : 0.0.8 : check for bad geometry. """