
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 = 1200;               //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(SOUND);
  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;
    }

    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]);
        }
      }
    }

    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();
}
