timestamp_maker 1.2.1 → 1.3.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 +4 -4
- data/bin/timestamp +8 -2
- data/lib/timestamp_maker.rb +62 -48
- data/lib/timestamp_maker/{video_timestamper.rb → handlers/ffmpeg.rb} +36 -6
- data/lib/timestamp_maker/handlers/image_magick.rb +113 -0
- data/lib/timestamp_maker/mime_recognizers/marcel.rb +13 -0
- data/lib/timestamp_maker/time_zone_lookupers/geo_name.rb +30 -0
- data/lib/timestamp_maker/time_zone_lookupers/mock.rb +15 -0
- data/lib/timestamp_maker/time_zone_lookupers/wheretz.rb +18 -0
- metadata +22 -5
- data/lib/timestamp_maker/image_timestamper.rb +0 -69
- data/lib/timestamp_maker/mime_recognizer.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7f96f255213ceac42648b8ad1317b745d64062f6ad116fd8365d64408077101
|
4
|
+
data.tar.gz: 6c4c191ee701c7ffbf539b9665df9b3064608f50cf7eee06cffede24f46aa7a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f4e5a1e7f61304642cc758d82a4ec2a95af43a4f10fda93cb1f813468a1f6a6a321da5606acca79ad8724ca9ed79af162b8bedf82b3d9c0cae51ffd59a111de
|
7
|
+
data.tar.gz: 02712023f7dcd89f9083b95074ddde9770c6b73083e314d380bc71dd5020896a650ef509f79ff7183821f5ca18193805c2c1e9cc5a8cd65624ff5796bbe74214
|
data/bin/timestamp
CHANGED
@@ -16,7 +16,8 @@ options = {
|
|
16
16
|
coordinate_origin: 'top-left',
|
17
17
|
x: 32,
|
18
18
|
y: 32,
|
19
|
-
font_padding: 8
|
19
|
+
font_padding: 8,
|
20
|
+
require: []
|
20
21
|
}
|
21
22
|
|
22
23
|
option_parser = OptionParser.new do |parser|
|
@@ -69,6 +70,10 @@ option_parser = OptionParser.new do |parser|
|
|
69
70
|
parser.on('--font-padding NUM', Integer, 'Defaults to 8.') do |value|
|
70
71
|
options[:font_padding] = value
|
71
72
|
end
|
73
|
+
|
74
|
+
parser.on('-r LIB', '--require LIB', Array, 'Comma-separated Ruby libs') do |value|
|
75
|
+
options[:require] = value
|
76
|
+
end
|
72
77
|
end
|
73
78
|
option_parser.parse!
|
74
79
|
|
@@ -80,7 +85,8 @@ end
|
|
80
85
|
input = ARGV.shift
|
81
86
|
output = ARGV.shift
|
82
87
|
|
83
|
-
|
88
|
+
options[:require].each { |i| require i }
|
89
|
+
TimestampMaker.instance.add_timestamp(
|
84
90
|
input,
|
85
91
|
output,
|
86
92
|
format: options[:format],
|
data/lib/timestamp_maker.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'marcel'
|
4
3
|
require 'pathname'
|
5
|
-
require 'timestamp_maker/
|
6
|
-
require 'timestamp_maker/
|
7
|
-
require 'timestamp_maker/
|
4
|
+
require 'timestamp_maker/handlers/image_magick'
|
5
|
+
require 'timestamp_maker/handlers/ffmpeg'
|
6
|
+
require 'timestamp_maker/mime_recognizers/marcel'
|
7
|
+
require 'timestamp_maker/time_zone_lookupers/wheretz'
|
8
8
|
require 'tzinfo'
|
9
9
|
|
10
|
-
|
10
|
+
class TimestampMaker
|
11
11
|
COORDINATE_ORIGINS = %w[
|
12
12
|
top-left
|
13
13
|
top-right
|
@@ -15,55 +15,69 @@ module TimestampMaker
|
|
15
15
|
bottom-right
|
16
16
|
].freeze
|
17
17
|
|
18
|
-
|
19
|
-
@image_timestamper = ImageTimestamper
|
20
|
-
@video_timestamper = VideoTimestamper
|
18
|
+
attr_accessor :mime_recognizer, :handlers
|
21
19
|
|
22
|
-
|
23
|
-
|
20
|
+
singleton_class.attr_writer :instance
|
21
|
+
def self.instance
|
22
|
+
@instance ||= new
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
mime_type = mime_recognizer.recognize(input_path)
|
40
|
-
processor =
|
41
|
-
case mime_type.split('/').first
|
42
|
-
when 'image' then image_timestamper
|
43
|
-
when 'video' then video_timestamper
|
44
|
-
else raise "Unsupported MIME type: ##{mime_type}"
|
45
|
-
end
|
46
|
-
time = processor.creation_time(input_path) if time.nil?
|
47
|
-
raise ArgumentError unless time.is_a?(Time)
|
25
|
+
def initialize(
|
26
|
+
mime_recognizer: MimeRecognizers::Marcel.new,
|
27
|
+
handlers: [
|
28
|
+
Handlers::ImageMagick.new(
|
29
|
+
time_zone_lookuper: TimeZoneLookupers::Wheretz.new
|
30
|
+
),
|
31
|
+
Handlers::Ffmpeg.new(
|
32
|
+
time_zone_lookuper: TimeZoneLookupers::Wheretz.new
|
33
|
+
)
|
34
|
+
]
|
35
|
+
)
|
36
|
+
@mime_recognizer = mime_recognizer
|
37
|
+
@handlers = handlers
|
38
|
+
end
|
48
39
|
|
49
|
-
|
40
|
+
def add_timestamp(
|
41
|
+
input_path, output_path,
|
42
|
+
format: '%Y-%m-%d %H:%M:%S',
|
43
|
+
time: nil,
|
44
|
+
font_size: 32,
|
45
|
+
font_family: 'Sans',
|
46
|
+
font_color: 'white',
|
47
|
+
background_color: '#000000B3',
|
48
|
+
time_zone: nil,
|
49
|
+
coordinate_origin: 'top-left',
|
50
|
+
x: 32,
|
51
|
+
y: 32,
|
52
|
+
font_padding: 8
|
53
|
+
)
|
54
|
+
mime_type = mime_recognizer.recognize(input_path)
|
55
|
+
handler = handlers.find { |i| i.accept?(mime_type) }
|
56
|
+
raise "Unsupported MIME type: ##{mime_type}" if handler.nil?
|
50
57
|
|
51
|
-
|
52
|
-
|
53
|
-
end
|
58
|
+
time = handler.creation_time(input_path) if time.nil?
|
59
|
+
raise ArgumentError unless time.is_a?(Time)
|
54
60
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
background_color: background_color,
|
62
|
-
coordinate_origin: coordinate_origin,
|
63
|
-
x: x,
|
64
|
-
y: y,
|
65
|
-
font_padding: font_padding
|
61
|
+
time.localtime(TZInfo::Timezone.get(time_zone)) unless time_zone.nil?
|
62
|
+
|
63
|
+
unless COORDINATE_ORIGINS.include?(coordinate_origin)
|
64
|
+
raise(
|
65
|
+
ArgumentError,
|
66
|
+
"coordinate origin should be one of #{COORDINATE_ORIGINS.join(',')}"
|
66
67
|
)
|
67
68
|
end
|
69
|
+
|
70
|
+
handler.add_timestamp(
|
71
|
+
input_path, output_path, time,
|
72
|
+
format: format,
|
73
|
+
font_size: font_size,
|
74
|
+
font_family: font_family,
|
75
|
+
font_color: font_color,
|
76
|
+
background_color: background_color,
|
77
|
+
coordinate_origin: coordinate_origin,
|
78
|
+
x: x,
|
79
|
+
y: y,
|
80
|
+
font_padding: font_padding
|
81
|
+
)
|
68
82
|
end
|
69
83
|
end
|
@@ -6,9 +6,20 @@ require 'open3'
|
|
6
6
|
require 'English'
|
7
7
|
require 'tzinfo'
|
8
8
|
|
9
|
-
|
10
|
-
module
|
11
|
-
class
|
9
|
+
class TimestampMaker
|
10
|
+
module Handlers
|
11
|
+
class Ffmpeg
|
12
|
+
|
13
|
+
attr_accessor :time_zone_lookuper
|
14
|
+
|
15
|
+
def initialize(time_zone_lookuper:)
|
16
|
+
@time_zone_lookuper = time_zone_lookuper
|
17
|
+
end
|
18
|
+
|
19
|
+
def accept?(mime_type)
|
20
|
+
mime_type.start_with?('video/')
|
21
|
+
end
|
22
|
+
|
12
23
|
def add_timestamp(
|
13
24
|
input_path,
|
14
25
|
output_path,
|
@@ -45,13 +56,14 @@ module TimestampMaker
|
|
45
56
|
]
|
46
57
|
|
47
58
|
tz = tz_env_string(time)
|
48
|
-
raise "Command failed with exit #{$CHILD_STATUS.exitstatus}: #{command.first}" unless system({ 'TZ' => tz },
|
59
|
+
raise "Command failed with exit #{$CHILD_STATUS.exitstatus}: #{command.first}" unless system({ 'TZ' => tz },
|
60
|
+
*command)
|
49
61
|
end
|
50
62
|
|
51
63
|
def creation_time(input_path)
|
52
64
|
command = %W[
|
53
65
|
ffprobe -v warning -print_format json
|
54
|
-
-show_entries format_tags=creation_time,com.apple.quicktime.creationdate
|
66
|
+
-show_entries format_tags=creation_time,com.apple.quicktime.creationdate,location
|
55
67
|
#{input_path}
|
56
68
|
]
|
57
69
|
stdout_string, status = Open3.capture2(*command)
|
@@ -61,11 +73,29 @@ module TimestampMaker
|
|
61
73
|
iso8601_string = parsed['format']['tags']['com.apple.quicktime.creationdate'] || parsed['format']['tags']['creation_time']
|
62
74
|
raise 'Cannot find creation time' if iso8601_string.nil?
|
63
75
|
|
64
|
-
Time.iso8601(iso8601_string)
|
76
|
+
time = Time.iso8601(iso8601_string)
|
77
|
+
|
78
|
+
iso6709_string = parsed['format']['tags']['location']
|
79
|
+
if iso6709_string && (time_zone = retrieve_time_zone_from_iso6709(iso6709_string))
|
80
|
+
begin
|
81
|
+
return TZInfo::Timezone.get(time_zone).to_local(time)
|
82
|
+
rescue TZInfo::InvalidTimezoneIdentifier
|
83
|
+
warn "Can not find time zone: #{time_zone}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
time
|
65
88
|
end
|
66
89
|
|
67
90
|
private
|
68
91
|
|
92
|
+
def retrieve_time_zone_from_iso6709(string)
|
93
|
+
data = string.match(/([+-]\d*\.?\d*)([+-]\d*\.?\d*)/)
|
94
|
+
latitude = data[1].to_f
|
95
|
+
longitude = data[2].to_f
|
96
|
+
time_zone_lookuper.lookup(latitude: latitude, longitude: longitude)
|
97
|
+
end
|
98
|
+
|
69
99
|
def tz_env_string(time)
|
70
100
|
return time.zone.name if time.zone.is_a? TZInfo::Timezone
|
71
101
|
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'time'
|
5
|
+
require 'English'
|
6
|
+
require 'tzinfo'
|
7
|
+
|
8
|
+
class TimestampMaker
|
9
|
+
module Handlers
|
10
|
+
class ImageMagick
|
11
|
+
GRAVITY_MAP = {
|
12
|
+
'top-left' => 'NorthWest',
|
13
|
+
'top-right' => 'NorthEast',
|
14
|
+
'bottom-left' => 'SouthWest',
|
15
|
+
'bottom-right' => 'SouthEast'
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
attr_accessor :time_zone_lookuper
|
19
|
+
|
20
|
+
def initialize(time_zone_lookuper:)
|
21
|
+
@time_zone_lookuper = time_zone_lookuper
|
22
|
+
end
|
23
|
+
|
24
|
+
def accept?(mime_type)
|
25
|
+
mime_type.start_with?('image/')
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_timestamp(
|
29
|
+
input_path,
|
30
|
+
output_path,
|
31
|
+
time,
|
32
|
+
format:,
|
33
|
+
font_size:,
|
34
|
+
font_family:,
|
35
|
+
font_color:,
|
36
|
+
background_color:,
|
37
|
+
coordinate_origin:,
|
38
|
+
x:,
|
39
|
+
y:,
|
40
|
+
font_padding:
|
41
|
+
)
|
42
|
+
time_string = time.strftime(format)
|
43
|
+
command = %W[
|
44
|
+
convert #{input_path}
|
45
|
+
(
|
46
|
+
-background #{background_color}
|
47
|
+
-fill #{font_color}
|
48
|
+
-family #{font_family}
|
49
|
+
-pointsize #{font_size}
|
50
|
+
-gravity NorthWest
|
51
|
+
-splice #{font_padding}x#{font_padding}
|
52
|
+
-gravity SouthEast
|
53
|
+
-splice #{font_padding}x#{font_padding}
|
54
|
+
label:#{time_string}
|
55
|
+
)
|
56
|
+
-gravity #{GRAVITY_MAP[coordinate_origin]}
|
57
|
+
-geometry +#{x}+#{y}
|
58
|
+
-composite #{output_path}
|
59
|
+
]
|
60
|
+
raise "Command failed with exit #{$CHILD_STATUS.exitstatus}: #{command.first}" unless system(*command)
|
61
|
+
end
|
62
|
+
|
63
|
+
def creation_time(input_path)
|
64
|
+
command = %W[
|
65
|
+
identify -format %[exif:DateTime*]%[exif:OffsetTime*]%[exif:GPSLatitude*]%[exif:GPSLongitude*] #{input_path}
|
66
|
+
]
|
67
|
+
|
68
|
+
stdout_string, status = Open3.capture2(*command)
|
69
|
+
raise unless status.success?
|
70
|
+
|
71
|
+
parsed = Hash[stdout_string.split("\n").map! { |i| i[5..-1].split('=') }]
|
72
|
+
|
73
|
+
time_string = parsed['DateTimeOriginal'] || parsed['DateTimeDigitized'] || parsed['DateTime']
|
74
|
+
raise 'Cannot find creation time' if time_string.nil?
|
75
|
+
|
76
|
+
time_arguments = time_string.split(/[: ]/).map(&:to_i)
|
77
|
+
|
78
|
+
if (time_zone = retrieve_time_zone_by_coordinate(parsed))
|
79
|
+
begin
|
80
|
+
return TZInfo::Timezone.get(time_zone).local_time(*time_arguments)
|
81
|
+
rescue TZInfo::InvalidTimezoneIdentifier
|
82
|
+
warn "Can not find time zone: #{time_zone}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
time_offset_string = parsed['OffsetTimeOriginal'] || parsed['OffsetTimeDigitized'] || parsed['OffsetTime']
|
87
|
+
raise 'Can not find time offset' if time_offset_string.nil?
|
88
|
+
|
89
|
+
Time.new(*time_arguments, time_offset_string)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def parse_coordinate_number(string)
|
95
|
+
degree, minute, second = string.split(', ').map! { |i| Rational(i) }
|
96
|
+
(degree + minute / 60 + second / 3600).to_f
|
97
|
+
end
|
98
|
+
|
99
|
+
def retrieve_time_zone_by_coordinate(exif)
|
100
|
+
unless exif['GPSLatitude'] && exif['GPSLatitudeRef'] && exif['GPSLongitude'] && exif['GPSLongitudeRef']
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
latitude = parse_coordinate_number(exif['GPSLatitude'])
|
105
|
+
latitude = -latitude if exif['GPSLatitudeRef'] == 'S'
|
106
|
+
longitude = parse_coordinate_number(exif['GPSLongitude'])
|
107
|
+
longitude = -longitude if exif['GPSLongitudeRef'] == 'W'
|
108
|
+
|
109
|
+
time_zone_lookuper.lookup(latitude: latitude, longitude: longitude)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
class TimestampMaker
|
8
|
+
module TimeZoneLookupers
|
9
|
+
ENDPOINT = URI.parse('http://api.geonames.org/timezoneJSON')
|
10
|
+
|
11
|
+
class GeoName
|
12
|
+
attr_accessor :username
|
13
|
+
|
14
|
+
def initialize(username:)
|
15
|
+
@username = username
|
16
|
+
end
|
17
|
+
|
18
|
+
def lookup(latitude:, longitude:)
|
19
|
+
query = URI.encode_www_form(
|
20
|
+
[['lat', latitude], ['lng', longitude], ['username', username]]
|
21
|
+
)
|
22
|
+
response = Net::HTTP.get_response(URI.parse("#{ENDPOINT}?#{query}"))
|
23
|
+
raise "Got HTTP status code: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
24
|
+
|
25
|
+
parsed = JSON.parse(response.body)
|
26
|
+
parsed['timezoneId']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wheretz'
|
4
|
+
require 'json' # wheretz forgets to require json
|
5
|
+
|
6
|
+
class TimestampMaker
|
7
|
+
module TimeZoneLookupers
|
8
|
+
class Wheretz
|
9
|
+
def lookup(latitude:, longitude:)
|
10
|
+
case result = WhereTZ.lookup(latitude, longitude)
|
11
|
+
when String then result
|
12
|
+
when Array then result.first
|
13
|
+
else raise 'Something went wrong with WhereTZ'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timestamp_maker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weihang Jian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: marcel
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: wheretz
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.0.6
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.6
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: minitest
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -76,9 +90,12 @@ extra_rdoc_files: []
|
|
76
90
|
files:
|
77
91
|
- bin/timestamp
|
78
92
|
- lib/timestamp_maker.rb
|
79
|
-
- lib/timestamp_maker/
|
80
|
-
- lib/timestamp_maker/
|
81
|
-
- lib/timestamp_maker/
|
93
|
+
- lib/timestamp_maker/handlers/ffmpeg.rb
|
94
|
+
- lib/timestamp_maker/handlers/image_magick.rb
|
95
|
+
- lib/timestamp_maker/mime_recognizers/marcel.rb
|
96
|
+
- lib/timestamp_maker/time_zone_lookupers/geo_name.rb
|
97
|
+
- lib/timestamp_maker/time_zone_lookupers/mock.rb
|
98
|
+
- lib/timestamp_maker/time_zone_lookupers/wheretz.rb
|
82
99
|
homepage: https://github.com/tonytonyjan/timestamp_maker
|
83
100
|
licenses:
|
84
101
|
- MIT
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'open3'
|
4
|
-
require 'time'
|
5
|
-
require 'English'
|
6
|
-
|
7
|
-
module TimestampMaker
|
8
|
-
module ImageTimestamper
|
9
|
-
GRAVITY_MAP = {
|
10
|
-
'top-left' => 'NorthWest',
|
11
|
-
'top-right' => 'NorthEast',
|
12
|
-
'bottom-left' => 'SouthWest',
|
13
|
-
'bottom-right' => 'SouthEast'
|
14
|
-
}.freeze
|
15
|
-
|
16
|
-
def self.add_timestamp(
|
17
|
-
input_path,
|
18
|
-
output_path,
|
19
|
-
time,
|
20
|
-
format:,
|
21
|
-
font_size:,
|
22
|
-
font_family:,
|
23
|
-
font_color:,
|
24
|
-
background_color:,
|
25
|
-
coordinate_origin:,
|
26
|
-
x:,
|
27
|
-
y:,
|
28
|
-
font_padding:
|
29
|
-
)
|
30
|
-
time_string = time.strftime(format)
|
31
|
-
command = %W[
|
32
|
-
convert #{input_path}
|
33
|
-
(
|
34
|
-
-background #{background_color}
|
35
|
-
-fill #{font_color}
|
36
|
-
-family #{font_family}
|
37
|
-
-pointsize #{font_size}
|
38
|
-
-gravity NorthWest
|
39
|
-
-splice #{font_padding}x#{font_padding}
|
40
|
-
-gravity SouthEast
|
41
|
-
-splice #{font_padding}x#{font_padding}
|
42
|
-
label:#{time_string}
|
43
|
-
)
|
44
|
-
-gravity #{GRAVITY_MAP[coordinate_origin]}
|
45
|
-
-geometry +#{x}+#{y}
|
46
|
-
-composite #{output_path}
|
47
|
-
]
|
48
|
-
raise "Command failed with exit #{$CHILD_STATUS.exitstatus}: #{command.first}" unless system(*command)
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.creation_time(input_path)
|
52
|
-
command = %W[
|
53
|
-
identify -format %[exif:DateTime*]%[exif:OffsetTime*] #{input_path}
|
54
|
-
]
|
55
|
-
|
56
|
-
stdout_string, status = Open3.capture2(*command)
|
57
|
-
raise unless status.success?
|
58
|
-
|
59
|
-
parsed = Hash[stdout_string.split("\n").map! { |i| i[5..-1].split('=') }]
|
60
|
-
|
61
|
-
time_string = parsed['DateTimeOriginal'] || parsed['DateTimeDigitized'] || parsed['DateTime']
|
62
|
-
raise 'Cannot find creation time' if time_string.nil?
|
63
|
-
|
64
|
-
time_offset_string = parsed['OffsetTimeOriginal'] || parsed['OffsetTimeDigitized'] || parsed['OffsetTime'] || 'Z'
|
65
|
-
|
66
|
-
Time.strptime("#{time_string} #{time_offset_string}", '%Y:%m:%d %H:%M:%S %Z')
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|