Last active
November 23, 2022 15:49
-
-
Save doubleedesign/e535083b2986cca873203304d9dd7e7a to your computer and use it in GitHub Desktop.
InDesign Image Catalog for multiple folders at once (and multiple folders per page). Created for school yearbook projects (one folder per class, two classes per page) but could be adapted for other use cases where you want to lay out contact sheets of multiple folders automatically.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Custom Image Catalog script that runs for all subfolders in a selected folder. | |
* Lays out each folder of images in the specified number of rows and columns, 2 folders per page, shows an alert if there's more images than allowed for, | |
* labels each group with the folder name, creates paragraph styles for the captions and group headings, and saves the file. | |
* | |
* Based on the built-in Image Catalog script but modified and simplified (e.g. hard-coding the settings) for my use case. | |
* Could be modified to suit different numbers of folders per page, different image quantities etc by changing the settings at the top | |
* and making tweaks to other code as needed. | |
* | |
* Could also be extended to show one dialog for settings prior to the loop, | |
* so all settings would apply to every folder rather than it prompting for each one. | |
*/ | |
main(); | |
function main() { | |
var settings = { | |
foldersPerPage: 2, | |
rows: 3, | |
columns: 9, | |
removeEmptyFrames: true, | |
fitProportional: true, | |
centerContent: true, | |
fitFrameToContent: true, | |
horizontalOffset: 1.4, // 4pt | |
verticalOffset: 1.4, // 4pt | |
labels: true, | |
labelType: 0, // file name | |
labelHeight: 8.5, // 24pt in mm | |
labelOffset: 0.7, // 2pt in mm | |
labelStyle: 'Labels', | |
headingStyle: 'Group headings', | |
layerName: 'Layer 1', | |
pageMargin: 12 // mm | |
} | |
// Prompt for top-level folder choices | |
var folder = Folder.selectDialog("Select the folder containing the subfolders of images", ""); | |
var saveLocation = Folder.selectDialog("Select the folder to save the files to", ""); | |
// Get subfolders | |
var subfolders = getSubFolders(folder); | |
// Create the document | |
var document = app.documents.add(); | |
var docPreferences = document.documentPreferences; | |
docPreferences.pagesPerDocument = Math.ceil(subfolders.length / foldersPerPage); | |
docPreferences.facingPages = false; | |
document.viewPreferences.horizontalMeasurementUnits = MeasurementUnits.millimeters; | |
document.viewPreferences.verticalMeasurementUnits = MeasurementUnits.millimeters; | |
docPreferences.pageWidth = 210; | |
docPreferences.pageHeight = 297; | |
// Counter for page numbers | |
var pageNo = 0; | |
// Loop through the subfolders of files | |
for(var i = 0; i < subfolders.length; i+=2) { | |
// Get (and transform) the folder name so the label can be added | |
// decodeURIComponent replaces characters like 20% where spaces should be | |
// replace(folderPath, '') removes the folder path and just leaves the folder name | |
// The rest is specific to the folders I had at the time of writing, | |
// which were in the format Year [class name] - 2021, when all I want to use is the class name | |
var folderPath = decodeURIComponent(folder.toString()); | |
var files1Label = decodeURIComponent(subfolders[i].toString()).replace(folderPath, ''); | |
files1Label = files1Label.replace('/', '') | |
.replace('-', '') | |
.replace('Year', '') | |
.replace('2021', '') | |
.replace(/\s/g, ''); | |
var files2Label = decodeURIComponent(subfolders[i+1].toString()).replace(folderPath, ''); | |
files2Label = files2Label.replace('/', '') | |
.replace('-', '') | |
.replace('Year', '') | |
.replace('2021', '') | |
.replace(/\s/g, ''); | |
// Get the files | |
var files1 = subfolders[i].getFiles(); | |
var files2 = subfolders[i+1].getFiles(); | |
// Alert if there's more files than allowed for | |
if(files1.length > (settings.rows * settings.columns)) { | |
alert("Too many files in " + subfolders[i]); | |
} | |
if(files2.length > (settings.rows * settings.columns)) { | |
alert("Too many files in " + subfolders[i+1]); | |
} | |
// If there's files, make the catalog page with the hard-coded settings | |
// after making sure the images are sorted alphabetically | |
if(files1 || files2) { | |
files1.sort(); | |
files2.sort(); | |
makeImageCatalog([files1, files2], [files1Label, files2Label], settings, document, docPreferences, pageNo); | |
} | |
else { | |
$.writeln('Nope'); | |
} | |
// Increment the counter so the next pair goes on the next page | |
pageNo++; | |
} | |
// Save the file | |
var newFile = new File(saveLocation + "/ImageCatalogue.indd"); | |
document.save(newFile); | |
// Ok, now we're done | |
alert('Done!'); | |
} | |
function getSubFolders(folder) { | |
var myStuff = []; | |
var myFileList = folder.getFiles(); | |
for (var i = 0; i < myFileList.length; i++) { | |
if (myFileList[i].toString().indexOf('.jpg') !== -1) { | |
// is an image file | |
} else { | |
myStuff.push(myFileList[i]); | |
} | |
} | |
return myStuff; | |
} | |
function makeImageCatalog(filegroups, fileGroupLabels, settings, document, docPreferences, pageNo) { | |
var file, counter, X1, Y1, X2, Y2, rectangle, labelLayer, labelParagraphStyle, headingParagraphStyle; | |
var page = document.pages.item(pageNo); | |
var leftMargin = settings.pageMargin; | |
var topMargin = settings.pageMargin; | |
var rightMargin = settings.pageMargin; | |
var bottomMargin = settings.pageMargin; | |
var liveWidth = (docPreferences.pageWidth - (leftMargin + rightMargin)) + settings.horizontalOffset; | |
var liveHeight = docPreferences.pageHeight - (topMargin + bottomMargin); | |
var columnWidth = liveWidth / settings.columns; | |
var frameWidth = columnWidth - settings.horizontalOffset; | |
var rowHeight = ((liveHeight / settings.rows) / 2) - 6; // divide by 2 because we're doing 2 groups per page | |
var frameHeight = rowHeight - settings.verticalOffset; | |
// Move the view to the current page so we can watch where the script is up to | |
app.activeDocument.layoutWindows[0].activePage = app.activeDocument.pages[pageNo]; | |
// If settings.labels is true, then add the label style and layer if they do not already exist. | |
if (settings.labels == true) { | |
try { | |
labelLayer = document.layers.item(settings.layerName); | |
labelLayer.name; | |
} | |
catch (error) { | |
labelLayer = document.layers.add({name: settings.layerName}); | |
} | |
// If the paragraph styles do not exist, create them | |
try { | |
labelParagraphStyle = document.paragraphStyles.item(settings.labelStyle); | |
labelParagraphStyle.name; | |
} | |
catch (error) { | |
document.paragraphStyles.add({name: settings.labelStyle}); | |
} | |
try { | |
headingParagraphStyle = document.paragraphStyles.item(settings.headingStyle); | |
headingParagraphStyle.name; | |
} | |
catch (error) { | |
document.paragraphStyles.add({name: settings.headingStyle}); | |
} | |
} | |
// IMPORTANT: This loop expects 2 filegroups for a 2-group-per-page layout, | |
// and would need to be modified for a different use case | |
for(var i = 0; i < 2; i++) { | |
var numberOfFrames = filegroups[i].length; | |
// For the first group, add to the top margin to allow space for the group name | |
if(i == 0) { | |
topMargin = topMargin + 8; | |
} | |
// For the second group, add to the top margin so it appears below the first group and with some space between | |
if(i == 1) { | |
topMargin = topMargin + (rowHeight * settings.rows) + 24; | |
} | |
// Add the group labels | |
if(settings.labels == true) { | |
var textFrame = page.textFrames.add(); | |
textFrame.geometricBounds = [topMargin - 8, leftMargin, topMargin * 2, leftMargin * 4]; | |
textFrame.contents = fileGroupLabels[i]; | |
} | |
textFrame.parentStory.texts.item(0).appliedParagraphStyle = headingParagraphStyle; | |
// Construct the frames in reverse order to save us time later (when we place the graphics). | |
for (var rowCounter = settings.rows; rowCounter >= 1; rowCounter--) { | |
Y1 = topMargin + (rowHeight * (rowCounter - 1)); | |
Y2 = Y1 + frameHeight; | |
for (var columnCounter = settings.columns; columnCounter >= 1; columnCounter--) { | |
X1 = leftMargin + (columnWidth * (columnCounter - 1)); | |
X2 = X1 + frameWidth; | |
rectangle = page.rectangles.add(document.layers.item(-1), undefined, undefined, { | |
geometricBounds: [Y1, X1, Y2, X2], | |
strokeWeight: 0, | |
strokeColor: document.swatches.item("None") | |
}); | |
} | |
} | |
// Because we constructed the frames in reverse order, rectangle 1 is the first rectangle on page 1, | |
// so we can simply iterate through the rectangles, placing a file in each one in turn. myFiles = myFolder.Files; | |
for (counter = 0; counter < numberOfFrames; counter++) { | |
file = filegroups[i][counter]; | |
rectangle = page.rectangles.item(counter); | |
rectangle.place(File(file)); | |
rectangle.label = file.fsName.toString(); | |
if (settings.fitProportional) { | |
rectangle.fit(FitOptions.proportionally); | |
} | |
if (settings.centerContent) { | |
rectangle.fit(FitOptions.centerContent); | |
} | |
if (settings.fitFrameToContent) { | |
rectangle.fit(FitOptions.frameToContent); | |
} | |
if (settings.labels == true) { | |
addLabel(rectangle, settings.labelType, settings.labelHeight, settings.labelOffset, settings.labelStyle, settings.layerName); | |
} | |
} | |
if (settings.removeEmptyFrames == 1) { | |
for (var myCounter = document.rectangles.length - 1; myCounter >= 0; myCounter--) { | |
if (document.rectangles.item(myCounter).contentType == ContentType.unassigned) { | |
document.rectangles.item(myCounter).remove(); | |
} else { | |
//As soon as you encounter a rectangle with content, exit the loop. | |
break; | |
} | |
} | |
} | |
} | |
} | |
function addLabel(frame, labelType, labelHeight, labelOffset, labelStyleName, layerName) { | |
var document = app.documents.item(0); | |
var label; | |
var labelStyle = document.paragraphStyles.item(labelStyleName); | |
var labelLayer = document.layers.item(layerName); | |
var link = frame.graphics.item(0).itemLink; | |
//Label type defines the text that goes in the label. | |
switch (labelType) { | |
//File name | |
case 0: | |
label = (link.name).replace('.jpg', ''); | |
break; | |
//File path | |
case 1: | |
label = link.filePath; | |
break; | |
//XMP description | |
case 2: | |
try { | |
label = link.linkXmp.description; | |
if (label.replace(/^\s*$/gi, "") == "") { | |
throw myError; | |
} | |
} catch (myError) { | |
label = "No description available."; | |
} | |
break; | |
//XMP author | |
case 3: | |
try { | |
label = link.linkXmp.author | |
if (label.replace(/^\s*$/gi, "") == "") { | |
throw myError; | |
} | |
} catch (myError) { | |
label = "No author available."; | |
} | |
break; | |
} | |
var X1 = frame.geometricBounds[1]; | |
var Y1 = frame.geometricBounds[2] + labelOffset; | |
var X2 = frame.geometricBounds[3]; | |
var Y2 = Y1 + labelHeight; | |
var textFrame = frame.parent.textFrames.add(labelLayer, undefined, undefined, { | |
geometricBounds: [Y1, X1, Y2, X2], | |
contents: label | |
}); | |
textFrame.textFramePreferences.firstBaselineOffset = FirstBaseline.leadingOffset; | |
textFrame.parentStory.texts.item(0).appliedParagraphStyle = labelStyle; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment