sharethought は何をしますか?
Sharethoughtファイルは、画像と一緒にテキストセクションを運ぶ有効なPNGです。どこでも通常の画像として開き、レンダリングされます。画像の背後には、レンダリング元のSVGソースとMarkdown文書があります。運ばれるデータの先頭にあるマニフェストがすべてをインデックスし、ファイル自体の中に展開/再パックの指示を保持します。
セクションは2つの運搬手段のいずれかで移動できます。IENDチャンクの後に追加されるトレーラー(簡素、バイト完全転送のみ)またはPNG補助チャンク内(画像パイプラインを生き抜く)です。運搬手段を参照してください。
リファレンス実装を使用する
このリポジトリの sharethought.py は、テスト済みで標準ライブラリのみを使用した実装です。手書きのパーサーよりも優先してください。すべての再実装は新たなバグの表面積です。
python3 sharethought.py pack --png base.png --svg s.svg --doc d.md -o out.png --what "one line"
python3 sharethought.py unpack out.png [--out-dir DIR]
python3 sharethought.py repack --text out.png --set doc=new.md -o out2.png
python3 sharethought.py verify out.png
python3 -m unittest -q test_sharethought # テストスイート
pack は1回のビルドで両方の運搬手段を書き込みます(それらは乖離できません)。unpack はチャンクを優先し、トレーラーにフォールバックし、両方の運搬手段が存在するが一致しない場合はエラーを出力します。packはそのような状態を生成できないため、不一致は外部からの改ざんを意味します。verify はシグネチャ、IEND、両方の運搬手段、セクション名の文法、マニフェストとセクションの一貫性をチェックします。
フォーマット(詳細)
[ PNGバイト ... IENDチャンク ]
SHARETHOUGHT-v1\n
[ JSONマニフェスト — 1つのUTF-8オブジェクト、スキーマ参照 ]\n
<<<SECTION:名前>>>\n
[ セクション内容 — デフォルトは生のUTF-8、"enc":"base64"の場合はbase64 ]\n
<<<END:名前>>>\n
... (さらにセクション、同じフレーミング)
マーカー SHARETHOUGHT-v1 はPNGのIENDチャンクの直後に始まり、改行で終了します。(その前のバイトはIENDチャンクのCRC末尾であり、改行ではないため、バイト単位で改行ではなくで検索してください。)マーカーからEOFまですべてがトレーラーです。標準セクション:svg(画像ソース、image/svg+xml)および doc(Markdown、text/markdown)。任意の追加セクションも許可されます。任意のテキストタイプです。
セクション名は [A-Za-z0-9_]+ に一致する必要があります。 この文法外の名前はパック時に拒否され、アンパック時に名前付きエラーでフラグが立てられます。決して静かに消えることはありません。
運搬手段
同じマニフェストとセクションを2つの方法で運搬できます。転送手段に応じて選択します。
A. トレーラー — IENDチャンクの後に追加(上記のデフォルト)
- 手動で簡単に書き込み可能:
cat trailer.txt >> file.png。CRC、チャンクフレーミング、ライブラリは不要。 - バイト完全転送のみで生き残ります:git、zip、
curl -O、直接ファイルコピー。 - 画像を再シリアライズまたは再エンコードするパイプラインでは生き残りません。PNG→JPEG変換で完全に削除されます。多くのチャットやドライブパイプラインが行うチャンクレベルの再書き込みは、すべてのIDATバイトを保持しますが、IEND以降は破棄します。
B. 補助チャンク — PNGチャンクストリーム内の iTXt
- チャンクリスト内に存在するため、PNGを保持するチャンクレベルの再書き込みや画像パイプラインを生き残ります。
- CRCとチャンクフレーミングのコストがかかり、大きなセクションは圧縮が望ましいです。
- PNG→JPEG変換では生き残りません — ピクセル以外何も生き残りません。
推奨:両方を書き込む(pack が行います)。同じペイロード、1回のビルド、乖離は不可能です。リーダーは最初にチャンクを探し、トレーラーにフォールバックするため、ファイルはチャットパイプラインとバイト完全転送の両方で機能します。コストはおおよそペイロードのコピーが1つ増えることです。
チャンクレイアウト:
[ IHDR ] [ IDAT ... ] [ iTXt sharethought:manifest ] [ iTXt sharethought:<名前> ] ... [ IEND ]
セクションごとに1つの iTXt:マニフェスト用キーワード sharethought:manifest、各セクションボディ用 sharethought:<名前>(生のボディ、区切りなし)。標準の iTXt フレーミング — キーワード \0、圧縮フラグ(1バイト、1 = zlib)、圧縮方式(0)、言語タグ \0、翻訳キーワード \0、そしてテキスト。
マニフェストスキーマ
{
"v": 1,
"what": "このアーティファクトを説明する1行",
"how": {
"unpack": "iTXtチャンク sharethought:* が存在する場合はそれらを読み取ります(マニフェストは sharethought:manifest)。そうでなければ、SHARETHOUGHT-v1 マーカーからEOFまでのバイトを読み取ります。JSONマニフェストを解析し、次に各セクションを <<<SECTION:名前>>> と <<<END:名前>>> の区切り文字の間で解析します。base64セクションをデコードします。",
"repack_text_only": "元のPNGバイトをマーカーまで保持し、更新されたマニフェストとセクションでトレーラーを書き換えます。再レンダリングは不要です。または実行:python3 sharethought.py repack --text FILE --set doc=new.md",
"repack_visual": "画像を変更するには:svgセクションを編集し、PNGにレンダリングし(rsvg-convert -w 1200 in.svg > out.png、または cairosvg、または resvg、または Playwright 経由の Chromium などのヘッドレスブラウザ)、その後 pack を再度実行します。"
},
"sections": [
{ "name": "svg", "type": "image/svg+xml", "enc": "utf8", "summary": "画像の1行説明" },
{ "name": "doc", "type": "text/markdown", "enc": "utf8", "summary": "文書の1行説明" }
]
}
how フィールドは必須であり、動作する指示が含まれている必要があります。ファイルは、どんな冷えた読み手にも開き方を教えます。
展開
最良: python3 sharethought.py unpack file.png。手動、トレーラー運搬手段:
raw = open("file.png", "rb").read()
# PNGチャンクを実際のIENDまでウォークする — raw.index() でマーカーを探してはいけません。
# 圧縮されたIDATバイトが偶然マーカーを含む可能性があります。
off = 8
while True:
import struct
ln, typ = struct.unpack(">I", raw[off:off+4])[0], raw[off+4:off+8]
if typ == b"IEND": break
off += 12 + ln
trailer = raw[off + 12:].decode("utf-8")
import json
manifest, _ = json.JSONDecoder().raw_decode(trailer[trailer.index("\n")+1:])
import re
for m in re.finditer(r"<<<SECTION:(\w+)>>>\n(.*?)\n<<<END:\1>>>", trailer, re.DOTALL):
name, body = m.group(1), m.group(2)
# enc が base64 の場合: import base64; body = base64.b64decode(body)
手動、チャンク運搬手段(優先チェック):
import struct, zlib, json
raw = open("file.png", "rb").read()
off, chunks = 8, {}
while off + 8 <= len(raw):
ln = struct.unpack(">I", raw[off:off+4])[0]
typ = raw[off+4:off+8]
data = raw[off+8:off+8+ln]
if typ == b"IEND": break
if typ == b"iTXt":
key, _, rest = data.partition(b"\x00")
flag = rest[0]
rest = rest[2:]
_, _, rest = rest.partition(b"\x00") # 言語タグ
_, _, rest = rest.partition(b"\x00") # 翻訳キーワード
if flag == 1:
rest = zlib.decompress(rest)
chunks[key.decode()] = rest.decode("utf-8")
off += 12 + ln
manifest = json.loads(chunks["sharethought:manifest"])
sections = {k[len("sharethought:"):]: v
for k, v in chunks.items()
if k.startswith("sharethought:") and k != "sharethought:manifest"}
シェルのみ: tail -c +$(( $(grep -abo 'SHARETHOUGHT-v1' file.png | head -1 | cut -d: -f1) + 1 )) file.png でトレーラーをテキストとして出力します(トレーラーが転送を生き残った場合のみ機能します)。
作成
最良: SVGをレンダリングし、python3 sharethought.py pack --png out.png --svg diagram.svg --doc notes.md -o artifact.png を実行します。手動:
- SVG(人間が見るための画像または図)を書きます。
- PNGにレンダリングします:
rsvg-convert -w 1200 diagram.svg > out.png(またはcairosvg diagram.svg -o out.png、またはresvg diagram.svg out.png、またはヘッドレスブラウザ — Playwright 経由の Chromium — しばしば唯一インストールされているSVGレンダラーです)。 - Markdown文書を書きます。
- トレーラーを追加します(
cat trailer.txt >> file.pngの形式)、さらにiTXtチャンクも書き込むのが望ましいです —packは1回のビルドで両方を行います。
出力は通常のPNGとしてレンダリングされます。確認:任意の画像ビューアで開き(画像が表示される)、strings artifact.png | grep SHARETHOUGHT を実行し(トレーラーが存在することを表示)、または python3 sharethought.py verify artifact.png を実行します(すべてをチェックし、両方の運搬手段が一致するかも含みます)。
再パック
最良: python3 sharethought.py repack --text artifact.png --set doc=new.md -o out.png — 両方の運搬手段を書き換え、レンダラーは不要です。
テキストのみの編集(doc変更、画像変更なし):元のPNGバイト(SHARETHOUGHT-v1 より前のすべて)を再利用し、編集されたセクションと更新されたマニフェストサマリーで新しいトレーラーを書き込みます。ファイルがチャンクも運んでいる場合は、それらも書き換えます — リファレンス実装は1つのソースから両方を再構築し、それらを一致させる唯一の安全な方法です。
ビジュアル編集(svg変更):変更されたSVGを新しいPNGにレンダリングし、新しいsvgセクション + doc + マニフェストで再度パックします。ヘッドレスブラウザを含む任意のSVGレンダラーが機能します。
ラウンドトリップルール:再パックされたファイルはsharethoughtファイルです。同じフォーマット、同じマーカー、更新されたマニフェスト。
区切り文字の安全性
セクション区切り文字は、各行に <<<SECTION:名前>>> および <<<END:名前>>> です。コンテンツがそのような行を含む可能性がある場合は、そのセクションのマニフェストで "enc":"base64" を設定し、ボディをbase64エンコードします — pack は、ボディが自身の終了区切り文字またはマーカー文字列を含む場合に自動的にこれを行います。
実際に問題を引き起こす衝突:異なるセクションの区切り文字を含むボディは無害です。自身の <<<END:名前>>> 行を含むボディは、非貪欲な正規表現によって切り詰められます — その行以降のすべてが孤立します。リファレンス実装はこれを検出し(孤立コンテンツおよびアンバランス区切り文字エラー)、静かに切り詰めることはありません。疑わしい場合はbase64を使用してください。
ペイロードを生かし続ける
ペイロードはPNGのピクセルデータの一部ではないため、画像を再エンコードするツールは静かにペイロードを削除し、深層のない完全に正常に見えるPNGを残す可能性があります。これにはPillowでの Image.open(f).save(g)、画像エディター、CDN最適化ツールが含まれます。レンダリングされたファイルを再保存するのではなく、元のバイトから再パックしてください。
IEND後のトレーラーはバイト完全転送のみで生き残ります。ファイルが画像パイプライン(チャット添付ファイル、サムネイラー、トランスコーダー)を通過する必要がある場合は、チャンク運搬手段を使用し(packが行います)、PNGがJPEGに変換されないようにインライン画像ではなくファイルとしてアップロードしてください。
人間と協力する
人間はPNG — 簡略化された視覚レイヤーを見ます。すべての深層(SVGソース、文書、マニフェスト)はカジュアルな閲覧では見えませんが、あなたにとっては完全に存在しています。人間が画像について尋ねた場合、展開して文書を読み、コンテキストを得てください。人間が画像の変更を希望した場合、svgを編集しビジュアル再パックを行ってください。人間が深層の追加を希望した場合、docを編集しテキストのみの再パックを行ってください。ファイルは交換全体です — 1つのアーティファクト、両方のモダリティ、サイドチャネルなし。