photo-helper 0.2.10 → 0.3
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 +4 -4
- data/Gemfile.lock +1 -1
- data/bin/photo-helper +1 -0
- data/lib/helpers/file_helper.rb +3 -1
- data/lib/helpers/image_helper.rb +13 -4
- data/lib/helpers/smugmug_album.rb +233 -0
- data/lib/helpers/smugmug_api.rb +45 -33
- data/lib/photo-helper/compress.rb +14 -13
- data/lib/photo-helper/screensaver.rb +30 -0
- data/lib/photo-helper/smugmug.rb +11 -11
- data/lib/photo-helper/version.rb +3 -2
- data/lib/photo_helper.rb +12 -7
- data/photo-helper.gemspec +2 -2
- metadata +4 -3
- data/lib/helpers/smugmug.rb +0 -84
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4bbf183dc81a5e29d5e0a0d8007073494144da3
|
|
4
|
+
data.tar.gz: 53085441c3aec638bd8b4a5e6362dd9f2de88164
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d872ce3085f8282759470f87eeb8acc04e5409d18ec958a1ee3ac557829e223d7ff09165f09fbcbafa419e87b715ee6851d5904f851c20949fe60223c47da45
|
|
7
|
+
data.tar.gz: a6cc39c361ed40180fa1c030cbec37fb4c51c30c9a1e04dde194bb8aef1f1f7d1b2f2fe3726f434f6d04c6baf1776a4b2abc6fc836fb7debe958f610b9c2b48b
|
data/Gemfile.lock
CHANGED
data/bin/photo-helper
CHANGED
data/lib/helpers/file_helper.rb
CHANGED
data/lib/helpers/image_helper.rb
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
class ImageHelper
|
|
2
|
-
IMAGE_CLASS_REGEX =
|
|
3
|
-
RATING_REGEX =
|
|
4
|
-
|
|
3
|
+
IMAGE_CLASS_REGEX = /xmp:Label="(.+)"/
|
|
4
|
+
RATING_REGEX = /xmp:Rating="(.+)"/
|
|
5
|
+
|
|
5
6
|
def self.xmp(image)
|
|
6
7
|
xmp = File.join(File.dirname(image), File.basename(image, ".*") + ".XMP")
|
|
7
|
-
return unless File.
|
|
8
|
+
return unless File.exist?(xmp)
|
|
8
9
|
File.read(xmp)
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def self.color_class(image)
|
|
12
13
|
contents = xmp(image)
|
|
14
|
+
return if contents.nil?
|
|
13
15
|
matches = contents.match(IMAGE_CLASS_REGEX)
|
|
14
16
|
matches[1] if matches
|
|
15
17
|
end
|
|
@@ -32,4 +34,11 @@ class ImageHelper
|
|
|
32
34
|
def self.is_5_star?(image)
|
|
33
35
|
rating(image) == '5'
|
|
34
36
|
end
|
|
37
|
+
|
|
38
|
+
def self.is_jpeg?(path)
|
|
39
|
+
# remove . from the beginning
|
|
40
|
+
extension = File.extname(path)[1..-1]
|
|
41
|
+
return false if extension.nil?
|
|
42
|
+
JPEG_EXTENSIONS.include? extension.downcase
|
|
43
|
+
end
|
|
35
44
|
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "helpers/smugmug_api"
|
|
3
|
+
require "helpers/image_helper"
|
|
4
|
+
require "helpers/file_helper"
|
|
5
|
+
require 'set'
|
|
6
|
+
|
|
7
|
+
class SmugmugAlbumHelper
|
|
8
|
+
attr_accessor :smugmug_api
|
|
9
|
+
|
|
10
|
+
# to figure out what to delete, read all xmp files, loop through uploaded files and check xmp file
|
|
11
|
+
|
|
12
|
+
PATH_REGEX = %r{^.+Pictures\/.+\/(\d{4})\/(\d{2})_.+\/[^_]+_([^\/]+)}
|
|
13
|
+
KEYWORD_WHITELITS = %w("instagram exported")
|
|
14
|
+
|
|
15
|
+
def initialize(search_path, album = nil)
|
|
16
|
+
@search_path = Pathname.new(search_path)
|
|
17
|
+
@smugmug = SmugmugAPI.new
|
|
18
|
+
|
|
19
|
+
@album_name = album || album_name
|
|
20
|
+
|
|
21
|
+
@album = @smugmug.get_or_create_album(@album_name, album_url: @location&.downcase)
|
|
22
|
+
|
|
23
|
+
@dl_album_name = File.join("dl", @album_name)
|
|
24
|
+
@dl_album = @smugmug.get_or_create_album(@dl_album_name, album_url: @location&.downcase)
|
|
25
|
+
|
|
26
|
+
@keyword_list = Set.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parse_path
|
|
30
|
+
if matches = "#{@search_path}/".to_s.match(PATH_REGEX)
|
|
31
|
+
@year = matches[1]
|
|
32
|
+
@month = Date::MONTHNAMES[matches[2].to_i].capitalize
|
|
33
|
+
@location = matches[3].split("_").map(&:capitalize).join(' ')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def album_name
|
|
38
|
+
parse_path
|
|
39
|
+
if @year && @month && @location
|
|
40
|
+
album_name_short = "#{@location} #{@month} #{@year}"
|
|
41
|
+
File.join(@year, @month, album_name_short)
|
|
42
|
+
else
|
|
43
|
+
puts 'Unable to determine album from path'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def image_list
|
|
48
|
+
Dir["#{@search_path}/**/*.{#{IMAGE_EXTENSIONS.join(',')}}"].reject { |p| FileHelper.ingore_file?(p) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def exported_list
|
|
52
|
+
Dir["#{@search_path}/**/{Exported,exported}/*.{#{IMAGE_EXTENSIONS.join(',')}}"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def instagram_list
|
|
56
|
+
Dir["#{@search_path}/**/{Instagram,instagram}/*.{#{IMAGE_EXTENSIONS.join(',')}}"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def merge_exported(images = image_list, concat = false)
|
|
60
|
+
exported = Dir["#{@search_path}/**/{Exported,exported}/*.{#{IMAGE_EXTENSIONS.join(',')}}"]
|
|
61
|
+
unless concat
|
|
62
|
+
exported_basenames = exported.map { |p| File.basename(p, ".*") }
|
|
63
|
+
images = images.reject { |p| exported_basenames.include? File.basename(p, ".*") }
|
|
64
|
+
end
|
|
65
|
+
images.concat(exported)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def uploaded_to_hash(album)
|
|
69
|
+
uploaded = @smugmug.images(album[:id])
|
|
70
|
+
uploaded_hash = {}
|
|
71
|
+
uploaded.each do |u|
|
|
72
|
+
filename = File.basename(u[:filename], ".*")
|
|
73
|
+
push_hash_array(uploaded_hash, filename, u)
|
|
74
|
+
end
|
|
75
|
+
uploaded_hash
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def image_list_to_hash(images)
|
|
79
|
+
image_list_hash = {}
|
|
80
|
+
images.each do |i|
|
|
81
|
+
filename = File.basename(i, ".*")
|
|
82
|
+
tags = image_dir_keywords(i)
|
|
83
|
+
@keyword_list.merge(tags) if tags
|
|
84
|
+
push_hash_array(image_list_hash, filename, file: i,
|
|
85
|
+
keywords: tags,
|
|
86
|
+
md5: Digest::MD5.file(i).hexdigest)
|
|
87
|
+
end
|
|
88
|
+
image_list_hash
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def sync(album, image_list_hash, reject_trash = true)
|
|
92
|
+
uploaded_hash = uploaded_to_hash(album)
|
|
93
|
+
|
|
94
|
+
to_upload = {}
|
|
95
|
+
to_update = {}
|
|
96
|
+
to_delete = []
|
|
97
|
+
|
|
98
|
+
image_list_hash.each do |filename, images|
|
|
99
|
+
images.each do |image|
|
|
100
|
+
next unless ImageHelper.is_jpeg?(image[:file])
|
|
101
|
+
next if reject_trash && ImageHelper.color_class(image[:file]) == "Trash"
|
|
102
|
+
|
|
103
|
+
upload_image = true
|
|
104
|
+
|
|
105
|
+
if uploaded_hash.key?(filename)
|
|
106
|
+
!uploaded_hash[filename].each do |uploaded|
|
|
107
|
+
next unless uploaded_match_requested?(image, uploaded)
|
|
108
|
+
|
|
109
|
+
# & returns if in both arrays
|
|
110
|
+
upload_image = false
|
|
111
|
+
if uploaded[:md5] != image[:md5]
|
|
112
|
+
push_hash_array(to_update, image[:keywords], image.merge!(uri: uploaded[:uri]))
|
|
113
|
+
end
|
|
114
|
+
break
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if upload_image
|
|
119
|
+
push_hash_array(to_upload, image[:keywords], image[:file])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
uploaded_hash.each do |filename, uploaded_images|
|
|
125
|
+
uploaded_images.each do |uploaded|
|
|
126
|
+
if image_list_hash.key?(filename)
|
|
127
|
+
image_hash = image_list_hash[filename].find do |image|
|
|
128
|
+
uploaded_match_requested?(image, uploaded)
|
|
129
|
+
end
|
|
130
|
+
to_delete.push(uploaded) if image_hash.nil?
|
|
131
|
+
to_delete.push(uploaded) if reject_trash && ImageHelper.color_class(image_hash[:file]) == "Trash"
|
|
132
|
+
else
|
|
133
|
+
to_delete.push(uploaded)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
to_upload.each do |keywords, images|
|
|
139
|
+
puts keywords
|
|
140
|
+
upload(album, images, keywords)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if to_delete.any?
|
|
144
|
+
puts "Deleting #{to_delete.count} images"
|
|
145
|
+
to_delete.each do |uploaded|
|
|
146
|
+
puts uploaded[:filename]
|
|
147
|
+
@smugmug.http(:delete, uploaded[:uri])
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
to_update.each do |keywords, images|
|
|
152
|
+
puts keywords
|
|
153
|
+
update(album, images, keywords)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def upload(album, pictures, keywords = nil)
|
|
158
|
+
puts "Uploading #{pictures.count} jpegs"
|
|
159
|
+
|
|
160
|
+
headers = {}
|
|
161
|
+
headers["X-Smug-Keywords"] = keywords.join(",") unless keywords.nil?
|
|
162
|
+
|
|
163
|
+
@smugmug.upload_images(pictures, album[:id], headers, workers: 8, filename_as_title: true)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def update(album, pictures, keywords = nil)
|
|
167
|
+
puts "Updating #{pictures.count} jpegs"
|
|
168
|
+
|
|
169
|
+
headers = {}
|
|
170
|
+
headers["X-Smug-Keywords"] = keywords.join(",") unless keywords.nil?
|
|
171
|
+
|
|
172
|
+
@smugmug.update_images(pictures, album[:id], headers, workers: 8, filename_as_title: true)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def upload_dl
|
|
176
|
+
@keyword_list = Set.new
|
|
177
|
+
puts "Uploading all images to album #{@album_name} --> #{@dl_album[:web_uri]}\n"
|
|
178
|
+
|
|
179
|
+
@image_list = image_list_to_hash(image_list)
|
|
180
|
+
@image_list = merge_hash_array(@image_list, image_list_to_hash(exported_list))
|
|
181
|
+
@image_list = merge_hash_array(@image_list, image_list_to_hash(instagram_list))
|
|
182
|
+
|
|
183
|
+
sync(@dl_album, @image_list, true)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def upload_select
|
|
187
|
+
@keyword_list = Set.new
|
|
188
|
+
|
|
189
|
+
pictures = image_list
|
|
190
|
+
pictures = pictures.select { |p| ImageHelper.is_select?(p) }
|
|
191
|
+
pictures = merge_exported(pictures)
|
|
192
|
+
|
|
193
|
+
puts "Uploading selects to album #{@album_name} --> #{@album[:web_uri]}\n"
|
|
194
|
+
|
|
195
|
+
@image_list = image_list_to_hash(pictures)
|
|
196
|
+
|
|
197
|
+
sync(@album, @image_list, true)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def push_hash_array(hash, key, item)
|
|
203
|
+
hash[key] = [] unless hash.key?(key)
|
|
204
|
+
hash[key].push(item)
|
|
205
|
+
hash
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def merge_hash_array(hash1, hash2)
|
|
209
|
+
hash2.each do |key, value|
|
|
210
|
+
hash1[key] = [] unless hash1.key?(key)
|
|
211
|
+
hash1[key].concat(value)
|
|
212
|
+
end
|
|
213
|
+
hash1
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def image_dir_keywords(image)
|
|
217
|
+
rel = Pathname.new(image).relative_path_from(@search_path).to_s.split("/")
|
|
218
|
+
# ignore first and last parts
|
|
219
|
+
rel &= KEYWORD_WHITELITS
|
|
220
|
+
return nil if rel.empty?
|
|
221
|
+
rel
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def uploaded_match_requested?(image, uploaded)
|
|
225
|
+
if image[:keywords].nil?
|
|
226
|
+
# empty from keyword list
|
|
227
|
+
return true if uploaded[:keywords].nil? || @keyword_list & uploaded[:keywords] == Set.new
|
|
228
|
+
else
|
|
229
|
+
return true if image[:keywords] - uploaded[:keywords] == []
|
|
230
|
+
end
|
|
231
|
+
false
|
|
232
|
+
end
|
|
233
|
+
end
|
data/lib/helpers/smugmug_api.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'helpers/secrets'
|
|
2
3
|
require 'oauth'
|
|
3
4
|
require 'uri'
|
|
@@ -7,16 +8,16 @@ require 'parallel'
|
|
|
7
8
|
|
|
8
9
|
class SmugmugAPI
|
|
9
10
|
attr_accessor :http, :uploader
|
|
10
|
-
OAUTH_ORIGIN = 'https://secure.smugmug.com'
|
|
11
|
-
REQUEST_TOKEN_URL = '/services/oauth/1.0a/getRequestToken'
|
|
12
|
-
ACCESS_TOKEN_URL = '/services/oauth/1.0a/getAccessToken'
|
|
13
|
-
AUTHORIZE_URL = '/services/oauth/1.0a/authorize'
|
|
14
|
-
API_ENDPOINT = 'https://api.smugmug.com'
|
|
15
|
-
UPLOAD_ENDPOINT = 'https://upload.smugmug.com/'
|
|
11
|
+
OAUTH_ORIGIN = 'https://secure.smugmug.com'
|
|
12
|
+
REQUEST_TOKEN_URL = '/services/oauth/1.0a/getRequestToken'
|
|
13
|
+
ACCESS_TOKEN_URL = '/services/oauth/1.0a/getAccessToken'
|
|
14
|
+
AUTHORIZE_URL = '/services/oauth/1.0a/authorize'
|
|
15
|
+
API_ENDPOINT = 'https://api.smugmug.com'
|
|
16
|
+
UPLOAD_ENDPOINT = 'https://upload.smugmug.com/'
|
|
16
17
|
|
|
17
18
|
def initialize(ejson_file = '~/.photo_helper.ejson')
|
|
18
19
|
ejson_file = File.expand_path(ejson_file)
|
|
19
|
-
@secrets = Secrets.new(ejson_file, %i
|
|
20
|
+
@secrets = Secrets.new(ejson_file, %i(api_key api_secret))
|
|
20
21
|
request_access_token if !@secrets["access_token"] || !@secrets["access_secret"]
|
|
21
22
|
|
|
22
23
|
@http = get_access_token
|
|
@@ -69,8 +70,6 @@ class SmugmugAPI
|
|
|
69
70
|
get('/api/v2!authuser')['User']
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
def images; end
|
|
73
|
-
|
|
74
73
|
def folders
|
|
75
74
|
folder_list = []
|
|
76
75
|
resp = get('/api/v2/folder/user/bcaldwell!folderlist')
|
|
@@ -83,7 +82,6 @@ class SmugmugAPI
|
|
|
83
82
|
def get_or_create_album(path, album_url: nil)
|
|
84
83
|
folder_path = File.dirname(path).split('/').map(&:capitalize).join('/')
|
|
85
84
|
album_name = File.basename(path).split(' ').map(&:capitalize).join(' ')
|
|
86
|
-
puts album_name
|
|
87
85
|
album = nil
|
|
88
86
|
|
|
89
87
|
folder = get_or_create_folder(folder_path)
|
|
@@ -165,14 +163,14 @@ class SmugmugAPI
|
|
|
165
163
|
@images.map { |i| i[:filename] }
|
|
166
164
|
end
|
|
167
165
|
|
|
168
|
-
def http(method, url, headers = {},
|
|
169
|
-
response = http_raw(method, url, headers,
|
|
166
|
+
def http(method, url, headers = {}, body = nil)
|
|
167
|
+
response = http_raw(method, url, headers, body)
|
|
170
168
|
raise 'Request failed' unless response.is_a? Net::HTTPSuccess
|
|
171
169
|
JSON.parse(response.body)['Response']
|
|
172
170
|
end
|
|
173
171
|
|
|
174
172
|
def get(url, params = nil, headers = {})
|
|
175
|
-
url.tr
|
|
173
|
+
url = url.tr(' ', '-')
|
|
176
174
|
uri = URI.parse(url)
|
|
177
175
|
uri.query = URI.encode_www_form(params) if params
|
|
178
176
|
http(:get, uri.to_s, headers)
|
|
@@ -186,38 +184,53 @@ class SmugmugAPI
|
|
|
186
184
|
JSON.parse(response.body)['Response']
|
|
187
185
|
end
|
|
188
186
|
|
|
189
|
-
def upload(image_path, album_id, headers = {})
|
|
187
|
+
def upload(image_path, album_id, headers = {}, filename_as_title: false)
|
|
190
188
|
image = File.open(image_path)
|
|
191
189
|
|
|
192
190
|
headers.merge!('Content-Type' => MimeMagic.by_path(image_path).type,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
'X-Smug-AlbumUri' => "/api/v2/album/#{album_id}",
|
|
192
|
+
'X-Smug-ResponseType' => 'JSON',
|
|
193
|
+
'X-Smug-Version' => 'v2',
|
|
194
|
+
'charset' => 'UTF-8',
|
|
195
|
+
'Accept' => 'JSON',
|
|
196
|
+
'X-Smug-FileName' => File.basename(image_path),
|
|
197
|
+
'Content-MD5' => Digest::MD5.file(image_path).hexdigest)
|
|
198
|
+
|
|
199
|
+
headers['X-Smug-Title'] = File.basename(image_path, ".*") if filename_as_title
|
|
200
200
|
|
|
201
201
|
resp = @uploader.post('/', image, headers)
|
|
202
202
|
resp.body
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
-
def upload_images(images, album_id, headers = {}, workers: 4)
|
|
205
|
+
def upload_images(images, album_id, headers = {}, workers: 4, filename_as_title: false)
|
|
206
|
+
counter = 0
|
|
207
|
+
Parallel.each(images, in_processes: workers, progress: "Uploading images") do |image|
|
|
208
|
+
upload(image, album_id, headers, filename_as_title: filename_as_title)
|
|
209
|
+
# puts "#{counter}/#{images.count / workers}
|
|
210
|
+
puts "Done #{image}"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def update_images(images, album_id, headers = {}, workers: 4, filename_as_title: false)
|
|
206
215
|
counter = 0
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
|
|
217
|
+
Parallel.each(images, in_processes: workers, progress: "Updating images") do |image|
|
|
218
|
+
# replace not working, delete then upload
|
|
219
|
+
http(:delete, image[:uri])
|
|
220
|
+
upload(image[:file], album_id, headers, filename_as_title: filename_as_title)
|
|
221
|
+
# counter += 1
|
|
222
|
+
# puts "#{counter}/#{images.count / workers}
|
|
223
|
+
puts "Done #{image[:file]}"
|
|
211
224
|
end
|
|
212
225
|
end
|
|
213
226
|
|
|
214
227
|
def request_access_token
|
|
215
228
|
@consumer = OAuth::Consumer.new(@secrets.api_key, @secrets.api_secret,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
site: OAUTH_ORIGIN,
|
|
230
|
+
name: 'photo-helper',
|
|
231
|
+
request_token_path: REQUEST_TOKEN_URL,
|
|
232
|
+
authorize_path: AUTHORIZE_URL,
|
|
233
|
+
access_token_path: ACCESS_TOKEN_URL)
|
|
221
234
|
|
|
222
235
|
# Generate request token
|
|
223
236
|
@request_token = @consumer.get_request_token
|
|
@@ -244,9 +257,8 @@ class SmugmugAPI
|
|
|
244
257
|
private
|
|
245
258
|
|
|
246
259
|
def http_raw(method, url, headers = {}, _body = nil)
|
|
247
|
-
url.tr
|
|
260
|
+
url = url.tr(' ', '-')
|
|
248
261
|
headers['Accept'] = 'application/json'
|
|
249
|
-
|
|
250
262
|
@http.request(method, url, headers)
|
|
251
263
|
end
|
|
252
264
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require 'mini_magick'
|
|
2
|
+
require 'helpers/file_helper'
|
|
3
3
|
|
|
4
4
|
module PhotoHelper
|
|
5
5
|
class Compress < Thor
|
|
6
6
|
include Thor::Actions
|
|
7
7
|
|
|
8
|
-
method_option :recursive, aliases:
|
|
9
|
-
method_option :overwrite, aliases:
|
|
10
|
-
desc
|
|
11
|
-
def images(folder=nil)
|
|
8
|
+
method_option :recursive, aliases: '-r', type: :boolean, default: false
|
|
9
|
+
method_option :overwrite, aliases: '-o', type: :boolean, default: false
|
|
10
|
+
desc 'images', 'compress images in folder'
|
|
11
|
+
def images(folder = nil)
|
|
12
12
|
folder ||= options[:folder]
|
|
13
13
|
|
|
14
14
|
search_path = File.expand_path(folder)
|
|
@@ -21,28 +21,29 @@ module PhotoHelper
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
files.each do |file|
|
|
24
|
-
next if File.basename(file,
|
|
24
|
+
next if File.basename(file, '.*').end_with? '.min'
|
|
25
25
|
next unless FileHelper.is_jpeg?(file)
|
|
26
26
|
|
|
27
27
|
image = MiniMagick::Image.open(file)
|
|
28
28
|
orig_size = image.size
|
|
29
29
|
|
|
30
30
|
image.combine_options do |b|
|
|
31
|
-
b.sampling_factor
|
|
31
|
+
b.sampling_factor '4:2:0'
|
|
32
32
|
b.strip
|
|
33
|
-
b.interlace
|
|
34
|
-
b.colorspace
|
|
33
|
+
b.interlace 'JPEG'
|
|
34
|
+
b.colorspace 'RGB'
|
|
35
35
|
b.quality 85
|
|
36
36
|
end
|
|
37
|
+
next if orig_size == image.size
|
|
37
38
|
puts "#{file} (#{(orig_size / image.size) * 100}%)"
|
|
38
39
|
|
|
39
|
-
output_path =
|
|
40
|
+
output_path =
|
|
40
41
|
if options[:overwrite]
|
|
41
42
|
file
|
|
42
43
|
else
|
|
43
|
-
File.join(
|
|
44
|
+
File.join(File.dirname(file), File.basename(file, '.*') + '.min' + File.extname(file))
|
|
44
45
|
end
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
image.write output_path
|
|
47
48
|
end
|
|
48
49
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'helpers/file_helper'
|
|
2
|
+
require 'photo-helper/compress'
|
|
3
|
+
|
|
4
|
+
module PhotoHelper
|
|
5
|
+
class Screensaver < Thor
|
|
6
|
+
include Thor::Actions
|
|
7
|
+
|
|
8
|
+
method_option :overwrite, aliases: '-o', type: :boolean, default: false
|
|
9
|
+
desc 'move', 'Move best photos to screensaver folder and compress'
|
|
10
|
+
def move
|
|
11
|
+
files = Dir["#{BEST_OF_ROOT}/**/*"]
|
|
12
|
+
|
|
13
|
+
files.each do |file|
|
|
14
|
+
next unless FileHelper.is_jpeg?(file)
|
|
15
|
+
dest = File.join(SCREENSAVER_ROOT, File.basename(file))
|
|
16
|
+
next if File.exist?(dest)
|
|
17
|
+
puts file
|
|
18
|
+
FileUtils.copy(file, dest)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts 'Compressing'
|
|
22
|
+
|
|
23
|
+
compress = PhotoHelper::Compress.new
|
|
24
|
+
compress.options = { overwrite: true }
|
|
25
|
+
compress.images(SCREENSAVER_ROOT)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
default_task :move
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/photo-helper/smugmug.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'helpers/smugmug_album'
|
|
2
3
|
require 'date'
|
|
3
4
|
require 'helpers/image_helper'
|
|
4
5
|
|
|
@@ -11,28 +12,27 @@ module PhotoHelper
|
|
|
11
12
|
method_option :folder, aliases: '-f', type: :string, default: '.'
|
|
12
13
|
method_option :recursive, aliases: '-r', type: :boolean, default: false
|
|
13
14
|
method_option :dry_run, aliases: '-d', type: :boolean, default: false
|
|
14
|
-
def sync(folder = nil,
|
|
15
|
+
def sync(folder = nil, _album_name = nil)
|
|
15
16
|
search_path = File.expand_path(folder)
|
|
16
17
|
|
|
17
|
-
@smugmug =
|
|
18
|
+
@smugmug = SmugmugAlbumHelper.new(search_path)
|
|
18
19
|
|
|
19
20
|
@smugmug.upload_select
|
|
20
21
|
puts("\n")
|
|
21
|
-
if album_name
|
|
22
|
-
|
|
23
|
-
else
|
|
24
|
-
|
|
25
|
-
end
|
|
22
|
+
# if album_name
|
|
23
|
+
# @smugmug.upload(album_name, @smugmug.image_list)
|
|
24
|
+
# else
|
|
25
|
+
@smugmug.upload_dl
|
|
26
|
+
# end
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
desc 'oauth', "fetch oauth credentials"
|
|
29
|
-
def oauth
|
|
30
|
+
def oauth
|
|
30
31
|
SmugmugAPI.new.request_access_token
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
desc 'albums', "list albums with their weburl"
|
|
34
|
-
|
|
35
|
-
def albums(folder = nil, album_name = nil)
|
|
35
|
+
def albums
|
|
36
36
|
@smugmug = SmugmugAPI.new
|
|
37
37
|
albums = @smugmug.albums_long
|
|
38
38
|
|
data/lib/photo-helper/version.rb
CHANGED
data/lib/photo_helper.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'thor'
|
|
2
3
|
|
|
3
4
|
require 'helpers/printer'
|
|
@@ -9,17 +10,20 @@ require 'photo-helper/move'
|
|
|
9
10
|
require 'photo-helper/instagram'
|
|
10
11
|
require 'photo-helper/compress'
|
|
11
12
|
require 'photo-helper/smugmug'
|
|
13
|
+
require 'photo-helper/screensaver'
|
|
12
14
|
|
|
13
|
-
#
|
|
15
|
+
# TODO: move to config file
|
|
14
16
|
RAW_EXTENSION = "dng"
|
|
15
17
|
RAW_EXTENSIONS = [RAW_EXTENSION, "DNG", "ORF"]
|
|
16
18
|
JPEG_EXTENSION = "JPG"
|
|
17
|
-
JPEG_EXTENSIONS =
|
|
19
|
+
JPEG_EXTENSIONS = %w(JPG jpg jpeg)
|
|
18
20
|
IMAGE_EXTENSIONS = JPEG_EXTENSIONS.concat([])
|
|
19
|
-
PHOTOS_ROOT = "
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
PHOTOS_ROOT = File.expand_path("~/Pictures/Pictures")
|
|
22
|
+
BEST_OF_ROOT = File.expand_path("~/Pictures/Pictures/Best\ of")
|
|
23
|
+
SCREENSAVER_ROOT = File.expand_path("~/Pictures/screensaver")
|
|
24
|
+
JPEG_ROOT = File.expand_path("~/Pictures/jpegs")
|
|
25
|
+
IGNORE_FOLDERS = %w(instagram exported edited)
|
|
26
|
+
SELECT_COLOR_TAGS = ["Winner", "Winner alt", "Superior", "Superior alt", "Typical", "Typical alt"]
|
|
23
27
|
|
|
24
28
|
module PhotoHelper
|
|
25
29
|
class CLI < Thor
|
|
@@ -37,6 +41,7 @@ module PhotoHelper
|
|
|
37
41
|
register PhotoHelper::Instagram, :instagram, "instagram", "Do something else"
|
|
38
42
|
register PhotoHelper::Move, :move, "move", "Do something else"
|
|
39
43
|
register PhotoHelper::Compress, :compress, "compress", "Do something else"
|
|
40
|
-
register PhotoHelper::Smugmug, :smugmug, "smugmug", "Interface with Smugmug"
|
|
44
|
+
register PhotoHelper::Smugmug, :smugmug, "smugmug", "Interface with Smugmug"
|
|
45
|
+
register PhotoHelper::Screensaver, :screensaver, "screensaver", "Move best photos to screensaver folder and compress"
|
|
41
46
|
end
|
|
42
47
|
end
|
data/photo-helper.gemspec
CHANGED
|
@@ -5,7 +5,7 @@ require 'photo-helper/version'
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "photo-helper"
|
|
8
|
-
spec.version =
|
|
8
|
+
spec.version = PhotoHelper::VERSION
|
|
9
9
|
spec.authors = ["Benjamin Caldwell"]
|
|
10
10
|
spec.email = ["caldwellbenjamin8@gmail.com"]
|
|
11
11
|
|
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
spec.add_development_dependency "pry"
|
|
28
28
|
spec.add_development_dependency "byebug"
|
|
29
29
|
spec.add_development_dependency "rubocop"
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
spec.add_dependency "thor"
|
|
32
32
|
spec.add_dependency "mini_magick"
|
|
33
33
|
spec.add_dependency "ejson"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: photo-helper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.3'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Caldwell
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-12-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -216,7 +216,7 @@ files:
|
|
|
216
216
|
- lib/helpers/image_helper.rb
|
|
217
217
|
- lib/helpers/printer.rb
|
|
218
218
|
- lib/helpers/secrets.rb
|
|
219
|
-
- lib/helpers/
|
|
219
|
+
- lib/helpers/smugmug_album.rb
|
|
220
220
|
- lib/helpers/smugmug_api.rb
|
|
221
221
|
- lib/helpers/trash.rb
|
|
222
222
|
- lib/photo-helper/compress.rb
|
|
@@ -224,6 +224,7 @@ files:
|
|
|
224
224
|
- lib/photo-helper/generate.rb
|
|
225
225
|
- lib/photo-helper/instagram.rb
|
|
226
226
|
- lib/photo-helper/move.rb
|
|
227
|
+
- lib/photo-helper/screensaver.rb
|
|
227
228
|
- lib/photo-helper/smugmug.rb
|
|
228
229
|
- lib/photo-helper/version.rb
|
|
229
230
|
- lib/photo_helper.rb
|
data/lib/helpers/smugmug.rb
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
require "helpers/smugmug_api"
|
|
2
|
-
require "helpers/image_helper"
|
|
3
|
-
require "helpers/file_helper"
|
|
4
|
-
|
|
5
|
-
class SmugmugHelper
|
|
6
|
-
attr_accessor :smugmug_api
|
|
7
|
-
|
|
8
|
-
# to figure out what to delete, read all xmp files, loop through uploaded files and check xmp file
|
|
9
|
-
|
|
10
|
-
PATH_REGEX = %r{^.+Pictures\/.+\/(\d{4})\/(\d{2})_.+\/[^_]+_([^\/]+)}
|
|
11
|
-
|
|
12
|
-
def initialize(search_path)
|
|
13
|
-
@search_path = search_path
|
|
14
|
-
@smugmug = SmugmugAPI.new
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def parse_path
|
|
18
|
-
if matches = "#{@search_path}/".to_s.match(PATH_REGEX)
|
|
19
|
-
@year = matches[1]
|
|
20
|
-
@month = Date::MONTHNAMES[matches[2].to_i].capitalize
|
|
21
|
-
@location = matches[3].split("_").map(&:capitalize).join(' ')
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def album_name
|
|
26
|
-
parse_path
|
|
27
|
-
if @year && @month && @location
|
|
28
|
-
folder = "#{@month} #{@year}"
|
|
29
|
-
album_name_short = "#{@location} #{@month} #{@year}"
|
|
30
|
-
File.join(@year, @month, album_name_short)
|
|
31
|
-
else
|
|
32
|
-
puts 'Unable to determine album from path'
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def image_list
|
|
37
|
-
# todo: exclude exported path
|
|
38
|
-
Dir["#{@search_path}/**/*.{#{IMAGE_EXTENSIONS.join(",")}}"].reject{ |p| FileHelper.ingore_file?(p) }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def merge_exported(images = image_list)
|
|
42
|
-
exported = Dir["#{@search_path}/**/{Exported,exported}/*.{#{IMAGE_EXTENSIONS.join(",")}}"]
|
|
43
|
-
exported_basenames = exported.map{ |p| File.basename(p, ".*") }
|
|
44
|
-
images = images.reject { |p| exported_basenames.include? File.basename(p, ".*") }
|
|
45
|
-
images.concat(exported)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def upload(album_name, pictures, reject_trash = true)
|
|
49
|
-
album = @smugmug.get_or_create_album(album_name, album_url: @location&.downcase)
|
|
50
|
-
puts "#{album[:web_uri]}\n"
|
|
51
|
-
|
|
52
|
-
# remove uploaded pictures
|
|
53
|
-
uploaded = @smugmug.image_list(album[:id])
|
|
54
|
-
|
|
55
|
-
pictures = pictures.reject do |p|
|
|
56
|
-
if reject_trash
|
|
57
|
-
return true if ImageHelper.color_class(p) == "Trash"
|
|
58
|
-
end
|
|
59
|
-
uploaded.include? File.basename(p)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
puts "Uploading #{pictures.count} jpegs"
|
|
63
|
-
|
|
64
|
-
@smugmug.upload_images(pictures, album[:id], workers: 8)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def upload_dl
|
|
68
|
-
@album_name = album_name
|
|
69
|
-
@album_name = File.join("dl", @album_name)
|
|
70
|
-
|
|
71
|
-
puts "Uploading all images to album #{@album_name}"
|
|
72
|
-
upload(@album_name, image_list)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def upload_select
|
|
76
|
-
@album_name = album_name
|
|
77
|
-
pictures = image_list
|
|
78
|
-
pictures = pictures.select{ |p| ImageHelper.is_select?(p)}
|
|
79
|
-
pictures = merge_exported(pictures)
|
|
80
|
-
|
|
81
|
-
puts "Uploading selects to album #{@album_name}"
|
|
82
|
-
upload(@album_name, pictures)
|
|
83
|
-
end
|
|
84
|
-
end
|