Making a photobook from a directory of images using a script

From Scribus Wiki
Jump to: navigation, search
This article is part of the Scripts series.

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

#!/usr/bin/env python
# -*- coding: ascii -*-
# File: photobook_layout.py
# Created: 25th May 2007 by Chris Crouch

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

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

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

# 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)

###### Constants
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

######
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

#!/usr/bin/env python
# -*- coding: ascii -*-
# File: pb_flip_h.py
# Created: 25th May 2007 by Chris Crouch

# 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

#!/usr/bin/env python
# -*- coding: ascii -*-
# File: pb_flip_v.py
# 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

#!/usr/bin/env python
# -*- coding: ascii -*-
# File: pb_layout_current.py
# Created: 25th May 2007 by Chris Crouch

import photobook_layout

######
if __name__ == '__main__':
	photobook_layout.pb_layout_page()

pb_layout_from_current.py

#!/usr/bin/env python
# -*- coding: ascii -*-
# File: pb_layout_from_current.py
# Created: 25th May 2007 by Chris Crouch

import photobook_layout

######
if __name__ == '__main__':
	photobook_layout.pb_layout_all_pages()