Module: OllamaChat::Utils::PNGMetadataExtractor

Extended by:
UTF8Converter
Includes:
UTF8Converter
Defined in:
lib/ollama_chat/utils/png_metadata_extractor.rb

Overview

Extracts embedded metadata from PNG image files, such as character profiles, ComfyUI workflows, and prompts stored in 'tEXt' chunks.

Class Method Summary collapse

Methods included from UTF8Converter

convert_to_utf8

Class Method Details

.decode_character(text) ⇒ String?

Decodes a Base64 encoded character profile and validates it as JSON.

Parameters:

  • text (String)

    The raw 'chara' metadata value.

Returns:

  • (String, nil)

    The decoded JSON string if valid, otherwise nil.



64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ollama_chat/utils/png_metadata_extractor.rb', line 64

def decode_character(text)
  return nil unless text

  begin
    decoded = Base64.decode64(text)
    decoded = convert_to_utf8(decoded)
    JSON.parse(decoded) # Validation check
    decoded
  rescue JSON::ParserError, ArgumentError
    nil
  end
end

.extract_all(io) ⇒ Hash?

Extracts all 'tEXt' chunk metadata from the provided IO object.

Parameters:

  • io (IO)

    An IO-like object providing access to the PNG binary data.

Returns:

  • (Hash, nil)

    A hash of { keyword => text } if a valid PNG is found, otherwise nil.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/ollama_chat/utils/png_metadata_extractor.rb', line 16

def extract_all(io)
  data = if io.respond_to?(:binmode)
           io.binmode
           io.read
         elsif io.respond_to?(:binread)
           io.binread
         else
           return nil
         end

  # PNG Signature is 8 bytes: \x89PNG\r\n\x1a\n
  pos = 8
   = {}

  while pos < data.length
    # 1. Read Length (4 bytes, Big Endian)
    length = data[pos, 4]&.unpack1('L>')
    break unless length

    pos += 4

    # 2. Read Chunk Type (4 bytes)
    type = data[pos, 4]
    pos += 4

    # 3. Read Chunk Data
    chunk_data = data[pos, length]
    pos += length

    # 4. Skip CRC (4 bytes)
    pos += 4

    if type == 'tEXt'
      # tEXt chunks are formatted as: Keyword + NULL Byte + Text
      keyword, text = chunk_data.split("\x00", 2)
      [keyword] = text if keyword
    end
  end

  .empty? ? nil : 
ensure
  io.ask_and_send(:rewind)
end

.extract_character(io) ⇒ String?

Convenience method to extract a character profile from a PNG. Maintained for backward compatibility with existing call sites.

Parameters:

  • io (IO)

    An IO-like object providing access to the PNG binary data.

Returns:

  • (String, nil)

    The decoded JSON string if found and valid, otherwise nil.



107
108
109
110
# File 'lib/ollama_chat/utils/png_metadata_extractor.rb', line 107

def extract_character(io)
   = extract_all(io) or return
  decode_character(['chara'])
end

.parse_a1111_parameters(text) ⇒ Hash?

Parses Automatic1111 / Stable Diffusion WebUI parameters metadata.

Parameters:

  • text (String)

    The raw 'parameters' metadata value.

Returns:

  • (Hash, nil)

    A hash containing :prompt, :negative_prompt, and :settings.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/ollama_chat/utils/png_metadata_extractor.rb', line 81

def parse_a1111_parameters(text)
  text or return

  lines = text.split("\n")
  result = { prompt: lines[0], negative_prompt: nil, settings: {} }

  lines[1..].each do |line|
    if line.start_with?('Negative prompt: ')
      result[:negative_prompt] = line.sub('Negative prompt: ', '')
    else
      # Parse comma-separated key-value pairs (e.g., "Steps: 20, Sampler: Euler")
      line.split(', ').each do |pair|
        key, value = pair.split(': ', 2)
        result[:settings][key.downcase.to_sym] = value if key && value
      end
    end
  end

  result
end