Scripts - Batch Rename

This script doesn’t seem to take into account any events within a folder, so you can’t auto rename large sections since I have to drill down into sub folders to select them and batch rename. Is it possible to add this functionality manually?

Hi Dominic, this is entirely possible. If you look at the AddAhdsrToSelectedEvents.js script that we also ship, you’ll see that this script has a check box to find events recursively. So you should be able to copy the findEventsRecursively() function (and optionally allow folders to be gathered also) and run this over the studio.window.browserSelection() before doing the forEach loop in the BatchRename.js script.

1 Like

Seemed to be a bit more complicated than the above, to also have the UI box appear as well. To pin this here, here is what we changed.

studio.menu.addMenuItem({
name: “Custom Scripts\Batch Rename w Folders”,
isEnabled: function() { return studio.window.browserSelection().length; },
execute: function() {

    function findEventsRecursively(items, filter) {
        var result = [];
        items.forEach(function(item) {
            if(item.isOfType("Event")) {
                result.push(item);
            }
            if(item.isOfType("Folder")) {
                result = result.concat(findEventsRecursively(item.items));
            }
        });
        return result;
    }

    function doBatchRename(widget, commit) {
        var action = widget.findWidget("m_action").currentIndex();            
        var findStr = widget.findWidget("m_findText").text();
        var replaceStr = widget.findWidget("m_replaceText").text();
        var occurrenceType = widget.findWidget("m_occurrence").currentIndex();
        var caseSensitive = widget.findWidget("m_caseSensitive").isChecked();
        var useRegex = widget.findWidget("m_regexEnabled").isChecked();
        var findRecursively = widget.findWidget("m_findEventsRecursively").isChecked();
     
        // Change the action label, or hide when we're just changing case
        switch(action) {
            case 0: widget.findWidget("m_actionLabel").setText("Replace with"); break;
            case 1: widget.findWidget("m_actionLabel").setText("Append with"); break;
            case 2: widget.findWidget("m_actionLabel").setText("Prepend with"); break;
        }
        widget.findWidget("m_findLabel").setText(action == 0 ? "Find" : "Filter");
        widget.findWidget("m_occurrenceLabel").setVisible(action == 0);
        widget.findWidget("m_occurrence").setVisible(action == 0);            
        
        // Build up the regex
        var regexStr = "";
        var regexFlags = caseSensitive ? '' : 'i';
        if(findStr.length) {
            regexStr = useRegex ? findStr : RegExp.escape(findStr);
            switch(occurrenceType) {
                case 0: regexFlags += 'g'; break; // All
                case 2: regexStr = regexStr + '(?!.*' + regexStr + ')'; break; // Last
            }
        }
        
        // Generate preview and optionally commit changes
        var previewStr = "";
        try {
            var regex = regexStr.length ? new RegExp(regexStr, regexFlags) : null;

            var allEvents = studio.window.browserSelection();
            
            if(findRecursively)
            allEvents = findEventsRecursively(allEvents);

            allEvents.forEach(function(item) {
                if(item.isOfType("WorkspaceItem")) {
                    var newName = item.name;
                    switch(action) {
                        case 0: newName = regex ? newName.replace(regex, replaceStr) : newName; break; // Replace
                        case 1: newName = !regex || regex.test(newName) ? newName + replaceStr : newName; break; // Append
                        case 2: newName = !regex || regex.test(newName) ? replaceStr + newName : newName; break; // Prepend
                    }
                    newName = newName.trim();
                    
                    if(item.name == newName) {
                        previewStr += newName.toHtmlEscaped().replace(/ /g, " ") + '<font color="Gray"> unchanged</font><br />';
                    }
                    else {
                        previewStr += item.name.toHtmlEscaped().replace(/ /g, " ");
                        previewStr += '<font color="Gray"> to </font><font color="YellowGreen">';
                        previewStr += newName.toHtmlEscaped().replace(/ /g, " ") + '</font><br />';
                    }
                    if(commit) {
                        item.name = newName;
                    }
                }
            });
        }
        catch(e) {
            previewStr += '<font color="Red">Invalid regular expression specified.</font>';
        }
        widget.findWidget("m_preview").setHtml(previewStr);
    }
    
    studio.ui.showModalDialog({
        windowTitle: "Batch Rename",
        windowWidth: 340,
        widgetType: studio.ui.widgetType.Layout,
        layout: studio.ui.layoutType.VBoxLayout,
        items: [
            { widgetType: studio.ui.widgetType.Label, text: "Operation" },
            {
                widgetType: studio.ui.widgetType.ComboBox,
                widgetId: "m_action",
                items: [
                    { text: "Replace" }, // 0
                    { text: "Append" },  // 1
                    { text: "Prepend" }, // 2
                ],
                currentIndex: 0,
                onCurrentIndexChanged: function() { doBatchRename(this); },
            },
            { widgetType: studio.ui.widgetType.Label, widgetId: "m_findLabel", text: "Find" },
            {
                widgetType: studio.ui.widgetType.LineEdit,
                widgetId: "m_findText",
                text: "",
                onTextEdited: function() { doBatchRename(this); },
            },
            { widgetType: studio.ui.widgetType.Label, widgetId: "m_actionLabel", text: "Replace with" },
            {
                widgetType: studio.ui.widgetType.LineEdit,
                widgetId: "m_replaceText",
                text: "",
                onTextEdited: function() { doBatchRename(this); },
            },
            { widgetType: studio.ui.widgetType.Label, widgetId: "m_occurrenceLabel", text: "Occurrence" },
            {
                widgetType: studio.ui.widgetType.ComboBox,
                widgetId: "m_occurrence",
                items: [
                    { text: "All" },   // 0
                    { text: "First" }, // 1
                    { text: "Last" },  // 2
                ],
                currentIndex: 0,
                onCurrentIndexChanged: function() { doBatchRename(this); },
            },

            {
                widgetType: studio.ui.widgetType.CheckBox,
                widgetId: "m_findEventsRecursively",
                text: "Find Events Recursively",
                isChecked: false,
                onToggled: function() { updateAHDSR(this); },
            },

            {
                widgetType: studio.ui.widgetType.CheckBox,
                widgetId: "m_caseSensitive",
                text: "Case sensitive",
                isChecked: false,
                onToggled: function() { doBatchRename(this); },
            },
            {
                widgetType: studio.ui.widgetType.CheckBox,
                widgetId: "m_regexEnabled",
                text: "Use regular expressions",
                isChecked: false,
                onToggled: function() { doBatchRename(this); },
            },
            { widgetType: studio.ui.widgetType.Label, text: "Preview" },
            {
                widgetType: studio.ui.widgetType.TextEdit,
                widgetId: "m_preview",
                html: '<font color="Gray">Enter parameters to preview changes. Use <i>$n</i> within the replacement text to use captured groups.</font>',
                isReadOnly: true,
            },
            {
                widgetType: studio.ui.widgetType.Layout,
                layout: studio.ui.layoutType.HBoxLayout,
                contentsMargins: { left: 0, top: 12, right: 0, bottom: 0 },
                items: [
                    { widgetType: studio.ui.widgetType.Spacer, sizePolicy: { horizontalPolicy: studio.ui.sizePolicy.MinimumExpanding } },
                    { widgetType: studio.ui.widgetType.PushButton, text: "Apply", onClicked: function() { doBatchRename(this, true); this.closeDialog(); } },
                ],
            },
        ],
    });
},

});

The sorting might be off, as you need to check “search recursively” before typing, but it functions as needed.

Thanks for sharing Dominic, looks good :slight_smile:

[Thread Necromancer]
Is there a possibility to rename AudioFiles with the JS API? We get an exception when changing the script as follows:
item.isOfType(“WorkspaceItem”) || item.entity == “AudioFile”

Or is there another way to rename audio files without changing the corresponding metadata which in turn destroys the references?

You should be able to do so with something like this

var someAudioFile = studio.project.workspace.masterAssetFolder.getAsset("abc/test.wav");
someAudioFile.setAssetPath("abc/another test2.wav");

Thank you for sharing the code Dominic, it really helped me today :smile:

Thankyou for solution. It also help me.