chandra 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chandra-0.1.0/LICENSE.md +24 -0
- chandra-0.1.0/PKG-INFO +274 -0
- chandra-0.1.0/README.md +246 -0
- chandra-0.1.0/chandra/__init__.py +30 -0
- chandra-0.1.0/chandra/analyze.py +451 -0
- chandra-0.1.0/chandra/cli.py +68 -0
- chandra-0.1.0/chandra/concordance.py +233 -0
- chandra-0.1.0/chandra/hashing.py +144 -0
- chandra-0.1.0/chandra/inputs.py +51 -0
- chandra-0.1.0/chandra/palimpsest.py +211 -0
- chandra-0.1.0/chandra/pngchunks.py +265 -0
- chandra-0.1.0/chandra/rosetta.py +256 -0
- chandra-0.1.0/chandra/synthesize.py +115 -0
- chandra-0.1.0/chandra/xmp.py +56 -0
- chandra-0.1.0/pyproject.toml +128 -0
- chandra-0.1.0/tests/fixtures/chroma1-hd-img2img.png +0 -0
- chandra-0.1.0/tests/fixtures/chroma1-hd-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/chroma1-hd-txt2img.png +0 -0
- chandra-0.1.0/tests/fixtures/ernie-img2img.png +0 -0
- chandra-0.1.0/tests/fixtures/ernie-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/ernie-txt2img.png +0 -0
- chandra-0.1.0/tests/fixtures/flux2-edit-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/flux2-edit.png +0 -0
- chandra-0.1.0/tests/fixtures/flux2-img2img.png +0 -0
- chandra-0.1.0/tests/fixtures/flux2-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/flux2-txt2img.png +0 -0
- chandra-0.1.0/tests/fixtures/illustrious-img2img.png +0 -0
- chandra-0.1.0/tests/fixtures/illustrious-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/illustrious-txt2img.png +0 -0
- chandra-0.1.0/tests/fixtures/qwen-edit-2511-basic.png +0 -0
- chandra-0.1.0/tests/fixtures/qwen-edit-2511-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/qwen2512-img2img.png +0 -0
- chandra-0.1.0/tests/fixtures/qwen2512-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/qwen2512-txt2img.png +0 -0
- chandra-0.1.0/tests/fixtures/z-image-img2img.png +0 -0
- chandra-0.1.0/tests/fixtures/z-image-inpaint.png +0 -0
- chandra-0.1.0/tests/fixtures/z-image-txt2img.png +0 -0
- chandra-0.1.0/tests/test_analyze.py +251 -0
- chandra-0.1.0/tests/test_cli.py +68 -0
- chandra-0.1.0/tests/test_eject.py +113 -0
- chandra-0.1.0/tests/test_fixtures.py +102 -0
- chandra-0.1.0/tests/test_hashing.py +143 -0
- chandra-0.1.0/tests/test_inputs.py +86 -0
- chandra-0.1.0/tests/test_palimpsest.py +162 -0
- chandra-0.1.0/tests/test_pngchunks.py +233 -0
- chandra-0.1.0/tests/test_search.py +198 -0
- chandra-0.1.0/tests/test_synthesize.py +254 -0
- chandra-0.1.0/tests/test_xmp.py +49 -0
chandra-0.1.0/LICENSE.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Juha Jeronen
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
chandra-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chandra
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tools for working with the metadata that image generators embed in their output.
|
|
5
|
+
Keywords: comfyui,civitai,stable-diffusion,metadata,png,a1111,sd-prompt-reader
|
|
6
|
+
Author-Email: Juha Jeronen <juha.jeronen@jamk.fi>
|
|
7
|
+
License-Expression: BSD-2-Clause
|
|
8
|
+
License-File: LICENSE.md
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Project-URL: Homepage, https://github.com/Technologicat/chandra
|
|
21
|
+
Project-URL: Repository, https://github.com/Technologicat/chandra
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: simpleeval>=0.9.13
|
|
24
|
+
Requires-Dist: argcomplete>=3.0
|
|
25
|
+
Requires-Dist: chardet>=5.0
|
|
26
|
+
Requires-Dist: colorama>=0.4.6
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# chandra
|
|
30
|
+
|
|
31
|
+
Tools for working with the metadata that AI image generators embed in their output.
|
|
32
|
+
|
|
33
|
+
    [](https://codecov.io/gh/Technologicat/chandra)
|
|
34
|
+
  
|
|
35
|
+
  [](http://makeapullrequest.com/)
|
|
36
|
+
|
|
37
|
+
For my stance on AI contributions, see the [collaboration guidelines](https://github.com/Technologicat/substrate-independent/blob/main/collaboration.md).
|
|
38
|
+
|
|
39
|
+
We use [semantic versioning](https://semver.org/).
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
Everything is one command, **`chandra`**, with these subcommands:
|
|
44
|
+
|
|
45
|
+
| Command | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `chandra show <png…>` | Read a ComfyUI image and **print** the [AUTOMATIC1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui)/[SD-Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) metadata that `chandra inject` *would* write. Read-only. |
|
|
48
|
+
| `chandra inject <png…>` | **Write** that metadata into the image(s), in place, so they're recognized by services and apps that don't analyze ComfyUI graphs — notably, [CivitAI](https://civitai.com) on upload, and [SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader) locally. |
|
|
49
|
+
| `chandra eject <png…>` | **Remove** that metadata again — the inverse of `inject`. Strips the `parameters` chunk and XMP description chandra wrote, leaving the original ComfyUI graph byte-intact. |
|
|
50
|
+
| `chandra search <terms…>` | Search the prompts embedded across a directory tree of generated images. |
|
|
51
|
+
| `chandra scrub <png…>` | Strip a ComfyUI image to an anonymized skeleton — graph wiring kept, image/prompts/docs removed — safe to share when reporting a parsing bug. Writes a copy; never modifies the original. |
|
|
52
|
+
|
|
53
|
+
Reading and writing are deliberately separate commands: `show` never modifies anything, and writing
|
|
54
|
+
only happens when you explicitly ask for `inject` (or `eject`, to undo it).
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
chandra show image.png # preview the synthesized metadata
|
|
58
|
+
chandra inject *.png # write metadata into a batch, in place
|
|
59
|
+
chandra inject imgs/ # …or hand it a directory (recursed)
|
|
60
|
+
chandra eject *.png # remove that metadata again (inverse of inject)
|
|
61
|
+
chandra search starfleet captain # find images whose prompt mentions a starfleet captain
|
|
62
|
+
chandra search catgirl -d imgs | chandra search -n blurry # chain searches to refine the result set
|
|
63
|
+
chandra search catgirl -d imgs | chandra inject # inject only the images a search found
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Every command takes the same inputs: files and/or directories (directories are recursed), or a
|
|
67
|
+
list of paths piped in on stdin, one per line — which is what lets a `search` feed `show`, `inject`,
|
|
68
|
+
or `eject`. `search` takes its roots with `-d` (its positional arguments are the search terms); the
|
|
69
|
+
others take them as positional arguments. With nothing to act on, each command prints a short usage
|
|
70
|
+
instead of guessing: bare `chandra search` asks for terms, bare `chandra show` / `inject` / `eject`
|
|
71
|
+
ask for paths. The one convenience is that `search` (once it has terms) defaults its search root to
|
|
72
|
+
the current directory; the writing commands never default to the cwd — so a bare `chandra inject` or
|
|
73
|
+
`chandra eject` can't modify files there by surprise.
|
|
74
|
+
|
|
75
|
+
Why this is useful: many services and apps such as CivitAI and SD Prompt Reader mostly *punt* on
|
|
76
|
+
analyzing ComfyUI workflows — a trivial txt2img graph is sometimes captured, but img2img, inpaint,
|
|
77
|
+
edit-mode, LoRA chains, and non-standard loaders are not. `chandra` walks the embedded ComfyUI graph
|
|
78
|
+
itself, reconstructs the recipe, and re-expresses it in the one format those tools read robustly.
|
|
79
|
+
|
|
80
|
+
## Injecting metadata (`inject`)
|
|
81
|
+
|
|
82
|
+
`inject` writes the recipe straight into the PNG, in place and losslessly — the original ComfyUI
|
|
83
|
+
`prompt`/`workflow` chunks are never touched. It writes two independent layers, both on by default: a
|
|
84
|
+
machine-readable A1111/SD-Forge `parameters` chunk (what CivitAI and SD Prompt Reader read) and an
|
|
85
|
+
XMP `dc:description` (what general image viewers show). The two sections below cover what each layer
|
|
86
|
+
is for — auto-linking your resources on CivitAI, and seeing the recipe in an everyday image viewer.
|
|
87
|
+
|
|
88
|
+
### Auto-linking resources on CivitAI (`--hash`)
|
|
89
|
+
|
|
90
|
+
By default the checkpoint and LoRAs are named as plain text — readable by a human and by SD Prompt
|
|
91
|
+
Reader, but invisible to CivitAI, which keys its resource detection off hashes and surfaces nothing
|
|
92
|
+
without them. Add `--hash` (to `show` or `inject`) and `chandra` computes the AutoV2 hash
|
|
93
|
+
(`sha256[:10]`) of each file and emits `Model hash:` and `Lora hashes:`, which CivitAI matches to the
|
|
94
|
+
corresponding resource pages on upload:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
chandra inject *.png --hash --models-dir ~/ComfyUI/models
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Hashing needs the actual files, so you need to tell `chandra` where they live — either with
|
|
101
|
+
`--models-dir DIR` (repeatable) or via the **`CHANDRA_MODELS_DIR`** environment variable,
|
|
102
|
+
a `PATH`-style list of directories (colon-separated on Linux/macOS, semicolon on Windows):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
export CHANDRA_MODELS_DIR=~/ComfyUI/models:~/extra/loras
|
|
106
|
+
chandra inject *.png --hash # picks up the dirs from the environment
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
On Linux, to set the environment variable persistently, place the `export` command in your `.bashrc`.
|
|
110
|
+
|
|
111
|
+
The directories are indexed once and hashes are cached (keyed by path, size, and mtime), so a
|
|
112
|
+
multi-GB checkpoint shared across a batch is hashed only the first time. Only the checkpoint and
|
|
113
|
+
LoRAs auto-link on CivitAI — its detection covers nothing else.
|
|
114
|
+
|
|
115
|
+
The recipe also records the **VAE** (`VAE:`, plus `VAE hash:` under `--hash`) and any separate
|
|
116
|
+
**text encoders** — common on modern models (Flux, Qwen, …), often an LLM — as SD-Forge `Module N`
|
|
117
|
+
fields. CivitAI ignores both, but they're standard, faithful metadata that SD Prompt Reader, general
|
|
118
|
+
image viewers, and `chandra show --recipe` display; the text encoder in particular materially shapes
|
|
119
|
+
the result, so it's worth recording. Text encoders aren't hashed (no standard infotext hash field).
|
|
120
|
+
|
|
121
|
+
### Seeing the recipe in a general image viewer
|
|
122
|
+
|
|
123
|
+
`inject` also embeds a clean, human-readable rendering of the recipe — the same information as
|
|
124
|
+
`chandra show --recipe` — as an XMP `dc:description`. So a general image viewer that reads standard
|
|
125
|
+
metadata (e.g. [Pix](https://github.com/linuxmint/pix), the Linux Mint viewer) shows the prompt and
|
|
126
|
+
settings in its **Description** caption, no SD software needed — often enough to skip opening a
|
|
127
|
+
dedicated prompt reader just to glance at what made an image. This is on by default; pass `--no-xmp`
|
|
128
|
+
to write only the machine-oriented `parameters` chunk. The two layers are independent and both
|
|
129
|
+
lossless — the original ComfyUI `prompt`/`workflow` chunks are never touched.
|
|
130
|
+
|
|
131
|
+
LoRAs differ between the layers, by design. The machine `parameters` chunk renders them in A1111's
|
|
132
|
+
inline `<lora:name:strength>` notation — that's the format's idiom, and the only standard place a
|
|
133
|
+
LoRA's *strength* is recorded.
|
|
134
|
+
|
|
135
|
+
ComfyUI itself never writes LoRAs into the prompt text, so the human-readable views keep the prose
|
|
136
|
+
clean and list them separately (`LoRA: name (strength X)` in the description and `chandra show --recipe`).
|
|
137
|
+
The inlined-into-prompt form is a data interchange convention.
|
|
138
|
+
|
|
139
|
+
## Undoing an inject (`eject`)
|
|
140
|
+
|
|
141
|
+
Changed your mind? `chandra eject` is the inverse of `inject`: it removes the `parameters` chunk and
|
|
142
|
+
the XMP description, leaving the original ComfyUI `prompt`/`workflow` chunks byte-for-byte intact — an
|
|
143
|
+
`inject` followed by an `eject` restores the file exactly (byte-identical to the original, with the
|
|
144
|
+
same `md5sum`).
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
chandra eject *.png # remove chandra's metadata from a batch, in place
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
By default `eject` removes **only metadata chandra wrote** — both layers carry a `chandra-rosetta`
|
|
151
|
+
stamp (the `Version:` field of the `parameters` chunk and the `x:xmptk` attribute of the XMP packet),
|
|
152
|
+
and anything unstamped is left alone, so it won't clobber a `parameters` block from A1111/Forge or an
|
|
153
|
+
XMP caption some other tool added. Two flags adjust that: `--no-xmp` removes only the `parameters`
|
|
154
|
+
chunk and leaves the XMP description; `--force` removes the `parameters` chunk and XMP regardless of
|
|
155
|
+
who wrote them.
|
|
156
|
+
|
|
157
|
+
## Searching (`search`)
|
|
158
|
+
|
|
159
|
+
`chandra search` builds boolean queries from three primitives — no special syntax or metacharacters:
|
|
160
|
+
|
|
161
|
+
| | flag | example |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| **AND** | *(default)* | `chandra search cat photo` — prompt contains both fragments |
|
|
164
|
+
| **OR** | `--or` (`--any`) | `chandra search --or captain admiral` — either fragment |
|
|
165
|
+
| **NOT** | `--not` (`--invert`, `-v`) | `chandra search --not klingon` — prompt lacks the fragment |
|
|
166
|
+
|
|
167
|
+
Fragments match as **substrings**, order-independent: `cat photo` also matches `photocatalytic`.
|
|
168
|
+
|
|
169
|
+
Fragments are **smart-cased**: an all-lowercase fragment is case-insensitive, a fragment with
|
|
170
|
+
any uppercase letter is case-sensitive. The flag `-i` forces case-insensitive.
|
|
171
|
+
|
|
172
|
+
`chandra search` is a *nix-style filter — matching paths go to stdout, and when input is piped,
|
|
173
|
+
it reads candidate paths from stdin. So **chaining refines**: each stage filters the previous
|
|
174
|
+
stage's results (set intersection), which gives full boolean in conjunctive normal form:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
chandra search starship | chandra search --or captain admiral | chandra search --not klingon
|
|
178
|
+
# → starship AND (captain OR admiral) AND (NOT klingon)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
…and results compose with the rest of the shell:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
chandra search wizard -d imgs | wc -l # count matches
|
|
185
|
+
chandra search cat -d imgs | xargs -d'\n' cp -t picks/ # copy matches elsewhere
|
|
186
|
+
chandra search catgirl -d imgs | fzf # pick one interactively
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
More flags:
|
|
190
|
+
|
|
191
|
+
- `-p` / `-n` search the positive / negative prompt only,
|
|
192
|
+
- `--exact` matches the whole query as one contiguous phrase instead of fragments,
|
|
193
|
+
- `-C` / `--context` prints a highlighted snippet of each match, colorized on a terminal,
|
|
194
|
+
- `--dirs-only` prints matching directories instead of files, and
|
|
195
|
+
- `-d DIR` sets the search roots, repeatable; default is piped stdin, else the current directory.
|
|
196
|
+
|
|
197
|
+
## On the names
|
|
198
|
+
|
|
199
|
+
**`chandra`** is Sanskrit for *the moon* (चन्द्र), the Hindu lunar deity. The metadata this tool
|
|
200
|
+
recovers is an image's nocturnal layer — dimmer than the bright pixels, easy to overlook, but there
|
|
201
|
+
to be read once you look for it. The name rewards a second glance: the astrophysicist *Subrahmanyan
|
|
202
|
+
Chandrasekhar* (of the [Chandrasekhar limit](https://en.wikipedia.org/wiki/Chandrasekhar_limit))
|
|
203
|
+
carries the same root — *Chandra·shekhar*, "moon-crested" — as does NASA's
|
|
204
|
+
[Chandra X-ray Observatory](https://en.wikipedia.org/wiki/Chandra_X-ray_Observatory), named in his
|
|
205
|
+
honour, which exists to image the *invisible* sky. Reading what's present but unseen is the whole job.
|
|
206
|
+
*(This project is not affiliated with or endorsed by NASA.)*
|
|
207
|
+
|
|
208
|
+
The engines under the hood carry their own names:
|
|
209
|
+
|
|
210
|
+
- **`rosetta`** powers `show`, `inject`, and `eject`. Named for the
|
|
211
|
+
[Rosetta Stone](https://en.wikipedia.org/wiki/Rosetta_Stone), which carries one message in several
|
|
212
|
+
scripts so a reader of any one of them can understand it. This engine does the same for a
|
|
213
|
+
generation recipe: it takes what ComfyUI wrote in its own dialect and re-expresses it in the
|
|
214
|
+
dialect CivitAI and SD Prompt Reader read fluently. (No relation to Apple's Rosetta.)
|
|
215
|
+
|
|
216
|
+
- **`concordance`** powers `search`. A
|
|
217
|
+
[concordance](https://en.wikipedia.org/wiki/Concordance_(publishing)) is an alphabetical index of
|
|
218
|
+
the words in a text or corpus together with where each one occurs — biblical and Shakespearean
|
|
219
|
+
concordances are the classic examples. Searching the prompts across a folder of images is the same
|
|
220
|
+
operation over a corpus of pictures. It only reads — its report goes to your terminal, never into
|
|
221
|
+
the files — which is why it isn't called `scribe`.
|
|
222
|
+
|
|
223
|
+
- **`palimpsest`** powers `scrub`. A [palimpsest](https://en.wikipedia.org/wiki/Palimpsest) is a
|
|
224
|
+
manuscript page whose original writing was scraped or washed off so the surface could be reused —
|
|
225
|
+
yet traces of the older text remain, legible to anyone who looks closely. `scrub` does the same to
|
|
226
|
+
an image: the picture and the prompt prose are washed away, but the graph's wiring stays behind —
|
|
227
|
+
enough to reproduce a parsing bug, without carrying anything personal.
|
|
228
|
+
|
|
229
|
+
## Installation
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
pipx install chandra
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
And later, to uninstall:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
pipx uninstall chandra
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Shell completion (optional)
|
|
242
|
+
|
|
243
|
+
`chandra` supports tab-completion via [argcomplete](https://github.com/kislyuk/argcomplete). Enable it
|
|
244
|
+
once by adding this to your `~/.bashrc` (or `~/.zshrc`):
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
eval "$(register-python-argcomplete chandra)"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Open a new shell (or `source` the file) and `chandra <TAB>` will complete subcommands and flags.
|
|
251
|
+
|
|
252
|
+
`register-python-argcomplete` ships with argcomplete. If `chandra` is installed inside a virtualenv, the
|
|
253
|
+
helper lives there too — to have it on `PATH` in every shell, install argcomplete globally with
|
|
254
|
+
`pipx install argcomplete`.
|
|
255
|
+
|
|
256
|
+
The *global* `activate-global-python-argcomplete` hook does **not** pick up `chandra`: the installed
|
|
257
|
+
console-script wrapper doesn't carry argcomplete's `# PYTHON_ARGCOMPLETE_OK` marker, so per-command
|
|
258
|
+
registration as above is the reliable way.
|
|
259
|
+
|
|
260
|
+
**To disable it:** remove the `eval` line from your shell rc — and, to drop it from the current
|
|
261
|
+
shell immediately, run `complete -r chandra`. If you installed argcomplete solely for this,
|
|
262
|
+
`pipx uninstall argcomplete`.
|
|
263
|
+
|
|
264
|
+
## Contributing
|
|
265
|
+
|
|
266
|
+
Found a workflow `chandra` doesn't parse correctly? Bug reports (with an example image) and pull
|
|
267
|
+
requests are welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
268
|
+
|
|
269
|
+
Two things up front: you can run `chandra scrub your.png` to produce an anonymized skeleton
|
|
270
|
+
(no image, no prompt text, just the graph wiring that reproduces the bug) to attach instead
|
|
271
|
+
of the original; and please keep any example images **SFW** (character art is fine), since
|
|
272
|
+
the issue tracker is public.
|
|
273
|
+
|
|
274
|
+
If you are interested in the technical design, architectural briefs live under [`briefs/`](briefs/).
|
chandra-0.1.0/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# chandra
|
|
2
|
+
|
|
3
|
+
Tools for working with the metadata that AI image generators embed in their output.
|
|
4
|
+
|
|
5
|
+
    [](https://codecov.io/gh/Technologicat/chandra)
|
|
6
|
+
  
|
|
7
|
+
  [](http://makeapullrequest.com/)
|
|
8
|
+
|
|
9
|
+
For my stance on AI contributions, see the [collaboration guidelines](https://github.com/Technologicat/substrate-independent/blob/main/collaboration.md).
|
|
10
|
+
|
|
11
|
+
We use [semantic versioning](https://semver.org/).
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Everything is one command, **`chandra`**, with these subcommands:
|
|
16
|
+
|
|
17
|
+
| Command | What it does |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `chandra show <png…>` | Read a ComfyUI image and **print** the [AUTOMATIC1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui)/[SD-Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) metadata that `chandra inject` *would* write. Read-only. |
|
|
20
|
+
| `chandra inject <png…>` | **Write** that metadata into the image(s), in place, so they're recognized by services and apps that don't analyze ComfyUI graphs — notably, [CivitAI](https://civitai.com) on upload, and [SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader) locally. |
|
|
21
|
+
| `chandra eject <png…>` | **Remove** that metadata again — the inverse of `inject`. Strips the `parameters` chunk and XMP description chandra wrote, leaving the original ComfyUI graph byte-intact. |
|
|
22
|
+
| `chandra search <terms…>` | Search the prompts embedded across a directory tree of generated images. |
|
|
23
|
+
| `chandra scrub <png…>` | Strip a ComfyUI image to an anonymized skeleton — graph wiring kept, image/prompts/docs removed — safe to share when reporting a parsing bug. Writes a copy; never modifies the original. |
|
|
24
|
+
|
|
25
|
+
Reading and writing are deliberately separate commands: `show` never modifies anything, and writing
|
|
26
|
+
only happens when you explicitly ask for `inject` (or `eject`, to undo it).
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
chandra show image.png # preview the synthesized metadata
|
|
30
|
+
chandra inject *.png # write metadata into a batch, in place
|
|
31
|
+
chandra inject imgs/ # …or hand it a directory (recursed)
|
|
32
|
+
chandra eject *.png # remove that metadata again (inverse of inject)
|
|
33
|
+
chandra search starfleet captain # find images whose prompt mentions a starfleet captain
|
|
34
|
+
chandra search catgirl -d imgs | chandra search -n blurry # chain searches to refine the result set
|
|
35
|
+
chandra search catgirl -d imgs | chandra inject # inject only the images a search found
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Every command takes the same inputs: files and/or directories (directories are recursed), or a
|
|
39
|
+
list of paths piped in on stdin, one per line — which is what lets a `search` feed `show`, `inject`,
|
|
40
|
+
or `eject`. `search` takes its roots with `-d` (its positional arguments are the search terms); the
|
|
41
|
+
others take them as positional arguments. With nothing to act on, each command prints a short usage
|
|
42
|
+
instead of guessing: bare `chandra search` asks for terms, bare `chandra show` / `inject` / `eject`
|
|
43
|
+
ask for paths. The one convenience is that `search` (once it has terms) defaults its search root to
|
|
44
|
+
the current directory; the writing commands never default to the cwd — so a bare `chandra inject` or
|
|
45
|
+
`chandra eject` can't modify files there by surprise.
|
|
46
|
+
|
|
47
|
+
Why this is useful: many services and apps such as CivitAI and SD Prompt Reader mostly *punt* on
|
|
48
|
+
analyzing ComfyUI workflows — a trivial txt2img graph is sometimes captured, but img2img, inpaint,
|
|
49
|
+
edit-mode, LoRA chains, and non-standard loaders are not. `chandra` walks the embedded ComfyUI graph
|
|
50
|
+
itself, reconstructs the recipe, and re-expresses it in the one format those tools read robustly.
|
|
51
|
+
|
|
52
|
+
## Injecting metadata (`inject`)
|
|
53
|
+
|
|
54
|
+
`inject` writes the recipe straight into the PNG, in place and losslessly — the original ComfyUI
|
|
55
|
+
`prompt`/`workflow` chunks are never touched. It writes two independent layers, both on by default: a
|
|
56
|
+
machine-readable A1111/SD-Forge `parameters` chunk (what CivitAI and SD Prompt Reader read) and an
|
|
57
|
+
XMP `dc:description` (what general image viewers show). The two sections below cover what each layer
|
|
58
|
+
is for — auto-linking your resources on CivitAI, and seeing the recipe in an everyday image viewer.
|
|
59
|
+
|
|
60
|
+
### Auto-linking resources on CivitAI (`--hash`)
|
|
61
|
+
|
|
62
|
+
By default the checkpoint and LoRAs are named as plain text — readable by a human and by SD Prompt
|
|
63
|
+
Reader, but invisible to CivitAI, which keys its resource detection off hashes and surfaces nothing
|
|
64
|
+
without them. Add `--hash` (to `show` or `inject`) and `chandra` computes the AutoV2 hash
|
|
65
|
+
(`sha256[:10]`) of each file and emits `Model hash:` and `Lora hashes:`, which CivitAI matches to the
|
|
66
|
+
corresponding resource pages on upload:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
chandra inject *.png --hash --models-dir ~/ComfyUI/models
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Hashing needs the actual files, so you need to tell `chandra` where they live — either with
|
|
73
|
+
`--models-dir DIR` (repeatable) or via the **`CHANDRA_MODELS_DIR`** environment variable,
|
|
74
|
+
a `PATH`-style list of directories (colon-separated on Linux/macOS, semicolon on Windows):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
export CHANDRA_MODELS_DIR=~/ComfyUI/models:~/extra/loras
|
|
78
|
+
chandra inject *.png --hash # picks up the dirs from the environment
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
On Linux, to set the environment variable persistently, place the `export` command in your `.bashrc`.
|
|
82
|
+
|
|
83
|
+
The directories are indexed once and hashes are cached (keyed by path, size, and mtime), so a
|
|
84
|
+
multi-GB checkpoint shared across a batch is hashed only the first time. Only the checkpoint and
|
|
85
|
+
LoRAs auto-link on CivitAI — its detection covers nothing else.
|
|
86
|
+
|
|
87
|
+
The recipe also records the **VAE** (`VAE:`, plus `VAE hash:` under `--hash`) and any separate
|
|
88
|
+
**text encoders** — common on modern models (Flux, Qwen, …), often an LLM — as SD-Forge `Module N`
|
|
89
|
+
fields. CivitAI ignores both, but they're standard, faithful metadata that SD Prompt Reader, general
|
|
90
|
+
image viewers, and `chandra show --recipe` display; the text encoder in particular materially shapes
|
|
91
|
+
the result, so it's worth recording. Text encoders aren't hashed (no standard infotext hash field).
|
|
92
|
+
|
|
93
|
+
### Seeing the recipe in a general image viewer
|
|
94
|
+
|
|
95
|
+
`inject` also embeds a clean, human-readable rendering of the recipe — the same information as
|
|
96
|
+
`chandra show --recipe` — as an XMP `dc:description`. So a general image viewer that reads standard
|
|
97
|
+
metadata (e.g. [Pix](https://github.com/linuxmint/pix), the Linux Mint viewer) shows the prompt and
|
|
98
|
+
settings in its **Description** caption, no SD software needed — often enough to skip opening a
|
|
99
|
+
dedicated prompt reader just to glance at what made an image. This is on by default; pass `--no-xmp`
|
|
100
|
+
to write only the machine-oriented `parameters` chunk. The two layers are independent and both
|
|
101
|
+
lossless — the original ComfyUI `prompt`/`workflow` chunks are never touched.
|
|
102
|
+
|
|
103
|
+
LoRAs differ between the layers, by design. The machine `parameters` chunk renders them in A1111's
|
|
104
|
+
inline `<lora:name:strength>` notation — that's the format's idiom, and the only standard place a
|
|
105
|
+
LoRA's *strength* is recorded.
|
|
106
|
+
|
|
107
|
+
ComfyUI itself never writes LoRAs into the prompt text, so the human-readable views keep the prose
|
|
108
|
+
clean and list them separately (`LoRA: name (strength X)` in the description and `chandra show --recipe`).
|
|
109
|
+
The inlined-into-prompt form is a data interchange convention.
|
|
110
|
+
|
|
111
|
+
## Undoing an inject (`eject`)
|
|
112
|
+
|
|
113
|
+
Changed your mind? `chandra eject` is the inverse of `inject`: it removes the `parameters` chunk and
|
|
114
|
+
the XMP description, leaving the original ComfyUI `prompt`/`workflow` chunks byte-for-byte intact — an
|
|
115
|
+
`inject` followed by an `eject` restores the file exactly (byte-identical to the original, with the
|
|
116
|
+
same `md5sum`).
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
chandra eject *.png # remove chandra's metadata from a batch, in place
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
By default `eject` removes **only metadata chandra wrote** — both layers carry a `chandra-rosetta`
|
|
123
|
+
stamp (the `Version:` field of the `parameters` chunk and the `x:xmptk` attribute of the XMP packet),
|
|
124
|
+
and anything unstamped is left alone, so it won't clobber a `parameters` block from A1111/Forge or an
|
|
125
|
+
XMP caption some other tool added. Two flags adjust that: `--no-xmp` removes only the `parameters`
|
|
126
|
+
chunk and leaves the XMP description; `--force` removes the `parameters` chunk and XMP regardless of
|
|
127
|
+
who wrote them.
|
|
128
|
+
|
|
129
|
+
## Searching (`search`)
|
|
130
|
+
|
|
131
|
+
`chandra search` builds boolean queries from three primitives — no special syntax or metacharacters:
|
|
132
|
+
|
|
133
|
+
| | flag | example |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| **AND** | *(default)* | `chandra search cat photo` — prompt contains both fragments |
|
|
136
|
+
| **OR** | `--or` (`--any`) | `chandra search --or captain admiral` — either fragment |
|
|
137
|
+
| **NOT** | `--not` (`--invert`, `-v`) | `chandra search --not klingon` — prompt lacks the fragment |
|
|
138
|
+
|
|
139
|
+
Fragments match as **substrings**, order-independent: `cat photo` also matches `photocatalytic`.
|
|
140
|
+
|
|
141
|
+
Fragments are **smart-cased**: an all-lowercase fragment is case-insensitive, a fragment with
|
|
142
|
+
any uppercase letter is case-sensitive. The flag `-i` forces case-insensitive.
|
|
143
|
+
|
|
144
|
+
`chandra search` is a *nix-style filter — matching paths go to stdout, and when input is piped,
|
|
145
|
+
it reads candidate paths from stdin. So **chaining refines**: each stage filters the previous
|
|
146
|
+
stage's results (set intersection), which gives full boolean in conjunctive normal form:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
chandra search starship | chandra search --or captain admiral | chandra search --not klingon
|
|
150
|
+
# → starship AND (captain OR admiral) AND (NOT klingon)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
…and results compose with the rest of the shell:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
chandra search wizard -d imgs | wc -l # count matches
|
|
157
|
+
chandra search cat -d imgs | xargs -d'\n' cp -t picks/ # copy matches elsewhere
|
|
158
|
+
chandra search catgirl -d imgs | fzf # pick one interactively
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
More flags:
|
|
162
|
+
|
|
163
|
+
- `-p` / `-n` search the positive / negative prompt only,
|
|
164
|
+
- `--exact` matches the whole query as one contiguous phrase instead of fragments,
|
|
165
|
+
- `-C` / `--context` prints a highlighted snippet of each match, colorized on a terminal,
|
|
166
|
+
- `--dirs-only` prints matching directories instead of files, and
|
|
167
|
+
- `-d DIR` sets the search roots, repeatable; default is piped stdin, else the current directory.
|
|
168
|
+
|
|
169
|
+
## On the names
|
|
170
|
+
|
|
171
|
+
**`chandra`** is Sanskrit for *the moon* (चन्द्र), the Hindu lunar deity. The metadata this tool
|
|
172
|
+
recovers is an image's nocturnal layer — dimmer than the bright pixels, easy to overlook, but there
|
|
173
|
+
to be read once you look for it. The name rewards a second glance: the astrophysicist *Subrahmanyan
|
|
174
|
+
Chandrasekhar* (of the [Chandrasekhar limit](https://en.wikipedia.org/wiki/Chandrasekhar_limit))
|
|
175
|
+
carries the same root — *Chandra·shekhar*, "moon-crested" — as does NASA's
|
|
176
|
+
[Chandra X-ray Observatory](https://en.wikipedia.org/wiki/Chandra_X-ray_Observatory), named in his
|
|
177
|
+
honour, which exists to image the *invisible* sky. Reading what's present but unseen is the whole job.
|
|
178
|
+
*(This project is not affiliated with or endorsed by NASA.)*
|
|
179
|
+
|
|
180
|
+
The engines under the hood carry their own names:
|
|
181
|
+
|
|
182
|
+
- **`rosetta`** powers `show`, `inject`, and `eject`. Named for the
|
|
183
|
+
[Rosetta Stone](https://en.wikipedia.org/wiki/Rosetta_Stone), which carries one message in several
|
|
184
|
+
scripts so a reader of any one of them can understand it. This engine does the same for a
|
|
185
|
+
generation recipe: it takes what ComfyUI wrote in its own dialect and re-expresses it in the
|
|
186
|
+
dialect CivitAI and SD Prompt Reader read fluently. (No relation to Apple's Rosetta.)
|
|
187
|
+
|
|
188
|
+
- **`concordance`** powers `search`. A
|
|
189
|
+
[concordance](https://en.wikipedia.org/wiki/Concordance_(publishing)) is an alphabetical index of
|
|
190
|
+
the words in a text or corpus together with where each one occurs — biblical and Shakespearean
|
|
191
|
+
concordances are the classic examples. Searching the prompts across a folder of images is the same
|
|
192
|
+
operation over a corpus of pictures. It only reads — its report goes to your terminal, never into
|
|
193
|
+
the files — which is why it isn't called `scribe`.
|
|
194
|
+
|
|
195
|
+
- **`palimpsest`** powers `scrub`. A [palimpsest](https://en.wikipedia.org/wiki/Palimpsest) is a
|
|
196
|
+
manuscript page whose original writing was scraped or washed off so the surface could be reused —
|
|
197
|
+
yet traces of the older text remain, legible to anyone who looks closely. `scrub` does the same to
|
|
198
|
+
an image: the picture and the prompt prose are washed away, but the graph's wiring stays behind —
|
|
199
|
+
enough to reproduce a parsing bug, without carrying anything personal.
|
|
200
|
+
|
|
201
|
+
## Installation
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
pipx install chandra
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
And later, to uninstall:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pipx uninstall chandra
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Shell completion (optional)
|
|
214
|
+
|
|
215
|
+
`chandra` supports tab-completion via [argcomplete](https://github.com/kislyuk/argcomplete). Enable it
|
|
216
|
+
once by adding this to your `~/.bashrc` (or `~/.zshrc`):
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
eval "$(register-python-argcomplete chandra)"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Open a new shell (or `source` the file) and `chandra <TAB>` will complete subcommands and flags.
|
|
223
|
+
|
|
224
|
+
`register-python-argcomplete` ships with argcomplete. If `chandra` is installed inside a virtualenv, the
|
|
225
|
+
helper lives there too — to have it on `PATH` in every shell, install argcomplete globally with
|
|
226
|
+
`pipx install argcomplete`.
|
|
227
|
+
|
|
228
|
+
The *global* `activate-global-python-argcomplete` hook does **not** pick up `chandra`: the installed
|
|
229
|
+
console-script wrapper doesn't carry argcomplete's `# PYTHON_ARGCOMPLETE_OK` marker, so per-command
|
|
230
|
+
registration as above is the reliable way.
|
|
231
|
+
|
|
232
|
+
**To disable it:** remove the `eval` line from your shell rc — and, to drop it from the current
|
|
233
|
+
shell immediately, run `complete -r chandra`. If you installed argcomplete solely for this,
|
|
234
|
+
`pipx uninstall argcomplete`.
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Found a workflow `chandra` doesn't parse correctly? Bug reports (with an example image) and pull
|
|
239
|
+
requests are welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
240
|
+
|
|
241
|
+
Two things up front: you can run `chandra scrub your.png` to produce an anonymized skeleton
|
|
242
|
+
(no image, no prompt text, just the graph wiring that reproduces the bug) to attach instead
|
|
243
|
+
of the original; and please keep any example images **SFW** (character art is fine), since
|
|
244
|
+
the issue tracker is public.
|
|
245
|
+
|
|
246
|
+
If you are interested in the technical design, architectural briefs live under [`briefs/`](briefs/).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""chandra — tools for the metadata image generators embed in their output.
|
|
2
|
+
|
|
3
|
+
Everything is dispatched through a single `chandra` entry point:
|
|
4
|
+
|
|
5
|
+
- ``chandra show`` — print the A1111/CivitAI-compatible metadata derived from an embedded
|
|
6
|
+
ComfyUI workflow (read-only).
|
|
7
|
+
- ``chandra inject`` — write that metadata into the image(s) in place, so they're recognized by
|
|
8
|
+
services that don't analyze ComfyUI graphs themselves.
|
|
9
|
+
- ``chandra eject`` — remove that metadata again (the inverse of inject), restoring the image to
|
|
10
|
+
its pre-inject state.
|
|
11
|
+
- ``chandra search`` — search the prompts embedded across a directory tree of generated images.
|
|
12
|
+
- ``chandra scrub`` — strip a ComfyUI image to a de-branded, shareable skeleton.
|
|
13
|
+
|
|
14
|
+
Three engines do the work: `rosetta` (analyze + synthesize + inject/eject, behind show/inject/eject),
|
|
15
|
+
`concordance` (behind search), and `palimpsest` (behind scrub). See the README for the naming lore.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
__version__ = version("chandra")
|
|
22
|
+
except PackageNotFoundError: # running from a source tree without an installed dist
|
|
23
|
+
__version__ = "0.0.0+unknown"
|
|
24
|
+
|
|
25
|
+
# Signature stamped into everything `inject` writes — the A1111 `Version:` field and the XMP packet's
|
|
26
|
+
# `x:xmptk` (toolkit) attribute — so `eject` can recognize chandra's own output and remove only that,
|
|
27
|
+
# never a third party's metadata. The `rosetta` suffix names the engine that writes it (README lore).
|
|
28
|
+
TOOL_TAG = "chandra-rosetta"
|
|
29
|
+
|
|
30
|
+
__all__ = ["__version__", "TOOL_TAG"]
|