イラストACにてイラストも公開中。まずはここのブログでチェック!!欲しいイラストがあれば無料でダウンロード出来ますのでDownloadへGo!

【完全版】InDesignが落ちる・戻れない!を1クリックで直す「最強の救急箱ツール」をPythonで作った話(コード公開)

【完全版】InDesignが落ちる・戻れない!を1クリックで直す「最強の救急箱ツール」をPythonで作った話(コード公開)

こんにちは、DTPの現場で戦う皆さん。

「締め切り前なのにInDesignが落ちた!」
「再起動しても、特定のファイルを開くとまた落ちる(無限ループ)」
「なぜか『取り消し(Ctrl+Z)』が3〜4回しかできない…」

こんな絶望的な経験、ありませんか?
InDesignは高機能ゆえに、長く使っているとどうしても「見えないゴミ」が溜まり、動作が不安定になります。

ネットで検索すると「環境設定をリセットせよ」と出てきますが、ショートカットキー(Ctrl+Alt+Shift)の同時押しはタイミングが難しいし、せっかく作ったワークスペースやショートカット設定まで消えてしまうのが怖くて、なかなか踏み切れないですよね。

そこで今回、「InDesignの不調を安全かつ確実に直すための救急箱ツール」 をPythonで自作しました。

今回は、そのツールの機能解説と、誰でも使えるように全ソースコードを公開します。
特に**「軽いデータなのに数回しかUndo(取り消し)できない」**という謎のバグに悩んでいる方は必見です。


目次

  1. なぜInDesignは不安定になるのか?

  2. 自作ツール「InDesign救急箱 (Ultimate V2)」の機能

    • ① 壊れたフォントの精密検査&隔離

    • ② キャッシュ削除(表示バグ対策)

    • ③ 環境設定リセット(設定保護機能付き!)

    • ④ 復元データの強制削除(クラッシュループ脱出)

  3. 【コード公開】Pythonで作るInDesign救急箱

  4. 使い方とexe化の手順

  5. まとめ


1. なぜInDesignは不安定になるのか?

PCのスペックは足りているはずなのに、なぜInDesignは重くなったり落ちたりするのでしょうか?
主な原因は以下の3つに集約されます。

A. 「SavedData」の肥大化・破損

InDesignは、操作の履歴(Undo情報)やウィンドウ位置などをInDesign SavedDataという一時ファイルに記録しています。
これが長年の使用で壊れると、**「メモリはあるのに履歴を書き込めない」**という状態になります。
「軽いデータなのに5回しか戻れない」という症状は、十中八九これが犯人です。

B. フォントの破損・競合

フリーフォントや古いフォントの中には、データ構造が微妙に壊れているものがあります。InDesignはフォントの描画にシビアなので、そういった「爆弾フォント」を読み込んだ瞬間にクラッシュします。

C. 復元データの破損

作業中に落ちた際、InDesignは「復元データ」を作ります。しかし、落ちた原因そのものが復元データに含まれている場合、次回起動時にそれを読み込んでまた落ちる…という「無限クラッシュループ」が発生します。

これらを手動でメンテナンスするのは非常に面倒です。そこで、これらを自動で解決するツールを作りました。


2. 自作ツール「InDesign救急箱 (Ultimate V2)」の機能

今回作成したツールは、GUI(画面)で操作できるWindows/Mac両対応のアプリです。
主な機能は以下の4つです。

① 壊れたフォントの精密検査&隔離

指定したフォントフォルダをスキャンし、以下の2段階でチェックします。

  1. 構造チェック: ファイルとして壊れていないか?

  2. 描画負荷テスト: 実際にメモリ上で「Testあ亜!?」のような文字を描画できるか?

特に2番目が重要です。「ファイルとしては開けるけど、日本語データが壊れている」といった隠れ危険フォントも見つけ出します。
さらに、TTC(TrueType Collection)形式の全フォントチェックにも対応。
検知された危険なフォントは、ボタン一つで「ゴミ箱」へ移動できます(完全削除ではないので安心です)。

② キャッシュ削除(表示バグ対策)

PC内のAdobe関連フォルダから、トラブルの元凶となりやすいAdobeFnt*.lst(フォントリストキャッシュ)を一括検索して削除します。
「文字が化ける」「フォントメニューがおかしい」といった軽微なトラブルはこれで直ります。

③ 環境設定リセット(設定保護機能付き!)

これが今回の目玉機能です。
InDesignの挙動がおかしい時、公式推奨の対処法は「環境設定のリセット」ですが、これをやるとワークスペースやショートカットが初期化されてしまいます。

このツールでは、リセットを行う前に**「Workspaces」と「Shortcut Sets」フォルダを自動でバックアップ**します。
その後、不具合の原因であるInDesign DefaultsInDesign SavedDataだけをリネーム(.old化)して無効化します。

つまり、「不具合は直るけど、俺の使いやすい設定は守られる(またはすぐに戻せる)」 という、いいとこ取りのリセットが可能です。
もちろん、インストールされている全バージョンのInDesignを自動検知して、特定のバージョンだけをリセットできます。

④ 復元データの強制削除(クラッシュループ脱出)

InDesignが起動すらしなくなった時のための「最終奥義」です。
InDesign Recoveryフォルダの中身を強制的に空にします。
これにより、壊れた復元データを読み込もうとして落ちるループから脱出できます。

3. 【コード公開】Pythonで作るInDesign救急箱

では、実際のPythonコードを公開します。
GUIライブラリのtkinterを使い、ファイル操作やフォント解析を行っています。

必要なライブラリ:
事前に以下のコマンドでライブラリをインストールしてください。

Bash

pip install fonttools pillow send2trash

ソースコード (indesign_doctor.py):

Python

import os
import threading
import tkinter as tk
import platform
import fnmatch
import datetime
import shutil
import subprocess  ### <<< 変更: プロセスチェックのために追加
from tkinter import ttk, filedialog, scrolledtext, messagebox

# ゴミ箱ライブラリ
try:
    from send2trash import send2trash
except ImportError:
    def send2trash(path):
        os.remove(path)

from fontTools.ttLib import TTFont, TTCollection
from PIL import Image, ImageFont, ImageDraw

class FontCheckerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("InDesign救急箱 (Ultimate V2)")
        self.root.geometry("500x620") # UI要素が増えたので縦長に

        style = ttk.Style()
        style.configure("Bold.TButton", font=("Meiryo UI", 9, "bold"))
        style.configure("Danger.TButton", foreground="red", font=("Meiryo UI", 9, "bold"))
        style.configure("Blue.TButton", foreground="blue", font=("Meiryo UI", 9, "bold"))

        # =========================================
        #  ① フォント検査エリア
        # =========================================
        frame_check = ttk.LabelFrame(root, text="① フォントファイルの健康診断", padding=10)
        frame_check.pack(fill=tk.X, padx=10, pady=5)
        
        frame_path = ttk.Frame(frame_check)
        frame_path.pack(fill=tk.X)
        ttk.Label(frame_path, text="対象フォルダ:").pack(side=tk.LEFT)
        self.entry_path = ttk.Entry(frame_path)
        self.entry_path.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
        self.entry_path.insert(0, self.get_system_font_folder())
        ttk.Button(frame_path, text="参照...", command=self.select_folder).pack(side=tk.LEFT)

        self.btn_start = ttk.Button(frame_check, text="フォント検査を実行", command=self.start_check_thread, style="Bold.TButton")
        self.btn_start.pack(fill=tk.X, pady=5)
        
        self.btn_delete = ttk.Button(frame_check, text="検知されたファイルをゴミ箱へ移動", command=self.confirm_trash_files, style="Danger.TButton", state='disabled')
        self.btn_delete.pack(fill=tk.X, pady=5)
        
        self.progress = ttk.Progressbar(frame_check, orient=tk.HORIZONTAL, mode='determinate')
        self.progress.pack(fill=tk.X)
        self.lbl_status = ttk.Label(frame_check, text="待機中...")
        self.lbl_status.pack()

        # =========================================
        #  ② キャッシュ削除エリア
        # =========================================
        frame_cache = ttk.LabelFrame(root, text="② おかしくなったらまずコレ (キャッシュ削除)", padding=10)
        frame_cache.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(frame_cache, text="※「AdobeFnt*.lst」を削除します。表示がおかしい時に有効。", foreground="#555").pack(anchor=tk.W)
        self.btn_cache = ttk.Button(frame_cache, text="フォントキャッシュを削除", command=self.confirm_delete_cache)
        self.btn_cache.pack(fill=tk.X, pady=2)

        # =========================================
        #  ③ 環境設定 & 復元データ (バージョン指定)
        # =========================================
        frame_reset = ttk.LabelFrame(root, text="③ 起動しない・すぐ落ちる時はコレ", padding=10)
        frame_reset.pack(fill=tk.X, padx=10, pady=5)
        
        # バージョン選択
        frame_ver = ttk.Frame(frame_reset)
        frame_ver.pack(fill=tk.X, pady=5)
        ttk.Label(frame_ver, text="対象バージョン:").pack(side=tk.LEFT)
        self.combo_ver = ttk.Combobox(frame_ver, state="readonly", width=30)
        self.combo_ver.pack(side=tk.LEFT, padx=5)
        
        versions = self.scan_indesign_versions()
        self.combo_ver['values'] = versions
        if versions: self.combo_ver.current(0)
        else: self.combo_ver.set("InDesignが見つかりません")

        # ----------------------------------------------------
        # A. 環境設定リセット (保護機能付き)
        # ----------------------------------------------------
        ttk.Label(frame_reset, text="A. 環境設定をリセット (バックアップ付)", foreground="#333", font=("Meiryo UI", 9, "bold")).pack(anchor=tk.W, pady=(10,0))
        
        # ★追加機能:設定維持チェックボックス
        self.var_keep_settings = tk.BooleanVar(value=True)
        self.chk_keep = ttk.Checkbutton(frame_reset, text="ワークスペースとキーボードショートカットを保護(バックアップ)する", variable=self.var_keep_settings)
        self.chk_keep.pack(anchor=tk.W, padx=10)
        
        self.btn_reset = ttk.Button(frame_reset, text="環境設定をリセットする", command=self.confirm_reset_prefs, style="Blue.TButton")
        self.btn_reset.pack(fill=tk.X, pady=2)

        # ----------------------------------------------------
        # B. 復元データ削除
        # ----------------------------------------------------
        ttk.Label(frame_reset, text="B. クラッシュループ脱出 (復元データを削除)", foreground="#333", font=("Meiryo UI", 9, "bold")).pack(anchor=tk.W, pady=(15,0))
        self.btn_recovery = ttk.Button(frame_reset, text="復元データ(Recovery Data)を空にする", command=self.confirm_delete_recovery)
        self.btn_recovery.pack(fill=tk.X, pady=2)

        # =========================================
        #  ログエリア
        # =========================================
        frame_log = ttk.Frame(root, padding=10)
        frame_log.pack(fill=tk.BOTH, expand=True)
        
        ttk.Label(frame_log, text="実行ログ:").pack(anchor=tk.W)
        self.txt_log = scrolledtext.ScrolledText(frame_log, state='disabled', height=10)
        self.txt_log.pack(fill=tk.BOTH, expand=True)

        self.txt_log.tag_config("ERROR", foreground="red")
        self.txt_log.tag_config("WARNING", foreground="#D35400")
        self.txt_log.tag_config("SAFE", foreground="green")
        self.txt_log.tag_config("INFO", foreground="black")
        self.txt_log.tag_config("CACHE", foreground="blue")
        self.txt_log.tag_config("TRASH", foreground="purple")
        self.txt_log.tag_config("RESET", foreground="#008080")
        self.txt_log.tag_config("BACKUP", foreground="#800080") # Purple for backup

        self.is_running = False
        self.detected_files = []

    ### <<< 変更: InDesignが起動中かチェックする関数を追加
    def is_indesign_running(self):
        """InDesignが起動中かチェックする"""
        system = platform.system()
        try:
            if system == "Windows":
                # tasklistの出力をチェック
                output = subprocess.check_output(['tasklist'], universal_newlines=True, creationflags=0x08000000) # コンソールウィンドウを非表示に
                return 'indesign.exe' in output.lower()
            elif system == "Darwin": # macOS
                # ps -ax の出力をチェック
                output = subprocess.check_output(['ps', '-ax'], universal_newlines=True)
                # "Adobe InDesign" がプロセス名に含まれているかで判定
                return 'adobe indesign' in output.lower()
        except (subprocess.CalledProcessError, FileNotFoundError):
            # コマンドが存在しない、またはエラーが発生した場合
            self.log("プロセスの確認に失敗しました。", "WARNING")
            return False # 安全のためにFalseを返す
        return False

    def get_system_font_folder(self):
        system = platform.system()
        if system == "Windows": return r"C:\Windows\Fonts"
        elif system == "Darwin": return os.path.expanduser("~/Library/Fonts")
        return ""

    def scan_indesign_versions(self):
        found_versions = ["全バージョン一括 (All)"]
        search_path = ""
        user_home = os.path.expanduser("~")
        system = platform.system()
        if system == "Windows":
            search_path = os.path.join(user_home, "AppData", "Roaming", "Adobe", "InDesign")
        elif system == "Darwin":
            search_path = os.path.join(user_home, "Library", "Preferences", "Adobe InDesign")
        
        if os.path.exists(search_path):
            try:
                dirs = [d for d in os.listdir(search_path) if os.path.isdir(os.path.join(search_path, d)) and "Version" in d]
                dirs.sort(reverse=True)
                found_versions.extend(dirs)
            except: pass
        return found_versions

    def select_folder(self):
        folder = filedialog.askdirectory(initialdir=self.entry_path.get())
        if folder:
            self.entry_path.delete(0, tk.END)
            self.entry_path.insert(0, folder)

    def log(self, message, tag="INFO"):
        self.txt_log.configure(state='normal')
        self.txt_log.insert(tk.END, message + "\n", tag)
        self.txt_log.see(tk.END)
        self.txt_log.configure(state='disabled')

    def set_buttons_state(self, state):
        self.btn_start.config(state=state)
        self.btn_cache.config(state=state)
        self.btn_reset.config(state=state)
        self.btn_recovery.config(state=state)
        if state == 'normal' and not self.detected_files:
            self.btn_delete.config(state='disabled')
        elif state == 'disabled':
            self.btn_delete.config(state='disabled')

    # =========================================
    #  ③ 環境設定リセット機能 (保護機能付き)
    # =========================================
    def confirm_reset_prefs(self):
        if self.is_running: return
        
        ### <<< 変更: InDesignの起動チェックを追加 if self.is_indesign_running(): messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。") return target_ver = self.combo_ver.get() if "InDesignが見つかりません" in target_ver: return keep_msg = "\n★ワークスペースとショートカットは保護されます" if self.var_keep_settings.get() else "" res = messagebox.askyesno( "環境設定リセット", f"【{target_ver}】の環境設定をリセットしますか?\n\n" "「InDesign Defaults」と「SavedData」を初期化します。\n" "(戻る回数制限バグなどに有効です)\n" f"{keep_msg}\n\n" "※必ずInDesignを終了してから実行してください。" ) if res: self.start_reset_thread(target_ver) def start_reset_thread(self, target_ver): self.is_running = True self.set_buttons_state('disabled') self.txt_log.configure(state='normal') self.txt_log.delete(1.0, tk.END) self.txt_log.configure(state='disabled') thread = threading.Thread(target=self.run_reset_prefs, args=(target_ver,), daemon=True) thread.start() def run_reset_prefs(self, target_ver): self.log(f"--- 環境設定リセット: {target_ver} ---", "RESET") # リセット対象ファイル target_files = ["InDesign Defaults", "InDesign SavedData"] # 保護対象フォルダ protected_folders = ["Workspaces", "InDesign Shortcut Sets"] user_home = os.path.expanduser("~") system = platform.system() paths_to_check = [] if system == "Windows": paths_to_check.append(os.path.join(user_home, "AppData", "Roaming", "Adobe", "InDesign")) paths_to_check.append(os.path.join(user_home, "AppData", "Local", "Adobe", "InDesign")) elif system == "Darwin": paths_to_check.append(os.path.join(user_home, "Library", "Preferences", "Adobe InDesign")) paths_to_check.append(os.path.join(user_home, "Library", "Caches", "Adobe InDesign")) reset_count = 0 backup_count = 0 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") is_all = "All" in target_ver do_keep = self.var_keep_settings.get() for root_base in paths_to_check: if not os.path.exists(root_base): continue for dirpath, dirnames, filenames in os.walk(root_base): # ターゲットバージョンか判定 if is_all or (target_ver in dirpath): # ---------------------------------------- # ★ ワークスペース等のバックアップ処理 # ---------------------------------------- if do_keep and "Roaming" in dirpath or "Preferences" in dirpath: # 設定フォルダのルート(ja_JPなど)にいるか確認 for p_folder in protected_folders: if p_folder in dirnames: src_path = os.path.join(dirpath, p_folder) # バックアップ先(同じ階層に _Backup_日付 を作る) backup_folder_name = f"{p_folder}_Backup_{timestamp}" dst_path = os.path.join(dirpath, backup_folder_name) try: if os.path.exists(dst_path): shutil.rmtree(dst_path) # 念のため既存なら消す shutil.copytree(src_path, dst_path) self.log(f"保護(Backup): {p_folder} -> {backup_folder_name}", "BACKUP")
                                    backup_count += 1
                                except Exception as e:
                                    self.log(f"保護失敗: {p_folder} ({e})", "WARNING")

                    # ----------------------------------------
                    # ★ 設定ファイルのリセット(リネーム)
                    # ----------------------------------------
                    for target in target_files:
                        if target in filenames:
                            file_path = os.path.join(dirpath, target)
                            backup_name = f"{target}.old_{timestamp}"
                            try:
                                os.rename(file_path, os.path.join(dirpath, backup_name))
                                reset_count += 1
                                self.log(f"リセット: {target}", "RESET")
                            except: pass

        self.log("-" * 30)
        
        # 結果メッセージの作成
        result_msg = ""
        if reset_count > 0:
            result_msg += f"・設定ファイル {reset_count} 個をリセットしました。\n"
        if backup_count > 0:
            result_msg += f"・ワークスペース/ショートカットを {backup_count} 箇所バックアップしました。\n"
            
        if result_msg:
            self.log("完了しました。InDesignを起動してください。", "SAFE")
            if do_keep:
                self.log("※もし設定が消えていても、作成された「_Backup」フォルダから\n中身を戻せば復元可能です。", "INFO")
            messagebox.showinfo("完了", f"処理が完了しました。\n\n{result_msg}")
        else:
            self.log("対象の設定ファイルが見つかりませんでした。", "WARNING")
            messagebox.showwarning("結果", "リセット対象が見つかりませんでした。")
            
        self.finish_task_common()

    # =========================================
    #  ④ 復元データ削除
    # =========================================
    def confirm_delete_recovery(self):
        if self.is_running: return

        ### <<< 変更: InDesignの起動チェックを追加 if self.is_indesign_running(): messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。") return target_ver = self.combo_ver.get() if "InDesignが見つかりません" in target_ver: return res = messagebox.askyesno("確認", f"【{target_ver}】の自動復元データを削除しますか?\n保存されていない作業データは消えます。") if res: self.start_recovery_thread(target_ver) def start_recovery_thread(self, target_ver): self.is_running = True self.set_buttons_state('disabled') self.txt_log.configure(state='normal') self.txt_log.delete(1.0, tk.END) self.txt_log.configure(state='disabled') thread = threading.Thread(target=self.run_delete_recovery, args=(target_ver,), daemon=True) thread.start() def run_delete_recovery(self, target_ver): self.log(f"--- 復元データ削除: {target_ver} ---", "RESET") target_folder_name = "InDesign Recovery" user_home = os.path.expanduser("~") system = platform.system() paths_to_check = [] if system == "Windows": paths_to_check.append(os.path.join(user_home, "AppData", "Local", "Adobe", "InDesign")) elif system == "Darwin": paths_to_check.append(os.path.join(user_home, "Library", "Caches", "Adobe InDesign")) deleted_count = 0 is_all = "All" in target_ver for root_base in paths_to_check: if not os.path.exists(root_base): continue for dirpath, dirnames, filenames in os.walk(root_base): if is_all or (target_ver in dirpath): if target_folder_name in dirnames: recovery_path = os.path.join(dirpath, target_folder_name) try: files = os.listdir(recovery_path) if not files: continue for f in files: f_path = os.path.join(recovery_path, f) if os.path.isfile(f_path): os.remove(f_path) elif os.path.isdir(f_path): shutil.rmtree(f_path) deleted_count += 1 self.log(f"削除: Recoveryフォルダ内", "RESET") except Exception as e: self.log(f"失敗: {e}", "WARNING") self.log("-" * 30) if deleted_count > 0:
            self.log(f"完了: {deleted_count} 個削除しました。", "SAFE")
            messagebox.showinfo("完了", "復元データを削除しました。")
        else:
            self.log("復元データは空でした。", "INFO")
            messagebox.showinfo("結果", "復元データは空でした。")
        self.finish_task_common()

    # =========================================
    #  ② キャッシュ削除
    # =========================================
    def confirm_delete_cache(self):
        if self.is_running: return
        
        ### <<< 変更: InDesignの起動チェックを追加
        if self.is_indesign_running():
            messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。")
            return
        
        res = messagebox.askyesno("確認", "フォントキャッシュ(AdobeFnt*.lst)を削除しますか?")
        if res: self.start_cache_thread()

    def start_cache_thread(self):
        self.is_running = True
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_delete_cache, daemon=True)
        thread.start()

    def run_delete_cache(self):
        self.log("--- キャッシュ削除開始 ---", "CACHE")
        user_home = os.path.expanduser("~")
        roots = []
        if platform.system() == "Windows":
            roots = [os.path.join(user_home, "AppData", "Local", "Adobe"),
                     os.path.join(user_home, "AppData", "Roaming", "Adobe")]
        elif platform.system() == "Darwin":
            roots = [os.path.join(user_home, "Library", "Caches", "Adobe InDesign"),
                     os.path.join(user_home, "Library", "Application Support", "Adobe")]
        
        count = 0
        for r in roots:
            if os.path.exists(r):
                for dp, dn, fn in os.walk(r):
                    for f in fnmatch.filter(fn, 'AdobeFnt*.lst'):
                        try:
                            os.remove(os.path.join(dp, f))
                            count += 1
                        except: pass
        self.log(f"完了: {count} 個削除しました。", "SAFE")
        self.finish_task_common()

    # =========================================
    #  ① フォント検査
    # =========================================
    def confirm_trash_files(self):
        if not self.detected_files: return
        res = messagebox.askyesno("確認", f"{len(self.detected_files)} 個のファイルをゴミ箱へ移動しますか?")
        if res: self.run_trash_files()

    def run_trash_files(self):
        self.log("--- 削除処理開始 ---", "TRASH")
        success = 0
        targets = list(set(self.detected_files))
        for path in targets:
            try:
                send2trash(path)
                self.log(f"ゴミ箱へ: {os.path.basename(path)}", "TRASH")
                success += 1
            except: pass
        self.detected_files = []
        self.btn_delete.config(state='disabled')
        messagebox.showinfo("完了", f"{success} 個移動しました。")

    def start_check_thread(self):
        path = self.entry_path.get()
        if not path or not os.path.exists(path): return
        if self.is_running: return
        self.is_running = True
        self.detected_files = []
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_check, args=(path,), daemon=True)
        thread.start()

    def run_check(self, folder_path):
        self.log("--- チェック開始 ---", "INFO")
        valid_exts = ('.ttf', '.otf', '.ttc')
        file_list = []
        for root, dirs, files in os.walk(folder_path):
            for filename in files:
                if filename.lower().endswith(valid_exts):
                    file_list.append(os.path.join(root, filename))

        total = len(file_list)
        if total == 0:
            self.log("ファイルなし", "WARNING")
            self.finish_task_common()
            return

        self.root.after(0, lambda: self.progress.configure(maximum=total, value=0))
        corrupt, warning = 0, 0

        for idx, file_path in enumerate(file_list):
            filename = os.path.basename(file_path)
            self.root.after(0, lambda v=idx+1, t=f"確認中: {filename}": self.update_ui(v, t))

            if os.path.getsize(file_path) == 0:
                self.log(f"[× 破損] {filename}", "ERROR")
                corrupt += 1
                self.detected_files.append(file_path)
                continue

            font_indices = range(len(TTCollection(file_path))) if filename.lower().endswith('.ttc') else [0]
            is_bad = False
            for i in font_indices:
                if is_bad: break
                try:
                    font = TTFont(file_path, fontNumber=i)
                    for tbl in ['name', 'cmap']: _ = font[tbl]
                    font.close()
                except:
                    self.log(f"[× 破損] {filename}", "ERROR")
                    corrupt += 1
                    is_bad = True
                    continue
                try:
                    img_font = ImageFont.truetype(file_path, 40, index=i)
                    ImageDraw.Draw(Image.new("RGB",(1,1))).text((0,0), "Testあ亜", font=img_font)
                except:
                    self.log(f"[! 危険] {filename}", "WARNING")
                    warning += 1
                    is_bad = True
            if is_bad: self.detected_files.append(file_path)

        self.log("-" * 30)
        if corrupt == 0 and warning == 0:
            self.log("★ 問題なし", "SAFE")
            messagebox.showinfo("完了", "正常でした。")
        else:
            self.log(f"破損: {corrupt} / 危険: {warning}", "ERROR")
            messagebox.showwarning("完了", "問題が見つかりました。")
            self.btn_delete.config(state='normal')
        self.finish_task_common()

    def update_ui(self, value, text):
        self.progress['value'] = value
        self.lbl_status.config(text=text)

    def finish_task_common(self):
        self.root.after(0, self._reset_ui)

    def _reset_ui(self):
        self.progress['value'] = 0
        self.lbl_status.config(text="待機中")
        self.set_buttons_state('normal')
        self.is_running = False

if __name__ == "__main__":
    root = tk.Tk()
    app = FontCheckerApp(root)
    root.mainloop()

4. 使い方とexe化の手順

このコードを自分のPCで動かす、または同僚に配布するための.exeファイルを作る手順です。

手順①:Pythonのインストール

Python公式サイトからPythonをダウンロードしてインストールします。インストーラーの「Add Python to PATH」にチェックを入れるのを忘れずに。

手順②:ライブラリのインストール

コマンドプロンプト(またはターミナル)を開き、以下のコマンドを入力します。

Bash

pip install pyinstaller fonttools pillow send2trash

手順③:exeファイルの作成

上記のPythonコードをコピーして、indesign_doctor.py という名前で保存します。
保存したフォルダでコマンドプロンプトを開き、以下を実行します。

Bash

pyinstaller --onefile --noconsole --clean indesign_doctor.py

少し待つと、同じフォルダ内にdistというフォルダができ、その中にindesign_doctor.exeが完成しています!
これを実行するだけで、誰でもInDesignのメンテナンスが可能になります。


5. まとめ

InDesignのトラブルの9割は、このツールで解決できます。

  • 戻る回数が少ない? → 環境設定リセット(SavedDataの初期化)で直ります。

  • 特定のファイルで落ちる? → フォント検査で「危険」なフォントを探して捨ててください。

  • 起動しない? → 復元データ削除でループから脱出できます。

「原因不明の不調」に怯えながら作業するのは精神衛生上よくありません。
ぜひこのツールをUSBメモリに入れて、「InDesign救急箱」として現場に常備してみてください。あなたの、そしてチームの貴重な時間を守れるはずです。

EXEのダウンロードはこちら(ちなみにアイコンは作ってませんのでご了承を)


2026/01/31 コードの変更。リセット時、indesignが落ちてるか確認処理を追加
2026/01/30 公開