Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pngmetagrep

PNG tEXt metadata extractor — NDJSON output for jq pipelines.

Extracts tEXt chunks from PNG files and outputs one JSON object per line (NDJSON). No image decoding — reads only binary chunk headers for speed.

Use Cases

  • VDSL — search/aggregate vdsl recipe chunks embedded by the VDSL image generation platform
  • ComfyUI — extract prompt / workflow chunks
  • General — any arbitrary tEXt keyword

Install

Pre-built binary (recommended)

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/ynishi/pngmetagrep/releases/latest/download/pngmetagrep-installer.sh | sh

Platforms: x86_64-linux, aarch64-linux, x86_64-macos, aarch64-macos

From source

cargo install pngmetagrep

Usage

# Extract all tEXt chunks from PNGs under a directory
pngmetagrep ./images

# Specify chunk keywords (repeatable)
pngmetagrep ./images --chunk prompt --chunk workflow

# Regex filter on JSON output
pngmetagrep ./images -e '"seed":\s*42'

# Case-insensitive filter
pngmetagrep ./images -e 'landscape' -i

# Print matching file paths only (no JSON)
pngmetagrep ./images -e 'portrait' -l

# Limit parallel threads
pngmetagrep ./images -j 4

# Pipe to jq
pngmetagrep ./images | jq 'select(.seed == 42)'

Options

Flag Description
--chunk <KEY> Text chunk keyword to extract (repeatable, default: all)
-e <REGEX> Regex filter applied to serialized JSON output
-i Case-insensitive matching for -e
-l Print matching file paths only (no JSON)
-j <N> Number of parallel threads (default: CPU count)

Output Format

Single chunk whose value is a JSON object — path is merged flat:

{"path":"images/001.png","_v":1,"seed":42,"model":"sd-xl"}

Multiple chunks or non-object values — nested by keyword:

{"path":"images/002.png","prompt":{...},"workflow":{...}}

tEXt, zTXt and iTXt are all extracted — Pillow and similar writers store large metadata compressed, and those chunks are searchable by -e too.

Damaged Files

A truncated or otherwise malformed PNG does not silently look like a file with no metadata. Whatever chunks were readable are still printed on stdout, and the defect goes to stderr with its byte offset:

pngmetagrep: images/broken.png: truncated PNG: chunk at offset 1181085 declares 12476 bytes, 8179 available

stdout therefore stays clean NDJSON for the pipeline downstream. Chunks sitting past the damage are unreachable — writers differ on where text chunks go, so a damaged file may yield all, some, or none of its metadata.

Crate Structure

Crate Role
pngmeta Chunk-level PNG reader/writer — spans, text chunks, malformed-input guarantees
pngmetagrep-core Text chunk extraction + match strategy selection
pngmetagrep (CLI) Parallel CLI built on clap + rayon + walkdir

Using pngmeta as a library

// Map the file without reading image data: each span carries type,
// offset and length, and the payload is read only when asked for.
let mut reader = pngmeta::ChunkReader::open(std::path::Path::new("a.png"))?;
while let Some(span) = reader.next_span()? {
    println!("{} at {} ({} bytes)", span.kind, span.offset, span.length);
}

// Or hand it bytes you already have, borrowing each payload.
let bytes = std::fs::read("a.png")?;
for item in pngmeta::chunk_spans(&bytes)? {
    let (span, payload) = item?;
    if span.kind.is_text() {
        // ...
    }
}

// Text chunks decoded — tEXt, zTXt and iTXt alike.
for entry in pngmeta::read_text_entries_at(std::path::Path::new("a.png"))? {
    println!("{} = {}", entry.keyword, entry.text);
}

// The same walk as an iterator, when the readable entries of a damaged
// file are worth more than failing on it.
let readable: Vec<_> = pngmeta::text_entries_from_bytes(&bytes)?
    .flatten()
    .collect();

zTXt and compressed iTXt need the inflate feature:

pngmeta = { version = "0.2", features = ["inflate"] }

Malformed input never panics: a truncated file reports Error::Truncated rather than looking like a file with no metadata, an oversized length field is refused before allocating, and non-PNG input is identifiable via Error::is_not_png() without consulting the file extension.

Release

Pre-built binaries are distributed via cargo-dist. Tag push (v*.*.*) triggers GitHub Actions to build and publish to Releases.

cargo release patch --execute

License

MIT

About

PNG tEXt metadata extractor — NDJSON output for jq pipelines

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages