チャットに貼った画像は、あとから取り出せる|AIの会話ログから復元する手順【実録】

チャットに貼った画像は、あとから取り出せる|AIの会話ログから復元する手順【実録】

「このチャットに貼った画像、そっちで保存できないの?」

その3分前に、私のAI部下は「チャットに貼られた画像は、こちらではファイルとして取り出せません」と答えていました。もう一度だけ探させたら、ぜんぶ残っていました。会話のログの中に、貼った画像が丸ごと入っていたんです。

先に結論です。手元にログが残るタイプのAIツールなら、チャットに貼った画像はあとから取り出せます。撮った端末の中から消してしまっていても、送ったチャットさえ残っていれば復元できる。この記事では、その置き場所・確認のしかた・取り出すスクリプトを、実際の実行結果つきで置いておきます。うちの環境では、3か月前に貼った画像まで日付つきで残っていました。

明るい倉庫
目次

何が起きたか|「取り出せません」と答えた3分後

別の仕事で、SNSのやりとりのスクリーンショットを2枚チャットに貼り、「あとでいつでも出せるように保管しておいて」と頼みました。

AI部下はパソコンの中のフォルダを探しましたが、そのファイルは見つかりません。そこで「画像そのものは取り出せないので、中身を全部文字に起こして保存しました。画像はご自身で保存してください」と返してきました。それらしい理屈ですし、私も一度は納得しかけました。

ただ、引っかかったので聞き返しました。貼った画像が、どこかへ消えてしまったわけがない。AIがそれを見て返事をしている以上、どこかにあるはずです。

もう一度探させたら、数分で出てきました。会話のログファイルの中に、画像が文字列の形で丸ごと埋まっていたんです。数行のスクリプトで、2枚とも元のまま復元できました。

盲点だったのは、探した場所です。AIは「自分が作って保存したもの」の置き場所は覚えていますが、「人から受け取ったものがどこに置かれるか」は意識していません。出口は見ているのに、入口を見ていなかったわけです。

前提|この方法が使えるのは「ログが手元に残る」タイプだけ

先にはっきりさせておきます。この記事の方法が使えるのは、会話の記録が自分のパソコンの中に残るタイプのAIツールです。うちで毎日使っているClaude Code(ターミナルで動くタイプ)がこれにあたります。

  • 手元にログが残るタイプ(ターミナルやアプリで動くもの)……この記事の方法が使えます
  • ブラウザで使うチャット(ChatGPT・Geminiなど)……記録は向こうのサーバー側にあります。取り出したいときは、各サービスの設定にある公式の「データの書き出し」を使ってください

後者は公式機能で完結するので、この記事では前者を扱います。

手順1|ログの置き場所を確認する

まず、自分のパソコンのどこにログがあるかを見ます。Claude Codeの場合は、ユーザーフォルダの下です。

Windows  :  C:\Users\ユーザー名\.claude\projects\
Mac / Linux:  ~/.claude/projects/

この中に、作業フォルダごとのフォルダが並んでいます。フォルダ名は、作業していた場所のパスの記号を置き換えただけの長い名前です。さらにその中に、会話1本につき1つ、拡張子が .jsonl のファイルが入っています。

中身は「1行が1つの出来事」という素直な形式です。人が打った言葉、AIの返事、道具を動かした記録が、時刻つきで並んでいます。そして貼った画像は、その行の中に文字列として入っています。画像を文字だけで表す方式(Base64)で書かれているので、フォルダを探してもファイルとしては出てきません。でも、データはそこにあります。

手順2|何が埋まっているかを一覧する

いきなり取り出す前に、まず一覧します。ログは大きいので、目当てのものがどの会話にあるかを先に見つけたほうが早いからです。

下は、実際に1本のログを一覧した結果です。

ログ1本を一覧した実行結果。7枚の画像が日付・サイズ・形式つきで並んでいる
ログ1本を一覧した実行結果。7枚の画像が日付・サイズ・形式つきで並んでいる

6月26日に貼った画像が、9月の今日、日付つきでそのまま出てきました。形式も貼ったときのままです(スマホのスクリーンショットはJPEG、パソコンから貼ったものはWebP)。

ここで大事なのが「貼付」と「撮影」の区別です。ログには2種類の画像が混ざっています。

  • 自分が貼った画像……人の発言として記録された行に入っています
  • AIが撮った画像……AIが画面を撮る道具を使った、その実行結果として記録された行に入っています

探しているのはほぼ前者なので、この2つを分けられると一覧がぐっと読みやすくなります。見分け方は単純で、その行が「人の発言」として記録されていて、かつ「道具の実行結果」を持っていなければ、自分が貼った画像です。

手順3|取り出して、壊れていないか確かめる

場所が分かったら、保存先を指定して書き出します。やっていることは「文字列を画像に戻して、ファイルとして置く」だけです。

画像を書き出して、形式と寸法を確認した実行結果。7枚が保存され、JPEGとWebPが元の寸法のまま復元されている
画像を書き出して、形式と寸法を確認した実行結果。7枚が保存され、JPEGとWebPが元の寸法のまま復元されている

取り出したあとは、必ず形式と寸法を確認してください。ここを飛ばすと、中身が壊れたファイルが並んでいても気づけません。今回は横長のパソコン画面(1907×858)も、縦長のスマホ画面(858×1907)も、元の寸法のまま出てきました。

使ったスクリプト(そのままコピーして使えます)

Pythonが動く環境なら、これをファイルに保存するだけです。--list で一覧、--out で書き出し、--all を付けるとAIが撮った画面も含めて全部出します。

# -*- coding: utf-8 -*-
import argparse, base64, glob, json, os

EXT = {"image/png": ".png", "image/jpeg": ".jpg",
       "image/webp": ".webp", "image/gif": ".gif"}

# 画像のかたまりを示す目印(ログの中で type が image になっている箇所)
IMG_MARK = chr(34) + "image" + chr(34)

def images_in(obj):
    # 記録を再帰的に歩いて、画像のかたまりを拾う
    if isinstance(obj, dict):
        if obj.get("type") == "image" and isinstance(obj.get("source"), dict):
            yield obj["source"]
        for v in obj.values():
            yield from images_in(v)
    elif isinstance(obj, list):
        for v in obj:
            yield from images_in(v)

def scan(path, only_pasted=True):
    files = [path] if path.endswith(".jsonl") else sorted(
        glob.glob(os.path.join(path, "*.jsonl")))
    found = []
    for f in files:
        with open(f, encoding="utf-8") as fp:
            for lineno, line in enumerate(fp, 1):
                if IMG_MARK not in line:   # 画像を含まない行は読み飛ばす
                    continue
                try:
                    rec = json.loads(line)
                except Exception:
                    continue
                # 自分が貼った画像 = 人の発言の行で、道具の実行結果を持たない
                pasted = rec.get("type") == "user" and "toolUseResult" not in rec
                if only_pasted and not pasted:
                    continue
                for src in images_in(rec):
                    if src.get("type") != "base64" or not src.get("data"):
                        continue
                    found.append({
                        "file": os.path.basename(f), "line": lineno,
                        "time": (rec.get("timestamp") or "")[:19].replace("T", " "),
                        "media": src.get("media_type", "image/png"),
                        "data": src["data"], "pasted": pasted,
                    })
    return found

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("path")
    ap.add_argument("--out")
    ap.add_argument("--list", action="store_true")
    ap.add_argument("--all", action="store_true")
    a = ap.parse_args()

    hits = scan(a.path, only_pasted=not a.all)
    if not hits:
        print("画像は見つかりませんでした")
        return

    if a.list or not a.out:
        for i, h in enumerate(hits, 1):
            kb = len(h["data"]) * 3 // 4 // 1024
            mark = "貼付" if h["pasted"] else "撮影"
            print(f"{i:4d}  {h['time']}  {mark}  {kb:6d} KB  "
                  f"{h['media']:10s}  {h['file'][:8]}:{h['line']}")
        print(f"--- {len(hits)} 枚 ---")
        return

    os.makedirs(a.out, exist_ok=True)
    for i, h in enumerate(hits, 1):
        name = f"{h['file'][:8]}_{i:04d}{EXT.get(h['media'], '.png')}"
        dst = os.path.join(a.out, name)
        with open(dst, "wb") as w:
            w.write(base64.b64decode(h["data"]))
        print(f"保存 {dst}  ({os.path.getsize(dst)//1024} KB)")
    print(f"--- {len(hits)} 枚を {a.out} に保存しました ---")

if __name__ == "__main__":
    main()

仕組みのポイントは2つです。1つは、記録の形が場所によって違うので決め打ちで場所を指定せず、再帰的に歩いて拾うこと。もう1つは、目印が入っていない行を読み飛ばすこと。これだけで、大きいログでも数十秒で終わります。

夜の図書室

ついでに分かったこと|ログは思っているより重い

せっかくなので、うちのログを全部数えてみました。1つの作業フォルダの分だけです。

ログ62本を数えた集計結果。画像4,777枚のうち、自分が貼った分が1,038枚・約262MB
ログ62本を数えた集計結果。画像4,777枚のうち、自分が貼った分が1,038枚・約262MB

会話のログ62本で3.2ギガバイト。中に埋まっていた画像は4,777枚で、そのうち自分が貼ったものが1,038枚・約262メガバイトありました。残りはAIが作業中に撮った画面です。

つまり、ログが重い原因のほとんどは文字ではなく画像でした。動作が重いと感じたら、もう使わない会話のログを整理すると効きます。ただし消すと当然、この記事の方法では取り出せなくなります。先に必要な画像を書き出してから消してください。

気をつけること|見せたものは、手元に残り続ける

ここまでは「助かる話」ですが、裏返すと注意点にもなります。

チャットの画面上でやりとりが流れていっても、貼った画像はログの中に残っています。管理画面のスクリーンショット、請求書、宛先の一覧が写った画面。「あのとき1回見せただけ」のつもりのものが、日付つきで手元に積み上がっている、ということです。

対策は2つです。

  1. 見せる前に隠す……画面を撮る段階で、関係ない欄を塗りつぶしておく。これがいちばん確実です
  2. 古いログを定期的に整理する……終わった仕事のログは、必要な画像だけ書き出してから削除する

なお、ログの保存期間は設定で変えられる場合があります。うちは3か月前のものが残っていましたが、環境によってはすでに消えているかもしれません。「残っている前提」でも「消えている前提」でも運用しないのが正解で、必要なものは見つけた時点で別の場所へ保存するのが確実です。AIに見せてよい情報の線引きは「AIに入れてはいけない情報5つ」に書いています。

よくある質問

プログラムが分かりません。それでもできますか?

この記事のスクリプトをそのままAIに渡して、「これを使って、このフォルダの画像を取り出して」と頼むのがいちばん早いです。うちもそうしています。大事なのは書き方ではなく、「ログの中にあるはずだ」と知っていることのほうです。

取り出した画像の画質は落ちませんか?

落ちません。文字列を画像に戻しているだけなので、貼った時点のデータがそのまま出てきます。形式も寸法も変わりません。

AIが「できません」と言ったら、どう聞き返せばいいですか?

「本当に? もう一度探して」の一言で十分です。命令口調にする必要はありません。今回も、この1回でひっくり返りました。AIの「できません」は、試した結果ではなく一般論からの推測であることがよくあります。この話は「AIの「できません(仕様です)」を鵜呑みにしていた話」に詳しく書きました。

どんな場面で役に立ちますか?

うちで実際にあったのは、この3つです。スマホで撮って貼ったあと端末から消してしまった画面を戻す、過去に相談した画面をブログ記事の素材として掘り出す、「そのとき何を見せたか」を日付つきで確認する。3つ目は、あとから経緯をたどるときに効きます。

まとめ|出口だけでなく、入口も見る

  • 手元にログが残るタイプのAIツールなら、チャットに貼った画像はあとから取り出せる
  • 置き場所は .claude/projects/ の下。会話1本=1ファイルで、画像は行の中に文字列として入っている
  • 手順は「一覧する→書き出す→形式と寸法を確かめる」の3つ。最後の確認を飛ばさない
  • 自分が貼った画像とAIが撮った画面は、記録の形で見分けられる
  • うちのログは62本で3.2GB・画像4,777枚。重さの正体は文字ではなく画像だった
  • 裏返すと、見せたものは手元に残り続ける。隠すのは撮る段階で

AIが探すのは、たいてい「自分が作ったもの」の置き場所です。受け取ったものの置き場所は、こちらから聞かないと見にいきません。「保存できません」と言われたら、「じゃあ、私が渡したものはどこに行ったの?」と聞いてみてください。うちは、それで1,038枚の在処が分かりました。

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

この記事を書いた人

AIを"部下"のように使って、ブログ運営や副業を効率化している運営者です。
煩雑な作業はAI部下たちに任せて、空いた時間を仕事・趣味・大切な人に。
「楽して稼ぐ」ではなく「時間を取り戻す」をモットーに、AI活用のリアルを正直に発信しています。

目次