Class: XmpToolkitRuby::XmpFile

Inherits:
Object
  • Object
show all
Defined in:
lib/xmp_toolkit_ruby/xmp_file.rb

Overview

XmpFile provides a high-level Ruby interface for managing XMP metadata in files such as JPEG, TIFF, PNG, and PDF. It wraps the underlying Adobe XMP SDK calls, offering simplified methods to open, read, update, and write XMP packets, as well as to retrieve file and packet information.

Core Features

  • Open files for read or update, with optional fallback flags

  • Read raw and parsed XMP metadata

  • Update metadata by bulk XML or by individual property/schema

  • Write changes back to the file

  • Retrieve file-level info (format, handler flags, open flags)

  • Retrieve packet-level info (packet size, padding)

  • Support for localized text properties (alt-text arrays)

Examples:

Read and print XMP data:

XmpFile.with_xmp_file("image.jpg") do |xmp|
  p xmp.meta["xmp_data"]
end

Update a custom property:

XmpFile.with_xmp_file("doc.pdf", open_flags: XmpFileOpenFlags::OPEN_FOR_UPDATE) do |xmp|
  new_xml = '<x:xmpmeta xmlns:x="adobe:ns:meta/">...'</x>
  xmp.update_meta(new_xml)
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_path, open_flags: XmpFileOpenFlags::OPEN_FOR_READ, fallback_flags: nil) ⇒ XmpFile

Initialize an XmpFile for a given path.

Examples:

XmpFile.new("photo.tif", open_flags: XmpFileOpenFlags::OPEN_FOR_UPDATE)

Parameters:

  • file_path (String, Pathname)

    Local file path to open.

  • open_flags (Integer) (defaults to: XmpFileOpenFlags::OPEN_FOR_READ)

    XmpFileOpenFlags bitmask (default: OPEN_FOR_READ).

  • fallback_flags (Integer, nil) (defaults to: nil)

    Alternate flags on failure.

Raises:

  • (ArgumentError)

    if file_path is not readable.



101
102
103
104
105
106
107
108
109
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 101

def initialize(file_path, open_flags: XmpFileOpenFlags::OPEN_FOR_READ, fallback_flags: nil)
  @file_path = file_path.to_s
  raise ArgumentError, "File path '#{@file_path}' must exist and be readable" unless File.readable?(@file_path)

  @open_flags = open_flags
  @fallback_flags = fallback_flags
  @open = false
  @xmp_wrapper = XmpWrapper.new
end

Instance Attribute Details

#fallback_flagsInteger? (readonly)

Optional fallback flags if opening with primary flags fails.

Returns:

  • (Integer, nil)


41
42
43
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 41

def fallback_flags
  @fallback_flags
end

#file_pathString (readonly)

Path to the file on disk containing XMP metadata.

Returns:

  • (String)


33
34
35
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 33

def file_path
  @file_path
end

#open_flagsInteger (readonly)

Flags used for the primary open operation. See XmpFileOpenFlags.

Returns:

  • (Integer)


37
38
39
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 37

def open_flags
  @open_flags
end

Class Method Details

.register_namespace(namespace, suggested_prefix) ⇒ String

Register a custom namespace URI for subsequent property operations.

Parameters:

  • namespace (String)

    Full URI of the namespace, e.g. “ns.adobe.com/photoshop/1.0/”

  • suggested_prefix (String)

    Short prefix to use in XML (e.g. “photoshop”).

Returns:

  • (String)

    The actual prefix registered by the SDK.

Raises:

  • (RuntimeError)

    if the XMP toolkit has not been initialized.



50
51
52
53
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 50

def register_namespace(namespace, suggested_prefix)
  warn "XMP Toolkit not initialized; loading default plugins from \#{XmpToolkitRuby::PLUGINS_PATH}" unless XmpToolkitRuby::XmpToolkit.initialized?
  XmpWrapper.register_namespace(namespace, suggested_prefix)
end

.with_xmp_file(file_path, open_flags: XmpFileOpenFlags::OPEN_FOR_READ, plugin_path: XmpToolkitRuby::PLUGINS_PATH, fallback_flags: nil, auto_terminate_toolkit: true) {|xmp_file| ... } ⇒ void

This method returns an undefined value.

Open a file with XMP support, yielding a managed XmpFile instance. This method ensures the XMP toolkit is initialized and terminated, and that the file is closed and written (if modified).

Parameters:

  • file_path (String)

    Path to the target file.

  • open_flags (Integer) (defaults to: XmpFileOpenFlags::OPEN_FOR_READ)

    Bitmask from XmpFileOpenFlags (default: OPEN_FOR_READ).

  • plugin_path (String) (defaults to: XmpToolkitRuby::PLUGINS_PATH)

    Directory of XMP SDK plugins (default: PLUGINS_PATH).

  • fallback_flags (Integer, nil) (defaults to: nil)

    Alternate flags if primary fails.

  • auto_terminate_toolkit (Boolean) (defaults to: true)

    Shutdown toolkit after block (default: true).

Yields:

  • (xmp_file)

    Gives an XmpFile instance for metadata operations.

Yield Parameters:

Raises:

  • (IOError)

    if file open fails and no fallback succeeds.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 68

def with_xmp_file(
  file_path,
  open_flags: XmpFileOpenFlags::OPEN_FOR_READ,
  plugin_path: XmpToolkitRuby::PLUGINS_PATH,
  fallback_flags: nil,
  auto_terminate_toolkit: true
)
  XmpToolkitRuby.check_file!(file_path,
                             need_to_read: true,
                             need_to_write: XmpFileOpenFlags.contains?(open_flags, :open_for_update))

  XmpToolkitRuby::XmpToolkit.initialize_xmp(plugin_path) unless XmpToolkitRuby.sdk_initialized?

  xmp_file = new(file_path,
                 open_flags: open_flags,
                 fallback_flags: fallback_flags)
  xmp_file.open
  yield xmp_file
ensure
  xmp_file.write if xmp_file && XmpFileOpenFlags.contains?(xmp_file.open_flags, :open_for_update)
  xmp_file&.close
  XmpToolkitRuby::XmpToolkit.terminate if auto_terminate_toolkit && XmpToolkitRuby.sdk_initialized?
end

Instance Method Details

#closevoid

This method returns an undefined value.

Close the file and clear internal state.



285
286
287
288
289
290
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 285

def close
  return unless open?

  @open = false
  @xmp_wrapper.close
end

#file_infoHash{String=>Object}

Retrieve a hash of file-level metadata and flags.

Examples:

info = xmp.file_info
puts "Format: #{info['format']}"

Returns:

  • (Hash{String=>Object})


145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 145

def file_info
  @file_info ||= begin
                   info = @xmp_wrapper.file_info
                   {
                     "handler_flags" => XmpToolkitRuby::XmpFileHandlerFlags.flags_for(info["handler_flags"]),
                     "handler_flags_orig" => info["handler_flags"],
                     "format" => XmpToolkitRuby::XmpFileFormat.name_for(info["format"]),
                     "format_orig" => info["format"],
                     "open_flags" => XmpToolkitRuby::XmpFileOpenFlags.flags_for(info["open_flags"]),
                     "open_flags_orig" => info["open_flags"]
                   }
                 end
end

#localized_property(schema_ns:, alt_text_name:, generic_lang:, specific_lang:) ⇒ String?

Retrieve a localized (alt-text) value from an XMP array.

Locates the alt-text array identified by ‘alt_text_name` in the given `schema_ns`, then returns the string matching the requested generic and specific language codes.

Parameters:

  • schema_ns (String)

    Namespace URI of the alt-text schema

  • alt_text_name (String)

    The name of the localized text array

  • generic_lang (String)

    Base language code (e.g. “en”)

  • specific_lang (String)

    Locale variant (e.g. “en-US”)

Returns:

  • (String, nil)

    The localized string for that locale, or nil if not found

Raises:

  • (RuntimeError)

    if the file cannot be opened



251
252
253
254
255
256
257
258
259
260
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 251

def localized_property(schema_ns:, alt_text_name:, generic_lang:, specific_lang:)
  open

  @xmp_wrapper.localized_property(
    schema_ns: schema_ns,
    alt_text_name: alt_text_name,
    generic_lang: generic_lang,
    specific_lang: specific_lang
  )
end

#metaHash

Get parsed XMP metadata and packet boundaries.

rubocop:disable Metrics/AbcSize

Returns:

  • (Hash)
    • “begin” [String]: Packet start marker timestamp

    • “packet_id” [String]: Unique XMP packet ID

    • “xmp_data” [String]: Inner RDF/XML content

    • “xmp_data_orig” [String]: Full packet including processing instruction



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 174

def meta
  raw = @xmp_wrapper.meta
  return {} if raw.nil? || raw.empty?

  doc = Nokogiri::XML(raw)
  pis = doc.xpath("//processing-instruction('xpacket')")
  begin_pi = pis.detect { |pi| pi.content.start_with?("begin=") }
  attrs = begin_pi.content.scan(/(\w+)="([^"]*)"/).to_h
  pis.remove

  {
    "begin" => attrs["begin"],
    "packet_id" => attrs["id"],
    "xmp_data" => doc.root.to_xml,
    "xmp_data_orig" => raw
  }
end

#openvoid

Note:

Emits warning if toolkit not initialized.

This method returns an undefined value.

Open the file for XMP operations. If initialization flags fail and fallback_flags is provided, attempts a second open with fallback flags.

Raises:

  • (IOError)

    if both primary and fallback open(…) fail.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 118

def open
  return if open?

  warn "XMP Toolkit not initialized; using default plugin path" unless XmpToolkitRuby::XmpToolkit.initialized?

  begin
    @xmp_wrapper.open(file_path, open_flags).tap { @open = true }
  rescue IOError => e
    @xmp_wrapper.close
    @open = false
    raise e unless fallback_flags

    @xmp_wrapper.open(file_path, fallback_flags).tap { @open = true }
  end
end

#open?Boolean

Returns Whether the file is currently open for XMP operations.

Returns:

  • (Boolean)

    Whether the file is currently open for XMP operations.



135
136
137
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 135

def open?
  @open
end

#packet_infoHash

Retrieve low-level packet information (size, offset, padding).

Returns:

  • (Hash)

    Raw packet info as provided by the SDK.



162
163
164
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 162

def packet_info
  @packet_info ||= @xmp_wrapper.packet_info
end

#property(namespace, property) ⇒ String?

Retrieve the value of a simple XMP property.

This will open the file (if not already open), query the underlying SDK for the given namespace + property, and return whatever value is stored.

Parameters:

  • namespace (String)

    Namespace URI of the schema (e.g. “ns.adobe.com/photoshop/1.0/”)

  • property (String)

    Property name (without prefix), e.g. “CreatorTool”

Returns:

  • (String, nil)

    The value of the property, or nil if not set

Raises:

  • (RuntimeError)

    if the file cannot be opened



234
235
236
237
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 234

def property(namespace, property)
  open
  @xmp_wrapper.property(namespace, property)
end

#update_localized_property(schema_ns:, alt_text_name:, generic_lang:, specific_lang:, item_value:, options:) ⇒ void

This method returns an undefined value.

Update an alternative-text (localized string) property.

Parameters:

  • schema_ns (String)

    Namespace URI of the alt-text schema

  • alt_text_name (String)

    Name of the alt-text array

  • generic_lang (String)

    Base language (e.g. “en”)

  • specific_lang (String)

    Specific locale (e.g. “en-US”)

  • item_value (String)

    Localized string value

  • options (Integer)

    Bitmask for array operations (see SDK)



271
272
273
274
275
276
277
278
279
280
281
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 271

def update_localized_property(schema_ns:, alt_text_name:, generic_lang:, specific_lang:, item_value:, options:)
  open
  @xmp_wrapper.update_localized_property(
    schema_ns: schema_ns,
    alt_text_name: alt_text_name,
    generic_lang: generic_lang,
    specific_lang: specific_lang,
    item_value: item_value,
    options: options
  )
end

#update_meta(xmp_data, mode: :upsert) ⇒ void

This method returns an undefined value.

Bulk update XMP metadata using an RDF/XML string.

Parameters:

  • xmp_data (String)

    Full RDF/XML payload or fragment

  • mode (Symbol) (defaults to: :upsert)

    :upsert (default) or :replace



209
210
211
212
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 209

def update_meta(xmp_data, mode: :upsert)
  open
  @xmp_wrapper.update_meta(xmp_data, mode: mode)
end

#update_property(namespace, property, value) ⇒ void

This method returns an undefined value.

Update a single property in the XMP schema.

Parameters:

  • namespace (String)

    Schema namespace URI

  • property (String)

    Qualified property name (without prefix)

  • value (String)

    New value for the property



220
221
222
223
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 220

def update_property(namespace, property, value)
  open
  @xmp_wrapper.update_property(namespace, property, value)
end

#writevoid

This method returns an undefined value.

Persist all pending XMP updates to the file.

Raises:

  • (RuntimeError)

    unless file is open.



198
199
200
201
202
# File 'lib/xmp_toolkit_ruby/xmp_file.rb', line 198

def write
  raise "File not open; cannot write" unless open?

  @xmp_wrapper.write
end