flickarr 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/.rubocop_todo.yml +13 -0
- data/CHANGELOG.md +42 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/HELP.txt +50 -0
- data/HOWTO.md +260 -0
- data/LICENSE.txt +21 -0
- data/README.md +109 -0
- data/Rakefile +10 -0
- data/TODO.md +6 -0
- data/exe/flickarr +5 -0
- data/lib/flickarr/auth.rb +43 -0
- data/lib/flickarr/cli.rb +692 -0
- data/lib/flickarr/client/photo_query.rb +23 -0
- data/lib/flickarr/client/profile_query.rb +19 -0
- data/lib/flickarr/client.rb +94 -0
- data/lib/flickarr/collection.rb +103 -0
- data/lib/flickarr/config.rb +88 -0
- data/lib/flickarr/errors.rb +7 -0
- data/lib/flickarr/license.rb +34 -0
- data/lib/flickarr/photo.rb +23 -0
- data/lib/flickarr/photo_set.rb +139 -0
- data/lib/flickarr/post.rb +253 -0
- data/lib/flickarr/profile.rb +177 -0
- data/lib/flickarr/rate_limiter.rb +24 -0
- data/lib/flickarr/version.rb +3 -0
- data/lib/flickarr/video.rb +65 -0
- data/lib/flickarr.rb +17 -0
- data/sig/flickarr.rbs +4 -0
- metadata +144 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
require 'down'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'slugify'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
|
|
8
|
+
module Flickarr
|
|
9
|
+
class Post
|
|
10
|
+
FLICKR_URL_PATTERN = %r{\Ahttps?://(?:www\.)?flickr\.com/photos/[^/]+/(\d+)}
|
|
11
|
+
|
|
12
|
+
def self.build info:, sizes:, exif: nil
|
|
13
|
+
case info.media.to_s
|
|
14
|
+
when 'video' then Video.new(info: info, sizes: sizes, exif: exif)
|
|
15
|
+
else Photo.new(info: info, sizes: sizes, exif: exif)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.id_from_url url
|
|
20
|
+
match = url.match FLICKR_URL_PATTERN
|
|
21
|
+
match&.captures&.first
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.file_path_from_list_item item, archive_path:
|
|
25
|
+
id = item.id
|
|
26
|
+
title = item.title.to_s
|
|
27
|
+
slug = title.slugify
|
|
28
|
+
basename = [id, slug.empty? ? nil : slug].compact.join('_')
|
|
29
|
+
ext = item.media.to_s == 'video' ? 'mp4' : item.originalformat.to_s
|
|
30
|
+
date = compute_date_from_list_item(item)
|
|
31
|
+
folder = date.strftime '%Y/%m/%d'
|
|
32
|
+
|
|
33
|
+
File.join archive_path, folder, "#{basename}.#{ext}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.compute_date_from_list_item item
|
|
37
|
+
if item.datetakenunknown.to_i.zero?
|
|
38
|
+
Date.parse item.datetaken
|
|
39
|
+
else
|
|
40
|
+
Time.at(item.dateupload.to_i).to_date
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_reader :camera,
|
|
45
|
+
:description,
|
|
46
|
+
:exif,
|
|
47
|
+
:extension,
|
|
48
|
+
:id,
|
|
49
|
+
:license,
|
|
50
|
+
:location,
|
|
51
|
+
:media,
|
|
52
|
+
:owner,
|
|
53
|
+
:tags,
|
|
54
|
+
:title,
|
|
55
|
+
:views,
|
|
56
|
+
:visibility
|
|
57
|
+
|
|
58
|
+
def initialize info:, sizes:, exif: nil
|
|
59
|
+
@id = info.id
|
|
60
|
+
@description = info.description.to_s
|
|
61
|
+
@license = License.new(info.license)
|
|
62
|
+
@media = info.media.to_s
|
|
63
|
+
@tags = extract_tags info
|
|
64
|
+
@title = info.title.to_s
|
|
65
|
+
@views = info.views.to_s
|
|
66
|
+
@visibility = extract_visibility info
|
|
67
|
+
@owner = extract_owner info
|
|
68
|
+
|
|
69
|
+
@dates = info.dates
|
|
70
|
+
@location = extract_location info
|
|
71
|
+
@sizes = sizes
|
|
72
|
+
@urls = extract_urls info
|
|
73
|
+
@camera = exif&.camera.to_s if exif
|
|
74
|
+
@exif = extract_exif exif
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def basename
|
|
78
|
+
[id, slug].compact.join '_'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def date_taken
|
|
82
|
+
if @dates.takenunknown.to_i.zero?
|
|
83
|
+
Date.parse @dates.taken
|
|
84
|
+
else
|
|
85
|
+
Time.at(@dates.posted.to_i).to_date
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def folder_path
|
|
90
|
+
date_taken.strftime '%Y/%m/%d'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def slug
|
|
94
|
+
s = title.slugify
|
|
95
|
+
s.empty? ? nil : s
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_h
|
|
99
|
+
{
|
|
100
|
+
camera: camera,
|
|
101
|
+
dates: {
|
|
102
|
+
lastupdate: @dates.respond_to?(:lastupdate) ? @dates.lastupdate.to_s : '',
|
|
103
|
+
posted: @dates.posted.to_s,
|
|
104
|
+
taken: @dates.taken.to_s,
|
|
105
|
+
takenunknown: @dates.takenunknown.to_i
|
|
106
|
+
},
|
|
107
|
+
description: description,
|
|
108
|
+
exif: exif,
|
|
109
|
+
extension: extension,
|
|
110
|
+
id: id,
|
|
111
|
+
license: license.to_h,
|
|
112
|
+
location: location,
|
|
113
|
+
media: media,
|
|
114
|
+
original_url: original_url,
|
|
115
|
+
owner: owner,
|
|
116
|
+
sizes: extract_sizes,
|
|
117
|
+
tags: tags,
|
|
118
|
+
title: title,
|
|
119
|
+
urls: @urls,
|
|
120
|
+
views: views,
|
|
121
|
+
visibility: visibility
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def write archive_path:, overwrite: false
|
|
126
|
+
dir = post_dir archive_path
|
|
127
|
+
file_path = File.join dir, "#{basename}.#{extension}"
|
|
128
|
+
existed = File.exist? file_path
|
|
129
|
+
|
|
130
|
+
return :skipped if existed && !overwrite
|
|
131
|
+
|
|
132
|
+
FileUtils.mkdir_p dir
|
|
133
|
+
download archive_path: archive_path
|
|
134
|
+
write_json archive_path: archive_path
|
|
135
|
+
write_yaml archive_path: archive_path
|
|
136
|
+
|
|
137
|
+
existed ? :overwritten : :created
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def write_json archive_path:
|
|
141
|
+
dir = post_dir archive_path
|
|
142
|
+
json = JSON.pretty_generate to_h
|
|
143
|
+
|
|
144
|
+
FileUtils.mkdir_p dir
|
|
145
|
+
File.write File.join(dir, "#{basename}.json"), json
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def write_yaml archive_path:
|
|
149
|
+
dir = post_dir archive_path
|
|
150
|
+
hash = deep_stringify_keys to_h
|
|
151
|
+
yaml = YAML.dump hash
|
|
152
|
+
|
|
153
|
+
FileUtils.mkdir_p dir
|
|
154
|
+
File.write File.join(dir, "#{basename}.yaml"), yaml
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def deep_stringify_keys obj
|
|
160
|
+
case obj
|
|
161
|
+
when Hash then obj.transform_keys(&:to_s).transform_values { deep_stringify_keys it }
|
|
162
|
+
when Array then obj.map { deep_stringify_keys it }
|
|
163
|
+
else obj
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def extract_exif exif_response
|
|
168
|
+
return [] unless exif_response
|
|
169
|
+
return [] unless exif_response.respond_to?(:exif)
|
|
170
|
+
|
|
171
|
+
exif_response.exif.map do |tag|
|
|
172
|
+
{
|
|
173
|
+
clean: tag.respond_to?(:clean) ? tag.clean&.to_s : nil,
|
|
174
|
+
label: tag.label.to_s,
|
|
175
|
+
raw: tag.raw.to_s,
|
|
176
|
+
tag: tag.tag.to_s,
|
|
177
|
+
tagspace: tag.tagspace.to_s
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def extract_location info
|
|
183
|
+
return nil unless info.respond_to?(:location)
|
|
184
|
+
|
|
185
|
+
loc = info.location
|
|
186
|
+
|
|
187
|
+
{
|
|
188
|
+
accuracy: loc.accuracy.to_s,
|
|
189
|
+
context: loc.context.to_s,
|
|
190
|
+
country: loc.country.to_s,
|
|
191
|
+
county: loc.county.to_s,
|
|
192
|
+
latitude: loc.latitude.to_s,
|
|
193
|
+
locality: loc.locality.to_s,
|
|
194
|
+
longitude: loc.longitude.to_s,
|
|
195
|
+
region: loc.region.to_s
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def extract_owner info
|
|
200
|
+
return {} unless info.respond_to?(:owner)
|
|
201
|
+
|
|
202
|
+
o = info.owner
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
nsid: o.nsid.to_s,
|
|
206
|
+
realname: o.realname.to_s,
|
|
207
|
+
username: o.username.to_s
|
|
208
|
+
}
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def extract_sizes
|
|
212
|
+
@sizes.map do |size|
|
|
213
|
+
{
|
|
214
|
+
height: size.height.to_i,
|
|
215
|
+
label: size.label.to_s,
|
|
216
|
+
source: size.source.to_s,
|
|
217
|
+
width: size.width.to_i
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def extract_tags info
|
|
223
|
+
return [] unless info.tags.respond_to?(:tag)
|
|
224
|
+
|
|
225
|
+
info.tags.tag.map(&:_content)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def extract_urls info
|
|
229
|
+
return {} unless info.respond_to?(:urls)
|
|
230
|
+
return {} unless info.urls.respond_to?(:url)
|
|
231
|
+
|
|
232
|
+
info.urls.url.to_h do |url|
|
|
233
|
+
[url.type.to_s, url.to_s]
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def extract_visibility info
|
|
238
|
+
return {} unless info.respond_to?(:visibility)
|
|
239
|
+
|
|
240
|
+
vis = info.visibility
|
|
241
|
+
|
|
242
|
+
{
|
|
243
|
+
isfamily: vis.isfamily.to_i,
|
|
244
|
+
isfriend: vis.isfriend.to_i,
|
|
245
|
+
ispublic: vis.ispublic.to_i
|
|
246
|
+
}
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def post_dir archive_path
|
|
250
|
+
File.join archive_path, folder_path
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
require 'down'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
module Flickarr
|
|
7
|
+
class Profile
|
|
8
|
+
DEFAULT_AVATAR_URL = 'https://www.flickr.com/images/buddyicon.gif'.freeze
|
|
9
|
+
PROFILE_URL_PATTERN = %r{\Ahttps?://(?:www\.)?flickr\.com/(?:photos|people)/([^/]+)/?\z}
|
|
10
|
+
|
|
11
|
+
def self.matches_url? url
|
|
12
|
+
PROFILE_URL_PATTERN.match?(url.to_s)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :city,
|
|
16
|
+
:country,
|
|
17
|
+
:description,
|
|
18
|
+
:email,
|
|
19
|
+
:facebook,
|
|
20
|
+
:first_name,
|
|
21
|
+
:hometown,
|
|
22
|
+
:iconfarm,
|
|
23
|
+
:iconserver,
|
|
24
|
+
:instagram,
|
|
25
|
+
:ispro,
|
|
26
|
+
:join_date,
|
|
27
|
+
:last_name,
|
|
28
|
+
:location,
|
|
29
|
+
:nsid,
|
|
30
|
+
:occupation,
|
|
31
|
+
:path_alias,
|
|
32
|
+
:photos_first_date,
|
|
33
|
+
:photos_first_date_taken,
|
|
34
|
+
:photos_count,
|
|
35
|
+
:photosurl,
|
|
36
|
+
:pinterest,
|
|
37
|
+
:profileurl,
|
|
38
|
+
:realname,
|
|
39
|
+
:timezone,
|
|
40
|
+
:tumblr,
|
|
41
|
+
:twitter,
|
|
42
|
+
:upload_count,
|
|
43
|
+
:username,
|
|
44
|
+
:website
|
|
45
|
+
|
|
46
|
+
def initialize person:, profile: nil
|
|
47
|
+
@description = person.description.to_s
|
|
48
|
+
@iconfarm = person.iconfarm
|
|
49
|
+
@iconserver = person.iconserver.to_s
|
|
50
|
+
@ispro = person.ispro
|
|
51
|
+
@location = person.location.to_s
|
|
52
|
+
@nsid = person.nsid
|
|
53
|
+
@path_alias = person.path_alias
|
|
54
|
+
@photosurl = person.photosurl.to_s
|
|
55
|
+
@profileurl = person.profileurl.to_s
|
|
56
|
+
@realname = person.realname.to_s
|
|
57
|
+
@timezone = { label: person.timezone.label.to_s, offset: person.timezone.offset.to_s }
|
|
58
|
+
@upload_count = person.respond_to?(:upload_count) ? person.upload_count.to_i : nil
|
|
59
|
+
@username = person.username.to_s
|
|
60
|
+
|
|
61
|
+
extract_photos person
|
|
62
|
+
|
|
63
|
+
return unless profile
|
|
64
|
+
|
|
65
|
+
@city = profile.city.to_s
|
|
66
|
+
@country = profile.country.to_s
|
|
67
|
+
@email = profile.email.to_s
|
|
68
|
+
@facebook = profile.facebook.to_s
|
|
69
|
+
@first_name = profile.first_name.to_s
|
|
70
|
+
@hometown = profile.hometown.to_s
|
|
71
|
+
@instagram = profile.instagram.to_s
|
|
72
|
+
@join_date = profile.join_date.to_s
|
|
73
|
+
@last_name = profile.last_name.to_s
|
|
74
|
+
@occupation = profile.occupation.to_s
|
|
75
|
+
@pinterest = profile.pinterest.to_s
|
|
76
|
+
@tumblr = profile.tumblr.to_s
|
|
77
|
+
@twitter = profile.twitter.to_s
|
|
78
|
+
@website = profile.website.to_s
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def avatar_url
|
|
82
|
+
if iconserver == '0' || iconfarm.zero?
|
|
83
|
+
DEFAULT_AVATAR_URL
|
|
84
|
+
else
|
|
85
|
+
"https://farm#{iconfarm}.staticflickr.com/#{iconserver}/buddyicons/#{nsid}_r.jpg"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def download_avatar archive_path:
|
|
90
|
+
dir = profile_dir archive_path
|
|
91
|
+
ext = File.extname avatar_url
|
|
92
|
+
dest = File.join dir, "avatar#{ext}"
|
|
93
|
+
|
|
94
|
+
FileUtils.mkdir_p dir
|
|
95
|
+
Down.download avatar_url, destination: dest
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_h
|
|
99
|
+
{
|
|
100
|
+
avatar_url: avatar_url,
|
|
101
|
+
city: city,
|
|
102
|
+
country: country,
|
|
103
|
+
description: description,
|
|
104
|
+
email: email,
|
|
105
|
+
facebook: facebook,
|
|
106
|
+
first_name: first_name,
|
|
107
|
+
hometown: hometown,
|
|
108
|
+
iconfarm: iconfarm,
|
|
109
|
+
iconserver: iconserver,
|
|
110
|
+
instagram: instagram,
|
|
111
|
+
ispro: ispro,
|
|
112
|
+
join_date: join_date,
|
|
113
|
+
last_name: last_name,
|
|
114
|
+
location: location,
|
|
115
|
+
nsid: nsid,
|
|
116
|
+
occupation: occupation,
|
|
117
|
+
path_alias: path_alias,
|
|
118
|
+
photos_count: photos_count,
|
|
119
|
+
photos_first_date: photos_first_date,
|
|
120
|
+
photos_first_date_taken: photos_first_date_taken,
|
|
121
|
+
photosurl: photosurl,
|
|
122
|
+
pinterest: pinterest,
|
|
123
|
+
profileurl: profileurl,
|
|
124
|
+
realname: realname,
|
|
125
|
+
timezone: timezone,
|
|
126
|
+
tumblr: tumblr,
|
|
127
|
+
twitter: twitter,
|
|
128
|
+
upload_count: upload_count,
|
|
129
|
+
username: username,
|
|
130
|
+
website: website
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def write archive_path:, overwrite: false
|
|
135
|
+
dir = profile_dir archive_path
|
|
136
|
+
json_path = File.join dir, 'profile.json'
|
|
137
|
+
existed = File.exist? json_path
|
|
138
|
+
|
|
139
|
+
return :skipped if existed && !overwrite
|
|
140
|
+
|
|
141
|
+
FileUtils.mkdir_p dir
|
|
142
|
+
write_json dir: dir
|
|
143
|
+
write_yaml dir: dir
|
|
144
|
+
download_avatar archive_path: archive_path
|
|
145
|
+
|
|
146
|
+
existed ? :overwritten : :created
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def write_json dir:
|
|
150
|
+
FileUtils.mkdir_p dir
|
|
151
|
+
json = JSON.pretty_generate to_h
|
|
152
|
+
File.write File.join(dir, 'profile.json'), json
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def write_yaml dir:
|
|
156
|
+
FileUtils.mkdir_p dir
|
|
157
|
+
hash = to_h.transform_keys(&:to_s)
|
|
158
|
+
yaml = YAML.dump hash
|
|
159
|
+
File.write File.join(dir, 'profile.yaml'), yaml
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def extract_photos person
|
|
165
|
+
return unless person.respond_to?(:photos)
|
|
166
|
+
|
|
167
|
+
photos = person.photos
|
|
168
|
+
@photos_count = photos.respond_to?(:count) ? photos.count.to_i : nil
|
|
169
|
+
@photos_first_date = photos.respond_to?(:firstdate) ? photos.firstdate.to_s : nil
|
|
170
|
+
@photos_first_date_taken = photos.respond_to?(:firstdatetaken) ? photos.firstdatetaken.to_s : nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def profile_dir archive_path
|
|
174
|
+
File.join archive_path, 'Profile'
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Flickarr
|
|
2
|
+
class RateLimiter
|
|
3
|
+
def initialize interval: 1.0
|
|
4
|
+
@interval = interval
|
|
5
|
+
@last_request_at = nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def wait
|
|
9
|
+
return unless @last_request_at
|
|
10
|
+
|
|
11
|
+
elapsed = Time.now - @last_request_at
|
|
12
|
+
remaining = @interval - elapsed
|
|
13
|
+
|
|
14
|
+
sleep remaining if remaining.positive?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def track
|
|
18
|
+
wait
|
|
19
|
+
result = yield
|
|
20
|
+
@last_request_at = Time.now
|
|
21
|
+
result
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'http'
|
|
2
|
+
|
|
3
|
+
module Flickarr
|
|
4
|
+
class Video < Post
|
|
5
|
+
# Preferred video sizes, largest to smallest
|
|
6
|
+
VIDEO_SIZE_PRIORITY = ['Video Original', '1080p', '720p', '700', '360p', '288p', 'iphone_wifi'].freeze
|
|
7
|
+
|
|
8
|
+
def initialize info:, sizes:, exif: nil
|
|
9
|
+
super
|
|
10
|
+
@extension = 'mp4'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def download archive_path:
|
|
14
|
+
dir = post_dir archive_path
|
|
15
|
+
dest = File.join dir, "#{basename}.#{extension}"
|
|
16
|
+
url = resolve_download_url
|
|
17
|
+
|
|
18
|
+
FileUtils.mkdir_p dir
|
|
19
|
+
Down.download url, destination: dest
|
|
20
|
+
download_poster_frame dir: dir
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def original_url
|
|
24
|
+
video_original = @sizes.find { it.label == 'Video Original' }
|
|
25
|
+
photo_original = @sizes.find { it.label == 'Original' }
|
|
26
|
+
size = video_original || photo_original || @sizes.last
|
|
27
|
+
|
|
28
|
+
size.source
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def download_poster_frame dir:
|
|
34
|
+
poster = @sizes.find { it.label == 'Original' && it.media == 'photo' }
|
|
35
|
+
return unless poster
|
|
36
|
+
|
|
37
|
+
ext = File.extname(poster.source)
|
|
38
|
+
dest = File.join dir, "#{basename}#{ext}"
|
|
39
|
+
|
|
40
|
+
Down.download poster.source, destination: dest
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def resolve_download_url
|
|
44
|
+
video_sizes = VIDEO_SIZE_PRIORITY.filter_map do |label|
|
|
45
|
+
@sizes.find { it.label == label && it.media == 'video' }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
video_sizes.each do |size|
|
|
49
|
+
url = resolve_redirect(size.source)
|
|
50
|
+
response = HTTP.head(url)
|
|
51
|
+
|
|
52
|
+
return url if response.status.success?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fall back to original_url if nothing worked
|
|
56
|
+
resolve_redirect original_url
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resolve_redirect url
|
|
60
|
+
response = HTTP.head(url)
|
|
61
|
+
|
|
62
|
+
response.status.redirect? ? response.headers['Location'] : url
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/flickarr.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require_relative 'flickarr/version'
|
|
2
|
+
require_relative 'flickarr/errors'
|
|
3
|
+
require_relative 'flickarr/config'
|
|
4
|
+
require_relative 'flickarr/auth'
|
|
5
|
+
require_relative 'flickarr/client'
|
|
6
|
+
require_relative 'flickarr/cli'
|
|
7
|
+
require_relative 'flickarr/collection'
|
|
8
|
+
require_relative 'flickarr/license'
|
|
9
|
+
require_relative 'flickarr/post'
|
|
10
|
+
require_relative 'flickarr/photo'
|
|
11
|
+
require_relative 'flickarr/photo_set'
|
|
12
|
+
require_relative 'flickarr/profile'
|
|
13
|
+
require_relative 'flickarr/rate_limiter'
|
|
14
|
+
require_relative 'flickarr/video'
|
|
15
|
+
|
|
16
|
+
module Flickarr
|
|
17
|
+
end
|
data/sig/flickarr.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: flickarr
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shane Becker
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: cgi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.5'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.5'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: down
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.4'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.4'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: flickr
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: http
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.2'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.2'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: slugify
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.0'
|
|
82
|
+
description: Export an archive of your Flickr library — photos, videos, metadata,
|
|
83
|
+
tags, albums, collections, and profile.
|
|
84
|
+
email:
|
|
85
|
+
- veganstraightedge@gmail.com
|
|
86
|
+
executables:
|
|
87
|
+
- flickarr
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- ".rubocop_todo.yml"
|
|
92
|
+
- CHANGELOG.md
|
|
93
|
+
- CODE_OF_CONDUCT.md
|
|
94
|
+
- HELP.txt
|
|
95
|
+
- HOWTO.md
|
|
96
|
+
- LICENSE.txt
|
|
97
|
+
- README.md
|
|
98
|
+
- Rakefile
|
|
99
|
+
- TODO.md
|
|
100
|
+
- exe/flickarr
|
|
101
|
+
- lib/flickarr.rb
|
|
102
|
+
- lib/flickarr/auth.rb
|
|
103
|
+
- lib/flickarr/cli.rb
|
|
104
|
+
- lib/flickarr/client.rb
|
|
105
|
+
- lib/flickarr/client/photo_query.rb
|
|
106
|
+
- lib/flickarr/client/profile_query.rb
|
|
107
|
+
- lib/flickarr/collection.rb
|
|
108
|
+
- lib/flickarr/config.rb
|
|
109
|
+
- lib/flickarr/errors.rb
|
|
110
|
+
- lib/flickarr/license.rb
|
|
111
|
+
- lib/flickarr/photo.rb
|
|
112
|
+
- lib/flickarr/photo_set.rb
|
|
113
|
+
- lib/flickarr/post.rb
|
|
114
|
+
- lib/flickarr/profile.rb
|
|
115
|
+
- lib/flickarr/rate_limiter.rb
|
|
116
|
+
- lib/flickarr/version.rb
|
|
117
|
+
- lib/flickarr/video.rb
|
|
118
|
+
- sig/flickarr.rbs
|
|
119
|
+
homepage: https://github.com/veganstraightedge/flickarr
|
|
120
|
+
licenses:
|
|
121
|
+
- MIT
|
|
122
|
+
metadata:
|
|
123
|
+
homepage_uri: https://github.com/veganstraightedge/flickarr
|
|
124
|
+
source_code_uri: https://github.com/veganstraightedge/flickarr
|
|
125
|
+
changelog_uri: https://github.com/veganstraightedge/flickarr/blob/main/CHANGELOG.md
|
|
126
|
+
rubygems_mfa_required: 'true'
|
|
127
|
+
rdoc_options: []
|
|
128
|
+
require_paths:
|
|
129
|
+
- lib
|
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: 4.0.0
|
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
|
+
requirements:
|
|
137
|
+
- - ">="
|
|
138
|
+
- !ruby/object:Gem::Version
|
|
139
|
+
version: '0'
|
|
140
|
+
requirements: []
|
|
141
|
+
rubygems_version: 4.0.8
|
|
142
|
+
specification_version: 4
|
|
143
|
+
summary: Export your Flickr library archive
|
|
144
|
+
test_files: []
|