Improve Performance on executeTransaction & monitoring progress

Hi all,
I have a function that adds pre-defined metadata to the clipName of selected Clips in a Sequences. This has to happen as quick as possibles as it reacts to an interactive input mask and should feel responsive. To give feedback to the user, I´m adding an overlay with a progressBar.
2 Questions:

  1. Is there a way to improve the performance on executeTransactions, anything that helps make them faster? E.g. can I create the createSetNameActions all at once and then do the compoundAction.addAction() with a whole array of actions? Any other ways to make this faster?

  2. The way I´m updating the progress bar right now it almost straight away goes to 100 although the clips are not all renamed yet. I guess addAction just adds the actions to a premiere actions queue and returns straight away even before the action is actually executed. Is there a way to monitor the actions queued inside premiere or to get an update once the queue is finished?

Cheers!

async function writeMetadataToClipname(writeData) {
  console.log("writeMetadataToClipname");
  var project = await ppro.Project.getActiveProject();
  var seq = await project.getActiveSequence();
  var selection = await seq.getSelection();
  var selectedItems = await selection.getTrackItems();
  
  const renameActions = [];
  for (var i = 0; i < selectedItems.length; i++) {
    var item = selectedItems[i];
    if (Object.prototype.toString.call(item) !== '[object VideoClipTrackItem]') {
      continue;
    }
    
    const getName = await item.getName();
    let originalName, originalData;
    
    if (getName.includes("//MD:")) {
      const parts = getName.split("//MD:");
      originalName = parts[0];
      const originalDataString = parts[1];
      try {
        originalData = JSON.parse(originalDataString);
      } catch (error) {
        console.log("Error parsing existing metadata, starting fresh:", error);
        originalData = {};
      }
    } else {
      originalName = getName;
      originalData = {};
    }
    
    const newData = Object.assign({}, originalData, writeData);
    for (const key in newData) {
      if (newData[key] === "") {
        delete newData[key];
      }
    }
    const newDataString = JSON.stringify(newData);
    const newName = originalName + "//MD:" + newDataString;
    
    renameActions.push({ item: item, newName: newName });
  }
  console.log("renameActions: " + renameActions.length);
  
  if (renameActions.length > 0) {
    try {
      await new Promise((resolve, reject) => {
        project.lockedAccess(() => {
          try {
            const success = project.executeTransaction((compoundAction) => {
              var i = 0;
              for (const { item, newName } of renameActions) {
                updateProgressbar(i / renameActions.length);
                i++;
                const renameAction = item.createSetNameAction(newName);
                compoundAction.addAction(renameAction);
              }
            }, "rename trackItems");
            
            if (success) {
              resolve();
            } else {
              reject(new Error("Transaction execution failed"));
            }
          } catch (error) {
            reject(error);
          }
        });
      });
      console.log(`Successfully renamed ${renameActions.length} track items`);
    } catch (error) {
      console.log("Error renaming trackItems:", error);
    }
  }
}

1 * Seems like you already batch all execute within one transaction, I imaging that would help.
On my side I am doing as little as possible within the Lock event (so the UI at least feel responsive)
For instance all action are created ahead of time & outside the Lock.

[ Note: I have not measure this, so cannot know the affect it has - or not. You are welcome to try, measure and report your finding here :slight_smile: ]

Also for large set of actions that take a longer time, I also tried to have a smaller batch (that lock, release and “sleep” for a small time to let UI refresh) but it does not really help, so I may end up removing.

2 * I’m have not find any event that would trigger when a (projectItem?) rename is complete (although I thought I saw something similar in the doc a couple of month back, but my memory might play tricks on me).
As a (hackish) solution, you can probably setup an async loop to check for that &update. Not pretty (event would be much nicer) but might be a solution that work for your need:

  • Gather all ProjectItems your are renaming (along with new name you want to have)
  • Kick the loop (use promises to “sleep” in between calls)
  • on callback, check if the name for the projectItem has changed; if so remove from list of tracked items, and move onto the next projectItem in the list - once all item in the list have been parsed update your global %
  • if the list is not empty, schedule the next callback to this loop

Another option might be to split into multiple executeTransaction and update your slider in between calls.
But would probably take more time (repeat “lock→ execute→ unlock→update slider”, I assume lock is an expensive operation).
May not be appropriate for what you what to achieve (especially given your question #1)

Thanks for your help.

1 * I guess batch executing them in one transaction definitely helps - in general it also is way faster than CEP already. I just hoped there maybe was an option to just execute them all at once and kind of pass compoundAction an array of actions in one go. But as Premiere also executed them one after another this seems like it would not speed anything up anyways.
I actually wanted to create all actions ahead of time and then lock & execute them, but for some weird reason some createActions only work within the Lock Event (that´s why I was so confused in the other thread where I thought createSetNameAction was not working properly). Or can you see any other issues in this function with actions being created outside the lock event

async function writeMetadataToClipname(writeData) {
  var project = await ppro.Project.getActiveProject();
  var seq = await project.getActiveSequence();
  var selection = await seq.getSelection();
  var selectedItems = await selection.getTrackItems();
  
  const renameActions = [];
  const onlyVideoClips = [];
  for (var i = 0; i < selectedItems.length; i++) {
    var item = selectedItems[i];
    if (Object.prototype.toString.call(item) === '[object VideoClipTrackItem]') {
      onlyVideoClips.push(item);
    }
  }
  for (var i = 0; i < onlyVideoClips.length; i++) {
    var item = onlyVideoClips[i];
    const getName = await item.getName();
    let originalName, originalData;
    
    if (getName.includes("//MD:")) {
      const parts = getName.split("//MD:");
      originalName = parts[0];
      const originalDataString = parts[1];
      try {
        originalData = JSON.parse(originalDataString);
      } catch (error) {
        console.log("Error parsing existing metadata, starting fresh:", error);
        originalData = {};
      }
    } else {
      originalName = getName;
      originalData = {};
    }
    
    const newData = Object.assign({}, originalData, writeData);
    for (const key in newData) {
      if (newData[key] === "") {
        delete newData[key];
      }
    }
    const newDataString = JSON.stringify(newData);
    const newName = originalName + "//MD:" + newDataString;
    
    var renameAction = item.createSetNameAction(newName);
    renameActions.push(renameAction);
  }
  
  if (renameActions.length > 0) {
    try {
      await new Promise((resolve, reject) => {
        project.lockedAccess(() => {
          try {
            const success = project.executeTransaction((compoundAction) => {
              var i = 0;
              for (const action of renameActions) {
                updateProgressbar(i / renameActions.length);
                i++;
                compoundAction.addAction(action);
              }
            }, "rename trackItems");
            
            if (success) {
              resolve();
            } else {
              reject(new Error("Transaction execution failed"));
            }
          } catch (error) {
            reject(error);
          }
        });
      });
    } catch (error) {
      console.log("Error renaming trackItems:", error);
    }
  }
}

2 * I also already tried changing my updateProgressbar function with a promise to wait for the panel to render the next frame and then resolve but this made the whole progress a lot slower.
Maybe I´ll actually try adding a async loop to check and update the slider. Will give an update on how it affects performance and also compare it with splitting the whole batch into maybe 10-20 parts and updating the progress bar in between.

I am working on moving a bigger Project from CEP to UXP, looking forward to comparing performances later in the process. There were some operations that took 30mins and more so I definitely want to find a solution to give feedback on the progress.

Cheers!