gpx2exif 0.0.4 → 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.
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'gpx_utils/waypoints_exporter'
4
+ require 'gpx_utils/waypoints_importer'
5
+
6
+ require 'gpx_utils/track_importer'
7
+
8
+ module GpxUtils
9
+ end
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+
4
+ #$:.unshift(File.dirname(__FILE__))
5
+
6
+ # Simple parsing GPX file
7
+ module GpxUtils
8
+ class TrackImporter
9
+
10
+ def initialize
11
+ @coords = Array.new
12
+ end
13
+
14
+ attr_reader :coords
15
+
16
+ def add_file(path)
17
+ f = File.new(path)
18
+ doc = Nokogiri::XML(f)
19
+ doc.remove_namespaces!
20
+ a = Array.new
21
+ error_count = 0
22
+
23
+ trackpoints = doc.xpath('//gpx/trk/trkseg/trkpt')
24
+ trackpoints.each do |wpt|
25
+ w = {
26
+ :lat => wpt.xpath('@lat').to_s.to_f,
27
+ :lon => wpt.xpath('@lon').to_s.to_f,
28
+ :time => proc_time(wpt.xpath('time').children.first.to_s),
29
+ :alt => wpt.xpath('ele').children.first.to_s.to_f
30
+ }
31
+
32
+ if self.class.coord_valid?(w[:lat], w[:lon], w[:alt], w[:time])
33
+ a << w
34
+ else
35
+ error_count += 1
36
+ end
37
+
38
+ end
39
+
40
+ f.close
41
+
42
+ @coords += a
43
+ @coords = @coords.sort { |b, c| b[:time] <=> c[:time] }
44
+ end
45
+
46
+ # Only import valid coords
47
+ def self.coord_valid?(lat, lon, elevation, time)
48
+ return true if lat and lon
49
+ return false
50
+ end
51
+
52
+ def self.proc_time(ts)
53
+ if ts =~ /(\d{4})-(\d{2})-(\d{2})T(\d{1,2}):(\d{2}):(\d{2})Z/
54
+ return Time.gm($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).localtime
55
+ end
56
+ end
57
+
58
+ def proc_time(ts)
59
+ self.class.proc_time(ts)
60
+ end
61
+
62
+ end
63
+ end
@@ -4,8 +4,8 @@ require 'yaml'
4
4
 
5
5
  $:.unshift(File.dirname(__FILE__))
6
6
 
7
- module GarminUtils
8
- class WaypointListGenerator
7
+ module GpxUtils
8
+ class WaypointsExporter
9
9
 
10
10
  def initialize
11
11
  @pois = Array.new
@@ -2,8 +2,8 @@ require 'rubygems'
2
2
 
3
3
  $:.unshift(File.dirname(__FILE__))
4
4
 
5
- module GarminUtils
6
- class GpxWaypointParser
5
+ module GpxUtils
6
+ class WaypointsImporter
7
7
 
8
8
  def initialize
9
9
  @pois = Array.new
@@ -26,7 +26,7 @@ module GarminUtils
26
26
  w = {
27
27
  :lat => wpt.xpath('@lat').to_s.to_f,
28
28
  :lon => wpt.xpath('@lon').to_s.to_f,
29
- :time => Gpx2exif::GpxParser.proc_time(wpt.xpath('time').children.first.to_s),
29
+ :time => GpxUtils::TrackImporter.proc_time(wpt.xpath('time').children.first.to_s),
30
30
  :alt => wpt.xpath('ele').children.first.to_s.to_f,
31
31
  :name => wpt.xpath('name').children.first.to_s,
32
32
  :sym => wpt.xpath('sym').children.first.to_s
@@ -0,0 +1,450 @@
1
+ # -- encoding: utf-8 --
2
+ #
3
+ # MiniExiftool
4
+ #
5
+ # This library is wrapper for the Exiftool command-line
6
+ # application (http://www.sno.phy.queensu.ca/~phil/exiftool/)
7
+ # written by Phil Harvey.
8
+ # Read and write access is done in a clean OO manner.
9
+ #
10
+ # Author: Jan Friedrich
11
+ # Copyright (c) 2007-2012 by Jan Friedrich
12
+ # Licensed under the GNU LESSER GENERAL PUBLIC LICENSE,
13
+ # Version 2.1, February 1999
14
+ #
15
+
16
+ # This file was copied from
17
+
18
+ require 'fileutils'
19
+ require 'tempfile'
20
+ require 'pstore'
21
+ require 'rational'
22
+ require 'set'
23
+ require 'shellwords'
24
+ require 'time'
25
+
26
+ # Simple OO access to the Exiftool command-line application.
27
+ class MiniExiftool
28
+
29
+ # Name of the Exiftool command-line application
30
+ @@cmd = 'exiftool'
31
+
32
+ # Hash of the standard options used when call MiniExiftool.new
33
+ @@opts = { :numerical => false, :composite => true, :convert_encoding => false, :ignore_minor_errors => false, :timestamps => Time }
34
+
35
+ attr_reader :filename
36
+ attr_accessor :numerical, :composite, :convert_encoding, :ignore_minor_errors, :errors, :timestamps
37
+
38
+ VERSION = '1.5.1'
39
+
40
+ # +opts+ support at the moment
41
+ # * <code>:numerical</code> for numerical values, default is +false+
42
+ # * <code>:composite</code> for including composite tags while loading,
43
+ # default is +true+
44
+ # * <code>:convert_encoding</code> convert encoding (See -L-option of
45
+ # the exiftool command-line application, default is +false+)
46
+ # * <code>:ignore_minor_errors</code> ignore minor errors (See -m-option
47
+ # of the exiftool command-line application, default is +false+)
48
+ # * <code>:timestamps</code> generating DateTime objects instead of
49
+ # Time objects if set to <code>DateTime</code>, default is +Time+
50
+ #
51
+ # <b>ATTENTION:</b> Time objects are created using <code>Time.local</code>
52
+ # therefore they use <em>your local timezone</em>, DateTime objects instead
53
+ # are created <em>without timezone</em>!
54
+ def initialize filename=nil, opts={}
55
+ opts = @@opts.merge opts
56
+ @numerical = opts[:numerical]
57
+ @composite = opts[:composite]
58
+ @convert_encoding = opts[:convert_encoding]
59
+ @ignore_minor_errors = opts[:ignore_minor_errors]
60
+ @timestamps = opts[:timestamps]
61
+ @values = TagHash.new
62
+ @tag_names = TagHash.new
63
+ @changed_values = TagHash.new
64
+ @errors = TagHash.new
65
+ load filename unless filename.nil?
66
+ end
67
+
68
+ def initialize_from_hash hash # :nodoc:
69
+ hash.each_pair do |tag,value|
70
+ set_value tag, value
71
+ end
72
+ set_attributes_by_heuristic
73
+ self
74
+ end
75
+
76
+ # Load the tags of filename.
77
+ def load filename
78
+ unless filename && File.exist?(filename)
79
+ raise MiniExiftool::Error.new("File '#{filename}' does not exist.")
80
+ end
81
+ if File.directory?(filename)
82
+ raise MiniExiftool::Error.new("'#{filename}' is a directory.")
83
+ end
84
+ @filename = filename
85
+ @values.clear
86
+ @tag_names.clear
87
+ @changed_values.clear
88
+ opt_params = ''
89
+ opt_params << (@numerical ? '-n ' : '')
90
+ opt_params << (@composite ? '' : '-e ')
91
+ opt_params << (@convert_encoding ? '-L ' : '')
92
+ cmd = %Q(#@@cmd -q -q -s -t #{opt_params} #{@@sep_op} #{Shellwords.escape(filename)})
93
+ if run(cmd)
94
+ parse_output
95
+ else
96
+ raise MiniExiftool::Error.new(@error_text)
97
+ end
98
+ self
99
+ end
100
+
101
+ # Reload the tags of an already read file.
102
+ def reload
103
+ load @filename
104
+ end
105
+
106
+ # Returns the value of a tag.
107
+ def [] tag
108
+ @changed_values[tag] || @values[tag]
109
+ end
110
+
111
+ # Set the value of a tag.
112
+ def []=(tag, val)
113
+ @changed_values[tag] = val
114
+ end
115
+
116
+ # Returns true if any tag value is changed or if the value of a
117
+ # given tag is changed.
118
+ def changed? tag=false
119
+ if tag
120
+ @changed_values.include? tag
121
+ else
122
+ !@changed_values.empty?
123
+ end
124
+ end
125
+
126
+ # Revert all changes or the change of a given tag.
127
+ def revert tag=nil
128
+ if tag
129
+ val = @changed_values.delete(tag)
130
+ res = val != nil
131
+ else
132
+ res = @changed_values.size > 0
133
+ @changed_values.clear
134
+ end
135
+ res
136
+ end
137
+
138
+ # Returns an array of the tags (original tag names) of the read file.
139
+ def tags
140
+ @values.keys.map { |key| @tag_names[key] }
141
+ end
142
+
143
+ # Returns an array of all changed tags.
144
+ def changed_tags
145
+ @changed_values.keys.map { |key| MiniExiftool.original_tag(key) }
146
+ end
147
+
148
+ # Save the changes to the file.
149
+ def save
150
+ return false if @changed_values.empty?
151
+ @errors.clear
152
+ temp_file = Tempfile.new('mini_exiftool')
153
+ temp_file.close
154
+ temp_filename = temp_file.path
155
+ FileUtils.cp filename, temp_filename
156
+ all_ok = true
157
+ @changed_values.each do |tag, val|
158
+ original_tag = MiniExiftool.original_tag(tag)
159
+ arr_val = val.kind_of?(Array) ? val : [val]
160
+ arr_val.map! {|e| convert e}
161
+ tag_params = ''
162
+ arr_val.each do |v|
163
+ tag_params << %Q(-#{original_tag}=#{Shellwords.escape(v.to_s)} )
164
+ end
165
+ opt_params = ''
166
+ opt_params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '')
167
+ opt_params << (@convert_encoding ? '-L ' : '')
168
+ opt_params << (@ignore_minor_errors ? '-m' : '')
169
+ cmd = %Q(#@@cmd -q -P -overwrite_original #{opt_params} #{tag_params} #{temp_filename})
170
+ if convert_encoding && cmd.respond_to?(:encode)
171
+ cmd.encode('ISO-8859-1')
172
+ end
173
+ result = run(cmd)
174
+ unless result
175
+ all_ok = false
176
+ @errors[tag] = @error_text.gsub(/Nothing to do.\n\z/, '').chomp
177
+ end
178
+ end
179
+ if all_ok
180
+ FileUtils.cp temp_filename, filename
181
+ reload
182
+ end
183
+ temp_file.delete
184
+ all_ok
185
+ end
186
+
187
+ # Returns a hash of the original loaded values of the MiniExiftool
188
+ # instance.
189
+ def to_hash
190
+ result = {}
191
+ @values.each do |k,v|
192
+ result[@tag_names[k]] = v
193
+ end
194
+ result
195
+ end
196
+
197
+ # Returns a YAML representation of the original loaded values of the
198
+ # MiniExiftool instance.
199
+ def to_yaml
200
+ to_hash.to_yaml
201
+ end
202
+
203
+ # Create a MiniExiftool instance from a hash
204
+ def self.from_hash hash
205
+ instance = MiniExiftool.new
206
+ instance.initialize_from_hash hash
207
+ instance
208
+ end
209
+
210
+ # Create a MiniExiftool instance from YAML data created with
211
+ # MiniExiftool#to_yaml
212
+ def self.from_yaml yaml
213
+ MiniExiftool.from_hash YAML.load(yaml)
214
+ end
215
+
216
+ # Returns the command name of the called Exiftool application.
217
+ def self.command
218
+ @@cmd
219
+ end
220
+
221
+ # Setting the command name of the called Exiftool application.
222
+ def self.command= cmd
223
+ @@cmd = cmd
224
+ end
225
+
226
+ # Returns the options hash.
227
+ def self.opts
228
+ @@opts
229
+ end
230
+
231
+ # Returns a set of all known tags of Exiftool.
232
+ def self.all_tags
233
+ unless defined? @@all_tags
234
+ @@all_tags = pstore_get :all_tags
235
+ end
236
+ @@all_tags
237
+ end
238
+
239
+ # Returns a set of all possible writable tags of Exiftool.
240
+ def self.writable_tags
241
+ unless defined? @@writable_tags
242
+ @@writable_tags = pstore_get :writable_tags
243
+ end
244
+ @@writable_tags
245
+ end
246
+
247
+ # Returns the original Exiftool name of the given tag
248
+ def self.original_tag tag
249
+ unless defined? @@all_tags_map
250
+ @@all_tags_map = pstore_get :all_tags_map
251
+ end
252
+ @@all_tags_map[tag]
253
+ end
254
+
255
+ # Returns the version of the Exiftool command-line application.
256
+ def self.exiftool_version
257
+ output = `#{MiniExiftool.command} -ver 2>&1`
258
+ unless $?.exitstatus == 0
259
+ raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found")
260
+ end
261
+ output.chomp!
262
+ end
263
+
264
+ def self.unify tag
265
+ tag.to_s.gsub(/[-_]/,'').downcase
266
+ end
267
+
268
+ # Exception class
269
+ class MiniExiftool::Error < StandardError; end
270
+
271
+ ############################################################################
272
+ private
273
+ ############################################################################
274
+
275
+ @@error_file = Tempfile.new 'errors'
276
+ @@error_file.close
277
+
278
+ # quick fix - when exiftool command is not installed and not used
279
+ begin
280
+ @@exiftool_version = exiftool_version
281
+ rescue
282
+ @@exiftool_version = '-1.0'
283
+ end
284
+
285
+ if Float(@@exiftool_version) < 7.41
286
+ @@separator = ', '
287
+ @@sep_op = ''
288
+ else
289
+ @@separator = '@@'
290
+ @@sep_op = '-sep @@'
291
+ end
292
+
293
+ def run cmd
294
+ if $DEBUG
295
+ $stderr.puts cmd
296
+ end
297
+ @output = `#{cmd} 2>#{@@error_file.path}`
298
+ if convert_encoding && @output.respond_to?(:force_encoding)
299
+ @output.force_encoding('ISO-8859-1')
300
+ end
301
+ @status = $?
302
+ unless @status.exitstatus == 0
303
+ @error_text = File.readlines(@@error_file.path).join
304
+ return false
305
+ else
306
+ @error_text = ''
307
+ return true
308
+ end
309
+ end
310
+
311
+ def convert val
312
+ case val
313
+ when Time
314
+ val = val.strftime('%Y:%m:%d %H:%M:%S')
315
+ end
316
+ val
317
+ end
318
+
319
+ def method_missing symbol, *args
320
+ tag_name = symbol.id2name
321
+ if tag_name.sub!(/=$/, '')
322
+ self[tag_name] = args.first
323
+ else
324
+ self[tag_name]
325
+ end
326
+ end
327
+
328
+ def parse_output
329
+ @output.each_line do |line|
330
+ tag, value = parse_line line
331
+ set_value tag, value
332
+ end
333
+ end
334
+
335
+ def parse_line line
336
+ if line =~ /^([^\t]+)\t(.*)$/
337
+ tag, value = $1, $2
338
+ case value
339
+ when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d/
340
+ s = value.sub(/^(\d+):(\d+):/, '\1-\2-')
341
+ begin
342
+ if @timestamps == Time
343
+ value = Time.parse(s)
344
+ elsif @timestamps == DateTime
345
+ value = DateTime.parse(s)
346
+ else
347
+ raise MiniExiftool::Error.new("Value #@timestamps not allowed for option timestamps.")
348
+ end
349
+ rescue ArgumentError
350
+ value = false
351
+ end
352
+ when /^\d+\.\d+$/
353
+ value = value.to_f
354
+ when /^0+[1-9]+$/
355
+ # nothing => String
356
+ when /^-?\d+$/
357
+ value = value.to_i
358
+ when %r(^(\d+)/(\d+)$)
359
+ value = Rational($1.to_i, $2.to_i)
360
+ when /^[\d ]+$/
361
+ # nothing => String
362
+ when /#{@@separator}/
363
+ value = value.split @@separator
364
+ end
365
+ else
366
+ raise MiniExiftool::Error.new("Malformed line #{line.inspect} of exiftool output.")
367
+ end
368
+ return [tag, value]
369
+ end
370
+
371
+ def set_value tag, value
372
+ @tag_names[tag] = tag
373
+ @values[tag] = value
374
+ end
375
+
376
+ def set_attributes_by_heuristic
377
+ self.composite = tags.include?('ImageSize') ? true : false
378
+ self.numerical = self.file_size.kind_of?(Integer) ? true : false
379
+ # TODO: Is there a heuristic to determine @convert_encoding?
380
+ self.timestamps = self.FileModifyDate.kind_of?(DateTime) ? DateTime : Time
381
+ end
382
+
383
+ def temp_filename
384
+ unless @temp_filename
385
+ temp_file = Tempfile.new('mini-exiftool')
386
+ temp_file.close
387
+ FileUtils.cp(@filename, temp_file.path)
388
+ @temp_filename = temp_file.path
389
+ end
390
+ @temp_filename
391
+ end
392
+
393
+ def self.pstore_get attribute
394
+ load_or_create_pstore unless defined? @@pstore
395
+ result = nil
396
+ @@pstore.transaction(true) do |ps|
397
+ result = ps[attribute]
398
+ end
399
+ result
400
+ end
401
+
402
+ def self.load_or_create_pstore
403
+ # This will hopefully work on *NIX and Windows systems
404
+ home = ENV['HOME'] || ENV['HOMEDRIVE'] + ENV['HOMEPATH'] || ENV['USERPROFILE']
405
+ subdir = RUBY_PLATFORM =~ /\bmswin/i ? '_mini_exiftool' : '.mini_exiftool'
406
+ FileUtils.mkdir_p(File.join(home, subdir))
407
+ filename = File.join(home, subdir, 'exiftool_tags_' << exiftool_version.gsub('.', '_') << '.pstore')
408
+ @@pstore = PStore.new filename
409
+ if !File.exist?(filename) || File.size(filename) == 0
410
+ @@pstore.transaction do |ps|
411
+ ps[:all_tags] = all_tags = determine_tags('list')
412
+ ps[:writable_tags] = determine_tags('listw')
413
+ map = {}
414
+ all_tags.each { |k| map[unify(k)] = k }
415
+ ps[:all_tags_map] = map
416
+ end
417
+ end
418
+ end
419
+
420
+ def self.determine_tags arg
421
+ output = `#{@@cmd} -#{arg}`
422
+ lines = output.split(/\n/)
423
+ tags = Set.new
424
+ lines.each do |line|
425
+ next unless line =~ /^\s/
426
+ tags |= line.chomp.split
427
+ end
428
+ tags
429
+ end
430
+
431
+
432
+ # Hash with indifferent access:
433
+ # DateTimeOriginal == datetimeoriginal == date_time_original
434
+ class TagHash < Hash # :nodoc:
435
+ def[] k
436
+ super(unify(k))
437
+ end
438
+ def []= k, v
439
+ super(unify(k), v)
440
+ end
441
+ def delete k
442
+ super(unify(k))
443
+ end
444
+
445
+ def unify tag
446
+ MiniExiftool.unify tag
447
+ end
448
+ end
449
+
450
+ end