Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
3

Batch Save As AVIF script

Community Expert ,
Aug 16, 2025 Aug 16, 2025

I have created a "Batch Save As AVIF" script, based on my "Batch Save As WebP" script.

 

Key Features:

* Version Check - Requires Photoshop 2025+ for native AVIF export support.

* Non-Destructive Workflow - Original source files remain unchanged.

* Folder Selection with Recursion - Optionally process subfolders.

* Mirror Folder Structure - Retain the original folder layout in the output.

* Compression Settings - Choose Lossy or Lossless, with adjustable quality (0–100).

* Resize by Longest Edge & PPI - Proportionally scale by longest edge and/or set resolution metadata.

* Automatic RGB Conversion - Converts non-RGB modes (CMYK, LAB, Bitmap, Indexed) to 8-bit RGB.

* sRGB Profile Option - Convert to sRGB IEC61966-2.1 or keep current color profile.

* Metadata Toggles - Include/exclude XMP, EXIF, or Photoshop metadata.

* Action Runner - Apply Photoshop actions during batch processing.
 
AVIF-Batch-Processor-Win.png
 
/*
Batch Save As AVIF scriptUI GUI v1-0.jsx
Stephen Marsh
https://community.adobe.com/t5/photoshop-ecosystem-discussions/...
v1.0 - 30th July 2025: Initial release

* Version Check - Requires Photoshop 2025+ for native AVIF export support.  
* Non-Destructive Workflow - Keeps original files unchanged.  
* Folder Selection with Recursion - Optionally processes subfolders.  
* Mirror Folder Structure - Retains original folder layout in output.  
* Compression Settings - Choose lossy or lossless for size/quality control.  
* Resize by Longest Edge & PPI - Scales proportionally and or sets resolution metadata.  
* Auto RGB Conversion - Ensure that non-RGB colour modes are converted to RGB.  
* sRGB Profile Option - Standardizes color output.  
* Metadata Toggles - Include/exclude XMP, EXIF, or PSD data.  
* Action Runner - Applies Photoshop actions during batch.

Based on:
Batch Save As WebP scriptUI GUI v1-9.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/export-many-files-at-once-in-webp-format-photoshop/td-p/13604411/page/4#U14859793
*/

#target photoshop

// Ensure that version 2025 or later is being used
var versionNumber = app.version.split(".");
var versionCheck = parseInt(versionNumber[0]);
if (versionCheck < 26) {
    alert("You must use Photoshop 2025 or later to save using native AVIF format...");

} else {
    // Create the ScriptUI dialog window
    var theDialogWin = new Window("dialog", "AVIF Batch Processor (v1.0)");

    theDialogWin.orientation = "column";
    theDialogWin.alignChildren = "left";

    // Input Folder Section
    var inputFolderPanel = theDialogWin.add("panel", undefined, "Input Folder");
    inputFolderPanel.orientation = "column";
    inputFolderPanel.alignChildren = ["fill", "top"];
    inputFolderPanel.alignment = ["fill", "top"];

    inputFolderPanel.add("statictext", undefined, "Select the source image folder to process:");
    var inputFolderRow = inputFolderPanel.add("group");
    inputFolderRow.orientation = "row";
    inputFolderRow.alignment = ["fill", "top"];

    var inputFolderButton = inputFolderRow.add("button", undefined, "Browse...");
    inputFolderButton.alignment = ["left", "top"];

    var inputFolderText = inputFolderRow.add("edittext", undefined, "");
    inputFolderText.characters = 30;
    inputFolderText.alignment = ["fill", "top"];

    // Process Subfolders Checkbox
    var recursiveCheckbox = inputFolderPanel.add("checkbox", undefined, "Include all subfolders");
    recursiveCheckbox.value = false;

    // Output Folder Section
    var outputFolderPanel = theDialogWin.add("panel", undefined, "Output Folder");
    outputFolderPanel.orientation = "column";
    outputFolderPanel.alignChildren = ["fill", "top"];
    outputFolderPanel.alignment = ["fill", "top"];

    outputFolderPanel.add("statictext", undefined, "Select the location to save the AVIF files:");
    var outputFolderRow = outputFolderPanel.add("group");
    outputFolderRow.orientation = "row";
    outputFolderRow.alignment = ["fill", "top"];

    var outputFolderButton = outputFolderRow.add("button", undefined, "Browse...");
    outputFolderButton.alignment = ["left", "top"];

    var outputFolderText = outputFolderRow.add("edittext", undefined, "");
    outputFolderText.characters = 30;
    outputFolderText.alignment = ["fill", "top"];

    // Mirror Directory Structure Checkbox
    var mirrorStructureCheckbox = outputFolderPanel.add("checkbox", undefined, "Retain the source subfolder structure");
    mirrorStructureCheckbox.value = false;
    mirrorStructureCheckbox.enabled = false;

    // Enable/Disable "Mirror Structure" based on the recursive option
    recursiveCheckbox.onClick = function () {
        mirrorStructureCheckbox.enabled = recursiveCheckbox.value;
    };

    // Create a panel for the compression settings
    var compressionPanel = theDialogWin.add("panel", undefined, "Compression Settings");
    compressionPanel.orientation = "column";
    compressionPanel.alignChildren = ["left", "top"];
    compressionPanel.margins = 15;
    compressionPanel.alignment = ["fill", "top"];

    var mainGroup = compressionPanel.add("group");
    mainGroup.orientation = "row";
    mainGroup.alignChildren = ["left", "center"];
    mainGroup.alignment = ["fill", "top"];
    mainGroup.spacing = 10;

    mainGroup.add("statictext", undefined, "Compression Type:");
    var compTypeDropdown = mainGroup.add("dropdownlist", undefined, ["Lossy", "Lossless"]);
    compTypeDropdown.selection = 0;

    mainGroup.add("statictext", undefined, "Compression Quality:");

    var qualityGroup = mainGroup.add("group");
    qualityGroup.orientation = "row";
    qualityGroup.alignChildren = ["left", "center"];
    qualityGroup.spacing = 10;

    var compValueSlider = qualityGroup.add("slider", undefined, 50, 0, 100);
    compValueSlider.size = [281, 20];
    var compValueText = qualityGroup.add("edittext", undefined, "50");
    compValueText.characters = 4;

    // Update the text while dragging slider
    compValueSlider.onChanging = function () {
        compValueText.text = Math.round(this.value).toString();
    };

    // Update the text when slider changes via keyboard or release
    compValueSlider.onChange = function () {
        compValueText.text = Math.round(this.value).toString();
    };

    // Update the slider when text changes
    compValueText.onChange = function () {
        var inputValue = parseInt(this.text, 10);
        if (!isNaN(inputValue) && inputValue >= 0 && inputValue <= 100) {
            compValueSlider.value = inputValue;
        } else {
            this.text = Math.round(compValueSlider.value).toString();
        }
    };

    // Function to toggle the slider based on compression type
    function updateCompressionControls() {
        var enableControls = compTypeDropdown.selection.text !== "Lossless";
        compValueSlider.enabled = enableControls;
        compValueText.enabled = enableControls;
    }

    updateCompressionControls();

    compTypeDropdown.onChange = updateCompressionControls;

    // Create a panel for all checkboxes
    var checkboxPanel = theDialogWin.add("panel", undefined, "Options");
    checkboxPanel.orientation = "column";
    checkboxPanel.alignChildren = ["left", "top"];
    checkboxPanel.margins = 15;
    checkboxPanel.alignment = ["fill", "top"];

    var checkboxGroup = checkboxPanel.add("group");
    checkboxGroup.orientation = "row";
    checkboxGroup.alignChildren = ["left", "top"];
    checkboxGroup.alignment = ["left", "top"];
    checkboxGroup.spacing = 20;

    // Left column
    var leftColumn = checkboxGroup.add("group");
    leftColumn.orientation = "column";
    leftColumn.alignChildren = ["left", "top"];

    var fitImageGroup = leftColumn.add("group");
    fitImageGroup.orientation = "row";
    fitImageGroup.alignChildren = ["left", "center"];
    fitImageGroup.alignment = ["fill", "top"];
    var fitImageCheckbox = fitImageGroup.add("checkbox", undefined, "Fit Longest Edge (px):");
    fitImageCheckbox.value = false;
    var fitImageInput = fitImageGroup.add("editnumber", undefined, "1920");
    fitImageInput.helpTip = "Proportionally resize the longest edge of the image to the specified value";
    fitImageInput.characters = 5;
    fitImageInput.enabled = fitImageCheckbox.value;
    fitImageCheckbox.onClick = function () {
        fitImageInput.enabled = fitImageCheckbox.value;
    };

    var xmpDataCheckbox = leftColumn.add("checkbox", undefined, "Include XMP Data");
    xmpDataCheckbox.value = false;

    // Middle column
    var middleColumn = checkboxGroup.add("group");
    middleColumn.orientation = "column";
    middleColumn.alignChildren = ["fill", "top"];

    var ppiGroup = middleColumn.add("group");
    ppiGroup.orientation = "row";
    ppiGroup.alignChildren = ["left", "center"];
    ppiGroup.alignment = ["left", "top"];
    var ppiCheckbox = ppiGroup.add("checkbox", undefined, "PPI Value:");
    ppiCheckbox.value = false;
    var ppiInput = ppiGroup.add("editnumber", undefined, "300");
    ppiInput.characters = 5;
    ppiInput.enabled = ppiCheckbox.value;
    ppiCheckbox.onClick = function () {
        ppiInput.enabled = ppiCheckbox.value;
    };

    var exifDataCheckbox = middleColumn.add("checkbox", undefined, "Include EXIF Data");
    exifDataCheckbox.value = false;

    // Right column
    var rightColumn = checkboxGroup.add("group");
    rightColumn.orientation = "column";
    rightColumn.alignChildren = ["fill", "top"];

    var psDataCheckbox = rightColumn.add("checkbox", undefined, "Include Photoshop Data");
    psDataCheckbox.value = false;

    var rgbGroup = checkboxPanel.add("group");
    rgbGroup.orientation = "row";
    rgbGroup.alignChildren = ["left", "center"];
    rgbGroup.spacing = 10;
    rgbGroup.add("statictext", undefined, "RGB Profile Conversion:");
    var rgbConversionDropdown = rgbGroup.add("dropdownlist", undefined, ["Convert to sRGB space", "Keep Current RGB space"]);
    rgbConversionDropdown.selection = 0;

    // Create a panel to contain the action dropdowns
    var actionPanel = theDialogWin.add("panel", undefined, "Run Action");
    actionPanel.orientation = "column";
    actionPanel.alignChildren = ["fill", "top"];
    actionPanel.margins = 15;
    actionPanel.alignment = ["fill", "top"];

    var dropdownWidth = 350;

    var actionGroup = actionPanel.add("group");
    actionGroup.orientation = "row";
    actionGroup.alignChildren = ["left", "top"];
    actionGroup.alignment = ["fill", "top"];

    var actionSetColumn = actionGroup.add("group");
    actionSetColumn.orientation = "column";
    actionSetColumn.alignChildren = ["left", "top"];
    actionSetColumn.alignment = ["fill", "top"];

    var actionSetDropdown = actionSetColumn.add('dropdownlist', undefined, []);
    actionSetDropdown.preferredSize.width = dropdownWidth;

    var actionLabelColumn = actionGroup.add("group");
    actionLabelColumn.orientation = "column";
    actionLabelColumn.alignChildren = ["left", "top"];
    actionLabelColumn.alignment = ["fill", "top"];

    var actionDropdown = actionLabelColumn.add('dropdownlist', undefined, []);
    actionDropdown.preferredSize.width = dropdownWidth;

    actionSetDropdown.onResizing = actionSetDropdown.onResize = function () {
        this.size.width = dropdownWidth;
    };
    actionDropdown.onResizing = actionDropdown.onResize = function () {
        this.size.width = dropdownWidth;
    };

    // Populate the action set dropdown
    actionSetDropdown.add('item', '');
    var actionSets = getActionSets();
    for (var i = 0; i < actionSets.length; i++) {
        actionSetDropdown.add('item', actionSets[i]);
    }
    actionSetDropdown.selection = actionSetDropdown.items[0];
    actionDropdown.add('item', '');

    actionSetDropdown.onChange = function () {
        actionDropdown.removeAll();

        if (actionSetDropdown.selection && actionSetDropdown.selection.text != '') {
            var actions = getActions(actionSetDropdown.selection.text);
            for (var i = 0; i < actions.length; i++) {
                actionDropdown.add('item', actions[i]);
            }
            if (actions.length > 0) {
                actionDropdown.selection = actionDropdown.items[0];
            }
        } else {
            actionDropdown.add('item', '');
            actionDropdown.selection = actionDropdown.items[0];
        }
    };

    var buttonGroup = theDialogWin.add("group");
    buttonGroup.alignment = "right";
    var cancelButton = buttonGroup.add("button", undefined, "Cancel");
    var okButton = buttonGroup.add("button", undefined, "OK");

    inputFolderButton.onClick = function () {
        var inputFolder = Folder.selectDialog("Select the input folder:");
        if (inputFolder) {
            inputFolderText.text = inputFolder.fsName;
            if (!outputFolderText.text) {
                outputFolderText.text = inputFolder.fsName;
            }
        }
    };

    outputFolderButton.onClick = function () {
        var outputFolder = Folder.selectDialog("Select the output folder:");
        if (outputFolder) {
            outputFolderText.text = outputFolder.fsName;
        }
    };

    cancelButton.onClick = function () {
        theDialogWin.close();
    };

    okButton.onClick = function () {
        theDialogWin.close();
        app.refresh();
        app.togglePalettes();
        if (inputFolderText.text === "" || outputFolderText.text === "") {
            alert("Ensure that you select both input and output folders and other options before pressing OK!");
            app.togglePalettes();
            return;
        }

        var inputFolder = new Folder(inputFolderText.text);
        var outputFolder = new Folder(outputFolderText.text);

        var compType = compTypeDropdown.selection.text === "Lossless" ? "compressionLossless" : "compressionLossy";
        var compValue = Math.round(compValueSlider.value);
        var xmpData = xmpDataCheckbox.value;
        var exifData = exifDataCheckbox.value;
        var psData = psDataCheckbox.value;
        var rgbConversion = rgbConversionDropdown.selection.text;

        var fitValue = fitImageCheckbox.value ? parseInt(fitImageInput.text) : null;
        var ppi = ppiCheckbox.value ? parseInt(ppiInput.text) : null;

        processFiles(inputFolder, outputFolder, compType, compValue, xmpData, exifData, psData, fitValue, ppi, recursiveCheckbox.value, mirrorStructureCheckbox.value, rgbConversion);
        theDialogWin.close();
    };

    theDialogWin.show();
}


// Main processing function with optional recursion and mirroring structure
function processFiles(inputFolder, outputFolder, compType, compValue, xmpData, exifData, psData, fitValue, ppi, recursive, mirrorStructure, rgbConversion) {
    var fileList = getFilesRecursive(inputFolder, recursive);
    fileList.sort();
    var savedDisplayDialogs = app.displayDialogs;
    app.displayDialogs = DialogModes.NO;

    var inputFileCounter = 0;
    var fileCounter = 0;

    // Create the progress bar palette
    var progressBar = new Window("palette", "Processing Files");
    progressBar.preferredSize = [350, 100];
    // Status text
    var statusText = progressBar.add("statictext", undefined, "Preparing...");
    statusText.alignment = "fill";
    // Progress bar
    var progress = progressBar.add("progressbar", undefined, 0, fileList.length);
    progress.preferredSize.width = 300;
    progressBar.show();
    app.refresh();

    // Process each file
    for (var i = 0; i < fileList.length; i++) {
        // Update progress UI
        var percent = Math.round(((i + 1) / fileList.length) * 100);
        statusText.text = "Processing file " + (i + 1) + " of " + fileList.length + " (" + percent + "%)";
        progress.value = i + 1;
        app.refresh();

        // Open file
        open(fileList[i]);

        // Prepare target folder
        var relativePath = fileList[i].parent.fsName.replace(inputFolder.fsName, "");
        var targetFolder = outputFolder;
        if (mirrorStructure && relativePath !== "") {
            targetFolder = new Folder(outputFolder + "/" + relativePath);
            if (!targetFolder.exists) {
                targetFolder.create();
            }
        }

        // Mode conversion logic
        if (activeDocument.mode == DocumentMode.BITMAP) {
            activeDocument.changeMode(ChangeMode.GRAYSCALE);
            activeDocument.changeMode(ChangeMode.RGB);
            //if (rgbConversion === "Convert to sRGB space") {
            if (rgbConversionDropdown.selection.index === 0) {
                activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            }
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
        } else if (activeDocument.mode == DocumentMode.INDEXEDCOLOR || activeDocument.mode == DocumentMode.CMYK || activeDocument.mode == DocumentMode.LAB) {
            activeDocument.changeMode(ChangeMode.RGB);
            //if (rgbConversion === "Convert to sRGB space") {
            if (rgbConversionDropdown.selection.index === 0) {
                activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            }
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
        } else {
            activeDocument.changeMode(ChangeMode.RGB);
            //if (rgbConversion === "Convert to sRGB space") {
            if (rgbConversionDropdown.selection.index === 0) {
                activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            }
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
        }

        // Run actions if selected
        if (actionSetDropdown.selection && actionSetDropdown.selection.text != '' &&
            actionDropdown.selection && actionDropdown.selection.text != '') {
            runAction(actionSetDropdown.selection.text, actionDropdown.selection.text);
        }

        // Fit image if needed
        if (fitValue !== null || ppi !== null) {
            fitImage(fitValue, ppi);
        }

        // Save & close
        saveAVIF(compType, compValue, xmpData, exifData, psData, targetFolder);
        activeDocument.close(SaveOptions.DONOTSAVECHANGES);

        // Increment the counters
        inputFileCounter++;
        fileCounter++;
    }

    // Return focus to the host and close the progress bar palette
    app.bringToFront();
    progressBar.close();

    // End of script notification
    app.displayDialogs = savedDisplayDialogs;
    app.beep();
    alert('Script completed!' + '\r' + inputFileCounter + ' source files saved as ' + fileCounter + ' AVIF files!');
    app.togglePalettes();
}


/// Helper Functions ///

function getFilesRecursive(folder, recursive) {
    var fileList = [];
    var allFiles = folder.getFiles();
    for (var i = 0; i < allFiles.length; i++) {
        var file = allFiles[i];
        if (file instanceof Folder && recursive) {
            fileList = fileList.concat(getFilesRecursive(file, recursive));
        } else if (file instanceof File && /\.(avif|webp|tif|tiff|jpg|jpeg|psd|psb|png|tga)$/i.test(file.name)) {
            fileList.push(file);
        }
    }
    return fileList;
}

function fitImage(fitValue, ppi) {
    if (fitValue !== null && ppi !== null) {
        if (activeDocument.height.value > activeDocument.width.value) {
            activeDocument.resizeImage(null, UnitValue(fitValue, "px"), ppi, ResampleMethod.BICUBIC);
        } else {
            activeDocument.resizeImage(UnitValue(fitValue, "px"), null, ppi, ResampleMethod.BICUBIC);
        }
    } else if (fitValue !== null) {
        if (activeDocument.height.value > activeDocument.width.value) {
            activeDocument.resizeImage(null, UnitValue(fitValue, "px"), activeDocument.resolution, ResampleMethod.BICUBIC);
        } else {
            activeDocument.resizeImage(UnitValue(fitValue, "px"), null, activeDocument.resolution, ResampleMethod.BICUBIC);
        }
    } else if (ppi !== null) {
        activeDocument.resizeImage(undefined, undefined, ppi, ResampleMethod.NONE);
    }
}

function saveAVIF(compType, compValue, xmpData, exifData, psData, outputFolder) {
    var exportSettings = {
        isLossy: compType === "compressionLossy",
        quality: compValue,
        outputFolder: outputFolder
    };
    var AVIFDocName = activeDocument.name.replace(/\.[^\.]+$/, '');
    try {
        function s2t(s) { return stringIDToTypeID(s); }
        var descSave = new ActionDescriptor();
        var descAVIFOptions = new ActionDescriptor();
        var idAVIFCompression = s2t("AVIFCompression");
        if (exportSettings.isLossy) {
            descAVIFOptions.putEnumerated(s2t("colorCompression"), idAVIFCompression, s2t("compressionLossy"));
            descAVIFOptions.putInteger(s2t("colorQuality"), exportSettings.quality);
        } else {
            descAVIFOptions.putEnumerated(s2t("colorCompression"), idAVIFCompression, s2t("compressionLossless"));
        }
        descAVIFOptions.putEnumerated(s2t("alphaCompression"), idAVIFCompression, s2t("compressionLossless"));
        descAVIFOptions.putEnumerated(s2t("colorFormat"), s2t("AVIFColorFormat"), s2t("AVIFFormat422")); // Options: AVIFFormat444, AVIFFormat422, AVIFFormat420
        descAVIFOptions.putEnumerated(s2t("sampleDepth"), s2t("AVIFSampleDepth"), s2t("AVIFDepth12bit"));
        descAVIFOptions.putInteger(s2t("encoderSpeed"), 6); // Options: 2 (slowest/smallest), 4, 6, 8, 10 (fastest/largest)
        descAVIFOptions.putBoolean(s2t("includeXMPData"), xmpData);
        descAVIFOptions.putBoolean(s2t("includeEXIFData"), exifData);
        descAVIFOptions.putBoolean(s2t("includePsExtras"), psData);
        descSave.putObject(s2t("as"), s2t("AVIFFormat"), descAVIFOptions);
        descSave.putPath(s2t("in"), new File(exportSettings.outputFolder + '/' + AVIFDocName + '.avif'));
        descSave.putBoolean(s2t("lowerCase"), true);
        descSave.putEnumerated(s2t("saveStage"), s2t("saveStageType"), s2t("saveSucceeded"));
        executeAction(s2t("save"), descSave, DialogModes.NO);
    } catch (e) {
        alert("Error saving AVIF:\n" + e.message + "\nLine: " + e.line);
    }
}

function getActionSets() {
    var actionSets = [];
    var i = 1;
    while (true) {
        try {
            var ref = new ActionReference();
            ref.putIndex(charIDToTypeID('ASet'), i);
            var desc = executeActionGet(ref);
            var name = desc.getString(charIDToTypeID('Nm  '));
            actionSets.push(name);
            i++;
        } catch (e) {
            break;
        }
    }
    return actionSets;
}

function getActions(actionSet) {
    var actions = [];
    var i = 1;
    while (true) {
        try {
            var ref = new ActionReference();
            ref.putIndex(charIDToTypeID('Actn'), i);
            ref.putName(charIDToTypeID('ASet'), actionSet);
            var desc = executeActionGet(ref);
            var actionName = desc.getString(charIDToTypeID('Nm  '));
            actions.push(actionName);
            i++;
        } catch (e) {
            break;
        }
    }
    return actions;
}

function runAction(actionSet, actionName) {
    var actionRef = new ActionReference();
    actionRef.putName(charIDToTypeID('Actn'), actionName);
    actionRef.putName(charIDToTypeID('ASet'), actionSet);
    var desc = new ActionDescriptor();
    desc.putReference(charIDToTypeID('null'), actionRef);
    executeAction(charIDToTypeID('Ply '), desc, DialogModes.NO);
}

 

https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html

 

TOPICS
Actions and scripting , macOS , Windows
216
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe
Community Expert ,
Aug 18, 2025 Aug 18, 2025

@Stephen Marsh was there a question in here? Or a 'hey, I created this plugin for you?' post?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 18, 2025 Aug 18, 2025
quote

@Stephen Marsh was there a question in here? Or a 'hey, I created this plugin for you?' post?


By @creative explorer

 

Adobe only offer three choices for a topic:

Discussion, Idea and Bug.

 

So no, there is clearly no question. It's a pre-emptive public service announcement, usually I write scripts in response to a lamentation or request, this time there wasn't a request yet.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
Aug 18, 2025 Aug 18, 2025
LATEST

Such topics are very useful - users often use search to find solution of their problem. They do not care whether the question was asked or not, the main thing is to get the answer they need 🤷

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines