mindee 5.2.1 → 5.3.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/CHANGELOG.md +16 -0
- data/SKILL.md +172 -0
- data/bin/mindee.rb +20 -15
- data/bin/v2/parser.rb +27 -8
- data/lib/mindee/image/extracted_image.rb +1 -8
- data/lib/mindee/image/extracted_images.rb +20 -0
- data/lib/mindee/image/image_extractor.rb +26 -8
- data/lib/mindee/image/image_utils.rb +0 -17
- data/lib/mindee/image.rb +1 -0
- data/lib/mindee/input/sources/local_input_source.rb +24 -5
- data/lib/mindee/pdf/extracted_pdf.rb +1 -1
- data/lib/mindee/pdf/extracted_pdfs.rb +20 -0
- data/lib/mindee/pdf/pdf_extractor.rb +10 -10
- data/lib/mindee/pdf.rb +1 -0
- data/lib/mindee/v2/file_operations/crop.rb +7 -6
- data/lib/mindee/v2/file_operations/split.rb +2 -2
- data/lib/mindee/v2/file_operations.rb +0 -2
- data/lib/mindee/v2/parsing/failed_inference_response.rb +34 -0
- data/lib/mindee/v2/parsing/search/search_response.rb +8 -5
- data/lib/mindee/v2/parsing.rb +1 -0
- data/lib/mindee/v2/product/crop/crop_item.rb +1 -1
- data/lib/mindee/v2/product/crop/crop_response.rb +0 -8
- data/lib/mindee/v2/product/crop/crop_result.rb +8 -0
- data/lib/mindee/v2/product/split/split_range.rb +1 -1
- data/lib/mindee/v2/product/split/split_response.rb +0 -8
- data/lib/mindee/v2/product/split/split_result.rb +7 -0
- data/lib/mindee/version.rb +1 -1
- data/sig/mindee/image/extracted_image.rbs +0 -1
- data/sig/mindee/image/extracted_images.rbs +8 -0
- data/sig/mindee/image/image_extractor.rbs +3 -2
- data/sig/mindee/image/image_utils.rbs +0 -1
- data/sig/mindee/input/sources/local_input_source.rbs +6 -0
- data/sig/mindee/pdf/extracted_pdfs.rbs +8 -0
- data/sig/mindee/pdf/pdf_extractor.rbs +2 -2
- data/sig/mindee/v2/file_operation/crop.rbs +1 -1
- data/sig/mindee/v2/file_operation/split.rbs +1 -1
- data/sig/mindee/v2/parsing/failed_inference_response.rbs +15 -0
- data/sig/mindee/v2/parsing/search/search_models.rbs +13 -0
- data/sig/mindee/v2/parsing/search/search_response.rbs +1 -0
- data/sig/mindee/v2/product/crop/crop_item.rbs +1 -1
- data/sig/mindee/v2/product/crop/crop_response.rbs +0 -2
- data/sig/mindee/v2/product/crop/crop_result.rbs +1 -0
- data/sig/mindee/v2/product/split/split_range.rbs +1 -1
- data/sig/mindee/v2/product/split/split_response.rbs +0 -2
- data/sig/mindee/v2/product/split/split_result.rbs +3 -0
- metadata +11 -7
- data/lib/mindee/v2/file_operations/crop_files.rb +0 -25
- data/lib/mindee/v2/file_operations/split_files.rb +0 -25
- data/sig/mindee/v2/file_operation/crop_files.rbs +0 -9
- data/sig/mindee/v2/file_operation/split_files.rbs +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8eb9e96fa7c4b8a41479c87152f858112a72c52f18c23ad247c220615d4df15
|
|
4
|
+
data.tar.gz: 44da90b0f6e814f13cd418502998e3e17610e76857199744c8f2e98b217d72c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 543a63c5085472676f044b20721f03844ad3f425f4fef936c147240b0c286da6f81d8ceffb9ebb90b6c70340426e0529350406b0acfb685176cedb85968ddc06
|
|
7
|
+
data.tar.gz: 591a11727b309a1ac91840ae3dc260f1bcd5186c840b82a71bdc815a8dd3741500191801491ce25edcc44178077d5dd02d437b1c388f9164f1b80c49114a9ea4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Mindee Ruby API Library Changelog
|
|
2
2
|
|
|
3
|
+
## v5.3.0 - 2026-07-03
|
|
4
|
+
### Changes:
|
|
5
|
+
* :memo: add SKILL file
|
|
6
|
+
* :recycle: use generic array types for images, PDFs
|
|
7
|
+
* :sparkles: allow passing webhooks to the CLI
|
|
8
|
+
* :sparkles: add webhook error response
|
|
9
|
+
### Fixes:
|
|
10
|
+
* :bug: fix for extraction method naming
|
|
11
|
+
|
|
12
|
+
## v5.2.2 - 2026-06-18
|
|
13
|
+
### Changes
|
|
14
|
+
* :recycle: refactor CLI to conform with other client libraries
|
|
15
|
+
* :recycle: add secondary accessor for search model pagination to match json return
|
|
16
|
+
* :recycle: ensure HEIF images are detected as such and properly handled
|
|
17
|
+
|
|
18
|
+
|
|
3
19
|
## v5.2.1 - 2026-05-27
|
|
4
20
|
### Changes
|
|
5
21
|
* :recycle: add polling options as a dedicated parameter (#252)
|
data/SKILL.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Mindee Ruby SDK
|
|
2
|
+
|
|
3
|
+
Use this skill for Mindee V2 integrations with the official Ruby SDK.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- Use the official `mindee` Ruby gem.
|
|
8
|
+
- Focus on SDK-based integration patterns only.
|
|
9
|
+
- Do not suggest direct HTTP calls, cURL, or non-SDK integrations.
|
|
10
|
+
- Do not use undocumented SDK internals.
|
|
11
|
+
|
|
12
|
+
## Primary documentation
|
|
13
|
+
|
|
14
|
+
### SDK overview
|
|
15
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk.md
|
|
16
|
+
|
|
17
|
+
### Client setup
|
|
18
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk/configure-the-client.md
|
|
19
|
+
|
|
20
|
+
### Model parameters
|
|
21
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk/basic-model-configuration.md
|
|
22
|
+
|
|
23
|
+
### Load local files
|
|
24
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk/load-and-adjust-a-file.md
|
|
25
|
+
|
|
26
|
+
### Load remote URLs
|
|
27
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk/load-an-url.md
|
|
28
|
+
|
|
29
|
+
### Send files and URLs
|
|
30
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk/send-a-file-or-url.md
|
|
31
|
+
|
|
32
|
+
### Process responses
|
|
33
|
+
- https://docs.mindee.com/integrations/client-libraries-sdk/process-the-response.md
|
|
34
|
+
|
|
35
|
+
### Handle errors
|
|
36
|
+
- https://docs.mindee.com/integrations/problem-database.md
|
|
37
|
+
|
|
38
|
+
## Handling responses by model type
|
|
39
|
+
|
|
40
|
+
### Extraction
|
|
41
|
+
- Use: https://docs.mindee.com/extraction-models/sdk-integration/extraction-result.md
|
|
42
|
+
- Use this page for accessing dynamic fields from `response.inference.result.fields`.
|
|
43
|
+
- Use this page for examples of `SimpleField`, `ObjectField`, `ListField`, confidence, and locations.
|
|
44
|
+
|
|
45
|
+
### Split
|
|
46
|
+
- Use: https://docs.mindee.com/split-models/sdk-integration/split-result.md
|
|
47
|
+
- Use this page for iterating over `response.inference.result.splits`.
|
|
48
|
+
- Use this page for `document_type`, `page_range`, and optional chained extraction results.
|
|
49
|
+
|
|
50
|
+
### Crop
|
|
51
|
+
- Use: https://docs.mindee.com/crop-models/sdk-integration/crop-result.md
|
|
52
|
+
- Use this page for iterating over `response.inference.result.crops`.
|
|
53
|
+
- Use this page for `object_type`, crop location, polygon data, and optional chained extraction results.
|
|
54
|
+
|
|
55
|
+
### Classification
|
|
56
|
+
- Use: https://docs.mindee.com/classification-models/sdk-integration/classification-result.md
|
|
57
|
+
- Use this page for accessing `response.inference.result.classification`.
|
|
58
|
+
- Use this page for `document_type` and optional chained extraction results.
|
|
59
|
+
|
|
60
|
+
### OCR
|
|
61
|
+
- Use: https://docs.mindee.com/raw-text-ocr-models/sdk-integration/ocr-result.md
|
|
62
|
+
- Use this page for iterating over `response.inference.result.pages`.
|
|
63
|
+
- Use this page for page text (`content`), words, and word polygon data.
|
|
64
|
+
|
|
65
|
+
## Default workflow
|
|
66
|
+
|
|
67
|
+
When answering questions, follow this order:
|
|
68
|
+
|
|
69
|
+
1. Initialize the SDK client.
|
|
70
|
+
2. Configure `model_id` and other inference parameters.
|
|
71
|
+
3. Load the input source.
|
|
72
|
+
4. Optionally adjust the file before upload.
|
|
73
|
+
5. Send with polling or webhooks.
|
|
74
|
+
6. Process the response.
|
|
75
|
+
7. Handle errors and retries.
|
|
76
|
+
|
|
77
|
+
## Answering rules
|
|
78
|
+
|
|
79
|
+
- Base answers on the documentation above.
|
|
80
|
+
- Prefer documented SDK methods and patterns.
|
|
81
|
+
- Use environment variables for API keys in production.
|
|
82
|
+
- Reuse a client instance when possible.
|
|
83
|
+
- Prefer polling for simple examples.
|
|
84
|
+
- Prefer webhooks for production or high-volume workflows.
|
|
85
|
+
- If a feature is not documented, say it is not officially supported.
|
|
86
|
+
- If a user asks for code, keep examples minimal and working.
|
|
87
|
+
|
|
88
|
+
## Code sample rules
|
|
89
|
+
|
|
90
|
+
- Use Ruby examples only.
|
|
91
|
+
- Use the official `mindee` gem.
|
|
92
|
+
- Show requires explicitly.
|
|
93
|
+
- Include the exact documented class and method names.
|
|
94
|
+
- Use placeholders like `MY_API_KEY`, `MY_MODEL_ID`, and `/path/to/file.pdf`.
|
|
95
|
+
- Keep samples focused on one task.
|
|
96
|
+
|
|
97
|
+
## Preferred example topics
|
|
98
|
+
|
|
99
|
+
### Client initialization
|
|
100
|
+
Use:
|
|
101
|
+
- `Mindee::V2::Client.new(api_key: 'MY_API_KEY')`
|
|
102
|
+
- `Mindee::V2::Client.new` with `MINDEE_V2_API_KEY` env var
|
|
103
|
+
|
|
104
|
+
### Input loading
|
|
105
|
+
Use:
|
|
106
|
+
- `Mindee::Input::Source::PathInputSource`
|
|
107
|
+
- `Mindee::Input::Source::BytesInputSource`
|
|
108
|
+
- `Mindee::Input::Source::Base64InputSource`
|
|
109
|
+
- `Mindee::Input::Source::FileInputSource`
|
|
110
|
+
- `Mindee::Input::Source::URLInputSource`
|
|
111
|
+
|
|
112
|
+
### Sending documents
|
|
113
|
+
Use:
|
|
114
|
+
- `client.enqueue_and_get_result(...)` for polling
|
|
115
|
+
- `client.enqueue(...)` for webhooks
|
|
116
|
+
|
|
117
|
+
### Response handling
|
|
118
|
+
Use:
|
|
119
|
+
- `response.inference`
|
|
120
|
+
- `response.raw_http`
|
|
121
|
+
- `Mindee::Input::LocalResponse` for webhook payloads
|
|
122
|
+
- HMAC signature validation via `local_response.valid_hmac_signature?(secret_key, signature)` when relevant
|
|
123
|
+
|
|
124
|
+
### File preparation
|
|
125
|
+
Use:
|
|
126
|
+
- `input_source.page_count` — get number of pages
|
|
127
|
+
- `input_source.compress!(...)` — compress before upload
|
|
128
|
+
- `input_source.apply_page_options(options)` — trim or remove pages
|
|
129
|
+
|
|
130
|
+
## Avoid
|
|
131
|
+
|
|
132
|
+
- Direct REST examples
|
|
133
|
+
- cURL examples
|
|
134
|
+
- Manual authentication header construction
|
|
135
|
+
- Bearer token examples for API keys
|
|
136
|
+
- Non-Ruby examples
|
|
137
|
+
- V1 examples unless the user explicitly asks for V1
|
|
138
|
+
|
|
139
|
+
## If the user is unclear
|
|
140
|
+
|
|
141
|
+
Ask for only what is needed:
|
|
142
|
+
|
|
143
|
+
- input type: local file or URL
|
|
144
|
+
- delivery pattern: polling or webhook
|
|
145
|
+
- model ID
|
|
146
|
+
- runtime context: Rails, Sinatra, background job, or script
|
|
147
|
+
|
|
148
|
+
## Output style
|
|
149
|
+
|
|
150
|
+
- Be concise.
|
|
151
|
+
- Answer with runnable examples when code is requested.
|
|
152
|
+
- Link to the most relevant doc section.
|
|
153
|
+
- Do not overwhelm the user with every option.
|
|
154
|
+
- Start with the documented default path.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
# Agent Instructions: Querying The Documentation
|
|
159
|
+
|
|
160
|
+
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
|
|
161
|
+
|
|
162
|
+
Perform an HTTP GET request on the documentation URL with the `ask` query parameter.
|
|
163
|
+
Include `ruby+sdk+-+` at the beginning of the question to get answers specific to this library:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
GET https://docs.mindee.com/integrations.md?ask=ruby+sdk+-+<question>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The question should be specific, self-contained, and written in natural language.
|
|
170
|
+
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
|
|
171
|
+
|
|
172
|
+
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
|
data/bin/mindee.rb
CHANGED
|
@@ -6,24 +6,29 @@ require 'optparse'
|
|
|
6
6
|
require_relative 'v1/parser'
|
|
7
7
|
require_relative 'v2/parser'
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
opts.separator " v2 Use Version 2 of the Mindee API"
|
|
9
|
+
def root_help
|
|
10
|
+
help = "Usage: mindee command [options]\n\nAvailable commands:\n"
|
|
11
|
+
help += " #{'v1'.ljust(50)}Use Version 1 of the Mindee API\n"
|
|
12
|
+
help += " #{'search-models'.ljust(50)}Search for available models for this API key\n"
|
|
13
|
+
|
|
14
|
+
V2_PRODUCTS.each do |product_key, product_values|
|
|
15
|
+
help += " #{product_key.ljust(50)}#{product_values[:description]}\n"
|
|
17
16
|
end
|
|
18
|
-
main_command = ARGV.shift
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
help
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def setup_main_parser
|
|
22
|
+
main_command = ARGV.first
|
|
23
|
+
|
|
24
|
+
if main_command == 'v1'
|
|
25
|
+
ARGV.shift
|
|
26
|
+
MindeeCLI::V1Parser.new(ARGV).execute
|
|
27
|
+
elsif main_command.nil? || %w[help -h --help].include?(main_command)
|
|
28
|
+
abort(root_help)
|
|
25
29
|
else
|
|
26
|
-
|
|
30
|
+
ARGV.shift if main_command == 'v2'
|
|
31
|
+
MindeeCLI::V2Parser.new(ARGV, command_prefix: 'mindee').execute
|
|
27
32
|
end
|
|
28
33
|
end
|
|
29
34
|
|
data/bin/v2/parser.rb
CHANGED
|
@@ -19,10 +19,11 @@ module MindeeCLI
|
|
|
19
19
|
# @return [Parser]
|
|
20
20
|
attr_reader :search_parser
|
|
21
21
|
|
|
22
|
-
def initialize(arguments)
|
|
22
|
+
def initialize(arguments, command_prefix: 'mindee v2')
|
|
23
23
|
@arguments = arguments
|
|
24
|
+
@command_prefix = command_prefix
|
|
24
25
|
@options_parser = OptionParser.new do |opts|
|
|
25
|
-
opts.banner =
|
|
26
|
+
opts.banner = "Usage: #{@command_prefix} command [options]"
|
|
26
27
|
end
|
|
27
28
|
@product_parser = init_product_parser
|
|
28
29
|
@search_parser = init_search_parser
|
|
@@ -67,6 +68,8 @@ module MindeeCLI
|
|
|
67
68
|
else
|
|
68
69
|
abort("#{e.message}\n\n#{@product_parser[command].help}")
|
|
69
70
|
end
|
|
71
|
+
rescue Mindee::Error::MindeeError => e
|
|
72
|
+
abort(format_cli_error(e))
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
private
|
|
@@ -83,9 +86,21 @@ module MindeeCLI
|
|
|
83
86
|
abort(error_msg)
|
|
84
87
|
end
|
|
85
88
|
|
|
89
|
+
def format_cli_error(error)
|
|
90
|
+
if error.is_a?(Mindee::Error::MindeeHTTPErrorV2) && error.status.to_i == 401
|
|
91
|
+
"CLI error: Missing credentials. Provide an API key using '--key' or " \
|
|
92
|
+
"the 'MINDEE_V2_API_KEY' environment variable."
|
|
93
|
+
elsif error.is_a?(Mindee::Error::MindeeAPIError) && error.message.include?('Missing API key')
|
|
94
|
+
"CLI error: Missing API key. Provide it using '--key' or " \
|
|
95
|
+
"the 'MINDEE_V2_API_KEY' environment variable."
|
|
96
|
+
else
|
|
97
|
+
"CLI error: #{error.message}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
86
101
|
def init_search_parser
|
|
87
102
|
OptionParser.new do |options_parser|
|
|
88
|
-
options_parser.banner =
|
|
103
|
+
options_parser.banner = "Usage: #{@command_prefix} search-models [options]"
|
|
89
104
|
init_common_options(options_parser)
|
|
90
105
|
options_parser.on('-n [NAME]', '--name [NAME]',
|
|
91
106
|
'Search for partial matches in model name. Note: case insensitive') do |v|
|
|
@@ -126,7 +141,7 @@ module MindeeCLI
|
|
|
126
141
|
# Initialize common options for search and product commands.
|
|
127
142
|
# @param options_parser [OptionParser]
|
|
128
143
|
def init_common_options(options_parser)
|
|
129
|
-
options_parser.on('-k [KEY]', '--key [KEY]', 'API key
|
|
144
|
+
options_parser.on('-k [KEY]', '--key [KEY]', 'Mindee V2 API key.') { |v| @options[:api_key] = v }
|
|
130
145
|
options_parser.on('-o FORMAT', '--output-format FORMAT', ['raw', 'full', 'summary'],
|
|
131
146
|
'Format of the output (raw, full, summary). Default: summary') do |format|
|
|
132
147
|
@options[:output_format] = format
|
|
@@ -159,11 +174,15 @@ module MindeeCLI
|
|
|
159
174
|
v2_product_parser = {}
|
|
160
175
|
V2_PRODUCTS.each do |product_key, product_values|
|
|
161
176
|
v2_product_parser[product_key] = OptionParser.new do |options_parser|
|
|
162
|
-
options_parser.banner = "Usage:
|
|
177
|
+
options_parser.banner = "Usage: #{@command_prefix} #{product_key} [options] file"
|
|
163
178
|
options_parser.on('-m MODEL_ID', '--model-id MODEL_ID', 'Model ID') { |v| @options[:model_id] = v }
|
|
164
179
|
options_parser.on('-a ALIAS', '--alias ALIAS', 'Add a file alias to the response') do |v|
|
|
165
180
|
@options[:alias] = v
|
|
166
181
|
end
|
|
182
|
+
options_parser.on('-w WEBHOOK_ID', '--webhook-id WEBHOOK_ID',
|
|
183
|
+
'Specify a webhook by ID. May be used multiple times.') do |v|
|
|
184
|
+
(@options[:webhook_ids] ||= []) << v
|
|
185
|
+
end
|
|
167
186
|
init_common_options(options_parser)
|
|
168
187
|
options_parser.on('-F', '--fix-pdf', 'Attempt to repair PDF before enqueueing') do
|
|
169
188
|
@options[:repair_pdf] = true
|
|
@@ -175,10 +194,10 @@ module MindeeCLI
|
|
|
175
194
|
end
|
|
176
195
|
|
|
177
196
|
# @return [Hash]
|
|
178
|
-
def setup_product_params
|
|
197
|
+
def setup_product_params(product_command)
|
|
179
198
|
params = { model_id: @options[:model_id] }
|
|
180
199
|
@options.each_pair do |key, value|
|
|
181
|
-
params[key] = value if V2_PRODUCTS[
|
|
200
|
+
params[key] = value if V2_PRODUCTS[product_command].include?(key) || %i[alias webhook_ids].include?(key)
|
|
182
201
|
end
|
|
183
202
|
params
|
|
184
203
|
end
|
|
@@ -190,7 +209,7 @@ module MindeeCLI
|
|
|
190
209
|
mindee_client = Mindee::V2::Client.new(api_key: options[:api_key])
|
|
191
210
|
response_class = V2_PRODUCTS[product_command][:response_class]
|
|
192
211
|
input_source = setup_input_source(options)
|
|
193
|
-
params = setup_product_params
|
|
212
|
+
params = setup_product_params(product_command)
|
|
194
213
|
|
|
195
214
|
mindee_client.enqueue_and_get_result(
|
|
196
215
|
response_class,
|
|
@@ -73,17 +73,10 @@ module Mindee
|
|
|
73
73
|
# Return the file as a Mindee-compatible BufferInput source.
|
|
74
74
|
#
|
|
75
75
|
# @return [FileInputSource] A BufferInput source.
|
|
76
|
-
def
|
|
76
|
+
def as_input_source
|
|
77
77
|
@buffer.rewind
|
|
78
78
|
Mindee::Input::Source::BytesInputSource.new(@buffer.read || '', @filename)
|
|
79
79
|
end
|
|
80
|
-
|
|
81
|
-
# Return the file as a Mindee-compatible BufferInput source.
|
|
82
|
-
#
|
|
83
|
-
# @return [FileInputSource] A BufferInput source.
|
|
84
|
-
def as_input_source
|
|
85
|
-
as_source
|
|
86
|
-
end
|
|
87
80
|
end
|
|
88
81
|
end
|
|
89
82
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'extracted_image'
|
|
4
|
+
|
|
5
|
+
module Mindee
|
|
6
|
+
# Image Extraction Module.
|
|
7
|
+
module Image
|
|
8
|
+
# List of extracted images.
|
|
9
|
+
class ExtractedImages < Array
|
|
10
|
+
# Save all extracted images to disk.
|
|
11
|
+
#
|
|
12
|
+
# @param output_path [String, Pathname] Directory path to save the extracted images to.
|
|
13
|
+
def save_all_to_disk(output_path)
|
|
14
|
+
each do |image|
|
|
15
|
+
image.write_to_file(File.join(output_path.to_s, image.filename))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -7,16 +7,17 @@ require 'stringio'
|
|
|
7
7
|
require 'tempfile'
|
|
8
8
|
require_relative '../input/sources'
|
|
9
9
|
require_relative 'extracted_image'
|
|
10
|
+
require_relative 'extracted_images'
|
|
10
11
|
|
|
11
12
|
module Mindee
|
|
12
13
|
# Image Extraction Module.
|
|
13
14
|
module Image
|
|
14
15
|
# Image Extraction wrapper class.
|
|
15
16
|
module ImageExtractor
|
|
16
|
-
# Attaches an image as a new page in a
|
|
17
|
+
# Attaches an image as a new page in a PDFDocument object.
|
|
17
18
|
#
|
|
18
19
|
# @param [StringIO] input_buffer Input buffer. Only supports JPEG.
|
|
19
|
-
# @return [Origami::PDF] A
|
|
20
|
+
# @return [Origami::PDF] A PDFDocument handle.
|
|
20
21
|
def self.attach_image_as_new_file(input_buffer, format: 'jpg')
|
|
21
22
|
magick_image = MiniMagick::Image.read(input_buffer)
|
|
22
23
|
# NOTE: We force format consolidation to a single format to avoid frames being interpreted as the final output.
|
|
@@ -32,12 +33,12 @@ module Mindee
|
|
|
32
33
|
# @param [Input::Source::LocalInputSource] input_source
|
|
33
34
|
# @param [Integer] page_id ID of the Page to extract from.
|
|
34
35
|
# @param [Array<Array<Geometry::Point>>, Array<Geometry::Quadrilateral>] polygons List of coordinates to extract.
|
|
35
|
-
# @return [
|
|
36
|
+
# @return [Image::ExtractedImages] Extracted Images.
|
|
36
37
|
def self.extract_multiple_images_from_source(input_source, page_id, polygons)
|
|
37
38
|
new_stream = load_input_source_pdf_page_as_stringio(input_source, page_id)
|
|
38
39
|
new_stream.seek(0)
|
|
39
40
|
|
|
40
|
-
extract_images_from_polygons(input_source, page_id, polygons)
|
|
41
|
+
ExtractedImages.new(extract_images_from_polygons(input_source, page_id, polygons))
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# Extracts images from their positions on a file (as polygons).
|
|
@@ -45,9 +46,9 @@ module Mindee
|
|
|
45
46
|
# @param [Input::Source::LocalInputSource] input_source Local input source.
|
|
46
47
|
# @param [Integer] page_id Page ID.
|
|
47
48
|
# @param [Array<Geometry::Point, Geometry::Polygon, Geometry::Quadrilateral>] polygons
|
|
48
|
-
# @return [
|
|
49
|
+
# @return [Image::ExtractedImages] Extracted Images.
|
|
49
50
|
def self.extract_images_from_polygons(input_source, page_id, polygons)
|
|
50
|
-
extracted_elements =
|
|
51
|
+
extracted_elements = ExtractedImages.new # @type var extracted_elements: Image::ExtractedImages
|
|
51
52
|
|
|
52
53
|
input_source.io_stream.rewind
|
|
53
54
|
pdf_stream = StringIO.new(input_source.io_stream.read.to_s)
|
|
@@ -65,7 +66,7 @@ module Mindee
|
|
|
65
66
|
min_max_x = Geometry.get_min_max_x(points)
|
|
66
67
|
min_max_y = Geometry.get_min_max_y(points)
|
|
67
68
|
file_extension = ImageUtils.determine_file_extension(input_source)
|
|
68
|
-
cropped_image =
|
|
69
|
+
cropped_image = crop_image(page_content, min_max_x, min_max_y)
|
|
69
70
|
if file_extension == 'pdf'
|
|
70
71
|
cropped_image.format('jpg')
|
|
71
72
|
else
|
|
@@ -102,7 +103,7 @@ module Mindee
|
|
|
102
103
|
#
|
|
103
104
|
# @param input_file [LocalInputSource] Local input.
|
|
104
105
|
# @param [Integer] page_id Page ID.
|
|
105
|
-
# @return [StringIO] A valid
|
|
106
|
+
# @return [StringIO] A valid PDFDocument handle.
|
|
106
107
|
def self.load_input_source_pdf_page_as_stringio(input_file, page_id)
|
|
107
108
|
input_file.io_stream.rewind
|
|
108
109
|
if input_file.pdf?
|
|
@@ -111,6 +112,23 @@ module Mindee
|
|
|
111
112
|
input_file.io_stream
|
|
112
113
|
end
|
|
113
114
|
end
|
|
115
|
+
|
|
116
|
+
# Crops a MiniMagick Image from the given bounding box.
|
|
117
|
+
#
|
|
118
|
+
# @param [MiniMagick::Image] image Input Image.
|
|
119
|
+
# @param [Mindee::Geometry::MinMax] min_max_x minimum & maximum values for the x coordinates.
|
|
120
|
+
# @param [Mindee::Geometry::MinMax] min_max_y minimum & maximum values for the y coordinates.
|
|
121
|
+
def self.crop_image(image, min_max_x, min_max_y)
|
|
122
|
+
width = image[:width].to_i
|
|
123
|
+
height = image[:height].to_i
|
|
124
|
+
|
|
125
|
+
image.format('jpg')
|
|
126
|
+
new_width = (min_max_x.max - min_max_x.min) * width
|
|
127
|
+
new_height = (min_max_y.max - min_max_y.min) * height
|
|
128
|
+
image.crop("#{new_width}x#{new_height}+#{min_max_x.min * width}+#{min_max_y.min * height}")
|
|
129
|
+
|
|
130
|
+
image
|
|
131
|
+
end
|
|
114
132
|
end
|
|
115
133
|
end
|
|
116
134
|
end
|
|
@@ -124,23 +124,6 @@ module Mindee
|
|
|
124
124
|
MiniMagick::Image.read(pdf_stream)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
# Crops a MiniMagick Image from a the given bounding box.
|
|
128
|
-
#
|
|
129
|
-
# @param [MiniMagick::Image] image Input Image.
|
|
130
|
-
# @param [Mindee::Geometry::MinMax] min_max_x minimum & maximum values for the x coordinates.
|
|
131
|
-
# @param [Mindee::Geometry::MinMax] min_max_y minimum & maximum values for the y coordinates.
|
|
132
|
-
def self.crop_image(image, min_max_x, min_max_y)
|
|
133
|
-
width = image[:width].to_i
|
|
134
|
-
height = image[:height].to_i
|
|
135
|
-
|
|
136
|
-
image.format('jpg')
|
|
137
|
-
new_width = (min_max_x.max - min_max_x.min) * width
|
|
138
|
-
new_height = (min_max_y.max - min_max_y.min) * height
|
|
139
|
-
image.crop("#{new_width}x#{new_height}+#{min_max_x.min * width}+#{min_max_y.min * height}")
|
|
140
|
-
|
|
141
|
-
image
|
|
142
|
-
end
|
|
143
|
-
|
|
144
127
|
# Writes a MiniMagick::Image to a buffer.
|
|
145
128
|
#
|
|
146
129
|
# @param [MiniMagick::Image] image a valid MiniMagick image.
|
data/lib/mindee/image.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Mindee
|
|
|
16
16
|
ALLOWED_MIME_TYPES = [
|
|
17
17
|
'application/pdf',
|
|
18
18
|
'image/heic',
|
|
19
|
+
'image/heif',
|
|
19
20
|
'image/png',
|
|
20
21
|
'image/jpeg',
|
|
21
22
|
'image/tiff',
|
|
@@ -37,11 +38,7 @@ module Mindee
|
|
|
37
38
|
def initialize(io_stream, filename, repair_pdf: false)
|
|
38
39
|
@io_stream = io_stream
|
|
39
40
|
@filename = filename
|
|
40
|
-
@file_mimetype =
|
|
41
|
-
Marcel::MimeType.for @io_stream
|
|
42
|
-
else
|
|
43
|
-
Marcel::MimeType.for @io_stream, name: @filename
|
|
44
|
-
end
|
|
41
|
+
@file_mimetype = detect_mime_type(repair_pdf)
|
|
45
42
|
if ALLOWED_MIME_TYPES.include? @file_mimetype
|
|
46
43
|
logger.debug("Loaded new input #{@filename} from #{self.class}")
|
|
47
44
|
return
|
|
@@ -197,6 +194,28 @@ module Mindee
|
|
|
197
194
|
|
|
198
195
|
Mindee::PDF::PDFTools.source_text?(@io_stream)
|
|
199
196
|
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
# Checks the mimetype for the file. If it is a PDF, it will attempt to repair it if repair_pdf is true.
|
|
201
|
+
# @param repair_pdf [bool] Whether to attempt to repair the PDF.
|
|
202
|
+
# @return [String] The mimetype of the file.
|
|
203
|
+
def detect_mime_type(repair_pdf)
|
|
204
|
+
return Marcel::MimeType.for(@io_stream) if repair_pdf
|
|
205
|
+
|
|
206
|
+
heif_mimetype_from_extension || Marcel::MimeType.for(@io_stream, name: @filename)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Checks the file extension for a HEIF mimetype.
|
|
210
|
+
# @return [String, nil] The mimetype if found, nil otherwise.
|
|
211
|
+
def heif_mimetype_from_extension
|
|
212
|
+
case File.extname(@filename.to_s).downcase
|
|
213
|
+
when '.heic'
|
|
214
|
+
'image/heic'
|
|
215
|
+
when '.heif'
|
|
216
|
+
'image/heif'
|
|
217
|
+
end
|
|
218
|
+
end
|
|
200
219
|
end
|
|
201
220
|
|
|
202
221
|
# Replaces non-ASCII characters by their UNICODE escape sequence.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'extracted_pdf'
|
|
4
|
+
|
|
5
|
+
module Mindee
|
|
6
|
+
# PDF Extraction Module.
|
|
7
|
+
module PDF
|
|
8
|
+
# List of extracted PDFs.
|
|
9
|
+
class ExtractedPDFs < Array
|
|
10
|
+
# Save all extracted PDFs to disk.
|
|
11
|
+
#
|
|
12
|
+
# @param output_path [String, Pathname] Directory path to save the extracted PDFs to.
|
|
13
|
+
def save_all_to_disk(output_path)
|
|
14
|
+
each do |pdf|
|
|
15
|
+
pdf.write_to_file(File.join(output_path.to_s, pdf.filename))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Mindee
|
|
4
|
-
#
|
|
4
|
+
# PDF Extraction Module.
|
|
5
5
|
module PDF
|
|
6
|
-
#
|
|
6
|
+
# PDF extraction class.
|
|
7
7
|
class PDFExtractor
|
|
8
8
|
# @param local_input [Mindee::Input::Source::LocalInputSource]
|
|
9
9
|
def initialize(local_input)
|
|
@@ -23,15 +23,15 @@ module Mindee
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# Retrieves the page count for the
|
|
26
|
+
# Retrieves the page count for the PDF object.
|
|
27
27
|
# @return [Integer]
|
|
28
28
|
def page_count
|
|
29
29
|
Mindee::PDF::PDFProcessor.open_pdf(@source_pdf).pages.size
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
# Creates a new
|
|
33
|
-
# @param page_indexes [Array<Integer>] List of page number to use for merging in the original
|
|
34
|
-
# @return [StringIO] The buffer containing the new
|
|
32
|
+
# Creates a new PDF from pages and save it into a buffer.
|
|
33
|
+
# @param page_indexes [Array<Integer>] List of page number to use for merging in the original PDF.
|
|
34
|
+
# @return [StringIO] The buffer containing the new PDF.
|
|
35
35
|
def cut_pages(page_indexes)
|
|
36
36
|
options = PageOptions.new(params: {
|
|
37
37
|
page_indexes: page_indexes,
|
|
@@ -41,10 +41,10 @@ module Mindee
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Extract the sub-documents from the main pdf, based on the given list of page indexes.
|
|
44
|
-
# @param page_indexes [Array<Array<Integer>>] List of page number to use for merging in the original
|
|
45
|
-
# @return [
|
|
44
|
+
# @param page_indexes [Array<Array<Integer>>] List of page number to use for merging in the original PDF.
|
|
45
|
+
# @return [Mindee::PDF::ExtractedPDFs] The buffer containing the new PDF.
|
|
46
46
|
def extract_sub_documents(page_indexes)
|
|
47
|
-
extracted_pdfs =
|
|
47
|
+
extracted_pdfs = ExtractedPDFs.new # @type var extracted_pdfs: Mindee::PDF::ExtractedPDFs
|
|
48
48
|
extension = File.extname(@filename)
|
|
49
49
|
basename = File.basename(@filename, extension)
|
|
50
50
|
page_indexes.each do |page_index_list|
|
|
@@ -74,7 +74,7 @@ module Mindee
|
|
|
74
74
|
# Extracts invoices as complete PDFs from the document.
|
|
75
75
|
# @param page_indexes [Array<Array<Integer>, InvoiceSplitterV1InvoicePageGroup>]
|
|
76
76
|
# @param strict [bool]
|
|
77
|
-
# @return [
|
|
77
|
+
# @return [Mindee::PDF::ExtractedPDFs]
|
|
78
78
|
def extract_invoices(page_indexes, strict: false)
|
|
79
79
|
raise Error::MindeePDFError, 'No indexes provided.' if page_indexes.empty?
|
|
80
80
|
|