vcs_ruby 1.1.8 → 1.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/vcs.rb +171 -169
- data/lib/FFmpeg/ffmpeg.rb +121 -121
- data/lib/FFmpeg/ffmpeg_audio_stream.rb +37 -37
- data/lib/FFmpeg/ffmpeg_meta_info.rb +36 -36
- data/lib/FFmpeg/ffmpeg_video_stream.rb +62 -56
- data/lib/MPlayer/mplayer.rb +101 -101
- data/lib/MPlayer/mplayer_audio_stream.rb +33 -33
- data/lib/MPlayer/mplayer_meta_info.rb +39 -39
- data/lib/MPlayer/mplayer_video_stream.rb +44 -44
- data/lib/black.yml +12 -12
- data/lib/capturer.rb +69 -69
- data/lib/command.rb +51 -51
- data/lib/configuration.rb +150 -150
- data/lib/contact_sheet.rb +380 -366
- data/lib/defaults.yml +39 -39
- data/lib/font.rb +81 -81
- data/lib/frame.rb +161 -161
- data/lib/libAV/libav.rb +151 -151
- data/lib/libAV/libav_audio_stream.rb +37 -37
- data/lib/libAV/libav_meta_info.rb +36 -36
- data/lib/libAV/libav_video_stream.rb +62 -56
- data/lib/oldstyle.yml +22 -22
- data/lib/stream.rb +24 -24
- data/lib/time_index.rb +121 -121
- data/lib/tools.rb +81 -70
- data/lib/vcs.rb +26 -26
- data/lib/version.info +1 -1
- data/lib/version.rb +25 -25
- data/lib/video.rb +92 -88
- data/lib/white.yml +12 -12
- metadata +39 -47
data/lib/defaults.yml
CHANGED
@@ -1,39 +1,39 @@
|
|
1
|
-
main:
|
2
|
-
rows: 4
|
3
|
-
columns: 4
|
4
|
-
interval: ~
|
5
|
-
padding: 2
|
6
|
-
quality: 95
|
7
|
-
filter:
|
8
|
-
timestamp: true
|
9
|
-
polaroid: false
|
10
|
-
softshadow: true
|
11
|
-
style:
|
12
|
-
header:
|
13
|
-
font: DejaVuSans.ttf
|
14
|
-
size: 14
|
15
|
-
color: Black
|
16
|
-
background: "#afcd7a"
|
17
|
-
title:
|
18
|
-
font: DejaVuSans.ttf
|
19
|
-
size: 33
|
20
|
-
color: Black
|
21
|
-
background: White
|
22
|
-
highlight:
|
23
|
-
background: LightGoldenRod
|
24
|
-
contact:
|
25
|
-
background: White
|
26
|
-
timestamp:
|
27
|
-
font: DejaVuSans.ttf
|
28
|
-
size: 14
|
29
|
-
color: White
|
30
|
-
background: "#000000aa"
|
31
|
-
signature:
|
32
|
-
font: DejaVuSans.ttf
|
33
|
-
size: 10
|
34
|
-
color: Black
|
35
|
-
background: SlateGray
|
36
|
-
lowlevel:
|
37
|
-
blank_evasion: true
|
38
|
-
blank_threshold: 0.08
|
39
|
-
blank_alternatives: [ -2, 2, -5, 5, -10, 10, -15, 15, -30, 30, 0]
|
1
|
+
main:
|
2
|
+
rows: 4
|
3
|
+
columns: 4
|
4
|
+
interval: ~
|
5
|
+
padding: 2
|
6
|
+
quality: 95
|
7
|
+
filter:
|
8
|
+
timestamp: true
|
9
|
+
polaroid: false
|
10
|
+
softshadow: true
|
11
|
+
style:
|
12
|
+
header:
|
13
|
+
font: DejaVuSans.ttf
|
14
|
+
size: 14
|
15
|
+
color: Black
|
16
|
+
background: "#afcd7a"
|
17
|
+
title:
|
18
|
+
font: DejaVuSans.ttf
|
19
|
+
size: 33
|
20
|
+
color: Black
|
21
|
+
background: White
|
22
|
+
highlight:
|
23
|
+
background: LightGoldenRod
|
24
|
+
contact:
|
25
|
+
background: White
|
26
|
+
timestamp:
|
27
|
+
font: DejaVuSans.ttf
|
28
|
+
size: 14
|
29
|
+
color: White
|
30
|
+
background: "#000000aa"
|
31
|
+
signature:
|
32
|
+
font: DejaVuSans.ttf
|
33
|
+
size: 10
|
34
|
+
color: Black
|
35
|
+
background: SlateGray
|
36
|
+
lowlevel:
|
37
|
+
blank_evasion: true
|
38
|
+
blank_threshold: 0.08
|
39
|
+
blank_alternatives: [ -2, 2, -5, 5, -10, 10, -15, 15, -30, 30, 0]
|
data/lib/font.rb
CHANGED
@@ -1,81 +1,81 @@
|
|
1
|
-
#
|
2
|
-
# Font helper
|
3
|
-
#
|
4
|
-
|
5
|
-
require 'mini_magick'
|
6
|
-
require 'pp'
|
7
|
-
|
8
|
-
module VCSRuby
|
9
|
-
IMFont = Struct.new(:name, :family, :style, :stretch, :weight, :glyphs)
|
10
|
-
|
11
|
-
class Font
|
12
|
-
attr_reader :name, :path, :size
|
13
|
-
|
14
|
-
@@fonts = {}
|
15
|
-
|
16
|
-
def initialize name, size
|
17
|
-
@name = name
|
18
|
-
@path = find_path
|
19
|
-
@size = size
|
20
|
-
end
|
21
|
-
|
22
|
-
def exists?
|
23
|
-
load_font_cache if @@fonts.length == 0
|
24
|
-
|
25
|
-
!!font_by_name(@name)
|
26
|
-
end
|
27
|
-
|
28
|
-
def find_path
|
29
|
-
load_font_cache if @@fonts.length == 0
|
30
|
-
|
31
|
-
if exists?
|
32
|
-
font_by_name(@name).glyphs
|
33
|
-
else
|
34
|
-
nil
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def font_by_name name
|
39
|
-
if name =~ /\./
|
40
|
-
key, font = @@fonts.select{ |key, f| f.glyphs =~ /#{name}\z/ }.first
|
41
|
-
return font
|
42
|
-
else
|
43
|
-
@@fonts[name]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def load_font_cache
|
48
|
-
|
49
|
-
fonts = MiniMagick::Tool::Identify.new(whiny: false) do |identify|
|
50
|
-
identify.list 'font'
|
51
|
-
end
|
52
|
-
|
53
|
-
parse_fonts(fonts)
|
54
|
-
end
|
55
|
-
|
56
|
-
def parse_fonts(fonts)
|
57
|
-
font = nil
|
58
|
-
fonts.lines.each do |line|
|
59
|
-
key, value = line.strip.split(':', 2).map(&:strip)
|
60
|
-
|
61
|
-
next if [nil, 'Path'].include? key
|
62
|
-
|
63
|
-
if key == 'Font'
|
64
|
-
@@fonts[value] = font = IMFont.new(value)
|
65
|
-
else
|
66
|
-
font.send("#{key}=", value)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def line_height
|
72
|
-
MiniMagick::Tool::Convert.new do |convert|
|
73
|
-
convert.font path if exists?
|
74
|
-
convert.pointsize size
|
75
|
-
convert << 'label:F'
|
76
|
-
convert.format '%h'
|
77
|
-
convert << 'info:'
|
78
|
-
end.to_i
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
1
|
+
#
|
2
|
+
# Font helper
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'mini_magick'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
module VCSRuby
|
9
|
+
IMFont = Struct.new(:name, :family, :style, :stretch, :weight, :glyphs)
|
10
|
+
|
11
|
+
class Font
|
12
|
+
attr_reader :name, :path, :size
|
13
|
+
|
14
|
+
@@fonts = {}
|
15
|
+
|
16
|
+
def initialize name, size
|
17
|
+
@name = name
|
18
|
+
@path = find_path
|
19
|
+
@size = size
|
20
|
+
end
|
21
|
+
|
22
|
+
def exists?
|
23
|
+
load_font_cache if @@fonts.length == 0
|
24
|
+
|
25
|
+
!!font_by_name(@name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_path
|
29
|
+
load_font_cache if @@fonts.length == 0
|
30
|
+
|
31
|
+
if exists?
|
32
|
+
font_by_name(@name).glyphs
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def font_by_name name
|
39
|
+
if name =~ /\./
|
40
|
+
key, font = @@fonts.select{ |key, f| f.glyphs =~ /#{name}\z/ }.first
|
41
|
+
return font
|
42
|
+
else
|
43
|
+
@@fonts[name]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_font_cache
|
48
|
+
|
49
|
+
fonts = MiniMagick::Tool::Identify.new(whiny: false) do |identify|
|
50
|
+
identify.list 'font'
|
51
|
+
end
|
52
|
+
|
53
|
+
parse_fonts(fonts)
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_fonts(fonts)
|
57
|
+
font = nil
|
58
|
+
fonts.lines.each do |line|
|
59
|
+
key, value = line.strip.split(':', 2).map(&:strip)
|
60
|
+
|
61
|
+
next if [nil, 'Path'].include? key
|
62
|
+
|
63
|
+
if key == 'Font'
|
64
|
+
@@fonts[value] = font = IMFont.new(value)
|
65
|
+
else
|
66
|
+
font.send("#{key}=", value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def line_height
|
72
|
+
MiniMagick::Tool::Convert.new do |convert|
|
73
|
+
convert.font path if exists?
|
74
|
+
convert.pointsize size
|
75
|
+
convert << 'label:F'
|
76
|
+
convert.format '%h'
|
77
|
+
convert << 'info:'
|
78
|
+
end.to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/frame.rb
CHANGED
@@ -1,161 +1,161 @@
|
|
1
|
-
#
|
2
|
-
# Frame from video
|
3
|
-
#
|
4
|
-
|
5
|
-
require 'mini_magick'
|
6
|
-
|
7
|
-
module VCSRuby
|
8
|
-
class Frame
|
9
|
-
attr_accessor :width, :height, :aspect
|
10
|
-
attr_reader :filters, :time
|
11
|
-
|
12
|
-
def initialize video, capturer, time
|
13
|
-
@video = video
|
14
|
-
@capturer = capturer
|
15
|
-
@time = time
|
16
|
-
@filters = []
|
17
|
-
end
|
18
|
-
|
19
|
-
def filename= file_path
|
20
|
-
@out_path = File.dirname(file_path)
|
21
|
-
@out_filename = File.basename(file_path,'.*')
|
22
|
-
end
|
23
|
-
|
24
|
-
def filename
|
25
|
-
File.join(@out_path, "#{@out_filename}.#{@capturer.format_extension}")
|
26
|
-
end
|
27
|
-
|
28
|
-
def format= fmt
|
29
|
-
@capturer.format = fmt
|
30
|
-
end
|
31
|
-
|
32
|
-
def format
|
33
|
-
@capturer.format
|
34
|
-
end
|
35
|
-
|
36
|
-
def capture
|
37
|
-
@capturer.grab @time, filename
|
38
|
-
end
|
39
|
-
|
40
|
-
def capture_and_evade interval = nil
|
41
|
-
times = [TimeIndex.new] + Configuration.instance.blank_alternatives
|
42
|
-
if interval
|
43
|
-
times.select! { |t| (t < interval / 2) and (t > interval / -2) }
|
44
|
-
end
|
45
|
-
times.map! { |t| @time + t }
|
46
|
-
|
47
|
-
times.each do |time|
|
48
|
-
@time = time
|
49
|
-
capture
|
50
|
-
break unless blank?
|
51
|
-
puts "Blank frame detected. => #{@time}" unless Configuration.instance.quiet?
|
52
|
-
puts "Giving up!" if time == times.last && !Configuration.instance.quiet?
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def blank?
|
57
|
-
image = MiniMagick::Image.open filename
|
58
|
-
image.colorspace 'Gray'
|
59
|
-
mean = image['%[fx:image.mean]'].to_f
|
60
|
-
return mean < Configuration.instance.blank_threshold
|
61
|
-
end
|
62
|
-
|
63
|
-
def apply_filters
|
64
|
-
MiniMagick::Tool::Convert.new do |convert|
|
65
|
-
convert.background 'Transparent'
|
66
|
-
convert.fill 'Transparent'
|
67
|
-
convert << filename
|
68
|
-
|
69
|
-
sorted_filters.each do |filter|
|
70
|
-
call_filter filter, convert
|
71
|
-
end
|
72
|
-
|
73
|
-
convert << filename
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
def call_filter filter, convert
|
79
|
-
if respond_to?(filter, true)
|
80
|
-
method(filter).call(convert)
|
81
|
-
else
|
82
|
-
raise "Filter '#{filter}' does not exist"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def sorted_filters
|
87
|
-
[:resize_filter, :timestamp_filter, :photoframe_filter, :polaroid_filter, :random_rotation_filter, :softshadow_filter].select{ |filter| @filters.include?(filter) }
|
88
|
-
end
|
89
|
-
|
90
|
-
def resize_filter convert
|
91
|
-
convert.resize "#{width}x#{height}!"
|
92
|
-
end
|
93
|
-
|
94
|
-
def timestamp_filter convert
|
95
|
-
convert.stack do |box|
|
96
|
-
box.box Configuration.instance.timestamp_background
|
97
|
-
box.fill Configuration.instance.timestamp_color
|
98
|
-
box.pointsize Configuration.instance.timestamp_font.size
|
99
|
-
box.gravity 'SouthEast'
|
100
|
-
if Configuration.instance.timestamp_font.exists?
|
101
|
-
box.font Configuration.instance.timestamp_font.path
|
102
|
-
end
|
103
|
-
box.annotate('+10+10', " #{@time.to_timestamp} ")
|
104
|
-
end
|
105
|
-
convert.flatten
|
106
|
-
convert.gravity 'None'
|
107
|
-
end
|
108
|
-
|
109
|
-
def softshadow_filter convert
|
110
|
-
convert.stack do |box|
|
111
|
-
box.background 'Black'
|
112
|
-
box.clone.+
|
113
|
-
box.shadow '50x2+4+4'
|
114
|
-
box.background 'None'
|
115
|
-
end
|
116
|
-
convert.swap.+
|
117
|
-
convert.flatten
|
118
|
-
convert.trim
|
119
|
-
convert.repage.+
|
120
|
-
end
|
121
|
-
|
122
|
-
def photoframe_filter convert
|
123
|
-
convert.bordercolor 'White'
|
124
|
-
convert.border 3
|
125
|
-
convert.bordercolor 'Grey60'
|
126
|
-
convert.border 1
|
127
|
-
end
|
128
|
-
|
129
|
-
def polaroid_filter convert
|
130
|
-
border = 8
|
131
|
-
convert.stack do |a|
|
132
|
-
a.fill 'White'
|
133
|
-
a.background 'White'
|
134
|
-
a.bordercolor 'White'
|
135
|
-
if Tools.magick_version.major > 6
|
136
|
-
a.alpha_color 'White'
|
137
|
-
else
|
138
|
-
a.mattecolor 'White'
|
139
|
-
end
|
140
|
-
a.frame "#{border}x#{border}"
|
141
|
-
a.stack do |b|
|
142
|
-
b.flip
|
143
|
-
b.splice "0x#{border*5}"
|
144
|
-
end
|
145
|
-
a.flip
|
146
|
-
a.bordercolor 'Grey60'
|
147
|
-
a.border 1
|
148
|
-
end
|
149
|
-
convert.repage.+
|
150
|
-
end
|
151
|
-
|
152
|
-
def random_rotation_filter convert
|
153
|
-
angle = Random::rand(-18..18)
|
154
|
-
convert.background 'None'
|
155
|
-
convert.rotate angle
|
156
|
-
end
|
157
|
-
|
158
|
-
def film_filter
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
1
|
+
#
|
2
|
+
# Frame from video
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'mini_magick'
|
6
|
+
|
7
|
+
module VCSRuby
|
8
|
+
class Frame
|
9
|
+
attr_accessor :width, :height, :aspect
|
10
|
+
attr_reader :filters, :time
|
11
|
+
|
12
|
+
def initialize video, capturer, time
|
13
|
+
@video = video
|
14
|
+
@capturer = capturer
|
15
|
+
@time = time
|
16
|
+
@filters = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def filename= file_path
|
20
|
+
@out_path = File.dirname(file_path)
|
21
|
+
@out_filename = File.basename(file_path,'.*')
|
22
|
+
end
|
23
|
+
|
24
|
+
def filename
|
25
|
+
File.join(@out_path, "#{@out_filename}.#{@capturer.format_extension}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def format= fmt
|
29
|
+
@capturer.format = fmt
|
30
|
+
end
|
31
|
+
|
32
|
+
def format
|
33
|
+
@capturer.format
|
34
|
+
end
|
35
|
+
|
36
|
+
def capture
|
37
|
+
@capturer.grab @time, filename
|
38
|
+
end
|
39
|
+
|
40
|
+
def capture_and_evade interval = nil
|
41
|
+
times = [TimeIndex.new] + Configuration.instance.blank_alternatives
|
42
|
+
if interval
|
43
|
+
times.select! { |t| (t < interval / 2) and (t > interval / -2) }
|
44
|
+
end
|
45
|
+
times.map! { |t| @time + t }
|
46
|
+
|
47
|
+
times.each do |time|
|
48
|
+
@time = time
|
49
|
+
capture
|
50
|
+
break unless blank?
|
51
|
+
puts "Blank frame detected. => #{@time}" unless Configuration.instance.quiet?
|
52
|
+
puts "Giving up!" if time == times.last && !Configuration.instance.quiet?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def blank?
|
57
|
+
image = MiniMagick::Image.open filename
|
58
|
+
image.colorspace 'Gray'
|
59
|
+
mean = image['%[fx:image.mean]'].to_f
|
60
|
+
return mean < Configuration.instance.blank_threshold
|
61
|
+
end
|
62
|
+
|
63
|
+
def apply_filters
|
64
|
+
MiniMagick::Tool::Convert.new do |convert|
|
65
|
+
convert.background 'Transparent'
|
66
|
+
convert.fill 'Transparent'
|
67
|
+
convert << filename
|
68
|
+
|
69
|
+
sorted_filters.each do |filter|
|
70
|
+
call_filter filter, convert
|
71
|
+
end
|
72
|
+
|
73
|
+
convert << filename
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def call_filter filter, convert
|
79
|
+
if respond_to?(filter, true)
|
80
|
+
method(filter).call(convert)
|
81
|
+
else
|
82
|
+
raise "Filter '#{filter}' does not exist"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def sorted_filters
|
87
|
+
[:resize_filter, :timestamp_filter, :photoframe_filter, :polaroid_filter, :random_rotation_filter, :softshadow_filter].select{ |filter| @filters.include?(filter) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def resize_filter convert
|
91
|
+
convert.resize "#{width}x#{height}!"
|
92
|
+
end
|
93
|
+
|
94
|
+
def timestamp_filter convert
|
95
|
+
convert.stack do |box|
|
96
|
+
box.box Configuration.instance.timestamp_background
|
97
|
+
box.fill Configuration.instance.timestamp_color
|
98
|
+
box.pointsize Configuration.instance.timestamp_font.size
|
99
|
+
box.gravity 'SouthEast'
|
100
|
+
if Configuration.instance.timestamp_font.exists?
|
101
|
+
box.font Configuration.instance.timestamp_font.path
|
102
|
+
end
|
103
|
+
box.annotate('+10+10', " #{@time.to_timestamp} ")
|
104
|
+
end
|
105
|
+
convert.flatten
|
106
|
+
convert.gravity 'None'
|
107
|
+
end
|
108
|
+
|
109
|
+
def softshadow_filter convert
|
110
|
+
convert.stack do |box|
|
111
|
+
box.background 'Black'
|
112
|
+
box.clone.+
|
113
|
+
box.shadow '50x2+4+4'
|
114
|
+
box.background 'None'
|
115
|
+
end
|
116
|
+
convert.swap.+
|
117
|
+
convert.flatten
|
118
|
+
convert.trim
|
119
|
+
convert.repage.+
|
120
|
+
end
|
121
|
+
|
122
|
+
def photoframe_filter convert
|
123
|
+
convert.bordercolor 'White'
|
124
|
+
convert.border 3
|
125
|
+
convert.bordercolor 'Grey60'
|
126
|
+
convert.border 1
|
127
|
+
end
|
128
|
+
|
129
|
+
def polaroid_filter convert
|
130
|
+
border = 8
|
131
|
+
convert.stack do |a|
|
132
|
+
a.fill 'White'
|
133
|
+
a.background 'White'
|
134
|
+
a.bordercolor 'White'
|
135
|
+
if Tools.magick_version.major > 6
|
136
|
+
a.alpha_color 'White'
|
137
|
+
else
|
138
|
+
a.mattecolor 'White'
|
139
|
+
end
|
140
|
+
a.frame "#{border}x#{border}"
|
141
|
+
a.stack do |b|
|
142
|
+
b.flip
|
143
|
+
b.splice "0x#{border*5}"
|
144
|
+
end
|
145
|
+
a.flip
|
146
|
+
a.bordercolor 'Grey60'
|
147
|
+
a.border 1
|
148
|
+
end
|
149
|
+
convert.repage.+
|
150
|
+
end
|
151
|
+
|
152
|
+
def random_rotation_filter convert
|
153
|
+
angle = Random::rand(-18..18)
|
154
|
+
convert.background 'None'
|
155
|
+
convert.rotate angle
|
156
|
+
end
|
157
|
+
|
158
|
+
def film_filter
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|