FlickrCollage 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,399 @@
1
+ require "FlickrCollage/version"
2
+ require "methadone"
3
+ require "flickraw"
4
+ require "mini_magick"
5
+ require "open-uri"
6
+ require "fileutils"
7
+
8
+ # Creating collage from top-rated Flickr images for your keywords
9
+ # @author Chromium
10
+ module FlickrCollage
11
+ # Class for Dictionary. Provides random words
12
+ IMG_COUNT = 10
13
+ IMG_PATH = "./"
14
+ COLLAGE_FILE = "flickrCollage"
15
+ RAISE_ERROR = true
16
+
17
+ class Dictionary
18
+ DICT_FILE = "/usr/share/dict/words"
19
+ # @return [Array] All words at dictionary file
20
+ attr_accessor :words
21
+
22
+ def initialize(opts = {})
23
+ opts[:file] ||= DICT_FILE
24
+ @words = File.read(opts[:file]).lines.select do |line|
25
+ (4..9).cover?(line.strip.size)
26
+ end
27
+ end
28
+
29
+ # Get random word from Dictionary file
30
+ #
31
+ # @return [String] random word
32
+ def get_word
33
+ @words.sample.strip
34
+ end
35
+ end
36
+
37
+ # Class for Flickr. Get and download top-rated images for keywords
38
+ class Flickr
39
+ include Methadone::CLILogging
40
+
41
+ API_KEY = "27ad23a09746546c02c79f39879ee408"
42
+ SHARED_SECRET = "707eaf92eece0744"
43
+ SEARCH_KEYWORD = "Giant Robots Smashing into Other Giant Robots"
44
+ SEARCH_COUNT = 1
45
+ # @return [String] Path for saving images
46
+ attr_accessor :img_path
47
+
48
+ def initialize(opts = {})
49
+ opts[:api_key] ||= API_KEY
50
+ opts[:shared_secret] ||= SHARED_SECRET
51
+ opts[:img_path] ||= IMG_PATH
52
+
53
+ FlickRaw.api_key = opts[:api_key]
54
+ FlickRaw.shared_secret = opts[:shared_secret]
55
+ @img_path = opts[:img_path]
56
+ end
57
+
58
+ # Search top-rated images for keyword
59
+ #
60
+ # @param [Hash] opts
61
+ # @option opts [String] :keyword ('Giant Robots Smashing into Other Giant Robots') keyword for search
62
+ # @option opts [Number] :count (1) how many images you need
63
+ #
64
+ # @return [Array<String>] urls with top-rated images for keyword
65
+ # @return [String] url with top-rated image for keyword
66
+ def search(opts = {})
67
+ opts[:keyword] ||= SEARCH_KEYWORD
68
+ opts[:count] ||= SEARCH_COUNT
69
+ photos = []
70
+ begin
71
+ list = flickr.photos.search(text: opts[:keyword], sort: "interestingness-desc", per_page: opts[:count])
72
+ debug "flickr.search: #{opts[:keyword]} (img: #{list.size})"
73
+
74
+ list.each do |photo|
75
+ info = flickr.photos.getInfo(photo_id: photo.id)
76
+ debug "flickr.search: get image url for #{photo.id}"
77
+ # debug "flickr.search: #{FlickRaw.url_photopage(info)}"
78
+ photos << FlickRaw.url_c(info)
79
+ end
80
+ rescue StandardError => e
81
+ warn "flickr.search: caught exception #{e}"
82
+ raise e if RAISE_ERROR
83
+ end
84
+ photos.size > 1 ? photos : photos.first
85
+ end
86
+
87
+ # Download images from urls
88
+ #
89
+ # @param [Array<Hash>] opts
90
+ # @option opts [String] :keyword keyword for file name
91
+ # @option opts [String] :url image url
92
+ #
93
+ # @return [Array<String>] files paths for downloaded images
94
+ # @return [String] file path for downloaded image
95
+ def download(opts = {})
96
+ opts ||= []
97
+ photos = []
98
+ return photos unless opts.any?
99
+ opts = [keyword: opts[:keyword], url: opts[:url]] unless opts.is_a?(Array)
100
+ FileUtils.mkdir_p("#{@img_path}tmp")
101
+
102
+ begin
103
+ opts.each do |img|
104
+ debug "flickr.download: #{img[:keyword]} #{img[:url]}"
105
+ next unless defined?(img[:url]) && !img[:url].nil?
106
+ open(img[:url]) do |url|
107
+ File.open("#{@img_path}tmp/#{img[:keyword]}.jpg", "wb") do |file|
108
+ file.puts url.read
109
+ end
110
+ file = "#{@img_path}tmp/#{img[:keyword]}.jpg"
111
+ debug "flickr.download: file #{file}"
112
+ photos << file if Image.valid?(file)
113
+ end
114
+ end
115
+ rescue StandardError => e
116
+ warn "flickr.download: caught exception: #{e.inspect}"
117
+ raise e if RAISE_ERROR
118
+ end
119
+ photos.size > 1 ? photos : photos.first
120
+ end
121
+
122
+ # Scrape top-rated images for keyword
123
+ #
124
+ # @param [Hash] opts
125
+ # @option opts [String] :keyword ('Giant Robots Smashing into Other Giant Robots') keyword for scrape
126
+ #
127
+ # @return [String] file path for downloaded image
128
+ def scrape(opts = {})
129
+ opts[:keyword] ||= SEARCH_KEYWORD
130
+
131
+ url = search(keyword: opts[:keyword])
132
+ debug "flickr.scrape: #{opts[:keyword]} #{url}"
133
+ download(keyword: opts[:keyword], url: url)
134
+ end
135
+ end
136
+
137
+ # Class for Image. Create collage and crop images
138
+ # @attr_reader [String] img_path Path for saving images
139
+ # @attr_reader [Number] img_width Width for images crop
140
+ # @attr_reader [Number] img_height Height for images crop
141
+ # @attr_reader [String] collage_file Collage file name
142
+ # @attr_reader [Boolean] clear_tmp Delete all downloaded images
143
+ class Image
144
+ include Methadone::CLILogging
145
+
146
+ IMG_WIDTH = 800
147
+ IMG_HEIGHT = 600
148
+
149
+ attr_accessor :img_path, :img_width, :img_height, :collage_file, :clear_tmp
150
+
151
+ def initialize(opts = {})
152
+ opts[:img_path] ||= IMG_PATH
153
+ opts[:img_width] ||= IMG_WIDTH
154
+ opts[:img_height] ||= IMG_HEIGHT
155
+ opts[:collage_file] ||= COLLAGE_FILE
156
+
157
+ @img_path = opts[:img_path]
158
+ @img_width = opts[:img_width]
159
+ @img_height = opts[:img_height]
160
+ @collage_file = opts[:collage_file]
161
+ @clear_tmp = opts[:clear_tmp]
162
+ end
163
+
164
+ # Validates downloaded image
165
+ #
166
+ # @param [String] file file path for downloaded image
167
+ #
168
+ # @return [true] image is valid
169
+ # @return [false] image is not valid
170
+ def self.valid?(file)
171
+ return false if file.nil?
172
+ begin
173
+ image = MiniMagick::Image.open(file)
174
+ rescue StandardError => e
175
+ warn "Image.valid: caught exception #{e}"
176
+ raise e if RAISE_ERROR
177
+ end
178
+ image.valid?
179
+ end
180
+
181
+ # Resize image
182
+ #
183
+ # @param [Hash] opts
184
+ # @option opts [String] :file file path for image
185
+ # @option opts [Number] :width (@img_width) image new width
186
+ # @option opts [Number] :height (@img_height) image new height
187
+ #
188
+ # @return [String] file path for resized image
189
+ def resize(opts = {})
190
+ return false if opts[:file].nil?
191
+
192
+ opts[:width] ||= @img_width
193
+ opts[:height] ||= @img_height
194
+ begin
195
+ image = MiniMagick::Image.open(opts[:file])
196
+ resize_to_fill(opts[:width], opts[:height], image)
197
+ file_resize = "#{@img_path}tmp/#{File.basename(opts[:file], ".jpg")}_resize.jpg"
198
+ image.write file_resize
199
+ debug "Resize #{opts[:file]} to #{opts[:width]}x#{opts[:height]}"
200
+ rescue StandardError => e
201
+ warn "Image.resize: caught exception #{e}"
202
+ raise e if RAISE_ERROR
203
+ end
204
+ file_resize
205
+ end
206
+
207
+ # Creates collage from 10 images
208
+ #
209
+ # @param [Hash] opts
210
+ # @option opts [Array<String>] :files file paths for collage images
211
+ #
212
+ # @return [true] collage created successfully
213
+ # @return [false] something went wrong
214
+ def collage(opts = {})
215
+ unless opts[:files].size == IMG_COUNT
216
+ info "Not enough images for collage"
217
+ return false
218
+ end
219
+
220
+ begin
221
+ (1..4).each do |row|
222
+ montage = MiniMagick::Tool::Montage.new
223
+ files = montage_resize(files: opts[:files], row: row)
224
+ files.each do |file|
225
+ montage << file
226
+ end
227
+ montage << "-mode"
228
+ montage << "Concatenate"
229
+ montage << "-background"
230
+ montage << "none"
231
+ montage << "-geometry"
232
+ montage << montage_geometry(row: row)
233
+ montage << "-tile"
234
+ montage << montage_tile(row: row)
235
+ montage << montage_img_path(row: row)
236
+ montage.call
237
+ end
238
+ rescue StandardError => e
239
+ warn "Image.collage: caught exception #{e}"
240
+ raise e if RAISE_ERROR
241
+ end
242
+ FileUtils.rm_rf("#{@img_path}tmp") if @clear_tmp
243
+ true
244
+ end
245
+
246
+ # Smart image crop
247
+ #
248
+ # @param [Number] width image new width
249
+ # @param [Number] height image new height
250
+ # @param [MiniMagick::Image] img image
251
+ # @param [String] gravity crop around
252
+ #
253
+ # @return [MiniMagick::Image] cropped image
254
+ def resize_to_fill(width, height, img, gravity = "Center")
255
+ cols, rows = img[:dimensions]
256
+ img.combine_options do |cmd|
257
+ if width != cols || height != rows
258
+ scale_x = width / cols.to_f
259
+ scale_y = height / rows.to_f
260
+ if scale_x >= scale_y
261
+ cols = (scale_x * (cols + 0.5)).round
262
+ rows = (scale_x * (rows + 0.5)).round
263
+ cmd.resize cols.to_s
264
+ else
265
+ cols = (scale_y * (cols + 0.5)).round
266
+ rows = (scale_y * (rows + 0.5)).round
267
+ cmd.resize "x#{rows}"
268
+ end
269
+ end
270
+
271
+ cmd.gravity gravity
272
+ cmd.background "rgba(255,255,255,0.0)"
273
+ cmd.extent "#{width}x#{height}" if cols != width || rows != height
274
+ end
275
+ end
276
+
277
+ # Creates simple collage 5x2 from 10 images
278
+ #
279
+ # @param [Hash] opts
280
+ # @option opts [Array<String>] :files file paths for collage images
281
+ #
282
+ # @return [true] collage created successfully
283
+ # @return [false] something went wrong
284
+ # @deprecated First collage implementation
285
+ def collage_simple(opts = {})
286
+ unless opts[:files].size == IMG_COUNT
287
+ info "No images for collage"
288
+ return false
289
+ end
290
+
291
+ begin
292
+ montage = MiniMagick::Tool::Montage.new
293
+ opts[:files].each do |file|
294
+ montage << resize(file: file)
295
+ end
296
+ montage << "-mode"
297
+ montage << "Concatenate"
298
+ montage << "-background"
299
+ montage << "none"
300
+ montage << "-geometry"
301
+ montage << "#{@img_width}x#{@img_height}+0+0"
302
+ montage << "-tile"
303
+ montage << "5x2"
304
+ montage << "#{@img_path}#{@collage_file}.jpg"
305
+ puts montage.inspect.to_s
306
+ montage.call
307
+ rescue StandardError => e
308
+ warn "Image.collage: caught exception #{e}"
309
+ raise e if RAISE_ERROR
310
+ end
311
+ true
312
+ end
313
+
314
+ private
315
+
316
+ # Montage Collage - resizing images for rows
317
+ # 1 row - 3 images
318
+ # 2 row - 4 images
319
+ # 3 row - 3 images
320
+ #
321
+ # @param [Hash] opts
322
+ # @option opts [Number] :row collage row 1..4 (4 row - final collage from first 3 rows)
323
+ #
324
+ # @return [Array<String>] Array of file paths for resized images
325
+ def montage_resize(opts = {})
326
+ files = []
327
+ case opts[:row]
328
+ when 1
329
+ (0..2).each do |i|
330
+ files << resize(file: opts[:files][i])
331
+ end
332
+ when 2
333
+ (3..6).each do |i|
334
+ files << resize(file: opts[:files][i], width: @img_width * 0.75)
335
+ end
336
+ when 3
337
+ (7..9).each do |i|
338
+ files << resize(file: opts[:files][i])
339
+ end
340
+ when 4
341
+ (1..3).each do |i|
342
+ files << "#{@img_path}tmp/#{@collage_file}-#{i}.jpg"
343
+ end
344
+ end
345
+ files
346
+ end
347
+
348
+ # Montage Collage - setting images geometry for rows
349
+ # 1 row - 3 images
350
+ # 2 row - 4 images
351
+ # 3 row - 3 images
352
+ #
353
+ # @param [Hash] opts
354
+ # @option opts [Number] :row collage row 1..4 (4 row - final collage from first 3 rows)
355
+ #
356
+ # @return [String] images geometry
357
+ def montage_geometry(opts = {})
358
+ case opts[:row]
359
+ when 1, 3 then "#{@img_width}x#{@img_height}+0+0"
360
+ when 2 then "#{@img_width * 0.75}x#{@img_height}+0+0"
361
+ when 4 then "#{@img_width * 3}x#{@img_height}+0+0"
362
+ end
363
+ end
364
+
365
+ # Montage Collage - setting images tile for rows
366
+ # 1 row - 3 images
367
+ # 2 row - 4 images
368
+ # 3 row - 3 images
369
+ #
370
+ # @param [Hash] opts
371
+ # @option opts [Number] :row collage row 1..4 (4 row - final collage from first 3 rows)
372
+ #
373
+ # @return [String] images tile
374
+ def montage_tile(opts = {})
375
+ case opts[:row]
376
+ when 1, 3 then "3x1"
377
+ when 2 then "4x1"
378
+ when 4 then "1x3"
379
+ end
380
+ end
381
+
382
+ # Montage Collage - setting images paths for rows
383
+ # 1 row - 3 images
384
+ # 2 row - 4 images
385
+ # 3 row - 3 images
386
+ #
387
+ # @param [Hash] opts
388
+ # @option opts [Number] :row collage row 1..4 (4 row - final collage from first 3 rows)
389
+ #
390
+ # @return [String] images path
391
+ def montage_img_path(opts = {})
392
+ case opts[:row]
393
+ when 1..3 then "#{@img_path}tmp/#{@collage_file}-#{opts[:row]}.jpg"
394
+ when 4 then "#{@img_path}#{@collage_file}.jpg"
395
+ else ""
396
+ end
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,3 @@
1
+ module FlickrCollage
2
+ VERSION = "0.1.1"
3
+ end
Binary file
metadata ADDED
@@ -0,0 +1,215 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: FlickrCollage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Chromium
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 4.2.2
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 4.2.2
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: 4.2.2
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 4.2.2
61
+ - !ruby/object:Gem::Dependency
62
+ name: aruba
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.14.2
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.14.2
75
+ - !ruby/object:Gem::Dependency
76
+ name: methadone
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.9.2
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.9.2
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 1.9.2
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 1.9.2
95
+ - !ruby/object:Gem::Dependency
96
+ name: test-unit
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: 3.2.1
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 3.2.1
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 3.2.1
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 3.2.1
115
+ - !ruby/object:Gem::Dependency
116
+ name: flickraw
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: 0.9.9
122
+ type: :runtime
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: 0.9.9
129
+ - !ruby/object:Gem::Dependency
130
+ name: mini_magick
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: 4.5.1
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 4.5.1
139
+ type: :runtime
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 4.5.1
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 4.5.1
149
+ description: |-
150
+ This gem creates collage from 10 top-rated Flickr images for your keywords.
151
+ It's a part of coding challenge and it's fun.
152
+ email:
153
+ - crcs2o4@gmail.com
154
+ executables: []
155
+ extensions: []
156
+ extra_rdoc_files: []
157
+ files:
158
+ - ".gitignore"
159
+ - FlickrCollage.gemspec
160
+ - Gemfile
161
+ - Gemfile.lock
162
+ - LICENSE.txt
163
+ - README.md
164
+ - Rakefile
165
+ - bin/FlickrCollage
166
+ - bin/console
167
+ - bin/setup
168
+ - doc/FlickrCollage.html
169
+ - doc/FlickrCollage/Dictionary.html
170
+ - doc/FlickrCollage/Flickr.html
171
+ - doc/FlickrCollage/Image.html
172
+ - doc/_index.html
173
+ - doc/class_list.html
174
+ - doc/css/common.css
175
+ - doc/css/full_list.css
176
+ - doc/css/style.css
177
+ - doc/file.README.html
178
+ - doc/file_list.html
179
+ - doc/flickrCollage.jpg
180
+ - doc/frames.html
181
+ - doc/index.html
182
+ - doc/js/app.js
183
+ - doc/js/full_list.js
184
+ - doc/js/jquery.js
185
+ - doc/method_list.html
186
+ - doc/top-level-namespace.html
187
+ - lib/FlickrCollage.rb
188
+ - lib/FlickrCollage/version.rb
189
+ - tmp/Giant Robots Smashing into Other Giant Robots.jpg
190
+ - tmp/cat.jpg
191
+ homepage: https://github.com/CrCs2O4/FlickrCollage.git
192
+ licenses:
193
+ - MIT
194
+ metadata: {}
195
+ post_install_message:
196
+ rdoc_options: []
197
+ require_paths:
198
+ - lib
199
+ required_ruby_version: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ required_rubygems_version: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ requirements: []
210
+ rubyforge_project:
211
+ rubygems_version: 2.5.1
212
+ signing_key:
213
+ specification_version: 4
214
+ summary: Create collage from 10 top-rated Flickr images for your keywords
215
+ test_files: []