Skip to main content
March 11, 2017
Question

Script - stack images from folder + text from excel sheet

  • March 11, 2017
  • 6 replies
  • 4560 views

Hi everyone!

I've been using photoshop for a while but it is the first time I want to use the script features. Thus, I'm a little bit confused...

I've been searching among previous subjects but did not find any answer related to my issue.

I want to do the following :

> I have a folder with 100 pictures.

> I have an excel sheet with 100 quotes (1 per row in the first column, from row 1 to 100).

> I have a template.psd containing 4 layers (from foreground to background):

     - 1 for the quote from the excel sheet.

     - 1 for a static text which has to be on every picture.

     - 1 for a "filter" (black square with 50% as opacity, in order to make the background image darker).

     - 1 for the image from the folder (which has to be set as background).

> I want to stack on the template the relevant picture and quote, from 1 to 100.

I've been told that I could run a script that, from 1 to 100, takes the picture number x, quote number x, assign them at the right place in the template and export the result in a folder.

However, as I am totally new to photoshop scripting, I really don't know where to start. Therefore, my questions are :

> what should I write in my script so that it executes the actions I want to perform?

> how to launch such a script?

I really hope that you guys can help me with that!

Victoria

This topic has been closed for replies.

6 replies

Participant
March 13, 2017

these adobe charges of software a terrible

Jarda Bereza
Inspiring
March 12, 2017

You shouldn't write script in Word. Use notepad instead ;-) And you need rename file extension from .txt to .jsx

You maybe will need change settings to show extension.

March 12, 2017

I exported the script using Webstorm editor as .jsx file, it didn't work too. Then I tried with .js file but it was not better.

In both cases, it printed :

Ligne: 331

->  })(); 

Kukurykus
Legend
January 21, 2018

When you compare both version of code you'll notice there is lack of first character in second version code: (

Jarda Bereza
Inspiring
March 12, 2017

If you convert paragraph to point text, there is possibility, that text which overflow frame will be removed. So make sure that your frame is enought big or catch these cases with script ;-)

Kukurykus
Legend
January 21, 2018

I had the same problem with other script converting paragraph to point. That cut some side text. I am not sure was that solution I found but probably I changed text composition from ADOBEEVERYLINE, when it was still PARAGRAPHTEXT to:

(tI = activeDocument.activeLayer.textItem).textComposer = TextComposer.ADOBESINGLELINE, tI.kind = TextType.POINTTEXT

Jarda Bereza
Inspiring
March 12, 2017

1. There's no way to vertically center-align text in Paragraph. You will have to tinker text layer manually, after the script is finished working.

You can convert paragraph text to "point" text. Line breaks will remain. This will change layer bounds and you can center layer itself. Then you can: 1) select all canvas 2) center layer to selection 3)deselect 4) save 5) do some undos before point 1 if needed

If you work with artboards, you don't need selection.

Tomas Sinkunas
Legend
March 12, 2017

Updated version of the script with some notes:

1. First layer in template file has to be paragraph text and has to be the size of available space for text. Final quote is aligned based on paragraph center. Thanks to Jarda Bereza I didn't know one can convert POINTTEXT to PARAGRAPH text. Live and learn.

2. You also have to save you Quotes spreadsheet file as CSV file with ; as a delimiter. If you use different delimiter, you have to update scripts quotesArray variable on line 159 (second argument). Also, script assumes your quotes are on first column. If not, then you have to set column number in quotesArray variable (third argument)

3. Script saves final files as PSD files, so you can go in and adjust TextLayers as you need;

function(){

    #target photoshop;

    var prefs = { // Create global object to store values

        templateFile : undefined,

        quotesFile : undefined,

        imagesFolder : undefined,

        outputFolder : undefined,

        saveNumber : undefined,

    }

    buildUI();

    function buildUI() {

        var txtWidth = 100;

        var inputSize = 226;

        var minSpace = 2;

        var win = new Window('dialog', "Queter");

            win.alignChildren = ["fill", "fill"];

            win.spacing = 5;

            win.grpTemplateFile = win.add("group");

            win.grpTemplateFile.spacing = minSpace;

            win.grpTemplateFile.stTemplateFile = win.grpTemplateFile.add('statictext', undefined, "Template File");

            win.grpTemplateFile.etTemplateFile = win.grpTemplateFile.add('edittext', undefined, "");

            win.grpTemplateFile.btnSelectTemplateFile = win.grpTemplateFile.add('button', undefined, "...");

            win.grpTemplateFile.stTemplateFile.preferredSize.width = txtWidth;

            win.grpTemplateFile.etTemplateFile.preferredSize.width = inputSize;

            win.grpTemplateFile.btnSelectTemplateFile.onClick = function () {

                var templateFile = selectFiles(false, "psd");

                if (templateFile === null) return;

                win.grpTemplateFile.etTemplateFile.text = File.decode(templateFile);

            }

            win.grpQuotesFile = win.add("group");

            win.grpQuotesFile.spacing = minSpace;

            win.grpQuotesFile.stQuotesFile = win.grpQuotesFile.add('statictext', undefined, "Quotes File");

            win.grpQuotesFile.etQuotesFile = win.grpQuotesFile.add('edittext', undefined, "");

            win.grpQuotesFile.btnSelectQuotesFile = win.grpQuotesFile.add('button', undefined, "...");

            win.grpQuotesFile.stQuotesFile.preferredSize.width = txtWidth;

            win.grpQuotesFile.etQuotesFile.preferredSize.width = inputSize;

            win.grpQuotesFile.btnSelectQuotesFile.onClick = function () {

                var quotesFile = selectFiles(false, "csv");

                if (quotesFile === null) return;

                win.grpQuotesFile.etQuotesFile.text = File.decode(quotesFile);

            }

            win.grpImagesFolder = win.add("group");

            win.grpImagesFolder.spacing = minSpace;

            win.grpImagesFolder.stImagesFolder = win.grpImagesFolder.add('statictext', undefined, "Images Folder");

            win.grpImagesFolder.etImagesFolder = win.grpImagesFolder.add('edittext', undefined, "");

            win.grpImagesFolder.btnSelectImagesFolder = win.grpImagesFolder.add('button', undefined, "...");

            win.grpImagesFolder.stImagesFolder.preferredSize.width = txtWidth;

            win.grpImagesFolder.etImagesFolder.preferredSize.width = inputSize;

            win.grpImagesFolder.btnSelectImagesFolder.onClick = function () {

                var imagesFolder = Folder.selectDialog("Select images folder");

                if (imagesFolder === null) return;

                win.grpImagesFolder.etImagesFolder.text = File.decode(imagesFolder);

            }

            win.grpOutputFolder = win.add("group");

            win.grpOutputFolder.spacing = minSpace;

            win.grpOutputFolder.stOutputFolder = win.grpOutputFolder.add('statictext', undefined, "Output Folder");

            win.grpOutputFolder.etOutputFolder = win.grpOutputFolder.add('edittext', undefined, "");

            win.grpOutputFolder.btnSelectOutputFolder = win.grpOutputFolder.add('button', undefined, "...");

            win.grpOutputFolder.stOutputFolder.preferredSize.width = txtWidth;

            win.grpOutputFolder.etOutputFolder.preferredSize.width = inputSize;

            win.grpOutputFolder.btnSelectOutputFolder.onClick = function () {

                var outputFolder = Folder.selectDialog("Select output folder");

                if (outputFolder === null) return;

                win.grpOutputFolder.etOutputFolder.text = File.decode(outputFolder);

            }

            win.grpSaveIndex = win.add("group");

            win.grpSaveIndex.spacing = minSpace;

            win.grpSaveIndex.stSaveNumber = win.grpSaveIndex.add('statictext', undefined, "Save Start #");

            win.grpSaveIndex.etSaveNumber = win.grpSaveIndex.add('edittext', undefined, 0);

            win.grpSaveIndex.stSaveNumber.preferredSize.width = txtWidth;

            win.grpSaveIndex.etSaveNumber.preferredSize.width = inputSize;

            win.grpActionButtons = win.add("group");

            win.grpActionButtons.spacing = minSpace;

            win.grpActionButtons.margins.top = 20;

            win.grpActionButtons.alignChildren = ["fill", "fill"];

            win.grpActionButtons.btnCloseWindow = win.grpActionButtons.add('button',undefined, "Cancel");

            win.grpActionButtons.btnRunScript  = win.grpActionButtons.add('button',undefined, "Run Script");

            win.grpActionButtons.btnCloseWindow.onClick = function () {

                win.close();

            }

            win.grpActionButtons.btnRunScript.onClick = function () {

                if (!File(win.grpTemplateFile.etTemplateFile.text).exists) {

                    win.grpTemplateFile.etTemplateFile.active = true;

                    return alert("Please select Template File");

                } else if (!File(win.grpQuotesFile.etQuotesFile.text).exists) {

                    win.grpQuotesFile.etQuotesFile.active = true;

                    return alert("Please select Quotes File");

                } else if (!Folder(win.grpImagesFolder.etImagesFolder.text).exists) {

                    win.grpImagesFolder.etImagesFolder.active = true;

                    return alert("Please select Images Folder");

                } else if (!Folder(win.grpOutputFolder.etOutputFolder.text).exists) {

                    win.grpOutputFolder.etOutputFolder.active = true;

                    return alert("Please select Output Folder");

                } else if (isNaN(parseInt(win.grpSaveIndex.etSaveNumber.text)) || parseInt(win.grpSaveIndex.etSaveNumber) < 0) {

                    win.grpSaveIndex.etSaveNumber.active = true;

                    return alert("Please enter valid Save Start Number");

                }

                prefs.templateFile  = File(win.grpTemplateFile.etTemplateFile.text);

                prefs.quotesFile    = File(win.grpQuotesFile.etQuotesFile.text);

                prefs.imagesFolder  = Folder(win.grpImagesFolder.etImagesFolder.text);

                prefs.outputFolder  = Folder(win.grpOutputFolder.etOutputFolder.text);

                prefs.saveNumber    = parseInt(win.grpSaveIndex.etSaveNumber.text);

                win.close();

                main();

            }

        win.show();

    }

    function main() {

        try {

            // Change project unites to Pixels

            var originalUnits = app.preferences.rulerUnits;

            app.preferences.rulerUnits = Units.PIXELS;

            // Move all quotes to an array;

            // Provide quotes File, delimiter and columnNumber

            var quotesArray = readQuotes(prefs.quotesFile, ";", 0);

          

            // Move all image file to an array

            var imagesArray = prefs.imagesFolder.getFiles(/\.(png|jpg|tif)$/i);

            // Establish For loop length

            var loopLength = (quotesArray.length <= imagesArray.length) ? quotesArray.length : imagesArray.length;

            var templateDoc = app.open(prefs.templateFile);          

            var quotesLayer = templateDoc.layers[0];

            if (quotesLayer.kind !== LayerKind.TEXT || quotesLayer.textItem.kind !== TextType.PARAGRAPHTEXT)

                return alert("First layer has to be Paragraph text.");

            // Store paragraph layer data

            var paragraphData = {

                width : quotesLayer.textItem.width,

                height : quotesLayer.textItem.height,

                position : quotesLayer.textItem.position,

                center : {

                    x : Number(quotesLayer.textItem.width/2 + quotesLayer.textItem.position[0]),

                    y : Number(quotesLayer.textItem.height/2 + quotesLayer.textItem.position[1])

                }

            };

            var backgroundDoc, backgroundLayer, oldBackgroundLayer;

            for (var i = 0; i < loopLength; i ++) {

                // Update text layer with new Quote

                quotesLayer.textItem.contents = quotesArray;

                quotesLayer.textItem.kind = TextType.POINTTEXT;

                moveLayer(quotesLayer, paragraphData.center);

                quotesLayer.textItem.kind = TextType.PARAGRAPHTEXT;

                // Remove old background layer from the document

                oldBackgroundLayer = templateDoc.artLayers.getByName("Image") ;

                oldBackgroundLayer && oldBackgroundLayer.remove();

                // Open background Image file and copy first layer to template document

                backgroundDoc = app.open(imagesArray);

                backgroundDoc.changeMode(ChangeMode.RGB);

                backgroundLayer = backgroundDoc.layers[0].duplicate(templateDoc, ElementPlacement.PLACEATEND);

                backgroundDoc.close(SaveOptions.DONOTSAVECHANGES);

                backgroundLayer.name = "Image";

                resizeLayer(backgroundLayer, templateDoc);

                moveLayer(backgroundLayer);

              

                savePSD(File(prefs.outputFolder + "/" + (prefs.saveNumber + i) + ".psd"));

                // Restor original Paragraph layer data.

                quotesLayer.textItem.width = paragraphData.width;

                quotesLayer.textItem.height = paragraphData.height;

                quotesLayer.textItem.position = paragraphData.position;

            }

            templateDoc.close(SaveOptions.DONOTSAVECHANGES);

            app.preferences.rulerUnits = originalUnits;

            alert("Done");

        } catch (e) {

            alert(e.toString() + "\nLine: " + e.line.toString());

        }    

    }

    ////// Helper Functions

  

    function moveLayer(layer, position) {

        position = position || {

            x : app.activeDocument.width/2,

            y : app.activeDocument.height/2

        };

        var layerCenterHor = Number(layer.bounds[2] - layer.bounds[0]) / 2;

        var layerCenterVer = Number(layer.bounds[3] - layer.bounds[1]) / 2;

        var dX = position.x - layerCenterHor - Number(layer.bounds[0]);

        var dY = position.y - layerCenterVer - Number(layer.bounds[1]);

        layer.translate(dX, dY);

    }

    function resizeLayer(layer, doc) {

        var percent = calculatePercent(layer, doc);

        layer.resize(percent, percent, AnchorPosition.MIDDLECENTER);

  

        function calculatePercent(layer, doc) {

            var layerWidth  = Number(layer.bounds[2] - layer.bounds[0]);

            var layerHeight = Number(layer.bounds[3] - layer.bounds[1]);

            return percent  = (doc.width / layerWidth > doc.height / layerHeight)

                            ? 100 * doc.width / layerWidth

                            : 100 * doc.height / layerHeight;

        }

    }

    function savePSD(saveFile) {

        var saveOptions = new PhotoshopSaveOptions();

            saveOptions.embedColorProfile = true;

            saveOptions.alphaChannels = true;

            saveOptions.layers = true;

        activeDocument.saveAs(saveFile, saveOptions, true, Extension.LOWERCASE);

    };

    function readQuotes(quotesFile, deliminator, columnNumber) {

        var fileContent = readFile(quotesFile),

            lines = fileContent.split(/\n|\r/),

            quotesArray = [],

            singleQuote = "";

      

        for (var i = 0, il = lines.length; i < il; i ++) {

            singleQuote = lines.split(deliminator)[columnNumber];

            if (singleQuote !== "")

                quotesArray.push(singleQuote);

        }

        return quotesArray;

        function readFile(file) {

            file.open('r');

            var fileContent = file.read();

            file.close();

            return fileContent;

        }

    }

  

    function selectFiles (multiSelect, extension) {

        var theString = "Please select file",

            theFiles;

        if ($.os.search(/windows/i) != -1)

            theFiles = File.openDialog(theString, "*." + extension, multiSelect);

        else

            theFiles = File.openDialog(theString, getFiles, multiSelect);

        function getFiles(theFile) {

            var re = new RegExp("\.(" + extension + ")$","i");

            if (theFile.name.match(re) || theFile.constructor.name == "Folder")

                return true

        }

        return theFiles;

    }

})();

March 12, 2017

Hi guys!

Thank you very much for your help and especially, thank a lot to Tomas! You may have spend a lot of time on that, I am really grateful, it saves me a lot!

I tried to run the script but I am not sure about how it works.. I exported the script as a .js file and open it using file > scripts > browse, but it didn't work... I had the following message printing :

Line: 2

->  <?mso-application progid="Word.Document"?>

What did I do wrong?

Once again, thanks a lot Tomas!!

Victoria

Tomas Sinkunas
Legend
March 11, 2017

Hello Victoria.

I could help you out, but for that I'd need your setup.

Could you please upload some files that you are describing above? I dont want to go guessing if I get a correct setup as you have. So having your files would speed up process very much.

Thank you.

March 11, 2017

Hi Tomas!

I don't know how to upload on the forum the files (excel and .psd) thus I will try to explain and join screenshots

First, I'm not sure if this is relevant but I use a mac.

The images I use are these kind of images :

951 Landscape Pictures | Free HD Stock Photos | Unsplash

I download them and store them in a folder.

The quotes are stored in the first column of a excel document (the other columns contain other informations but I can erase them) as follow :

As you can see, some of the quotes are longer or shorter than the average. Thus I would love that my script be able to adapt my template to these variations of the quote's length (like automatically line breaking or size-reducing).

Here is a screenshot of my psd template :

A few comments :

> as you can see, it is squared. Thus, I'd like the images to adapt to this shape.

> there are 4 layers :

     - "Quote" (which has to be replaced by the ones in the excel sheet),

     - "logo" (my name at the bottom of the template),

     - "filtre" (a black square which has to make the background image look darker)

     - and "image". it is a white square which has to be replaced by the images in the folder.

As an output, I want the images to be stored as .png files in another folder. I would love if they could have a number as name. I would like to specify the first number, and that the script automatically raise this number by 1 for every following picture (example : if I choose "300", the first picture would be named 300.png, the following picture 301.png, and so on).

I think these are all the informations I can provide you with...

Do you see anything else I should tell you?

Many thanks for your help, it really saves me!!

Victoria

March 11, 2017

Aha, so First quote gets first image in the folder, second quote gets second image in the folder etc etc?


Yes, you get it!

SuperMerlin
Inspiring
March 11, 2017

You may not need a script, check out Data driven Variables.

Working with Variables in Photoshop - YouTube

Creating data-driven graphics in Photoshop

Jarda Bereza
Inspiring
March 11, 2017

SuperMerlin: It's always the same. Nobody care that there is buil-in and ready to use function right for this cases which reads data from excel and can generate images according data.

Everybody wants spend several hours/days with writing custom difficult scripting even if they don't have experience with scripting.

I don't get it. :-D :-D

Tomas Sinkunas
Legend
March 12, 2017

Jarda Bereza​ I don't think your comment helps anyone in this thread. If you don't feel like providing any help - that's your choice. I personally love scripting, so I just go and do that.