flickru 0.0.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.
- data/bin/flickru +17 -0
- data/bin/geowiki +54 -0
- data/lib/flickru.rb +112 -0
- data/lib/flickru/file.rb +64 -0
- data/lib/flickru/flickr.rb +150 -0
- data/lib/flickru/journey.rb +38 -0
- data/lib/flickru/location.rb +96 -0
- data/lib/flickru/printer.rb +52 -0
- data/lib/flickru/ruby.rb +24 -0
- data/lib/flickru/string.rb +5 -0
- data/lib/flickru/version.rb +5 -0
- data/spec/flickru_spec.rb +41 -0
- metadata +176 -0
data/bin/flickru
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#flickru_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
#$LOAD_PATH.unshift(flickru_dir) unless $LOAD_PATH.include?(flickru_dir)
|
4
|
+
|
5
|
+
require 'flickru'
|
6
|
+
|
7
|
+
photo_dir = ARGV[0]
|
8
|
+
|
9
|
+
if ARGV.length > 1
|
10
|
+
Flickru.usage
|
11
|
+
Flickru.die __LINE__, "wrong number of arguments: #{ARGV[1,ARGV.length]}"
|
12
|
+
elsif photo_dir.nil? or photo_dir.empty?
|
13
|
+
Flickru.usage
|
14
|
+
Flickru.die __LINE__, "missing photo directory"
|
15
|
+
end
|
16
|
+
|
17
|
+
Flickru.flickru photo_dir
|
data/bin/geowiki
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
APP_NAME=`basename $0`
|
4
|
+
|
5
|
+
function error {
|
6
|
+
echo -e "\E[31m$APP_NAME: $1: $2$(tput sgr0)" 1>&2
|
7
|
+
}
|
8
|
+
|
9
|
+
if [ $# -ne 1 ]; then
|
10
|
+
echo "$APP_NAME - find coordinates of a place described by Wikipedia"
|
11
|
+
echo "usage: $APP_NAME <place Wikipedia page>"
|
12
|
+
echo "example: $APP_NAME 'Mille-Isles, Quebec'"
|
13
|
+
error $LINENO "wrong number of arguments"
|
14
|
+
exit 1
|
15
|
+
fi
|
16
|
+
|
17
|
+
PAGE=`echo "$1" | tr ' ' '_'`
|
18
|
+
|
19
|
+
# temporal file management
|
20
|
+
if [ -d /tmp ]; then
|
21
|
+
TMP="/tmp/$PAGE.tmp.$$.$RANDOM.html"
|
22
|
+
else
|
23
|
+
TMP="$PAGE.tmp.html"
|
24
|
+
fi
|
25
|
+
function clean_up {
|
26
|
+
rm -f "$TMP"
|
27
|
+
exit $1
|
28
|
+
}
|
29
|
+
trap clean_up SIGHUP SIGINT SIGTERM
|
30
|
+
|
31
|
+
curl "http://en.wikipedia.org/wiki/$PAGE" > "$TMP" 2> /dev/null
|
32
|
+
|
33
|
+
if [ $? -ne 0 ]; then
|
34
|
+
error $LINENO "Wikipedia page '$PAGE' not found"
|
35
|
+
clean_up 1
|
36
|
+
fi
|
37
|
+
|
38
|
+
URL=`cat "$TMP" | tr '"' '\n' | grep geohack | head -n 1 | sed -e 's|&|\&|'`
|
39
|
+
|
40
|
+
if [ -z "$URL" ]; then
|
41
|
+
error $LINENO "location not found in Wikipedia page '$PAGE'"
|
42
|
+
clean_up 1
|
43
|
+
fi
|
44
|
+
|
45
|
+
curl "$URL" > "$TMP" 2> /dev/null
|
46
|
+
|
47
|
+
if [ $? -ne 0 ]; then
|
48
|
+
error $LINENO "GeoHack URL '$URL' not found"
|
49
|
+
clean_up 1
|
50
|
+
fi
|
51
|
+
|
52
|
+
cat "$TMP" | grep '<span class="geo"' | sed -e 's|^.*"Latitude">\([^<]*\)</span>, .*"Longitude">\([^<]*\)</span>.*$|\1, \2|'
|
53
|
+
|
54
|
+
clean_up
|
data/lib/flickru.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# ruby
|
2
|
+
require 'etc'
|
3
|
+
require 'set'
|
4
|
+
# gems
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler/setup'
|
7
|
+
# flickru
|
8
|
+
require 'flickru/file'
|
9
|
+
require 'flickru/flickr'
|
10
|
+
require 'flickru/journey'
|
11
|
+
require 'flickru/printer'
|
12
|
+
|
13
|
+
FlickRaw.api_key = "aaf7253e0f88f03aa59f9d3074bf2d4b"
|
14
|
+
FlickRaw.shared_secret = "138ee268f76cd780"
|
15
|
+
|
16
|
+
module Flickru
|
17
|
+
|
18
|
+
def Flickru.usage
|
19
|
+
filename = File.basename __FILE__
|
20
|
+
Printer.show "#{filename} -- command-line Flickr upload automator\n"
|
21
|
+
Printer.show "usage: #{filename} <photo directory>\n"
|
22
|
+
Printer.show "example: #{filename} my_photos\n"
|
23
|
+
Printer.show "\n#{IO.read('README')}\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
def Flickru.die code, message
|
27
|
+
Printer.error "error:#{code}: #{message}"
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def Flickru.config_filename
|
32
|
+
File.join Etc.getpwuid.dir, "." + File.basename(__FILE__, File.extname(__FILE__)) + "rc"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.read_config
|
36
|
+
file = File.open config_filename, "r"
|
37
|
+
token, secret = file.readlines.map { |line| line.sub(/\#.*$/, '').strip } # remove line comments
|
38
|
+
file.close
|
39
|
+
Flickr.access token, secret
|
40
|
+
rescue
|
41
|
+
raise RuntimeError, "unable to open configuration file #{config_filename}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def Flickru.write_config token, secret
|
45
|
+
Printer.show "writing configuration file #{config_filename}... "
|
46
|
+
if File.exists? config_filename
|
47
|
+
file = File.new config_filename, "w"
|
48
|
+
else
|
49
|
+
file = File.open config_filename, "w"
|
50
|
+
end
|
51
|
+
file.puts "#{token} \# access token"
|
52
|
+
file.puts "#{secret} \# access secret"
|
53
|
+
file.close
|
54
|
+
Printer.success
|
55
|
+
rescue
|
56
|
+
raise RuntimeError, "unable to write configuration file #{config_filename}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.flickru photo_dir
|
60
|
+
begin
|
61
|
+
Flickru.read_config
|
62
|
+
rescue RuntimeError
|
63
|
+
token, secret = Flickr.login
|
64
|
+
write_config token, secret
|
65
|
+
end
|
66
|
+
|
67
|
+
# upload and classify
|
68
|
+
photos = File.find(photo_dir) {|f| File.image? f or File.video? f }
|
69
|
+
total_size = photos.reduce(0) {|t,p| t + File.size(p)}
|
70
|
+
Printer.info "#{File.human_readable_size total_size} to upload"
|
71
|
+
journey = Journey.new total_size
|
72
|
+
photoset_ids = Set.new
|
73
|
+
photos.each do |photo|
|
74
|
+
Printer.info "file '#{File.join File.basename(File.dirname(photo)), File.basename(photo)}' under process"
|
75
|
+
begin
|
76
|
+
photo_id = Flickr.upload_photo photo
|
77
|
+
photoset_ids << Flickr.classify(photo, photo_id)
|
78
|
+
rescue ArgumentError || Errno::ENOENT || Errno::EAGAIN || FlickRaw::FailedResponse
|
79
|
+
Printer.failure "#{photo}: #{$!}"
|
80
|
+
end
|
81
|
+
journey.take File.size(photo)
|
82
|
+
Printer.info "#{File.human_readable_size journey.progress} uploaded, " +
|
83
|
+
"#{File.human_readable_size journey.distance} remaining. " +
|
84
|
+
"ETA: #{Printer.human_readable_seconds journey.eta}" if journey.eta > 0
|
85
|
+
end
|
86
|
+
|
87
|
+
photoset_ids.each do |set_id|
|
88
|
+
Flickr.arrangePhotos set_id
|
89
|
+
end
|
90
|
+
|
91
|
+
Printer.ask "Please, review whether: any of your photos need to be rotated,\n" +
|
92
|
+
" better primary photos for your sets have been uploaded,\n" +
|
93
|
+
" and better collection mosaics can be randomised."
|
94
|
+
rescue
|
95
|
+
die __LINE__, $!
|
96
|
+
end
|
97
|
+
|
98
|
+
end # module Flickru
|
99
|
+
|
100
|
+
if __FILE__ == $0
|
101
|
+
photo_dir = ARGV[0]
|
102
|
+
|
103
|
+
if ARGV.length > 1
|
104
|
+
Flickru.usage
|
105
|
+
Flickru.die __LINE__, "wrong number of arguments: #{ARGV[1,ARGV.length]}"
|
106
|
+
elsif not photo_dir or photo_dir.empty?
|
107
|
+
Flickru.usage
|
108
|
+
Flickru.die __LINE__, "missing photo directory"
|
109
|
+
end
|
110
|
+
|
111
|
+
Flickru.flickru photo_dir
|
112
|
+
end
|
data/lib/flickru/file.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# ruby
|
2
|
+
require 'escape'
|
3
|
+
require 'find'
|
4
|
+
|
5
|
+
class File
|
6
|
+
|
7
|
+
def File.find dir
|
8
|
+
found = Array.new
|
9
|
+
Find.find dir do |file|
|
10
|
+
if File.directory? file
|
11
|
+
if File.basename(file)[0] == ?.
|
12
|
+
Find.prune # don't look any further into this directory
|
13
|
+
else
|
14
|
+
next
|
15
|
+
end
|
16
|
+
else
|
17
|
+
if yield file
|
18
|
+
found << file
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
found
|
23
|
+
end
|
24
|
+
|
25
|
+
def File.image? file
|
26
|
+
IMAGE_EXTENSIONS.include? File.extname(file).downcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def File.video? file
|
30
|
+
VIDEO_EXTENSIONS.include? File.extname(file).downcase
|
31
|
+
end
|
32
|
+
|
33
|
+
def File.duration video
|
34
|
+
s = `#{Escape.shell_command ["ffmpeg", "-i", video]} 2>&1`
|
35
|
+
if s =~ /Duration: ([\d][\d]):([\d][\d]):([\d][\d]).([\d]+)/
|
36
|
+
hours = $1.to_i
|
37
|
+
mins = $2.to_i
|
38
|
+
seconds = $3.to_i
|
39
|
+
# fractions = ("." + $4).to_f
|
40
|
+
|
41
|
+
hours * 60 * 60 + mins * 60 + seconds
|
42
|
+
else
|
43
|
+
0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def File.human_readable_size file_size
|
48
|
+
if file_size < 1000
|
49
|
+
file_size.to_s + " bytes"
|
50
|
+
elsif file_size < 1000000
|
51
|
+
(file_size / 1000).to_s + "KB"
|
52
|
+
elsif file_size < 1000000000
|
53
|
+
(file_size / 1000000).to_s + "MB"
|
54
|
+
else
|
55
|
+
(file_size / 1000000000).to_s + "GB"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
IMAGE_EXTENSIONS = [".gif", ".jpg", ".jpeg", ".png"] # lowercase
|
62
|
+
VIDEO_EXTENSIONS = [".mpeg", ".mpg", ".avi"] # lowercase
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# gems
|
2
|
+
require 'flickraw'
|
3
|
+
require 'unicode_utils'
|
4
|
+
# flickru
|
5
|
+
require 'flickru/file'
|
6
|
+
require 'flickru/location'
|
7
|
+
require 'flickru/printer'
|
8
|
+
require 'flickru/ruby'
|
9
|
+
|
10
|
+
module Flickr
|
11
|
+
|
12
|
+
def self.access token, secret
|
13
|
+
flickr.access_token = token
|
14
|
+
flickr.access_secret = secret
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.login
|
18
|
+
token = flickr.get_request_token
|
19
|
+
auth_url = flickr.get_authorize_url token['oauth_token'], :perms => 'write' # read, delete
|
20
|
+
Printer.ask "Open this URL in your process to complete the authentication process:\n#{auth_url}\n"
|
21
|
+
Printer.enter "Copy the number given when you complete the process here"
|
22
|
+
|
23
|
+
verify = STDIN.gets.strip
|
24
|
+
flickr.get_access_token token['oauth_token'], token['oauth_token_secret'], verify
|
25
|
+
|
26
|
+
login = flickr.test.login
|
27
|
+
Printer.show "you are now authenticated as #{login.username} " +
|
28
|
+
"with token #{flickr.access_token} and secret #{flickr.access_secret}\n"
|
29
|
+
[flickr.access_token, flickr.access_secret]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.size_limit_exceeded? file
|
33
|
+
if File.image? file
|
34
|
+
File.size(file).to_i > 20000000 # 20MB
|
35
|
+
elsif File.video? file
|
36
|
+
File.size(file).to_i > 500000000 # 500MB
|
37
|
+
else
|
38
|
+
raise ArgumentError, "#{file}: not an image nor a video"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.upload_photo photo
|
43
|
+
Ruby.assert("not Flickr.size_limit_exceeded?(photo)") \
|
44
|
+
{ not Flickr.size_limit_exceeded?(photo) }
|
45
|
+
|
46
|
+
if File.duration(photo) > 90 # seconds
|
47
|
+
description = "video duration (#{File.duration photo} sec) exceeds Flickr's duration limit (90 sec)."
|
48
|
+
Printer.warn description
|
49
|
+
description = "This " + description + "\nDownload original file to play full video."
|
50
|
+
end
|
51
|
+
|
52
|
+
date = File.mtime(photo).strftime("%y-%m-%d %H:%M:%S")
|
53
|
+
loc = Location.new photo
|
54
|
+
Printer.show "uploading as " +
|
55
|
+
(loc.nil? ? "\"#{loc.name.black}\" (no location given)" : loc.to_s.black) +
|
56
|
+
" taken on #{date}... "
|
57
|
+
begin
|
58
|
+
id = flickr.upload_photo photo, :title => UnicodeUtils.nfkc(loc.name), :is_public => 0,
|
59
|
+
:description => description, :tags => UPLOADING_TAG,
|
60
|
+
:is_friend => 1, :is_family => 1, :safety_level => 1, # Safe,
|
61
|
+
:content_type => 1, :hidden => 2 # Photo/Video
|
62
|
+
# TODO visibility MAY be configurable
|
63
|
+
req = flickr.photos.getNotInSet(:extras => 'tags').to_hash["photo"][-1]
|
64
|
+
rescue Timeout::Error
|
65
|
+
Flickru.read_config
|
66
|
+
req = flickr.photos.getNotInSet(:extras => 'tags').to_hash["photo"][-1]
|
67
|
+
if req.nil?
|
68
|
+
raise RuntimeError, "unrecoverable timeout due to large file size"
|
69
|
+
else
|
70
|
+
if req.to_hash["tags"] != UPLOADING_TAG
|
71
|
+
raise RuntimeError, "unrecoverable timeout due to large file size"
|
72
|
+
end
|
73
|
+
id = req.to_hash["id"]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
flickr.photos.setTags :photo_id => id, :tags => ''
|
77
|
+
|
78
|
+
flickr.photos.setDates :photo_id => id, :date_taken => date
|
79
|
+
flickr.photos.setPerms :photo_id => id, :perm_comment => 1, :perm_addmeta => 0
|
80
|
+
# TODO permission MAY be configurable
|
81
|
+
if not loc.nil?
|
82
|
+
flickr.photos.geo.setLocation :photo_id => id, :lat => loc.latitude,
|
83
|
+
:lon => loc.longitude, :accuracy => loc.accuracy
|
84
|
+
end
|
85
|
+
|
86
|
+
Printer.success
|
87
|
+
|
88
|
+
return id
|
89
|
+
end
|
90
|
+
|
91
|
+
# TODO face tagging (http://developers.face.com http://github.com/rociiu/face) MAY be available
|
92
|
+
|
93
|
+
def self.classify photo_path, photo_id
|
94
|
+
set_title = File.basename(File.dirname(File.dirname(photo_path)))
|
95
|
+
set_id = Flickr.photoset_id set_title
|
96
|
+
photo_name = File.basename(photo_path, File.extname(photo_path))
|
97
|
+
if set_id
|
98
|
+
Printer.show "classifying #{photo_name.black} under set #{set_title}... "
|
99
|
+
flickr.photosets.addPhoto :photoset_id => set_id, :photo_id => photo_id
|
100
|
+
Printer.success
|
101
|
+
else
|
102
|
+
Printer.show "creating photoset #{set_title.black} with primary photo #{photo_name}... "
|
103
|
+
response = flickr.photosets.create :title => set_title, :primary_photo_id => photo_id
|
104
|
+
set_id = response.to_hash["id"]
|
105
|
+
Printer.success
|
106
|
+
|
107
|
+
# TODO new photosets MAY be included in some collection. Unfortunately, collections cannot be yet modified by the Flickr API.
|
108
|
+
# TODO photosets MAY be ordered alphabetically (flickr.photosets.orderSets API function)
|
109
|
+
Printer.ask "Please, choose yourself a better primary photo for this photoset,\n" +
|
110
|
+
" order your photosets for positioning this new addition,\n" +
|
111
|
+
" and include the new photoset in some collection."
|
112
|
+
end
|
113
|
+
|
114
|
+
return set_id
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.arrangePhotos set_id
|
118
|
+
set_title = flickr.photosets.getInfo(:photoset_id => set_id).to_hash["title"]
|
119
|
+
|
120
|
+
Printer.show "arranging photos in photoset #{set_title.black} by date taken (older first)... "
|
121
|
+
response = flickr.photosets.getPhotos(:photoset_id => set_id, :extras => "date_taken").to_hash
|
122
|
+
if response["pages"] == 1
|
123
|
+
photos = response["photo"].sort! { |a,b| a["datetaken"] <=> b["datetaken"] } # older first
|
124
|
+
photo_ids = photos.map { |photo| photo["id"].to_s }
|
125
|
+
if not photo_ids.empty?
|
126
|
+
photo_ids = photo_ids[1,photo_ids.length].reduce photo_ids[1] do |s,i| s + ',' + i end
|
127
|
+
flickr.photosets.reorderPhotos :photoset_id => set_id, :photo_ids => photo_ids
|
128
|
+
end
|
129
|
+
Printer.success
|
130
|
+
else
|
131
|
+
Printer.failure "photoset #{set_title} has more than #{response['perpage']} photos."
|
132
|
+
Printer.ask "Please, arrange by date taken (older first) within Flickr Organizr."
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.photoset_id set_title
|
137
|
+
flickr.photosets.getList.each do |photoset|
|
138
|
+
if photoset["title"] == set_title
|
139
|
+
return photoset["id"]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
return nil
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
UPLOADING_TAG = "uploading"
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# flickru
|
2
|
+
require 'flickru/ruby'
|
3
|
+
|
4
|
+
class Journey
|
5
|
+
|
6
|
+
def initialize destination
|
7
|
+
Ruby.assert("destination >= 0") { destination >= 0 }
|
8
|
+
|
9
|
+
@distance = destination
|
10
|
+
@destination = destination
|
11
|
+
@beginning = Time.now
|
12
|
+
@current = Time.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def take step
|
16
|
+
Ruby.assert("@distance >= step") { @distance >= step }
|
17
|
+
|
18
|
+
@distance -= step
|
19
|
+
@current = Time.now
|
20
|
+
end
|
21
|
+
|
22
|
+
def progress
|
23
|
+
@destination - @distance
|
24
|
+
end
|
25
|
+
|
26
|
+
def distance
|
27
|
+
@distance
|
28
|
+
end
|
29
|
+
|
30
|
+
def elapsed
|
31
|
+
@current - @beginning
|
32
|
+
end
|
33
|
+
|
34
|
+
def eta # seconds
|
35
|
+
@distance * elapsed / progress
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# gems
|
2
|
+
require 'escape'
|
3
|
+
# flickru
|
4
|
+
require 'flickru/ruby'
|
5
|
+
require 'flickru/string'
|
6
|
+
|
7
|
+
class Location
|
8
|
+
|
9
|
+
GEOWIKI = File.expand_path(File.join File.dirname(__FILE__), '..', '..', 'bin', 'geowiki')
|
10
|
+
DEFAULT_ACCURACY = "street"
|
11
|
+
COORD_PATTERN = "-?[0-9]+\.?[0-9]*"
|
12
|
+
|
13
|
+
attr_accessor :name, :place, :latitude, :longitude, :accuracy
|
14
|
+
|
15
|
+
def initialize photo
|
16
|
+
dir = File.basename File.dirname(photo)
|
17
|
+
name, place, accuracy = Location.parse dir
|
18
|
+
raise RuntimeError, "unnamed location #{dir}" if name.nil?
|
19
|
+
@name = name
|
20
|
+
begin
|
21
|
+
@place = place.nil? ? name : place
|
22
|
+
@latitude, @longitude = Location.locate @place
|
23
|
+
@accuracy = Location.s_to_accuracy(accuracy ? accuracy : DEFAULT_ACCURACY)
|
24
|
+
rescue RuntimeError
|
25
|
+
raise if place
|
26
|
+
raise RuntimeError, "location #{name} not found" if accuracy
|
27
|
+
@place, @latitude, @longitude, @accuracy = nil # no location
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def nil?
|
32
|
+
Ruby.assert("@accuracy.nil? implies @latitude.nil? and @longitude.nil?") {
|
33
|
+
@accuracy.nil? or not (@latitude.nil? and @longitude.nil?)
|
34
|
+
}
|
35
|
+
|
36
|
+
@accuracy.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
place = @place.nil? ? "an undefined place" : @place
|
41
|
+
"\"#{@name}\" at #{place} (~#{accuracy_to_s}) " +
|
42
|
+
"on lat: #{@latitude}, lon: #{@longitude}"
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def self.parse location
|
48
|
+
# TODO special characters MAY be escaped
|
49
|
+
idx_accuracy = location.index('#') ? location.index('#') : location.length
|
50
|
+
idx_place = location.index('@') ? location.index('@') : idx_accuracy
|
51
|
+
|
52
|
+
name = location.sub(/[@#].*$/, '').strip | nil
|
53
|
+
place = location.index('@') ? location.sub(/^.*@/, '').sub(/\#.*$/, '').strip : nil
|
54
|
+
accuracy = location.index('#') ? location.sub(/^.*#/, '').strip : nil
|
55
|
+
|
56
|
+
[name, place, accuracy]
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.locate place
|
60
|
+
the_place = place # needed for RuntimeError reporting
|
61
|
+
begin
|
62
|
+
place = `#{Escape.shell_command [GEOWIKI, place]} 2> /dev/null`[0..-2] \
|
63
|
+
if place !~ /^#{COORD_PATTERN}, *#{COORD_PATTERN}$/
|
64
|
+
if place =~ /^#{COORD_PATTERN}, *#{COORD_PATTERN}$/
|
65
|
+
# latitude, longitude
|
66
|
+
[place.sub(/, *#{COORD_PATTERN}$/, ''), place.sub(/^#{COORD_PATTERN}, */, '')]
|
67
|
+
else
|
68
|
+
raise RuntimeError
|
69
|
+
end
|
70
|
+
rescue
|
71
|
+
raise RuntimeError, "location #{the_place} not found"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def Location.s_to_accuracy str
|
76
|
+
case str.downcase
|
77
|
+
when "world" then 1
|
78
|
+
when "country" then 3
|
79
|
+
when "region" then 6
|
80
|
+
when "city" then 11
|
81
|
+
when "street" then 16
|
82
|
+
else raise ArgumentError, "unknown accuracy label '#{str}'"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def accuracy_to_s
|
87
|
+
case @accuracy
|
88
|
+
when 1 then "world"
|
89
|
+
when 3 then "country"
|
90
|
+
when 6 then "region"
|
91
|
+
when 11 then "city"
|
92
|
+
when 16 then "street"
|
93
|
+
else @accuracy
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# gems
|
2
|
+
require 'colorize'
|
3
|
+
# flickru
|
4
|
+
require 'flickru/ruby'
|
5
|
+
|
6
|
+
module Printer
|
7
|
+
|
8
|
+
def self.human_readable_seconds seconds
|
9
|
+
Ruby.assert("seconds >= 0") { seconds >= 0 }
|
10
|
+
|
11
|
+
return "%.1f" % seconds + " seconds" if seconds < 60
|
12
|
+
minutes = seconds / 60
|
13
|
+
return "%.1f" % minutes + " minutes" if minutes < 60
|
14
|
+
hours = minutes / 60
|
15
|
+
return "%.1f" % hours + " hours" if hours < 24
|
16
|
+
days = hours / 24
|
17
|
+
return "%.1f" % days + " days"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.show msg
|
21
|
+
print msg
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.info msg
|
25
|
+
puts msg.cyan
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.warn msg
|
29
|
+
puts ("warning: " + msg).magenta
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.error msg
|
33
|
+
STDERR.puts msg.red
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.enter msg
|
37
|
+
print (">>> " + msg + ": ").black
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.ask msg
|
41
|
+
puts ("*** " + msg + " ***").light_blue
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.success
|
45
|
+
puts "done".green
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.failure msg=nil
|
49
|
+
puts ("fail" + (msg.nil? ? "" : ": " + msg)).red
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/flickru/ruby.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ruby
|
2
|
+
|
3
|
+
def self.assert msg=nil
|
4
|
+
if $DEBUG
|
5
|
+
raise ArgumentError, msg || "assertion failed" unless yield
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.caller_method_name
|
10
|
+
parse_caller(caller(2).first).last
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.parse_caller at
|
16
|
+
if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
|
17
|
+
file = Regexp.last_match[1]
|
18
|
+
line = Regexp.last_match[2].to_i
|
19
|
+
method = Regexp.last_match[3]
|
20
|
+
[file, line, method]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# gems
|
2
|
+
require 'colorize'
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.coverage_dir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'var', 'cov')))
|
5
|
+
SimpleCov.start # should be before loading flickru code!
|
6
|
+
# flickru
|
7
|
+
require 'flickru'
|
8
|
+
|
9
|
+
describe Flickru do
|
10
|
+
it "accents work" do
|
11
|
+
Flickru.flickru "var/ts/tc_accents"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "different accuracies work case-insensitively" do
|
15
|
+
Flickru.flickru "var/ts/tc_accuracies"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "several collections, several sizes" do
|
19
|
+
Flickru.flickru "var/ts/tc_collections"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "various filetypes available, skipping garbage" do
|
23
|
+
Flickru.flickru "var/ts/tc_filetypes"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "large files work" do
|
27
|
+
Flickru.flickru "var/ts/tc_large_files"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "locations can be specified in many ways" do
|
31
|
+
Flickru.flickru "var/ts/tc_locations"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "leading and trailing whitespaces are removed" do
|
35
|
+
Flickru.flickru "var/ts/tc_whitespaces"
|
36
|
+
end
|
37
|
+
|
38
|
+
after(:all) do
|
39
|
+
puts "\nRemember to delete the testing files that have been just uploaded to your Flickr's collection 'Testing Collection*'.".red
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flickru
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Jes\xC3\xBAs Pardillo"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-12-18 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: colorize
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.5.8
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: escape
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.0.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: flickraw
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.4
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rubygems-update
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.8.11
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: simplecov
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.5.4
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id005
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: unicode_utils
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 1.1.2
|
79
|
+
type: :runtime
|
80
|
+
version_requirements: *id006
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.7.0
|
90
|
+
type: :development
|
91
|
+
version_requirements: *id007
|
92
|
+
description: |
|
93
|
+
Command-line tool 'flickru' automatises photos/videos uploads to Flickr.
|
94
|
+
|
95
|
+
Entering 'flickru <directory>' in your command line, any photos under 'directory' (and subdirs)
|
96
|
+
are uploaded to your Flickr account (interactively entered at the first time you start flickru).
|
97
|
+
|
98
|
+
Photos are identified by case-insensitive extensions: GIF, JPEG, JPG, and PNG.
|
99
|
+
Videos are identified by case-insensitive extensions: AVI, MPEG, and MPG.
|
100
|
+
|
101
|
+
The following Flickr metadata for photos (as well as videos) is set:
|
102
|
+
- date-taken from the file (last) modification time
|
103
|
+
- private, visible by friends & family, hidden for public searches
|
104
|
+
- safety level to safe
|
105
|
+
- permission for friends & family to add comments to the photo and it's notes
|
106
|
+
- permission for nobody to add notes and tags to the photo
|
107
|
+
- for videos larger than the Flickr's longer than 90s (but shorter than 500MB,
|
108
|
+
Flickr's maximum permisible size), the description will contain an annotation
|
109
|
+
about its large duration.
|
110
|
+
- title, geolocation, and accuracy from the owner directory's name.
|
111
|
+
Owner directory name format is given by the format 'TITLE[@LOCATION[#PRECISION]]',
|
112
|
+
where:
|
113
|
+
- TITLE is the desired title for the photos directly stored in the directory
|
114
|
+
- LOCATION is the location of the stored photos specified as either:
|
115
|
+
- the Wikipedia page name (whitespaces allowed) of the location (if exists) or
|
116
|
+
- its coordinates LATITUDE,LONGITUDE
|
117
|
+
- PRECISION is the Flickr geolocation precision given by one of the following
|
118
|
+
case insentitive literals: street, city, region, country, world.
|
119
|
+
|
120
|
+
Photos are classified into a photoset (existing or not) entitled with the
|
121
|
+
second-level directory name containing the photo. The photoset is arranged by
|
122
|
+
date taken (older first).
|
123
|
+
|
124
|
+
To see some examples on the directory structure recognised by flickru, please
|
125
|
+
explore the subdirectories under var/ts.
|
126
|
+
|
127
|
+
email:
|
128
|
+
- dev@jesuspardillo.com
|
129
|
+
executables:
|
130
|
+
- flickru
|
131
|
+
extensions: []
|
132
|
+
|
133
|
+
extra_rdoc_files: []
|
134
|
+
|
135
|
+
files:
|
136
|
+
- bin/flickru
|
137
|
+
- bin/geowiki
|
138
|
+
- lib/flickru.rb
|
139
|
+
- lib/flickru/file.rb
|
140
|
+
- lib/flickru/flickr.rb
|
141
|
+
- lib/flickru/journey.rb
|
142
|
+
- lib/flickru/location.rb
|
143
|
+
- lib/flickru/printer.rb
|
144
|
+
- lib/flickru/ruby.rb
|
145
|
+
- lib/flickru/string.rb
|
146
|
+
- lib/flickru/version.rb
|
147
|
+
- spec/flickru_spec.rb
|
148
|
+
homepage: http://rubygems.org/gems/flickru
|
149
|
+
licenses: []
|
150
|
+
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: "0"
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
none: false
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: "0"
|
168
|
+
requirements: []
|
169
|
+
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 1.8.11
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: Command-line Flickr Upload Automator
|
175
|
+
test_files:
|
176
|
+
- spec/flickru_spec.rb
|