LinearAndLoom Icons

Batch Convert AI to SVG and EPS in Illustrator (Free Script)

Free Illustrator script that converts folders of AI files to SVG, EPS or both, and resizes each EPS to just under 25 megapixels for Shutterstock.

Download illustrator-batch-ai-to-svg-eps.jsxJavaScript (ExtendScript) · 317 lines · 14 KB · free

Selling the same artwork on several sites means exporting it several ways: SVG for one, EPS under 25 megapixels for another. This script takes a folder of Illustrator files and produces both, sized correctly, in one run.

What it does

How to use it

  1. Run the script in Illustrator (see below).
  2. In the dialog, choose SVG only, EPS only or Both SVG and EPS.
  3. Choose the folder that holds your .ai files, or a parent folder.
  4. Read the summary. It shows how many files were converted and the pixel size of each EPS.

How to run an Illustrator script

  1. Download the script with the button on this page.
  2. Open Adobe Illustrator. No document needs to be open.
  3. Go to File > Scripts > Other Script... (Ctrl+F12 on Windows, Cmd+F12 on Mac).
  4. Choose the downloaded .jsx file.

To keep the script in the menu, copy it into Illustrator's Presets > en_US > Scripts folder inside the Illustrator application folder and restart Illustrator. It then appears under File > Scripts.

Illustrator may warn that scripts can change your files. That is normal for any script. This one's full code is shown at the bottom of the page so you can read what it does.

Why 25 megapixels?

Shutterstock accepts vector EPS files between 4 and 25 megapixels. A larger artboard is rejected at upload. The script measures the first artboard and scales the file so width times height lands just below the limit, which also enlarges small artwork to the biggest size allowed.

Good to know

Questions

Can I change the 25 megapixel limit?

Yes. Open the script in a text editor and change MAX_PIXELS = 25000000 near the top. For 60 megapixels, use 60000000.

Is the SVG resized as well?

No. The SVG is exported first, at the original size. Only the EPS is resized.

I only have SVG files. Is there a script for those?

Yes: batch convert SVG to EPS.

The code

This is the complete script. Nothing else is downloaded or installed.

// illustrator-batch-ai-to-svg-eps.jsx
// Free script from LinearAndLoom Icons: https://linearandloomicons.netlify.app/scripts/
// Provided as is. Try it on a copy of your files first.

/*  AI Convert - Adobe Illustrator script
    ------------------------------------------------------------
    Converts Adobe Illustrator (.ai) files to SVG, EPS (Illustrator 10), or BOTH.
    You choose the mode when the script runs.

    EPS files are resized so the artboard is as large as possible while staying
    UNDER 25 megapixels (at 72 ppi, 1 pt = 1 px). The scale is uniform, so
    proportions, layout, strokes, patterns and gradients stay exactly as drawn.
    Small files are scaled up and oversized files are scaled down.
    SVG files are exported at the original size (before any resizing).

    Output files are saved in "SVG" and/or "EPS" folders next to the source .ai files.
    Original .ai files are never modified.
    Existing output files are skipped, so it is safe to run again.
    (Delete old EPS files if you want them regenerated at the new size.)

    HOW TO RUN
      Illustrator: File > Scripts > Other Script... > choose this file.
      1. A dialog asks: SVG only / EPS only / Both
      2. Pick the folder that contains your .ai files (or any parent folder).
      Done.

    OUTPUT FOLDER STRUCTURE
      YourFolder/
        yourfile.ai          <- never touched
        SVG/
          yourfile.svg       <- created if SVG or Both chosen
        EPS/
          yourfile.eps       <- created if EPS or Both chosen (resized to < 25 MP)
*/
#target illustrator

(function () {

    // ── Settings ──────────────────────────────────────────────────────────────
    var MAX_PIXELS = 25000000;   // must stay strictly below this (25 MP)
    var SAFETY     = 0.9995;     // tiny margin so rounding never reaches 25 MP
    var MAX_SIDE   = 16383;      // Illustrator canvas limit in points

    // ── 1. Ask the user which conversion to run ──────────────────────────────
    var dlg = new Window("dialog", "AI Convert — Choose Output");
    dlg.orientation = "column";
    dlg.alignChildren = ["fill", "top"];
    dlg.margins = 24;
    dlg.spacing = 14;

    var lbl = dlg.add("statictext", undefined,
        "Convert .ai files to:");
    lbl.graphics.font = ScriptUI.newFont("dialog", ScriptUI.FontStyle.BOLD, 13);

    var grp = dlg.add("group");
    grp.orientation = "column";
    grp.alignChildren = ["left", "center"];
    grp.spacing = 10;

    var r1 = grp.add("radiobutton", undefined, "SVG only");
    var r2 = grp.add("radiobutton", undefined, "EPS only  (Illustrator 10, resized to max under 25 MP)");
    var r3 = grp.add("radiobutton", undefined, "Both SVG and EPS");
    r3.value = true;   // default: both

    var btnGrp = dlg.add("group");
    btnGrp.alignment = "right";
    btnGrp.spacing = 10;
    var btnCancel = btnGrp.add("button", undefined, "Cancel", { name: "cancel" });
    var btnOK     = btnGrp.add("button", undefined, "Next: Choose Folder »", { name: "ok" });

    btnOK.onClick = function () { dlg.close(1); };
    btnCancel.onClick = function () { dlg.close(0); };

    var result = dlg.show();
    if (result !== 1) { return; }

    var doSVG = r1.value || r3.value;
    var doEPS = r2.value || r3.value;

    // ── 2. Pick folder ────────────────────────────────────────────────────────
    var root = Folder.selectDialog("Select the folder with the .ai files");
    if (!root) { return; }

    var previousLevel = app.userInteractionLevel;
    app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;

    var svgConverted = 0, svgSkipped = 0;
    var epsConverted = 0, epsSkipped = 0;
    var failed = [];
    var sizeLog = [];

    // ── SVG export options ────────────────────────────────────────────────────
    function svgOptions() {
        var o = new ExportOptionsSVG();
        o.DTD                 = SVGDTDVersion.SVG1_1;
        o.fontType            = SVGFontType.OUTLINEFONT;   // text as outlines
        o.embedRasterImages   = true;
        o.preserveEditability = false;
        o.includeFileInfo     = false;
        o.includeUnusedStyles = false;
        o.coordinatePrecision = 4;
        o.documentEncoding    = SVGDocumentEncoding.UTF8;
        o.cssProperties       = SVGCSSPropertyLocation.STYLEATTRIBUTES;
        return o;
    }

    // ── EPS save options (Illustrator 10, Shutterstock spec) ──────────────────
    function epsOptions() {
        var o = new EPSSaveOptions();
        o.compatibility             = Compatibility.ILLUSTRATOR10;
        o.preview                   = EPSPreview.COLORTIFF;
        o.embedAllFonts             = true;
        o.embedLinkedFiles          = true;
        o.includeDocumentThumbnails = true;
        o.cmykPostScript            = false;
        o.postScript                = EPSPostScriptLevelEnum.LEVEL2;
        return o;
    }

    // ── Unlock / show all layers (recursively), remembering their state ──────
    function openLayers(layers, states) {
        for (var i = 0; i < layers.length; i++) {
            var L = layers[i];
            states.push({ layer: L, locked: L.locked, visible: L.visible });
            if (!L.visible) { L.visible = true; }
            if (L.locked)   { L.locked = false; }
            if (L.layers.length) { openLayers(L.layers, states); }
        }
    }

    function restoreLayers(states) {
        // reverse order: sublayers are restored before their parents
        for (var i = states.length - 1; i >= 0; i--) {
            var s = states[i];
            try { s.layer.locked  = s.locked;  } catch (e) {}
            try { s.layer.visible = s.visible; } catch (e) {}
        }
    }

    // ── Resize artwork so artboard 1 is the largest size under 25 MP ─────────
    //    Uniform scale about the artboard centre; every artboard and every
    //    top-level object is scaled by the same factor, so nothing shifts
    //    relative to anything else.
    function fitUnder25MP(doc) {
        var prevCS = app.coordinateSystem;
        app.coordinateSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM;

        var prevScaleStrokes = null;
        try {
            prevScaleStrokes = app.preferences.getBooleanPreference("scaleLineWeight");
            app.preferences.setBooleanPreference("scaleLineWeight", true);
        } catch (e) { prevScaleStrokes = null; }

        var layerStates = [];
        var info = null;

        try {
            var r = doc.artboards[0].artboardRect;          // [left, top, right, bottom]
            var w = Math.abs(r[2] - r[0]);
            var h = Math.abs(r[1] - r[3]);
            if (w <= 0 || h <= 0) { return null; }

            // Largest uniform scale that keeps w*h under 25 MP
            var s = Math.sqrt((MAX_PIXELS * SAFETY) / (w * h));
            if (w * s > MAX_SIDE) { s = MAX_SIDE / w; }
            if (h * s > MAX_SIDE) { s = MAX_SIDE / h; }
            while (Math.ceil(w * s) * Math.ceil(h * s) >= MAX_PIXELS) { s *= 0.9999; }

            info = {
                w: Math.round(w * s), h: Math.round(h * s),
                mp: (Math.ceil(w * s) * Math.ceil(h * s) / 1000000).toFixed(2),
                capped: (w * s >= MAX_SIDE - 0.5 || h * s >= MAX_SIDE - 0.5)
            };

            if (Math.abs(s - 1) < 0.000001) { return info; }   // already the right size

            var cx = (r[0] + r[2]) / 2;
            var cy = (r[1] + r[3]) / 2;
            var pct = s * 100;

            openLayers(doc.layers, layerStates);

            // Collect top-level objects only (children move with their parent)
            var all = doc.pageItems, list = [], i;
            for (i = 0; i < all.length; i++) {
                if (all[i].parent.typename === "Layer") { list.push(all[i]); }
            }

            for (i = 0; i < list.length; i++) {
                var it = list[i];
                var wasLocked = it.locked, wasHidden = it.hidden;
                if (wasLocked) { it.locked = false; }
                if (wasHidden) { it.hidden = false; }

                var b0 = it.geometricBounds;
                var ox = (b0[0] + b0[2]) / 2, oy = (b0[1] + b0[3]) / 2;

                // scale positions, fill patterns, gradients, stroke patterns, line widths
                it.resize(pct, pct, true, true, true, true, pct, Transformation.CENTER);

                // move the object so its centre lands at the scaled position
                var b1 = it.geometricBounds;
                var nx = (b1[0] + b1[2]) / 2, ny = (b1[1] + b1[3]) / 2;
                it.translate((cx + (ox - cx) * s) - nx, (cy + (oy - cy) * s) - ny);

                if (wasHidden) { it.hidden = true; }
                if (wasLocked) { it.locked = true; }
            }

            // Scale every artboard about the same centre
            for (i = 0; i < doc.artboards.length; i++) {
                var a = doc.artboards[i].artboardRect;
                doc.artboards[i].artboardRect = [
                    cx + (a[0] - cx) * s,
                    cy + (a[1] - cy) * s,
                    cx + (a[2] - cx) * s,
                    cy + (a[3] - cy) * s
                ];
            }
        } finally {
            restoreLayers(layerStates);
            if (prevScaleStrokes !== null) {
                try { app.preferences.setBooleanPreference("scaleLineWeight", prevScaleStrokes); } catch (e) {}
            }
            app.coordinateSystem = prevCS;
        }
        return info;
    }

    // ── Recursive folder walker ───────────────────────────────────────────────
    function walk(folder) {
        var items = folder.getFiles(), ais = [], i;
        for (i = 0; i < items.length; i++) {
            var it = items[i];
            if (it instanceof Folder) {
                var n = it.name.toLowerCase();
                if (n !== "eps" && n !== "svg" && n !== "illustrator" &&
                    n !== "__macosx" && n.charAt(0) !== ".") { walk(it); }
            } else if (/\.ai$/i.test(it.name) && it.name.charAt(0) !== ".") {
                ais.push(it);
            }
        }
        if (!ais.length) { return; }

        // Create output folders only if needed
        var svgOut = doSVG ? new Folder(folder.fsName + "/SVG") : null;
        var epsOut = doEPS ? new Folder(folder.fsName + "/EPS") : null;
        if (svgOut && !svgOut.exists) { svgOut.create(); }
        if (epsOut && !epsOut.exists) { epsOut.create(); }

        for (i = 0; i < ais.length; i++) {
            var baseName   = decodeURI(ais[i].name).replace(/\.ai$/i, "");
            var svgTarget  = doSVG ? new File(svgOut.fsName + "/" + baseName + ".svg") : null;
            var epsTarget  = doEPS ? new File(epsOut.fsName + "/" + baseName + ".eps") : null;

            // Check what actually needs doing for this file
            var needSVG = doSVG && !svgTarget.exists;
            var needEPS = doEPS && !epsTarget.exists;

            if (doSVG && !needSVG) { svgSkipped++; }
            if (doEPS && !needEPS) { epsSkipped++; }

            if (!needSVG && !needEPS) { continue; }  // both already exist

            var doc = null;
            try {
                doc = app.open(ais[i]);

                // SVG first, at the original size
                if (needSVG) {
                    doc.exportFile(svgTarget, ExportType.SVG, svgOptions());
                    svgConverted++;
                }

                // Then resize to max under 25 MP and save EPS
                if (needEPS) {
                    var info = fitUnder25MP(doc);
                    doc.saveAs(epsTarget, epsOptions());
                    epsConverted++;
                    if (info) {
                        sizeLog.push(baseName + ".eps  " + info.w + " x " + info.h +
                                     " px  (" + info.mp + " MP" +
                                     (info.capped ? ", limited by canvas size" : "") + ")");
                    }
                }

                doc.close(SaveOptions.DONOTSAVECHANGES);
            } catch (e) {
                failed.push(baseName + ".ai  (" + e + ")");
                try { if (doc) { doc.close(SaveOptions.DONOTSAVECHANGES); } } catch (e2) {}
            }
        }
    }

    // ── Run ───────────────────────────────────────────────────────────────────
    try { walk(root); } finally { app.userInteractionLevel = previousLevel; }

    // ── Summary ───────────────────────────────────────────────────────────────
    var msg = "AI Convert — finished\n";

    if (doSVG) {
        msg += "\nSVG\n  Converted : " + svgConverted + "\n  Skipped   : " + svgSkipped;
    }
    if (doEPS) {
        msg += "\nEPS\n  Converted : " + epsConverted + "\n  Skipped   : " + epsSkipped;
        if (sizeLog.length) {
            msg += "\n\nEPS sizes:\n" + sizeLog.slice(0, 15).join("\n");
            if (sizeLog.length > 15) { msg += "\n…and " + (sizeLog.length - 15) + " more"; }
        }
    }
    if (failed.length) {
        msg += "\n\nFailed (" + failed.length + "):\n" + failed.slice(0, 15).join("\n");
    }

    alert(msg);
})();

Download illustrator-batch-ai-to-svg-eps.jsxJavaScript (ExtendScript) · 317 lines · 14 KB · free

These scripts are free. If one saved you time, you can say thanks by buying a pack from the LinearAndLoom Etsy shop.

More scripts