Amazonで商品を見るセール会場へ

Illustratorスクリプト(jsx)│フォルダー内のAIをまとめてPDFにする方法

Illustratorで複数のAIファイルをPDFへ変換する場合、PDFプリセットを指定してフォルダー内のファイルを順番に保存する方法があります。この記事のJSXは、同名のPDFをスキップし、ファイルごとの成功・失敗をログへ残す処理例です。

2026年10月3日にコードを改修し、模擬オブジェクトによる試験と、Windows 11 Pro/Illustrator 30.8.2(64-bit)での限定条件の実機試験を行いました。確認した入力・結果と、未確認の条件は後述します。Macの実機試験は行っていません。コピーした少数のファイルで試し、出力を開いて確認してから用途に合わせて使います。

目次

アクションのバッチとJSXの使い分け

同じ操作を記録して繰り返すだけなら、標準のアクションとバッチが候補になります。この記事は、既存出力の確認や処理結果の記録をコードで管理したい場合を扱います。1ファイル内のアートボードを画像へ書き出す処理や、CSVで文字を差し替える処理とは対象が異なります。

処理対象と保存方法

項目このコードの扱い
入力選んだフォルダー直下の.ai/.AIファイル。サブフォルダーへは入りません。
出力別フォルダーに元の名前.pdfとして保存。sample.v2.AIはsample.v2.pdfになります。
同名PDF開く前と保存直前に確認し、存在すればスキップ。上書きや自動連番にはしません。
元のAI入力パスへ保存する呼び出しはありません。処理用のドキュメントを保存せずに閉じます。
PDF設定PRESETで指定したプリセットを使い、保存後にビューアーを自動起動する設定をfalseにします。生成結果は別途確認します。
操作中の警告確認ダイアログを抑制しません。フォントやリンクの警告が出たら内容を確認します。

実行前の準備

  1. 作業に使うAIをコピーし、入力用フォルダーへ3件ほど入れる。サブフォルダーは対象外です。
  2. 入力とは別の出力用フォルダーを作る。ほかのアプリや同期処理から同じ出力先へ書き込まない。
  3. Illustratorで開いているドキュメントを保存して閉じる。入力AIを1件ずつ手動で開き、フォント・リンク・見た目を確認して閉じる。
  4. 掲載コードをUTF-8のテキストとして保存する。日本語が化ける場合はBOM付きUTF-8にする。拡張子が「.jsx.txt」になっていないか確認する。
  5. 「ファイル」→「スクリプト」→「その他のスクリプト」から実行する。外部JSXの警告は内容を確認し、この手順では無効化しません。

PDFプリセットを指定する

  1. 下のコードをconvert_to_pdf.jsxとして保存し、PRESETを空のまま一度実行する。この回はプリセット一覧を表示するだけです。
  2. 一覧にある名前をPRESETのダブルクォーテーション内へ正確に入力して保存する。名前に「”」や「\」が含まれる場合はJavaScript文字列としてエスケープする。
  3. 再実行して入力と出力のフォルダーを選ぶ。名前が一覧と一致しなければ、フォルダー選択前に停止します。
  4. 結果ログと生成PDFを開き、ページ数、アートボード、文字、リンク画像、サイズ、色、トンボ・塗り足しを用途に合わせて確認する。

入稿用のプリセットは印刷会社の指定に合わせます。PDF/Xの名前だけで入稿条件を満たしたと判断せず、PDF/X-1aとPDF/X-4の選び方と入稿前の確認項目を参照してください。

PDF一括変換のコード

// Illustrator実機未検証。コピーした少数のAIで試してください。
(function () {
  var PRESET = ""; // 空のまま実行して一覧を確認し、正確な名前を入力
  var OUTPUT_SUFFIX = ".pdf";
  var LOG_PREFIX = "pdf_batch_";
  var RUN_NOTE = "PDF preset=" + PRESET;

  function clean(value) {
    return String(value).replace(/[\t\r\n]+/g, " ");
  }
  function logRow(log, status, input, output, note) {
    log.encoding = "UTF-8";
    if (!log.open("a")) throw new Error("ログを開けません: " + log.error);
    var written = false;
    var closed = false;
    try {
      written = log.writeln([new Date().toString(), status, clean(input),
        clean(output), clean(note)].join("\t"));
    } finally {
      closed = log.close();
    }
    if (!written || !closed) throw new Error("ログを保存できません: " + log.error);
  }
  function makeLog(dst) {
    var stamp = new Date().getTime();
    var number = 0;
    var log;
    do {
      log = new File(dst.fsName + "/" + LOG_PREFIX + stamp + "_" + number + ".tsv");
      number++;
    } while (log.exists);
    log.encoding = "UTF-8";
    if (!log.open("w")) throw new Error("ログを作成できません: " + log.error);
    var written = false;
    var closed = false;
    try {
      written = log.writeln("time\tstatus\tinput\toutput\tnote");
    } finally {
      closed = log.close();
    }
    if (!written || !closed) throw new Error("ログを保存できません: " + log.error);
    return log;
  }

  function processDocument(doc) {
    return "preset=" + PRESET;
  }
  function saveDocument(doc, output) {
    var options = new PDFSaveOptions();
    options.pDFPreset = PRESET;
    options.viewAfterSaving = false;
    doc.saveAs(output, options);
  }

  function main() {
    var presets = app.PDFPresetsList;
    if (PRESET === "") {
      alert("PDFプリセット名をPRESETへ入力してください。\n" + presets.join("\n"));
      return;
    }
    var valid = false;
    for (var presetIndex = 0; presetIndex < presets.length; presetIndex++) {
      if (presets[presetIndex] === PRESET) valid = true;
    }
    if (!valid) { alert("指定したPDFプリセットが見つかりません。"); return; }
    if (app.documents.length !== 0) {
      alert("開いているドキュメントを保存して閉じてから実行してください。");
      return;
    }
    var src = Folder.selectDialog("コピーした入力AIのフォルダーを選択");
    if (!src) return;
    var dst = Folder.selectDialog("入力とは別の出力フォルダーを選択");
    if (!dst) return;
    if (src.fsName.toLowerCase() === dst.fsName.toLowerCase()) {
      alert("入力と出力は別のフォルダーにしてください。");
      return;
    }
    var files = src.getFiles(function (entry) {
      return entry instanceof File && /\.ai$/i.test(entry.name);
    });
    if (files.length === 0) {
      alert("入力フォルダー直下にAIファイルがありません。");
      return;
    }
    var log = makeLog(dst);
    logRow(log, "START", src.fsName, dst.fsName, RUN_NOTE);
    var done = 0;
    var failed = 0;
    var skipped = 0;
    var processed = 0;
    var halted = false;
    for (var fileIndex = 0; fileIndex < files.length; fileIndex++) {
      var input = files[fileIndex];
      var name = File.decode(input.name).replace(/\.ai$/i, "") + OUTPUT_SUFFIX;
      var output = new File(dst.fsName + "/" + name);
      var doc = null;
      var status = "OK";
      var note = "";
      if (output.exists) {
        status = "SKIP";
        note = "同名の出力があるため処理しません";
      } else {
        try {
          doc = app.open(input);
          note = processDocument(doc);
          // 開いている間に出力が作られた場合も保存を避ける。
          output = new File(output.fsName);
          if (output.exists) {
            status = "SKIP";
            note = "保存直前に同名の出力を検出";
          } else {
            saveDocument(doc, output);
            if (!new File(output.fsName).exists) {
              throw new Error("保存後に出力ファイルを確認できません");
            }
          }
        } catch (error) {
          status = "ERROR";
          note = String(error);
        } finally {
          if (doc !== null) {
            try {
              doc.close(SaveOptions.DONOTSAVECHANGES);
            } catch (closeError) {
              status = "ERROR";
              note += " / 終了失敗: " + closeError;
              halted = true;
            }
          }
        }
      }
      processed++;
      if (status === "OK") done++;
      else if (status === "SKIP") skipped++;
      else failed++;
      logRow(log, status, input.fsName, output.fsName, note);
      // 開く処理の途中でドキュメントが残った場合も継続しない。
      if (app.documents.length !== 0) halted = true;
      if (halted) break;
    }
    var remaining = files.length - processed;
    var summary = "保存 " + done + " / 失敗 " + failed + " / スキップ " + skipped +
      " / 未処理 " + remaining;
    logRow(log, halted ? "HALTED" : "END", "", "", summary);
    alert(summary + "\nログ: " + log.fsName +
      "\nERRORの出力は不完全な可能性があります。開いて確認してください。");
  }
  try {
    main();
  } catch (fatalError) {
    alert("処理を停止しました: " + fatalError +
      "\n出力とログ、開いているドキュメントを確認してください。");
  }
}());

ログと失敗したファイルの確認

出力フォルダーにTSV形式のログを作ります。各行は日時、状態、入力パス、出力パス、補足の順です。テキストエディターで開き、入力件数と「保存・失敗・スキップ・未処理」の合計を照合します。入力フォルダーに残したファイルは変更していないか、更新日時や内容でも確認します。

状態意味と次の確認
OK保存処理が戻り、出力ファイルの存在を確認した状態。見た目や印刷条件への適合は判定していません。
SKIP同名の出力があったため保存を見送った状態。既存ファイルが今回の入力と一致するとは限りません。
ERROR開く・処理する・保存する・閉じる、のいずれかで例外が起きた状態。出力があっても不完全な可能性があるため開いて確認します。
HALTED/未処理ドキュメントを閉じられないなどの理由で後続を停止した状態。開いているファイルとログを確認し、原因を解消してから再開します。

通常のファイル別エラーはログに記録して次へ進みます。ただし、ドキュメントが残った場合やログを保存できない場合は停止します。強制終了・アプリのクラッシュでは最後の行が残らない場合もあります。ERRORの出力を自動削除する処理は入れていません。再実行時も既存出力はスキップされるので、不完全な出力を別の場所へ退避してから必要な入力だけを再処理します。

同名チェックは保存前に行いますが、別の処理が同時に出力を作る状況まで排他的に防ぐ仕組みではありません。同じ出力先を使うスクリプトを並行して実行しないでください。入力・出力を分ける判定はパス文字列によるため、同じフォルダーを指す別名・エイリアスも選ばないでください。

実機で確認するケース

  • 同名PDFがあるケースで、そのファイルの内容と更新日時が変わらないこと。
  • 途中に開けないAIや保存できないファイルを置き、ログと後続ファイルの結果を照合すること。
  • 複数ドット・日本語・大文字拡張子の名前、複数アートボード、フォント不足、リンク切れを含むコピーで結果を確認すること。
  • 手動で同じプリセットから作ったPDFと比較し、ページ範囲・圧縮・色・塗り足しなどの設定結果を確認すること。

Windows実機で確認した入力と結果

2026年10月3日15:12〜15:14(日本時間)に、自作のRGBドキュメント3件を処理しました。PDFプリセットは「[Illustrator 初期設定]」。試験用ハーネスでフォルダー選択とプリセット値を固定し、情報メッセージを記録しています。掲載コードの保存・同名判定・終了処理は変更していません。操作ダイアログを手動で選ぶ手順全体の再現試験とは区別します。

入力AI条件生成PDF
名札.v2.ai日本語のファイル名、複数ドット、文字2件(サブレイヤー内を含む)、1アートボード名札.v2.pdf/1ページ
sample-two.ai文字2件、2アートボード。2枚目は意図した空白sample-two.pdf/2ページ
no-text.ai文字なし、1アートボードno-text.pdf/1ページ

初回ログは保存3・失敗0・スキップ0。再実行は保存0・失敗0・スキップ3でした。生成した全4ページをレンダリングして確認し、文字のある2ページでは「名札」と「Nested text」が表示され、2アートボードの空白ページも含まれていました。各ページは360×240pt。試験前後の入力3件のSHA-256は一致しました。

一括生成した名札.v2.pdfの1ページ目
実際に生成したPDFの1ページ目を144dpiでレンダリングした画像です。2026年10月3日/Illustrator 30.8.2。

これは基本的な保存結果の確認です。途中に破損AIを含む処理、リンク切れ・フォント不足、保存権限不足、同時書き込み、Mac、手動保存との設定比較、PDF/X規格・印刷会社の受理は実機未確認です。模擬試験で確認した例外処理を、この実機試験で確認済みとは扱いません。大文字拡張子は今回のPDF入力に含まれていません。

参照資料

実行手順はAdobeのInstall and run scripts、PDF設定はAdobe PDFの作成を参照。File・Folderの読み書きはAdobe公開リポジトリーのJavaScript Tools Guide CC第3章を参照しています。APIの説明はAdobe資料をもとに維持されているPDFSaveOptionsの参照ページも案内します。掲載JSXの実機結果とは区別しています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次