filmless/filmless_processing/filmless_processing.pde

357 lines
11 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import processing.sound.*;
import soundtrack.optical.*;
// Export video to image sequence using ffmpeg
// ffmpeg -i video.mov -f image2 -r 24 /tmp/image-%04d.png
// ffmpeg -i video.mov -acodec pcm_s16le -ac 1 audio.wav //-ar 16000 sets rate
/**
* CHANGE THESE
**/
//types: unilateral, variable area, dual variable area, maurer, variable density
int DPI = 1440; //maximum printer DPI
String SOUNDTRACK_TYPE = "unilateral";
String PITCH = "long"; // long, short //7.62, 7.605
String FORMAT = "16mm"; //16mm or super16
int PERFS = 1; //single (1) or double (2) perf film
float PAGE_W = 8.5; //page width in inches
float PAGE_H = 11.0; //page height in inches
float SAFE_W = .25; //safe area on each side of the page
float SAFE_H = .5; //safe area on top and bottom of page
color BACKGROUND = color(0); //the color that will fill the entire frame where there's no image
boolean NEGATIVE = false; //true to invert image data
boolean SHOW_PERFS = true; //set to true to print perfs for cutting registration
color PERFS_COLOR = color(255);
int SOUND_OFFSET = 25;
//Don't change unless necessary
String SEP = System.getProperty("file.separator");
String SOURCE = "frames"; //path to directory containing frames
String SOUND = "audio"; //leave empty string if silent
//This is a magic number that is used to scale the vertical (H) or horizontal (W) resolution
//because the printer sometimes lies to you.
float MAGIC_H_CORRECTION = 1.0;
float MAGIC_W_CORRECTION = 1.0;
/**
* CONSTANTS (DON'T CHANGE PLZ)
**/
float IN = 25.4;
float LONG_H = 7.62;
float SHORT_H = 7.605;
float STD16_W = 10.26; //0.413"
float STD16_H = 7.49; //0.295"
float SUPER16_W = 12.52; //0.492"
float SUPER16_H = 7.41; //0.292"
float PERF_W = 1.829;
float PERF_H = 1.27;
float DPMM = DPI / IN;
int SPACING = PITCH.equals("long") ? round(LONG_H * DPMM * MAGIC_H_CORRECTION) : round(SHORT_H * DPMM * MAGIC_H_CORRECTION);
int PAGE_W_PIXELS = ceil((PAGE_W - (SAFE_W * 2)) * DPI * MAGIC_W_CORRECTION);
int PAGE_H_PIXELS = ceil((PAGE_H - (SAFE_H * 2)) * DPI * MAGIC_H_CORRECTION);
int FRAME_W = FORMAT.equals("super16") ? round(SUPER16_W * DPMM * MAGIC_W_CORRECTION) : round(STD16_W * DPMM * MAGIC_W_CORRECTION);
int FRAME_H = FORMAT.equals("super16") ? round(SUPER16_H * DPMM * MAGIC_H_CORRECTION) : round(STD16_H * DPMM * MAGIC_H_CORRECTION);
int LEFT_PAD = round(((16 - STD16_W) / 2) * DPMM * MAGIC_W_CORRECTION); //space to left of frame
int COLUMNS = floor(PAGE_W_PIXELS / (16 * DPMM));
int ROWS = floor(PAGE_H_PIXELS / SPACING);
int FRAME_LINE = round((SPACING - FRAME_H) / 2);
int PAGES = 0;
int FRAMES = 0;
int SOUND_W = ceil(DPMM * (12.52 - 10.26));
boolean HAS_SOUND = false;
String SOUNDTRACK_FILE = "";
SoundtrackOptical soundtrack;
String[] frames;
PImage frameBuffer;
PGraphics frameBlank;
PGraphics pageBuffer;
PGraphics soundBuffer;
void setup () {
size(640, 480);
//surface.setResizable(true);
println(SOURCE);
println(SOUND);
frames = listFrames(SOURCE, SOUND);
if (frames == null) {
println("Frames not found, check SOURCE path");
exit();
return;
}
FRAMES = frames.length;
PAGES = ceil((float) FRAMES / (ROWS * COLUMNS));
pageBuffer = createGraphics(PAGE_W_PIXELS, PAGE_H_PIXELS);
if (HAS_SOUND) {
soundtrack = new SoundtrackOptical(this, SOUNDTRACK_FILE, DPI, 1.0, SOUNDTRACK_TYPE, PITCH, !NEGATIVE);
}
printInfo();
if (PERFS == 2 && HAS_SOUND ) {
println("WARNING: Double perf film and soundtrack will interfere with one another. Are you sure?");
}
if (FORMAT.equals("super16") && HAS_SOUND) {
println("WARNING: Super16 frame and soundtrack will interfere with one another. Are you sure?");
}
if (FORMAT.equals("super16") && PERFS == 2) {
println("WARNING: Super16 frame and double perf film will interfere with one another. Are you sure?");
}
text("DISPLAY", 200, 200);
frameBlank = createGraphics(FRAME_W, FRAME_H);
noLoop();
delay(1000);
thread("renderPages");
}
void draw () {
background(0);
fill(255);
text("SOURCE DIR: " + SOURCE, 10, 20);
text("DPI: " + DPI, 10, 40);
text("STRETCH: " + MAGIC_W_CORRECTION + " x " + MAGIC_H_CORRECTION, 10, 60);
text("FRAMES: " + FRAMES, 10, 80);
text("FRAME SIZE: " + FRAME_W + "x" + FRAME_H + " px", 10, 100);
text("PAGES: " + PAGES, 10, 120);
text("PAGE SIZE: " + PAGE_W_PIXELS + "x" + PAGE_H_PIXELS + " px", 10, 140);
text("FRAMES/PAGE: " + (ROWS * COLUMNS), 10, 160);
text("SECONDS/PAGE: " + ((ROWS * COLUMNS) / 24), 10, 180);
}
void printInfo() {
println("STRETCH: " + MAGIC_W_CORRECTION + " x " + MAGIC_H_CORRECTION);
println("PAGE SIZE: " + PAGE_W_PIXELS + "x" + PAGE_H_PIXELS);
println("FRAME SIZE: " + FRAME_W + "x" + FRAME_H);
println("FRAMES PER STRIP: " + ROWS);
println("STRIPS PER PAGE: " + COLUMNS);
println("FRAMES PER PAGE: " + (ROWS * COLUMNS));
println("SECONDS PER PAGE: " + ((ROWS * COLUMNS) / 24));
println("FRAMES: " + FRAMES);
println("PAGES: " + PAGES);
//println("RENDER_PATH: " + RENDER_PATH);
if (!SOUND.equals("")) {
println("SOUNDTRACK SAMPLE RATE: " + (SPACING * 24));
}
}
String[] listFrames (String dir, String audioDir) {
ArrayList<String> tmp = new ArrayList<String>();
ArrayList<String> audioTmp = new ArrayList<String>();
String output[];
File file;
File audioFile;
int arraySize;
int o = 0;
dir = dataPath(dir);
audioDir = dataPath(audioDir);
println(dir);
println(audioDir);
if (dir.substring(dir.length() - 1, dir.length()) != SEP) {
dir = dir + SEP;
}
if (audioDir.substring(audioDir.length() - 1, audioDir.length()) != SEP) {
audioDir = audioDir + SEP;
}
file = new File(dir);
audioFile = new File(audioDir);
if (file.isDirectory()) {
String names[] = file.list();
names = sort(names);
for (int i = 0; i < names.length; i++) {
if (names[i].toLowerCase().contains(".jpg") ||
names[i].toLowerCase().contains(".jpeg") ||
names[i].toLowerCase().contains(".tif") || //only works with Processing tiffs
names[i].toLowerCase().contains(".png")) {
tmp.add(dir + names[i]);
}
}
arraySize = tmp.size();
if (arraySize == 0) {
println("ERROR: No frames detected, exiting");
exit();
return null;
}
if (audioFile.isDirectory()) {
String audioNames[] = audioFile.list();
if (audioNames != null) {
audioNames = sort(audioNames);
for (int i = 0; i < audioNames.length; i++) {
if (audioNames[i].toLowerCase().contains(".wav")) {
audioTmp.add(audioDir + audioNames[i]);
}
}
} else {
println("Audio directory " + audioDir + " not found");
}
} else {
println("SOUND string " + audioDir + " does not point to a directory");
}
if (audioTmp.size() > 0) {
HAS_SOUND = true;
SOUNDTRACK_FILE = audioTmp.get(0);
println("Using audio file " + SOUNDTRACK_FILE);
} else {
println("No audio file detected, creating silent tracks");
}
if (HAS_SOUND) {
arraySize += SOUND_OFFSET;
}
output = new String[arraySize];
if (HAS_SOUND) {
for (int i = 0; i < SOUND_OFFSET; i++) {
output[o] = "_BLANK_";
o++;
}
}
for (int i = 0; i < tmp.size(); i++) {
output[o] = tmp.get(i);
o++;
}
sort(output);
return output;
} else {
println("ERROR: SOURCE variable does not point to a directory");
exit();
}
return null;
}
String leftPad (int val) {
String str = "" + val;
if (str.length() == 1) {
str = "0" + str;
}
return str;
}
void renderPages() {
//surface.setSize(PAGE_W_PIXELS, PAGE_H_PIXELS / SEGMENTS);
//delay(1000);
int cursor;
int leftX;
int topY;
int perfLeft;
int perfRight;
int perfTop;
int soundTop;
int soundLeft;
boolean hasFrames = false;
frameBlank.beginDraw();
frameBlank.background(BACKGROUND);
frameBlank.endDraw();
for (int page = 0; page < PAGES; page++) {
pageBuffer.beginDraw();
pageBuffer.textSize(60);
pageBuffer.clear();
pageBuffer.background(255);
pageBuffer.stroke(0);
pageBuffer.noFill();
//draw calibration marks to be overwritten if needed
pageBuffer.rect(0, 0, 10 * DPMM, 10 * DPMM);
pageBuffer.rect(((16 * COLUMNS) * DPMM) - (10 * DPMM) - 1, 0, 10 * DPMM, 10 * DPMM);
pageBuffer.rect(((16 * COLUMNS) * DPMM) - (10 * DPMM) - 1, ((ROWS * (SPACING / DPMM)) * DPMM) - (10 * DPMM) - 1, 10 * DPMM, 10 * DPMM);
pageBuffer.rect(0, ((ROWS * (SPACING / DPMM)) * DPMM) - (10 * DPMM) - 1, 10 * DPMM, 10 * DPMM);
//pageBuffer.stroke(0);
for (int x = 0; x < COLUMNS; x++) {
//pageBuffer.line(x * (16 * DPMM), 0, x * (16 * DPMM), PAGE_H_PIXELS);
for (int y = 0; y < ROWS; y++) {
cursor = (page * (COLUMNS * ROWS)) + (x * ROWS) + y;
if (cursor >= FRAMES) {
hasFrames = false;
break;
}
hasFrames = true;
println("Frame " + cursor + "/" + FRAMES);
topY = (y * SPACING) + FRAME_LINE;
leftX = x * (round(16 * DPMM)) + LEFT_PAD;
perfTop = round((y * SPACING) - ((PERF_H / 2) * DPMM));
pageBuffer.noStroke();
pageBuffer.fill(BACKGROUND);
pageBuffer.rect(x * (round(16 * DPMM)), (y * SPACING), round(16*DPMM), SPACING);
if (SHOW_PERFS){
perfLeft = round(x * (round(16 * DPMM)) + (.85 * DPMM));
pageBuffer.fill(PERFS_COLOR);
//rect([1.829, 1.27, 2], d = .5, center = true);
//.85 from side
pageBuffer.rect(perfLeft, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM);
//last perf
if (y == ROWS - 1) {
perfTop = round(((y + 1) * SPACING) - ((PERF_H / 2) * DPMM));
pageBuffer.rect(perfLeft, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM);
}
}
if (SHOW_PERFS && PERFS == 2) {
perfRight = round((x + 1) * (round(16 * DPMM)) - (PERF_W * DPMM) - (.85 * DPMM));
pageBuffer.rect(perfRight, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM);
if (y == ROWS - 1) {
perfTop = round(((y + 1) * SPACING) - ((PERF_H / 2) * DPMM));
pageBuffer.rect(perfRight, perfTop, PERF_W * DPMM, PERF_H * DPMM, .26 * DPMM);
}
}
if (frames[cursor].equals("_BLANK_")) {
pageBuffer.image(frameBlank, leftX, topY, FRAME_W, FRAME_H);
} else {
frameBuffer = loadImage(frames[cursor]);
if (NEGATIVE) {
frameBuffer.filter(INVERT);
}
frameBuffer.resize(FRAME_W, FRAME_H);
pageBuffer.image(frameBuffer, leftX, topY, FRAME_W, FRAME_H);
}
if (HAS_SOUND) {
soundTop = y * SPACING;
soundLeft = (x * round(16 * DPMM)) + LEFT_PAD + FRAME_W + round(0.3368 * DPMM);
try {
soundBuffer = soundtrack.buffer(cursor);
if (soundBuffer != null) {
pageBuffer.image(soundBuffer, soundLeft, soundTop, round((12.52 - 10.26 - 0.3368) * DPMM), SPACING);
}
} catch (Error e) {
//
}
}
if (hasFrames) {
text((x + 1) + "", (x * 16 * DPMM) + (8 * DPMM), ROWS * SPACING + 20);
}
}
}
pageBuffer.endDraw();
pageBuffer.save(dataPath("page_" + page + ".tif"));
println("Saved page_" + dataPath(page + ".tif"));
}
printInfo();
println("Completed");
exit();
}