flicket 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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +72 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/flicket +7 -0
- data/bin/setup +8 -0
- data/docs/layout-10.jpg +0 -0
- data/flicket.gemspec +30 -0
- data/lib/flicket.rb +76 -0
- data/lib/flicket/app.rb +42 -0
- data/lib/flicket/collage_maker.rb +50 -0
- data/lib/flicket/downloader.rb +24 -0
- data/lib/flicket/environment.rb +13 -0
- data/lib/flicket/flicker_query.rb +59 -0
- data/lib/flicket/keyword_source.rb +31 -0
- data/lib/flicket/layout_calculator.rb +101 -0
- data/lib/flicket/rectangle.rb +38 -0
- data/lib/flicket/storage.rb +38 -0
- data/lib/flicket/version.rb +3 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 55f142070269cb20ed163e5ca4f93af5c8e152b5
|
4
|
+
data.tar.gz: 6d5631bc63d86b81e38629600a93e2afe7143f7d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 965bc8261f3e9786ee71abb968d4d0d68f05a64f84700861c8d12f97da5b7629011fdb296c3a540eb85cc039c7f09a81ac8da1be063ddce623b3c7f74ec9ad56
|
7
|
+
data.tar.gz: fe4a382b6b425cdf17f89bee9299e9a9258d5db60bf84cefb2ca1512be5ebedc40ddec914ecd0885b7b480cde9e4356fcb151ba86c4526edc898848ace9df736
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
flicket
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Madis Nõmme
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Flicket
|
2
|
+
|
3
|
+
## About
|
4
|
+
|
5
|
+
Flicket is a ruby command line tool that:
|
6
|
+
|
7
|
+
* accepts a list of search keywords as arguments
|
8
|
+
* queries the Flickr API for the top-rated image for each keyword
|
9
|
+
* downloads the results
|
10
|
+
* crops them rectangularly
|
11
|
+
* assembles a collage grid from ten images and
|
12
|
+
* writes the result to a user-supplied filename
|
13
|
+
|
14
|
+
If given less than ten keywords, or if any keyword fails to
|
15
|
+
result in a match, it retrieves random words from a dictionary
|
16
|
+
source such as `/usr/share/dict/words`. Repeating as necessary
|
17
|
+
until you have gathered ten images.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
```
|
22
|
+
git clone https://github.com/madis/flicket.git
|
23
|
+
cd flicket
|
24
|
+
bundle install
|
25
|
+
rake build
|
26
|
+
gem install pkg/flicket.gem
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
1. Obtain flickr API key from https://www.flickr.com/services/apps/create/apply
|
32
|
+
2. Export them as environment variables
|
33
|
+
```bash
|
34
|
+
export FLICKR_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Replace with your key
|
35
|
+
export FLICKR_SECRET=xxxxxxxxxxxxxxxx # Replace with your secret
|
36
|
+
```
|
37
|
+
|
38
|
+
3. After building and installing the gem: `flicket [keyword ...]`
|
39
|
+
4. Or inside the gem's source folder (after installing dependencies with `bundle install`): `./bin/flicket [keyword ...]`
|
40
|
+
|
41
|
+
> NB! Output filename can be provided with `--output or -o` e.g. `flicket -o mycollage.png tree river bratwurst`
|
42
|
+
|
43
|
+
## Development
|
44
|
+
|
45
|
+
Most of the code is covered with tests using RSpec. [VCR](https://github.com/vcr/vcr) is used to avoid hitting Flickr api every time and to speed up tests.
|
46
|
+
|
47
|
+
[ImageMagick](http://www.imagemagick.org/) must be installed too. On OS X it can be done with Homebrew `brew install imagemagick`.
|
48
|
+
|
49
|
+
|
50
|
+
1. Clone the repo `git clone https://github.com/madis/flicket`
|
51
|
+
2. Install dependencies `cd flicket && bundle install`
|
52
|
+
3. Run tests `rspec`
|
53
|
+
|
54
|
+
|
55
|
+
## Layouts
|
56
|
+
|
57
|
+
Layouts can be defined in LayoutCalculator. Example layout is defined.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
LAYOUT = [
|
61
|
+
[0,1,2,3],
|
62
|
+
[4,5,6,3],
|
63
|
+
[4,7,8,9]
|
64
|
+
]
|
65
|
+
```
|
66
|
+
|
67
|
+

|
68
|
+
|
69
|
+
|
70
|
+
The numbers in the matrix are *cell names*. To mark bigger cell, e.g. cell **4** that is double the height of single cell, use same name in consequtive rows. Layout calculator will crop and resize automatically
|
71
|
+
|
72
|
+
> NB The LayoutCalculator may not handle more complex layouts correctly in its initial incarnation. It is mainly here as a starting point and a direction for future development.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "flicket"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/flicket
ADDED
data/bin/setup
ADDED
data/docs/layout-10.jpg
ADDED
Binary file
|
data/flicket.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'flicket/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "flicket"
|
8
|
+
spec.version = Flicket::VERSION
|
9
|
+
spec.authors = ["Madis Nõmme"]
|
10
|
+
spec.email = ["madis.nomme@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Command line tool to create collages of flickr images}
|
13
|
+
spec.homepage = "http://github.com/madis/flicket"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "bin"
|
18
|
+
spec.executables = "flicket"
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'flickraw', '0.9.8'
|
22
|
+
spec.add_dependency 'mini_magick', '~>4.5.1'
|
23
|
+
spec.add_development_dependency 'awesome_print', '~>1.7'
|
24
|
+
spec.add_development_dependency 'pry', '~>0.10.3'
|
25
|
+
spec.add_development_dependency 'vcr', '~>3.0.3'
|
26
|
+
spec.add_development_dependency 'webmock', '~>2.1.0'
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
+
end
|
data/lib/flicket.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'flickraw'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require "flicket/version"
|
6
|
+
require 'flicket/environment'
|
7
|
+
require 'flicket/app'
|
8
|
+
require 'flicket/flicker_query'
|
9
|
+
require 'flicket/downloader'
|
10
|
+
require 'flicket/keyword_source'
|
11
|
+
require 'flicket/collage_maker'
|
12
|
+
require 'flicket/storage'
|
13
|
+
|
14
|
+
module Flicket
|
15
|
+
|
16
|
+
class CLI
|
17
|
+
def self.start(args)
|
18
|
+
puts "Flicket #{VERSION}\n"
|
19
|
+
keywords, options = parse_args(args)
|
20
|
+
puts "Got keywords: #{keywords} options: #{options}"
|
21
|
+
puts "Using initial keywords #{keywords.join(' ')}"
|
22
|
+
env = setup_env(keywords, options)
|
23
|
+
App.new(env).call
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse_args(args)
|
27
|
+
options = {}
|
28
|
+
parser = OptionParser.new do |opts|
|
29
|
+
opts.banner = "Usage: flicket [options] [keywords...]"
|
30
|
+
opts.on("-o", "--output [FILENAME]", "Output filename to store collage") do |o|
|
31
|
+
options[:output] = o
|
32
|
+
end
|
33
|
+
end
|
34
|
+
parser.parse!(args)
|
35
|
+
keywords = args
|
36
|
+
[keywords, options]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.setup_env(keywords, options={})
|
40
|
+
flicker_query = setup_flicker_query
|
41
|
+
keyword_source = setup_keyword_source
|
42
|
+
keyword_source.add_user_words keywords
|
43
|
+
|
44
|
+
downloader = Downloader.new
|
45
|
+
collage_maker = CollageMaker.new
|
46
|
+
storage = Storage.new
|
47
|
+
storage.output_filename = options[:output]
|
48
|
+
Environment.new(
|
49
|
+
flicker_query: flicker_query,
|
50
|
+
downloader: downloader,
|
51
|
+
keyword_source: keyword_source,
|
52
|
+
collage_maker: collage_maker,
|
53
|
+
storage: storage)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.setup_keyword_source(dictionary='/usr/share/dict/words')
|
57
|
+
if File.exist?(dictionary)
|
58
|
+
keyword_source = KeywordSource.new(dictionary)
|
59
|
+
else
|
60
|
+
STDERR.puts "Exiting as dictionary not found at #{dictionary}"
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.setup_flicker_query(key=ENV['FLICKR_KEY'], secret=ENV['FLICKR_SECRET'])
|
66
|
+
if key != nil && secret != nil
|
67
|
+
FlickRaw.api_key = key
|
68
|
+
FlickRaw.shared_secret = secret
|
69
|
+
FlickerQuery.build(FlickRaw::Flickr.new)
|
70
|
+
else
|
71
|
+
STDERR.puts "Exiting because environment variables FLICKR_KEY and FLICKR_SECRET were not set"
|
72
|
+
exit 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/flicket/app.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Flicket
|
2
|
+
class App
|
3
|
+
MAX_KEYWORD_RETRIES = 10
|
4
|
+
|
5
|
+
class ApplicationError < StandardError; end
|
6
|
+
|
7
|
+
attr_reader :env
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@env = env
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
image_paths = []
|
15
|
+
image_paths << obtain_image while image_paths.count < 10
|
16
|
+
make_collage(image_paths)
|
17
|
+
rescue ApplicationError => e
|
18
|
+
puts "Application could not continue. #{e.message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def obtain_image
|
22
|
+
tries ||= MAX_KEYWORD_RETRIES
|
23
|
+
|
24
|
+
keyword = env.keyword_source.call
|
25
|
+
image_url = env.flicker_query.call(keyword)
|
26
|
+
downloaded_image = env.downloader.call(image_url)
|
27
|
+
rescue NoImageForKeyword => e
|
28
|
+
puts "Image not found for keyword '#{keyword}'. Message: #{e.message}. Retrying"
|
29
|
+
if (tries -= 1) > 0
|
30
|
+
retry
|
31
|
+
else
|
32
|
+
raise ApplicationError, "Failed getting image after retrying #{MAX_KEYWORD_RETRIES} times"
|
33
|
+
end
|
34
|
+
rescue DownloadError => e
|
35
|
+
raise ApplicationError, e.message
|
36
|
+
end
|
37
|
+
|
38
|
+
def make_collage(images)
|
39
|
+
env.storage.call(env.collage_maker.call(images))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'mini_magick'
|
2
|
+
require 'flicket/layout_calculator'
|
3
|
+
|
4
|
+
module Flicket
|
5
|
+
class CollageMaker
|
6
|
+
attr_reader :layout
|
7
|
+
|
8
|
+
def initialize(layout=LayoutCalculator.new)
|
9
|
+
@layout = layout
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(image_paths)
|
13
|
+
images = image_paths.map { |p| MiniMagick::Image.open p.path }
|
14
|
+
min_height = images.min_by(&:height).height
|
15
|
+
min_width = images.min_by(&:width).width
|
16
|
+
canvas_tempfile = Tempfile.new ['collage_maker', '.png']
|
17
|
+
canvas = create_new_image(path: canvas_tempfile.path)
|
18
|
+
|
19
|
+
resized_images = image_paths.map.with_index do |path, idx|
|
20
|
+
image = MiniMagick::Image.open path.path
|
21
|
+
image.crop(layout.crop_rect(idx, image.width, image.height).to_minimagick)
|
22
|
+
image.resize(layout.resize_rect(idx, image.width, image.height).to_minimagick)
|
23
|
+
|
24
|
+
add_image_to_canvas(image, layout.cell_rect(idx), canvas_tempfile)
|
25
|
+
end
|
26
|
+
canvas_tempfile
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def add_image_to_canvas(image, rect, canvas_tempfile)
|
32
|
+
canvas = MiniMagick::Image.new canvas_tempfile.path
|
33
|
+
result = canvas.composite(image) do |c|
|
34
|
+
puts "Adding image to collage #{image}"
|
35
|
+
c.compose 'Over'
|
36
|
+
c.geometry rect.to_minimagick
|
37
|
+
end
|
38
|
+
result.write canvas_tempfile.path
|
39
|
+
canvas_tempfile.rewind
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_new_image(width:layout.width, height:layout.height, path:)
|
43
|
+
MiniMagick::Tool::Convert.new do |i|
|
44
|
+
i.size "#{width}x#{height}"
|
45
|
+
i.xc "white"
|
46
|
+
i << path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Flicket
|
5
|
+
class DownloadError < StandardError; end
|
6
|
+
|
7
|
+
class Downloader
|
8
|
+
def call(url)
|
9
|
+
download_to_temp_file(url)
|
10
|
+
rescue OpenURI::HTTPError => e
|
11
|
+
raise DownloadError, "Unable to download from #{url} #{e.message}"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def download_to_temp_file(url)
|
17
|
+
tempfile = Tempfile.new 'flicket-image'
|
18
|
+
IO.copy_stream(open(url, read_timeout: 5), tempfile)
|
19
|
+
tempfile.rewind
|
20
|
+
puts "Downloaded to #{tempfile.path}"
|
21
|
+
tempfile
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Flicket
|
2
|
+
class Environment
|
3
|
+
attr_reader :flicker_query, :downloader, :keyword_source, :collage_maker, :storage
|
4
|
+
|
5
|
+
def initialize(params={})
|
6
|
+
@flicker_query = params.fetch(:flicker_query)
|
7
|
+
@downloader = params.fetch(:downloader)
|
8
|
+
@keyword_source = params.fetch(:keyword_source)
|
9
|
+
@collage_maker = params.fetch(:collage_maker)
|
10
|
+
@storage = params.fetch(:storage)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Flicket
|
2
|
+
class NoImageForKeyword < StandardError; end
|
3
|
+
class KeywordMissing < StandardError; end
|
4
|
+
|
5
|
+
class BaseCommand
|
6
|
+
attr_reader :api
|
7
|
+
|
8
|
+
def initialize(flickraw)
|
9
|
+
@api = flickraw
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class SearchCommand < BaseCommand
|
14
|
+
def call(text)
|
15
|
+
api.photos.search({text: text, sort: 'interestingness-desc', per_page: 10})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InfoCommand < BaseCommand
|
20
|
+
def call(photo_id)
|
21
|
+
api.photos.getInfo(photo_id: photo_id)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SizesCommand < BaseCommand
|
26
|
+
def call(photo_id)
|
27
|
+
api.photos.getSizes(photo_id: photo_id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FlickerQuery
|
32
|
+
def self.build(flickraw)
|
33
|
+
search_command = SearchCommand.new(flickraw)
|
34
|
+
sizes_command = SizesCommand.new(flickraw)
|
35
|
+
new(search_command: search_command, sizes_command: sizes_command)
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(search_command:, sizes_command:)
|
39
|
+
@search_command = search_command
|
40
|
+
@sizes_command = sizes_command
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(keyword)
|
44
|
+
raise KeywordMissing, "Search keyword is required (can't be nil)" if keyword.nil?
|
45
|
+
puts "Searching images for keyword '#{keyword}'"
|
46
|
+
results = @search_command.call(keyword)
|
47
|
+
raise NoImageForKeyword if results.count == 0
|
48
|
+
get_image_url(results.first['id'])
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def get_image_url(photo_id)
|
54
|
+
sizes = @sizes_command.call(photo_id)
|
55
|
+
middle = sizes.count / 2 # Will pick middle sized image from the ones available
|
56
|
+
sizes[middle]['source']
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Flicket
|
2
|
+
class KeywordSource
|
3
|
+
def initialize(dictionary_path)
|
4
|
+
@dictionary_path = dictionary_path
|
5
|
+
@user_words = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
next_user_word || random_dictionary_word
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_user_words(words)
|
13
|
+
@user_words += words
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def next_user_word
|
19
|
+
@user_words.shift
|
20
|
+
end
|
21
|
+
|
22
|
+
# Based on http://goo.gl/HbJbR2
|
23
|
+
def random_dictionary_word
|
24
|
+
chosen_line = nil
|
25
|
+
File.foreach(@dictionary_path).each_with_index do |line, number|
|
26
|
+
chosen_line = line if rand < 1.0 / (number + 1)
|
27
|
+
end
|
28
|
+
chosen_line.strip
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'flicket/rectangle'
|
2
|
+
|
3
|
+
module Flicket
|
4
|
+
class LayoutCalculator
|
5
|
+
WIDTH = 2304
|
6
|
+
HEIGHT = 1440
|
7
|
+
LAYOUT = [
|
8
|
+
[0,1,2,3],
|
9
|
+
[4,5,6,3],
|
10
|
+
[4,7,8,9]
|
11
|
+
]
|
12
|
+
|
13
|
+
def rows
|
14
|
+
LAYOUT.size
|
15
|
+
end
|
16
|
+
|
17
|
+
def columns
|
18
|
+
LAYOUT.max_by(&:length).length
|
19
|
+
end
|
20
|
+
|
21
|
+
def row_cells
|
22
|
+
LAYOUT
|
23
|
+
end
|
24
|
+
|
25
|
+
def column_cells
|
26
|
+
LAYOUT.transpose
|
27
|
+
end
|
28
|
+
|
29
|
+
def cell_width(name)
|
30
|
+
size_multiplier = repeated_matches(LAYOUT, name)
|
31
|
+
calculate_cell_size(WIDTH, columns, size_multiplier)
|
32
|
+
end
|
33
|
+
|
34
|
+
def cell_height(name)
|
35
|
+
size_multiplier = repeated_matches(column_cells, name)
|
36
|
+
calculate_cell_size(HEIGHT, rows, size_multiplier)
|
37
|
+
end
|
38
|
+
|
39
|
+
def crop_rect(cell_name, image_width, image_height)
|
40
|
+
image = Rectangle.new(image_width, image_height)
|
41
|
+
cell = cell_rect(cell_name)
|
42
|
+
|
43
|
+
target_x = 0
|
44
|
+
target_y = 0
|
45
|
+
|
46
|
+
if image.aspect_ratio >= cell.aspect_ratio # image is wider, make it narrower
|
47
|
+
crop_size = image.width - (image.height * cell.aspect_ratio)
|
48
|
+
target_width = image_width - crop_size
|
49
|
+
target_height = image.height
|
50
|
+
else # image is narrower, cut it shorter (thus it becoming relatively wider)
|
51
|
+
crop_size = image.height - (image.width / cell.aspect_ratio)
|
52
|
+
target_width = image.width
|
53
|
+
target_height = image.height - crop_size
|
54
|
+
end
|
55
|
+
|
56
|
+
crop_rect = Rectangle.new(target_x, target_y, target_width, target_height)
|
57
|
+
crop_rect
|
58
|
+
end
|
59
|
+
|
60
|
+
def resize_rect(cell_name, image_width, image_height)
|
61
|
+
image = Rectangle.new(image_width, image_height)
|
62
|
+
cell_rect(cell_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def cell_rect(name)
|
66
|
+
row, column = cell_coordinates(name)
|
67
|
+
width = WIDTH / columns
|
68
|
+
height = HEIGHT / rows
|
69
|
+
|
70
|
+
Rectangle.new(column*width, row*height, cell_width(name), cell_height(name))
|
71
|
+
end
|
72
|
+
|
73
|
+
def height
|
74
|
+
HEIGHT
|
75
|
+
end
|
76
|
+
|
77
|
+
def width
|
78
|
+
WIDTH
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def cell_coordinates(name)
|
84
|
+
row = LAYOUT.find_index {|row| row.include? name }
|
85
|
+
column = LAYOUT.transpose.find_index {|row| row.include? name }
|
86
|
+
[row, column]
|
87
|
+
end
|
88
|
+
|
89
|
+
def repeated_matches(table, name)
|
90
|
+
wideness_multiplier = table.map {|row| row.count(name)}.max
|
91
|
+
end
|
92
|
+
|
93
|
+
def calculate_cell_size(total, divisions, multiplier)
|
94
|
+
if multiplier == 0
|
95
|
+
0
|
96
|
+
else
|
97
|
+
(total / divisions) * multiplier
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Flicket
|
2
|
+
class Rectangle
|
3
|
+
attr_reader :x, :y, :width, :height
|
4
|
+
|
5
|
+
def initialize(x=0, y=0, width, height)
|
6
|
+
@x = x
|
7
|
+
@y = y
|
8
|
+
@width = width
|
9
|
+
@height = height
|
10
|
+
end
|
11
|
+
|
12
|
+
def aspect_ratio
|
13
|
+
width.to_f / height
|
14
|
+
end
|
15
|
+
|
16
|
+
def landscape?(width, height)
|
17
|
+
aspect_ratio >= 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def portrait?(width, height)
|
21
|
+
!landscape?
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_minimagick
|
25
|
+
"#{width}x#{height}+#{x}+#{y}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(o)
|
29
|
+
o.class == self.class && o.state == state
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def state
|
35
|
+
[@x, @y, @width, @height]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Flicket
|
5
|
+
class Storage
|
6
|
+
|
7
|
+
attr_accessor :output_filename
|
8
|
+
|
9
|
+
def call(file_path)
|
10
|
+
destination = collage_filename
|
11
|
+
FileUtils.mv file_path, destination
|
12
|
+
puts "Finished collage is available at #{destination}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def collage_filename
|
18
|
+
output_filename || ask_filename_from_user
|
19
|
+
end
|
20
|
+
|
21
|
+
def ask_filename_from_user
|
22
|
+
default = dated_filename
|
23
|
+
puts "Enter file name for collage (press ENTER to use '#{default}')"
|
24
|
+
filename = STDIN.gets.strip
|
25
|
+
if filename.empty?
|
26
|
+
default
|
27
|
+
else
|
28
|
+
filename
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def dated_filename
|
33
|
+
now = Date.new
|
34
|
+
timestamp = now.strftime('%Y%m%d%H%M%S')
|
35
|
+
"collage-#{timestamp}.png"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flicket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Madis Nõmme
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: flickraw
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mini_magick
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.5.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.5.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: awesome_print
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.10.3
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.10.3
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: vcr
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.0.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.11'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.11'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.0'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- madis.nomme@gmail.com
|
142
|
+
executables:
|
143
|
+
- flicket
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- ".ruby-gemset"
|
150
|
+
- ".ruby-version"
|
151
|
+
- ".travis.yml"
|
152
|
+
- Gemfile
|
153
|
+
- LICENSE.txt
|
154
|
+
- README.md
|
155
|
+
- Rakefile
|
156
|
+
- bin/console
|
157
|
+
- bin/flicket
|
158
|
+
- bin/setup
|
159
|
+
- docs/layout-10.jpg
|
160
|
+
- flicket.gemspec
|
161
|
+
- lib/flicket.rb
|
162
|
+
- lib/flicket/app.rb
|
163
|
+
- lib/flicket/collage_maker.rb
|
164
|
+
- lib/flicket/downloader.rb
|
165
|
+
- lib/flicket/environment.rb
|
166
|
+
- lib/flicket/flicker_query.rb
|
167
|
+
- lib/flicket/keyword_source.rb
|
168
|
+
- lib/flicket/layout_calculator.rb
|
169
|
+
- lib/flicket/rectangle.rb
|
170
|
+
- lib/flicket/storage.rb
|
171
|
+
- lib/flicket/version.rb
|
172
|
+
homepage: http://github.com/madis/flicket
|
173
|
+
licenses:
|
174
|
+
- MIT
|
175
|
+
metadata: {}
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
requirements: []
|
191
|
+
rubyforge_project:
|
192
|
+
rubygems_version: 2.5.1
|
193
|
+
signing_key:
|
194
|
+
specification_version: 4
|
195
|
+
summary: Command line tool to create collages of flickr images
|
196
|
+
test_files: []
|