Automating Book Making

A lot of my work is difficult in book form because its video, animated interactive code, or patterns and object recognition in large data sets. So I found myself with thousands of images, but no single image that told a story about the work in total. So I decided to create a catalog of this work showing all of the images sequentially by using a custom script to automate the layout of the book.

Most existing tools for book-making are relatively poor. InDesign allows you to generate catalogs of images but the amount of curation and control is rather limited, and it can be unfortunately glitchy. I used Processing with a few custom Java scripts to create this custom catalog:


Process

Step 1: Creating Images from Movies

First I needed to get a sequence of images out of the movies I’ve made at an interesting frame rate. I didn’t want the frames to occur too frequently because it would make my catalog too long and boring. I imported each movie into After Effects and changed the frame rate to be incredibly low. Usually the default is about 30 frames per second. Thats a lot of frames for a 6 minute video (30 frames X 360 sec = 10,800)! I tried to keep most of my movies between 200–1500 final images. There were about 50 images per page in my final page. After changing the frame rate for the video, I exported a sequence of JPEGs to a folder.

video -> exported frames -> sequence of images on a page

The original video:

Step 2: Batch Image Renaming

The next thing I did was batch rename the images using Bridge.

Step 3: Creating a Name Index

I wanted to pair the images with a caption. So I created an Excel file so that I could quickly create the names for the images. For this video I used the time stamp for each image as its caption. I then exported this to a CSV so that it could be imported into the script for making the catalog pages.

csv file -> image caption

Step 4: Creating the Catalog Pages

I used this script in Processing Java mode. The script was created with help from Allan William Martin at Spatial Pixel.

Some things to watch out for:
The CSV file needs to be in the data folder with headers.
The fonts will need to be updated based on what fonts you have access to.

Table data;
PGraphics contactSheet;
PImage panorama;
PFont neutraFont;
PFont courierFont;
void setup() {
size(100, 100);

neutraFont = createFont("KozMinPr6N-Heavy", 40);
courierFont = createFont("Courier New", 23);
//textSize(30);

int thumbnailWidth = 328;
int thumbnailHeight = 242;
int panoramasPerSheet = 48;

int numColumns = 6;
int margin = 30; // in pixels between each image. 300 pixels == 1 inch.

int contactSheetWidth = thumbnailWidth * numColumns + margin * (numColumns - 1);
int contactSheetHeight = (thumbnailHeight + margin) * (panoramasPerSheet/numColumns);
contactSheet = createGraphics(contactSheetWidth, contactSheetHeight);

color backgroundColor = color(255);

contactSheet.beginDraw();
contactSheet.background(backgroundColor);
contactSheet.endDraw();

String thumbnailName = "dm"; // prefix of each panorama
String setName = "dm"; // name of the folder inside the "data" folder.
String csvFilename = "dm.csv";
String contactSheetName = "dm_contactsheet";

String path = sketchPath() + "/data/" + setName;
println("Loading images from " + path);

data = loadTable(csvFilename, "header");
int numRows = data.getRowCount();

//Draw text.
contactSheet.fill(0,0,0);
contactSheet.textSize(23);

int index = 0; // The index of this image on the current page.
int totalIndex = 0; // The index of the image in the total set of images.
int sheetNumber = 0;
for (TableRow row : data.rows()) {
contactSheet.beginDraw();

String time = row.getString("time");
String id = row.getString("num");
String filename = thumbnailName + id + ".jpg";
println("Placing " + filename + ", " + time);

panorama = loadImage(setName + "/" + filename);

int x = index % numColumns;
int y = index / numColumns;

contactSheet.textFont(courierFont, 23);
contactSheet.text("time: " + time, x * (thumbnailWidth + 35), (y * (thumbnailHeight+ 30))+thumbnailHeight+24);
contactSheet.image(panorama, x * (thumbnailWidth + margin), y * (thumbnailHeight + margin));


index ++;
totalIndex ++;
boolean atLastImage = totalIndex == numRows - 1;
if (index == panoramasPerSheet || atLastImage) {
String sheetName = contactSheetName + "_" + sheetNumber + ".jpg";
println("Saving " + sheetName + "\n\n");
contactSheet.save(sheetName);
contactSheet.background(backgroundColor);
sheetNumber ++;
index = 0;
}

contactSheet.endDraw();
}

println("DONE!");
}
void draw() {
}
// This function returns all the files in a directory as an array of Strings  
String[] listFileNames(String dir) {
File file = new File(dir);
if (file.isDirectory()) {
String names[] = file.list();
return names;
} else {
// If it's not a directory
return null;
}
}
// This function returns all the files in a directory as an array of File objects
// This is useful if you want more info about the file
File[] listFiles(String dir) {
File file = new File(dir);
if (file.isDirectory()) {
File[] files = file.listFiles();
return files;
} else {
// If it's not a directory
return null;
}
}
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.