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 +4 -4
- data/bin/timestamp +80 -3
- data/lib/timestamp_maker.rb +43 -2
- data/lib/timestamp_maker/image_timestamper.rb +31 -9
- data/lib/timestamp_maker/video_timestamper.rb +93 -33
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed37875d238df5bcc9566649a2767aff49522b6baab2f2c5bb07f4a788e38f5b
|
4
|
+
data.tar.gz: 14647536fd8c17f34a02e9b7d4542e8c154561fcea3c569a18613fa174338ecf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
+
}
|
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(
|
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
|
+
)
|
data/lib/timestamp_maker.rb
CHANGED
@@ -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(
|
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.
|
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
|
-
|
9
|
-
|
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
|
14
|
-
-fill
|
15
|
-
-
|
16
|
-
-pointsize
|
33
|
+
-background #{background_color}
|
34
|
+
-fill #{font_color}
|
35
|
+
-family #{font_family}
|
36
|
+
-pointsize #{font_size}
|
17
37
|
-gravity NorthWest
|
18
|
-
-splice
|
38
|
+
-splice #{font_padding}x#{font_padding}
|
19
39
|
-gravity SouthEast
|
20
|
-
-splice
|
40
|
+
-splice #{font_padding}x#{font_padding}
|
21
41
|
label:#{time_string}
|
22
42
|
)
|
23
|
-
-gravity
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
#{
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
98
|
+
def escape_filter_description_value(string)
|
99
|
+
string.gsub(/[:\\']/, '\\\\\\&')
|
100
|
+
end
|
43
101
|
|
44
|
-
|
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
|
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-
|
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
|
56
|
-
|
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
|
92
|
-
|
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: []
|