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 +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: []
|