How to edit Session Token's file name?

Hey all,
Is there a way to edit the sessions token’s file name? I am exporting jpgs using batchplay and for _path, I am using session token. However my script needs to be able to modify the file name (ex. adding prefix or suffix). It seems like the _name of the session token is read only. is there any other way to modify it?
Thank you all in advance

return await batchPlay(
[
{
_obj: “save”,
as: {
_obj: “JPEG”,
extendedQuality: jpgQuality,
matteColor: {
_enum: “matteColor”,
_value: “none”
}
},
in: {
_path: token,
_kind: “local”
},
lowerCase: true,
saveStage: {
_enum: “saveStageType”,
_value: “saveBegin”
},
_isCommand: false
}
],
{}
);

You want to generate a new token for each instance, for example by calling a generic save utility function , something like this:

const saveJPEG = async (saveFolder, newFilename, quality) => {
  try {
    const doc = app.activeDocument;

    const theNewFile = await saveFolder.createFile(newFilename, {
      overwrite: true,
    });
    const saveFile = await fs.createSessionToken(theNewFile);

    return batchPlay(
      [
        {
          _obj: "save",
          as: {
            _obj: "JPEG",
            extendedQuality: quality,
            matteColor: {
              _enum: "matteColor",
              _value: "none",
            },
          },
          in: {
            _path: saveFile,
            _kind: "local",
          },
          documentID: doc.id,
          lowerCase: true,
          saveStage: {
            _enum: "saveStageType",
            _value: "saveBegin",
          },
          _isCommand: true,
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
      ],
      {}
    );
  } catch (error) {
    console.log("JPEG save failed")
    console log(error);
  }
};

Thank you so much Timothy. I am having trouble generating new token from string path. I tried the script you provided. I can see that this script is using createFile but I am not using the getFolder() from ‘localFileSystem’. Is there a way to make tokens from string path and filename? I tried generate new tokens using ‘createEntryWithUrl’ but I couldn’t figure it out!

createSessionToken() receives an entry and not a string.

What’s your workflow?
E.g. where are your string paths coming from? Collated from open documents? Or from a list of files in a folder?

1 Like

I understand. My first entry comes from ‘fs.getFileForSaving’. this is what my script does:

  • new file using fs.getFileForSaving
  • make changes to the image
  • export jpg (filename-V1)
  • make further changes to the image
  • export jpg under same folder (filename-V2)

The reason I used getFileForSaving instead of getFolder is to get the base filename from the user.
but aside from that I would love to be able to either generate new entry from a string path. I tried ‘createEntryWithUrl’ but I cannot make it work for the life of me!
anytime I use it I get this error:

I think this becomes clear in the error message. Just to double check, have you checked whether the root of said path actually exists? ‘/Users/user’ looks like a generic example path to me. I think you need to construct this path, e.g. using ‘os’ module’s homedir() function. (Unless of course if your user name happens to be ‘user’)

2 Likes

To clarify, would you like to:

A)
• open user selected file
• perform edits to file
• save file
• perform more edits
• save file
End result: 3 files

Or

B)
• create new file
• open new file
• perform edits to file
• save file
• perform more edits
• save file
End result: 2 files

1 Like

thanks for the help. That was my first guess as well. but no matter what I do it seems like I cannot get the right syntax for the file address. (I tried both mac and windows). Do you happen to know what’s the right syntax for file path?

Thanks Timothy.
I am trying to do A. Sorry for being a bit vague. I was trying to avoid boring you with the complete function of my script!
The function of my script is to export every frame of the timeline (as a copy) into individual jpgs and tiffs. which is why I am exporting every frame with a base name and a sequencer. I get the base name from “getFileForSaving” and repeat the base for every frame. Except I don’t want the user to enter the file name for every frame! I was going to use the SaveAs property of the document but I won’t be able to export tiff as well. if I can get the “createEntryWithUrl” to work I can use the _nativepath from the entry and modify the name for every frame.
appologies in advance for the long post!
p.s: I am aware of image sequencer but that’s limited to 5000px and I need to have more control.

As far as createEntryWithURL goes, all the information is in the error message. It wasn’t able to find the path to ‘/Users/user/Documents’ because that doesn’t exist unless your user name on the Mac you’re trying this out happens to be ‘user’. It’s just a matter of specifying a correct path. So, rather than:

"file:/Users/user/Documents/tmp" it should be "file:/Users/YOUR_USERNAME/Documents/tmp"

Obviously you don’t want to hard-code this. And my guess would be this would be different on Windows. The right thing to do here is to construct this path using the ‘os’ module (described here) and its function ‘homedir()’

So something like this:

// Construct file path from os.homedir:
const filePath = path.join(os.homedir(), "Documents", "tmp");
const file = await fs.createEntryWithUrl(`file:${filePath}`)

I say ‘like’ because I haven’t tested this, but it should give you an idea on what to do… :slight_smile:

1 Like

thank you very much. :slight_smile:
I will try this out and let you know the result.

I’ve posted about this before in detail, but essentially you need a token for the folder you want to save to.
In that post I’m giving the example of getting a folder entry/token via getFolder() and then using getEntries() to get an array of the folder contents - your use case is different in that your starting point is a file and you want the parent folder.
You’ve got two choices:

  1. prompt the user for a save destination with getFolder()
  2. use getEntryWithUrl() using the path of the active document to get an entry and bypass any user input

I’d say that 2 is what you want from a UX perspective, but as you can see I’m perhaps not the best person to comment on that :sob:

1 Like

Thank you so much mate. appreciate all your helps.
I will definitely give this a try

Here you go, a rough demo of what you want.
It bypasses user input and gets an entry for the opened file’s parent folder using getEntryWithUrl() and then generates a session token for each jpeg as it saves them.

const { app, core } = require("photoshop");
const batchPlay = require("photoshop").action.batchPlay;
const lfs = require("uxp").storage.localFileSystem;

try {
  // Helper functions
  const getParentFolderEntry = async (filepath) => {
    const folderPath = path.dirname(filepath);
    return await lfs.getEntryWithUrl(folderPath);
  };

  const getAnimationFrameCount = async () => {
    return await batchPlay(
      [
        {
          _obj: "get",
          _target: [
            {
              _property: "frameCount",
            },
            {
              _ref: "animationClass",
              _enum: "ordinal",
              _value: "targetEnum",
            },
          ],
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
      ],
      {}
    );
  };

 const selectAnimationFrame = async (index) => {
    return await batchPlay(
      [
        {
          _obj: "select",
          _target: [
            {
              _ref: "animationFrameClass",
              _index: index,
            },
          ],
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
      ],
      {}
    );
  };

  const makeSnapshot = async (name) => {
    return batchPlay(
      [
        {
          _obj: "make",
          _target: [
            {
              _ref: "snapshotClass",
            },
          ],
          from: {
            _ref: "historyState",
            _property: "currentHistoryState",
          },
          name: name,
          using: {
            _enum: "historyState",
            _value: "fullDocument",
          },
          _isCommand: true,
          _options: {},
        },
      ],
      {}
    );
  };

  const revertToSnapshot = async (name) => {
    return batchPlay(
      [
        {
          _obj: "select",
          _target: [
            {
              _ref: "snapshotClass",
              _name: name,
            },
          ],
          _isCommand: true,
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
      ],
      {}
    );
  };

  const deleteSnapshot = async (name) => {
    return batchPlay(
      [
        {
          _obj: "delete",
          _target: [
            {
              _ref: "historyState",
              _name: name,
            },
          ],
          _isCommand: true,
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
      ],
      {}
    );
  };

  const saveJPEG = async (saveFolder, newFilename, quality) => {
    try {
      const doc = app.activeDocument;
      const theNewFile = await saveFolder.createFile(newFilename, {
        overwrite: true,
      });
      const saveFile = await lfs.createSessionToken(theNewFile);
       return batchPlay(
        [
          {
            _obj: "save",
            as: {
              _obj: "JPEG",
              extendedQuality: quality,
              matteColor: {
                _enum: "matteColor",
                _value: "none",
              },
            },
            in: {
              _path: saveFile,
              _kind: "local",
            },
            documentID: doc.id,
            lowerCase: true,
            saveStage: {
              _enum: "saveStageType",
              _value: "saveBegin",
            },
            _isCommand: true,
            _options: {
              dialogOptions: "dontDisplay",
            },
          },
        ],
        {}
      );
    } catch (error) {
      console.log(error);
    }
  };

  // Main process
  await core.executeAsModal(async () => {
    const file = await lfs.getFileForOpening();
    const saveFolder = await getParentFolderEntry(file.nativePath);
     await app.open(file);
     const doc = app.activeDocument;
     const baseFilename = doc.name.slice(0, doc.name.lastIndexOf("."));

     const countFrames = await getAnimationFrameCount();

     const frameCount = countFrames[0].frameCount;

     for (let i = 0; i < frameCount; i++) {
       const newFilename = `${baseFilename}_${i + 1}`;

       await selectAnimationFrame(i + 1);

       await makeSnapshot(newFilename);

       doc.flatten();

       await saveJPEG(saveFolder, newFilename, 12);

       await revertToSnapshot(newFilename);

       await deleteSnapshot(newFilename);
     }
   });
 } catch (error) {
   console.error(error);
   console.trace(error);
 }

You’ll also need to have your manifest conform to v5 like so:

{
  "host": [
    {
      "app": "PS",
      "minVersion": "24.6.2",
      "data": {
        "apiVersion": 2
      }
    }
  ],
  "manifestVersion": 5,
   "requiredPermissions": {
    "localFileSystem": "fullAccess"
  }
  // ...all other manifest properties
}