Skip to main content
gui_paulino
Participant
July 15, 2025
Question

Automating partial text formatting across multiple comps

  • July 15, 2025
  • 2 replies
  • 191 views

Hello all,

I'm trying to create a script or expression that scans multiple comps and text layers, detects a specified word or phrase, and applies formatting (e.g. bold or faux bold) only to that part of the text wherever it occurs.

For example, I have several comps with similar content but different aspect ratios. Each comp contains 3 or 4 text layers. Let's say one of the layers contains the sentence:

"Your payment terminal 20% OFF right now!"
(using Inter Regular, size 28)

Instead of manually opening each comp, finding "20% OFF", and applying Inter Bold or faux bold, is there a way to automate this process?

Thanks in advance!

2 replies

Inspiring
July 16, 2025

After Effects comes with a script that almost does what you need, but only checks selected layers in the active comp. I've modified it to check every layer in every comp in the project. I've done some basic testing with it and it seems to work, however it has not been tested in complex projects so I would strongly advise you to have a backup copy of your project saved as this could very well cause a crash in complex projects with a large amount of compositions with a lot of text layers to check. So make sure to have a backup incase. Otherwise, hope this works for you. 

 

Copy and paste this into Notepad or any similar text editor and Save As a .jsx file. If saving in Notepad, when the File Explorer pops up you have to change "Save as type" (under the File Name box) to "All Files (*.*)". Then name it whatever you want followed by ".jsx".

{
	// Find and Replace Text.jsx
	// 
	// This script finds and/or replaces text in the Source Text property of 
	// all selected text layers.
	// 
	// It presents a UI with two text entry areas: a Find Text box for the
	// text to find and a Replacement Text box for the new text.
	//
	// When the user clicks the Find All button, the layer selection is modified 
	// to include only those text layers that include the Find Text string as a 
	// value in the Source Text property or any keyframe on the Source Text 
	// property.
	//
	// When the user clicks the Replace All button, the layer selection is 
	// modified as for Find All, and all instances of the Find Text string are 
	// are replaced in the Source Text property and any keyframes on the 
	// Source Text property.
	//
	// A button labeled "?" provides a brief explanation.


	function FindAndReplaceText(thisObj)
	{
		var scriptName = "Find and Replace Text";
		var myFindString    = "";
		var myReplaceString = "";

		// This function is used  during the Find All process.
		// It deselects the layer if it is not a text layer or if it does not
		// contain the Find Text string.
		function deselectLayerIfFindStringNotFound(theLayer, findString)
		{
			// foundIt is initialized to false. It is set to true only if the Find Text string is 
			// contained in the Source Text (sourceText) property, as determined by the  
			// test in the nested if/else block below.
			var foundIt = false;

			// Get the Source Text property, if there is one.
			var sourceText = theLayer.sourceText;
			// Test to see if the Find Text value is contained in the Source Text property.
			if (sourceText != null) {
				if (sourceText.numKeys == 0) {
					// textValue is a TextDocument. Check the string inside.
					if (sourceText.value.text.indexOf(findString) != -1) {
						foundIt = true;
					}
				} else {
					// Do the test for each keyframe:
					for (var keyIndex = 1; keyIndex <= sourceText.numKeys; keyIndex++) {
						// textValue is a TextDocument. Check the string inside.
						var oldString = sourceText.keyValue(keyIndex).text;
						if (sourceText.keyValue(keyIndex).text.indexOf(findString) != -1) {
							foundIt = true;
							break;
						}
					}
				}
			}
			// Deselect the layer if foundIt was not set to true in the tests of the Source Text property.
			if (foundIt == false) {
				theLayer.selected = false;
			}
		}

		// This function is called when the Find All button is clicked.
		// It changes which layers are selected by deselecting layers that are not text layers
		// or do not contain the Find Text string. Only text layers containing the Find Text string 
		// will remain selected.
		function onFindAll()
{
	if (myFindString == "") {
		alert("No text was entered in the Find Text box. The selection was not changed.", scriptName);
		return;
	}

	app.beginUndoGroup("Find All");

	for (var i = 1; i <= app.project.numItems; i++) {
		var item = app.project.item(i);
		if (item instanceof CompItem) {
			for (var j = 1; j <= item.numLayers; j++) {
				var layer = item.layer(j);
				deselectLayerIfFindStringNotFound(layer, myFindString);
			}
		}
	}

	app.endUndoGroup();
}


		// This function takes totalString and replaces all instances of 
		// findString with replaceString.
		// Returns the changed string.
		function replaceTextInString(totalString, findString, replaceString)
		{
			// Use a regular expression for the replacement.
			// The "g" flag will direct the replace() method to change all instances
			// of the findString instead of just the first.
			var regularExpression = new RegExp(findString,"g");
			var newString = totalString.replace(regularExpression, replaceString);
			return newString;
		}

		// This function replaces findString with replaceString in the layer's 
		// sourceText property.
		// The method changes all keyframes, if there are keyframes, or just 
		// the value, if there are not keyframes.
		function replaceTextInLayer(theLayer, findString, replaceString)
		{
			var changedSomething = false;

			// Get the sourceText property, if there is one.
			var sourceText = theLayer.sourceText;
			if (sourceText != null) {
				if (sourceText.numKeys == 0) {
					// textValue is a TextDocument. Retrieve the string inside
					var oldString = sourceText.value.text;
					if (oldString.indexOf(findString) != -1) {
						var newString = replaceTextInString(oldString, findString, replaceString);
						if (oldString != newString) {
							sourceText.setValue(newString);
							changedSomething = true;
						}
					}
				} else {
					// Do it for each keyframe:
					for (var keyIndex = 1; keyIndex <= sourceText.numKeys; keyIndex++) {
						// textValue is a TextDocument. Retrieve the string inside
						var oldString = sourceText.keyValue(keyIndex).text;
						if (oldString.indexOf(findString) != -1) {
							var newString = replaceTextInString(oldString, findString, replaceString);
							if (oldString != newString) {
								sourceText.setValueAtKey(keyIndex,newString);
								changedSomething = true;
							}
						}
					}
				}
			}
			// Return a boolean saying whether we replaced the text
			return changedSomething;
		}

		// Called when the Replace All button is clicked
		// Replaces the Find Text string with the Replacement Text string everywhere within 
		// the set of selected layers.  Does not change the selected flag of any layers.
	function onReplaceAll()
{
	if (myFindString == "") {
		alert("No text was entered in the Find Text box. No changes were made.", scriptName);
		return;
	}

	app.beginUndoGroup("Replace All");

	var numLayersChanged = 0;

	for (var i = 1; i <= app.project.numItems; i++) {
		var item = app.project.item(i);
		if (item instanceof CompItem) {
			for (var j = 1; j <= item.numLayers; j++) {
				var layer = item.layer(j);
				if (replaceTextInLayer(layer, myFindString, myReplaceString)) {
					numLayersChanged++;
				}
			}
		}
	}

	if (numLayersChanged == 0) {
		alert("The string \"" + myFindString + "\" was not found in any text layers. No changes were made.", scriptName);
	}

	app.endUndoGroup();
}


		// Called when the Find Text string is edited
		function onFindStringChanged()
		{
			myFindString = this.text;
		}

		// Called when the Replacement Text string is edited
		function onReplaceStringChanged()
		{
			myReplaceString = this.text;
		}

		// Called when the "?" button is clicked
		function onShowHelp()
		{
			alert(scriptName + ":\n" +
				"Select one or more layers and enter text to find in the Find Text box. \n" +
				"Click Find All to change (narrow) the layer selection to include only those text layers with a Source Text property that contain the text specified in the Find Text box.\n" +
				"Click Replace All to replace all instances of the Find Text string with the Replacement Text string. Replacements are made only within selected text layers, and the selection remains unchanged.\n" +
				"Searches and replacements occur for Source Text properties and all of their keyframes.", scriptName);
		}
		
		
		// main:
		// 
		
		if (parseFloat(app.version) < 8)
		{
			alert("This script requires After Effects CS3 or later.", scriptName);
			return;
		}
		else
		{
			// Create and show a floating palette
			var my_palette = (thisObj instanceof Panel) ? thisObj : new Window("palette", scriptName, undefined, {resizeable:true});
			if (my_palette != null)
			{
				var res = 
				"group { \
					orientation:'column', alignment:['fill','fill'], alignChildren:['left','top'], spacing:5, margins:[0,0,0,0], \
					findRow: Group { \
						alignment:['fill','top'], \
						findStr: StaticText { text:'Find Text:', alignment:['left','center'] }, \
						findEditText: EditText { text:'', characters:20, alignment:['fill','center'] }, \
					}, \
					replaceRow: Group { \
						alignment:['fill','top'], \
						replaceStr: StaticText { text:'Replacement Text:', alignment:['left','center'] }, \
						replaceEditText: EditText { text:'', characters:20, alignment:['fill','center'] }, \
					}, \
					cmds: Group { \
						alignment:['fill','top'], \
						findButton: Button { text:'Find All', alignment:['fill','center'] }, \
						replaceButton: Button { text:'Replace All', alignment:['fill','center'] }, \
						helpButton: Button { text:'?', alignment:['right','center'], preferredSize:[30,22] }, \
					}, \
				}";
				
				my_palette.margins = [10,10,10,10];
				my_palette.grp = my_palette.add(res);
				my_palette.grp.findRow.findStr.preferredSize.width = my_palette.grp.replaceRow.replaceStr.preferredSize.width;
				
				my_palette.grp.findRow.findEditText.onChange = my_palette.grp.findRow.findEditText.onChanging = onFindStringChanged;
				my_palette.grp.replaceRow.replaceEditText.onChange = my_palette.grp.replaceRow.replaceEditText.onChanging = onReplaceStringChanged;
				
				my_palette.grp.cmds.findButton.onClick    = onFindAll;
				my_palette.grp.cmds.replaceButton.onClick = onReplaceAll;
				my_palette.grp.cmds.helpButton.onClick    = onShowHelp;
				
				my_palette.onResizing = my_palette.onResize = function () {this.layout.resize();}
			
				if (my_palette instanceof Window) {
					my_palette.center();
					my_palette.show();
				} else {
					my_palette.layout.layout(true);
					my_palette.layout.resize();
				}
			}
			else {
				alert("Could not open the user interface.", scriptName);
			}
		}
	}
	
	
	FindAndReplaceText(this);
}

 Edit: Sorry Gui, for some reason I thought you were looking to find and replace specific words in text layers, but I'm now seeing you are trying to change the styling. I will take another look when I have a moment to see if I can add the option to change the styling and I'll get back to you.

gui_paulino
Participant
July 16, 2025

Thank you so much. I'm still trying on my side. If anything comes first I'll let you know

Kevin J. Monahan Jr.
Community Manager
Community Manager
July 15, 2025

Hi gui,

Thanks for the note. Unfortunately, crafting an expression for you is a bit outside my expertise. I hope that a dev or community member will come along soon, though. Sorry to ask for your patience.

 

Thanks,
Kevin

 

Kevin Monahan - Sr. Community and Engagement Strategist – Adobe Pro Video and Audio