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
@@ -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