timestamp_maker 1.0.2 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01abd157048ff7bdbf8fee5cd139f8e238c48dc82850202aa4f9c7748d7bf8da
4
- data.tar.gz: 7c94cd8a48aab112d17a48f97112cb5594fd517415518202e0dc6074e1b707a0
3
+ metadata.gz: ed37875d238df5bcc9566649a2767aff49522b6baab2f2c5bb07f4a788e38f5b
4
+ data.tar.gz: 14647536fd8c17f34a02e9b7d4542e8c154561fcea3c569a18613fa174338ecf
5
5
  SHA512:
6
- metadata.gz: 48cf23b7cb6c1526825316f148875a7554371e7e3c2ca5716a3e33b7585277789a7d698d4b0811eb60c59d445af742e16b43a3c454d68365f372f428db245f0a
7
- data.tar.gz: 9b1d3b546e5a0ac019efb0f46fa2763b40614e0efb406b38e5430d96fbb8540d99b92d7f34fcafe86f19e34fd91b70319ea7240ad9b1c9ee88d1f55c1c021c8b
6
+ metadata.gz: e8289f87c08d1f3b81f7f8835cf02a68e197cba768425f962a3d16e8b2fcb8567cb6cb675aa1e4e5ed762dfe077509cb74fb18b3394bf32952d2ed7e1c0e5c32
7
+ data.tar.gz: 063ccdfd4dbe3a585238b44e20a6728aff9faef849499efeb303b47154488f347644e934bb78eb1b177fd53ea43372c27bc6780ab29de4fe4e34dff0b08aceab
data/bin/timestamp CHANGED
@@ -3,9 +3,72 @@
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
+ }
21
+
22
+ option_parser = OptionParser.new do |parser|
23
+ parser.banner = "Usage: #{__FILE__} [options] INPUT_FILE_PATH OUTPUT_FILE_PATH"
24
+
25
+ parser.on('-f FORMAT', '--format FORMAT', 'strftime() format string, defaults to "%Y-%m-%d %H:%M:%S".') do |value|
26
+ options[:format] = value
27
+ end
28
+
29
+ parser.on(
30
+ '-t TIME',
31
+ '--time TIME', Time,
32
+ 'ISO 8601 or RFC 2616 string. By default, retrieves from file\'s metadata'
33
+ ) do |value|
34
+ options[:time] = value
35
+ end
36
+
37
+ parser.on('--font-size NUMBER', Integer, 'Defaults to 32.') do |value|
38
+ options[:font_size] = value
39
+ end
40
+
41
+ parser.on('--font-family FONT_FAMILY', 'Defaults to "Sans"') do |value|
42
+ options[:font_family] = value
43
+ end
44
+
45
+ parser.on('--font-color COLOR', '"#RRGGBB[AA]" or color name, Defaults to "white"') do |value|
46
+ options[:font_color] = value
47
+ end
48
+
49
+ parser.on('--background-color COLOR', '"#RRGGBB[AA]" or color name, Defaults to "#000000B3"') do |value|
50
+ options[:background_color] = value
51
+ end
52
+
53
+ parser.on('--time-zone TIME_ZONE', 'IANA time zone. By default, retrieves from media file\'s metadata') do |value|
54
+ options[:time_zone] = value
55
+ end
56
+
57
+ parser.on('--coordinate-origin ORIGIN', 'Should be "[top|bottom]-[left|right]". Defaults to "top-left"') do |value|
58
+ options[:coordinate_origin] = value
59
+ end
60
+
61
+ parser.on('-x X', Integer, 'coordinate x. Defaults to 32.') do |value|
62
+ options[:x] = value
63
+ end
64
+
65
+ parser.on('-y Y', Integer, 'coordinate y, Defaults to 32.') do |value|
66
+ options[:y] = value
67
+ end
68
+
69
+ parser.on('--font-padding NUM', Integer, 'Defaults to 8.') do |value|
70
+ options[:font_padding] = value
71
+ end
9
72
  end
10
73
  option_parser.parse!
11
74
 
@@ -17,4 +80,18 @@ end
17
80
  input = ARGV.shift
18
81
  output = ARGV.shift
19
82
 
20
- TimestampMaker.add_timestamp(input, output)
83
+ TimestampMaker.add_timestamp(
84
+ input,
85
+ output,
86
+ format: options[:format],
87
+ time: options[:time],
88
+ time_zone: options[:time_zone],
89
+ font_size: options[:font_size],
90
+ font_family: options[:font_family],
91
+ font_color: options[:font_color],
92
+ background_color: options[:background_color],
93
+ coordinate_origin: options[:coordinate_origin],
94
+ x: options[:x],
95
+ y: options[:y],
96
+ font_padding: options[:font_padding]
97
+ )
@@ -5,8 +5,16 @@ require 'pathname'
5
5
  require 'timestamp_maker/video_timestamper'
6
6
  require 'timestamp_maker/mime_recognizer'
7
7
  require 'timestamp_maker/image_timestamper'
8
+ require 'tzinfo'
8
9
 
9
10
  module TimestampMaker
11
+ COORDINATE_ORIGINS = %w[
12
+ top-left
13
+ top-right
14
+ bottom-left
15
+ bottom-right
16
+ ].freeze
17
+
10
18
  @mime_recognizer = MimeRecognizer
11
19
  @image_timestamper = ImageTimestamper
12
20
  @video_timestamper = VideoTimestamper
@@ -14,7 +22,20 @@ module TimestampMaker
14
22
  class << self
15
23
  attr_reader :mime_recognizer, :image_timestamper, :video_timestamper
16
24
 
17
- def add_timestamp(input_path, output_path)
25
+ def add_timestamp(
26
+ input_path, output_path,
27
+ format: '%Y-%m-%d %H:%M:%S',
28
+ time: nil,
29
+ font_size: 32,
30
+ font_family: 'Sans',
31
+ font_color: 'white',
32
+ background_color: '#000000B3',
33
+ time_zone: nil,
34
+ coordinate_origin: 'top-left',
35
+ x: 32,
36
+ y: 32,
37
+ font_padding: 8
38
+ )
18
39
  mime_type = mime_recognizer.recognize(input_path)
19
40
  processor =
20
41
  case mime_type.split('/').first
@@ -22,7 +43,27 @@ module TimestampMaker
22
43
  when 'video' then video_timestamper
23
44
  else raise "Unsupported MIME type: ##{mime_type}"
24
45
  end
25
- processor.add_timestamp(input_path, output_path)
46
+ time = processor.creation_time(input_path) if time.nil?
47
+ raise ArgumentError unless time.is_a?(Time)
48
+
49
+ time.localtime(TZInfo::Timezone.get(time_zone)) unless time_zone.nil?
50
+
51
+ unless COORDINATE_ORIGINS.include?(coordinate_origin)
52
+ raise ArgumentError, "coordinate origin should be one of #{COORDINATE_ORIGINS.join(',')}"
53
+ end
54
+
55
+ processor.add_timestamp(
56
+ input_path, output_path, time,
57
+ format: format,
58
+ font_size: font_size,
59
+ font_family: font_family,
60
+ font_color: font_color,
61
+ background_color: background_color,
62
+ coordinate_origin: coordinate_origin,
63
+ x: x,
64
+ y: y,
65
+ font_padding: font_padding
66
+ )
26
67
  end
27
68
  end
28
69
  end
@@ -5,22 +5,44 @@ require 'time'
5
5
 
6
6
  module TimestampMaker
7
7
  module ImageTimestamper
8
- def self.add_timestamp(input_path, output_path)
9
- time_string = creation_time(input_path).iso8601
8
+ GRAVITY_MAP = {
9
+ 'top-left' => 'NorthWest',
10
+ 'top-right' => 'NorthEast',
11
+ 'bottom-left' => 'SouthWest',
12
+ 'bottom-right' => 'SouthEast'
13
+ }.freeze
14
+
15
+ def self.add_timestamp(
16
+ input_path,
17
+ output_path,
18
+ time,
19
+ format:,
20
+ font_size:,
21
+ font_family:,
22
+ font_color:,
23
+ background_color:,
24
+ coordinate_origin:,
25
+ x:,
26
+ y:,
27
+ font_padding:
28
+ )
29
+ time_string = time.strftime(format)
10
30
  command = %W[
11
31
  magick convert #{input_path}
12
32
  (
13
- -background rgba(0,0,0,0.7)
14
- -fill white
15
- -font Roboto
16
- -pointsize 32
33
+ -background #{background_color}
34
+ -fill #{font_color}
35
+ -family #{font_family}
36
+ -pointsize #{font_size}
17
37
  -gravity NorthWest
18
- -splice 1x1
38
+ -splice #{font_padding}x#{font_padding}
19
39
  -gravity SouthEast
20
- -splice 1x1
40
+ -splice #{font_padding}x#{font_padding}
21
41
  label:#{time_string}
22
42
  )
23
- -gravity NorthWest -geometry +32+32 -composite #{output_path}
43
+ -gravity #{GRAVITY_MAP[coordinate_origin]}
44
+ -geometry +#{x}+#{y}
45
+ -composite #{output_path}
24
46
  ]
25
47
  system(*command, exception: true)
26
48
  end
@@ -3,45 +3,105 @@
3
3
  require 'json'
4
4
  require 'time'
5
5
  require 'open3'
6
- require 'shellwords'
7
6
 
8
7
  module TimestampMaker
9
8
  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
9
+ class << self
10
+ def add_timestamp(
11
+ input_path,
12
+ output_path,
13
+ time,
14
+ format:,
15
+ font_size:,
16
+ font_family:,
17
+ font_color:,
18
+ background_color:,
19
+ coordinate_origin:,
20
+ x:,
21
+ y:,
22
+ font_padding:
23
+ )
24
+ creation_timestamp = time.to_i
25
+ text = "%{pts:localtime:#{creation_timestamp}:#{escape_text_expansion_argument(format)}}"
26
+ drawtext = "#{coord_map(coordinate_origin, x, y)}:" << %W[
27
+ font=#{escape_filter_description_value(font_family)}
28
+ fontsize=#{font_size}
29
+ fontcolor=#{font_color}
30
+ box=1
31
+ boxcolor=#{background_color}
32
+ boxborderw=#{font_padding}
33
+ text=#{escape_filter_description_value(text)}
34
+ ].join(':')
35
+
36
+ command = %W[
37
+ ffmpeg -y
38
+ -v warning
39
+ -i #{input_path}
40
+ -map_metadata 0
41
+ -vf drawtext=#{escape_filter_description(drawtext)}
42
+ #{output_path}
43
+ ]
44
+
45
+ tz =
46
+ case time.zone
47
+ when TZInfo::Timezone then time.zone.name
48
+ when String
49
+ begin
50
+ TZInfo::Timezone.get(time.zone).name
51
+ rescue TZInfo::InvalidTimezoneIdentifier
52
+ time.strftime('%::z').then do |string|
53
+ sign =
54
+ case string[0]
55
+ when '+' then '-'
56
+ when '-' then '+'
57
+ else raise "Cannot parse time zone: #{string}"
58
+ end
59
+ "#{sign}#{string[1..]}"
60
+ end
61
+ end
62
+ else raise TypeError
63
+ end
64
+ system({ 'TZ' => tz }, *command, exception: true)
65
+ end
66
+
67
+ def creation_time(input_path)
68
+ command = %W[
69
+ ffprobe -v warning -print_format json
70
+ -show_entries format_tags=creation_time,com.apple.quicktime.creationdate
71
+ #{input_path}
72
+ ]
73
+ stdout_string, status = Open3.capture2(*command)
74
+ raise unless status.success?
75
+
76
+ parsed = JSON.parse(stdout_string)
77
+ iso8601_string = parsed['format']['tags']['com.apple.quicktime.creationdate'] || parsed['format']['tags']['creation_time']
78
+ raise 'Cannot find creation time' if iso8601_string.nil?
79
+
80
+ Time.iso8601(iso8601_string)
81
+ end
82
+
83
+ private
84
+
85
+ def coord_map(coordinate_origin, x, y)
86
+ case coordinate_origin
87
+ when 'top-left' then "x=#{x}:y=#{y}"
88
+ when 'top-right' then "x=w-tw-#{x}:y=#{y}"
89
+ when 'bottom-left' then "x=#{x}:y=h-th-#{y}"
90
+ when 'bottom-right' then "x=w-tw-#{x}:y=h-th-#{y}"
91
+ end
92
+ end
32
93
 
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?
94
+ def escape_text_expansion_argument(string)
95
+ string.gsub(/[:{}]/, '\\\\\\&')
96
+ end
39
97
 
40
- parsed = JSON.parse(stdout_string)
41
- iso8601_string = parsed['format']['tags']['creation_time']
42
- raise 'Cannot find creation time' if iso8601_string.nil?
98
+ def escape_filter_description_value(string)
99
+ string.gsub(/[:\\']/, '\\\\\\&')
100
+ end
43
101
 
44
- Time.iso8601(iso8601_string)
102
+ def escape_filter_description(string)
103
+ string.gsub(/[\\'\[\],;]/, '\\\\\\&')
104
+ end
45
105
  end
46
106
  end
47
107
  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.1.0
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-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: marcel
@@ -24,6 +24,20 @@ 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'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: minitest
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,8 +66,8 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '13.0'
55
- description: timestamp_maker is a command-line tool that adds timestamp to images
56
- and videos.
69
+ description: timestamp_maker is a command-line tool that adds timestamp on assets/videos
70
+ based on their creation time.
57
71
  email: tonytonyjan@gmail.com
58
72
  executables:
59
73
  - timestamp
@@ -88,6 +102,6 @@ requirements: []
88
102
  rubygems_version: 3.2.22
89
103
  signing_key:
90
104
  specification_version: 4
91
- summary: timestamp_maker is a command-line tool that adds timestamp to images and
92
- videos.
105
+ summary: timestamp_maker is a command-line tool that adds timestamp on assets/videos
106
+ based on their creation time.
93
107
  test_files: []