FlickrCollage 0.1.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.
@@ -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: []