flickrage 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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.rubocop.yml +50 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +188 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/flickrage +13 -0
- data/bin/setup +8 -0
- data/flickrage.gemspec +43 -0
- data/lib/flickrage.rb +183 -0
- data/lib/flickrage/cli.rb +228 -0
- data/lib/flickrage/entity.rb +7 -0
- data/lib/flickrage/entity/image.rb +42 -0
- data/lib/flickrage/entity/image_list.rb +66 -0
- data/lib/flickrage/helpers.rb +80 -0
- data/lib/flickrage/log.rb +88 -0
- data/lib/flickrage/pipeline.rb +64 -0
- data/lib/flickrage/service.rb +9 -0
- data/lib/flickrage/service/composer.rb +51 -0
- data/lib/flickrage/service/downloader.rb +63 -0
- data/lib/flickrage/service/resizer.rb +59 -0
- data/lib/flickrage/service/search.rb +53 -0
- data/lib/flickrage/types.rb +6 -0
- data/lib/flickrage/version.rb +4 -0
- data/lib/flickrage/worker.rb +10 -0
- data/lib/flickrage/worker/base.rb +73 -0
- data/lib/flickrage/worker/compose.rb +66 -0
- data/lib/flickrage/worker/download.rb +87 -0
- data/lib/flickrage/worker/resize.rb +85 -0
- data/lib/flickrage/worker/search.rb +106 -0
- data/log/.keep +0 -0
- data/tmp/.keep +0 -0
- metadata +322 -0
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'thor'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'flickraw'
|
5
|
+
require 'flickrage'
|
6
|
+
|
7
|
+
module Flickrage
|
8
|
+
# CLI is here
|
9
|
+
#
|
10
|
+
class CLI < Thor # rubocop:disable ClassLength
|
11
|
+
include Flickrage::Helpers::Log
|
12
|
+
|
13
|
+
default_task :help
|
14
|
+
|
15
|
+
map %w(c) => :collage
|
16
|
+
map %w(-V) => :version
|
17
|
+
|
18
|
+
# Overriding Thor method for custom initialization
|
19
|
+
#
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
|
23
|
+
setup_config
|
24
|
+
set_logger
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'c / collage', 'Download & generate collage\'s'
|
28
|
+
long_desc <<-LONGDESC
|
29
|
+
`flickrage` is a tool which loves search on the Flickr & making collages from findings.
|
30
|
+
|
31
|
+
You have to enter name of the output file and a max number of downloading files.
|
32
|
+
|
33
|
+
Parameters helps you specify rectangle size for each image, collage name, it's location,
|
34
|
+
..., and well the grid base size.
|
35
|
+
|
36
|
+
### Examples
|
37
|
+
|
38
|
+
Set keywords:
|
39
|
+
|
40
|
+
$ flickrage -k some nice grapefruit
|
41
|
+
|
42
|
+
Set Flickr API keys:
|
43
|
+
|
44
|
+
$ flickrage --flickr-api-key SOMELONGKEY --flickr-shared-secret SOMELONGSECRET
|
45
|
+
|
46
|
+
Select output folder:
|
47
|
+
|
48
|
+
$ flickrage -k some nice grapefruit -o ./tmp
|
49
|
+
|
50
|
+
Enter collage file_name:
|
51
|
+
|
52
|
+
$ flickrage -k some nice grapefruit --file-name some.jpg
|
53
|
+
|
54
|
+
Get collage of top 10 images:
|
55
|
+
|
56
|
+
$ flickrage -k some nice grapefruit --max 10
|
57
|
+
|
58
|
+
Get collage of top 20 images:
|
59
|
+
|
60
|
+
$ flickrage -k some nice grapefruit --max 20
|
61
|
+
|
62
|
+
Get collage of top 10 images custom width & height:
|
63
|
+
|
64
|
+
$ flickrage -k some nice grapefruit --max 10 --width 160 --height 120
|
65
|
+
|
66
|
+
VERSION: #{Flickrage::VERSION}
|
67
|
+
LONGDESC
|
68
|
+
method_option :keywords,
|
69
|
+
type: :array,
|
70
|
+
required: true,
|
71
|
+
aliases: %w(-k),
|
72
|
+
banner: 'some nice grapefruit'
|
73
|
+
method_option :max,
|
74
|
+
type: :numeric,
|
75
|
+
enum: (1..20).to_a,
|
76
|
+
default: 10,
|
77
|
+
banner: '10',
|
78
|
+
desc: 'Select number of files.'
|
79
|
+
method_option :grid,
|
80
|
+
type: :numeric,
|
81
|
+
banner: '2',
|
82
|
+
desc: 'Select grid base number.'
|
83
|
+
method_option :width,
|
84
|
+
type: :numeric,
|
85
|
+
banner: '120',
|
86
|
+
desc: 'Set width for resize downloaded images.'
|
87
|
+
method_option :height,
|
88
|
+
type: :numeric,
|
89
|
+
banner: '80',
|
90
|
+
desc: 'Set height for resize downloaded images.'
|
91
|
+
method_option :log,
|
92
|
+
type: :string,
|
93
|
+
aliases: %w(-l),
|
94
|
+
banner: '/Users/someone/.flickrage/main.log',
|
95
|
+
desc: 'Log file path. By default logging is disabled.'
|
96
|
+
method_option :output,
|
97
|
+
type: :string,
|
98
|
+
aliases: %w(-o),
|
99
|
+
banner: './tmp',
|
100
|
+
desc: 'Output directory, where all data will be stored.'
|
101
|
+
method_option :file_name,
|
102
|
+
type: :string,
|
103
|
+
banner: './some.png',
|
104
|
+
desc: 'Name for file with collage.'
|
105
|
+
method_option :dict_path,
|
106
|
+
type: :string,
|
107
|
+
banner: '/usr/share/dict/words',
|
108
|
+
desc: 'Path to file with multiline words (dictionary).'
|
109
|
+
method_option :cleanup,
|
110
|
+
default: false,
|
111
|
+
type: :boolean,
|
112
|
+
aliases: %w(-c),
|
113
|
+
desc: 'Cleanup files before collage.'
|
114
|
+
method_option :verbose,
|
115
|
+
type: :boolean,
|
116
|
+
aliases: %w(-v),
|
117
|
+
desc: 'Verbose mode.'
|
118
|
+
method_option :quiet,
|
119
|
+
type: :boolean,
|
120
|
+
aliases: %w(-q),
|
121
|
+
desc: 'Quiet mode. If don\'t need any messages and in console.'
|
122
|
+
|
123
|
+
method_option :flickr_api_key,
|
124
|
+
type: :string,
|
125
|
+
banner: 'YOURLONGAPIKEY',
|
126
|
+
desc: 'FLICKR_API_KEY. if you can\'t use environment.'
|
127
|
+
method_option :flickr_shared_secret,
|
128
|
+
type: :string,
|
129
|
+
banner: 'YOURLONGSHAREDSECRET',
|
130
|
+
desc: 'FLICKR_SHARED_SECRET. if you can\'t use environment.'
|
131
|
+
|
132
|
+
def collage
|
133
|
+
cleanup if options['cleanup']
|
134
|
+
try_keys_first
|
135
|
+
init_flickraw
|
136
|
+
|
137
|
+
pipeline.run
|
138
|
+
rescue Flickrage::NoKeysError, Flickrage::SearchError, Flickrage::DownloadError,
|
139
|
+
Flickrage::NumberError, Flickrage::PathError => e
|
140
|
+
error_simple(e)
|
141
|
+
rescue => e
|
142
|
+
error_with_backtrace(e)
|
143
|
+
end
|
144
|
+
|
145
|
+
desc 'clean', 'Cleanup folder'
|
146
|
+
method_option :output,
|
147
|
+
type: :string,
|
148
|
+
required: true,
|
149
|
+
aliases: %w(-o),
|
150
|
+
banner: './some.png'
|
151
|
+
def clean
|
152
|
+
cleanup
|
153
|
+
end
|
154
|
+
|
155
|
+
desc 'version, -V', 'Shows the version of the currently installed Flickrage gem'
|
156
|
+
def version
|
157
|
+
puts Flickrage::VERSION
|
158
|
+
end
|
159
|
+
|
160
|
+
no_commands do
|
161
|
+
def error_simple(e)
|
162
|
+
logger.error e.message
|
163
|
+
raise e
|
164
|
+
end
|
165
|
+
|
166
|
+
def error_with_backtrace(e)
|
167
|
+
logger.error e.message
|
168
|
+
backtrace(e) if options.include? 'trace'
|
169
|
+
raise e
|
170
|
+
end
|
171
|
+
|
172
|
+
def pipeline
|
173
|
+
@pipeline ||= Pipeline.new(options)
|
174
|
+
end
|
175
|
+
|
176
|
+
def cleanup
|
177
|
+
return unless options['output']
|
178
|
+
return unless Dir.exist?(options['output'])
|
179
|
+
FileUtils.rm_rf("#{options['output']}/.", secure: true)
|
180
|
+
end
|
181
|
+
|
182
|
+
def setup_config
|
183
|
+
Flickrage.configure do |config|
|
184
|
+
config.logger = ::Logger.new(options['log']) if options['log']
|
185
|
+
config.verbose = options['verbose']
|
186
|
+
config.quiet = options['quiet']
|
187
|
+
config.logger_level = Logger::DEBUG if config.verbose
|
188
|
+
|
189
|
+
config.flickr_api_key = options['flickr_api_key'] || ENV['FLICKR_API_KEY']
|
190
|
+
config.flickr_shared_secret = options['flickr_shared_secret'] || ENV['FLICKR_SHARED_SECRET']
|
191
|
+
|
192
|
+
config.dict_path = options['dict_path'] if options['dict_path']
|
193
|
+
|
194
|
+
config.width = options['width'] if options['width']
|
195
|
+
config.height = options['height'] if options['height']
|
196
|
+
|
197
|
+
config.max = options['max'] if options['max']
|
198
|
+
config.grid = options['grid'] if options['grid']
|
199
|
+
config.output = options['output']
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_logger
|
204
|
+
Flickrage.logger = Log.new(shell: shell)
|
205
|
+
end
|
206
|
+
|
207
|
+
def backtrace(e)
|
208
|
+
e.backtrace.each do |t|
|
209
|
+
logger.error t
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def init_flickraw
|
214
|
+
FlickRaw.api_key = Flickrage.config.flickr_api_key
|
215
|
+
FlickRaw.shared_secret = Flickrage.config.flickr_shared_secret
|
216
|
+
end
|
217
|
+
|
218
|
+
# Check for Flickr API keys
|
219
|
+
def try_keys_first
|
220
|
+
logger.debug 'Checking Flickr Key\'s.'
|
221
|
+
|
222
|
+
return if Flickrage.api_keys?
|
223
|
+
logger.error Flickrage::NoKeysError.new('You must provide Flickr API credentials via --flickr-api-key, --flickr-shared-secret via options. or have it in the environment')
|
224
|
+
exit
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Flickrage
|
3
|
+
module Entity
|
4
|
+
class Image < Dry::Types::Struct
|
5
|
+
constructor_type(:schema)
|
6
|
+
|
7
|
+
attribute :id, Types::Coercible::Int
|
8
|
+
attribute :title, Types::Coercible::String
|
9
|
+
attribute :keyword, Types::Coercible::String
|
10
|
+
attribute :url, Types::Coercible::String
|
11
|
+
attribute :file_name, Types::Coercible::String
|
12
|
+
attribute :width, Types::Coercible::Int
|
13
|
+
attribute :height, Types::Coercible::Int
|
14
|
+
|
15
|
+
attribute :download, Types::Bool.default(false)
|
16
|
+
attribute :resize, Types::Bool.default(false)
|
17
|
+
|
18
|
+
alias downloaded? download
|
19
|
+
alias resized? resize
|
20
|
+
|
21
|
+
%w(download resize).each do |m|
|
22
|
+
define_method(:"finish_#{m}") do
|
23
|
+
instance_variable_set(:"@#{m}", true)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def local_path
|
29
|
+
File.absolute_path("#{Flickrage.config.output}/#{file_name}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def resize_path
|
33
|
+
File.absolute_path("#{Flickrage.config.output}/#{Flickrage.config.resize_file_prefix}#{file_name}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def file_name=(file_name)
|
37
|
+
@file_name = file_name
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Flickrage
|
3
|
+
module Entity
|
4
|
+
class ImageList < Dry::Types::Struct
|
5
|
+
constructor_type(:schema)
|
6
|
+
|
7
|
+
attribute :images, Types::Strict::Array.member(Flickrage::Entity::Image).default([])
|
8
|
+
attribute :not_founds, Types::Strict::Array.member(Types::Coercible::String).default([])
|
9
|
+
attribute :total, Types::Int.optional.default(0)
|
10
|
+
attribute :compose, Types::Bool.default(false)
|
11
|
+
attribute :collage_path, Types::Coercible::String
|
12
|
+
|
13
|
+
alias composed? compose
|
14
|
+
|
15
|
+
def downloaded
|
16
|
+
images.select(&:downloaded?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def resized
|
20
|
+
images.select(&:resized?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def finish_compose
|
24
|
+
@compose = true
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def combine(image_list)
|
29
|
+
new_images = images + image_list
|
30
|
+
new_total = new_images.size
|
31
|
+
@images = new_images
|
32
|
+
@total = new_total
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def clean
|
37
|
+
@images = images.compact
|
38
|
+
@total = images.size
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?
|
43
|
+
total == Flickrage.config.max
|
44
|
+
end
|
45
|
+
|
46
|
+
def merge_not_founds(new_not_founds)
|
47
|
+
@not_founds = not_founds + new_not_founds
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def merge_images(new_images)
|
52
|
+
@images = images | new_images
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def size
|
57
|
+
images.size
|
58
|
+
end
|
59
|
+
|
60
|
+
def collage_path=(file_name = nil)
|
61
|
+
file_name = "collage.#{Time.now.to_i}.jpg" if file_name.nil?
|
62
|
+
@collage_path = File.absolute_path("#{Flickrage.config.output}/#{file_name}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'tty-spinner'
|
3
|
+
require 'pastel'
|
4
|
+
|
5
|
+
module Flickrage
|
6
|
+
# Helpers for main class.
|
7
|
+
#
|
8
|
+
module Helpers
|
9
|
+
module Log
|
10
|
+
def logger
|
11
|
+
UniversalLogger
|
12
|
+
end
|
13
|
+
|
14
|
+
def speaker
|
15
|
+
UniversalSpeaker
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Dict
|
20
|
+
def sample_words(n = 1)
|
21
|
+
Flickrage.dict.sample(n).map(&:strip)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sample_words_strict(n = 1, except: [])
|
25
|
+
return [] if Flickrage.dict.size < (n + except.size)
|
26
|
+
Flickrage.dict
|
27
|
+
.sample(n + except.size)
|
28
|
+
.map(&:strip)
|
29
|
+
.-(except)
|
30
|
+
.first(n)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Tty
|
35
|
+
def spinner(message: '', format: :dots)
|
36
|
+
spin = TTY::Spinner.new('[:spinner] :title',
|
37
|
+
format: format,
|
38
|
+
interval: 20,
|
39
|
+
hide_cursor: true,
|
40
|
+
success_mark: color(color: :green, message: '+'),
|
41
|
+
error_mark: color(color: :red, message: 'x'))
|
42
|
+
spin.update(title: message)
|
43
|
+
spin.start
|
44
|
+
|
45
|
+
return spin unless block_given?
|
46
|
+
|
47
|
+
yield(spin)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def pastel
|
53
|
+
@pastel ||= Pastel.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def color(color: :green, message: ' ')
|
57
|
+
pastel.send(color, message)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# UniversalLogger is a module to deal with singleton methods.
|
62
|
+
# Used to give classes access only for selected methods
|
63
|
+
#
|
64
|
+
module UniversalLogger
|
65
|
+
%w(debug info warn error fatal unknown).each do |name|
|
66
|
+
define_singleton_method(:"#{name}") { |*args, &block| Flickrage.logger.send(:"#{name}", *args, &block) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.close
|
70
|
+
Flickrage.logger.close if Flickrage.logger
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module UniversalSpeaker
|
75
|
+
%w(ask yes? no? print_table add_padding).each do |name|
|
76
|
+
define_singleton_method(:"#{name}") { |*args| Flickrage.logger.send(:"#{name}", *args) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Flickrage
|
5
|
+
# Shared logger
|
6
|
+
#
|
7
|
+
class Log
|
8
|
+
attr_reader :shell
|
9
|
+
attr_accessor :quiet, :verbose
|
10
|
+
attr_writer :buffer, :instance
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@verbose = Flickrage.config.verbose
|
14
|
+
@quiet = Flickrage.config.quiet
|
15
|
+
options.each { |key, option| instance_variable_set(:"@#{key}", option) }
|
16
|
+
instance.level = Flickrage.config.logger_level if instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def instance
|
20
|
+
@instance ||= Flickrage.config.logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def buffer
|
24
|
+
@buffer ||= %w()
|
25
|
+
end
|
26
|
+
|
27
|
+
def shell=(shell)
|
28
|
+
@shell = shell unless quiet
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
instance.close if instance
|
33
|
+
end
|
34
|
+
|
35
|
+
%w(debug info warn error fatal unknown).each_with_index do |name, severity|
|
36
|
+
define_method(:"#{name}") { |*args, &block| log severity, *args, &block }
|
37
|
+
end
|
38
|
+
|
39
|
+
%w(yes? no?).each do |name|
|
40
|
+
define_method(:"#{name}") do |statement, color = :green|
|
41
|
+
shell.send(:"#{name}", statement, color) if shell
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def print_table(*args)
|
46
|
+
args[0]&.each { |row| instance.info(row) } if instance
|
47
|
+
shell.print_table(*args) if shell
|
48
|
+
end
|
49
|
+
|
50
|
+
def ask(statement, color: :green, path: false)
|
51
|
+
shell.ask(statement, color, path: path) if shell
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_padding
|
55
|
+
shell.say_status('', '') if shell
|
56
|
+
end
|
57
|
+
|
58
|
+
def log(severity, message = nil, progname = nil, &block)
|
59
|
+
buffer << message
|
60
|
+
instance.add(severity, message, progname, &block) if instance.respond_to?(:add)
|
61
|
+
|
62
|
+
say(message, color(severity)) unless print?(severity)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def print?(type)
|
68
|
+
(type == Logger::DEBUG && !verbose) || quiet
|
69
|
+
end
|
70
|
+
|
71
|
+
def say(message, color)
|
72
|
+
shell.say message, color if shell
|
73
|
+
end
|
74
|
+
|
75
|
+
def color(severity)
|
76
|
+
case severity
|
77
|
+
when 0
|
78
|
+
:white
|
79
|
+
when 3
|
80
|
+
:red
|
81
|
+
when 2
|
82
|
+
:yellow
|
83
|
+
else
|
84
|
+
:green
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|