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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/SKILL.md +172 -0
  4. data/bin/mindee.rb +20 -15
  5. data/bin/v2/parser.rb +27 -8
  6. data/lib/mindee/image/extracted_image.rb +1 -8
  7. data/lib/mindee/image/extracted_images.rb +20 -0
  8. data/lib/mindee/image/image_extractor.rb +26 -8
  9. data/lib/mindee/image/image_utils.rb +0 -17
  10. data/lib/mindee/image.rb +1 -0
  11. data/lib/mindee/input/sources/local_input_source.rb +24 -5
  12. data/lib/mindee/pdf/extracted_pdf.rb +1 -1
  13. data/lib/mindee/pdf/extracted_pdfs.rb +20 -0
  14. data/lib/mindee/pdf/pdf_extractor.rb +10 -10
  15. data/lib/mindee/pdf.rb +1 -0
  16. data/lib/mindee/v2/file_operations/crop.rb +7 -6
  17. data/lib/mindee/v2/file_operations/split.rb +2 -2
  18. data/lib/mindee/v2/file_operations.rb +0 -2
  19. data/lib/mindee/v2/parsing/failed_inference_response.rb +34 -0
  20. data/lib/mindee/v2/parsing/search/search_response.rb +8 -5
  21. data/lib/mindee/v2/parsing.rb +1 -0
  22. data/lib/mindee/v2/product/crop/crop_item.rb +1 -1
  23. data/lib/mindee/v2/product/crop/crop_response.rb +0 -8
  24. data/lib/mindee/v2/product/crop/crop_result.rb +8 -0
  25. data/lib/mindee/v2/product/split/split_range.rb +1 -1
  26. data/lib/mindee/v2/product/split/split_response.rb +0 -8
  27. data/lib/mindee/v2/product/split/split_result.rb +7 -0
  28. data/lib/mindee/version.rb +1 -1
  29. data/sig/mindee/image/extracted_image.rbs +0 -1
  30. data/sig/mindee/image/extracted_images.rbs +8 -0
  31. data/sig/mindee/image/image_extractor.rbs +3 -2
  32. data/sig/mindee/image/image_utils.rbs +0 -1
  33. data/sig/mindee/input/sources/local_input_source.rbs +6 -0
  34. data/sig/mindee/pdf/extracted_pdfs.rbs +8 -0
  35. data/sig/mindee/pdf/pdf_extractor.rbs +2 -2
  36. data/sig/mindee/v2/file_operation/crop.rbs +1 -1
  37. data/sig/mindee/v2/file_operation/split.rbs +1 -1
  38. data/sig/mindee/v2/parsing/failed_inference_response.rbs +15 -0
  39. data/sig/mindee/v2/parsing/search/search_models.rbs +13 -0
  40. data/sig/mindee/v2/parsing/search/search_response.rbs +1 -0
  41. data/sig/mindee/v2/product/crop/crop_item.rbs +1 -1
  42. data/sig/mindee/v2/product/crop/crop_response.rbs +0 -2
  43. data/sig/mindee/v2/product/crop/crop_result.rbs +1 -0
  44. data/sig/mindee/v2/product/split/split_range.rbs +1 -1
  45. data/sig/mindee/v2/product/split/split_response.rbs +0 -2
  46. data/sig/mindee/v2/product/split/split_result.rbs +3 -0
  47. metadata +11 -7
  48. data/lib/mindee/v2/file_operations/crop_files.rb +0 -25
  49. data/lib/mindee/v2/file_operations/split_files.rb +0 -25
  50. data/sig/mindee/v2/file_operation/crop_files.rbs +0 -9
  51. 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: addb1d4afceb4816df637946d7d0028da0459681ae05ecf5ce9550f5a2329b2c
4
- data.tar.gz: f495ddafda38ba5f5cf6c0e00e88a331a32264a297704a941136d61b660700a8
3
+ metadata.gz: d8eb9e96fa7c4b8a41479c87152f858112a72c52f18c23ad247c220615d4df15
4
+ data.tar.gz: 44da90b0f6e814f13cd418502998e3e17610e76857199744c8f2e98b217d72c0
5
5
  SHA512:
6
- metadata.gz: 2e2784c6fb7c8a8cb98eb1bb6d191b451899fbd4c4a4ae985d8f7d0271b4aa2a97edc64a0b084a700d8769def4b0400cdab24393b3eb7b1f14726407d7f2c93b
7
- data.tar.gz: 6718cc7f954306771effbc7e96236bd246ed7aaf34f6296278d55cc821e3d850e54ce03608c64d7d81cb05d4b4ed8cf4055e2d0c08ec4ec315c0dfc0d948a188
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 setup_main_parser
10
- v1_parser = MindeeCLI::V1Parser.new(ARGV)
11
- v2_parser = MindeeCLI::V2Parser.new(ARGV)
12
- main_parser = OptionParser.new do |opts|
13
- opts.banner = "Usage: mindee [command]"
14
- opts.separator "Commands:"
15
- opts.separator " v1 Use Version 1 of the Mindee API"
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
- case main_command
21
- when 'v1'
22
- v1_parser.execute
23
- when 'v2'
24
- v2_parser.execute
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
- abort(main_parser.help)
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 = 'Usage: mindee v2 command [options]'
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 = 'Usage: mindee v2 search-models [options]'
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 for the endpoint') { |v| @options[:api_key] = v }
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: mindee v2 #{product_key} [options] file"
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['extraction'].include?(key)
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 as_source
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 PdfDocument object.
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 PdfDocument handle.
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 [Array<Image::ExtractedImage>] Extracted Images.
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 [Array<Image::ExtractedImage>] Extracted Images.
49
+ # @return [Image::ExtractedImages] Extracted Images.
49
50
  def self.extract_images_from_polygons(input_source, page_id, polygons)
50
- extracted_elements = [] # @type var extracted_elements: Array[Image::ExtractedImage]
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 = ImageUtils.crop_image(page_content, min_max_x, min_max_y)
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 PdfDocument handle.
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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'image/extracted_image'
4
+ require_relative 'image/extracted_images'
4
5
  require_relative 'image/image_compressor'
5
6
  require_relative 'image/image_extractor'
6
7
  require_relative 'image/image_utils'
@@ -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 = if repair_pdf
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.
@@ -3,7 +3,7 @@
3
3
  module Mindee
4
4
  # PDF Extraction Module.
5
5
  module PDF
6
- # An extracted sub-Pdf.
6
+ # An extracted sub-PDF.
7
7
  class ExtractedPDF
8
8
  # Byte contents of the pdf
9
9
  # @return [StringIO]
@@ -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
- # Pdf Extraction Module.
4
+ # PDF Extraction Module.
5
5
  module PDF
6
- # Pdf extraction class.
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 Pdf object.
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 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.
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 Pdf.
45
- # @return [Array<Mindee::PDF::ExtractedPDF>] The buffer containing the new Pdf.
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 = [] # @type var extracted_pdfs: Array[Mindee::PDF::ExtractedPDF]
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 [Array<Mindee::PDF::ExtractedPDF>]
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
 
data/lib/mindee/pdf.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'pdf/extracted_pdf'
4
+ require_relative 'pdf/extracted_pdfs'
4
5
  require_relative 'pdf/pdf_compressor'
5
6
  require_relative 'pdf/pdf_extractor'
6
7
  require_relative 'pdf/pdf_processor'