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
data/Guardfile DELETED
@@ -1,10 +0,0 @@
1
- clearing :on
2
-
3
-
4
- # Run the test suite
5
- guard :rspec, cmd: 'bundle exec rspec' do
6
- watch(%r{^spec/.+_spec\.rb$})
7
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8
- watch('spec/spec_helper.rb') { "spec" }
9
- end
10
-
data/Rakefile DELETED
@@ -1,24 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require 'yard'
4
-
5
- RSpec::Core::RakeTask.new(:spec)
6
-
7
- YARD::Rake::YardocTask.new do |t|
8
- t.files = ['lib/**/*.rb'] # optional
9
- t.stats_options = ['--list-undoc'] # optional
10
- end
11
-
12
- desc "Clear the screen"
13
- task :cls do
14
- puts "Clearing the Screen \033c"
15
- end
16
-
17
- task :full do
18
- ENV["TEST_INTERNET_CONNECTIVITY"] = "yes"
19
- ENV["SKIP_EXPENSIVE_TESTS"] = nil
20
- end
21
-
22
- task :default => [:cls, :spec, :yard]
23
-
24
- task :full_test => [:full, :default]
@@ -1,95 +0,0 @@
1
- require_relative "utilities"
2
- module IiifS3
3
-
4
- # Module BaseProperties provides the set of properties that are shared across
5
- # all IIIF types. It is not a comprehensive list (yet), but it does handle
6
- # most of the shared types. It does not include any types that have class-based
7
- # restrictions.
8
- #
9
- # It also performs some basic sanity checking on (some of) the fields, and
10
- # provides utility classes that are applicable across the fields.
11
- #
12
- # @todo Add within, service, seeAlso, viewingHint fields
13
- #
14
- # @author David Newbury <david.newbury@gmail.com>
15
- #
16
- module BaseProperties
17
- include Utilities::Helpers
18
-
19
-
20
- # @!attribute [rw] label
21
- # @return [String] The human-readable label for this record
22
- attr_accessor :label
23
- # @!attribute [r] id
24
- # @return [String] The URI for this record
25
- attr_reader :id
26
- # @!attribute [rw] description
27
- # @return [String] The long-form description of this record
28
- attr_accessor :description
29
- # @!attribute [rw] metadata
30
- # @return [Hash] A set of key/value pairs describing additional metadata for the object.
31
- attr_accessor :metadata
32
- # @!attribute [rw] attribution
33
- # @return [String] a human-readable label, typically used for attribution or credit.
34
- attr_accessor :attribution
35
- # @!attribute [rw] logo
36
- # @return [String] The URI to an image for the logo of the institution associated with this record.
37
- attr_accessor :logo
38
- # @!attribute [rw] license
39
- # @return [String] The URI to a resource that describes the license or rights statement associated.
40
- attr_accessor :license
41
- # @!attribute [rw] related
42
- # @return [String, Array<String>] The URI to related resources. Can be both a string or an array
43
- attr_accessor :related
44
-
45
- # The type of resource provided by this record.
46
- #
47
- # @return [String] The type of record
48
- def type
49
- self.class::TYPE
50
- end
51
-
52
- # Set the unique id for this record.
53
- # This will automatically append the defined prefixes and suffixes.
54
- #
55
- # @param [String] _id The unique portion of this ID
56
- # @return [string] The URI for this record
57
- def id=(_id)
58
- @id = generate_id(_id)
59
- end
60
-
61
- # Return the base data structure for this record as a Hash
62
- # This will be in IIIF format, and should convert to JSON as JSON-LD nicely.
63
- #
64
- # @return [Hash] The base properties of this record
65
- def base_properties
66
- obj = {
67
- "@context" => PRESENTATION_CONTEXT,
68
- "@id" => self.id,
69
- "@type" => self.type,
70
- "label" => self.label
71
- }
72
- obj["attribution"] = self.attribution if self.attribution
73
- obj["logo"] = self.logo if self.logo
74
- obj["description"] = self.description if self.description
75
- obj["attribution"] = self.attribution if self.attribution
76
- obj["license"] = self.license if self.license
77
- obj["related"] = self.related if self.related
78
- obj["metadata"] = self.metadata if self.metadata
79
-
80
- obj
81
- end
82
-
83
- # Save the JSON representation of this record to disk and to S3 (if enabled).
84
- #
85
- # @return [Void]
86
- def save
87
- save_to_disk(JSON.parse(self.to_json))
88
- end
89
-
90
-
91
-
92
- protected
93
-
94
- end
95
- end
@@ -1,241 +0,0 @@
1
- require_relative "utilities"
2
- require 'pathname'
3
-
4
- module IiifS3
5
- class Builder
6
-
7
- include Utilities::Helpers
8
-
9
- HEADER_VAL = 'filename'
10
-
11
- #
12
- # @!attribute [r] data
13
- # @return [Array<Hash>] The raw data computed for the given set of images
14
- attr_reader :data
15
-
16
- #
17
- # @!attribute [r] manifests
18
- # @return [Array<Hash>] The manifest hashes for this configuration
19
- attr_accessor :manifests
20
-
21
- # @!attribute [r] config
22
- # @return [IiifS3::Config] The configuration object
23
- attr_reader :config
24
-
25
- # Initialize the builder.
26
- #
27
- # @param [Hash] config an optional configuration object.
28
- # @see IiifS3::Config
29
- # @return [Void]
30
- #
31
- def initialize(config = {})
32
- @manifests = []
33
- @config = IiifS3::Config.new(config)
34
- end
35
-
36
-
37
- #
38
- # Load data into the IIIF builder.
39
- #
40
- # This will load the data, perform some basic verifications on it, and sort
41
- # it into proper order.
42
- #
43
- # @param [Array<ImageRecord>, ImageRecord] data
44
- # Either a single ImageRecord or an Array of ImageRecords.
45
- # @raise [IiifS3::Error::InvalidImageData] if any of the data does
46
- # not pass the validation checks
47
- #
48
- # @return [Void]
49
- #
50
- def load(data)
51
- @data = [data].flatten # handle hashes and arrays of hashes
52
-
53
- # validate
54
- @data.each do |image_record|
55
- raise IiifS3::Error::InvalidImageData, "Image record #{image_record.inspect} is not an ImageRecord" unless image_record.is_a? ImageRecord
56
- raise IiifS3::Error::InvalidImageData, "Image record #{image_record.inspect} does not have an ID and/or a page number" if image_record.id.nil? || image_record.page_number.nil?
57
- end
58
- end
59
-
60
-
61
- #
62
- # Take the loaded data and generate all the files.
63
- #
64
- # @param [Boolean] force_image_generation Generate images even if they already exist
65
- #
66
- # @return [Void]
67
- #
68
- def process_data(force_image_generation=false)
69
- return nil if @data.nil? # do nothing without data.
70
- @manifests = []
71
-
72
- resources = {}
73
- @data.each do |image_record|
74
-
75
- # image generation
76
- #
77
- # It attempts to load the info files and skip generation — not currently working.
78
- info_file = image_info_file_name(image_record)
79
- if (File.exist?(info_file) && !force_image_generation)
80
- puts "skipping #{info_file}" if @config.verbose?
81
- image_record.variants = load_variants(info_file)
82
- else
83
- image_record.variants = generate_variants(image_record, @config)
84
- generate_tiles(image_record, @config)
85
- generate_image_json(image_record, @config)
86
- end
87
- # Save the image info for the manifest
88
- resources[image_record.id] ||= []
89
- resources[image_record.id].push image_record
90
- end
91
-
92
- # Generate the manifests
93
- resources.each do |key, val|
94
- manifests.push generate_manifest(val, @config)
95
- end
96
-
97
- generate_collection
98
- end
99
-
100
- def generate_collection(label="top")
101
- collection = Collection.new(label,@config)
102
- manifests.each{|m| collection.add_manifest(m)}
103
- collection.save
104
- end
105
-
106
- # Creates the required directories for exporting to the file system.
107
- #
108
- # @return [Void]
109
- def create_build_directories
110
- root_dir = generate_build_location("")
111
- Dir.mkdir root_dir unless Dir.exists?(root_dir)
112
- img_dir = generate_image_location("","").split("/")[0...-1].join("/")
113
- Dir.mkdir img_dir unless Dir.exists?(img_dir)
114
- end
115
-
116
- # Load data into the IIIF server from a CSV
117
- #
118
- # @param [String] csv_path Path to the CSV file containing the image data
119
- #
120
- # @return [Void]
121
- # @todo Fix this to use the correct data format!
122
- #
123
- def load_csv(csv_path)
124
- raise Error::InvalidCSV unless File.exist? csv_path
125
- begin
126
- vals = CSV.read(csv_path)
127
- rescue CSV::MalformedCSVError
128
- raise Error::InvalidCSV
129
- end
130
-
131
- raise Error::BlankCSV if vals.length == 0
132
- raise Error::InvalidCSV if vals[0].length != 3
133
-
134
- # remove optional header
135
- vals.shift if vals[0][0] == HEADER_VAL
136
-
137
- @data = vals.collect do |data|
138
- {
139
- "image_path" => data[0],
140
- "id" => data[1],
141
- "label" => data[2]
142
- }
143
- end
144
- end
145
-
146
- protected
147
-
148
- #----------------------------------------------------------------
149
- def load_variants(path)
150
-
151
- data = JSON.parse File.read(path)
152
- id = data["@id"]
153
- w = data["width"]
154
- h = data["height"]
155
- thumb_size = data["sizes"].find{|a| a["width"] == config.thumbnail_size || a["height"] == config.thumbnail_size}
156
- thumb_w = thumb_size["width"]
157
- thumb_h = thumb_size["height"]
158
- full_url = "#{id}/full/full/0/default.jpg"
159
- thumb_url = "#{id}/full/#{thumb_w},/0/default.jpg"
160
- full = FakeImageVariant.new( id,w, h,full_url, "image/jpeg")
161
- thumbnail = FakeImageVariant.new( id, thumb_w, thumb_h, thumb_url, "image/jpeg")
162
- return {"full" => full, "thumbnail" => thumbnail}
163
- end
164
-
165
- def generate_tiles(data, config)
166
- width = data.variants["full"].width
167
- tile_width = config.tile_width
168
- height = data.variants["full"].height
169
- tiles = []
170
- config.tile_scale_factors.each do |s|
171
- (0..(height*1.0/(tile_width*s)).floor).each do |tileY|
172
- (0..(width*1.0/(tile_width*s)).floor).each do |tileX|
173
- tile = {
174
- scale_factor: s,
175
- xpos: tileX,
176
- ypos: tileY,
177
- x: tileX * tile_width * s,
178
- y: tileY * tile_width * s,
179
- width: tile_width * s,
180
- height: tile_width * s,
181
- xSize: tile_width,
182
- ySize: tile_width
183
- }
184
- if (tile[:x] + tile[:width] > width)
185
- tile[:width] = width - tile[:x]
186
- tile[:xSize] = (tile[:width]/(s*1.0)).ceil
187
- end
188
- if (tile[:y] + tile[:height] > height)
189
- tile[:height] = height - tile[:y]
190
- tile[:ySize] = (tile[:height]/(s*1.0)).ceil
191
- end
192
- tiles.push(tile)
193
- end
194
- end
195
- end
196
- tiles.each do |tile|
197
- ImageTile.new(data, @config, tile)
198
- end
199
- end
200
-
201
- def image_info_file_name(data)
202
- "#{generate_image_location(data.id,data.page_number)}/info.json"
203
- end
204
-
205
- def generate_image_json(data, config)
206
- filename = image_info_file_name(data)
207
- info = ImageInfo.new(data.variants["full"].id, data.variants ,config.tile_width, config.tile_scale_factors)
208
-
209
- puts "writing #{filename}" if config.verbose?
210
- Pathname.new(Pathname.new(filename).dirname).mkpath
211
- File.open(filename, "w") do |file|
212
- file.puts info.to_json
213
- end
214
- if @config.upload_to_s3
215
- add_file_to_s3(filename)
216
- add_default_redirect(filename)
217
- end
218
- return info
219
- end
220
-
221
-
222
- def generate_manifest(data, config)
223
- m = Manifest.new(data, config)
224
- m.save_all_files_to_disk
225
- return m
226
- end
227
-
228
-
229
- def generate_variants(data, config)
230
- obj = {
231
- "full" => FullImage.new(data, config),
232
- "thumbnail" => Thumbnail.new(data, config)
233
- }
234
-
235
- config.variants.each do |key,image_size|
236
- obj[key] = ImageVariant.new(data, config, image_size)
237
- end
238
- return obj
239
- end
240
- end
241
- end
@@ -1,61 +0,0 @@
1
- module IiifS3
2
-
3
- #
4
- # Class Collection is an abstraction over the IIIF Collection, which is an aggregation
5
- # of IIIF manifests.
6
- #
7
- # @author David Newbury <david.newbury@gmail.com>
8
- #
9
- class Collection
10
-
11
- # @return [String] The IIIF Type for collections
12
- TYPE = "sc:Collection"
13
-
14
- include BaseProperties
15
- attr_reader :collections, :manifests
16
-
17
- def initialize(label, config, name="top")
18
- raise IiifS3::Error::MissingCollectionName if label.nil? || label.empty?
19
- @config = config
20
- @manifests = []
21
- @collections = []
22
- self.label = label
23
- self.id = "collection/#{name}"
24
- end
25
-
26
- def add_collection(collection)
27
- raise IiifS3::Error::NotACollection unless collection.respond_to?(:type) && collection.type == Collection::TYPE
28
- @collections.push(collection)
29
- end
30
-
31
- def add_manifest(manifest)
32
- raise IiifS3::Error::NotAManifest unless manifest.respond_to?(:type) && manifest.type == Manifest::TYPE
33
- @manifests.push(manifest)
34
- end
35
-
36
- # The JSON representation of this collection in the IIIF-expected format
37
- #
38
- #
39
- # @return [String] The JSON representation as a string
40
- #
41
- def to_json
42
- obj = base_properties
43
- obj["collections"] = collect_object(collections) unless collections.empty?
44
- obj["manifests"] = collect_object(manifests) unless manifests.empty?
45
- JSON.pretty_generate obj
46
- end
47
-
48
- protected
49
-
50
- def collect_object(things)
51
- things.collect do |thing|
52
- {
53
- "@id" => thing.id,
54
- "@type" => thing.type,
55
- "label" => thing.label
56
- }
57
- end
58
- end
59
- end
60
- end
61
-
@@ -1,96 +0,0 @@
1
- module IiifS3
2
-
3
- #
4
- # Class ImageInfo is a data object for the JSON representation of the image.
5
- #
6
- # It is designed to support the http://iiif.io/api/image/2.0/#image-information spec.
7
- class ImageInfo
8
-
9
- attr_accessor :id
10
- attr_accessor :width
11
- attr_accessor :height
12
- attr_accessor :tile_width
13
- attr_accessor :tile_scale_factors
14
-
15
- def initialize(uri, variants, tile_width= nil, tile_scale_factors = nil)
16
-
17
- raise IiifS3::Error::InvalidImageData, "No full variant provided: variants: #{variants}" unless variants["full"]
18
- raise IiifS3::Error::InvalidImageData, "No thumbnail variant provided: variants: #{variants}" unless variants["thumbnail"]
19
- raise IiifS3::Error::InvalidImageData, "No URI was provided for this image!" if uri.nil?
20
-
21
- @id = uri
22
- full = variants["full"]
23
- @variants = variants
24
- @width = full.width
25
- @height = full.height
26
- @tile_width = tile_width
27
- @tile_scale_factors = tile_scale_factors
28
- end
29
-
30
- # @return [Hash] a collection of valid sizes based on the available image variants
31
- #
32
- def sizes
33
- @variants.collect do |name,obj|
34
- {"width" => obj.width, "height" => obj.height}
35
- end
36
- end
37
-
38
- # The hash of tile information, or nil if the information does not exist.
39
- #
40
- #
41
- # @return [Hash, nil] A hash of the tile metadata properly formatted for IIIF JSON.
42
- #
43
- def tiles
44
- return nil if @tile_scale_factors.nil? || @tile_scale_factors.empty?
45
-
46
- return [{
47
- "width" => @tile_width,
48
- "scaleFactors" => @tile_scale_factors
49
- }]
50
- end
51
-
52
-
53
- # Generate the JSON data for this image in the IIIF-expected format.
54
- #
55
- #
56
- # @return [String] the JSON representation of this image
57
- #
58
- def to_json
59
- obj = {
60
- "@context" => context,
61
- "@id" => URI.escape(id),
62
- "protocol" => protocol,
63
- "width" => width,
64
- "height" => height,
65
- "sizes" => sizes,
66
- "profile" => profile,
67
- }
68
- obj["tiles"] = tiles unless tiles.nil?
69
- obj["profile"] = profile
70
- obj["service"] = service unless service.nil?
71
- JSON.pretty_generate obj
72
- end
73
-
74
- # @return [String] The IIIF context for this image
75
- def context
76
- IiifS3::IMAGE_CONTEXT
77
- end
78
-
79
- # @return [String] The IIIF protocol for this image
80
- def protocol
81
- IiifS3::IMAGE_PROTOCOL
82
- end
83
-
84
- # @return [String] The IIIF profile this image supports
85
- def profile
86
- [IiifS3::LEVEL_0,{
87
- supports: ["cors","sizeByWhListed", "baseUriRedirect"]
88
- }]
89
- end
90
-
91
- # TODO: Implement this. See <http://iiif.io/api/annex/services/#physical-dimensions>
92
- def service
93
- return nil
94
- end
95
- end
96
- end