Scripts - Batch Rename


(Dominic Vega) #1

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?


(Patrick Wadsworth) #2

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.


(Dominic Vega) #3

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(); } },
                ],
            },
        ],
    });
},

});


(Dominic Vega) #4

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


(Patrick Wadsworth) #5

Thanks for sharing Dominic, looks good :slight_smile: