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.
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.
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;
}
}