wax_iiif 0.0.1

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 (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
@@ -0,0 +1,61 @@
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
+
@@ -0,0 +1,142 @@
1
+ module IiifS3
2
+
3
+ # Config provides a data structure for holding the configuration settings
4
+ # for the IiifS3 class.
5
+ #
6
+ # @author David Newbury <david.newbury@gmail.com>
7
+ #
8
+ class Config
9
+
10
+ # @return [String] The default URL to append to all IDs.
11
+ DEFAULT_URL = "http://0.0.0.0"
12
+ # @return [String] The name of the subdirectory where generated images live
13
+ DEFAULT_IMAGE_DIRECTORY_NAME = "images"
14
+ # @return [String] The default path for writing generated image files
15
+ DEFAULT_OUTPUT_DIRECTORY = "./build"
16
+ # @return [Number] The default tile width/height in pixels
17
+ DEFAULT_TILE_WIDTH = 512
18
+ # @return [Array<Number>] The default tile scaling factors
19
+ DEFAULT_TILE_SCALE_FACTORS = [1,2,4,8]
20
+ # @return [Number] The default thumbnail size in pixels
21
+ DEFAULT_THUMBNAIL_SIZE = 250
22
+
23
+ #
24
+ # @!attribute [r] base_url
25
+ # @return [String] The protocol, domain, and port used for generating URIs.
26
+ # Defaults to {IiifS3::Config::DEFAULT_URL}
27
+ attr_reader :base_url
28
+ #
29
+ # @!attribute [r] use_extensions
30
+ # @return [Boolean] Should generated IDs and files have a .json extension?
31
+ # Defaults to true
32
+ attr_reader :use_extensions
33
+ #
34
+ # @!attribute [r] output_dir
35
+ # @return [String] The directory on the local file system where the output
36
+ # files should be saved
37
+ # Defaults to {IiifS3::Config::DEFAULT_OUTPUT_DIRECTORY}
38
+ attr_reader :output_dir
39
+ #
40
+ # @!attribute [r] prefix
41
+ # @return [String] A prefix to be appended between the base URI and the id.
42
+ # Can be blank,and it will automatically prepend a slash if one is not
43
+ # provided.
44
+ # Defaults to ""
45
+ attr_reader :prefix
46
+ #
47
+ # @!attribute [r] image_directory_name
48
+ # @return [String] The name of the directory/prefix where image files will be
49
+ # located.
50
+ # Defaults to IiifS3::Config::DEFAULT_IMAGE_DIRECTORY_NAME
51
+ attr_reader :image_directory_name
52
+ #
53
+ # @!attribute [r] tile_width
54
+ # @return [Number] The width (and height) of each individual tile.
55
+ # Defaults to IiifS3::Config::DEFAULT_TILE_WIDTH
56
+ attr_reader :tile_width
57
+ #
58
+ # @!attribute [r] tile
59
+ # @return [Array<Number>] An array of tile ratios to be uploaded.
60
+ # Defaults to IiifS3::Config::DEFAULT_TILE_SCALE_FACTORS
61
+ attr_reader :tile_scale_factors
62
+ #
63
+ # @!attribute [r] variants
64
+ # @return [Hash] A Hash of key/value pairs. Each key should be the name of a variant,
65
+ # each value the maximum pixel dimension of the longest side.
66
+ # Defaults to {}
67
+ attr_reader :variants
68
+ #
69
+ # @!attribute [r] upload_to_s3
70
+ # @return [Boolean] Should the files that are created by automatically uploaded to Amazon S3?
71
+ # Defaults to false
72
+ attr_reader :upload_to_s3
73
+
74
+ # @!attribute [r] thumbnail_size
75
+ # @return [Number] The max width in pixels for a thumbnail image
76
+ attr_reader :thumbnail_size
77
+
78
+ # @!attribute [r] verbose
79
+ # @return [Bool] Should the program log information to the console?
80
+ attr_reader :verbose
81
+ alias :verbose? :verbose
82
+
83
+ # @!attribute [r] s3
84
+ # @return [IiifS3::AmazonS3] the S3 object for this system
85
+ attr_reader :s3
86
+
87
+
88
+ # Initialize a new configuration option.
89
+ #
90
+ # @param [Hash] opts
91
+ # @option opts [Boolean] :upload_to_s3 if true, images and metadata will be
92
+ # uploaded to Amazon S3. Defaults to False.
93
+ # @option opts [Number] :tile_width The width in pixels for generated tiles.
94
+ # Defaults to {DEFAULT_TILE_WIDTH}
95
+ # @option opts [Array<Number>] :tile_scale_factors An array of ratios for generated tiles.
96
+ # Defaults to {DEFAULT_TILE_SCALE_FACTORS}
97
+ # @option opts [String] :image_directory_name The name of the subdirectory for actual
98
+ # image data. Defaults to {DEFAULT_IMAGE_DIRECTORY_NAME}
99
+ # @option opts [String] :output_dir The name of the directory for generated files.
100
+ # image data. Defaults to {DEFAULT_OUTPUT_DIRECTORY}
101
+ # @option opts [String] :base_url The base URL for the generated URIs. Defaults to
102
+ # {DEFAULT_URL} if not auto-uploading to S3 and to the s3 bucket if upload_to_s3 is enabled.
103
+ # @option opts [Number] :thumbnail_size the size in pixels
104
+ # for the largest side of the thumbnail images. Defaults to {DEFAULT_THUMBNAIL_SIZE}.
105
+ # @option opts [Bool] :use_extensions (true) should files have exensions appended?
106
+ # @option opts [Bool] :verbose (false) Should debug information be printed to the console?
107
+ # @option opts [String] :prefix ("") a prefix (read: subdirectory) for the generated URIs.
108
+ # @option opts [Hash{String: String}] :variants
109
+ def initialize(opts = {})
110
+ @upload_to_s3 = opts[:upload_to_s3] || false
111
+ @s3 = IiifS3::AmazonS3.new if @upload_to_s3
112
+ @tile_width = opts[:tile_width] || DEFAULT_TILE_WIDTH
113
+ @tile_scale_factors = opts[:tile_scale_factors] || DEFAULT_TILE_SCALE_FACTORS
114
+ @image_directory_name = opts[:image_directory_name] || DEFAULT_IMAGE_DIRECTORY_NAME
115
+ @base_url = opts[:base_url] || ( @upload_to_s3 ? @s3.bucket.url : DEFAULT_URL)
116
+ @use_extensions = opts.fetch(:use_extensions, true) ## true
117
+ @output_dir = opts[:output_dir] || DEFAULT_OUTPUT_DIRECTORY
118
+ @variants = opts[:variants] || {}
119
+ @thumbnail_size = opts[:thumbnail_size] || DEFAULT_THUMBNAIL_SIZE
120
+ @verbose = opts.fetch(:verbose, false) ## false
121
+ @prefix = opts[:prefix] || ""
122
+ if @prefix.length > 0 && @prefix[0] != "/"
123
+ @prefix = "/#{@prefix}"
124
+ end
125
+ end
126
+
127
+
128
+ # Compare two configuration files
129
+ #
130
+ # @param [IiifS3::Config] other_config The configuration file to compare
131
+ #
132
+ # @return [Bool] True if they are the same, false otherwise
133
+ #
134
+ def ==(other_config)
135
+ valid = true
136
+ self.instance_variables.each do |v|
137
+ valid &&= instance_variable_get(v) == other_config.instance_variable_get(v)
138
+ end
139
+ valid
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,37 @@
1
+ module IiifS3
2
+
3
+ #
4
+ # Module Error collects standard errors for th IiifS3 library.
5
+ module Error
6
+
7
+ # Class BlankCSV indicates that a provided CSV has no data.
8
+ class BlankCSV < StandardError; end
9
+
10
+ # Class InvalidCSV indicates that there is something wrong with the provided CSV.
11
+ class InvalidCSV < StandardError; end
12
+
13
+ # Class BadAmazonCredentials indicates that something was wrong with the Amazon login information.
14
+ class BadAmazonCredentials < StandardError; end
15
+
16
+ # Class MissingCollectionName indicates that the collection provided did not have a label.
17
+ class MissingCollectionName < StandardError; end
18
+
19
+ # Class NotACollection indicates that the object provided was not a sc:Collection.
20
+ class NotACollection < StandardError; end
21
+
22
+ # Class NotAManifest indicates that the object provided was not a sc:Manifest.
23
+ class NotAManifest < StandardError; end
24
+
25
+ # Class InvalidCSV indicates that there is something wrong with the provided Image Data.
26
+ class InvalidImageData < StandardError; end
27
+
28
+ # Class InvalidViewingDirection indicates that the direction provided was not a valid viewing direction.
29
+ class InvalidViewingDirection < InvalidImageData; end
30
+
31
+ # Class MultiplePrimaryImages indicates that multiple images were tagged as primary for a given manifest.
32
+ class MultiplePrimaryImages < InvalidImageData; end
33
+
34
+ # Class NoMasterError indicates that all of the images in a collection are secondary images.
35
+ class NoMasterError < InvalidImageData; end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+
2
+ require "mini_magick"
3
+ require 'fileutils'
4
+
5
+ module IiifS3
6
+
7
+ #
8
+ # Class FullImage is a standard image variant that does not resize the image at all.
9
+ class FullImage < ImageVariant
10
+
11
+ protected
12
+
13
+ def filestring
14
+ "/full/full/0"
15
+ end
16
+
17
+ def resize(width, height); end
18
+
19
+ end
20
+ end
@@ -0,0 +1,96 @@
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
@@ -0,0 +1,141 @@
1
+ module IiifS3
2
+ # Class ImageRecord provides a data structure for a single image file.
3
+ # It contains information for content from the manifest level down to the
4
+ # specific variants of the images.
5
+ #
6
+ # It has the concept of primary images, which are the first (or only) image
7
+ # in the sequence. This is the image where much of the top-level metadata is
8
+ # taken from. Each sequence can only have a single primary image, but that
9
+ # constraint in enforced
10
+ #
11
+ # @author David Newbury <david.newbury@gmail.com>
12
+ #
13
+ class ImageRecord
14
+ attr_accessor :id
15
+ attr_accessor :label
16
+ attr_accessor :description
17
+ attr_accessor :attribution
18
+ attr_accessor :license
19
+ attr_accessor :metadata
20
+
21
+ attr_accessor :logo
22
+ attr_accessor :variants
23
+
24
+ attr_writer :page_number
25
+ attr_writer :section
26
+ attr_writer :section_label
27
+ attr_writer :is_document
28
+
29
+ # @param [Hash] opts
30
+ # @option opts [String] :id The primary ID for the object.
31
+ # @option opts [String] :label The human-readable label for all grouped records
32
+ # @option opts [String] :description A longer, human-readable description of the gropued records
33
+ # @option opts [String] :logo A URL pointing to a logo of the institution
34
+ # @option opts [Hash] :variants A hash of derivative names and sizes
35
+ # @example {thumb: 150}
36
+ def initialize(opts={})
37
+ opts.each do |key, val|
38
+ self.send("#{key}=",val) if self.methods.include? "#{key}=".to_sym
39
+ end
40
+ end
41
+
42
+ # The page number of this image. Defaults to 1.
43
+ #
44
+ # @return [Number]
45
+ def page_number
46
+ @page_number || 1
47
+ end
48
+
49
+ # The path to this image.
50
+ #
51
+ # @return [String]
52
+ def image_path
53
+ @path
54
+ end
55
+
56
+ def path=(_path)
57
+ raise IiifS3::Error::InvalidImageData, "Path is invalid: '#{_path}'" unless _path && File.exist?(_path)
58
+ @path = _path
59
+ end
60
+
61
+ # Is this image part of a document, or is it a standalone image (or image sequence)?
62
+ #
63
+ # Currently, the only effects the page viewing hint for the image sequence.
64
+ # This will only have an effect on the primary image for this sequence.
65
+ #
66
+ # @return [Bool]
67
+ #
68
+ def is_document
69
+ return !!@is_document
70
+ end
71
+ alias :is_document? :is_document
72
+
73
+ # The name of the section this image is contained in.
74
+ # Currently used to id the canvas for this image.
75
+ #
76
+ # defaults to IiifS3::DEFAULT_CANVAS_LABEL
77
+ #
78
+ # @return [String]
79
+ #
80
+ def section
81
+ @section || DEFAULT_CANVAS_LABEL
82
+ end
83
+
84
+ # The label for the section this image is contained in.
85
+ # Currently used to label the canvas for this image.
86
+ #
87
+ # defaults to IiifS3::DEFAULT_CANVAS_LABEL
88
+ #
89
+ # @return [String]
90
+ #
91
+ def section_label
92
+ @section_label || DEFAULT_CANVAS_LABEL
93
+ end
94
+
95
+ # @return [String] The prefered viewing direction for this image.
96
+ # Will default to IiifS3::DEFAULT_VIEWING_DIRECTION
97
+ #
98
+ def viewing_direction
99
+ @viewing_direction || DEFAULT_VIEWING_DIRECTION
100
+ end
101
+
102
+ def viewing_direction=(dir)
103
+ raise Error::InvalidViewingDirection unless IiifS3.is_valid_viewing_direction(dir)
104
+ @viewing_direction = dir
105
+ end
106
+
107
+ # Is this image the master image for its sequence?
108
+ #
109
+ # Each image sequence has a single image chosen as the primary image for
110
+ # the sequence. By default, page one is the master image, but another image
111
+ # could be chosen as the master if desired.
112
+ #
113
+ # This is, for instance, the image whose thumbnail is the representation for
114
+ # the entire sequence, and it defined viewing direction and other top-level
115
+ # metadata.
116
+ #
117
+ # @return [Bool]
118
+ #
119
+ def is_primary
120
+ if @is_primary.nil?
121
+ self.page_number == 1
122
+ else
123
+ @is_primary
124
+ end
125
+ end
126
+
127
+ alias :is_primary? :is_primary
128
+ alias :is_master :is_primary # Depriciated, but around for backwards compatibility
129
+
130
+
131
+ # Set this image record as the master record for the sequence
132
+ #
133
+ # @param [Bool] val Is this image the master
134
+ #
135
+ # @return [Bool]
136
+ #
137
+ def is_primary=(val)
138
+ @is_primary = !!val
139
+ end
140
+ end
141
+ end