PR(記事内にアフィリエイト広告が含まれています)
スポンサーリンク
Eagleツール

EagleのAIイレーサーで画像が壊れる原因

Eagle

Eagleの公式プラグイン「AIイレーサー」を使っていると、まれに画像が壊れてEagle上で扱えなくなることがありました。

この記事では、調査のために自作したプラグインの配布と、AIイレーサー側の修正方法をまとめます。

どんな症状か

AIイレーサーで画像の一部を消して上書き保存すると、通常は元のJPGがPNGに変換されます。ここまでは仕様どおりです。

ところが、まれに次のような状態になります。

  • エクスプローラーで見るとPNGなのに、Eagle上ではJPG扱いになっている
  • 画像のあったフォルダにファイル名.jpg.tmpという見慣れないファイルが残っている
  • Eagleが参照しているはずのファイル名.jpgが、実際には存在しない

こうなると、そのアイテムはEagle上でまともに扱えません。私はこれまで、エクスプローラーから画像を再インポートして対処していました。タグやフォルダ分けをやり直す必要があり、なかなか面倒な作業です。

しかも、この症状はPNGからJPGに変換する場合にも起きます。つまり、拡張子が変わる置き換え全般で起きる問題でした。

原因を突き止めるまで

最初の見当違い

当初は「プラグインがEagleの作法を守らずに、ファイルを直接書き換えているのでは」と考えました。ところがAIイレーサーのソースを読んでみると、保存処理はEagleが公式に推奨している手順そのままでした。

async replace() {
  const url = this.imageHistoryManager.current;
  const tempFilePath = `${__dirname}/temp/${crypto.randomUUID()}.png`;
  await utils$1.file.save(tempFilePath, url);
  await (await eagle.item.getById(this.item_id)).replaceFile(tempFilePath);
  await utils$1.file.destroy(tempFilePath);
}

一時ファイルに書き出してから、replaceFile()で差し替えるという流れです。EagleのAPIドキュメントにも「まず別の場所に保存し、正しいことを確認してからreplaceFile()で置き換える」と書かれており、まさにその通りの実装でした。

つまり、プラグインの実装に問題はありません。では何が問題なのか。

ログを見たら一発で分かった

Eagleにはデバッグレポートを出力する機能があります。

メニュー → ヘルプ → デバッグレポートを作成…

これで出力されるlog.logを読んだところ、壊れたときの記録が残ってました(見やすいように整形済)。

12:49:23.552  Replace item file: {プラグイン}/temp/xxx.png > ....\00270-....jpg
12:49:23.552  rename(00270-....jpg → 00270-....jpg.tmp)   ← 元ファイルを退避
12:49:23.559  Save item
12:49:23.560  metadata 保存(ext: png)
              ※ 新しいファイルを配置するログが存在しない
12:49:26.536  [error] TypeError: ai_eraser.stopServer is not a function
12:49:26.537  rmdir({プラグイン}\ai-eraser\temp)            ← コピー元を削除

特に見てほしいのは最後の2行で、画像のコピー元になっている一時フォルダが、処理の途中で削除されています。

削除が起きたのは保存処理の約3秒後で、これは私がプラグインのウィンドウを閉じた瞬間でした。

AIイレーサーは、ウィンドウが閉じられると自分の一時フォルダを削除する作りになっています。

window.addEventListener("unload", async () => {
  await utils.file.deleteFolder(`${__dirname}/temp`);
});

普通なら問題のない処理です。しかし、Eagle側のreplaceFile()がまだ内部で作業している最中にこれが走ると、コピー元が消えてしまい、処理が最後まで終わりません。その結果、.jpg.tmpが残り、Eagleの情報と実ファイルが食い違った状態のまま固まってしまう、という状況だったようです。

なぜ気づかずに閉じてしまうのか

厄介なのは、閉じてしまう理由のほうでした。AIイレーサーは保存ボタンを押すと、ほぼすぐに「エクスポート完了!」と表示します。私はそれを見て「処理が終わった」と判断し、ウィンドウを閉じていました。表示は確かに出ていたのですが、出ているのは一瞬で、表示も小さいので、気にも留めていませんでした。

しかし実際には、Eagle側の処理はまだ終わっていなかったようです。プラグインはreplaceFile()の完了を待ってから完了表示を出しています。ところが、そのreplaceFile()自身が、本当の完了より早く「終わりました」と返してきている疑いがあります。

つまり、画面に出る完了と実際の完了がずれています。これでは、利用者が正しいタイミングを知る方法がありません。

ちなみに、目立つエラーは無関係でした

ログに出ているai_eraser.stopServer is not a functionは、いかにも怪しいエラーです。しかし、これは壊れる原因ではありませんでした。

数えてみると、プラグインの起動42回に対し、このエラーも42回出ていました。つまり、ウィンドウを閉じるたびに必ず出ています。それなのに、壊れた画像はごく一部でした。

このエラーは、以前のバージョンで使っていた関数の呼び出しを消し忘れたものと思われます。エラーとして記録はされますが、処理は止まりません。

一方、一時フォルダの削除が実際に実行されたのは、ログ上で6回だけでした。処理中でファイルが残っていたこの6回こそが、危険なケースだったわけです。

紛らわしいエラーに引っかからずに切り分けできたのが、今回の収穫でした。

調査に使ったプラグイン

原因を追う過程で、壊れた画像を見つけ出すためのEagleプラグインを自作しました。

ext-restore.zip

zipを解凍して、Eagleで取り込んでください。

Eagleプラグイン全般的なインストールの操作方法はこちらを参照して下さい。

プラグインで出来ること

選択したアイテム、またはフォルダの中身をサブフォルダまで含めて検査し、次の情報を突き合わせて食い違いを見つけます。

  • Eagleが認識している拡張子
  • metadata.jsonに記録されている拡張子
  • 実ファイルの中身(バイナリの署名から判定するので、拡張子を偽っていても分かります)
  • Eagleが参照しているパスにファイルが実在するか
  • .tmpファイルが残っていないか

一覧にはサムネイル画像とフォルダ名も表示するので、どのファイルなのかを目で確認できます。同じファイル名が別フォルダにあっても区別がつきます。

問題が見つかったアイテムには修復機能も用意しました。編集前の状態に戻すか、編集結果を採用するかを選べます。

使うときの注意

まずは検査だけを実行して、結果を確認してください。

修復機能は、ファイルの改名やmetadata.jsonの書き換えを行います。元のファイルはユーザーフォルダ\EagleExtFixerBackupに退避されるので取り返しはつきますが、修復を実行する前に、この退避先を必ず確認してください。

私自身、開発中に判定の前提を間違えて、91件の画像に不要な変更を加えてしまいました。幸い、退避したファイルから全件復元できました。念のため、大事なライブラリで使う前にはバックアップを取ることをおすすめします。

修復後はEagleの再起動が必要です。このとき、ウィンドウの×ボタンで閉じるだけでは不十分です。Eagleはタスクトレイに常駐し続けるので、画面右下のタスクトレイでEagleのアイコンを右クリックし、「Eagle を終了」を選んでください。なお、タスクマネージャーからの強制終了はおすすめしません。終了処理が中断されて、今回と同じ種類の破損を招く可能性があります。

AIイレーサー側を修正する

原因が分かったので、プラグイン自体に手を入れて壊れないようにしました。

改修したプラグインそのものの配布は控えますが、変更した箇所を紹介します。

事前準備

プラグインのフォルダは次の場所にあります。

C:\Users\(ユーザー名)\AppData\Roaming\Eagle\Plugins\ai-eraser

このフォルダを丸ごとコピーして、コピー側を編集してください。元のフォルダはそのまま残しておいてください。

以下は自己責任でお願いします。公式プラグインに手を入れる行為なので、不具合が出た場合は元のフォルダに戻してください。

変更点1 消し忘れコードのガード

対象ファイルはassets/js/index-fqkouu4j.jsです。

// Before
window.onbeforeunload = (event2) => {
  ai_eraser.stopServer();
};

// After
window.onbeforeunload = (event2) => {
  if (typeof ai_eraser?.stopServer === 'function') ai_eraser.stopServer();
};

先ほど触れた、存在しない関数を呼んでいる箇所です。関数があるときだけ呼ぶようにしました。これ自体は壊れる原因ではないので、ログをきれいにするための修正です。本題は次の変更点2になります。

変更点2 一時ファイルの置き場所を変える

対象ファイルはassets/js/index-fqkouu4j.jsのMain.replace()内です。

// Before
const tempFilePath = `${__dirname}/temp/${crypto.randomUUID()}.png`;

// After
const os = require('os');
const path = require('path');
const tempFilePath = path.join(os.tmpdir(), `eagle-ai-eraser-${crypto.randomUUID()}.png`);

一時ファイルの置き場所を、プラグイン配下のtempフォルダから、OSの一時フォルダに変更しました。

これで直る理由は単純です。ウィンドウを閉じたときに削除されるのはプラグイン配下のtempフォルダだけなので、そこから外に出せば消される心配がなくなります。Eagle側の処理は、かかる時間に関係なく最後まで終えられます。

利用者がいつウィンドウを閉じても、Eagleの内部処理がいつ終わっても、どちらのタイミングにも左右されないのが利点です。「完了表示を遅らせる」といった対策では、そもそも正しい完了時刻が分からないので解決になりません。

変更点3 起動時にウィンドウを最大化

対象ファイルはassets/js/index-fqkouu4j.jsのeagle.onPluginCreate内です。

// Before
await eagle.window.setOpacity(1);
app.mount("#app");

// After
await eagle.window.setOpacity(1);
await eagle.window.maximize();
app.mount("#app");

こちらは不具合対策ではなく、使い勝手の改善です。毎回ウィンドウを広げるのが手間だったので、最初から最大化して開くようにしました。細かい部分を消すときは、画像が大きく表示されているほうが作業しやすいです。

変更点4 公式版と共存させる

対象ファイルはmanifest.jsonです。

// Before
{
  "id": "ai-eraser",
  "name": "AIイレーサー"
}

// After
{
  "id": "ai-eraser-ex",
  "name": "AIイレーサー EX"
}

idを変えると、公式版とは別のプラグインとして認識されます。両方をインストールしたままにできますし、公式版が更新されても改修版は上書きされません。

_localesフォルダ内の各言語ファイル(全8言語)についても、manifest.app.nameの末尾に「EX」を追記しておくと、どの言語設定でも一覧で区別できます。

それでも困ったときの回避策

プラグインに手を入れたくない場合は、次の方法で被害を避けられます。

保存後すぐにウィンドウを閉じない。「エクスポート完了!」が出てから、十数秒ほど待って閉じてください。Eagle側の処理が終わるまでの時間を稼げます。

「別名で保存」を使う。AIイレーサーの別名保存は、今回問題になっている処理をまったく通りません。任意のフォルダに書き出してからEagleにインポートするひと手間はかかりますが、確実です。

定期的に検査する。壊れた直後なら状態が単純なので、修復も簡単です。放置して別の操作を重ねると状況が複雑になり、直すのが難しくなります。

最後に

本件はEagleのサポートに報告済みです。原因が特定できているので、公式側で修正される可能性があります。

この記事の内容は2026年8月8日時点のものです。

公式が修正した後に改修を適用すると、二重の対応になってしまう可能性があります。作業前に最新の情報を確認してください。

確認した環境は、Eagle 4.0.0 Build23、AIイレーサー v1.0.1、Windows11です。ライブラリの規模は約11万ファイルでした。

ログを見るまでは、的外れな仮説をいくつも立てていました。再現性が低い不具合を追うときは、まずログを見る。これが今回いちばん身にしみた教訓です。

追記(2026年10月3日)

8月の修正以降は問題なく使えていましたが、10月2日の夜から別の症状が出るようになりました。

症状

保存ボタンを押すと「消去中、お待ちください」のまま戻らず、押し直すと「エクスポート失敗」と表示されます。止まったアイテムは、前回と同じく新しいPNGと.jpg.tmpが残り、Eagle上はJPGのままになります。

今回は一時ファイルが消されたわけではなく、公式版のAIイレーサーでも同じように止まりました。前回とは別の問題で、改修版が原因でもありません。Eagleを再起動しても直らず、Eagle本体もエラーや警告を出さないため、原因はまだ分かっていません。PC再起動しても再発したので、メモリでの一時的な状態ではなく、ライブラリのなんらかの物理ファイルが原因の可能性は高いですが、ログではこれ以上詳しくはわかりませんでした。

不思議なことに、押し直した直後に止まっていた1回目の保存が動き出し、最後まで終わります。

そのため、失敗と表示されてもアイテムは正常なPNGになっていました。理由は分かりません。

止まるアイテムの共通点

止まったのは、どれも以前に止まって修復したことのあるアイテムでした。

フォーマットコンバーターで8件をまとめて変換したときも、止まった4件はすべてこれに当てはまります。

また、止まった例はすべて、拡張子が変わる置き換え(JPG→PNG、PNG→JPG)でした。

同じ拡張子のまま上書きするモザイク系のプラグインでは、一度も止まっていません。

変更点5 元の拡張子のまま保存する

そこで、AIイレーサーのMain.replace()を書き換えて、元がJPGの画像はJPGのまま保存するようにしました。

JPGは透過を持てないので、背景を白で塗ってから品質0.95のJPGに変換します。JPG以外は従来どおりPNGで保存します。

保存のたびに圧縮し直すため、画質はわずかに落ちます。PNGに変わるのを避けたい場合の割り切りです。

変更点2の内容も含めたMain.replace()全体は次のとおりです。

async replace() {
  try {
    let url = this.imageHistoryManager.current;
    const os = require("os");
    const path = require("path");
    const item = await eagle.item.getById(this.item_id);
    // 元が jpg なら jpg で保存し、replaceFile() で拡張子が変わらないようにする
    const isJpg = ["jpg", "jpeg"].includes((item.ext || "").toLowerCase());
    if (isJpg) {
      const img = await utils$1.image.create(url);
      const canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = "#ffffff";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0);
      url = canvas.toDataURL("image/jpeg", 0.95);
    }
    const tempFilePath = path.join(os.tmpdir(), `eagle-ai-eraser-${crypto.randomUUID()}.${isJpg ? "jpg" : "png"}`);
    await utils$1.file.save(tempFilePath, url);
    await item.replaceFile(tempFilePath);
    await utils$1.file.destroy(tempFilePath);
    this.status = "success";
  } catch (error) {
    eagle.log.error(error);
    this.status = "fail";
  } finally {
    eagle.log.info("end replace");
  }
}

変更後に24回保存して、止まったのは0回でした。

新たに出たクラッシュ

ただし変更後、保存ボタンを押した直後にプラグインの画面が真っ暗になり、消した作業が失われることが2回ありました。2回とも同じ画像です。Eagleへの置き換えが始まる前に落ちているので、ライブラリ側は無傷です。今回追加したJPG変換との関係は、はっきりしていません。

その画像を一度モザイクプラグインで何も塗らずに上書き保存し、もう一度AIイレーサーで作業したところ通りました。開き直すだけでは直りませんでした。ただ、1件しか試していないので、これで必ず直るとは言えません。

止まったときの対処

  • 保存ボタンを押し直すと進むことがあります。そのあと、拡張子整合性チェックで.tmpが残っていないか確認してください。
  • 修復済みのアイテムでも、metadata.jsonのファイルサイズが古いJPGのままのことがあります。おかしな表示が出たら確認してください。
  • 失敗した回の一時ファイル(eagle-ai-eraser-*.png)が、OSの一時フォルダに残ることがあります。不要なら消してかまいません。

余談

実はここまでの文章、AI(Claude)に作ってもらった文章をコピペしただけです(調査・対応も言うに及ばずAIです)。

どうなるんだろ、と思ってこのブログで初めてほぼAIだけで入力したページです(さすがに画像の差し替えとかだけは自分ですが)。

初稿のあと、2026/10/3に下記スキルを導入して書き換えてもらいました。

個人的にはAIくささがかなりあって、自分が書いた文章とかなり違うな~と思ったんですが、皆さんはどんな印象をお持ちでしょうか。

コメント

タイトルとURLをコピーしました