How do you place an image from the local file system using BatchPlay?

Hi folks!

I’m trying to figure out how to place an image into a layer.

Anyone know the proper BatchPlay syntax for placing the image, using a storage Entry?

If I use a string for the _path I get: “invalid file token used,” which I expected.
If I use the Entry, I get: NAPI API failure: String expected.

The latter suggests I’m running the wrong BatchPlay command. Not having any luck finding the proper one. Any suggestions? :slight_smile:

Here’s a code snippet from my plugin:

// Place an image in the document, creating a new layer.
// "asset" is a simple object containing data about the image.
// "asset.file" is the string location of the image on the local file system
MGR.PlaceImage = async function (asset, log) {
	var layer, x, y, w, h, file;

    // SDK.IO.getEntry returns a UXP storage Entry
	file = await SDK.IO.getEntry(asset.file);

    // Build the BatchPlay command
	const cmd = [{
		"_obj": "placeEvent",
		"null": {
			"_path": file, // I've tried both the Entry and the plain string here to no avail
			"_kind": "local"
		},
		"offset": {
			"_obj": "offset",
			"horizontal": {
				"_unit": "pixelsUnit",
				"_value": 0
			},
			"vertical": {
				"_unit": "pixelsUnit",
				"_value": 0
			}
		},
		"_isCommand": true
	}];

	// Run the place.
	try {
        // MGR.Batch runs the BatchPlay command.
		await MGR.Batch(cmd);
	} catch (ex) {
		return { error: ex, command: 'PlaceImage', file: asset.file };
	}

Here’s what MGR.Batch looks like:

And this is what the file Entry looks like:

Entry is not enough… you need to make token from entry.

Possible I’m misunderstanding your answer, but the Entry I retrieve is from a dictionary that already has the appropriate persistent access token, previously granted by the user. (And if I don’t have it, there’s a fail state where it will re-request access).

So in this case, the Entry will already have the expected token and permissions shouldn’t be an issue.

I still think that one more conversion step is needed

Gotcha. Not sure what I’d be converting it to though? Do you have an example? I added a screenshot of the file Entry object above, if that helps.

@kerrishotts, know anyone who might have a good idea for me on this?

I think you’re looking for createSessionToken(entry)

2 Likes

Yup, createSessionToken(entry) is your friend :slight_smile:

1 Like

Simon, you’re a hero. And now I understand Jarda’s suggestion! haha Thanks all!

I’m trying to do the same thing. The function basically works, but I just have troubles to get the File object from the filename of the function call. For now I use the “getFileForOpening” function, which works nice. But how can I get the “entry” variable without the dialog and using the file parameter instead. Cheers.

Here’s the working function using getFileForOpening …

async function placeEmbedded(file) {
entry = await fs.getFileForOpening();
let token = fs.createSessionToken(entry);
const result = await batchPlay(
[
{
“_obj”: “placeEvent”,
“ID”: 6,
“null”: {
“_path”: token,
“_kind”: “local”
},
“freeTransformCenterState”: {
“_enum”: “quadCenterState”,
“_value”: “QCSAverage”
},
“offset”: {
“_obj”: “offset”,
“horizontal”: {
“_unit”: “pixelsUnit”,
“_value”: 0
},
“vertical”: {
“_unit”: “pixelsUnit”,
“_value”: 0
}
},
“_isCommand”: false,
“_options”: {
“dialogOptions”: “dontDisplay”
}
}
],{
“synchronousExecution”: false,
“modalBehavior”: “fail”
});
}

In order to work with files outside the plugin folder you MUST use the get file dialog (security issues).

You can create a persistent token and save this one so you only need to ask one time for the file (as long the path stays the same). Persistent tokens are persistent between PS sessions and PS updates.

1 Like

Damn. That’s not good. I want to save the filenames in a file an place them by their file reference rather than having to choose them via an unnecessary dialog. I guess I will have to find some other solution.

Cheers.

Two followup question …

*) Do I have to save the token via “localStorage.setItem(“persistent-file”, token);” so it’s accessible after restarting Photoshop?
*) Can the token be referenced by it’s string (like “persistent-token/75ef3924-5bba-451a-9c73-e37cd8a0d19a”) or is it an object?

I tried to pass the token to the function via its string value, but somehow this doesn’t work …
// PLACE EMBEDDED
async function placeEmbedded(token) {
const result = await batchPlay(
[
{
“_obj”: “placeEvent”,
“ID”: 6,
“null”: {
“_path”: token,
“_kind”: “local”
},
“freeTransformCenterState”: {
“_enum”: “quadCenterState”,
“_value”: “QCSAverage”
},
“offset”: {
“_obj”: “offset”,
“horizontal”: {
“_unit”: “pixelsUnit”,
“_value”: 0
},
“vertical”: {
“_unit”: “pixelsUnit”,
“_value”: 0
}
},
“_isCommand”: false,
“_options”: {
“dialogOptions”: “dontDisplay”
}
}
],{
“synchronousExecution”: false,
“modalBehavior”: “fail”
});
}

Thanks.

I never used localStorage but it should be persistent between PS sessions.

You can store it as string but you have to get the entry with getEntryForPersistentToken.

async function placeEmbedded(token) {
  token = await fs.getEntryForPersistentToken(token);
  const result = await batchPlay(
   ...
  )}
}
1 Like

Thanks for the reply. But somehow I can’t get it to work. The token string is correctly transferred to the function and the token in the function is created. But nothing gets embedded in the document. Here’s the adjusted function …

// PLACE EMBEDDED
async function placeEmbedded(tokenstring) {
  //entry = await fs.getFileForOpening();
  let token = await fs.getEntryForPersistentToken(tokenstring);
  const result = await batchPlay(
  [
     {
        "_obj": "placeEvent",
        "ID": 6,
        "null": {
           "_path": token,
           "_kind": "local"
        },
        "freeTransformCenterState": {
           "_enum": "quadCenterState",
           "_value": "QCSAverage"
        },
        "offset": {
           "_obj": "offset",
           "horizontal": {
              "_unit": "pixelsUnit",
              "_value": 0
           },
           "vertical": {
              "_unit": "pixelsUnit",
              "_value": 0
           }
        },
        "_isCommand": false,
        "_options": {
           "dialogOptions": "dontDisplay"
        }
     }
  ],{
     "synchronousExecution": false,
     "modalBehavior": "fail"
  });
}

And here’s the function that opens the file dialog and creates the persistent token …

// GET WATERMARK FILE
document.getElementById("button_watermarkfile").addEventListener("click", async evt => {
  try{
    var filename = await fs.getFileForOpening();
    if(filename) {
      var token = await fs.createPersistentToken(filename);
      document.querySelector("#input_watermarkfile").value = filename.name;
      document.querySelector("#input_watermarkfile").firstChild.value = token;
      localStorage.setItem("persistent-file", token);
    }
  } catch(error) {
    //console.log("Canceled");
  }
});

How are you calling placeEmbedded()?
Are you getting the tokenstring with localStorage.getItem("persistent-file")?

Often a try/catch around the code helps to track down the issue.

BTW please format the code in the posts for better readabelity (it’s the </> icon).

2 Likes

I call it like that … placeEmbedded(token); … where “token” is the string value of the token.

Oh, I forgot that you need also to create a session token from the received entry. These tokens can be confusing … :slight_smile:

// PLACE EMBEDDED
async function placeEmbedded(tokenstring) {
  const entry = await fs.getEntryForPersistentToken(tokenstring);
  const token = await fs.createSessionToken(entry);
  const result = await batchPlay(
   ...
1 Like

You’re awesome! It works. The next step now will be to save those tokens between sessions. I hope that works.

Thanks for now.

1 Like

So far the “Place Embedded” function works great.

// PLACE EMBEDDED
async function placeEmbedded(tokenstring) {
  //entry = await fs.getFileForOpening();
  const entry = await fs.getEntryForPersistentToken(tokenstring);
  const token = await fs.createSessionToken(entry);
  const result = await batchPlay(
  [
     {
        "_obj": "placeEvent",
        "ID": 6,
        "null": {
           "_path": token,
           "_kind": "local"
        },
        "freeTransformCenterState": {
           "_enum": "quadCenterState",
           "_value": "QCSAverage"
        },
        "offset": {
           "_obj": "offset",
           "horizontal": {
              "_unit": "pixelsUnit",
              "_value": 0
           },
           "vertical": {
              "_unit": "pixelsUnit",
              "_value": 0
           }
        },
        "_isCommand": false,
        "_options": {
           "dialogOptions": "dontDisplay"
        }
     }
  ],{
     "synchronousExecution": false,
     "modalBehavior": "fail"
  });
}

The next step would be to align the placed smart object to the tops, bottoms, lefts or rights. I tried to “Align Layers” approach, but the problem is, that the align process ignores the transparency of the smart object and the alignment ends up out outside the canvas.
What would be the best way to align the smart object to the chosen sides? Any suggestion would be appreciated.