// -*-java-*- // // Author: Ovidiu Predescu // Date: November 4, 2007 // // http://www.webweavertech.com/ovidiu/weblog/archives/000467.html // // Recursively process the image files contained in a directory and export them // to JPG. // // Released as public domain. You may use it as you wish. var logFile = new File("/Users/ovidiu/Desktop/convert-log.txt"); logFile.open("w"); // A list of camera raw extensions, keep them upper case var gFilesForCameraRaw = Array( "TIF", "CRW", "NEF", "RAF", "ORF", "MRW", "DCR", "MOS", "SRF", "PEF", "DCR", "CR2", "DNG", "ERF", "X3F", "RAW" ); var sourceDir; var destDir; var cancelAction = false; var numFiles = 0; var paletteWin = null; function log(msg) { logFile.writeln(msg + "\f"); } // Lifted from Adobe's Image Processor.jsx function isFileOneOfThese(inFileName, inArrayOfFileExtensions) { var lastDot = inFileName.toString().lastIndexOf( "." ); if (lastDot == -1) { return false; } var strLength = inFileName.toString().length; var extension = inFileName.toString().substring(lastDot + 1, strLength - lastDot); extension = extension.toUpperCase(); for (var i = 0; i < inArrayOfFileExtensions.length; i++ ) { if (extension == inArrayOfFileExtensions[i]) { return true; } } return false; } // Open a camera raw file using Adobe Camera Raw. Adapted from Adobe's // Image Processor.jsx function OpenCameraRaw(file) { var keyNull = charIDToTypeID('null'); var keyAs = charIDToTypeID('As '); var adobeCameraRawID = stringIDToTypeID("Adobe Camera Raw"); var desc = new ActionDescriptor(); desc.putPath(keyNull, File(file.toString())); // Suppress choose file dialog. var overrideOpenID = stringIDToTypeID('overrideOpen'); desc.putBoolean(overrideOpenID, true); var returnDesc = executeAction(charIDToTypeID('Opn '), desc, DialogModes.ALL); return (returnDesc.hasKey(keyAs) && returnDesc.hasKey(keyNull)); } function processAllFiles(srcDir, callback) { var files = srcDir.getFiles("*"); for (var i = 0; i < files.length; i++) { app.refresh(); if (cancelAction) { return; } var f = files[i]; if (f instanceof File && !f.hidden) { try { callback(f); } catch (e) { log("Could not process file: " + f.fsName + ": " + e); } } else if (f instanceof Folder) { processAllFiles(f, callback); } } } function countFiles(f) { numFiles++; } function convertToJPEG(f) { if (paletteWin) { paletteWin.pbar.value++; } log("Processing file: " + f.fsName); // Extract the path within sourceDir where `f' is // located. Photoshop's file API is horror! Why don't they allow // spaces in filenames is beyond me. I need to replace spaces in // filenames, but not in directory names. The multitude of methods, // fsName, absoluteURI, path, name, all return the file names // encoded in different ways. var path = f.fsName.substring(sourceDir.fsName.length, f.fsName.length); var destFileName = destDir.absoluteURI + path; if (destFileName.lastIndexOf(".") != -1) { destFileName = (destFileName.substring(0, destFileName.lastIndexOf(".")) + ".jpg"); } var jpgFile = new File(destFileName); jpgFile = new File(jpgFile.parent.fsName + "/" + File.decode(jpgFile.name).replace(/ /g, "-")); if (jpgFile.exists) { log(" Ignoring already processed " + jpgFile.fsName); return; } if (!jpgFile.parent.exists) { jpgFile.parent.create(); } var doc = null; try { if (isFileOneOfThese(f, gFilesForCameraRaw)) { if (!OpenCameraRaw(f)) { log("Could not process " + f.toString()); return; } doc = app.activeDocument; } else { doc = app.open(f); } var saveOptions = new ExportOptionsSaveForWeb(); saveOptions.format = SaveDocumentType.JPEG; saveOptions.includeProfile = false; saveOptions.quality = 80; log(" Writing to " + jpgFile.fsName); doc.exportDocument(jpgFile, ExportType.SAVEFORWEB, saveOptions); } finally { if (doc) { doc.close(SaveOptions.DONOTSAVECHANGES); } // Flush the internal Photoshop caches, otherwise we run out of // space on the scratch disk (see Preferences -> Performance). app.purge(PurgeTarget.ALLCACHES); } } function showCountFiles() { paletteWin = new Window("palette", "Processing Status", [ 800, 200, 1100, 300]); paletteWin.add("statictext", [ 20, 20, 280, 45], "Determining files to process..."); paletteWin.cancelButton = paletteWin.add("button", [200, 60, 280, 85], "Cancel"); paletteWin.cancelButton.onClick = function() { cancelAction = true; } paletteWin.show(); } function showStatusWindow() { if (paletteWin != null) { paletteWin.close(); } paletteWin = new Window("palette", "Processing Status", [ 800, 200, 1100, 300]); paletteWin.pbar = paletteWin.add("progressbar", [ 20, 20, 280, 45], 0, numFiles); paletteWin.pbar.value = 0; paletteWin.cancelButton = paletteWin.add("button", [200, 60, 280, 85], "Cancel"); paletteWin.cancelButton.onClick = function() { cancelAction = true; } paletteWin.show(); } sourceDir = Folder.selectDialog("Select a source directory to recursively process its files:"); destDir = Folder.selectDialog("Select a destination directory to store the results into:"); if (sourceDir && destDir) { // Check to see if destDir is a subdirectory of sourceDir. This // happens if sourceDir is a substring of destDir. if (destDir.fsName.substring(0, sourceDir.fsName.length) == sourceDir.fsName) { alert("Destination directory " + destDir.fsName + " cannot be a subdirectory of " + sourceDir.fsName); } else { if (sourceDir && sourceDir instanceof Folder) { var startTime = new Date(); showCountFiles(); processAllFiles(sourceDir, countFiles); showStatusWindow(); log("Will process " + numFiles + " files"); processAllFiles(sourceDir, convertToJPEG); var endTime = new Date(); log("Processing took " + (endTime.getTime() - startTime.getTime()) / 1000 + " seconds"); } } } // Close the log file logFile.close();