timestamp_maker 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []