xmp_toolkit_ruby 0.0.2 → 0.1.0

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.
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XmpToolkitRuby
4
+ require_relative "xmp_file_open_flags"
5
+
6
+ # XmpFile provides a high-level Ruby interface for managing XMP metadata
7
+ # in files such as JPEG, TIFF, PNG, and PDF. It wraps the underlying
8
+ # Adobe XMP SDK calls, offering simplified methods to open, read, update,
9
+ # and write XMP packets, as well as to retrieve file and packet information.
10
+ #
11
+ # == Core Features
12
+ # - Open files for read or update, with optional fallback flags
13
+ # - Read raw and parsed XMP metadata
14
+ # - Update metadata by bulk XML or by individual property/schema
15
+ # - Write changes back to the file
16
+ # - Retrieve file-level info (format, handler flags, open flags)
17
+ # - Retrieve packet-level info (packet size, padding)
18
+ # - Support for localized text properties (alt-text arrays)
19
+ #
20
+ # @example Read and print XMP data:
21
+ # XmpFile.with_xmp_file("image.jpg") do |xmp|
22
+ # p xmp.meta["xmp_data"]
23
+ # end
24
+ #
25
+ # @example Update a custom property:
26
+ # XmpFile.with_xmp_file("doc.pdf", open_flags: XmpFileOpenFlags::OPEN_FOR_UPDATE) do |xmp|
27
+ # new_xml = '<x:xmpmeta xmlns:x="adobe:ns:meta/">...'</x>
28
+ # xmp.update_meta(new_xml)
29
+ # end
30
+ class XmpFile
31
+ # Path to the file on disk containing XMP metadata.
32
+ # @return [String]
33
+ attr_reader :file_path
34
+
35
+ # Flags used for the primary open operation. See XmpFileOpenFlags.
36
+ # @return [Integer]
37
+ attr_reader :open_flags
38
+
39
+ # Optional fallback flags if opening with primary flags fails.
40
+ # @return [Integer, nil]
41
+ attr_reader :fallback_flags
42
+
43
+ class << self
44
+ # Register a custom namespace URI for subsequent property operations.
45
+ #
46
+ # @param namespace [String] Full URI of the namespace, e.g. "http://ns.adobe.com/photoshop/1.0/"
47
+ # @param suggested_prefix [String] Short prefix to use in XML (e.g. "photoshop").
48
+ # @return [String] The actual prefix registered by the SDK.
49
+ # @raise [RuntimeError] if the XMP toolkit has not been initialized.
50
+ def register_namespace(namespace, suggested_prefix)
51
+ warn "XMP Toolkit not initialized; loading default plugins from \#{XmpToolkitRuby::PLUGINS_PATH}" unless XmpToolkitRuby::XmpToolkit.initialized?
52
+ XmpWrapper.register_namespace(namespace, suggested_prefix)
53
+ end
54
+
55
+ # Open a file with XMP support, yielding a managed XmpFile instance.
56
+ # This method ensures the XMP toolkit is initialized and terminated,
57
+ # and that the file is closed and written (if modified).
58
+ #
59
+ # @param file_path [String] Path to the target file.
60
+ # @param open_flags [Integer] Bitmask from XmpFileOpenFlags (default: OPEN_FOR_READ).
61
+ # @param plugin_path [String] Directory of XMP SDK plugins (default: PLUGINS_PATH).
62
+ # @param fallback_flags [Integer, nil] Alternate flags if primary fails.
63
+ # @param auto_terminate_toolkit [Boolean] Shutdown toolkit after block (default: true).
64
+ # @yield [xmp_file] Gives an XmpFile instance for metadata operations.
65
+ # @yieldparam xmp_file [XmpFile]
66
+ # @return [void]
67
+ # @raise [IOError] if file open fails and no fallback succeeds.
68
+ def with_xmp_file(
69
+ file_path,
70
+ open_flags: XmpFileOpenFlags::OPEN_FOR_READ,
71
+ plugin_path: XmpToolkitRuby::PLUGINS_PATH,
72
+ fallback_flags: nil,
73
+ auto_terminate_toolkit: true
74
+ )
75
+ XmpToolkitRuby.check_file!(file_path,
76
+ need_to_read: true,
77
+ need_to_write: XmpFileOpenFlags.contains?(open_flags, :open_for_update))
78
+
79
+ XmpToolkitRuby::XmpToolkit.initialize_xmp(plugin_path) unless XmpToolkitRuby.sdk_initialized?
80
+
81
+ xmp_file = new(file_path,
82
+ open_flags: open_flags,
83
+ fallback_flags: fallback_flags)
84
+ xmp_file.open
85
+ yield xmp_file
86
+ ensure
87
+ xmp_file.write if xmp_file && XmpFileOpenFlags.contains?(xmp_file.open_flags, :open_for_update)
88
+ xmp_file&.close
89
+ XmpToolkitRuby::XmpToolkit.terminate if auto_terminate_toolkit && XmpToolkitRuby.sdk_initialized?
90
+ end
91
+ end
92
+
93
+ # Initialize an XmpFile for a given path.
94
+ #
95
+ # @param file_path [String,Pathname] Local file path to open.
96
+ # @param open_flags [Integer] XmpFileOpenFlags bitmask (default: OPEN_FOR_READ).
97
+ # @param fallback_flags [Integer,nil] Alternate flags on failure.
98
+ # @raise [ArgumentError] if file_path is not readable.
99
+ # @example
100
+ # XmpFile.new("photo.tif", open_flags: XmpFileOpenFlags::OPEN_FOR_UPDATE)
101
+ def initialize(file_path, open_flags: XmpFileOpenFlags::OPEN_FOR_READ, fallback_flags: nil)
102
+ @file_path = file_path.to_s
103
+ raise ArgumentError, "File path '#{@file_path}' must exist and be readable" unless File.readable?(@file_path)
104
+
105
+ @open_flags = open_flags
106
+ @fallback_flags = fallback_flags
107
+ @open = false
108
+ @xmp_wrapper = XmpWrapper.new
109
+ end
110
+
111
+ # Open the file for XMP operations.
112
+ # If initialization flags fail and fallback_flags is provided,
113
+ # attempts a second open with fallback flags.
114
+ #
115
+ # @return [void]
116
+ # @raise [IOError] if both primary and fallback open(...) fail.
117
+ # @note Emits warning if toolkit not initialized.
118
+ def open
119
+ return if open?
120
+
121
+ warn "XMP Toolkit not initialized; using default plugin path" unless XmpToolkitRuby::XmpToolkit.initialized?
122
+
123
+ begin
124
+ @xmp_wrapper.open(file_path, open_flags).tap { @open = true }
125
+ rescue IOError => e
126
+ @xmp_wrapper.close
127
+ @open = false
128
+ raise e unless fallback_flags
129
+
130
+ @xmp_wrapper.open(file_path, fallback_flags).tap { @open = true }
131
+ end
132
+ end
133
+
134
+ # @return [Boolean] Whether the file is currently open for XMP operations.
135
+ def open?
136
+ @open
137
+ end
138
+
139
+ # Retrieve a hash of file-level metadata and flags.
140
+ #
141
+ # @return [Hash{String=>Object}]
142
+ # @example
143
+ # info = xmp.file_info
144
+ # puts "Format: #{info['format']}"
145
+ def file_info
146
+ @file_info ||= begin
147
+ info = @xmp_wrapper.file_info
148
+ {
149
+ "handler_flags" => XmpToolkitRuby::XmpFileHandlerFlags.flags_for(info["handler_flags"]),
150
+ "handler_flags_orig" => info["handler_flags"],
151
+ "format" => XmpToolkitRuby::XmpFileFormat.name_for(info["format"]),
152
+ "format_orig" => info["format"],
153
+ "open_flags" => XmpToolkitRuby::XmpFileOpenFlags.flags_for(info["open_flags"]),
154
+ "open_flags_orig" => info["open_flags"]
155
+ }
156
+ end
157
+ end
158
+
159
+ # Retrieve low-level packet information (size, offset, padding).
160
+ #
161
+ # @return [Hash] Raw packet info as provided by the SDK.
162
+ def packet_info
163
+ @packet_info ||= @xmp_wrapper.packet_info
164
+ end
165
+
166
+ # Get parsed XMP metadata and packet boundaries.
167
+ #
168
+ # @return [Hash]
169
+ # - "begin" [String]: Packet start marker timestamp
170
+ # - "packet_id" [String]: Unique XMP packet ID
171
+ # - "xmp_data" [String]: Inner RDF/XML content
172
+ # - "xmp_data_orig" [String]: Full packet including processing instruction
173
+ # rubocop:disable Metrics/AbcSize
174
+ def meta
175
+ raw = @xmp_wrapper.meta
176
+ return {} if raw.nil? || raw.empty?
177
+
178
+ doc = Nokogiri::XML(raw)
179
+ pis = doc.xpath("//processing-instruction('xpacket')")
180
+ begin_pi = pis.detect { |pi| pi.content.start_with?("begin=") }
181
+ attrs = begin_pi.content.scan(/(\w+)="([^"]*)"/).to_h
182
+ pis.remove
183
+
184
+ {
185
+ "begin" => attrs["begin"],
186
+ "packet_id" => attrs["id"],
187
+ "xmp_data" => doc.root.to_xml,
188
+ "xmp_data_orig" => raw
189
+ }
190
+ end
191
+
192
+ # rubocop:enable Metrics/AbcSize
193
+
194
+ # Persist all pending XMP updates to the file.
195
+ #
196
+ # @raise [RuntimeError] unless file is open.
197
+ # @return [void]
198
+ def write
199
+ raise "File not open; cannot write" unless open?
200
+
201
+ @xmp_wrapper.write
202
+ end
203
+
204
+ # Bulk update XMP metadata using an RDF/XML string.
205
+ #
206
+ # @param xmp_data [String] Full RDF/XML payload or fragment
207
+ # @param mode [Symbol] :upsert (default) or :replace
208
+ # @return [void]
209
+ def update_meta(xmp_data, mode: :upsert)
210
+ open
211
+ @xmp_wrapper.update_meta(xmp_data, mode: mode)
212
+ end
213
+
214
+ # Update a single property in the XMP schema.
215
+ #
216
+ # @param namespace [String] Schema namespace URI
217
+ # @param property [String] Qualified property name (without prefix)
218
+ # @param value [String] New value for the property
219
+ # @return [void]
220
+ def update_property(namespace, property, value)
221
+ open
222
+ @xmp_wrapper.update_property(namespace, property, value)
223
+ end
224
+
225
+ # Retrieve the value of a simple XMP property.
226
+ #
227
+ # This will open the file (if not already open), query the underlying
228
+ # SDK for the given namespace + property, and return whatever value is stored.
229
+ #
230
+ # @param namespace [String] Namespace URI of the schema (e.g. "http://ns.adobe.com/photoshop/1.0/")
231
+ # @param property [String] Property name (without prefix), e.g. "CreatorTool"
232
+ # @return [String, nil] The value of the property, or nil if not set
233
+ # @raise [RuntimeError] if the file cannot be opened
234
+ def property(namespace, property)
235
+ open
236
+ @xmp_wrapper.property(namespace, property)
237
+ end
238
+
239
+ # Retrieve a localized (alt-text) value from an XMP array.
240
+ #
241
+ # Locates the alt-text array identified by
242
+ # `alt_text_name` in the given `schema_ns`, then returns the string
243
+ # matching the requested generic and specific language codes.
244
+ #
245
+ # @param schema_ns [String] Namespace URI of the alt-text schema
246
+ # @param alt_text_name [String] The name of the localized text array
247
+ # @param generic_lang [String] Base language code (e.g. "en")
248
+ # @param specific_lang [String] Locale variant (e.g. "en-US")
249
+ # @return [String, nil] The localized string for that locale, or nil if not found
250
+ # @raise [RuntimeError] if the file cannot be opened
251
+ def localized_property(schema_ns:, alt_text_name:, generic_lang:, specific_lang:)
252
+ open
253
+
254
+ @xmp_wrapper.localized_property(
255
+ schema_ns: schema_ns,
256
+ alt_text_name: alt_text_name,
257
+ generic_lang: generic_lang,
258
+ specific_lang: specific_lang
259
+ )
260
+ end
261
+
262
+ # Update an alternative-text (localized string) property.
263
+ #
264
+ # @param schema_ns [String] Namespace URI of the alt-text schema
265
+ # @param alt_text_name [String] Name of the alt-text array
266
+ # @param generic_lang [String] Base language (e.g. "en")
267
+ # @param specific_lang [String] Specific locale (e.g. "en-US")
268
+ # @param item_value [String] Localized string value
269
+ # @param options [Integer] Bitmask for array operations (see SDK)
270
+ # @return [void]
271
+ def update_localized_property(schema_ns:, alt_text_name:, generic_lang:, specific_lang:, item_value:, options:)
272
+ open
273
+ @xmp_wrapper.update_localized_property(
274
+ schema_ns: schema_ns,
275
+ alt_text_name: alt_text_name,
276
+ generic_lang: generic_lang,
277
+ specific_lang: specific_lang,
278
+ item_value: item_value,
279
+ options: options
280
+ )
281
+ end
282
+
283
+ # Close the file and clear internal state.
284
+ # @return [void]
285
+ def close
286
+ return unless open?
287
+
288
+ @open = false
289
+ @xmp_wrapper.close
290
+ end
291
+
292
+ private
293
+
294
+ # Internal helper to map raw handler flags to named symbols.
295
+ #
296
+ # @param handler_flags [Integer,nil]
297
+ # @return [Hash]
298
+ # @api private
299
+ def map_handler_flags(handler_flags)
300
+ return {} if handler_flags.nil?
301
+
302
+ {
303
+ "handler_flags" => XmpToolkitRuby::XmpFileHandlerFlags.flags_for(handler_flags),
304
+ "handler_flags_orig" => handler_flags
305
+ }
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XmpToolkitRuby
4
+ module XmpFileOpenFlags
5
+ OPEN_FOR_READ = 0x0000_0001 # Open for read-only access
6
+ OPEN_FOR_UPDATE = 0x0000_0002 # Open for reading and writing
7
+ OPEN_ONLY_XMP = 0x0000_0004 # Only the XMP is wanted, allows space/time optimizations
8
+ FORCE_GIVEN_HANDLER = 0x0000_0008 # Force use of the given handler (format), do not verify format
9
+ OPEN_STRICTLY = 0x0000_0010 # Strictly use only designated file handler, no fallback
10
+ OPEN_USE_SMART_HANDLER = 0x0000_0020 # Require the use of a smart handler
11
+ OPEN_USE_PACKET_SCANNING = 0x0000_0040 # Force packet scanning, do not use smart handler
12
+ OPEN_LIMITED_SCANNING = 0x0000_0080 # Only scan files "known" to need scanning
13
+ OPEN_REPAIR_FILE = 0x0000_0100 # Attempt to repair a file opened for update
14
+ OPTIMIZE_FILE_LAYOUT = 0x0000_0200 # Optimize file layout when updating
15
+ PRESERVE_PDF_STATE = 0x0000_0400 # Preserve PDF document state when updating
16
+
17
+ FLAGS = {
18
+ open_for_read: OPEN_FOR_READ,
19
+ open_for_update: OPEN_FOR_UPDATE,
20
+ open_only_xmp: OPEN_ONLY_XMP,
21
+ force_given_handler: FORCE_GIVEN_HANDLER,
22
+ open_strictly: OPEN_STRICTLY,
23
+ open_use_smart_handler: OPEN_USE_SMART_HANDLER,
24
+ open_use_packet_scanning: OPEN_USE_PACKET_SCANNING,
25
+ open_limited_scanning: OPEN_LIMITED_SCANNING,
26
+ open_repair_file: OPEN_REPAIR_FILE,
27
+ optimize_file_layout: OPTIMIZE_FILE_LAYOUT,
28
+ preserve_pdf_state: PRESERVE_PDF_STATE
29
+ }.freeze
30
+
31
+ FLAGS_BY_VALUE = FLAGS.invert.freeze
32
+
33
+ class << self
34
+ def value_for(name)
35
+ key = name.is_a?(String) ? name.to_sym : name
36
+ FLAGS[key]
37
+ end
38
+
39
+ def name_for(hex_value)
40
+ FLAGS_BY_VALUE[hex_value]
41
+ end
42
+
43
+ def flags_for(bitmask)
44
+ FLAGS.select { |_, bit| bitmask.anybits?(bit) }.keys
45
+ end
46
+
47
+ def contains?(bitmask, flag)
48
+ raise ArgumentError, "Invalid flag type: #{flag.class}" unless flag.is_a?(Symbol) || flag.is_a?(String)
49
+
50
+ bitmask & value_for(flag) != 0
51
+ end
52
+
53
+ # Takes multiple flag names (symbols or strings) or constants,
54
+ # returns combined bitmask OR-ing all.
55
+ #
56
+ # Example:
57
+ # bitmask_for(:open_for_read, :force_given_handler)
58
+ # bitmask_for(OPEN_FOR_READ, FORCE_GIVEN_HANDLER)
59
+ def bitmask_for(*args)
60
+ args.reduce(0) do |mask, flag|
61
+ val = case flag
62
+ when Symbol, String then value_for(flag)
63
+ when Integer then flag
64
+ else
65
+ raise ArgumentError, "Invalid flag type: #{flag.class}"
66
+ end
67
+ raise ArgumentError, "Unknown flag: #{flag.inspect}" unless val
68
+
69
+ mask | val
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XmpToolkitRuby
4
+ class XmpValue
5
+ attr_reader :value, :type
6
+
7
+ TYPES = %i[string bool int int64 float date].freeze
8
+
9
+ def initialize(value, type: nil)
10
+ @value = value
11
+ @type = type.to_sym
12
+
13
+ raise ArgumentError, "Invalid type: #{type}" unless TYPES.include?(@type)
14
+ end
15
+ end
16
+ end
@@ -5,6 +5,7 @@ require_relative "xmp_toolkit_ruby/xmp_toolkit_ruby"
5
5
 
6
6
  require "nokogiri"
7
7
  require "rbconfig"
8
+ require "date"
8
9
 
9
10
  # The `XmpToolkitRuby` module serves as a Ruby interface to Adobe's XMP Toolkit,
10
11
  # a native C++ library. This module allows Ruby applications to read and write
@@ -30,10 +31,14 @@ require "rbconfig"
30
31
  # new_xmp_data = "<x:xmpmeta xmlns:x='adobe:ns:meta/'><rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'></rdf:RDF></x:xmpmeta>"
31
32
  # XmpToolkitRuby.xmp_to_file("path/to/image.jpg", new_xmp_data, override: true)
32
33
  #
34
+ # rubocop:disable Metrics/ModuleLength
33
35
  module XmpToolkitRuby
34
36
  require_relative "xmp_toolkit_ruby/xmp_file_format"
35
37
  require_relative "xmp_toolkit_ruby/namespaces"
36
38
  require_relative "xmp_toolkit_ruby/xmp_file_handler_flags"
39
+ require_relative "xmp_toolkit_ruby/xmp_file"
40
+ require_relative "xmp_toolkit_ruby/xmp_value"
41
+ require_relative "xmp_toolkit_ruby/xmp_char_form"
37
42
 
38
43
  # The `PLUGINS_PATH` constant defines the directory where the XMP Toolkit
39
44
  # should look for its plugins, particularly the PDF handler.
@@ -96,9 +101,17 @@ module XmpToolkitRuby
96
101
  check_file! file_path, need_to_read: true, need_to_write: false
97
102
 
98
103
  with_init do
99
- result = XmpToolkitRuby::XmpToolkit.read_xmp(file_path)
100
- result ||= {}
101
- result.merge(cleanup_xmp(result["xmp_data"])).merge(map_handler_flags(result["handler_flags"]))
104
+ XmpToolkitRuby::XmpFile.with_xmp_file(
105
+ file_path,
106
+ open_flags: XmpToolkitRuby::XmpFileOpenFlags.bitmask_for(:open_for_read, :open_use_smart_handler),
107
+ fallback_flags: XmpToolkitRuby::XmpFileOpenFlags.bitmask_for(:open_for_read, :open_use_packet_scanning)
108
+ ) do |xmp_file|
109
+ file_info = xmp_file.file_info
110
+ packet_info = xmp_file.packet_info
111
+ xmp_data = xmp_file.meta
112
+
113
+ file_info.merge(packet_info).merge(xmp_data)
114
+ end
102
115
  end
103
116
  end
104
117
 
@@ -123,24 +136,74 @@ module XmpToolkitRuby
123
136
  def xmp_to_file(file_path, xmp_data, override: false)
124
137
  check_file! file_path, need_to_read: true, need_to_write: true
125
138
 
126
- with_init { XmpToolkitRuby::XmpToolkit.write_xmp(file_path, xmp_data, override ? :override : :upsert) }
139
+ with_init do
140
+ XmpToolkitRuby::XmpFile.with_xmp_file(
141
+ file_path,
142
+ open_flags: XmpToolkitRuby::XmpFileOpenFlags.bitmask_for(:open_for_update, :open_use_smart_handler),
143
+ fallback_flags: XmpToolkitRuby::XmpFileOpenFlags.bitmask_for(:open_for_update, :open_use_packet_scanning)
144
+ ) do |xmp_file|
145
+ xmp_file.update_meta xmp_data, mode: override ? :override : :upsert
146
+
147
+ file_info = xmp_file.file_info
148
+ packet_info = xmp_file.packet_info
149
+ xmp_data = xmp_file.meta
150
+
151
+ file_info.merge(packet_info).merge(xmp_data)
152
+ end
153
+ end
127
154
  end
128
155
 
129
156
  # Ensures the native XMP Toolkit is initialized before executing a block
130
- # of code and terminated afterwards. This is crucial for managing the
157
+ # of code and terminated afterward. This is crucial for managing the
131
158
  # lifecycle of the underlying C++ library resources.
132
159
  #
133
160
  # This method should wrap any calls to the native `XmpToolkitRuby::XmpToolkit` methods.
134
161
  #
162
+ # @param path [String, nil] (nil) Optional path to the XMP Toolkit plugins directory.
163
+ # If `nil` or not provided, it defaults to `PLUGINS_PATH`.
135
164
  # @yield The block of code to execute while the XMP Toolkit is initialized.
136
165
  # @return The result of the yielded block.
137
- def with_init(&block)
138
- XmpToolkitRuby::XmpToolkit.initialize_xmp
166
+ def with_init(path = nil, &block)
167
+ XmpToolkitRuby::XmpToolkit.initialize_xmp(path || PLUGINS_PATH) unless XmpToolkitRuby.sdk_initialized?
168
+
139
169
  block.call
140
170
  ensure
141
171
  XmpToolkitRuby::XmpToolkit.terminate
142
172
  end
143
173
 
174
+ # Checks if the XMP Toolkit SDK has been initialized.
175
+ # This method is useful for ensuring that the SDK is ready for use
176
+ #
177
+ def sdk_initialized?
178
+ XmpToolkitRuby::XmpToolkit.initialized?
179
+ end
180
+
181
+ # Validates file accessibility before performing read or write operations.
182
+ #
183
+ # Checks for:
184
+ # - Nil file path.
185
+ # - File existence.
186
+ # - File readability (if `need_to_read` is true).
187
+ # - File writability (if `need_to_write` is true).
188
+ #
189
+ # @param file_path [String] The path to the file to check.
190
+ # @param need_to_read [Boolean] (true) Whether the file needs to be readable.
191
+ # @param need_to_write [Boolean] (false) Whether the file needs to be writable.
192
+ # @raise [FileNotFoundError] If any of the checks fail.
193
+ # @return [void]
194
+ # @api private
195
+ def check_file!(file_path, need_to_read: true, need_to_write: false)
196
+ if file_path.nil?
197
+ raise FileNotFoundError, "File path cannot be nil"
198
+ elsif !File.exist?(file_path)
199
+ raise FileNotFoundError, "File not found: #{file_path}"
200
+ elsif need_to_read && !File.readable?(file_path)
201
+ raise FileNotFoundError, "File exists but is not readable: #{file_path}"
202
+ elsif need_to_write && !File.writable?(file_path)
203
+ raise FileNotFoundError, "File exists but is not writable: #{file_path}"
204
+ end
205
+ end
206
+
144
207
  private
145
208
 
146
209
  # Parses raw XMP data string to extract `xpacket` processing instruction
@@ -207,31 +270,6 @@ module XmpToolkitRuby
207
270
  end
208
271
 
209
272
  # rubocop: enable Metrics/AbcSize, Metrics/MethodLength
210
-
211
- # Validates file accessibility before performing read or write operations.
212
- #
213
- # Checks for:
214
- # - Nil file path.
215
- # - File existence.
216
- # - File readability (if `need_to_read` is true).
217
- # - File writability (if `need_to_write` is true).
218
- #
219
- # @param file_path [String] The path to the file to check.
220
- # @param need_to_read [Boolean] (true) Whether the file needs to be readable.
221
- # @param need_to_write [Boolean] (false) Whether the file needs to be writable.
222
- # @raise [FileNotFoundError] If any of the checks fail.
223
- # @return [void]
224
- # @api private
225
- def check_file!(file_path, need_to_read: true, need_to_write: false)
226
- if file_path.nil?
227
- raise FileNotFoundError, "File path cannot be nil"
228
- elsif !File.exist?(file_path)
229
- raise FileNotFoundError, "File not found: #{file_path}"
230
- elsif need_to_read && !File.readable?(file_path)
231
- raise FileNotFoundError, "File exists but is not readable: #{file_path}"
232
- elsif need_to_write && !File.writable?(file_path)
233
- raise FileNotFoundError, "File exists but is not writable: #{file_path}"
234
- end
235
- end
236
273
  end
237
274
  end
275
+ # rubocop: enable Metrics/ModuleLength
@@ -0,0 +1,119 @@
1
+ module XmpToolkitRuby
2
+ module Namespaces
3
+ XMP_NS_ADOBE_STOCK_PHOTO: ::String
4
+
5
+ XMP_NS_AESCART: ::String
6
+
7
+ XMP_NS_ASF: ::String
8
+
9
+ XMP_NS_BWF: ::String
10
+
11
+ XMP_NS_CAMERA_RAW: ::String
12
+
13
+ XMP_NS_CREATOR_ATOM: ::String
14
+
15
+ XMP_NS_DC: ::String
16
+
17
+ XMP_NS_DICOM: ::String
18
+
19
+ XMP_NS_DM: ::String
20
+
21
+ XMP_NS_EXIF: ::String
22
+
23
+ XMP_NS_EXIF_AUX: ::String
24
+
25
+ XMP_NS_EXIF_EX: ::String
26
+
27
+ XMP_NS_IPTC_CORE: ::String
28
+
29
+ XMP_NS_IPTC_EXT: ::String
30
+
31
+ XMP_NS_IXML: ::String
32
+
33
+ XMP_NS_JP2K: ::String
34
+
35
+ XMP_NS_JPEG: ::String
36
+
37
+ XMP_NS_PDF: ::String
38
+
39
+ XMP_NS_PDFA_EXTENSION: ::String
40
+
41
+ XMP_NS_PDFA_FIELD: ::String
42
+
43
+ XMP_NS_PDFA_ID: ::String
44
+
45
+ XMP_NS_PDFA_PROPERTY: ::String
46
+
47
+ XMP_NS_PDFA_SCHEMA: ::String
48
+
49
+ XMP_NS_PDFA_TYPE: ::String
50
+
51
+ XMP_NS_PDFUA_ID: ::String
52
+
53
+ XMP_NS_PDFX: ::String
54
+
55
+ XMP_NS_PDFX_ID: ::String
56
+
57
+ XMP_NS_PHOTOSHOP: ::String
58
+
59
+ XMP_NS_PLUS: ::String
60
+
61
+ XMP_NS_PNG: ::String
62
+
63
+ XMP_NS_PSALBUM: ::String
64
+
65
+ XMP_NS_RDF: ::String
66
+
67
+ XMP_NS_RIFFINFO: ::String
68
+
69
+ XMP_NS_SCRIPT: ::String
70
+
71
+ XMP_NS_SWF: ::String
72
+
73
+ XMP_NS_TIFF: ::String
74
+
75
+ XMP_NS_WAV: ::String
76
+
77
+ XMP_NS_XML: ::String
78
+
79
+ XMP_NS_XMP: ::String
80
+
81
+ XMP_NS_XMP_BJ: ::String
82
+
83
+ XMP_NS_XMP_DIMENSIONS: ::String
84
+
85
+ XMP_NS_XMP_FONT: ::String
86
+
87
+ XMP_NS_XMP_GRAPHICS: ::String
88
+
89
+ XMP_NS_XMP_G_IMG: ::String
90
+
91
+ XMP_NS_XMP_IDENTIFIER_QUAL: ::String
92
+
93
+ XMP_NS_XMP_IMAGE: ::String
94
+
95
+ XMP_NS_XMP_MANIFEST_ITEM: ::String
96
+
97
+ XMP_NS_XMP_MM: ::String
98
+
99
+ XMP_NS_XMP_NOTE: ::String
100
+
101
+ XMP_NS_XMP_PAGED_FILE: ::String
102
+
103
+ XMP_NS_XMP_RESOURCE_EVENT: ::String
104
+
105
+ XMP_NS_XMP_RESOURCE_REF: ::String
106
+
107
+ XMP_NS_XMP_RIGHTS: ::String
108
+
109
+ XMP_NS_XMP_ST_JOB: ::String
110
+
111
+ XMP_NS_XMP_ST_VERSION: ::String
112
+
113
+ XMP_NS_XMP_T: ::String
114
+
115
+ XMP_NS_XMP_TEXT: ::String
116
+
117
+ XMP_NS_XMP_T_PG: ::String
118
+ end
119
+ end
@@ -0,0 +1,17 @@
1
+ module XmpToolkitRuby
2
+ module XmpCharForm
3
+ # Returns the flags for a given bitmask.
4
+ def self.flags_for: (Integer bitmask) -> Array[Symbol]
5
+
6
+ # Returns the name for a given value.
7
+ def self.name_for: (Integer value) -> String
8
+
9
+ # Returns the value for a given name.
10
+ def self.value_for: (String name) -> Integer
11
+
12
+ # Mapping from character form names to their values.
13
+ CHAR_FORM: ::Hash[String, Integer]
14
+
15
+ # Mapping from character form values to their names.
16
+ CHAR_FORM_BY_VALUE: ::Hash[Integer, String]
17
+ end