mirador 0.0.4 → 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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- Zjc0NzZjMDljNTNiZjlhNTlhMDlhZGZmYjg1NjRjZmEzZDFiZWU2MA==
4
+ MGZmMTU1ODJkZDdiZjM3MmQ1NzA1MjJkNjkxMjUxMzM1MmMyOTQ1Yg==
5
5
  data.tar.gz: !binary |-
6
- NTg0ZDE2NzYwNmVjOTVhNmU2MDYzMzljODhiNWQzYzE2NTA4NjNhNg==
6
+ OWYyNjc4N2EzMWU1Mjc4YWUxMWJmOGU4NjNhMmFkZjRiZWUzYWYwMA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Nzg1NDIxOWExZWFjZjQ5YTg3M2YxN2IyYmMyYjZjOGI5MTQ2ODdkYWRhOTJk
10
- NzYzMTQ3MjU4ZWVkNjU5NTlhZjg0ZWIwNDhkNjhkOWFjZTkwOTk0YTlhZTE4
11
- MjRmNzc1NmQyZTAzNGMwMTUwN2E4MmU4NmU4ZGQwYjc2YzE4OWM=
9
+ Mjk2MWU1MDM2ZmNiZDUyYTMwMTRlNzIyMTMyZWY5ZDg5ZDdmNGZhM2FhNDlk
10
+ MDk5MmQ4Nzg3MTI1MjE2ODM1MDY4MjU5YzEzNjAyZmRhYTEwYThmMjI2MTVj
11
+ Mjk1N2IxMGIyNzdhMmQxY2IyN2U3ZGQxZGQxOGRlZmI2ZDAyNDU=
12
12
  data.tar.gz: !binary |-
13
- YzBhODExNjc5OWE2N2E2OThjNDBjOTIwNmJiYjE3ZThlNTBjNzMyMGZiZjFi
14
- ZWRkMjhmOWVhYWY1ZGRkNzhjNTViNWNiNDgwMGUzMTgxZDRiNzNiZDE2MWM3
15
- MzY4NDlmMmVjYmRjOTM1NTUxYTRlMzQ4NDM3ZDQ5NjYxMzJmMDM=
13
+ Yzg0NDBiNTgwODg2MjRlMGVmZTUzNzQ1ZDQ1NTE0ZDhlZTE5MzYxMjhjNTQ0
14
+ MDVkNzFhZGIwNjU5NGQyMmY1MTBhN2RiZmM1MjU0MTJkYTYwNTNmY2EyZjE0
15
+ MmZkNGJmMmY0YzhkZTkwZjgzMjY4ZmFmNjdlNzYzMjdmNDBiNmU=
data/README.md CHANGED
@@ -18,42 +18,257 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- There are really two basic methods available on the API. To get started, you need an API key, available from [mirador.im/join](http://mirador.im/join). If you have problems with the API or this client, please contact support@mirador.im.
21
+ To get started, you need an API key, available from [mirador.im/join](http://mirador.im/). If you have problems with the API or this client, please contact support@mirador.im.
22
22
 
23
- ### `Mirador::Client.classify_files(files) -> [Mirador::Result]`
23
+ ## Mirador::Result and Mirador::ResultList
24
24
 
25
- This method takes a list of filenames and returns a list of `Mirador::Result` objects. See example:
25
+ All multiple-request methods (e.g., classify_files), return a [Mirador::ResultList](#resultlist), which is effectively a list of Mirador::Result objects; single-request methods (e.g., classify_url) return a [Mirador::Result](#result) object.
26
+
27
+ ## Classifying Files & Buffers
28
+
29
+ You can classify 4 types of files/file-objects:
30
+
31
+ * file objects (e.g., `x` where `x = File.open('myfile.jpg')`); [classify_files](#classify_files)
32
+ * filenames `myfile.jpg` [classify_files](#classify_files)
33
+ * buffers `buffer = File.read('myfile.jpg')` [classify_buffers](#classify_buffers)
34
+ * base64-encoded buffers (e.g., from a data URI) [classify_encoded_strings](#classify_encoded_strings)
35
+
36
+ The methods for file-based classification are as follows:
37
+
38
+
39
+ ### <a name="classify_files"></a> Mirador::Client#classify_files
26
40
 
27
41
  ```ruby
28
42
  require 'mirador'
43
+ mc = Mirador::Client.new('your_api_key')
44
+
45
+ results = mc.classify_files('test.jpg', 'picture.jpg')
29
46
 
30
- mc = Mirador::Client.new('your_key_here')
31
- mc.classify_files('bathing-suit.jpg', 'nsfw-user-upload.png').each do |result|
32
- puts "name: #{ result.name }, safe: #{ result.safe }, value: #{ result.value }"
47
+ assert results['test.jpg']
48
+ assert_equal results.length, 2
49
+
50
+ results.each do |res|
51
+ puts "#{ res.id }, #{ res.value }"
52
+ end
53
+
54
+ results.each do |id, res|
55
+ puts "#{ id }, #{ res.value }"
33
56
  end
34
57
 
35
58
  ```
36
59
 
37
- ### `Mirdor::Client.classify_urls(urls) -> [Mirador::Result]`
60
+ You can also specify an id to be used:
61
+
62
+ ```ruby
63
+ require 'mirador'
64
+ mc = Mirador::Client.new 'your_api_key'
65
+
66
+ # first method: use ids as keys
67
+ results = mc.classify_files(nsfw: 'nsfw.jpg', sfw: 'sfw.jpg')
38
68
 
39
- This method takes a list of urls and returns `Mirador::Result` objects. Identical to `classify_files`:
69
+ assert results[:nsfw]
70
+ assert results[:sfw]
71
+
72
+ # second method: pass an array of { id:, data: } hashes
73
+ results = mc.classify_files([{ id: :nsfw, data: 'nsfw.jpg'}, { id: :sfw, data: 'sfw.jpg' }])
74
+
75
+ assert results[:nsfw]
76
+ assert results[:sfw]
77
+ ```
78
+
79
+ File can be either a filename or a file object; e.g., the following is also valid:
80
+
81
+ ```ruby
82
+ results = mc.classify_files(nsfw: File.open('nsfw.jpg'))
83
+ ```
84
+
85
+ ### <a name='classify_file'></a> Mirador::Client#classify_file
86
+
87
+ A shortcut for classifying a single file; this will return a `Mirador::Result` instead of a `Mirador::ResultList`:
40
88
 
41
89
  ```ruby
42
90
  require 'mirador'
91
+ mc = Mirador::Client.new 'your_api_key'
43
92
 
44
- mc.classify_urls('http://possibly-nsfw.com/cool.png', 'http://mysite.net/image/bad-picture.jpg').each do |result|
45
- puts "name: #{ result.name }, safe: #{ result.safe }, value: #{ result.value }"
46
- end
93
+ # first method: use ids as keys
94
+ nsfw = mc.classify_file(nsfw: 'nsfw.jpg')
47
95
 
96
+ puts nsfw.value
48
97
  ```
49
98
 
50
- ### `Mirador::Result`
99
+ ### <a name='classify_buffers'></a> Mirador:Client#classify_buffers
100
+
101
+ Classify a buffer, e.g., an already-read file. This simplifies the classification of file uploads, e.g. POST data. The interface is identical to [classify_files](#classify_files), only differing in the actual data passed in:
102
+
103
+ ```ruby
104
+ require 'mirador'
105
+ mc = Mirador::Client.new 'your_api_key'
106
+
107
+ nsfw_buf = File.read('nsfw.jpg')
108
+ sfw_buf = File.read('sfw.jpg')
109
+
110
+ # these are equivalent
111
+ results = mc.classify_buffers(nsfw: nsfw_buf, sfw: sfw_buf)
112
+ results = mc.classify_buffers([{id: :nsfw, data: nsfw_buf}, {id: :sfw, data: sfw_buf}])
113
+
114
+ # since buffers dont have a name, you just get an index as id
115
+ results = mc.classify_buffers(nsfw_buf, sfw_buf)
116
+ ```
117
+
118
+ #### <a name='classify_buffer'></a> Mirador::Client#classify_buffer
119
+
120
+ As with classify_file, there is a shortcut for classifying only one buffer; see [classify_file](#classify_file) for clarifications on usage (it's identical).
121
+
122
+ ### <a name='classify_encoded_strings'></a> Mirador::Client#classify_encoded_strings
123
+
124
+ The Mirador API internally represents images as base64-encoded strings (agnostic of image encoding); this method lets you pass in an alread-encoded string in the event that you're also using base64 encoding elsewhere in your system. Usage is the same as [classify_buffers](#classify_buffers):
125
+
126
+ ```ruby
127
+ require 'mirador'
128
+ require 'base64'
129
+
130
+ mc = Mirador::Client.new 'your_api_key'
131
+
132
+ nsfw_buf = Base64.encode64(File.read('nsfw.jpg'))
133
+ sfw_buf = Base64.encode64(File.read('sfw.jpg'))
134
+
135
+ # these are equivalent
136
+ results = mc.classify_encoded_strings(nsfw: nsfw_buf, sfw: sfw_buf)
137
+ results = mc.classify_encoded_strings([{id: :nsfw, data: nsfw_buf}, {id: :sfw, data: sfw_buf}])
138
+
139
+ # since strings dont have a name, you just get an index as id
140
+ results = mc.classify_encoded_strings(nsfw_buf, sfw_buf)
141
+ ```
142
+
143
+ #### <a name='classify_encoded_string'></a> Mirador::Client#classify_encoded_string
144
+
145
+ Another helper for only working with 1 request/result at a time. See [classify_file](#classify_file) for more info.
146
+
147
+
148
+ ### <a name='classify_data_uris'></a> Mirador::Client#classify_data_uris
149
+
150
+ This simplifies data transfer between client applications and the mirador API. For example, given the following javascript:
151
+
152
+ ```javascript
153
+ document.getElementById('form-field').addEventListener('change', function (e) {
154
+
155
+ var file = this.files[0];
156
+
157
+ var reader = new FileReader();
158
+ reader.onload = function (e) {
159
+ $.post('/proxy/mirador', { id: file.name, data: e.target.result });
160
+ }
161
+
162
+ reader.readAsDataURL(file);
163
+ });
164
+ ```
165
+
166
+ Your could classify that data url with the following code:
167
+
168
+ ```ruby
169
+
170
+ res = mc.classify_data_uris(request['id'] => request['data'])
171
+
172
+ # send the result
173
+ res[request['id']].to_json
174
+
175
+ # or, even easier
176
+ mc.classify_data_uri(request['id'] => request['data']).to_json
177
+
178
+ ```
179
+
180
+ Otherwise, classify_data_uris and classify_data_uri have identical interfaces to the other methods covered so far.
181
+
182
+
183
+ ## Classify URLs
184
+
185
+ You can easily classify a publically-available URL (e.g., a public s3 bucket), with [classify_urls](#classify_urls) and [classify_url](#classify_url). The interfaces for these methods are identical to the file-handling methods covered above.
186
+
187
+
188
+ ### <a name='classify_urls'></a> Mirador::Client#classify_urls
189
+
190
+ The only things to keep in mind with URLs:
191
+
192
+ * must be publically-accessibly
193
+ * must be < Mirador::Client::MAX_ID_LEN if you are using the url as the item's id (see below)
194
+ * download/response time on url will affect response time of result, must be less than 60 seconds.
195
+
196
+
197
+ #### Examples:
198
+
199
+
200
+ Assigning specific ids to urls:
201
+
202
+ ```ruby
203
+ require 'mirador'
204
+
205
+ mc = Mirador::Client.new 'your_api_key'
206
+
207
+ res = mc.classify_urls(nsfw: 'http://static.mirador.im/test/nsfw.jpg', sfw: 'http://static.mirador.im/test/sfw.jpg')
208
+
209
+ assert res[:nsfw]
210
+ assert res[:sfw].safe
211
+
212
+ ```
213
+
214
+ Implicitly using url as its own id:
215
+
216
+ ```ruby
217
+ require 'mirador'
218
+
219
+ nsfw_url = 'http://static.mirador.im/test/nsfw.jpg'
220
+ sfw_url = 'http://static.mirador.im/test/sfw.jpg'
221
+
222
+ mc = Mirador::Client.new 'your_api_key'
223
+ res = mc.classify_urls(nsfw_url, sfw_url)
224
+
225
+ puts res[nsfw_url].value
226
+ puts res[sfw_url].value
227
+ ```
228
+
229
+ Classify a single URL using Mirador::Client#classify_url
230
+
231
+ ```ruby
232
+ require 'mirador'
233
+
234
+ mc = Mirador::Client.new 'your_api_key'
235
+ nsfw = mc.classify_url(nsfw_url)
236
+
237
+ assert (not nsfw.safe)
238
+ puts nsfw.value
239
+ ```
240
+
241
+ ## <a name='result'></a> Mirador::Result
242
+
243
+ The `Mirador::Result` class wraps the output of the API for a specific image/url. It has the following attributes:
244
+
245
+ * `@id` [Mixed]: the id, as specified in the request, or implied (see above)
246
+ * `@safe` [Boolean]: whether the image should be considered flagged/containing adult content
247
+ * `@value` [Float 0.0-1.0]: A float indicating the likelyhood of the image containing adult content (useful for creating custom thresholds)
248
+ * `@error` [String]: will only be non-nil if this is an error
249
+
250
+ The `Mirador::Result` object also has a couple of convenience methods:
251
+
252
+ * `#to_h` - convert to a hash
253
+ * `#to_json` - if json is require'd, serialize to json
254
+ * `#failed?` - returns a boolean indicating whether image is a failure/error
255
+ * `#to_s` - returns a string representation of the result`
256
+ * `#name` **(deprecated)** - this simply maps to `@id`
257
+
258
+ ## <a name='resultlist'></a> Mirador::ResultList [Enumerable]
259
+
260
+ Methods that return multiple results do so by returning a single `Mirador::ResultList`. This object is used in lieu of a Hash or Array as to provide mixed-access. You can treat it as an array, iterating via `each do |x|`, indexing with integers, or by simply calling `#to_a`, or as a hash, indexing with `@id`'s from image-requests.
261
+
262
+ The ResultList has the following methods:
51
263
 
52
- The `Mirador::Result` class has 3 fields:
264
+ * `#[](key)` operator override to index the ResultList. You can index by integers in range of 0 - ResultList#length, or by an `@id` for one of the Result objects within.
265
+ * `#to_a` convert to an array of `Mirador::Result` objects
266
+ * `#length` the number of items in the `ResultList`
267
+ * `#update` equivalent to Hash#update
268
+ * `#to_h` conver to a hash
269
+ * `#to_json` serialize the resultlist as json
270
+ * `#each` `ResultList` includes `Enumerable`, and this implementation of `#each` checks the arity of blocks passed in to allow iteration either as an array or as a Hash.
53
271
 
54
- * `Result.name` - `string`, the filename or url for this request
55
- * `Result.safe` - `bool`, a boolean indicating whether image contains adult content.
56
- * `Result.value` - `float`, a number 0.0 - 1.0 indicating confidence of judgement
57
272
 
58
273
  ## Contributing
59
274
 
data/lib/mirador.rb CHANGED
@@ -3,137 +3,343 @@ require 'base64'
3
3
 
4
4
  module Mirador
5
5
 
6
- API_BASE = "http://api.mirador.im/v1/"
6
+ class ApiError < StandardError
7
+ end
7
8
 
8
- class Result
9
- attr_accessor :name, :safe, :value
9
+ class ResultList
10
+ include Enumerable
11
+
12
+ def initialize(items=[])
13
+ @items = {}
10
14
 
11
- def initialize name, data
12
- @name = name
13
- @safe = data['safe']
14
- @value = data['value']
15
+ items.each do |x|
16
+ @items[x.id] = x
17
+ end
15
18
  end
16
19
 
17
- def to_s
18
- "<Mirador::Result; name: #{ @name }; safe: #{ @safe }; value: #{ @value }/>"
20
+ def <<(item)
21
+ @items[item.id] = item
22
+ end
23
+
24
+ def [](key)
25
+ if key.is_a? Integer and not @items.has_key? key
26
+ @items.values[key]
27
+ else
28
+ @items[key.to_s]
29
+ end
30
+ end
31
+
32
+ def to_a
33
+ @items.values
34
+ end
35
+
36
+ def length
37
+ @items.values.length
19
38
  end
20
39
 
21
- def self.parse_results reqs, results
40
+ def update other
41
+ @items.update(other)
42
+ end
43
+
44
+ def to_h
45
+ @items
46
+ end
22
47
 
23
- if not results
24
- raise ApiError, "no results for: #{ reqs }"
48
+ def to_json
49
+ @items.to_json
50
+ end
51
+
52
+ def each &block
53
+ if block.arity == 1
54
+ @items.values.each do |x|
55
+ block.call(x)
56
+ end
57
+ else
58
+ @items.each do |k, v|
59
+ block.call(k, v)
60
+ end
25
61
  end
62
+ end
63
+
64
+ def self.parse_results res
26
65
 
27
- results.each_with_index.map do |v, i|
28
- Result.new(reqs[i], v['result'])
66
+ output = {}
67
+ res.each do |x|
68
+ r = Result.new(x)
69
+ output[r.id] = r
29
70
  end
71
+
72
+ output
30
73
  end
31
74
 
32
75
  end
33
76
 
34
- class ApiError < StandardError
77
+ class Result
78
+ attr_accessor :id, :safe, :value, :error
79
+
80
+ def initialize data
81
+
82
+ if data.has_key? 'errors'
83
+ @error = data['errors']
84
+ return
85
+ end
86
+
87
+ @id = data['id']
88
+ @safe = data['result']['safe']
89
+ @value = data['result']['value']
90
+
91
+ end
92
+
93
+ def to_h
94
+ {
95
+ id: @id,
96
+ safe: @safe,
97
+ value: @value,
98
+ }
99
+ end
100
+
101
+ def to_json
102
+ as_h = self.to_h
103
+
104
+ if as_h.respond_to? :to_json
105
+ as_h.to_json
106
+ else
107
+ nil
108
+ end
109
+ end
110
+
111
+ def failed?
112
+ @error != nil
113
+ end
114
+
115
+ def to_s
116
+ "<Mirador::Result; id: #{ @id }; safe: #{ @safe }; value: #{ @value }/>"
117
+ end
118
+
119
+ def name
120
+ @id
121
+ end
122
+
35
123
  end
36
124
 
37
125
  class Client
38
126
  include HTTParty
39
127
  base_uri 'api.mirador.im'
128
+
40
129
  default_timeout 10
41
130
 
42
- MAX_LEN = 8
131
+ MAX_LEN = 5
132
+ MAX_ID_LEN = 256
133
+ DATA_URI_PRE = ';base64,'
134
+ DATA_URI_PRELEN = 8
43
135
 
44
136
  def initialize(api_key)
45
137
  @options = { api_key: api_key }
46
138
  end
47
139
 
48
- def classify_urls urls
49
-
50
- if urls.length > MAX_LEN
51
- out = []
52
- urls.each_slice(MAX_LEN) do |s|
53
- out << self.classify_urls(s)
140
+ # metaprogramming extreme
141
+ [:url, :file, :buffer, :encoded_string, :data_uri].each do |datatype|
142
+ define_method("classify_#{datatype.to_s}s") do |args, params={}|
143
+ flexible_request args, params do |item|
144
+ fmt_items(datatype, item)
54
145
  end
146
+ end
55
147
 
56
- return out.flatten
148
+ define_method("classify_#{datatype.to_s}") do |args, params={}|
149
+ res = self.send("classify_#{datatype.to_s}s", args, params)
150
+ res[0]
57
151
  end
58
152
 
59
- res = self.class.post(
60
- "/v1/classify",
61
- {
62
- body: @options.merge({url: urls}),
63
- headers: {"User-Agent" => "Mirador Client v1.0/Ruby"}
64
- }
65
- )
153
+ end
66
154
 
67
- if res['errors']
68
- raise ApiError, res['errors']
69
- elsif not res
70
- raise ApiError, "no response: #{ res.code }"
71
- end
155
+ protected
156
+
157
+ def flexible_request(args, params={}, &cb)
158
+ req = {}
159
+
160
+ req = (if args.is_a? Hash
161
+
162
+ Hash[args.map do |k, v|
163
+ process_param(k, v)
164
+ end]
165
+
166
+ elsif args.is_a? String
167
+ Hash[[process_argument(args)]]
168
+
169
+ elsif args and args.length
170
+ Hash[args.each_with_index.map do |a, idx|
171
+ process_argument(a, idx)
172
+ end]
72
173
 
73
- return Result.parse_results urls, res['results']
174
+ elsif params
175
+ Hash[params.map do |k, v|
176
+ process_param(k, v)
177
+ end]
178
+ end)
179
+
180
+ chunked_request(req) do |item|
181
+ formatted = cb.call(item)
182
+ make_request(formatted)
183
+ end
74
184
  end
75
185
 
76
- def classify_files files
77
- if files.length > MAX_LEN
78
- out = []
79
- files.each_slice(MAX_LEN) do |s|
80
- out << self.classify_files(s)
186
+ def process_argument arg, idx=0
187
+
188
+ if arg.is_a?(String)
189
+ if arg.length < MAX_ID_LEN
190
+ [arg, arg]
191
+ else
192
+ [idx, arg]
81
193
  end
82
194
 
83
- return out.flatten
84
- end
195
+ elsif arg.respond_to?(:name) and arg.respond_to?(:read)
85
196
 
86
- processed = files.map do |f| self.process_file(f) end
87
- return self.classify_encoded files, processed
88
- end
197
+ [arg.name, arg]
198
+
199
+ elsif arg.respond_to?(:id) and arg.respond_to?(:data)
89
200
 
201
+ [arg.id, arg.data]
90
202
 
91
- def classify_raw_images imgs
203
+ elsif arg.is_a?(Hash)
92
204
 
93
- if imgs.length > MAX_LEN
94
- out = []
95
- imgs.each_slice(MAX_LEN) do |s|
96
- out << self.classify_raw_images(Hash[s])
205
+ if arg.has_key? :id and arg.has_key? :data
206
+ [arg[:id], arg[:data]]
207
+ elsif arg.has_key? 'id' and arg.has_key? 'data'
208
+ [arg['id'], arg['data']]
97
209
  end
98
210
 
99
- return out.flatten
211
+ else
212
+ raise ApiError, "Invalid argument: #{ arg }"
100
213
  end
101
214
 
102
- # expects a hash
103
- # id => image
104
- images, names = [], []
105
- imgs.each_pair do |k, v|
106
- images << v
107
- names << k
215
+ end
216
+
217
+ # given a parameter passed in,
218
+ # assuming that its a id => data mapping, return
219
+ # the correct formatting/check for any fuck ups
220
+ # @arguments:
221
+ # k - key
222
+ # v - value
223
+ # @returns:
224
+ # { k => v } pair
225
+ def process_param k, v
226
+
227
+ if v.is_a?(File)
228
+ [ k, v.read ]
229
+ elsif k.respond_to?(:to_s) and v.is_a?(String)
230
+ [ k.to_s, v ]
231
+ else
232
+ raise ApiError, "Invalid Argument: #{ k } => #{ v }"
233
+ end
234
+
235
+ end
236
+
237
+ # given a request and a block,
238
+ # call the block X number of times
239
+ # where X is request.length / MAX_LEN
240
+ def chunked_request req, &mthd
241
+ output = ResultList.new
242
+ req.each_slice(MAX_LEN).each do |slice|
243
+ output.update(mthd.call(slice))
108
244
  end
109
245
 
110
- processed = images.map { |i| Base64.encode64(i).gsub("\n", '') }
111
- return self.classify_encoded names, processed
246
+ return output
112
247
  end
113
248
 
114
- def process_file file
115
- data = File.read(file)
116
- Base64.encode64(data).gsub("\n", '')
249
+ # basically, transform hash h into a hash
250
+ # where the key-value pairs are all formatted
251
+ # by 'fmt-item' (should double the number of key-value
252
+ # pairs in the hash)
253
+ def fmt_items name, h
254
+ out = {}
255
+ h.each_with_index do |kv, idx|
256
+ out.update fmt_item(name, idx, kv[0], kv[1])
257
+ end
258
+ return out
117
259
  end
118
260
 
119
- def classify_encoded files, encoded
261
+ @@name_map = {
262
+ file: 'image',
263
+ buffer: 'image',
264
+ raw: 'image',
265
+ url: 'url',
266
+ encoded_string: 'image',
267
+ data_uri: 'image',
268
+ }
269
+
270
+ @@formatters = {
271
+ url: Proc.new { |url| url },
272
+
273
+ file: Proc.new { |file|
274
+
275
+ Base64.encode64(if file.respond_to? :read
276
+ file.read
277
+ else
278
+ File.read(file)
279
+ end).gsub(/\n/, '')
280
+
281
+ },
282
+
283
+ buffer: Proc.new { |file|
284
+
285
+ Base64.encode64(file).gsub(/\n/, '')
286
+
287
+ },
288
+
289
+ raw: Proc.new { |file|
290
+
291
+ Base64.encode64(file).gsub(/\n/, '')
292
+
293
+ },
294
+
295
+ encoded_string: Proc.new { |b64str|
296
+ b64str.gsub(/\n/, '')
297
+ },
298
+
299
+ data_uri: Proc.new { |datauri|
300
+ datauri.sub(/^.+;base64,/, '').gsub(/\n/,'')
301
+ },
302
+
303
+ }
304
+
305
+ # produce a k-v mapping internal to the API,
306
+ # so that 'name' is the datatype:
307
+ # e.g., name[idx][id], name[idx][data]
308
+ def fmt_item name, idx, id, data
309
+ formatted = @@formatters[name].call(data)
310
+ datatype = @@name_map[name]
311
+ {
312
+ "#{datatype}[#{idx}][id]" => id,
313
+ "#{datatype}[#{idx}][data]" => formatted,
314
+ }
315
+ end
316
+
317
+ # base method to actually make the request
318
+ def make_request params
319
+
120
320
  res = self.class.post(
121
321
  "/v1/classify",
122
322
  {
123
- body: @options.merge({image: encoded}),
124
- headers: {'User-Agent' => 'Mirador Client v1.0/Ruby'},
323
+ body: @options.merge(params),
324
+ headers: {"User-Agent" => "Mirador Client v1.0/Ruby"}
125
325
  }
126
326
  )
127
327
 
328
+ k = 'results'
329
+
128
330
  if res['errors']
129
- raise ApiError, res['errors']
130
- end
131
331
 
132
- if not res
133
- raise ApiError, "no response", res.code
332
+ if not res['result']
333
+ raise ApiError, res
334
+ else
335
+ k = 'result'
336
+ end
337
+
338
+ elsif not res
339
+ raise ApiError, "no response: #{ res.code }"
134
340
  end
135
341
 
136
- return Result.parse_results(files, res['results'])
342
+ return ResultList.parse_results res[k]
137
343
  end
138
344
 
139
345
  end
@@ -1,3 +1,3 @@
1
1
  module Mirador
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
data/test/test_mirador.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'test/unit'
2
- require 'mirador'
2
+ require './lib/mirador'
3
+ require 'base64'
3
4
 
4
5
  class MiradorTest < Test::Unit::TestCase
5
6
 
@@ -11,15 +12,14 @@ class MiradorTest < Test::Unit::TestCase
11
12
  SFW_URL = "http://demo.mirador.im/test/sfw.jpg"
12
13
  NSFW_URL = "http://demo.mirador.im/test/nsfw.jpg"
13
14
 
14
- MM = Mirador::Client.new('')
15
+ MM = Mirador::Client.new(ENV['MIRADOR_API_KEY'])
15
16
 
16
17
  def test_classify_files
17
18
 
18
19
  res = MM.classify_files([NSFW_IM, SFW_IM])
19
-
20
20
  assert_equal res.length, 2
21
21
 
22
- nsfw, sfw = res
22
+ nsfw, sfw = res[NSFW_IM], res[SFW_IM]
23
23
 
24
24
  assert_operator nsfw.value, :>=, 0.50
25
25
  assert_operator sfw.value, :<, 0.50
@@ -27,92 +27,147 @@ class MiradorTest < Test::Unit::TestCase
27
27
  assert nsfw.name.eql?(NSFW_IM), "nsfw name does not match"
28
28
  assert sfw.name.eql?(SFW_IM), "sfw name does not match"
29
29
 
30
+ assert nsfw.id.eql?(NSFW_IM)
31
+ assert sfw.id.eql?(SFW_IM)
32
+
30
33
  assert sfw.safe
31
34
  assert (not nsfw.safe)
32
35
 
33
36
  end
34
37
 
35
- def test_chunked_files
36
- nsfw_files = [NSFW_IM]*10
37
- sfw_files = [SFW_IM]*10
38
+ def test_classify_urls
38
39
 
39
- nres = MM.classify_files(nsfw_files)
40
- assert_equal nres.length, 10
40
+ res = MM.classify_urls([NSFW_URL, SFW_URL])
41
+ assert_equal 2, res.length
41
42
 
42
- nres.each do |r|
43
- assert_operator r.value, :>=, 0.50
44
- assert r.name.eql?(NSFW_IM)
45
- assert (not r.safe)
46
- end
43
+ nsfw, sfw = res[NSFW_URL], res[SFW_URL]
47
44
 
48
- sres = MM.classify_files(sfw_files)
49
- assert_equal sres.length, 10
45
+ assert_operator nsfw.value, :>=, 0.50
46
+ assert_operator sfw.value, :<, 0.50
47
+
48
+ assert nsfw.name.eql?(NSFW_URL), "nsfw name does not match"
49
+ assert sfw.name.eql?(SFW_URL), "sfw name does not match"
50
+
51
+ assert nsfw.id.eql?(NSFW_URL)
52
+ assert sfw.id.eql?(SFW_URL)
53
+
54
+ assert sfw.safe
55
+ assert (not nsfw.safe)
50
56
 
51
- sres.each do |r|
52
- assert_operator r.value, :<, 0.50
53
- assert r.name.eql?(SFW_IM)
54
- assert r.safe
55
- end
56
57
  end
57
58
 
58
- def test_chunked_urls
59
- nsfw_urls = [NSFW_URL]*10
60
- sfw_urls = [SFW_URL]*10
59
+ def test_classify_chunked_urls
61
60
 
62
- nres = MM.classify_urls(nsfw_urls)
63
- assert_equal nres.length, 10
61
+ r = Hash[([NSFW_URL]*10).each_with_index.map do |url, idx|
62
+ [ "#{ idx }-im", url ]
63
+ end]
64
64
 
65
- nres.each do |r|
66
- assert_not_nil r
67
- assert_operator r.value, :>=, 0.50
68
- assert r.name.eql?(NSFW_URL)
69
- assert (not r.safe)
70
- end
65
+ res = MM.classify_urls(r)
71
66
 
72
- sres = MM.classify_urls(sfw_urls)
73
- assert_equal sres.length, 10
67
+ assert_equal 10, res.length
74
68
 
75
- sres.each do |r|
76
- assert_not_nil r
77
- assert_not_nil r.value
78
- assert_operator r.value, :<, 0.50
79
- assert r.name.eql?(SFW_URL)
80
- assert r.safe
69
+ res.each do |id, r|
70
+ assert_operator r.value, :>=, 0.50
81
71
  end
82
72
 
83
73
  end
84
74
 
85
- def test_classify_urls
86
- res = MM.classify_urls([NSFW_URL, SFW_URL])
75
+ def test_hash_call
87
76
 
88
- assert_equal res.length, 2
89
- nsfw, sfw = res
77
+ res = MM.classify_urls(nsfw: NSFW_URL, sfw: SFW_URL)
78
+
79
+ assert res[:nsfw]
80
+ assert res[:sfw]
90
81
 
91
- assert nsfw.name.eql?(NSFW_URL)
92
- assert sfw.name.eql?(SFW_URL)
82
+ nsfw = res[:nsfw]
83
+ sfw = res[:sfw]
93
84
 
94
85
  assert_operator nsfw.value, :>=, 0.50
95
86
  assert_operator sfw.value, :<, 0.50
96
87
 
88
+ assert nsfw.name.eql?('nsfw'), "nsfw name does not match"
89
+ assert sfw.name.eql?('sfw'), "sfw name does not match"
90
+
91
+ assert nsfw.id.eql?('nsfw')
92
+ assert sfw.id.eql?('sfw')
93
+
97
94
  assert sfw.safe
98
95
  assert (not nsfw.safe)
96
+
99
97
  end
100
98
 
101
- def test_classify_raw
102
- nsfw_d, sfw_d = [NSFW_IM, SFW_IM].map { |f| File.read(f) }
103
- res = MM.classify_raw_images({ "nsfw" => nsfw_d, "sfw" => sfw_d })
99
+ def test_single_url
100
+
101
+ res = MM.classify_url(nsfw: NSFW_URL)
102
+ res1 = MM.classify_url(NSFW_URL)
103
+
104
+ assert_equal res.value, res1.value
105
+
106
+ end
107
+
108
+ def test_items_call
109
+
110
+ res = MM.classify_urls([{ id: :nsfw, data: NSFW_URL }, { id: :sfw, data: SFW_URL }])
104
111
 
105
112
  assert_equal res.length, 2
106
- nsfw, sfw = res
107
113
 
108
- assert nsfw.name.eql?('nsfw'), "invalid name: #{ nsfw.name }"
109
- assert sfw.name.eql?('sfw')
114
+ assert res[:nsfw]
115
+ assert res[:sfw]
110
116
 
111
- assert_operator nsfw.value, :>=, 0.50
112
- assert_operator sfw.value, :<, 0.50
117
+ end
118
+
119
+ def test_classify_buffers
120
+
121
+ bufs = Hash[([File.read(NSFW_IM)]*3).each_with_index.map do |b, idx|
122
+ ["#{idx}-buf", b]
123
+ end]
124
+
125
+
126
+ res = MM.classify_buffers(bufs)
127
+
128
+ res1 = MM.classify_buffer(File.read(SFW_IM))
129
+
130
+ assert_equal res.length, 3
131
+ assert_operator res1.value, :<=, 0.50
132
+
133
+ res.each do |r|
134
+ assert_operator r.value, :>=, 0.5
135
+ end
136
+ end
137
+
138
+ def test_data_uris
139
+ duri = 'data:image/jpg;base64,' + Base64.encode64(File.read(NSFW_IM)).gsub(/\n/, '')
140
+
141
+ res = MM.classify_data_uris(nsfw: duri)
142
+
143
+ assert res[:nsfw]
144
+ assert_operator res[:nsfw].value, :>=, 0.50
145
+ end
146
+
147
+ def test_encoded_string
148
+
149
+ tdata = Hash[[SFW_IM, NSFW_IM].map do |fname|
150
+ [fname, Base64.encode64(File.read(fname))]
151
+ end]
152
+
153
+ res = MM.classify_encoded_strings(tdata)
154
+
155
+ assert res[SFW_IM]
156
+ assert res[NSFW_IM]
157
+
158
+ assert_operator res[NSFW_IM].value, :>=, 0.50
159
+
160
+ end
161
+
162
+ def test_item_error
163
+
164
+ res = MM.classify_urls([{ id: :nsfw, data: 'invalid-url'}, { id: :sfw, data: SFW_URL }])
165
+
166
+ assert_equal res.length, 2
167
+ assert res[:sfw]
168
+
169
+ assert res.any? do |r| r.failed? end
113
170
 
114
- assert sfw.safe
115
- assert (not nsfw.safe)
116
171
  end
117
172
 
118
173
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mirador
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Jacob
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-17 00:00:00.000000000 Z
11
+ date: 2014-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty