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,50 @@
1
+ require "mini_magick"
2
+
3
+ module IiifS3
4
+ module Utilities
5
+
6
+ #
7
+ # Class PdfSplitter is a utility function designed to convert a PDF into a stack of images.
8
+ #
9
+ # @author David Newbury <david.newbury@gmail.com>
10
+ #
11
+ class PdfSplitter
12
+ # Convert a PDF File into a series of JPEG images, one per-page
13
+ #
14
+ # @param [String] path The path to the PDF file
15
+ # @param [Hash] options The configuration hash
16
+ #
17
+ # @return [Array<String>] The paths to the generated files
18
+ #
19
+ def self.split(path, options={})
20
+
21
+ output_dir = options.fetch(:output_dir, ".")
22
+ verbose = options.fetch(:verbose, false)
23
+ puts "processing #{path}" if verbose
24
+ name = File.basename(path, File.extname(path))
25
+
26
+ pages = []
27
+
28
+ pdf = MiniMagick::Image.open(path)
29
+
30
+ pdf.pages.each_with_index do |page, index|
31
+ page_file_name = "#{output_dir}/#{name}_#{index+1}.jpg"
32
+
33
+ MiniMagick::Tool::Convert.new do |convert|
34
+ convert.density("300")
35
+ convert.units("PixelsPerInch")
36
+ convert << page.path
37
+ convert.quality("80")
38
+ convert.colorspace("sRGB")
39
+ convert.interlace("none")
40
+ convert.flatten
41
+ convert << page_file_name
42
+ end
43
+ pages.push(page_file_name)
44
+ end
45
+ GC.start
46
+ pages
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ module IiifS3
2
+ #
3
+ # @return [String] The current library version
4
+ VERSION = "0.1.0"
5
+ end
data/lib/wax_iiif.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'csv'
2
+ require 'json'
3
+
4
+ require "iiif_s3/version"
5
+ require "iiif_s3/errors"
6
+ require "iiif_s3/base_properties"
7
+ require "iiif_s3/image_record"
8
+ require "iiif_s3/builder"
9
+ require "iiif_s3/manifest"
10
+ require "iiif_s3/config"
11
+ require "iiif_s3/collection"
12
+ require "iiif_s3/image_variant"
13
+ require "iiif_s3/thumbnail"
14
+ require "iiif_s3/image_tile"
15
+ require "iiif_s3/full_image"
16
+ require "iiif_s3/image_info"
17
+ require "iiif_s3/utilities"
18
+
19
+ # Module IiifS3 is a tool for generating IIIF resources from a set of files.
20
+ # It's designed to support the IIIF level 0 profile, and generates entirely static files.
21
+ #
22
+ # @author David Newbury <david.newbury@gmail.com>
23
+ #
24
+ module IiifS3
25
+
26
+
27
+ #--------------------------------------------------------------------------
28
+ # CONSTANTS
29
+ #--------------------------------------------------------------------------
30
+
31
+
32
+ # @return [String] The URI of the presentation context for the IIIF V.2
33
+ PRESENTATION_CONTEXT = "http://iiif.io/api/presentation/2/context.json"
34
+ # @return [String] The URI of the image context for the IIIF V.2
35
+ IMAGE_CONTEXT = "http://iiif.io/api/image/2/context.json"
36
+ # @return [String] The URI of the image protocol for IIIF
37
+ IMAGE_PROTOCOL = "http://iiif.io/api/image"
38
+ # @return [String] The URI of the Level 0 profile for the IIIF V.2
39
+ LEVEL_0 = "http://iiif.io/api/image/2/level0.json"
40
+ # @return [String] The IIIF default type for a sequence.
41
+ SEQUENCE_TYPE = "sc:Sequence"
42
+ # @return [String] The IIIF default type for a canvas
43
+ CANVAS_TYPE = "sc:Canvas"
44
+ # @return [String] The IIIF default type for a annotation.
45
+ ANNOTATION_TYPE = "oa:Annotation"
46
+ # @return [String] The IIIF default type for an image.
47
+ IMAGE_TYPE = "dcterms:Image"
48
+ # @return [String] The default label for a canvas without a specified name.
49
+ MOTIVATION = "sc:painting"
50
+ # @return [String] The default label for a canvas without a specified name.
51
+ DEFAULT_CANVAS_LABEL = "front"
52
+ # @return [String] The default name for a sequence without a specified name.
53
+ DEFAULT_SEQUENCE_NAME = "default"
54
+ # @return [String] The default reading direction for this manifest.
55
+ DEFAULT_VIEWING_DIRECTION = "left-to-right"
56
+ # @return [Number] The size in pixels below which the canvas will be doubled.
57
+ MIN_CANVAS_SIZE = 1200
58
+
59
+
60
+ #--------------------------------------------------------------------------
61
+ # HELPERS
62
+ #--------------------------------------------------------------------------
63
+
64
+
65
+
66
+ # Validates a viewing direction string against the IIIF V.2.0 spec.
67
+ #
68
+ # According to v2 of the IIIF standards, there are only four valid viewing directions:
69
+ # "left-to-right", "top-to-bottom”, "bottom-to-top" , and "right-to-left". This
70
+ # returns true if the provided direction is one of these, and falst for anything else.
71
+ #
72
+ # @param [String] direction A viewing direction string
73
+ #
74
+ # @return [boolean] Is the provided string a valid viewing direction?
75
+ #
76
+ def self.is_valid_viewing_direction(direction)
77
+ direction == "left-to-right" ||
78
+ direction == "top-to-bottom" ||
79
+ direction == "bottom-to-top" ||
80
+ direction == "right-to-left"
81
+ end
82
+
83
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples "base properties" do
4
+ it "has a description" do
5
+ test_string = "test description"
6
+ @object.description = test_string
7
+ expect(@object.description).to eq test_string
8
+ end
9
+ it "has an @id" do
10
+ expect(@object.id).not_to be_nil
11
+ end
12
+ it "has a valid @id" do
13
+ expect(@object.id).to include IiifS3::Config::DEFAULT_URL
14
+ end
15
+ it "encodes @ids when set" do
16
+ @object.id = "http://www.example.com/a space"
17
+ expect(@object.id).to include "%20"
18
+ end
19
+ it "reveals the type" do
20
+ expect(@object.type).to eq described_class::TYPE
21
+ end
22
+ end
File without changes
@@ -0,0 +1 @@
1
+ I'm an invalid CSV file "
@@ -0,0 +1 @@
1
+ spec/data/test.jpg,1,"Test Image #1"
@@ -0,0 +1,2 @@
1
+ filename,identifier,name
2
+ spec/data/test.jpg,1,"Test Image #1"
Binary file
Binary file
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+ require 'fileutils'
4
+
5
+ describe IiifS3::Builder do
6
+ # before(:each) do
7
+ # fake_aws_bucket = OpenStruct.new({
8
+ # "exists?" => "true"
9
+ # })
10
+ # unless ENV["TEST_INTERNET_CONNECTIVITY"]
11
+ # allow(Aws::S3::Bucket).to receive(:new) {nil}
12
+ # allow(Aws::S3::Bucket).to receive(:exists?) {true}
13
+ # end
14
+ # end
15
+
16
+ let (:iiif) { IiifS3::Builder.new() }
17
+ let (:test_object_0) {ImageRecord.new({"id" => 1, "page_number" => 1})}
18
+ let (:test_object_1) {ImageRecord.new({"id" => 2, "page_number" => 1})}
19
+ let (:test_object_2) {ImageRecord.new({"id" => 2, "page_number" => 2})}
20
+ let (:data) {[test_object_0, test_object_1,test_object_2]}
21
+
22
+ context "When initializing" do
23
+ it "generates manifests" do
24
+ expect(iiif.manifests).to eq(Array.new)
25
+ end
26
+ it "uses the default config" do
27
+ expect(iiif.config).to eq(IiifS3::Config.new)
28
+ end
29
+ it "will accept a configuration hash" do
30
+ opts = {tile_width: 99}
31
+ iiif2 = IiifS3::Builder.new(opts)
32
+ expect(iiif2.config.tile_width).to eq(99)
33
+ end
34
+ end
35
+
36
+ context "loading data" do
37
+ it "will accept an array of objects" do
38
+ iiif.load(data)
39
+ expect(iiif.data).to eq(data)
40
+ end
41
+ it "will accept a single object" do
42
+ iiif.load(test_object_1)
43
+ expect(iiif.data).to eq([test_object_1])
44
+ end
45
+
46
+ it "will error if the data is bad" do
47
+ expect{iiif.load({random: "hash"})}.to raise_error(IiifS3::Error::InvalidImageData)
48
+ expect{iiif.load([{random: "hash"}])}.to raise_error(IiifS3::Error::InvalidImageData)
49
+ end
50
+ end
51
+
52
+ context "when processing data" do
53
+ include_context("fake variants")
54
+ include_context("fake data")
55
+
56
+ before(:example) do
57
+ @iiif = IiifS3::Builder.new({base_url: 'http://0.0.0.0', verbose: true, thumbnail_size: 120})
58
+ @iiif.load(@fake_data)
59
+ allow(@iiif).to receive(:generate_tiles) {nil}
60
+ allow(@iiif).to receive(:generate_variants) {@fake_variants}
61
+
62
+ end
63
+ it "does not fail with no data" do
64
+ expect {iiif.process_data}.not_to raise_error
65
+ end
66
+
67
+ it "does not fail with real data" do
68
+ expect {@iiif.process_data}.not_to raise_error
69
+ end
70
+
71
+ it " passes the Temporary Manifest Check" do
72
+ @iiif.process_data
73
+ expect(@iiif.manifests.count).to eq 1
74
+ expect(@iiif.manifests.first.to_json).to eq @fake_manifest
75
+ end
76
+ end
77
+
78
+
79
+ context "when dealing with already loaded data" do
80
+ include_context("fake data")
81
+ include_context("fake variants")
82
+
83
+ before(:example) do
84
+ @dir = Dir.mktmpdir
85
+ @iiif = IiifS3::Builder.new({output_dir: @dir, base_url: 'http://0.0.0.0', verbose: true, thumbnail_size: 120})
86
+ @iiif.load(@fake_data)
87
+ @info_json = "#{@dir}/images/1-1/info.json"
88
+ allow(@iiif).to receive(:generate_tiles) {nil}
89
+ allow(@iiif).to receive(:generate_variants) {@fake_variants}
90
+ @iiif.process_data
91
+ end
92
+
93
+ after(:example) do
94
+ FileUtils.remove_entry @dir
95
+ end
96
+
97
+ it "has the temporary file" do
98
+ expect(File.exist?(@info_json)).to eq true
99
+ end
100
+
101
+ it "does try to generate images if that file is missing" do
102
+ File.delete(@info_json)
103
+ @iiif.process_data
104
+ expect(@iiif).to have_received(:generate_tiles).twice
105
+ expect(@iiif).to have_received(:generate_variants).twice
106
+ end
107
+
108
+ it "does not try to generate images if that file is present" do
109
+ @iiif.process_data
110
+ expect(@iiif).to have_received(:generate_tiles).once
111
+ expect(@iiif).to have_received(:generate_variants).once
112
+ end
113
+
114
+ it "generates the correct manifest anyway" do
115
+ @iiif.process_data
116
+ expect(@iiif.manifests.count).to eq 1
117
+ expect(@iiif.manifests.first.to_json).to eq @fake_manifest
118
+ end
119
+
120
+ end
121
+
122
+ context "When load_csving CSV files" do
123
+ it "accepts a path" do
124
+ expect{iiif.load_csv('./spec/data/test.csv')}.not_to raise_error()
125
+ end
126
+ it "fails on blank CSV files" do
127
+ expect{iiif.load_csv('./spec/data/blank.csv')}.to raise_error(IiifS3::Error::BlankCSV)
128
+ end
129
+ it "fails on invalid CSV files" do
130
+ expect{iiif.load_csv('./spec/data/invalid.csv')}.to raise_error(IiifS3::Error::InvalidCSV)
131
+ end
132
+ it "fails on missing CSV files" do
133
+ expect{iiif.load_csv('./spec/data/i_dont_exist.csv')}.to raise_error(IiifS3::Error::InvalidCSV)
134
+ end
135
+ end
136
+
137
+ context "When loading a CSV file" do
138
+ it "saves the data into the @data param" do
139
+ expect(iiif.data).to be_nil
140
+ iiif.load_csv('./spec/data/test.csv')
141
+ expect(iiif.data).not_to be_nil
142
+ end
143
+ it "removes headers" do
144
+ iiif.load_csv('./spec/data/test.csv')
145
+ expect(iiif.data[0]['image_path']).to eq('spec/data/test.jpg')
146
+ end
147
+ it "doesn't remove headers if not present" do
148
+ iiif.load_csv('./spec/data/no_header.csv')
149
+ expect(iiif.data[0]['image_path']).to eq('spec/data/test.jpg')
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require "base_properties_spec"
3
+
4
+ describe IiifS3::Collection do
5
+ let (:config) {IiifS3::Config.new()}
6
+ context "base" do
7
+ before(:each) do
8
+ @object = IiifS3::Collection.new("Test data", config)
9
+ end
10
+ it_behaves_like "base properties"
11
+ end
12
+
13
+ context "initialization" do
14
+ it "initializes without issues" do
15
+ collection = nil
16
+ expect{collection = IiifS3::Collection.new("Test data", config)}.not_to raise_error
17
+ expect(collection.id).to eq("http://0.0.0.0/collection/top.json")
18
+ end
19
+ it "initializes without issues when provided a name" do
20
+ collection = nil
21
+ expect{collection = IiifS3::Collection.new("Test data", config, "name")}.not_to raise_error
22
+ expect(collection.id).to eq("http://0.0.0.0/collection/name.json")
23
+ end
24
+ it "initializes without issues when provided a name with a space in it" do
25
+ collection = nil
26
+ expect{collection = IiifS3::Collection.new("Test data", config, "name and space")}.not_to raise_error
27
+ expect(collection.id).to eq("http://0.0.0.0/collection/name%20and%20space.json")
28
+ end
29
+ it "fails if there is no label" do
30
+ expect{collection = IiifS3::Collection.new(nil, config)}.to raise_error(IiifS3::Error::MissingCollectionName)
31
+ end
32
+ it "fails if there is a blank label" do
33
+ expect{collection = IiifS3::Collection.new("", config)}.to raise_error(IiifS3::Error::MissingCollectionName)
34
+ end
35
+ it "has the correct default name" do
36
+ collection = IiifS3::Collection.new("Test data", config)
37
+ expect(collection.id).to include "top.json"
38
+ end
39
+ end
40
+ context "data init" do
41
+ include_context("fake data")
42
+ let(:collection) {IiifS3::Collection.new("Test Data", config, "name")}
43
+ let(:manifest) {IiifS3::Manifest.new([@fake_data],config)}
44
+ it "has a label" do
45
+ expect(collection.label).to eq "Test Data"
46
+ end
47
+ it "has an id" do
48
+ expect(collection.id).to eq "http://0.0.0.0/collection/name.json"
49
+ end
50
+
51
+ it "allows you to add a collection" do
52
+ newCollection = collection.clone
53
+ expect{collection.add_collection(newCollection)}.not_to raise_error
54
+ end
55
+
56
+ it "fails if you add something else to a collection" do
57
+ newCollection = {}
58
+ expect{collection.add_collection(newCollection)}.to raise_error(IiifS3::Error::NotACollection)
59
+ end
60
+
61
+
62
+ it "generates correct JSON" do
63
+ collection.add_manifest(manifest)
64
+ expect(collection.to_json).to eq @fake_collection
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe IiifS3::Config do
5
+ context "comparing" do
6
+ it "shows equal things to be equal" do
7
+ expect(IiifS3::Config.new).to eq(IiifS3::Config.new)
8
+ end
9
+ it "shows different things to be different" do
10
+ opts = {tile_width: 99, upload_to_s3: false}
11
+ opts2 = {tile_width: 100, upload_to_s3: false}
12
+ expect(IiifS3::Config.new opts).not_to eq(IiifS3::Config.new opts2)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ include IiifS3
5
+
6
+ describe IiifS3::ImageInfo do
7
+
8
+ let(:uri) {"http://www.example.com/test/1"}
9
+ include_context("fake variants")
10
+
11
+ context "intialization" do
12
+
13
+
14
+ it "initializes without errors" do
15
+ expect{ImageInfo.new(uri,@fake_variants)}.not_to raise_error
16
+ end
17
+ it "raises an error without a 'full' variant" do
18
+ @fake_variants.delete "full"
19
+ expect{ImageInfo.new(uri,@fake_variants)}.to raise_error(Error::InvalidImageData)
20
+ end
21
+ it "raises an error without a 'thumbnail' variant" do
22
+ @fake_variants.delete "thumbnail"
23
+ expect{ImageInfo.new(uri,@fake_variants)}.to raise_error(Error::InvalidImageData)
24
+ end
25
+ end
26
+
27
+
28
+ context "valid data" do
29
+ before(:example) do
30
+ @info = ImageInfo.new(uri,@fake_variants)
31
+ end
32
+
33
+ it "generates correct sizes" do
34
+ expect(@info.sizes).to eq([{"width" => 1000, "height" => 1200},{"width" => 100, "height" => 120}])
35
+ end
36
+
37
+ it "generates nil when no tile data appears" do
38
+ expect(@info.tiles).to be_nil
39
+ end
40
+
41
+ it "generates tile info when tile data appears" do
42
+ info = ImageInfo.new(uri,@fake_variants,500,[1,2,4])
43
+ expect(info.tiles).to eq([{"width" => 500, "scaleFactors" => [1,2,4]}])
44
+ end
45
+
46
+ it "has the correct other methods" do
47
+ expect(@info).to respond_to "context"
48
+ expect(@info).to respond_to "protocol"
49
+ expect(@info).to respond_to "profile"
50
+ expect(@info).to respond_to "service"
51
+ end
52
+
53
+ it "generates correct JSON" do
54
+ expect(@info.to_json).to eq(@fake_image_info)
55
+ end
56
+ end
57
+ end