Eagle の公式プラグイン「AI イレーサー」を使っていると、まれに画像が壊れて Eagle 上で扱えなくなることがありました。ずっと原因が分からずにいたのですが、ログを解析したところはっきりと突き止めることができました。
調査のために自作したプラグインの配布と、プラグイン側の修正方法をまとめます。
どんな症状か
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 プラグインを自作しました。
zipを解凍して、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万ファイルでした。
ログを見るまでは的外れな仮説をいくつも立てていました。再現性が低い不具合を追うときは、まずログを見る。今回いちばん身にしみた教訓です。
コメント