Making a photobook from a directory of images using a script

This is yet another script to layout photos in a book. It differs from Automatic import of images: Versions not requiring Tkinter and Automatic import of images from a directory using a script in that it is not meant to be fully automatic, and it allows for a variable number of images per page in mixtures of landscape and portrait orientation.

It's intended to produce a book in the same format as Adobe Photoshop Album: US Letter, landscape orientation.

Caveat: I learned Python for the sole purpose of writing this script. It's a little rough in places.

Usage
Running photobook_layout.py without an open document will bring up a dialog to select a directory. The script will then add all JPEG files (*.jpg) to a new document, 3 to a page. It will then go through each page, re-orienting and resizing the photos to nicely fit the page.

You can then re-arrange the photos, dragging them from page to page to get a nice layout. Re-running the photobook_layout.py script will go through all of the pages, re-laying them out.

The script can handle 1-4 photos per page, in any combination of portrait and landscape.

There are some auxilliary scripts. pb_layout_current.py will lay out only the current page. pb_layout_from_current.py will lay out all pages from the current one to the end of the document. pb_flip_h.py and pb_flip_v.py will flip arrangement of images on the current page.

Performance Hints
If you have a lot of images, pre-arrange them into separate subdirectories and create separate documents for "chapters". I found that Scribus spat the dummy at around 150 8mp images at PDF export time.

Create small copies of your images. Do your layout on the small images, then copy the large original images into the right place only for final export or printing. This makes it a lot more responsive.

photobook_layout.py

 * 1) !/usr/bin/env python
 * 2) -*- coding: ascii -*-
 * 3) File: photobook_layout.py
 * 4) Created: 25th May 2007 by Chris Crouch


 * 1) This script will create a photobook. It can both create a photobook from a folder full of images,
 * 2) and lay images out on the pages in a nice way. You can move the images from page to page, and have
 * 3) the script re-layout them for you. It lays out landscape and portrait images neatly.


 * 1) Requires the Python Image Library from http://www.pythonware.com/products/pil/ (to work out image orientation)
 * 2) Developed on a Windows machine running Scribus 1.3.3.9


 * 1) This is designed for Letter pages in Landscape orientation, with images that are 3:2 aspect ratio (DSLR)
 * 2) To change these, change the constants at the top for the paper size, then you'll need to just fiddle
 * 3) with the size & positions in pb_layout_page so that (a) the image frames are the correct aspect ratio,
 * 4) and (b) the sizes and positions look good on the page. Trial and error is how I got these.


 * 1) There are some auxilliary scripts that just allow you to directly invoke some of the functions in this script.

import glob try: import Image except ImportError: print "This script requires the Python Image Library" sys.exit(1) try: from scribus import * except ImportError: print "This script only runs from within Scribus." sys.exit(1)

page_size = (279.4, 215.9) #Letter, mm page_margins_left = (5, 5, 20, 5) #mm page_margins_right = (20, 5, 5, 5) #mm page_margins_points = (56.69, 14.17, 14.17, 14.17) #margins_right as points
 * 1) Constants

def cmpObj(obj1, obj2): # compare 2 image objects to sort by horizontal loc # (if ==, the vert. pos; then by name	pos1 = getPosition(obj1)	pos2 = getPosition(obj2)	if pos1[0] > pos2[0]:		return 1	if pos1[0] < pos2[0]:		return -1	if pos1[1] > pos2[1]:		return 1	if pos1[1] < pos2[1]:		return -1	return obj1 > obj2

def get_page_margins: if (currentPage % 2) == 1: return page_margins_right; else: return page_margins_left;

def placeobj(obj_size, x_place, y_place, obj): # move obj to specified position and specified size # obj_size - (w, h)	# x_place, y_place: 0.0 - 1.0 centre position, normalized to page size page_margins = get_page_margins page_width = page_size[0] - page_margins[0] - page_margins[2] page_height = page_size[1] - page_margins[1] - page_margins[3] xpos = (page_width * x_place) + page_margins[0] - obj_size[0]/2 ypos = (page_height * y_place) + page_margins[1] - obj_size[1]/2 selectObject(obj) sizeObject(obj_size[0], obj_size[1], obj) moveObjectAbs(xpos, ypos, obj) deselectAll return

def get_images_on_page: # returns a list of the image objects on the current page # get all the items on the page item_list = getPageItems # refine it to a list of only image objects objs = []; for item in item_list: if item[1] == 2: #type 2 == image objs.append(item[0]) return objs

def pb_layout_page: # Nicely layout all of the images on the current page deselectAll # get all of the images on this page and sort them by position objs = get_images_on_page if len(objs) == 0: return objs.sort(cmpObj) # refine it to lists of landscape & portrait images port = [] land = [] for obj in objs: file = getImageFile(obj) im = Image.open(file) sz = im.size if sz[0] > sz[1]: land.append(obj) else: port.append(obj) np = len(port) # number of portrait images nl = len(land) # number of landscape images # layouts for different numbers of portrait and landscape images if np==0 and nl==1: placeobj((240, 160), 0.5, 0.5, land[0]) elif np==0 and nl==2: placeobj((150, 100), 0.4, 0.25, land[0]) placeobj((150, 100), 0.6, 0.75, land[1]) elif np==0 and nl==3: placeobj((120, 80), 0.25, 0.21, land[0]) placeobj((165, 110), 0.5, 0.7, land[1]) placeobj((120, 80), 0.75, 0.21, land[2]) elif np==0 and nl==4: placeobj((120, 80), 0.25, 0.25, land[0]) placeobj((120, 80), 0.75, 0.25, land[1]) placeobj((120, 80), 0.25, 0.75, land[2]) placeobj((120, 80), 0.75, 0.75, land[3]) elif np==1 and nl==0: placeobj((144, 216), 0.74, 0.5, port[0]) elif np==1 and nl==1: placeobj((90, 135), 0.205, 0.5, port[0]) placeobj((150, 100), 0.7, 0.5, land[0]) elif np==1 and nl==2: placeobj((120, 180), 0.25, 0.5, port[0]) placeobj((120, 80), 0.75, 0.27, land[0]) placeobj((120, 80), 0.75, 0.73, land[1]) elif np==1 and nl==3: placeobj((130, 195), 0.27, 0.5, port[0]) placeobj((90, 60), 0.75, 0.18, land[0]) placeobj((90, 60), 0.75, 0.5, land[1]) placeobj((90, 60), 0.75, 0.82, land[2]) elif np==2 and nl==0: placeobj((120, 180), 0.25, 0.5, port[0]) placeobj((120, 180), 0.75, 0.5, port[1]) elif np==2 and nl==1: placeobj((60, 90), 0.14, 0.25, port[0]) placeobj((60, 90), 0.14, 0.75, port[1]) placeobj((180, 120), 0.63, 0.5, land[0]) elif np==2 and nl==2: placeobj((70, 105), 0.22, 0.27, port[0]) placeobj((70, 105), 0.78, 0.73, port[1]) placeobj((135, 90), 0.65, 0.24, land[0]) placeobj((135, 90), 0.35, 0.76, land[1]) elif np==3 and nl==0: placeobj((80, 120), 0.17, 0.5, port[0]) placeobj((80, 120), 0.5, 0.5, port[1]) placeobj((80, 120), 0.83, 0.5, port[2]) elif np==3 and nl==1: placeobj((120, 180), 0.25, 0.5, port[0]) placeobj((60, 90), 0.62, 0.72, port[1]) placeobj((60, 90), 0.87, 0.72, port[2]) placeobj((120, 80), 0.745, 0.26, land[0]) elif np==4 and nl==0: placeobj((90, 135), 0.19, 0.5, port[0]) placeobj((60, 90), 0.5, 0.25, port[1]) placeobj((60, 90), 0.5, 0.75, port[2]) placeobj((90, 135), 0.81, 0.5, port[3]) docChanged(True) deselectAll return

def pb_layout_all_pages: # re-layout all pages from the current page to the end of the document progressReset progressTotal(pageCount+1) messagebarText("Arranging pictures"); for i in range(currentPage, pageCount+1): gotoPage(i) progressSet(i) messagebarText("Page "+str(i)) pb_layout_page progressReset messagebarText("");

def pb_create_album: # Prompt the user for a directory to process. # Create a new document with all jpg files from the selected directory added as images # to the document, 3 to the page. # When finished, process the document to layout the images in a "nice" manner dirname = fileDialog("Select Directory","","",False,False,True) files = glob.glob(dirname + "*.jpg") if len(files) == 0: return # it seems that you _have_ to specify page margins in points newDocument(PAPER_LETTER, page_margins_points, LANDSCAPE, 1, UNIT_POINTS, PAGE_2, FACINGPAGES, FIRSTPAGERIGHT) zoomDocument(50) setUnit(UNIT_MILLIMETERS) setMargins(page_margins_right[0], page_margins_right[1], page_margins_right[2], page_margins_right[3]) progressReset progressTotal(len(files)) messagebarText("Creating pages..."); progress = 0; num_on_page = 0; for file in files: messagebarText("Adding "+file) progressSet(progress) progress = progress + 1 num_on_page = num_on_page + 1 if num_on_page == 4: newPage(-1) num_on_page = 1 im = Image.open(file) sz = im.size if sz[0] > sz[1]: f = createImage(num_on_page*90-80, 60, 90, 60) else: f = createImage(num_on_page*90-80, 10, 80, 120) loadImage(file, f)		setScaleImageToFrame(scaletoframe=1, proportional=1, name=f) progressReset messagebarText(""); deselectAll # now layout the pages nicely gotoPage(1) pb_layout_all_pages deselectAll

if __name__ == '__main__': # either create a new album document, or re-layout the entire existing document if haveDoc: gotoPage(1) pb_layout_all_pages else: pb_create_album

pb_flip_h.py

 * 1) !/usr/bin/env python
 * 2) -*- coding: ascii -*-
 * 3) File: pb_flip_h.py
 * 4) Created: 25th May 2007 by Chris Crouch


 * 1) flip the images on the current page horizontally

import photobook_layout try: from scribus import * except ImportError: print "This script only runs from within Scribus." sys.exit(1)

def pb_flip_h: # flip the images on the current page horizontally deselectAll page_margins = photobook_layout.get_page_margins page_width = photobook_layout.page_size[0] - page_margins[0] - page_margins[2] page_height = photobook_layout.page_size[1] - page_margins[1] - page_margins[3] objs = photobook_layout.get_images_on_page for obj in objs: obj_size = getSize(obj) obj_pos = getPosition(obj) obj_h_mid = obj_pos[0] + obj_size[0]/2 new_h_mid = page_width - (obj_h_mid - page_margins[0]) + page_margins[0] selectObject(obj) moveObjectAbs(new_h_mid - obj_size[0]/2, obj_pos[1], obj) deselectAll docChanged(True)

if __name__ == '__main__': pb_flip_h

pb_flip_v.py

 * 1) !/usr/bin/env python
 * 2) -*- coding: ascii -*-
 * 3) File: pb_flip_v.py
 * 4) Created: 25th May 2007 by Chris Crouch

import photobook_layout try: from scribus import * except ImportError: print "This script only runs from within Scribus." sys.exit(1)

def pb_flip_v: # flip the images on the current vertically deselectAll page_margins = get_page_margins page_width = page_size[0] - page_margins[0] - page_margins[2] page_height = page_size[1] - page_margins[1] - page_margins[3] objs = get_images_on_page for obj in objs: obj_size = getSize(obj) obj_pos = getPosition(obj) obj_v_mid = obj_pos[1] + obj_size[1]/2 new_v_mid = page_height - (obj_v_mid - page_margins[1]) + page_margins[1] selectObject(obj) moveObjectAbs(obj_pos[0], new_v_mid - obj_size[1]/2, obj) deselectAll docChanged(True)

if __name__ == '__main__': photobook_layout.pb_flip_v

pb_layout_current.py

 * 1) !/usr/bin/env python
 * 2) -*- coding: ascii -*-
 * 3) File: pb_layout_current.py
 * 4) Created: 25th May 2007 by Chris Crouch

import photobook_layout

if __name__ == '__main__': photobook_layout.pb_layout_page

pb_layout_from_current.py

 * 1) !/usr/bin/env python
 * 2) -*- coding: ascii -*-
 * 3) File: pb_layout_from_current.py
 * 4) Created: 25th May 2007 by Chris Crouch

import photobook_layout

if __name__ == '__main__': photobook_layout.pb_layout_all_pages