wax_iiif 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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +25 -0
  4. data/.travis.yml +2 -2
  5. data/Gemfile +3 -2
  6. data/README.md +4 -4
  7. data/lib/wax_iiif.rb +34 -44
  8. data/lib/wax_iiif/base_properties.rb +90 -0
  9. data/lib/wax_iiif/builder.rb +252 -0
  10. data/lib/wax_iiif/collection.rb +58 -0
  11. data/lib/{iiif_s3 → wax_iiif}/config.rb +46 -60
  12. data/lib/{iiif_s3 → wax_iiif}/errors.rb +6 -12
  13. data/lib/{iiif_s3 → wax_iiif}/full_image.rb +3 -8
  14. data/lib/wax_iiif/image_info.rb +89 -0
  15. data/lib/{iiif_s3 → wax_iiif}/image_record.rb +48 -63
  16. data/lib/{iiif_s3 → wax_iiif}/image_tile.rb +8 -14
  17. data/lib/{iiif_s3 → wax_iiif}/image_variant.rb +20 -38
  18. data/lib/wax_iiif/manifest.rb +151 -0
  19. data/lib/{iiif_s3 → wax_iiif}/thumbnail.rb +5 -9
  20. data/lib/{iiif_s3 → wax_iiif}/utilities.rb +5 -5
  21. data/lib/{iiif_s3 → wax_iiif}/utilities/helpers.rb +18 -48
  22. data/lib/{iiif_s3 → wax_iiif}/utilities/pdf_splitter.rb +20 -23
  23. data/spec/base_properties_spec.rb +8 -12
  24. data/spec/shared_contexts.rb +28 -92
  25. data/spec/spec_helper.rb +3 -3
  26. data/spec/wax_iiif/builder_spec.rb +143 -0
  27. data/spec/wax_iiif/collection_spec.rb +53 -0
  28. data/spec/wax_iiif/config_spec.rb +15 -0
  29. data/spec/wax_iiif/image_info_spec.rb +57 -0
  30. data/spec/wax_iiif/image_record_spec.rb +82 -0
  31. data/spec/wax_iiif/image_variant_spec.rb +66 -0
  32. data/spec/wax_iiif/manifest_spec.rb +97 -0
  33. data/spec/wax_iiif/utilities/pdf_splitter_spec.rb +14 -0
  34. data/wax_iiif.gemspec +15 -19
  35. metadata +52 -97
  36. data/Guardfile +0 -10
  37. data/Rakefile +0 -24
  38. data/lib/iiif_s3/base_properties.rb +0 -95
  39. data/lib/iiif_s3/builder.rb +0 -241
  40. data/lib/iiif_s3/collection.rb +0 -61
  41. data/lib/iiif_s3/image_info.rb +0 -96
  42. data/lib/iiif_s3/manifest.rb +0 -151
  43. data/lib/iiif_s3/version.rb +0 -5
  44. data/spec/iiif_s3/builder_spec.rb +0 -152
  45. data/spec/iiif_s3/collection_spec.rb +0 -68
  46. data/spec/iiif_s3/config_spec.rb +0 -15
  47. data/spec/iiif_s3/image_info_spec.rb +0 -57
  48. data/spec/iiif_s3/image_record_spec.rb +0 -96
  49. data/spec/iiif_s3/image_variant_spec.rb +0 -68
  50. data/spec/iiif_s3/manifest_spec.rb +0 -97
  51. data/spec/iiif_s3/utilities/pdf_splitter_spec.rb +0 -17
  52. data/test.rb +0 -77
@@ -1,23 +1,18 @@
1
-
2
- require "mini_magick"
1
+ require 'mini_magick'
3
2
  require 'fileutils'
4
3
 
5
- module IiifS3
6
-
7
- #
8
- # Class ImageTile is a specific ImageVariant used when generating a
4
+ module WaxIiif
5
+ # Class ImageTile is a specific ImageVariant used when generating a
9
6
  # stack of tiles suitable for Mirador-style zooming interfaces. Each
10
7
  # instance of ImageTile represents a single tile.
11
8
  #
12
9
  # @author David Newbury <david.newbury@gmail.com>
13
10
  #
14
11
  class ImageTile < ImageVariant
15
-
16
- #
17
12
  # Initializing this
18
13
  #
19
14
  # @param [Hash] data A Image Data object.
20
- # @param [IiifS3::Config] config The configuration object
15
+ # @param [WaxIiif::Config] config The configuration object
21
16
  # @param [Hash<width: Number, height: Number, x Number, y: Number, xSize: Number, ySize: Number>] tile
22
17
  # A hash of parameters that defines this tile.
23
18
  def initialize(data, config, tile)
@@ -26,14 +21,14 @@ module IiifS3
26
21
  end
27
22
 
28
23
  protected
29
-
30
- def resize(width=nil,height=nil)
24
+
25
+ def resize(_width = nil, _height = nil)
31
26
  @image.combine_options do |img|
32
27
  img.crop "#{@tile[:width]}x#{@tile[:height]}+#{@tile[:x]}+#{@tile[:y]}"
33
28
  img.resize "#{@tile[:xSize]}x#{@tile[:ySize]}"
34
29
  end
35
30
  end
36
-
31
+
37
32
  def region
38
33
  "#{@tile[:x]},#{@tile[:y]},#{@tile[:width]},#{@tile[:height]}"
39
34
  end
@@ -41,6 +36,5 @@ module IiifS3
41
36
  def filestring
42
37
  "/#{region}/#{@tile[:xSize]},/0"
43
38
  end
44
-
45
39
  end
46
- end
40
+ end
@@ -1,23 +1,17 @@
1
-
2
- require "mini_magick"
1
+ require 'mini_magick'
3
2
  require 'fileutils'
4
- require_relative "utilities"
5
-
6
- module IiifS3
3
+ require_relative 'utilities'
7
4
 
5
+ module WaxIiif
8
6
  FakeImageVariant = Struct.new(:id, :width, :height, :uri, :mime_type)
9
-
10
- #
11
7
  # Class ImageVariant represents a single image file within a manifest.
12
8
  #
13
- #
14
9
  # @author David Newbury <david.newbury@gmail.com>
15
10
  #
16
11
  class ImageVariant
17
12
  include Utilities::Helpers
18
13
  include MiniMagick
19
14
 
20
- #
21
15
  # Initializing an ImageVariant will create the actual image file
22
16
  # on the file system.
23
17
  #
@@ -25,57 +19,48 @@ module IiifS3
25
19
  # data hash to have an "id", a "image_path", and a "page_number".
26
20
  #
27
21
  # @param [Hash] data A Image Data object.
28
- # @param [IiifS3::Config] config The configuration object
22
+ # @param [WaxIiif::Config] config The configuration object
29
23
  # @param [Number] width the desired width of this object in pixels
30
24
  # @param [Number] height the desired height of this object in pixels
31
- # @raise IiifS3::Error::InvalidImageData
25
+ # @raise WaxIiif::Error::InvalidImageData
32
26
  #
33
27
  def initialize(data, config, size = nil)
34
-
35
28
  @config = config
36
- # Validate input data
37
- if data.id.nil? || data.id.to_s.empty?
38
- raise IiifS3::Error::InvalidImageData, "Each image needs an ID"
39
- elsif data.image_path.nil? || data.image_path.to_s.empty?
40
- raise IiifS3::Error::InvalidImageData, "Each image needs an path."
41
- end
29
+
30
+ raise WaxIiif::Error::InvalidImageData, 'Each image needs an ID' if data.id.nil? || data.id.to_s.empty?
31
+ raise WaxIiif::Error::InvalidImageData, 'Each image needs an path.' if data.image_path.nil? || data.image_path.to_s.empty?
42
32
 
43
33
  # open image
44
34
  begin
45
35
  @image = Image.open(data.image_path)
46
36
  rescue MiniMagick::Invalid => e
47
- raise IiifS3::Error::InvalidImageData, "Cannot read this image file: #{data.image_path}. #{e}"
37
+ raise WaxIiif::Error::InvalidImageData, "Cannot read this image file: #{data.image_path}. #{e}"
48
38
  end
49
39
 
50
40
  width = size.nil? ? width : size
51
41
  resize(width)
52
- @image.format "jpg"
53
42
 
54
- @id = generate_image_id(data.id,data.page_number)
55
- @uri = "#{id}#{filestring}/default.jpg"
43
+ @image.format 'jpg'
44
+ @id = generate_image_id(data.id)
45
+ @uri = "#{id}#{filestring}/default.jpg"
56
46
 
57
47
  # Create the on-disk version of the file
58
- path = "#{generate_image_location(data.id,data.page_number)}#{filestring}"
59
- FileUtils::mkdir_p path
48
+ path = "#{@id}#{filestring}".gsub(@config.base_url, @config.output_dir)
49
+ FileUtils.mkdir_p path
60
50
  filename = "#{path}/default.jpg"
61
- @image.write filename unless File.exists? filename
62
- add_file_to_s3(filename) if @config.upload_to_s3
51
+ @image.write filename unless File.exist? filename
63
52
  end
64
53
 
65
-
66
54
  # @!attribute [r] uri
67
55
  # @return [String] The URI for the jpeg image
68
56
  attr_reader :uri
69
57
 
70
- #
71
58
  # @!attribute [r] id
72
59
  # @return [String] The URI for the variant.
73
60
  attr_reader :id
74
61
 
75
-
76
62
  # Get the image width
77
63
  #
78
- #
79
64
  # @return [Number] The width of the image in pixels
80
65
  def width
81
66
  @image.width
@@ -83,16 +68,14 @@ module IiifS3
83
68
 
84
69
  # Get the image height
85
70
  #
86
- #
87
71
  # @return [Number] The height of the image in pixels
88
72
  def height
89
73
  @image.height
90
74
  end
91
75
 
92
- #
93
76
  # Get the MIME Content-Type of the image.
94
77
  #
95
- # @return [String] the MIME Content-Type (typically "image/jpeg")
78
+ # @return [String] the MIME Content-Type (typically 'image/jpeg')
96
79
  #
97
80
  def mime_type
98
81
  @image.mime_type
@@ -105,23 +88,22 @@ module IiifS3
105
88
  #
106
89
  # @return [<type>] <description>
107
90
  #
108
- def generate_image_id(id, page_number)
109
- "#{@config.base_url}#{@config.prefix}/#{@config.image_directory_name}/#{id}-#{page_number}"
91
+ def generate_image_id(id)
92
+ "#{@config.base_url}#{@config.prefix}/#{@config.image_directory_name}/#{id}"
110
93
  end
111
94
 
112
95
  protected
113
96
 
114
97
  def region
115
- "full"
98
+ 'full'
116
99
  end
117
100
 
118
101
  def resize(width)
119
- @image.resize "#{width}"
102
+ @image.resize width
120
103
  end
121
104
 
122
105
  def filestring
123
106
  "/#{region}/#{width},/0"
124
107
  end
125
-
126
108
  end
127
109
  end
@@ -0,0 +1,151 @@
1
+ module WaxIiif
2
+ FakeManifest = Struct.new(:id, :type, :label)
3
+
4
+ # Class Manifest is an abstraction over the IIIF Manifest, and by extension over the
5
+ # entire Presentation API. It takes the internal representation of data and converts
6
+ # it into a collection of JSON-LD documents. Optionally, it also provides the ability
7
+ # to save these files to disk and upload them to Amazon S3.
8
+ #
9
+ # @author David Newbury <david.newbury@gmail.com>
10
+ class Manifest
11
+ # @return [String] The IIIF default type for a manifest.
12
+ TYPE = 'sc:Manifest'.freeze
13
+
14
+ include BaseProperties
15
+
16
+ #--------------------------------------------------------------------------
17
+ # CONSTRUCTOR
18
+ #--------------------------------------------------------------------------
19
+
20
+ # This will initialize a new manifest.
21
+ #
22
+ # @param [Array<ImageRecord>] image_records An array of ImageRecord types
23
+ # @param [<type>] config <description>
24
+ # @param [<type>] opts <description>
25
+ #
26
+ def initialize(image_records, config, opts = {})
27
+ @config = config
28
+ image_records.each do |record|
29
+ raise WaxIiif::Error::InvalidImageData, 'The data provided to the manifest were not ImageRecords' unless record.is_a? ImageRecord
30
+ end
31
+
32
+ @primary = image_records.find(&:primary?)
33
+
34
+ raise WaxIiif::Error::InvalidImageData, "No 'primary?' was found in the image data." unless @primary
35
+ raise WaxIiif::Error::MultiplePrimaryImages, 'Multiple primary images were found in the image data.' unless image_records.count(&:primary?) == 1
36
+
37
+ id = @primary.manifest_id.nil? ? "#{@primary.id}/manifest" : "#{@primary.manifest_id}/manifest"
38
+
39
+ @id = generate_id(id)
40
+ @label = @primary.label || opts[:label] || ''
41
+ @description = @primary.description || opts[:description]
42
+ @attribution = @primary.attribution || opts.fetch(:attribution, nil)
43
+ @logo = @primary.logo || opts.fetch(:logo, nil)
44
+ @license = @primary.license || opts.fetch(:license, nil)
45
+ @metadata = @primary.metadata || opts.fetch(:metadata, nil)
46
+
47
+ @sequences = build_sequence(image_records)
48
+ end
49
+
50
+ #
51
+ # @return [String] the JSON-LD representation of the manifest as a string.
52
+ #
53
+ def to_json
54
+ obj = base_properties
55
+
56
+ obj['thumbnail'] = @primary.variants['thumbnail'].uri
57
+ obj['viewingDirection'] = @primary.viewing_direction
58
+ obj['viewingHint'] = @primary.document? ? 'paged' : 'individuals'
59
+ obj['sequences'] = [@sequences]
60
+
61
+ JSON.pretty_generate obj
62
+ end
63
+
64
+ # @return [String]
65
+ def base_id
66
+ @primary.manifest_id || @primary.id
67
+ end
68
+
69
+ #
70
+ # Save the manifest and all sub-resources to disk, using the
71
+ # paths contained withing the WaxIiif::Config object passed at
72
+ # initialization.
73
+ #
74
+ # Will create the manifest, sequences, canvases, and annotation subobjects.
75
+ #
76
+ # @return [Void]
77
+ #
78
+ def save_all_files_to_disk
79
+ data = JSON.parse(self.to_json)
80
+ save_to_disk(data)
81
+ data['sequences'].each do |sequence|
82
+ save_to_disk(sequence)
83
+ sequence['canvases'].each do |canvas|
84
+ save_to_disk(canvas)
85
+ canvas['images'].each do |annotation|
86
+ save_to_disk(annotation)
87
+ end
88
+ end
89
+ end
90
+ nil
91
+ end
92
+
93
+ protected
94
+
95
+ #--------------------------------------------------------------------------
96
+ def build_sequence(image_records, opts = {})
97
+ seq_id = generate_id "sequence/#{@primary.id}"
98
+
99
+ opts.merge(
100
+ '@id' => seq_id,
101
+ '@type' => SEQUENCE_TYPE,
102
+ 'canvases' => image_records.collect { |i| build_canvas(i) }
103
+ )
104
+ end
105
+
106
+ #--------------------------------------------------------------------------
107
+ def build_canvas(data)
108
+ canvas_id = generate_id "canvas/#{data.id}"
109
+
110
+ obj = {
111
+ '@type' => CANVAS_TYPE,
112
+ '@id' => canvas_id,
113
+ 'label' => data.section_label,
114
+ 'width' => data.variants['full'].width.floor,
115
+ 'height' => data.variants['full'].height.floor,
116
+ 'thumbnail' => data.variants['thumbnail'].uri
117
+ }
118
+ obj['images'] = [build_image(data, obj)]
119
+
120
+ # handle objects that are less than 1200px on a side by doubling canvas size
121
+ if obj['width'] < MIN_CANVAS_SIZE || obj['height'] < MIN_CANVAS_SIZE
122
+ obj['width'] *= 2
123
+ obj['height'] *= 2
124
+ end
125
+ obj
126
+ end
127
+
128
+ #--------------------------------------------------------------------------
129
+ def build_image(data, canvas)
130
+ annotation_id = generate_id "annotation/#{data.id}"
131
+ {
132
+ '@type' => ANNOTATION_TYPE,
133
+ '@id' => annotation_id,
134
+ 'motivation' => MOTIVATION,
135
+ 'resource' => {
136
+ '@id' => data.variants['full'].uri,
137
+ '@type' => IMAGE_TYPE,
138
+ 'format' => data.variants['full'].mime_type || 'image/jpeg',
139
+ 'service' => {
140
+ '@context' => WaxIiif::IMAGE_CONTEXT,
141
+ '@id' => data.variants['full'].id,
142
+ 'profile' => WaxIiif::LEVEL_0
143
+ },
144
+ 'width' => data.variants['full'].width,
145
+ 'height' => data.variants['full'].height
146
+ },
147
+ 'on' => canvas['@id']
148
+ }
149
+ end
150
+ end
151
+ end
@@ -1,9 +1,7 @@
1
- require "mini_magick"
1
+ require 'mini_magick'
2
2
  require 'fileutils'
3
3
 
4
- module IiifS3
5
-
6
- #
4
+ module WaxIiif
7
5
  # Class Thumbnail provides a specific variant of an image file used for the thumbnail links
8
6
  # within the metadata. It will generate a consistent sized version based on a max width
9
7
  # and height. By default, it generates images at 250px on the longest size.
@@ -11,7 +9,6 @@ module IiifS3
11
9
  # @author David Newbury <david.newbury@gmail.com>
12
10
  #
13
11
  class Thumbnail < ImageVariant
14
-
15
12
  # Initialize a new thumbnail.
16
13
  #
17
14
  # @param [hash] data The image data object
@@ -19,16 +16,15 @@ module IiifS3
19
16
  # @param [Integer] max_width The maximum width of the thumbnail
20
17
  # @param [Integer] max_height The maximum height of the thumbnail
21
18
  #
22
- def initialize(data, config, width=nil)
19
+ def initialize(data, config, width = nil)
23
20
  @width = width || config.thumbnail_size
24
- super(data,config)
21
+ super(data, config)
25
22
  end
26
23
 
27
24
  protected
28
25
 
29
26
  def resize(width)
30
- @image.resize "#{@width}"
27
+ @image.resize(width || @width)
31
28
  end
32
-
33
29
  end
34
30
  end
@@ -1,12 +1,12 @@
1
- require_relative "utilities/pdf_splitter"
2
- require_relative "utilities/helpers"
1
+ require_relative 'utilities/pdf_splitter'
2
+ require_relative 'utilities/helpers'
3
3
 
4
- module IiifS3
4
+ module WaxIiif
5
5
  # Module Utilities provides a set of basic utilities and helper functions for
6
- # the IIIFS3 library.
6
+ # the WaxIiif library.
7
7
  #
8
8
  # @author David Newbury <david.newbury@gmail.com>
9
9
  #
10
10
  module Utilities
11
11
  end
12
- end
12
+ end
@@ -1,15 +1,13 @@
1
- module IiifS3
1
+ module WaxIiif
2
2
  module Utilities
3
-
4
3
  # Module Helpers provides helper functions. Which seems logical.
5
- #
4
+ #
6
5
  # Note that these functions require an @config object to exist on the
7
- # mixed-in class.
6
+ # mixed-in class.
8
7
  #
9
8
  # @author David Newbury <david.newbury@gmail.com>
10
9
  #
11
10
  module Helpers
12
-
13
11
  # def self.included(klass)
14
12
  # unless respond_to? :config
15
13
  # raise StandardError, "The helpers have been included in class #{klass}, but #{klass} does not have a @config object."
@@ -17,7 +15,7 @@ module IiifS3
17
15
  # end
18
16
 
19
17
  # This will generate a valid, escaped URI for an object.
20
- #
18
+ #
21
19
  # This will prepend the standard path and prefix, and will append .json
22
20
  # if enabled.
23
21
  #
@@ -25,12 +23,12 @@ module IiifS3
25
23
  # @return [String] The generated URI
26
24
  def generate_id(path)
27
25
  val = "#{@config.base_url}#{@config.prefix}/#{path}"
28
- val += ".json" if @config.use_extensions
29
- URI.escape(val)
26
+ val += '.json' if @config.use_extensions
27
+ val
30
28
  end
31
29
 
32
30
  # Given an id, generate a path on disk for that id, based on the config file
33
- #
31
+ #
34
32
  # @param [String] id the path to the unique key for the object
35
33
  # @return [String] a path within the output dir, with the prefix included
36
34
  def generate_build_location(id)
@@ -39,58 +37,30 @@ module IiifS3
39
37
 
40
38
  # Given an id and a page number, generate a path on disk for an image
41
39
  # The path will be based on the config file.
42
- #
40
+ #
43
41
  # @param [String] id the unique key for the object
44
- # @param [String] page_number the page for this image.
45
42
  # @return [String] a path for the image
46
- def generate_image_location(id, page_number)
47
- generate_build_location "#{@config.image_directory_name}/#{id}-#{page_number}"
43
+ def generate_image_location(id)
44
+ generate_build_location "#{@config.image_directory_name}/#{id}"
48
45
  end
49
46
 
50
-
51
47
  def get_data_path(data)
52
- data['@id'].gsub(@config.base_url,@config.output_dir)
48
+ data['@id'].gsub(@config.base_url, @config.output_dir)
53
49
  end
54
50
 
55
51
  def save_to_disk(data)
56
52
  path = get_data_path(data)
57
- data["@context"] ||= IiifS3::PRESENTATION_CONTEXT
53
+ data['@context'] ||= WaxIiif::PRESENTATION_CONTEXT
58
54
  puts "writing #{path}" if @config.verbose?
59
- FileUtils::mkdir_p File.dirname(path)
60
- File.open(path, "w") do |file|
61
- file.puts JSON.pretty_generate(data)
55
+ FileUtils.mkdir_p File.dirname(path)
56
+ File.open(path, 'w') do |file|
57
+ file.puts JSON.pretty_generate(data)
62
58
  end
63
- add_file_to_s3(path) if @config.upload_to_s3
64
59
  end
65
60
 
66
- def get_s3_key(filename)
67
- key = filename.gsub(@config.output_dir,"")
68
- key = key[1..-1] if key[0] == "/"
69
- end
70
-
71
- def add_file_to_s3(filename)
72
- key = get_s3_key(filename)
73
- if File.extname(filename) == ".json" || File.extname(filename) == ""
74
- @config.s3.add_json(key,filename)
75
- elsif File.extname(filename) == ".jpg"
76
- @config.s3.add_image(key,filename)
77
- else
78
- raise "Cannot identify file type!"
79
- end
80
- end
81
-
82
- def add_default_redirect(filename)
83
- key = filename.gsub(@config.output_dir,"")
84
- key = key[1..-1] if key[0] == "/"
85
-
86
- name_key = key.split(".")[0..-2].join(".")
87
-
88
- unless key == name_key
89
- key = "#{@config.base_url}/#{key}"
90
- puts "adding redirect from #{name_key} to #{key}" if @config.verbose?
91
- @config.s3.add_redirect(name_key, key)
92
- end
61
+ def escape_yaml(str)
62
+ str.gsub(/\A---(.|\n)*?---/, '')
93
63
  end
94
64
  end
95
65
  end
96
- end
66
+ end