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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01abd157048ff7bdbf8fee5cd139f8e238c48dc82850202aa4f9c7748d7bf8da
4
- data.tar.gz: 7c94cd8a48aab112d17a48f97112cb5594fd517415518202e0dc6074e1b707a0
3
+ metadata.gz: e7f96f255213ceac42648b8ad1317b745d64062f6ad116fd8365d64408077101
4
+ data.tar.gz: 6c4c191ee701c7ffbf539b9665df9b3064608f50cf7eee06cffede24f46aa7a4
5
5
  SHA512:
6
- metadata.gz: 48cf23b7cb6c1526825316f148875a7554371e7e3c2ca5716a3e33b7585277789a7d698d4b0811eb60c59d445af742e16b43a3c454d68365f372f428db245f0a
7
- data.tar.gz: 9b1d3b546e5a0ac019efb0f46fa2763b40614e0efb406b38e5430d96fbb8540d99b92d7f34fcafe86f19e34fd91b70319ea7240ad9b1c9ee88d1f55c1c021c8b
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
- option_parser = OptionParser.new do |opts|
8
- opts.banner = "Usage: #{__FILE__} INPUT_FILE_PATH OUTPUT_FILE_PATH"
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
- TimestampMaker.add_timestamp(input, output)
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
+ )
@@ -1,28 +1,83 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'marcel'
4
3
  require 'pathname'
5
- require 'timestamp_maker/video_timestamper'
6
- require 'timestamp_maker/mime_recognizer'
7
- require 'timestamp_maker/image_timestamper'
8
-
9
- module TimestampMaker
10
- @mime_recognizer = MimeRecognizer
11
- @image_timestamper = ImageTimestamper
12
- @video_timestamper = VideoTimestamper
13
-
14
- class << self
15
- attr_reader :mime_recognizer, :image_timestamper, :video_timestamper
16
-
17
- def add_timestamp(input_path, output_path)
18
- mime_type = mime_recognizer.recognize(input_path)
19
- processor =
20
- case mime_type.split('/').first
21
- when 'image' then image_timestamper
22
- when 'video' then video_timestamper
23
- else raise "Unsupported MIME type: ##{mime_type}"
24
- end
25
- processor.add_timestamp(input_path, output_path)
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'marcel'
4
+
5
+ class TimestampMaker
6
+ module MimeRecognizers
7
+ class Marcel
8
+ def recognize(path)
9
+ ::Marcel::MimeType.for(Pathname.new(path))
10
+ end
11
+ end
12
+ end
13
+ 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TimestampMaker
4
+ module TimeZoneLookupers
5
+ class Mock
6
+ def initialize(time_zone)
7
+ @time_zone = time_zone
8
+ end
9
+
10
+ def lookup(*)
11
+ @time_zone
12
+ end
13
+ end
14
+ end
15
+ 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.0.2
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-18 00:00:00.000000000 Z
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 to images
56
- and videos.
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/image_timestamper.rb
66
- - lib/timestamp_maker/mime_recognizer.rb
67
- - lib/timestamp_maker/video_timestamper.rb
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: '0'
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 to images and
92
- videos.
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,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TimestampMaker
4
- module MimeRecognizer
5
- def self.recognize(path)
6
- Marcel::MimeType.for(Pathname.new(path))
7
- end
8
- end
9
- 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