wax_iiif 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +10 -0
  7. data/LICENSE.md +30 -0
  8. data/README.md +8 -0
  9. data/Rakefile +24 -0
  10. data/lib/iiif_s3/base_properties.rb +95 -0
  11. data/lib/iiif_s3/builder.rb +254 -0
  12. data/lib/iiif_s3/collection.rb +61 -0
  13. data/lib/iiif_s3/config.rb +142 -0
  14. data/lib/iiif_s3/errors.rb +37 -0
  15. data/lib/iiif_s3/full_image.rb +20 -0
  16. data/lib/iiif_s3/image_info.rb +96 -0
  17. data/lib/iiif_s3/image_record.rb +141 -0
  18. data/lib/iiif_s3/image_tile.rb +46 -0
  19. data/lib/iiif_s3/image_variant.rb +126 -0
  20. data/lib/iiif_s3/manifest.rb +151 -0
  21. data/lib/iiif_s3/thumbnail.rb +35 -0
  22. data/lib/iiif_s3/utilities.rb +12 -0
  23. data/lib/iiif_s3/utilities/helpers.rb +96 -0
  24. data/lib/iiif_s3/utilities/pdf_splitter.rb +50 -0
  25. data/lib/iiif_s3/version.rb +5 -0
  26. data/lib/wax_iiif.rb +83 -0
  27. data/spec/base_properties_spec.rb +22 -0
  28. data/spec/data/blank.csv +0 -0
  29. data/spec/data/invalid.csv +1 -0
  30. data/spec/data/no_header.csv +1 -0
  31. data/spec/data/test.csv +2 -0
  32. data/spec/data/test.jpg +0 -0
  33. data/spec/data/test.pdf +0 -0
  34. data/spec/iiif_s3/builder_spec.rb +152 -0
  35. data/spec/iiif_s3/collection_spec.rb +68 -0
  36. data/spec/iiif_s3/config_spec.rb +15 -0
  37. data/spec/iiif_s3/image_info_spec.rb +57 -0
  38. data/spec/iiif_s3/image_record_spec.rb +96 -0
  39. data/spec/iiif_s3/image_variant_spec.rb +71 -0
  40. data/spec/iiif_s3/manifest_spec.rb +97 -0
  41. data/spec/iiif_s3/utilities/pdf_splitter_spec.rb +17 -0
  42. data/spec/shared_contexts.rb +115 -0
  43. data/spec/spec_helper.rb +12 -0
  44. data/test.rb +77 -0
  45. data/wax_iiif.gemspec +29 -0
  46. metadata +218 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 25b432a185e5f5ee4cd830eb7bb65ed7306ae44bc48d392efeba1b501425470a
4
+ data.tar.gz: fbb6291fecd4e872ec20ac7471cf7078a7a4cd4772077d7f1a93368bf145133f
5
+ SHA512:
6
+ metadata.gz: 133ae808b98aa7d4ac1de14f266243f53e849ad81b355718faf5b0bde9bbac04e598475e1f00366a8acc96d2c85f3f170a7d576ee9a1c3fbfe62864f0985d7e2
7
+ data.tar.gz: 2ff7691fde04e30fc5be5402aaa8b56cd64b0dcfecbe0cf3f0044b7a5de014821d48f3c39e5f213bd2935dddb0914eb16c5eba7e49d8c17b1c2058682418099c
data/.gitignore ADDED
@@ -0,0 +1,29 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+
24
+ .env
25
+
26
+ build/*
27
+ data/*
28
+
29
+ !/*/.gitkeep
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
4
+ - 2.3
5
+ - 2.4
6
+ addons:
7
+ apt:
8
+ packages:
9
+ - ghostscript
10
+ script:
11
+ - bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in wax_iiif.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
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/LICENSE.md ADDED
@@ -0,0 +1,30 @@
1
+ Copyright (c) 2018 Marii Nyröp.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6
+
7
+ _This project incorporates code covered by the following terms:_
8
+
9
+ Copyright (c) 2015 Carnegie Museum of Pittsburgh
10
+
11
+ MIT License
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining
14
+ a copy of this software and associated documentation files (the
15
+ "Software"), to deal in the Software without restriction, including
16
+ without limitation the rights to use, copy, modify, merge, publish,
17
+ distribute, sublicense, and/or sell copies of the Software, and to
18
+ permit persons to whom the Software is furnished to do so, subject to
19
+ the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be
22
+ included in all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # wax_iiif
2
+ [![Build Status](https://travis-ci.org/mnyrop/wax_iiif.svg?branch=master)](https://travis-ci.org/mnyrop/wax_iiif) [![Dependency Status](https://gemnasium.com/badges/github.com/mnyrop/wax_iiif.svg)](https://gemnasium.com/github.com/mnyrop/wax_iiif) [![Maintainability](https://api.codeclimate.com/v1/badges/3eb2fcf5ae47e7b6d686/maintainability)](https://codeclimate.com/github/mnyrop/wax_iiif/maintainability)
3
+
4
+ This fork is TRULY just a copy of the [iiif_s3 gem](https://github.com/cmoa/iiif_s3) with all the s3 dependencies and functionality removed!
5
+
6
+ It enables the static creation of IIIF derivatives (level 0) for static exhibtion sites with [wax](https://minicomp.github.io/wax/).
7
+
8
+ For credit and documentation, head over to [iiif_s3](https://github.com/cmoa/iiif_s3).
data/Rakefile ADDED
@@ -0,0 +1,24 @@
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]
@@ -0,0 +1,95 @@
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
@@ -0,0 +1,254 @@
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
+ def build_a_manifest
229
+
230
+ manifest_uri = "@config.s3.bucket/#{generate_id(record)}/manifest.json"
231
+ # response = Typhoeus.get(manifest_uri, followlocation: true)
232
+ # if response.code == 200
233
+ # puts "Skipping #{file}—Manifest already exists." if verbose
234
+ # data = JSON.parse(response.body)
235
+ # obj = IiifS3::FakeManifest.new(data["@id"], data["@type"], data["label"])
236
+ # @iiif.manifests.push(obj)
237
+ # next
238
+ # end
239
+ end
240
+
241
+
242
+ def generate_variants(data, config)
243
+ obj = {
244
+ "full" => FullImage.new(data, config),
245
+ "thumbnail" => Thumbnail.new(data, config)
246
+ }
247
+
248
+ config.variants.each do |key,image_size|
249
+ obj[key] = ImageVariant.new(data, config, image_size, image_size)
250
+ end
251
+ return obj
252
+ end
253
+ end
254
+ end