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.
- checksums.yaml +4 -4
- data/.clang-format +4 -0
- data/CHANGELOG.md +37 -2
- data/README.md +131 -0
- data/ext/xmp_toolkit_ruby/xmp_toolkit.cpp +117 -284
- data/ext/xmp_toolkit_ruby/xmp_toolkit.hpp +15 -26
- data/ext/xmp_toolkit_ruby/xmp_toolkit_ruby.cpp +29 -35
- data/ext/xmp_toolkit_ruby/xmp_wrapper.cpp +582 -0
- data/ext/xmp_toolkit_ruby/xmp_wrapper.hpp +35 -0
- data/lib/xmp_toolkit_ruby/namespaces.rb +1 -0
- data/lib/xmp_toolkit_ruby/version.rb +1 -1
- data/lib/xmp_toolkit_ruby/xmp_char_form.rb +45 -0
- data/lib/xmp_toolkit_ruby/xmp_file.rb +308 -0
- data/lib/xmp_toolkit_ruby/xmp_file_open_flags.rb +74 -0
- data/lib/xmp_toolkit_ruby/xmp_value.rb +16 -0
- data/lib/xmp_toolkit_ruby.rb +71 -33
- data/sig/xmp_toolkit_ruby/namespaces.rbs +119 -0
- data/sig/xmp_toolkit_ruby/xmp_char_form.rbs +17 -0
- data/sig/xmp_toolkit_ruby/xmp_file.rbs +45 -0
- data/sig/xmp_toolkit_ruby/xmp_file_format.rbs +11 -0
- data/sig/xmp_toolkit_ruby/xmp_file_handler_flags.rbs +15 -0
- data/sig/xmp_toolkit_ruby/xmp_file_open_flags.rbs +29 -0
- data/sig/xmp_toolkit_ruby/xmp_toolkit.rbs +14 -0
- data/sig/xmp_toolkit_ruby/xmp_value.rbs +19 -0
- data/sig/xmp_toolkit_ruby/xmp_wrapper.rbs +29 -0
- data/sig/xmp_toolkit_ruby.rbs +6 -1
- data/tasks/clang_format.rake +44 -0
- metadata +18 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79c3338c7bfd4d7b6634b4a07791b21f970c2039e06522b3d465fdf83a114f3c
|
4
|
+
data.tar.gz: 96cba607570ff7fb55c712855bfd6b9696e94149bfc8f94b4b425d5cba474eed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 705b287f7a933014e54080e6b2668201b4b2815fa1b8cb3d7dc53d13bf717e909627caa29be9fab739b8449b3f7271e9f5d96536c9193165362f94d9938d7eaf
|
7
|
+
data.tar.gz: 925a360cd3f4c733e632020d53fa8251837a62c60a2e3d560bdee3f698d67fe7e6a0174a5b9649499909d382dc905f5af26d67b3dc561274b3da1475e2d90171
|
data/.clang-format
ADDED
data/CHANGELOG.md
CHANGED
@@ -7,10 +7,42 @@ All notable changes to this project will be documented in this file.
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
9
9
|
|
10
|
-
[unreleased]: https://github.com///compare/v0.0.
|
10
|
+
[unreleased]: https://github.com///compare/v0.0.2..HEAD
|
11
11
|
|
12
12
|
<!-- generated by git-cliff end -->
|
13
13
|
|
14
|
+
## [0.1.0] - 2025-06-11
|
15
|
+
|
16
|
+
### ๐งช Tests
|
17
|
+
|
18
|
+
- Add XMP data extraction tests for various files
|
19
|
+
|
20
|
+
### ๐ Changed
|
21
|
+
|
22
|
+
- Added Multiple new Classes and Methods for XMP Metadata Handling
|
23
|
+
|
24
|
+
### ๐ Added
|
25
|
+
|
26
|
+
- Add rbs signatures
|
27
|
+
- Add localized property retrieval method
|
28
|
+
- Add property retrieval method for XMP metadata
|
29
|
+
- Enhance XMP file handling with fallback options
|
30
|
+
- Enhance XMP file handling and error messaging
|
31
|
+
- Add update_meta method for XMP metadata
|
32
|
+
- Add metadata retrieval method
|
33
|
+
- Add character format mapping and lookup methods
|
34
|
+
- Add file_info and packet_info methods
|
35
|
+
- Add support for multiple XMP value types
|
36
|
+
- Enhance XMP file handling with fine-grained control
|
37
|
+
- Enable single property updates in XMP files
|
38
|
+
- Enhance SDK initialization with optional path
|
39
|
+
- Enhance SDK initialization and termination
|
40
|
+
|
41
|
+
### ๐ Docs
|
42
|
+
|
43
|
+
- Add docs for simple property and localized property getters
|
44
|
+
- Enhance XmpFile class with detailed documentation
|
45
|
+
|
14
46
|
## [0.0.2] - 2025-06-06
|
15
47
|
|
16
48
|
### ๐ Fixed
|
@@ -41,5 +73,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
41
73
|
|
42
74
|
- Initial release
|
43
75
|
|
44
|
-
|
76
|
+
## Changelog
|
77
|
+
|
78
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.0..HEAD)
|
79
|
+
- [0.1.0](https://github.com/dieter-medium/bidi2pdf/compare/v0.0.2..v0.1.0)
|
45
80
|
- [0.0.2](https://github.com/dieter-medium/bidi2pdf/compare/v0.0.1..v0.0.2)
|
data/README.md
CHANGED
@@ -214,6 +214,32 @@ XmpToolkitRuby.xmp_to_file(
|
|
214
214
|
new_xmp,
|
215
215
|
override: true # Set to false to upsert/merge instead of replacing
|
216
216
|
)
|
217
|
+
|
218
|
+
# fine-grained control
|
219
|
+
|
220
|
+
# if you want to take full control over the SDK lifecycle
|
221
|
+
# you can use the XmpToolkitRuby::XmpToolkit module directly:
|
222
|
+
XmpToolkitRuby::XmpToolkit.initialize_xmp(XmpToolkitRuby::PLUGINS_PATH)
|
223
|
+
# in this case you can call here
|
224
|
+
XmpToolkitRuby::XmpFile.register_namespace XmpToolkitRuby::Namespaces::XMP_NS_PDFUA_ID, "pdfua"
|
225
|
+
|
226
|
+
XmpToolkitRuby::XmpFile.with_xmp_file(filename, open_flags: XmpToolkitRuby::XmpFileOpenFlags.bitmask_for(:open_for_update, :open_use_smart_handler, auto_terminate_toolkit: false)) do |xmp_file|
|
227
|
+
# in case the lifecycle is managed by with_xmp_file
|
228
|
+
XmpToolkitRuby::XmpFile.register_namespace XmpToolkitRuby::Namespaces::XMP_NS_PDFUA_ID, "pdfua"
|
229
|
+
|
230
|
+
xmp_file.update_localized_property schema_ns: XmpToolkitRuby::Namespaces::XMP_NS_DC,
|
231
|
+
alt_text_name: "title",
|
232
|
+
generic_lang: "en",
|
233
|
+
specific_lang: "en-US",
|
234
|
+
item_value: "Hello world",
|
235
|
+
options: 0
|
236
|
+
|
237
|
+
xmp_file.update_property XmpToolkitRuby::Namespaces::XMP_NS_PDFUA_ID, "part", "1"
|
238
|
+
end
|
239
|
+
|
240
|
+
# if auto_terminate_toolkit is false, you must call
|
241
|
+
XmpToolkitRuby::XmpToolkit.terminate
|
242
|
+
# to clean up resources
|
217
243
|
```
|
218
244
|
|
219
245
|
If you built the XMP Toolkit yourself or store the plugins elsewhere,
|
@@ -227,6 +253,109 @@ export XMP_TOOLKIT_PLUGINS_PATH=/path/to/XMP-Toolkit-SDK/XMPFilesPlugins/PDF_Han
|
|
227
253
|
Run your Ruby code or CLI commands in the same shell so this variable is
|
228
254
|
visible.
|
229
255
|
|
256
|
+
#### Fine-grained Control with `XmpFile`
|
257
|
+
|
258
|
+
The `XmpToolkitRuby::XmpFile` class exposes low-level access to XMP metadata files, allowing you to:
|
259
|
+
|
260
|
+
- Register new or custom namespaces to extend XMP property support
|
261
|
+
- Open files with precise control over file open modes and handlers
|
262
|
+
- Update localized properties (e.g., multi-language text with alternatives)
|
263
|
+
- Update simple properties in specified namespaces
|
264
|
+
- Use blocks to ensure file handles are properly closed and changes saved
|
265
|
+
|
266
|
+
In order to work properly you need to ensure the XMP Toolkit is initialized:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
XmpToolkitRuby::XmpToolkit.initialize_xmp(XmpToolkitRuby::PLUGINS_PATH)
|
270
|
+
```
|
271
|
+
|
272
|
+
Alternatively, you can use the `with_xmp_file` method which automatically initializes and terminates the toolkit.
|
273
|
+
|
274
|
+
##### Registering a Namespace
|
275
|
+
|
276
|
+
Before working with properties in custom or less-common namespaces, you need to register them with a prefix:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
XmpToolkitRuby::XmpFile.register_namespace XmpToolkitRuby::Namespaces::XMP_NS_PDFUA_ID, "pdfua"
|
280
|
+
```
|
281
|
+
|
282
|
+
This allows the SDK to recognize and correctly handle properties within that namespace.
|
283
|
+
|
284
|
+
##### Opening a File with Custom Flags
|
285
|
+
|
286
|
+
You can open an XMP file with specific flags to control behavior:
|
287
|
+
|
288
|
+
- `open_for_update`: Open the file for modifying metadata
|
289
|
+
- `open_use_smart_handler`: Use smart handlers for better metadata processing
|
290
|
+
|
291
|
+
Flags are combined via the `bitmask_for` helper method:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
flags = XmpToolkitRuby::XmpFileOpenFlags.bitmask_for(:open_for_update, :open_use_smart_handler)
|
295
|
+
```
|
296
|
+
|
297
|
+
Open the file in a block to ensure safe usage:
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
XmpToolkitRuby::XmpFile.with_xmp_file(filename, open_flags: flags) do |xmp_file|
|
301
|
+
# work with xmp_file inside this block
|
302
|
+
end
|
303
|
+
```
|
304
|
+
|
305
|
+
##### Updating a Localized Property
|
306
|
+
|
307
|
+
Localized properties support multiple language alternatives. Use `update_localized_property` with options:
|
308
|
+
|
309
|
+
- `schema_ns`: Namespace URI for the schema (e.g., Dublin Core)
|
310
|
+
- `alt_text_name`: The property name that holds alternative texts
|
311
|
+
- `generic_lang`: Language tag for the generic language (e.g., `"en"`)
|
312
|
+
- `specific_lang`: Specific locale variant (e.g., `"en-US"`)
|
313
|
+
- `item_value`: The actual text value to set
|
314
|
+
- `options`: Optional flags (typically 0)
|
315
|
+
|
316
|
+
Example:
|
317
|
+
|
318
|
+
```ruby
|
319
|
+
xmp_file.update_localized_property(
|
320
|
+
schema_ns: XmpToolkitRuby::Namespaces::XMP_NS_DC,
|
321
|
+
alt_text_name: "title",
|
322
|
+
generic_lang: "en",
|
323
|
+
specific_lang: "en-US",
|
324
|
+
item_value: "Hello world",
|
325
|
+
options: 0
|
326
|
+
)
|
327
|
+
```
|
328
|
+
|
329
|
+
This sets or updates the localized `"title"` property in English (US).
|
330
|
+
|
331
|
+
##### Updating a Simple Property
|
332
|
+
|
333
|
+
For simpler property updates without localization, use `update_property`:
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
xmp_file.update_property(
|
337
|
+
XmpToolkitRuby::Namespaces::XMP_NS_PDFUA_ID,
|
338
|
+
"part",
|
339
|
+
"1"
|
340
|
+
)
|
341
|
+
```
|
342
|
+
|
343
|
+
This updates the `"part"` property within the PDF/UA ID namespace.
|
344
|
+
|
345
|
+
---
|
346
|
+
|
347
|
+
##### Summary
|
348
|
+
|
349
|
+
The fine-grained control API empowers you to work precisely with metadata:
|
350
|
+
|
351
|
+
- Register custom namespaces for extended support
|
352
|
+
- Open files with control over update modes and handlers
|
353
|
+
- Modify localized text properties with language variants
|
354
|
+
- Update simple XMP properties by namespace and name
|
355
|
+
|
356
|
+
Using these methods allows sophisticated metadata management, crucial for professional publishing, digital asset
|
357
|
+
management, or compliance workflows.
|
358
|
+
|
230
359
|
---
|
231
360
|
|
232
361
|
### CLI
|
@@ -285,3 +414,5 @@ Bug reports and pull requests are welcome on [GitHub](https://github.com/DieterS
|
|
285
414
|
## License
|
286
415
|
|
287
416
|
This project is open source, licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
417
|
+
|
418
|
+
---
|
@@ -1,321 +1,154 @@
|
|
1
1
|
#include "xmp_toolkit.hpp"
|
2
2
|
|
3
|
+
#include <mutex>
|
3
4
|
|
5
|
+
static std::mutex sdk_init_mutex;
|
6
|
+
static bool sdk_initialized = false;
|
7
|
+
static bool terminate_registered = false;
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
: (severity == kXMPErrSev_OperationFatal) ? "FATAL OPERATION"
|
13
|
-
: (severity == kXMPErrSev_FileFatal) ? "FATAL FILE"
|
14
|
-
: (severity == kXMPErrSev_ProcessFatal) ? "FATAL PROCESS"
|
15
|
-
: "UNKNOWN";
|
16
|
-
std::cerr << "[TXMPMeta " << sevStr << "] "
|
17
|
-
<< "Code=0x" << std::hex << cause << std::dec
|
18
|
-
<< " Msg=\"" << (message ? message : "(no detail)") << "\"\n";
|
19
|
-
|
20
|
-
// If it's a recoverable error, return true so XMP can try to continue;
|
21
|
-
// otherwise return false to force an exception back to the caller.
|
22
|
-
return (severity == kXMPErrSev_Recoverable);
|
9
|
+
static void terminate_sdk_internal() {
|
10
|
+
std::lock_guard<std::mutex> guard(sdk_init_mutex);
|
11
|
+
if (sdk_initialized) {
|
12
|
+
SXMPFiles::Terminate();
|
13
|
+
SXMPMeta::Terminate();
|
14
|
+
sdk_initialized = false;
|
15
|
+
}
|
23
16
|
}
|
24
17
|
|
25
|
-
|
26
|
-
void
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
XMP_StringPtr message){
|
31
|
-
|
32
|
-
const char * sevStr = (severity == kXMPErrSev_Recoverable) ? "RECOVERABLE"
|
33
|
-
: (severity == kXMPErrSev_OperationFatal) ? "FATAL OPERATION"
|
34
|
-
: (severity == kXMPErrSev_FileFatal) ? "FATAL FILE"
|
35
|
-
: (severity == kXMPErrSev_ProcessFatal) ? "FATAL PROCESS"
|
36
|
-
: "UNKNOWN";
|
18
|
+
// Register termination at Ruby exit
|
19
|
+
static void register_terminate_at_exit() {
|
20
|
+
if (terminate_registered) {
|
21
|
+
return; // Already registered
|
22
|
+
}
|
37
23
|
|
38
|
-
|
39
|
-
<< "file=\"" << (filePath ? filePath : "(null)") << "\" "
|
40
|
-
<< "cause=0x" << std::hex << cause << std::dec << "\n"
|
41
|
-
<< " msg=\"" << (message ? message : "(no detail)") << "\"\n";
|
24
|
+
terminate_registered = true;
|
42
25
|
|
43
|
-
|
44
|
-
return (severity == kXMPErrSev_Recoverable);
|
26
|
+
rb_set_end_proc([](VALUE) { terminate_sdk_internal(); }, Qnil);
|
45
27
|
}
|
46
28
|
|
47
|
-
|
48
|
-
|
49
|
-
XMP_OptionBits templateFlags = 0;
|
29
|
+
static void ensure_sdk_initialized(const char *path) {
|
30
|
+
std::lock_guard<std::mutex> guard(sdk_init_mutex);
|
50
31
|
|
51
|
-
|
32
|
+
if (sdk_initialized) {
|
33
|
+
return;
|
34
|
+
}
|
52
35
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
xmpString = StringValueCStr(rb_xmp_data);
|
36
|
+
try {
|
37
|
+
if (!SXMPMeta::Initialize()) {
|
38
|
+
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Toolkit metadata");
|
39
|
+
return;
|
58
40
|
}
|
59
41
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
} else {
|
66
|
-
// Already a string
|
67
|
-
mode_cstr = StringValueCStr(rb_mode_sym);
|
68
|
-
}
|
69
|
-
bool override;
|
42
|
+
sdk_initialized = true;
|
43
|
+
register_terminate_at_exit();
|
44
|
+
|
45
|
+
XMP_OptionBits options = 0;
|
46
|
+
options |= kXMPFiles_ServerMode;
|
70
47
|
|
71
|
-
if (
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
} else if (strcmp(mode_cstr, "override") == 0) {
|
77
|
-
override = true;
|
48
|
+
if (path) {
|
49
|
+
if (!SXMPFiles::Initialize(options, path)) {
|
50
|
+
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Files with plugin path");
|
51
|
+
return;
|
52
|
+
}
|
78
53
|
} else {
|
79
|
-
|
80
|
-
|
54
|
+
if (!SXMPFiles::Initialize(options)) {
|
55
|
+
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Files without plugin path");
|
56
|
+
return;
|
57
|
+
}
|
81
58
|
}
|
82
59
|
|
60
|
+
return;
|
61
|
+
} catch (const XMP_Error &e) {
|
62
|
+
rb_raise(rb_eRuntimeError, "XMP Error during initialization: %s", e.GetErrMsg());
|
63
|
+
return;
|
64
|
+
} catch (const std::exception &e) {
|
65
|
+
rb_raise(rb_eRuntimeError, "C++ exception during initialization: %s", e.what());
|
66
|
+
return;
|
67
|
+
} catch (...) {
|
68
|
+
rb_raise(rb_eRuntimeError, "Unknown error during XMP initialization");
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
}
|
83
72
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
SXMPMeta currentMeta;
|
90
|
-
XMP_PacketInfo xmpPacket;
|
73
|
+
VALUE
|
74
|
+
is_sdk_initialized(VALUE self) {
|
75
|
+
std::lock_guard<std::mutex> guard(sdk_init_mutex);
|
76
|
+
return sdk_initialized ? Qtrue : Qfalse;
|
77
|
+
}
|
91
78
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
79
|
+
void ensure_sdk_initialized() {
|
80
|
+
// Look up XmpToolkitRuby::PLUGINS_PATH if defined
|
81
|
+
VALUE xmp_module = rb_const_get(rb_cObject, rb_intern("XmpToolkitRuby"));
|
82
|
+
if (rb_const_defined(xmp_module, rb_intern("PLUGINS_PATH"))) {
|
83
|
+
VALUE plugins_path = rb_const_get(xmp_module, rb_intern("PLUGINS_PATH"));
|
97
84
|
|
98
|
-
|
85
|
+
if (TYPE(plugins_path) == T_STRING) {
|
86
|
+
const char *path = StringValueCStr(plugins_path);
|
87
|
+
ensure_sdk_initialized(path);
|
88
|
+
return;
|
99
89
|
}
|
90
|
+
}
|
100
91
|
|
101
|
-
|
102
|
-
XMP_OptionBits opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUseSmartHandler;
|
103
|
-
ok = xmpFile.OpenFile(fileName, kXMP_UnknownFile, opts);
|
104
|
-
if (!ok) {
|
105
|
-
xmpFile.CloseFile();
|
106
|
-
|
107
|
-
opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning;
|
108
|
-
ok = xmpFile.OpenFile( fileName, kXMP_UnknownFile, opts );
|
109
|
-
if ( ! ok ) {
|
110
|
-
// Neither smart handler nor packet scanning worked
|
111
|
-
xmpFile.CloseFile();
|
112
|
-
return Qnil;
|
113
|
-
}
|
114
|
-
}
|
115
|
-
|
116
|
-
fprintf(stderr, "Opened file %s for update\n", fileName);
|
117
|
-
|
118
|
-
ok = xmpFile.GetXMP(¤tMeta, 0, &xmpPacket);
|
119
|
-
if (!ok) {
|
120
|
-
xmpFile.CloseFile();
|
121
|
-
return Qnil;
|
122
|
-
}
|
123
|
-
|
124
|
-
XMP_DateTime dt;
|
125
|
-
SXMPUtils::CurrentDateTime(&dt);
|
126
|
-
std::string nowStr;
|
127
|
-
SXMPUtils::ConvertFromDate(dt, &nowStr);
|
128
|
-
|
129
|
-
if (xmpString != NULL){
|
130
|
-
newMeta.SetProperty(kXMP_NS_XMP, "MetadataDate", nowStr.c_str());
|
131
|
-
}
|
132
|
-
|
133
|
-
std::string newBuffer;
|
134
|
-
if (override) {
|
135
|
-
|
136
|
-
if (xmpFile.CanPutXMP(newMeta) ) {
|
137
|
-
xmpFile.PutXMP(newMeta);
|
138
|
-
} else {
|
139
|
-
xmpFile.CloseFile();
|
140
|
-
|
141
|
-
newMeta.SerializeToBuffer(&newBuffer);
|
142
|
-
|
143
|
-
rb_raise(rb_eArgError, "Can't update XMP new Data: '%s'", newBuffer.c_str());
|
144
|
-
|
145
|
-
return Qnil;
|
146
|
-
}
|
147
|
-
} else {
|
148
|
-
SXMPUtils::ApplyTemplate(
|
149
|
-
¤tMeta,
|
150
|
-
newMeta,
|
151
|
-
templateFlags
|
152
|
-
);
|
153
|
-
|
154
|
-
if (xmpFile.CanPutXMP(currentMeta) ) {
|
155
|
-
xmpFile.PutXMP(currentMeta);
|
156
|
-
} else {
|
157
|
-
xmpFile.CloseFile();
|
158
|
-
|
159
|
-
currentMeta.SerializeToBuffer(&newBuffer);
|
160
|
-
|
161
|
-
rb_raise(rb_eArgError, "Can't update XMP new Data: '%s'", newBuffer.c_str());
|
162
|
-
|
163
|
-
return Qnil;
|
164
|
-
}
|
165
|
-
}
|
166
|
-
|
167
|
-
|
168
|
-
|
92
|
+
ensure_sdk_initialized(nullptr);
|
169
93
|
|
170
|
-
|
171
|
-
}
|
172
|
-
catch (XMP_Error& e) {
|
173
|
-
xmpFile.CloseFile();
|
174
|
-
rb_raise(rb_eRuntimeError, "XMP Error: %s", e.GetErrMsg());
|
175
|
-
return Qnil;
|
176
|
-
}
|
177
|
-
catch (...) {
|
178
|
-
xmpFile.CloseFile();
|
179
|
-
rb_raise(rb_eRuntimeError, "Unknown error processing XMP file");
|
180
|
-
return Qnil;
|
181
|
-
}
|
182
|
-
|
183
|
-
return Qnil;
|
94
|
+
return;
|
184
95
|
}
|
185
96
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
ok = xmpFile.OpenFile(fileName, kXMP_UnknownFile, opts);
|
201
|
-
if (!ok) {
|
202
|
-
xmpFile.CloseFile();
|
203
|
-
|
204
|
-
opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUsePacketScanning;
|
205
|
-
ok = xmpFile.OpenFile( fileName, kXMP_UnknownFile, opts );
|
206
|
-
if ( ! ok ) {
|
207
|
-
// Neither smart handler nor packet scanning worked
|
208
|
-
xmpFile.CloseFile();
|
209
|
-
return Qnil;
|
210
|
-
}
|
211
|
-
}
|
212
|
-
|
213
|
-
ok = xmpFile.GetFileInfo(0, &openFlags, &format, &handlerFlags);
|
214
|
-
if (!ok) {
|
215
|
-
xmpFile.CloseFile();
|
216
|
-
return Qnil;
|
217
|
-
}
|
218
|
-
|
219
|
-
ok = xmpFile.GetXMP(&xmpMeta, 0, &xmpPacket);
|
220
|
-
if (!ok) {
|
221
|
-
xmpFile.CloseFile();
|
222
|
-
return Qnil;
|
223
|
-
}
|
224
|
-
|
225
|
-
VALUE result = rb_hash_new();
|
226
|
-
|
227
|
-
rb_hash_aset(result,
|
228
|
-
rb_str_new_cstr("format"),
|
229
|
-
UINT2NUM(format));
|
230
|
-
rb_hash_aset(result,
|
231
|
-
rb_str_new_cstr("handler_flags"),
|
232
|
-
UINT2NUM(handlerFlags));
|
233
|
-
rb_hash_aset(result,
|
234
|
-
rb_str_new_cstr("offset"),
|
235
|
-
LONG2NUM(xmpPacket.offset));
|
236
|
-
rb_hash_aset(result,
|
237
|
-
rb_str_new_cstr("length"),
|
238
|
-
LONG2NUM(xmpPacket.length));
|
239
|
-
|
240
|
-
std::string xmpString;
|
241
|
-
xmpMeta.SerializeToBuffer(&xmpString);
|
242
|
-
rb_hash_aset(result,
|
243
|
-
rb_str_new_cstr("xmp_data"),
|
244
|
-
rb_str_new_cstr(xmpString.c_str()));
|
97
|
+
bool xmp_meta_error_callback(void *clientContext, XMP_ErrorSeverity severity, XMP_Int32 cause, XMP_StringPtr message) {
|
98
|
+
const char *sevStr = (severity == kXMPErrSev_Recoverable) ? "RECOVERABLE"
|
99
|
+
: (severity == kXMPErrSev_OperationFatal) ? "FATAL OPERATION"
|
100
|
+
: (severity == kXMPErrSev_FileFatal) ? "FATAL FILE"
|
101
|
+
: (severity == kXMPErrSev_ProcessFatal) ? "FATAL PROCESS"
|
102
|
+
: "UNKNOWN";
|
103
|
+
std::cerr << "[TXMPMeta " << sevStr << "] "
|
104
|
+
<< "Code=0x" << std::hex << cause << std::dec << " Msg=\"" << (message ? message : "(no detail)")
|
105
|
+
<< "\"\n";
|
106
|
+
|
107
|
+
// If it's a recoverable error, return true so XMP can try to continue;
|
108
|
+
// otherwise return false to force an exception back to the caller.
|
109
|
+
return (severity == kXMPErrSev_Recoverable);
|
110
|
+
}
|
245
111
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
112
|
+
bool xmp_file_error_callback(void *clientContext, XMP_StringPtr filePath, XMP_ErrorSeverity severity, XMP_Int32 cause,
|
113
|
+
XMP_StringPtr message) {
|
114
|
+
const char *sevStr = (severity == kXMPErrSev_Recoverable) ? "RECOVERABLE"
|
115
|
+
: (severity == kXMPErrSev_OperationFatal) ? "FATAL OPERATION"
|
116
|
+
: (severity == kXMPErrSev_FileFatal) ? "FATAL FILE"
|
117
|
+
: (severity == kXMPErrSev_ProcessFatal) ? "FATAL PROCESS"
|
118
|
+
: "UNKNOWN";
|
119
|
+
|
120
|
+
std::cerr << "[TXMPFiles " << sevStr << "] "
|
121
|
+
<< "file=\"" << (filePath ? filePath : "(null)") << "\" "
|
122
|
+
<< "cause=0x" << std::hex << cause << std::dec << "\n"
|
123
|
+
<< " msg=\"" << (message ? message : "(no detail)") << "\"\n";
|
124
|
+
|
125
|
+
// Only attempt recovery if the error is marked recoverable
|
126
|
+
return (severity == kXMPErrSev_Recoverable);
|
259
127
|
}
|
260
128
|
|
261
|
-
// xmp_initialize(self
|
129
|
+
// xmp_initialize(self)
|
262
130
|
// Initialize the XMP Toolkit and SXMPFiles with an optional PLUGINS_PATH
|
263
|
-
VALUE
|
264
|
-
{
|
265
|
-
|
266
|
-
if (!SXMPMeta::Initialize()) {
|
267
|
-
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Toolkit metadata");
|
268
|
-
return Qnil;
|
269
|
-
}
|
131
|
+
VALUE
|
132
|
+
xmp_initialize(int argc, VALUE *argv, VALUE self) {
|
133
|
+
VALUE rb_path_arg = Qnil;
|
270
134
|
|
271
|
-
|
272
|
-
VALUE xmp_module = rb_const_get(rb_cObject, rb_intern("XmpToolkitRuby"));
|
273
|
-
if (rb_const_defined(xmp_module, rb_intern("PLUGINS_PATH"))) {
|
274
|
-
VALUE plugins_path = rb_const_get(xmp_module, rb_intern("PLUGINS_PATH"));
|
135
|
+
rb_scan_args(argc, argv, "01", &rb_path_arg);
|
275
136
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
options |= kXMPFiles_ServerMode;
|
137
|
+
if (rb_path_arg != Qnil) {
|
138
|
+
Check_Type(rb_path_arg, T_STRING);
|
139
|
+
const char *path = StringValueCStr(rb_path_arg);
|
280
140
|
|
281
|
-
|
282
|
-
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Files with plugin path");
|
283
|
-
return Qnil;
|
284
|
-
}
|
285
|
-
}
|
286
|
-
else {
|
287
|
-
if (!SXMPFiles::Initialize()) {
|
288
|
-
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Files without plugin path");
|
289
|
-
return Qnil;
|
290
|
-
}
|
291
|
-
}
|
292
|
-
}
|
293
|
-
else {
|
294
|
-
if (!SXMPFiles::Initialize()) {
|
295
|
-
rb_raise(rb_eRuntimeError, "Failed to initialize XMP Files (PLUGINS_PATH not defined)");
|
296
|
-
return Qnil;
|
297
|
-
}
|
298
|
-
}
|
141
|
+
ensure_sdk_initialized(path);
|
299
142
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
}
|
306
|
-
catch (const std::exception& e) {
|
307
|
-
rb_raise(rb_eRuntimeError, "C++ exception during initialization: %s", e.what());
|
308
|
-
return Qnil;
|
309
|
-
}
|
310
|
-
catch (...) {
|
311
|
-
rb_raise(rb_eRuntimeError, "Unknown error during XMP initialization");
|
312
|
-
return Qnil;
|
313
|
-
}
|
143
|
+
return Qnil;
|
144
|
+
}
|
145
|
+
|
146
|
+
ensure_sdk_initialized();
|
147
|
+
return Qnil;
|
314
148
|
}
|
315
149
|
|
316
|
-
VALUE
|
317
|
-
{
|
318
|
-
|
319
|
-
|
320
|
-
return Qnil;
|
150
|
+
VALUE
|
151
|
+
xmp_terminate(VALUE self) {
|
152
|
+
terminate_sdk_internal();
|
153
|
+
return Qnil;
|
321
154
|
}
|