adri 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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +166 -0
- data/bin/adri +306 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2fc54b8d1ef7e4486a3d5926fc8c28633ceab6709d0a9160d026ae8497e99e65
|
4
|
+
data.tar.gz: ea3895da8d463f866fe4b702f0204a31596dd0047d401fc6cdcf2ff4321d676d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5433f1bafa362f9d04e5c1c6401195941c82ba08aa86ce8f597b23459b58889311ca65e827ad5ebce9c018dea393d1a89274e840adac4926abe41c99ad7756bd
|
7
|
+
data.tar.gz: e9b46b82b46126ba281fd895154093bb339f9c3fb0bcc928acd888251e9ceb87323bd01f483432a8177e78c0dbd11beaa6c245dc828c512b37a96fb5f212d884
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Angelos Orfanakos
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# adri
|
2
|
+
|
3
|
+
adri organizes JPEG/TIFF photographs according to their EXIF date and location
|
4
|
+
data into a custom directory structure.
|
5
|
+
|
6
|
+
In other words, it turns this:
|
7
|
+
|
8
|
+
```sh
|
9
|
+
$ ls -1 photos/*.jpg
|
10
|
+
IMG100001.jpg
|
11
|
+
IMG100002.jpg
|
12
|
+
IMG100003.jpg
|
13
|
+
```
|
14
|
+
|
15
|
+
To this:
|
16
|
+
|
17
|
+
```sh
|
18
|
+
$ tree photos/2018/
|
19
|
+
photos/2018/
|
20
|
+
└── 10/
|
21
|
+
└── 14/
|
22
|
+
└── London
|
23
|
+
├── IMG100001.jpg
|
24
|
+
├── IMG100002.jpg
|
25
|
+
└── IMG100003.jpg
|
26
|
+
```
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Install the necessary packages. For Debian/Ubuntu, issue:
|
31
|
+
|
32
|
+
```sh
|
33
|
+
sudo apt install ruby-full git build-essential libexif-dev
|
34
|
+
```
|
35
|
+
|
36
|
+
Install adri:
|
37
|
+
|
38
|
+
```sh
|
39
|
+
sudo gem install adri
|
40
|
+
```
|
41
|
+
|
42
|
+
## Configuration
|
43
|
+
|
44
|
+
adri converts the GPS coordinates (latitude and longitude) recorded in a
|
45
|
+
photograph's EXIF headers to a location name using the [Google Maps API][].
|
46
|
+
|
47
|
+
To use it, you need a free [API key][] with the Geocoding API enabled.
|
48
|
+
|
49
|
+
You can then set the API key in a `GOOGLE_API_KEY` environment variable in your
|
50
|
+
shell's configuration file. For Bash, issue:
|
51
|
+
|
52
|
+
```sh
|
53
|
+
$ cat >>.~/.bashrc
|
54
|
+
export GOOGLE_API_KEY=yourapikeyhere
|
55
|
+
^D
|
56
|
+
```
|
57
|
+
|
58
|
+
Note: `^D` stands for `Ctrl-D`
|
59
|
+
|
60
|
+
You can also pass the API key as a command line option with `--api-key`. This
|
61
|
+
overrides the environment variable.
|
62
|
+
|
63
|
+
## Use
|
64
|
+
|
65
|
+
To get the help text, issue:
|
66
|
+
|
67
|
+
```sh
|
68
|
+
$ adri -h
|
69
|
+
usage: adri [options] <path>...
|
70
|
+
-p, --prefix Place everything under this path (default: photo parent directory)
|
71
|
+
-f, --path-format Format path with strftime and %{location} (default: %Y/%m/%d/%{location})
|
72
|
+
--api-key Google Maps API key (default: $GOOGLE_API_KEY)
|
73
|
+
--run Perform changes instead of a dry run
|
74
|
+
-q, --quiet Do not print operations
|
75
|
+
--version Print program version
|
76
|
+
-h, --help Print help text
|
77
|
+
```
|
78
|
+
|
79
|
+
**By default, adri runs in dry run mode.** This means it simply prints out what
|
80
|
+
it would do, without actually doing it:
|
81
|
+
|
82
|
+
```sh
|
83
|
+
$ pwd
|
84
|
+
/home/agorf/work/adri/
|
85
|
+
$ ls -1 photos/*.jpg
|
86
|
+
IMG100001.jpg
|
87
|
+
IMG100002.jpg
|
88
|
+
IMG100003.jpg
|
89
|
+
$ adri photos/*.jpg
|
90
|
+
/home/agorf/work/adri/photos/IMG100001.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100001.jpg (DRY RUN)
|
91
|
+
/home/agorf/work/adri/photos/IMG100002.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100002.jpg (DRY RUN)
|
92
|
+
/home/agorf/work/adri/photos/IMG100003.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100003.jpg (DRY RUN)
|
93
|
+
$ ls -1 photos/
|
94
|
+
IMG100001.jpg
|
95
|
+
IMG100002.jpg
|
96
|
+
IMG100003.jpg
|
97
|
+
```
|
98
|
+
|
99
|
+
To apply the changes, use the `--run` option:
|
100
|
+
|
101
|
+
```sh
|
102
|
+
$ adri --run photos/*.jpg
|
103
|
+
/home/agorf/work/adri/photos/IMG100001.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100001.jpg
|
104
|
+
/home/agorf/work/adri/photos/IMG100002.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100002.jpg
|
105
|
+
/home/agorf/work/adri/photos/IMG100003.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100003.jpg
|
106
|
+
$ tree photos/
|
107
|
+
photos/
|
108
|
+
└── 2018/
|
109
|
+
└── 10/
|
110
|
+
└── 14/
|
111
|
+
└── London/
|
112
|
+
├── IMG100001.jpg
|
113
|
+
├── IMG100002.jpg
|
114
|
+
└── IMG100003.jpg
|
115
|
+
```
|
116
|
+
|
117
|
+
To place everything under a path other than the parent directory of each
|
118
|
+
photograph, use the `--prefix` option:
|
119
|
+
|
120
|
+
```sh
|
121
|
+
$ adri --prefix . photos/*.jpg
|
122
|
+
/home/agorf/work/adri/photos/IMG100001.jpg -> /home/agorf/work/adri/2018/10/14/London/IMG100001.jpg (DRY RUN)
|
123
|
+
/home/agorf/work/adri/photos/IMG100002.jpg -> /home/agorf/work/adri/2018/10/14/London/IMG100002.jpg (DRY RUN)
|
124
|
+
/home/agorf/work/adri/photos/IMG100003.jpg -> /home/agorf/work/adri/2018/10/14/London/IMG100003.jpg (DRY RUN)
|
125
|
+
```
|
126
|
+
|
127
|
+
The default path format is year/month/day/location. It is possible to specify a
|
128
|
+
custom one with the `--path-format` option:
|
129
|
+
|
130
|
+
```sh
|
131
|
+
$ adri --path-format '%{location}/%b %Y/%d' photos/*.jpg
|
132
|
+
/home/agorf/work/adri/photos/IMG100001.jpg -> /home/agorf/work/adri/photos/London/Oct 2018/14/IMG100001.jpg (DRY RUN)
|
133
|
+
/home/agorf/work/adri/photos/IMG100002.jpg -> /home/agorf/work/adri/photos/London/Oct 2018/14/IMG100002.jpg (DRY RUN)
|
134
|
+
/home/agorf/work/adri/photos/IMG100003.jpg -> /home/agorf/work/adri/photos/London/Oct 2018/14/IMG100003.jpg (DRY RUN)
|
135
|
+
```
|
136
|
+
|
137
|
+
The date is formatted according to [strftime(3)][strftime].
|
138
|
+
|
139
|
+
It's also possible to process many photos at once by passing space-separated
|
140
|
+
file names and directories (in which case adri will [recurse][]):
|
141
|
+
|
142
|
+
```sh
|
143
|
+
$ adri photos/IMG100001.jpg photos/IMG100002.jpg photos/IMG100003.jpg
|
144
|
+
/home/agorf/work/adri/photos/IMG100001.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100001.jpg (DRY RUN)
|
145
|
+
/home/agorf/work/adri/photos/IMG100002.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100002.jpg (DRY RUN)
|
146
|
+
/home/agorf/work/adri/photos/IMG100003.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100003.jpg (DRY RUN)
|
147
|
+
$ adri photos/
|
148
|
+
/home/agorf/work/adri/photos/IMG100001.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100001.jpg (DRY RUN)
|
149
|
+
/home/agorf/work/adri/photos/IMG100002.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100002.jpg (DRY RUN)
|
150
|
+
/home/agorf/work/adri/photos/IMG100003.jpg -> /home/agorf/work/adri/photos/2018/10/14/London/IMG100003.jpg (DRY RUN)
|
151
|
+
```
|
152
|
+
|
153
|
+
## License
|
154
|
+
|
155
|
+
[MIT][]
|
156
|
+
|
157
|
+
## Author
|
158
|
+
|
159
|
+
[Angelos Orfanakos](https://agorf.gr/contact/)
|
160
|
+
|
161
|
+
[Bundler]: https://bundler.io/
|
162
|
+
[Google Maps API]: https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse
|
163
|
+
[API key]: https://cloud.google.com/maps-platform/#get-started
|
164
|
+
[MIT]: https://github.com/agorf/adri/blob/master/LICENSE.txt
|
165
|
+
[recurse]: https://softwareengineering.stackexchange.com/a/184600/316578
|
166
|
+
[strftime]: http://man7.org/linux/man-pages/man3/strftime.3.html
|
data/bin/adri
ADDED
@@ -0,0 +1,306 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'dotenv/load'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'exif'
|
12
|
+
require 'geocoder'
|
13
|
+
require 'slop'
|
14
|
+
|
15
|
+
module Adri
|
16
|
+
DEFAULT_PATH_FORMAT = '%Y/%m/%d/%{location}'.freeze
|
17
|
+
EXTENSIONS = %w[jpg jpeg JPG JPEG tif tiff TIF TIFF].freeze
|
18
|
+
GEOCODE_MAX_DELAY = 60 # Seconds
|
19
|
+
LOCATION_CACHE_SCALE = 2
|
20
|
+
VERSION = '0.0.1'.freeze
|
21
|
+
|
22
|
+
class Photo
|
23
|
+
class << self
|
24
|
+
attr_accessor :location_cache
|
25
|
+
end
|
26
|
+
|
27
|
+
self.location_cache = Hash.new(false)
|
28
|
+
|
29
|
+
attr_reader(
|
30
|
+
:source_path,
|
31
|
+
:prefix,
|
32
|
+
:path_format,
|
33
|
+
:verbose,
|
34
|
+
:dry_run
|
35
|
+
)
|
36
|
+
|
37
|
+
def initialize(path, options)
|
38
|
+
@source_path = File.absolute_path(path)
|
39
|
+
@path_format = options[:path_format].gsub('/', File::SEPARATOR)
|
40
|
+
@verbose = !options[:quiet]
|
41
|
+
@dry_run = !options[:run]
|
42
|
+
|
43
|
+
@prefix =
|
44
|
+
if options[:prefix].to_s.strip.empty?
|
45
|
+
File.dirname(@source_path)
|
46
|
+
else
|
47
|
+
File.absolute_path(options[:prefix])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def date_time
|
52
|
+
return @date_time if @date_time
|
53
|
+
|
54
|
+
if exif&.date_time && exif.date_time != '0000:00:00 00:00:00'
|
55
|
+
@date_time =
|
56
|
+
Time.strptime(exif.date_time.sub(' 24:', ' 00:'), '%Y:%m:%d %H:%M:%S')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def latitude
|
61
|
+
return @latitude if @latitude
|
62
|
+
|
63
|
+
if exif&.gps_latitude
|
64
|
+
@latitude = geo_float(exif.gps_latitude)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def longitude
|
69
|
+
return @longitude if @longitude
|
70
|
+
|
71
|
+
if exif&.gps_longitude
|
72
|
+
@longitude = geo_float(exif.gps_longitude)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def location
|
77
|
+
return @location if defined?(@location)
|
78
|
+
|
79
|
+
@location = read_location_from_cache
|
80
|
+
|
81
|
+
return @location if @location != false
|
82
|
+
|
83
|
+
@location = location_from_latlng
|
84
|
+
|
85
|
+
write_location_to_cache
|
86
|
+
|
87
|
+
@location
|
88
|
+
end
|
89
|
+
|
90
|
+
def destination_path
|
91
|
+
return @destination_path if @destination_path
|
92
|
+
|
93
|
+
path = date_time.strftime(path_format)
|
94
|
+
|
95
|
+
if location_in_path_format?
|
96
|
+
path = sprintf(path, location: location)
|
97
|
+
end
|
98
|
+
|
99
|
+
@destination_path ||= File.join(
|
100
|
+
prefix,
|
101
|
+
path,
|
102
|
+
File.basename(source_path)
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def move
|
107
|
+
return if skip_move?
|
108
|
+
|
109
|
+
if verbose
|
110
|
+
puts sprintf(
|
111
|
+
'%s -> %s%s',
|
112
|
+
source_path,
|
113
|
+
destination_path,
|
114
|
+
dry_run ? ' (DRY RUN)' : ''
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
return if dry_run
|
119
|
+
|
120
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
121
|
+
FileUtils.mv(source_path, destination_path)
|
122
|
+
end
|
123
|
+
|
124
|
+
private def exif
|
125
|
+
begin
|
126
|
+
@exif ||= Exif::Data.new(File.open(source_path))
|
127
|
+
rescue Exif::NotReadable
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private def location_in_path_format?
|
132
|
+
path_format['%{location}']
|
133
|
+
end
|
134
|
+
|
135
|
+
private def latlng
|
136
|
+
@latlng ||= [latitude, longitude].compact
|
137
|
+
end
|
138
|
+
|
139
|
+
private def skip_move?
|
140
|
+
if !File.exist?(source_path)
|
141
|
+
puts "Missing file #{source_path}" if verbose
|
142
|
+
return true
|
143
|
+
end
|
144
|
+
|
145
|
+
if date_time.nil?
|
146
|
+
puts "No datetime data #{source_path}" if verbose
|
147
|
+
return true
|
148
|
+
end
|
149
|
+
|
150
|
+
if location_in_path_format?
|
151
|
+
if latlng.empty?
|
152
|
+
puts "No location data #{source_path}" if verbose
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
if location.nil? # Geocoding failed
|
157
|
+
puts "Unknown location #{source_path}" if verbose
|
158
|
+
return true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
if File.exist?(destination_path)
|
163
|
+
puts "Existing file #{destination_path}" if verbose
|
164
|
+
return true
|
165
|
+
end
|
166
|
+
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
private def location_from_latlng
|
171
|
+
current_delay = 0.1 # 100 ms
|
172
|
+
|
173
|
+
begin
|
174
|
+
geocode_results = Geocoder.search(latlng)
|
175
|
+
rescue Geocoder::OverQueryLimitError
|
176
|
+
puts 'Got OverQueryLimitError' if verbose
|
177
|
+
|
178
|
+
if current_delay > GEOCODE_MAX_DELAY
|
179
|
+
puts "Exceeded max delay of #{GEOCODE_MAX_DELAY} seconds" if verbose
|
180
|
+
return
|
181
|
+
end
|
182
|
+
|
183
|
+
puts "Waiting #{current_delay} seconds before retrying..." if verbose
|
184
|
+
sleep(current_delay)
|
185
|
+
current_delay *= 2 # Exponential backoff
|
186
|
+
|
187
|
+
retry
|
188
|
+
end
|
189
|
+
|
190
|
+
geocode_results.map(&:city).compact.uniq.first
|
191
|
+
end
|
192
|
+
|
193
|
+
private def location_cache_key
|
194
|
+
@location_cache_key ||= [
|
195
|
+
latitude.truncate(LOCATION_CACHE_SCALE).to_s,
|
196
|
+
longitude.truncate(LOCATION_CACHE_SCALE).to_s
|
197
|
+
]
|
198
|
+
end
|
199
|
+
|
200
|
+
private def write_location_to_cache
|
201
|
+
self.class.location_cache[location_cache_key] = location
|
202
|
+
end
|
203
|
+
|
204
|
+
private def read_location_from_cache
|
205
|
+
self.class.location_cache[location_cache_key]
|
206
|
+
end
|
207
|
+
|
208
|
+
private def geo_float(value)
|
209
|
+
degrees, minutes, seconds = value
|
210
|
+
degrees + minutes / 60.0 + seconds / 3600.0
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.parse_args
|
215
|
+
Slop.parse do |o|
|
216
|
+
o.banner = "usage: #{$PROGRAM_NAME} [options] <path>..."
|
217
|
+
|
218
|
+
o.string(
|
219
|
+
'-p',
|
220
|
+
'--prefix',
|
221
|
+
'Place everything under this path (default: photo parent directory)'
|
222
|
+
)
|
223
|
+
|
224
|
+
o.string(
|
225
|
+
'-f',
|
226
|
+
'--path-format',
|
227
|
+
'Format path with strftime and %{location} (default: ' \
|
228
|
+
"#{DEFAULT_PATH_FORMAT})",
|
229
|
+
default: DEFAULT_PATH_FORMAT
|
230
|
+
)
|
231
|
+
|
232
|
+
o.string(
|
233
|
+
'--api-key',
|
234
|
+
'Google Maps API key (default: $GOOGLE_API_KEY)',
|
235
|
+
default: ENV['GOOGLE_API_KEY']
|
236
|
+
)
|
237
|
+
|
238
|
+
o.bool('--run', 'Perform changes instead of a dry run')
|
239
|
+
|
240
|
+
o.bool('-q', '--quiet', 'Do not print operations')
|
241
|
+
|
242
|
+
o.on('--version', 'Print program version') do
|
243
|
+
puts [$PROGRAM_NAME, VERSION].join(' ')
|
244
|
+
exit
|
245
|
+
end
|
246
|
+
|
247
|
+
o.on('-h', '--help', 'Print help text') do
|
248
|
+
puts o
|
249
|
+
exit
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.expand_paths(paths, verbose)
|
255
|
+
glob_pattern = File.join('**', "*.{#{EXTENSIONS.join(',')}}")
|
256
|
+
|
257
|
+
Enumerator.new do |y|
|
258
|
+
paths.each do |path|
|
259
|
+
file_paths =
|
260
|
+
if FileTest.directory?(path)
|
261
|
+
Dir.glob(File.join(path, glob_pattern)).sort
|
262
|
+
elsif !File.exist?(path)
|
263
|
+
puts "Missing #{path}" if verbose
|
264
|
+
elsif !FileTest.file?(path) || FileTest.symlink?(path)
|
265
|
+
puts "Not a file #{path}" if verbose
|
266
|
+
elsif !EXTENSIONS.include?(File.extname(path).delete('.'))
|
267
|
+
if verbose
|
268
|
+
puts "File extension not one of: #{EXTENSIONS.join(', ')}"
|
269
|
+
end
|
270
|
+
else
|
271
|
+
[path]
|
272
|
+
end || []
|
273
|
+
|
274
|
+
file_paths.each do |file_path|
|
275
|
+
y << file_path
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
opts = Adri.parse_args
|
283
|
+
args = opts.arguments
|
284
|
+
|
285
|
+
if args.empty?
|
286
|
+
puts opts
|
287
|
+
exit
|
288
|
+
end
|
289
|
+
|
290
|
+
options = opts.to_h
|
291
|
+
|
292
|
+
if options[:path_format]['%{location}'] && options[:api_key].to_s.strip.empty?
|
293
|
+
puts 'Please specify a Google Maps API key or remove %{location} from path ' \
|
294
|
+
'format'
|
295
|
+
exit 1
|
296
|
+
end
|
297
|
+
|
298
|
+
Geocoder.configure(
|
299
|
+
always_raise: :all,
|
300
|
+
lookup: :google,
|
301
|
+
api_key: options[:api_key]
|
302
|
+
)
|
303
|
+
|
304
|
+
Adri.expand_paths(args, !options[:quiet]).each do |path|
|
305
|
+
Adri::Photo.new(path, options).move
|
306
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adri
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Angelos Orfanakos
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: exif
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.2.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.2.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: geocoder
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.5'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.5.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.5'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.5.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: slop
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '4.6'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 4.6.2
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '4.6'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 4.6.2
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: dotenv
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '2.5'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.5.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.5'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 2.5.0
|
93
|
+
description:
|
94
|
+
email: me@agorf.gr
|
95
|
+
executables:
|
96
|
+
- adri
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- LICENSE.txt
|
101
|
+
- README.md
|
102
|
+
- bin/adri
|
103
|
+
homepage: https://github.com/agorf/adri
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 2.3.0
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.7.6
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Organize photos by date and location in a directory structure
|
127
|
+
test_files: []
|