timestamp_maker 1.0.2 → 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 +86 -3
- data/lib/timestamp_maker.rb +77 -22
- data/lib/timestamp_maker/handlers/ffmpeg.rb +133 -0
- 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 +44 -10
- data/lib/timestamp_maker/image_timestamper.rb +0 -46
- data/lib/timestamp_maker/mime_recognizer.rb +0 -9
- data/lib/timestamp_maker/video_timestamper.rb +0 -47
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
@@ -3,9 +3,77 @@
|
|
3
3
|
|
4
4
|
require 'timestamp_maker'
|
5
5
|
require 'optparse'
|
6
|
+
require 'optparse/time'
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
options = {
|
9
|
+
format: '%Y-%m-%d %H:%M:%S',
|
10
|
+
time: nil,
|
11
|
+
time_zone: nil,
|
12
|
+
font_size: 32,
|
13
|
+
font_family: 'Sans',
|
14
|
+
font_color: 'white',
|
15
|
+
background_color: '#000000B3',
|
16
|
+
coordinate_origin: 'top-left',
|
17
|
+
x: 32,
|
18
|
+
y: 32,
|
19
|
+
font_padding: 8,
|
20
|
+
require: []
|
21
|
+
}
|
22
|
+
|
23
|
+
option_parser = OptionParser.new do |parser|
|
24
|
+
parser.banner = "Usage: #{__FILE__} [options] INPUT_FILE_PATH OUTPUT_FILE_PATH"
|
25
|
+
|
26
|
+
parser.on('-f FORMAT', '--format FORMAT', 'strftime() format string, defaults to "%Y-%m-%d %H:%M:%S".') do |value|
|
27
|
+
options[:format] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
parser.on(
|
31
|
+
'-t TIME',
|
32
|
+
'--time TIME', Time,
|
33
|
+
'ISO 8601 or RFC 2616 string. By default, retrieves from file\'s metadata'
|
34
|
+
) do |value|
|
35
|
+
options[:time] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
parser.on('--font-size NUMBER', Integer, 'Defaults to 32.') do |value|
|
39
|
+
options[:font_size] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
parser.on('--font-family FONT_FAMILY', 'Defaults to "Sans"') do |value|
|
43
|
+
options[:font_family] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
parser.on('--font-color COLOR', '"#RRGGBB[AA]" or color name, Defaults to "white"') do |value|
|
47
|
+
options[:font_color] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
parser.on('--background-color COLOR', '"#RRGGBB[AA]" or color name, Defaults to "#000000B3"') do |value|
|
51
|
+
options[:background_color] = value
|
52
|
+
end
|
53
|
+
|
54
|
+
parser.on('--time-zone TIME_ZONE', 'IANA time zone. By default, retrieves from media file\'s metadata') do |value|
|
55
|
+
options[:time_zone] = value
|
56
|
+
end
|
57
|
+
|
58
|
+
parser.on('--coordinate-origin ORIGIN', 'Should be "[top|bottom]-[left|right]". Defaults to "top-left"') do |value|
|
59
|
+
options[:coordinate_origin] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
parser.on('-x X', Integer, 'coordinate x. Defaults to 32.') do |value|
|
63
|
+
options[:x] = value
|
64
|
+
end
|
65
|
+
|
66
|
+
parser.on('-y Y', Integer, 'coordinate y, Defaults to 32.') do |value|
|
67
|
+
options[:y] = value
|
68
|
+
end
|
69
|
+
|
70
|
+
parser.on('--font-padding NUM', Integer, 'Defaults to 8.') do |value|
|
71
|
+
options[:font_padding] = value
|
72
|
+
end
|
73
|
+
|
74
|
+
parser.on('-r LIB', '--require LIB', Array, 'Comma-separated Ruby libs') do |value|
|
75
|
+
options[:require] = value
|
76
|
+
end
|
9
77
|
end
|
10
78
|
option_parser.parse!
|
11
79
|
|
@@ -17,4 +85,19 @@ end
|
|
17
85
|
input = ARGV.shift
|
18
86
|
output = ARGV.shift
|
19
87
|
|
20
|
-
|
88
|
+
options[:require].each { |i| require i }
|
89
|
+
TimestampMaker.instance.add_timestamp(
|
90
|
+
input,
|
91
|
+
output,
|
92
|
+
format: options[:format],
|
93
|
+
time: options[:time],
|
94
|
+
time_zone: options[:time_zone],
|
95
|
+
font_size: options[:font_size],
|
96
|
+
font_family: options[:font_family],
|
97
|
+
font_color: options[:font_color],
|
98
|
+
background_color: options[:background_color],
|
99
|
+
coordinate_origin: options[:coordinate_origin],
|
100
|
+
x: options[:x],
|
101
|
+
y: options[:y],
|
102
|
+
font_padding: options[:font_padding]
|
103
|
+
)
|
data/lib/timestamp_maker.rb
CHANGED
@@ -1,28 +1,83 @@
|
|
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/
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
+
require 'tzinfo'
|
9
|
+
|
10
|
+
class TimestampMaker
|
11
|
+
COORDINATE_ORIGINS = %w[
|
12
|
+
top-left
|
13
|
+
top-right
|
14
|
+
bottom-left
|
15
|
+
bottom-right
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
attr_accessor :mime_recognizer, :handlers
|
19
|
+
|
20
|
+
singleton_class.attr_writer :instance
|
21
|
+
def self.instance
|
22
|
+
@instance ||= new
|
23
|
+
end
|
24
|
+
|
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
|
39
|
+
|
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?
|
57
|
+
|
58
|
+
time = handler.creation_time(input_path) if time.nil?
|
59
|
+
raise ArgumentError unless time.is_a?(Time)
|
60
|
+
|
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(',')}"
|
67
|
+
)
|
26
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
|
+
)
|
27
82
|
end
|
28
83
|
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'time'
|
5
|
+
require 'open3'
|
6
|
+
require 'English'
|
7
|
+
require 'tzinfo'
|
8
|
+
|
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
|
+
|
23
|
+
def add_timestamp(
|
24
|
+
input_path,
|
25
|
+
output_path,
|
26
|
+
time,
|
27
|
+
format:,
|
28
|
+
font_size:,
|
29
|
+
font_family:,
|
30
|
+
font_color:,
|
31
|
+
background_color:,
|
32
|
+
coordinate_origin:,
|
33
|
+
x:,
|
34
|
+
y:,
|
35
|
+
font_padding:
|
36
|
+
)
|
37
|
+
creation_timestamp = time.to_i
|
38
|
+
text = "%{pts:localtime:#{creation_timestamp}:#{escape_text_expansion_argument(format)}}"
|
39
|
+
drawtext = +"#{coord_map(coordinate_origin, x, y)}:" << %W[
|
40
|
+
font=#{escape_filter_description_value(font_family)}
|
41
|
+
fontsize=#{font_size}
|
42
|
+
fontcolor=#{font_color}
|
43
|
+
box=1
|
44
|
+
boxcolor=#{background_color}
|
45
|
+
boxborderw=#{font_padding}
|
46
|
+
text=#{escape_filter_description_value(text)}
|
47
|
+
].join(':')
|
48
|
+
|
49
|
+
command = %W[
|
50
|
+
ffmpeg -y
|
51
|
+
-v warning
|
52
|
+
-i #{input_path}
|
53
|
+
-map_metadata 0
|
54
|
+
-vf drawtext=#{escape_filter_description(drawtext)}
|
55
|
+
#{output_path}
|
56
|
+
]
|
57
|
+
|
58
|
+
tz = tz_env_string(time)
|
59
|
+
raise "Command failed with exit #{$CHILD_STATUS.exitstatus}: #{command.first}" unless system({ 'TZ' => tz },
|
60
|
+
*command)
|
61
|
+
end
|
62
|
+
|
63
|
+
def creation_time(input_path)
|
64
|
+
command = %W[
|
65
|
+
ffprobe -v warning -print_format json
|
66
|
+
-show_entries format_tags=creation_time,com.apple.quicktime.creationdate,location
|
67
|
+
#{input_path}
|
68
|
+
]
|
69
|
+
stdout_string, status = Open3.capture2(*command)
|
70
|
+
raise unless status.success?
|
71
|
+
|
72
|
+
parsed = JSON.parse(stdout_string)
|
73
|
+
iso8601_string = parsed['format']['tags']['com.apple.quicktime.creationdate'] || parsed['format']['tags']['creation_time']
|
74
|
+
raise 'Cannot find creation time' if iso8601_string.nil?
|
75
|
+
|
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
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
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
|
+
|
99
|
+
def tz_env_string(time)
|
100
|
+
return time.zone.name if time.zone.is_a? TZInfo::Timezone
|
101
|
+
|
102
|
+
TZInfo::Timezone.get(time.zone).name
|
103
|
+
rescue TZInfo::InvalidTimezoneIdentifier
|
104
|
+
offset = time.utc_offset
|
105
|
+
tz_string = "#{offset / 3600}:#{offset % 3600 / 16}:#{offset % 60}"
|
106
|
+
return "+#{tz_string}" if offset.negative?
|
107
|
+
|
108
|
+
"-#{tz_string}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def coord_map(coordinate_origin, x, y)
|
112
|
+
case coordinate_origin
|
113
|
+
when 'top-left' then "x=#{x}:y=#{y}"
|
114
|
+
when 'top-right' then "x=w-tw-#{x}:y=#{y}"
|
115
|
+
when 'bottom-left' then "x=#{x}:y=h-th-#{y}"
|
116
|
+
when 'bottom-right' then "x=w-tw-#{x}:y=h-th-#{y}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def escape_text_expansion_argument(string)
|
121
|
+
string.gsub(/[:{}]/, '\\\\\\&')
|
122
|
+
end
|
123
|
+
|
124
|
+
def escape_filter_description_value(string)
|
125
|
+
string.gsub(/[:\\']/, '\\\\\\&')
|
126
|
+
end
|
127
|
+
|
128
|
+
def escape_filter_description(string)
|
129
|
+
string.gsub(/[\\'\[\],;]/, '\\\\\\&')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -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
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tzinfo
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
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
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: minitest
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,8 +80,8 @@ dependencies:
|
|
52
80
|
- - "~>"
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '13.0'
|
55
|
-
description: timestamp_maker is a command-line tool that adds timestamp
|
56
|
-
|
83
|
+
description: timestamp_maker is a command-line tool that adds timestamp on assets/videos
|
84
|
+
based on their creation time.
|
57
85
|
email: tonytonyjan@gmail.com
|
58
86
|
executables:
|
59
87
|
- timestamp
|
@@ -62,9 +90,12 @@ extra_rdoc_files: []
|
|
62
90
|
files:
|
63
91
|
- bin/timestamp
|
64
92
|
- lib/timestamp_maker.rb
|
65
|
-
- lib/timestamp_maker/
|
66
|
-
- lib/timestamp_maker/
|
67
|
-
- 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
|
68
99
|
homepage: https://github.com/tonytonyjan/timestamp_maker
|
69
100
|
licenses:
|
70
101
|
- MIT
|
@@ -78,7 +109,10 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
109
|
requirements:
|
79
110
|
- - ">="
|
80
111
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
112
|
+
version: '2.5'
|
113
|
+
- - "<"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '4'
|
82
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
117
|
requirements:
|
84
118
|
- - ">="
|
@@ -88,6 +122,6 @@ requirements: []
|
|
88
122
|
rubygems_version: 3.2.22
|
89
123
|
signing_key:
|
90
124
|
specification_version: 4
|
91
|
-
summary: timestamp_maker is a command-line tool that adds timestamp
|
92
|
-
|
125
|
+
summary: timestamp_maker is a command-line tool that adds timestamp on assets/videos
|
126
|
+
based on their creation time.
|
93
127
|
test_files: []
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'open3'
|
4
|
-
require 'time'
|
5
|
-
|
6
|
-
module TimestampMaker
|
7
|
-
module ImageTimestamper
|
8
|
-
def self.add_timestamp(input_path, output_path)
|
9
|
-
time_string = creation_time(input_path).iso8601
|
10
|
-
command = %W[
|
11
|
-
magick convert #{input_path}
|
12
|
-
(
|
13
|
-
-background rgba(0,0,0,0.7)
|
14
|
-
-fill white
|
15
|
-
-font Roboto
|
16
|
-
-pointsize 32
|
17
|
-
-gravity NorthWest
|
18
|
-
-splice 1x1
|
19
|
-
-gravity SouthEast
|
20
|
-
-splice 1x1
|
21
|
-
label:#{time_string}
|
22
|
-
)
|
23
|
-
-gravity NorthWest -geometry +32+32 -composite #{output_path}
|
24
|
-
]
|
25
|
-
system(*command, exception: true)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.creation_time(input_path)
|
29
|
-
command = %W[
|
30
|
-
magick identify -format %[exif:DateTime*]%[exif:OffsetTime*] #{input_path}
|
31
|
-
]
|
32
|
-
|
33
|
-
stdout_string, status = Open3.capture2(*command)
|
34
|
-
raise unless status.success?
|
35
|
-
|
36
|
-
parsed = Hash[stdout_string.split("\n").map!{ _1[5..].split('=') }]
|
37
|
-
|
38
|
-
time_string = parsed['DateTimeOriginal'] || parsed['DateTimeDigitized'] || parsed['DateTime']
|
39
|
-
raise 'Cannot find creation time' if time_string.nil?
|
40
|
-
|
41
|
-
time_offset_string = parsed['OffsetTimeOriginal'] || parsed['OffsetTimeDigitized'] || parsed['OffsetTime'] || 'Z'
|
42
|
-
|
43
|
-
Time.strptime("#{time_string} #{time_offset_string}", '%Y:%m:%d %H:%M:%S %Z')
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'time'
|
5
|
-
require 'open3'
|
6
|
-
require 'shellwords'
|
7
|
-
|
8
|
-
module TimestampMaker
|
9
|
-
module VideoTimestamper
|
10
|
-
def self.add_timestamp(input_path, output_path)
|
11
|
-
creation_timestamp = creation_time(input_path).to_i
|
12
|
-
drawtext = %W[
|
13
|
-
x=32
|
14
|
-
y=32
|
15
|
-
font=Roboto
|
16
|
-
fontsize=32
|
17
|
-
fontcolor=white
|
18
|
-
box=1
|
19
|
-
boxcolor=black@0.7
|
20
|
-
boxborderw=8
|
21
|
-
text=%{pts\\\\:localtime\\\\:#{creation_timestamp}}
|
22
|
-
].join(':')
|
23
|
-
|
24
|
-
command = %W[
|
25
|
-
ffmpeg -y -v warning -i #{input_path} -map_metadata 0
|
26
|
-
-vf drawtext=#{drawtext}
|
27
|
-
#{output_path}
|
28
|
-
]
|
29
|
-
|
30
|
-
system(*command, exception: true)
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.creation_time(input_path)
|
34
|
-
command = %W[
|
35
|
-
ffprobe -v warning -print_format json -show_entries format_tags=creation_time #{Shellwords.escape(input_path)}
|
36
|
-
]
|
37
|
-
stdout_string, status = Open3.capture2(*command)
|
38
|
-
raise unless status.success?
|
39
|
-
|
40
|
-
parsed = JSON.parse(stdout_string)
|
41
|
-
iso8601_string = parsed['format']['tags']['creation_time']
|
42
|
-
raise 'Cannot find creation time' if iso8601_string.nil?
|
43
|
-
|
44
|
-
Time.iso8601(iso8601_string)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|