flico 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/flico.gemspec +1 -1
- data/lib/flico/app.rb +35 -28
- data/lib/flico/collager.rb +13 -32
- data/lib/flico/dictionary.rb +20 -9
- data/lib/flico/flickr/base_api.rb +66 -0
- data/lib/flico/image_file.rb +29 -0
- data/lib/flico.rb +1 -53
- data/spec/base_api_spec.rb +33 -0
- data/spec/dictionary_spec.rb +21 -0
- metadata +6 -7
- data/lib/flico/cell.rb +0 -32
- data/lib/flico/flickr_command.rb +0 -55
- data/lib/flico/grid.rb +0 -83
- data/lib/flico/saver.rb +0 -39
- data/spec/flickr_command_spec.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 473a26ece9ff8cf869ea977bdc1a96a5b800848ce82f4ee0282dad74bda96bdc
|
4
|
+
data.tar.gz: b4e83f8d45ea19e79e4d5884603f3941af769e279d8267dff9379f662f3acbbf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55a33bfccc1180547b859c572ef4802f4bd6b6299298319e0b21519ae381b4f040b0620ffbde06e002c9d9216d6f00b559af148aa38b93d9968b10464351c918
|
7
|
+
data.tar.gz: c96edd9e612f9cb0dbc1711c0d0dedf5ade8be1b1e685316055cd4d48b3bbda9bbdc6c2b8aa8eea6073a9da88f10d8589b9f1c4bfad9db033d7d20664ec3ab7a
|
data/flico.gemspec
CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'flico'
|
8
|
-
spec.version = '
|
8
|
+
spec.version = '2.0.0'
|
9
9
|
spec.summary = 'A CLI tool to create collage from keywords using Flickr'
|
10
10
|
spec.homepage = 'https://github.com/amandeepbhamra/flico'
|
11
11
|
spec.license = 'MIT'
|
data/lib/flico/app.rb
CHANGED
@@ -1,44 +1,51 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'flico/dictionary'
|
4
|
+
require 'flico/collager'
|
5
|
+
require 'flico/image_file'
|
6
|
+
|
3
7
|
module Flico
|
4
8
|
class ApplicationError < StandardError; end
|
5
|
-
class KeywordMissing < StandardError; end
|
6
|
-
class NoImage < StandardError; end
|
7
|
-
class FetchingError < StandardError; end
|
8
9
|
|
9
10
|
class App
|
10
|
-
|
11
|
+
KEYWORDS_COUNT = 10
|
12
|
+
|
13
|
+
attr_reader :keywords, :options
|
11
14
|
|
12
|
-
def initialize(
|
13
|
-
@
|
15
|
+
def initialize(keywords, options)
|
16
|
+
@keywords = add_missing_keywords(keywords)
|
17
|
+
@options = options
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
collage(image_urls)
|
20
|
+
def create
|
21
|
+
options[:file_name] = prepare_file_name(options)
|
22
|
+
|
23
|
+
Collager.new(prepare_images, options[:file_name]).save
|
24
|
+
|
25
|
+
puts "Flicollage saved at #{options[:file_name]}"
|
23
26
|
end
|
24
27
|
|
25
|
-
def
|
26
|
-
|
28
|
+
def prepare_images
|
29
|
+
keywords.map { |keyword| ImageFile.new(keyword).fetch_from_flickr }
|
27
30
|
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
private
|
33
|
+
|
34
|
+
def add_missing_keywords(words)
|
35
|
+
words_count = words.size
|
36
|
+
return words if words_count == KEYWORDS_COUNT
|
37
|
+
|
38
|
+
words += Dictionary.new.words(KEYWORDS_COUNT - words_count)
|
39
|
+
puts "KEYWORDS: #{words}"
|
40
|
+
words
|
41
|
+
end
|
42
|
+
|
43
|
+
def prepare_file_name(options)
|
44
|
+
return options[:file_name] unless options[:file_name].nil?
|
45
|
+
|
46
|
+
puts 'Enter file name for collage (press ENTER to use default)'
|
47
|
+
file_name = $stdin.gets.strip
|
48
|
+
file_name.empty? ? "flicollage-#{Time.now.strftime('%Y%m%d%H%M%S')}.png" : file_name
|
42
49
|
end
|
43
50
|
end
|
44
51
|
end
|
data/lib/flico/collager.rb
CHANGED
@@ -1,44 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'flico/grid'
|
4
3
|
require 'mini_magick'
|
5
4
|
|
6
5
|
module Flico
|
7
6
|
class Collager
|
8
|
-
attr_reader :
|
7
|
+
attr_reader :images_url, :file_name
|
9
8
|
|
10
|
-
def initialize(
|
11
|
-
@
|
9
|
+
def initialize(images_url, file_name)
|
10
|
+
@images_url = images_url
|
11
|
+
@file_name = file_name
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
image_urls.map.with_index do |path, idx|
|
24
|
-
image = MiniMagick::Image.open path.path
|
25
|
-
image.crop(grid.crop_rectangle(idx, image.width, image.height).to_mm)
|
26
|
-
image.resize(grid.resize_rectangle(idx, image.width, image.height).to_mm)
|
27
|
-
print_to_canvas(image, grid.cell_rectangle(idx), temp_file)
|
28
|
-
end
|
29
|
-
temp_file
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def print_to_canvas(image, rectangle, temp_file)
|
35
|
-
canvas = MiniMagick::Image.new temp_file.path
|
36
|
-
result = canvas.composite(image) do |c|
|
37
|
-
c.compose 'Over'
|
38
|
-
c.geometry rectangle.to_mm
|
39
|
-
end
|
40
|
-
result.write temp_file.path
|
41
|
-
temp_file.rewind
|
14
|
+
def save
|
15
|
+
montage = MiniMagick::Tool::Montage.new
|
16
|
+
montage.density '600'
|
17
|
+
montage.tile '5X2'
|
18
|
+
montage.geometry '+10+20'
|
19
|
+
montage.border '5'
|
20
|
+
images_url.each { |image_url| montage << image_url }
|
21
|
+
montage << file_name
|
22
|
+
montage.call
|
42
23
|
end
|
43
24
|
end
|
44
25
|
end
|
data/lib/flico/dictionary.rb
CHANGED
@@ -2,23 +2,34 @@
|
|
2
2
|
|
3
3
|
module Flico
|
4
4
|
class Dictionary
|
5
|
-
|
6
|
-
|
5
|
+
PATH = '/usr/share/dict/words'
|
6
|
+
ALPHABETS_LOWER_LIMIT = 3
|
7
|
+
ALPHABETS_UPPER_LIMIT = 10
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@dictionary_path = validate(PATH)
|
7
11
|
@words = []
|
8
12
|
end
|
9
13
|
|
10
|
-
def
|
11
|
-
@words
|
14
|
+
def words(count)
|
15
|
+
@words += pick_words(count)
|
12
16
|
end
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
private
|
19
|
+
|
20
|
+
def pick_words(count)
|
21
|
+
File.readlines(PATH).select do |word|
|
22
|
+
word.size > ALPHABETS_LOWER_LIMIT && word.size < ALPHABETS_UPPER_LIMIT
|
23
|
+
end.sample(count).map(&:strip)
|
16
24
|
end
|
17
25
|
|
18
|
-
|
26
|
+
def validate(path)
|
27
|
+
unless File.exist?(path)
|
28
|
+
warn "Exiting due to missing dictionary at path: #{path}"
|
29
|
+
exit 1
|
30
|
+
end
|
19
31
|
|
20
|
-
|
21
|
-
File.readlines("/usr/share/dict/words").select { |word| word.size > 3 && word.size < 10 }.sample.strip
|
32
|
+
path
|
22
33
|
end
|
23
34
|
end
|
24
35
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'flickraw'
|
4
|
+
|
5
|
+
module Flico
|
6
|
+
module Flickr
|
7
|
+
class BaseApi
|
8
|
+
attr_reader :key, :secret, :api
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@key = ENV['FLICKR_KEY']
|
12
|
+
@secret = ENV['FLICKR_SECRET']
|
13
|
+
@api = flickraw_api
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_photo(keyword)
|
17
|
+
valid_keyword?(keyword)
|
18
|
+
|
19
|
+
puts "Searching images for keyword: #{keyword}"
|
20
|
+
|
21
|
+
results = search_api(keyword)
|
22
|
+
|
23
|
+
results.count.zero? ? (warn "No Image for keyword: #{keyword}") : photo_url(results.first.id)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def flickraw_api
|
29
|
+
valid_secrets?
|
30
|
+
|
31
|
+
FlickRaw.api_key = key
|
32
|
+
FlickRaw.shared_secret = secret
|
33
|
+
FlickRaw::Flickr.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_secrets?
|
37
|
+
if key.nil? || secret.nil?
|
38
|
+
warn 'Exiting due to missing required environment variables: FLICKR_KEY or FLICKR_SECRET'
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def search_api(keyword)
|
46
|
+
api.photos.search({ text: keyword, per_page: 10, sort: 'interestingness-desc' })
|
47
|
+
end
|
48
|
+
|
49
|
+
def info_api(image)
|
50
|
+
api.photos.getInfo(photo_id: image)
|
51
|
+
end
|
52
|
+
|
53
|
+
def size_api(id)
|
54
|
+
api.photos.getSizes(photo_id: id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def photo_url(id)
|
58
|
+
size_api(id).find { |v| v['label'] == 'Square' }['source']
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid_keyword?(keyword)
|
62
|
+
warn "Missing Keywords (can't be nil)" if keyword.nil?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'flico/flickr/base_api'
|
4
|
+
|
5
|
+
module Flico
|
6
|
+
class NoImage < StandardError; end
|
7
|
+
class FetchingError < StandardError; end
|
8
|
+
|
9
|
+
class ImageFile
|
10
|
+
attr_accessor :keyword
|
11
|
+
|
12
|
+
def initialize(keyword)
|
13
|
+
@keyword = keyword
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch_from_flickr
|
17
|
+
Flickr::BaseApi.new.get_photo(keyword)
|
18
|
+
rescue NoImage => e
|
19
|
+
puts "Image not found for keyword '#{keyword}'. Message: #{e.message}. Retrying"
|
20
|
+
unless (tries -= 1).positive?
|
21
|
+
raise ApplicationError, "Failed getting image after retrying #{MAX_KEYWORD_RETRIES} times"
|
22
|
+
end
|
23
|
+
|
24
|
+
retry
|
25
|
+
rescue FetchingError => e
|
26
|
+
raise ApplicationError, e.message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/flico.rb
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'flickraw'
|
4
3
|
require 'ostruct'
|
5
4
|
require 'optparse'
|
6
5
|
require 'flico/app'
|
7
|
-
require 'flico/flickr_command'
|
8
|
-
require 'flico/dictionary'
|
9
|
-
require 'flico/saver'
|
10
|
-
require 'flico/collager'
|
11
6
|
|
12
7
|
module Flico
|
13
8
|
class CommandLineInterface
|
@@ -24,54 +19,7 @@ module Flico
|
|
24
19
|
|
25
20
|
def self.start(args)
|
26
21
|
keywords, options = parse_args(args)
|
27
|
-
|
28
|
-
resources = validate_resources(keywords, options)
|
29
|
-
App.new(resources).create_collage
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.validate_resources(keywords, options = {})
|
33
|
-
dictionary = validate_dictionary
|
34
|
-
dictionary.append(keywords)
|
35
|
-
col = SaveCollage.new
|
36
|
-
col.output_file_name = options[:file_name]
|
37
|
-
|
38
|
-
Resource.new(flickr_api: validate_flickr_api, dictionary:, fetch_image: FetchImage.new,
|
39
|
-
collager: Collager.new, save_collage: col)
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.validate_flickr_api
|
43
|
-
key = ENV['FLICKR_KEY']
|
44
|
-
secret = ENV['FLICKR_SECRET']
|
45
|
-
if key.nil? && secret.nil?
|
46
|
-
warn 'Performing AutoExit due to missing required environment variables: FLICKR_KEY and FLICKR_SECRET'
|
47
|
-
exit 1
|
48
|
-
else
|
49
|
-
FlickRaw.api_key = key
|
50
|
-
FlickRaw.shared_secret = secret
|
51
|
-
FlickrCommand.setup(FlickRaw::Flickr.new)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.validate_dictionary
|
56
|
-
dictionary_path = '/usr/share/dict/words'
|
57
|
-
if File.exist?(dictionary_path)
|
58
|
-
Dictionary.new(dictionary_path)
|
59
|
-
else
|
60
|
-
warn "Performing AutoExit due to missing required dictionary at path: #{dictionary_path}"
|
61
|
-
exit 1
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
class Resource
|
67
|
-
attr_reader :flickr_api, :dictionary, :fetch_image, :collager, :save_collage
|
68
|
-
|
69
|
-
def initialize(params = {})
|
70
|
-
@flickr_api = params.fetch(:flickr_api)
|
71
|
-
@dictionary = params.fetch(:dictionary)
|
72
|
-
@fetch_image = params.fetch(:fetch_image)
|
73
|
-
@collager = params.fetch(:collager)
|
74
|
-
@save_collage = params.fetch(:save_collage)
|
22
|
+
App.new(keywords, options).create
|
75
23
|
end
|
76
24
|
end
|
77
25
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'flico/flickr/base_api'
|
5
|
+
|
6
|
+
describe Flico::Flickr::BaseApi do
|
7
|
+
let(:photo_url) do
|
8
|
+
{
|
9
|
+
'label' => 'Square',
|
10
|
+
'width' => 75,
|
11
|
+
'height' => 75,
|
12
|
+
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_s.jpg',
|
13
|
+
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/sq/',
|
14
|
+
'media' => 'photo'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:subject) { described_class.new }
|
19
|
+
|
20
|
+
context 'search success' do
|
21
|
+
before do
|
22
|
+
ENV['FLICKR_KEY'] = 'FOO'
|
23
|
+
ENV['FLICKR_SECRET'] = 'BAR'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should return image url for search keyword' do
|
27
|
+
allow_any_instance_of(Flico::Flickr::BaseApi).to receive(:flickraw_api).and_return(double('flickraw_api'))
|
28
|
+
allow_any_instance_of(Flico::Flickr::BaseApi).to receive(:get_photo).and_return(photo_url)
|
29
|
+
|
30
|
+
expect(subject.get_photo('Volkswagen Golf in Luxembourg')['source']).to eq('https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_s.jpg')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'flico/dictionary'
|
5
|
+
|
6
|
+
describe Flico::Dictionary do
|
7
|
+
let(:subject) { described_class.new }
|
8
|
+
|
9
|
+
context '#words' do
|
10
|
+
let(:count) { 3 }
|
11
|
+
let(:words) { subject.words(count) }
|
12
|
+
|
13
|
+
it 'should not be empty' do
|
14
|
+
expect(words).to_not be_empty
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return 3 random words' do
|
18
|
+
expect(words.count).to eq count
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flico
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amandeep Singh Bhamra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flickraw
|
@@ -114,13 +114,12 @@ files:
|
|
114
114
|
- flico.gemspec
|
115
115
|
- lib/flico.rb
|
116
116
|
- lib/flico/app.rb
|
117
|
-
- lib/flico/cell.rb
|
118
117
|
- lib/flico/collager.rb
|
119
118
|
- lib/flico/dictionary.rb
|
120
|
-
- lib/flico/
|
121
|
-
- lib/flico/
|
122
|
-
-
|
123
|
-
- spec/
|
119
|
+
- lib/flico/flickr/base_api.rb
|
120
|
+
- lib/flico/image_file.rb
|
121
|
+
- spec/base_api_spec.rb
|
122
|
+
- spec/dictionary_spec.rb
|
124
123
|
- spec/spec_helper.rb
|
125
124
|
homepage: https://github.com/amandeepbhamra/flico
|
126
125
|
licenses:
|
data/lib/flico/cell.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Flico
|
4
|
-
class Cell
|
5
|
-
attr_reader :x, :y, :width, :height
|
6
|
-
|
7
|
-
def initialize(x = 0, y = 0, width, height)
|
8
|
-
@x = x
|
9
|
-
@y = y
|
10
|
-
@width = width
|
11
|
-
@height = height
|
12
|
-
end
|
13
|
-
|
14
|
-
def aspect_ratio
|
15
|
-
width.to_f / height
|
16
|
-
end
|
17
|
-
|
18
|
-
def to_mm
|
19
|
-
"#{width}x#{height}+#{x}+#{y}"
|
20
|
-
end
|
21
|
-
|
22
|
-
def ==(other)
|
23
|
-
other.class == self.class && other.state == state
|
24
|
-
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
|
28
|
-
def state
|
29
|
-
[@x, @y, @width, @height]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/lib/flico/flickr_command.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Flico
|
4
|
-
class FlickrCommand
|
5
|
-
def initialize(search_command:, sizes_command:)
|
6
|
-
@search_command = search_command
|
7
|
-
@sizes_command = sizes_command
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.setup(flickraw)
|
11
|
-
new(search_command: SearchCommand.new(flickraw), sizes_command: SizesCommand.new(flickraw))
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(keyword)
|
15
|
-
if keyword.nil?
|
16
|
-
warn "Missing Keywords (can't be nil)"
|
17
|
-
else
|
18
|
-
puts "Searching images for keyword: #{keyword}"
|
19
|
-
results = @search_command.call(keyword)
|
20
|
-
if results.count.zero?
|
21
|
-
warn "No Image for keyword: #{keyword}"
|
22
|
-
else
|
23
|
-
get_image_url(results.first['id'])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def get_image_url(photo_id)
|
31
|
-
sizes = @sizes_command.call(photo_id)
|
32
|
-
sizes[sizes.count / 2]['source']
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class FlickrBase
|
37
|
-
attr_reader :api
|
38
|
-
|
39
|
-
def initialize(flickraw)
|
40
|
-
@api = flickraw
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class SearchCommand < FlickrBase
|
45
|
-
def call(keyword)
|
46
|
-
api.photos.search({ text: keyword, per_page: 10, sort: 'interestingness-desc' })
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
class SizesCommand < FlickrBase
|
51
|
-
def call(photo_id)
|
52
|
-
api.photos.getSizes(photo_id:)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/flico/grid.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'flico/cell'
|
4
|
-
|
5
|
-
module Flico
|
6
|
-
class Grid
|
7
|
-
GRID = [[0, 1, 1, 2], [3, 4, 5, 6], [7, 8, 8, 9]].freeze
|
8
|
-
CANV_WIDTH = 1750
|
9
|
-
CANV_HEIGHT = 1250
|
10
|
-
|
11
|
-
def canv_width
|
12
|
-
CANV_WIDTH
|
13
|
-
end
|
14
|
-
|
15
|
-
def canv_height
|
16
|
-
CANV_HEIGHT
|
17
|
-
end
|
18
|
-
|
19
|
-
def columns
|
20
|
-
GRID.max_by(&:size).size
|
21
|
-
end
|
22
|
-
|
23
|
-
def column_cells
|
24
|
-
GRID.transpose
|
25
|
-
end
|
26
|
-
|
27
|
-
def rows
|
28
|
-
GRID.size
|
29
|
-
end
|
30
|
-
|
31
|
-
def row_cells
|
32
|
-
GRID
|
33
|
-
end
|
34
|
-
|
35
|
-
def cell_width(name)
|
36
|
-
cell_size(CANV_WIDTH, columns, size_multiplier(row_cells, name))
|
37
|
-
end
|
38
|
-
|
39
|
-
def cell_height(name)
|
40
|
-
cell_size(CANV_HEIGHT, rows, size_multiplier(column_cells, name))
|
41
|
-
end
|
42
|
-
|
43
|
-
def crop_rectangle(cell_name, image_width, image_height)
|
44
|
-
x = 0
|
45
|
-
y = 0
|
46
|
-
image = Cell.new(image_width, image_height)
|
47
|
-
cell = cell_rectangle(cell_name)
|
48
|
-
|
49
|
-
if image.aspect_ratio >= cell.aspect_ratio
|
50
|
-
final_width = image_width - (image.width - (image.height * cell.aspect_ratio))
|
51
|
-
final_height = image.height
|
52
|
-
else
|
53
|
-
final_width = image.width
|
54
|
-
final_height = image.height - (image.height - (image.width / cell.aspect_ratio))
|
55
|
-
end
|
56
|
-
|
57
|
-
Cell.new(x, y, final_width, final_height)
|
58
|
-
end
|
59
|
-
|
60
|
-
def resize_rectangle(cell_name, image_width, image_height)
|
61
|
-
Cell.new(image_width, image_height)
|
62
|
-
cell_rectangle(cell_name)
|
63
|
-
end
|
64
|
-
|
65
|
-
def cell_rectangle(name)
|
66
|
-
column = column_cells.find_index do |row|
|
67
|
-
row.include? name
|
68
|
-
end
|
69
|
-
row = row_cells.find_index { |row| row.include? name }
|
70
|
-
Cell.new(column * (CANV_WIDTH / columns), row * (CANV_HEIGHT / rows), cell_width(name), cell_height(name))
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def size_multiplier(table, name)
|
76
|
-
table.map { |row| row.count(name) }.max
|
77
|
-
end
|
78
|
-
|
79
|
-
def cell_size(total, divisions, multiplier)
|
80
|
-
multiplier.zero? ? 0 : ((total / divisions) * multiplier)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
data/lib/flico/saver.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'open-uri'
|
4
|
-
require 'tempfile'
|
5
|
-
require 'fileutils'
|
6
|
-
require 'date'
|
7
|
-
|
8
|
-
module Flico
|
9
|
-
class FetchingError < StandardError; end
|
10
|
-
|
11
|
-
class FetchImage
|
12
|
-
def call(url)
|
13
|
-
tempfile = Tempfile.new 'temp_image'
|
14
|
-
IO.copy_stream(URI.open(url, read_timeout: 5), tempfile)
|
15
|
-
tempfile.rewind
|
16
|
-
tempfile
|
17
|
-
rescue OpenURI::HTTPError => e
|
18
|
-
raise FetchingError, "Couldn't download from: #{url} due to #{e.message}"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class SaveCollage
|
23
|
-
attr_accessor :output_file_name
|
24
|
-
|
25
|
-
def call(file_path)
|
26
|
-
file_name = output_file_name || validate_file_name
|
27
|
-
FileUtils.mv file_path, file_name
|
28
|
-
puts "Flicollage saved at #{file_name}"
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def validate_file_name
|
34
|
-
puts "Enter file name for collage (press ENTER to use '#{default}')"
|
35
|
-
file_name = $stdin.gets.strip
|
36
|
-
file_name.empty? ? "flicollage-#{Time.now.strftime('%Y%m%d%H%M%S')}.png" : file_name
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/spec/flickr_command_spec.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'flico/flickr_command'
|
5
|
-
|
6
|
-
describe Flico::FlickrCommand do
|
7
|
-
let(:search_results) do
|
8
|
-
[
|
9
|
-
{
|
10
|
-
'id' => '33472607802',
|
11
|
-
'owner' => '78759190@N05',
|
12
|
-
'secret' => '42e011bcf5',
|
13
|
-
'server' => '2805',
|
14
|
-
'farm' => 3,
|
15
|
-
'title' => 'Volkswagen Golf in Luxembourg',
|
16
|
-
'ispublic' => 1,
|
17
|
-
'isfriend' => 0,
|
18
|
-
'isfamily' => 0
|
19
|
-
}
|
20
|
-
]
|
21
|
-
end
|
22
|
-
let(:sizes_results) do
|
23
|
-
[
|
24
|
-
{
|
25
|
-
'label' => 'Square',
|
26
|
-
'width' => 75,
|
27
|
-
'height' => 75,
|
28
|
-
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_s.jpg',
|
29
|
-
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/sq/',
|
30
|
-
'media' => 'photo'
|
31
|
-
},
|
32
|
-
{
|
33
|
-
'label' => 'Large Square',
|
34
|
-
'width' => '150',
|
35
|
-
'height' => '150',
|
36
|
-
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_q.jpg',
|
37
|
-
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/q/',
|
38
|
-
'media' => 'photo'
|
39
|
-
},
|
40
|
-
|
41
|
-
{
|
42
|
-
'label' => 'Thumbnail',
|
43
|
-
'width' => '100',
|
44
|
-
'height' => '66',
|
45
|
-
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_t.jpg',
|
46
|
-
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/t/',
|
47
|
-
'media' => 'photo'
|
48
|
-
}
|
49
|
-
]
|
50
|
-
end
|
51
|
-
let(:subject) { described_class.new(search_command:, sizes_command:) }
|
52
|
-
let(:search_command) { double('search', call: search_results) }
|
53
|
-
let(:sizes_command) { double('sizes', call: sizes_results) }
|
54
|
-
|
55
|
-
context 'search success' do
|
56
|
-
it 'should return image url for search keyword' do
|
57
|
-
expect(subject.call('Volkswagen Golf in Luxembourg')).to eq('https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_q.jpg')
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|