vcs_ruby 1.1.9 → 1.1.10
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/vcs.rb +171 -171
- 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 +64 -62
- 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 +50 -51
- data/lib/configuration.rb +150 -150
- data/lib/contact_sheet.rb +380 -380
- 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 +64 -62
- data/lib/oldstyle.yml +22 -22
- data/lib/stream.rb +24 -24
- data/lib/time_index.rb +121 -121
- data/lib/tools.rb +81 -81
- data/lib/vcs.rb +26 -26
- data/lib/version.info +1 -1
- data/lib/version.rb +25 -25
- data/lib/video.rb +92 -92
- data/lib/white.yml +12 -12
- metadata +47 -39
- checksums.yaml +0 -7
data/lib/contact_sheet.rb
CHANGED
@@ -1,380 +1,380 @@
|
|
1
|
-
#
|
2
|
-
# Contact Sheet Composited from the Thumbnails
|
3
|
-
#
|
4
|
-
|
5
|
-
require 'fileutils'
|
6
|
-
require 'tmpdir'
|
7
|
-
require 'yaml'
|
8
|
-
|
9
|
-
require 'vcs'
|
10
|
-
|
11
|
-
module VCSRuby
|
12
|
-
class ContactSheet
|
13
|
-
attr_accessor :signature, :title, :highlight
|
14
|
-
attr_accessor :softshadow, :timestamp, :polaroid
|
15
|
-
attr_reader :thumbnail_width, :thumbnail_height, :aspect_ratio, :format
|
16
|
-
attr_reader :length, :from, :to
|
17
|
-
|
18
|
-
def initialize video, capturer
|
19
|
-
@video = video
|
20
|
-
@capturer = capturer
|
21
|
-
@signature = "Created by Video Contact Sheet Ruby"
|
22
|
-
initialize_filename
|
23
|
-
|
24
|
-
if Configuration.instance.verbose?
|
25
|
-
puts "Processing #{File.basename(video.full_path)}..."
|
26
|
-
end
|
27
|
-
|
28
|
-
return unless @video.valid?
|
29
|
-
|
30
|
-
detect_video_properties
|
31
|
-
|
32
|
-
@from = TimeIndex.new 0
|
33
|
-
@to = @video.info.duration
|
34
|
-
|
35
|
-
@timestamp = Configuration.instance.timestamp
|
36
|
-
@softshadow = Configuration.instance.softshadow
|
37
|
-
@polaroid = Configuration.instance.polaroid
|
38
|
-
|
39
|
-
@tempdir = Dir.mktmpdir
|
40
|
-
|
41
|
-
ObjectSpace.define_finalizer(self, self.class.finalize(@tempdir) )
|
42
|
-
initialize_geometry(Configuration.instance.rows, Configuration.instance.columns, Configuration.instance.interval)
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
def initialize_filename override = nil
|
47
|
-
@out_path = File.dirname(@video.full_path)
|
48
|
-
@out_filename = File.basename(override || @video.full_path,'.*')
|
49
|
-
|
50
|
-
extension = override ? File.extname(override) : ''
|
51
|
-
@format = extension.length > 0 ? extension : '.png'
|
52
|
-
end
|
53
|
-
|
54
|
-
def initialize_geometry(rows, columns, interval)
|
55
|
-
@has_interval = !!interval
|
56
|
-
@rows = rows
|
57
|
-
@columns = columns
|
58
|
-
@interval = interval
|
59
|
-
end
|
60
|
-
|
61
|
-
def filename
|
62
|
-
"#{@out_filename}#{@format}"
|
63
|
-
end
|
64
|
-
|
65
|
-
def format= format
|
66
|
-
@format = ".#{format.to_s}"
|
67
|
-
end
|
68
|
-
|
69
|
-
def full_path
|
70
|
-
File.join(@out_path, filename)
|
71
|
-
end
|
72
|
-
|
73
|
-
def rows
|
74
|
-
@rows
|
75
|
-
end
|
76
|
-
|
77
|
-
def columns
|
78
|
-
@columns
|
79
|
-
end
|
80
|
-
|
81
|
-
def interval
|
82
|
-
@interval || (@to - @from) / (number_of_caps)
|
83
|
-
end
|
84
|
-
|
85
|
-
def number_of_caps
|
86
|
-
if @has_interval
|
87
|
-
(@to - @from) / @interval
|
88
|
-
else
|
89
|
-
if @rows && @columns
|
90
|
-
@rows * @columns
|
91
|
-
else
|
92
|
-
raise "you need at least 2 parameters from columns, rows and interval"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def thumbnail_width= width
|
98
|
-
@thumbnail_height = (width.to_f / @thumbnail_width * thumbnail_height).to_i
|
99
|
-
@thumbnail_width = width
|
100
|
-
end
|
101
|
-
|
102
|
-
def thumbnail_height= height
|
103
|
-
@thumbnail_width = (height.to_f / @thumbnail_height * thumbnail_width).to_i
|
104
|
-
@thumbnail_height = height
|
105
|
-
end
|
106
|
-
|
107
|
-
def aspect_ratio= aspect_ratio
|
108
|
-
@thumbnail_width = (@thumbnail_height * aspect_ratio).to_i
|
109
|
-
@aspect_ratio = aspect_ratio
|
110
|
-
end
|
111
|
-
|
112
|
-
def from= time
|
113
|
-
if (TimeIndex.new(0) < time) && (time < to) && (time < @length)
|
114
|
-
@from = time
|
115
|
-
else
|
116
|
-
raise "Invalid From Time"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def to= time
|
121
|
-
if (TimeIndex.new(0) < time) && (from < time) && (time < @length)
|
122
|
-
@to = time
|
123
|
-
else
|
124
|
-
raise "Invalid To Time"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def build
|
129
|
-
if (@video.info.duration.total_seconds < 1.0)
|
130
|
-
puts "Video is shorter than 1 sec"
|
131
|
-
else
|
132
|
-
initialize_filters
|
133
|
-
initialize_thumbnails
|
134
|
-
capture_thumbnails
|
135
|
-
|
136
|
-
puts "Composing standard contact sheet..." unless Configuration.instance.quiet?
|
137
|
-
montage = splice_montage(montage_thumbs)
|
138
|
-
|
139
|
-
image = MiniMagick::Image.open(montage)
|
140
|
-
|
141
|
-
puts "Adding header and footer..." unless Configuration.instance.quiet?
|
142
|
-
final = add_header_and_footer image
|
143
|
-
|
144
|
-
puts "Done. Output wrote to '#{filename}'" unless Configuration.instance.quiet?
|
145
|
-
FileUtils.mv(final, full_path)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
def self.finalize(tempdir)
|
151
|
-
proc do
|
152
|
-
puts "Cleaning up..." unless Configuration.instance.quiet?
|
153
|
-
FileUtils.rm_r tempdir
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def initialize_filters
|
158
|
-
@filters = []
|
159
|
-
@filters << :resize_filter
|
160
|
-
@filters << :softshadow_filter if softshadow
|
161
|
-
@filters << :timestamp_filter if timestamp
|
162
|
-
@filters << :polaroid_filter if polaroid
|
163
|
-
end
|
164
|
-
|
165
|
-
def initialize_thumbnails
|
166
|
-
@thumbnails = []
|
167
|
-
time = @from + (interval / 2)
|
168
|
-
(1..number_of_caps).each do |i|
|
169
|
-
thumb = Frame.new @video, @capturer, time
|
170
|
-
time = time + interval
|
171
|
-
thumb.width = thumbnail_width
|
172
|
-
thumb.height = thumbnail_height
|
173
|
-
thumb.filename = File::join(@tempdir, "th#{"%03d" % i}.#{@capturer.format_extension}")
|
174
|
-
thumb.filters.push(*@filters)
|
175
|
-
|
176
|
-
@thumbnails << thumb
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def capture_thumbnails
|
181
|
-
puts "Capturing in range [#{from}..#{to}]. Total length: #{@length}" unless Configuration.instance.quiet?
|
182
|
-
|
183
|
-
@thumbnails.each_with_index do |thumbnail, i|
|
184
|
-
puts "Generating capture ##{i + 1}/#{number_of_caps} #{thumbnail.time}..." unless Configuration.instance.quiet?
|
185
|
-
if Configuration.instance.blank_evasion?
|
186
|
-
thumbnail.capture_and_evade interval
|
187
|
-
else
|
188
|
-
thumbnail.capture
|
189
|
-
end
|
190
|
-
thumbnail.apply_filters
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def detect_video_properties
|
195
|
-
detect_length
|
196
|
-
detect_dimensions
|
197
|
-
end
|
198
|
-
|
199
|
-
def detect_length
|
200
|
-
@length = @video.info.duration
|
201
|
-
|
202
|
-
@from = TimeIndex.new 0.0
|
203
|
-
@to = @length
|
204
|
-
end
|
205
|
-
|
206
|
-
def detect_dimensions
|
207
|
-
@thumbnail_width = @video.video.width
|
208
|
-
@thumbnail_height = @video.video.height
|
209
|
-
|
210
|
-
@aspect_ratio = @video.video.aspect_ratio
|
211
|
-
|
212
|
-
if @aspect_ratio == 0
|
213
|
-
@aspect_ratio = Rational(@video.video.width, @video.video.height)
|
214
|
-
else
|
215
|
-
#recalculate width, for PAR 1:1 this should be the same as before
|
216
|
-
@thumbnail_width = (@aspect_ratio * @video.video.height).to_i
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def montage_thumbs
|
221
|
-
file_path = File::join(@tempdir, 'montage.png')
|
222
|
-
MiniMagick::Tool::Montage.new do |montage|
|
223
|
-
montage.background Configuration.instance.contact_background
|
224
|
-
@thumbnails.each do |thumbnail|
|
225
|
-
montage << thumbnail.filename
|
226
|
-
end
|
227
|
-
montage.geometry "+#{Configuration.instance.padding}+#{Configuration.instance.padding}"
|
228
|
-
# rows or columns can be nil (auto fit)
|
229
|
-
montage.tile "#{@columns}x#{@rows}"
|
230
|
-
montage << file_path
|
231
|
-
end
|
232
|
-
return file_path
|
233
|
-
end
|
234
|
-
|
235
|
-
def splice_montage montage_path
|
236
|
-
if softshadow
|
237
|
-
left = Configuration.instance.padding + 3
|
238
|
-
top = Configuration.instance.padding + 5
|
239
|
-
bottom = right = Configuration.instance.padding
|
240
|
-
else
|
241
|
-
left = right = top = bottom = Configuration.instance.padding
|
242
|
-
end
|
243
|
-
|
244
|
-
|
245
|
-
file_path = File::join(@tempdir, 'spliced.png')
|
246
|
-
MiniMagick::Tool::Convert.new do |convert|
|
247
|
-
convert << montage_path
|
248
|
-
convert.background Configuration.instance.contact_background
|
249
|
-
|
250
|
-
convert.splice "#{left}x#{top}"
|
251
|
-
convert.gravity 'SouthEast'
|
252
|
-
convert.splice "#{right}x#{bottom}"
|
253
|
-
|
254
|
-
convert << file_path
|
255
|
-
end
|
256
|
-
file_path
|
257
|
-
end
|
258
|
-
|
259
|
-
def create_title montage
|
260
|
-
file_path = File::join(@tempdir, 'title.png')
|
261
|
-
MiniMagick::Tool::Convert.new do |convert|
|
262
|
-
convert.stack do |ul|
|
263
|
-
ul.size "#{montage.width}x#{Configuration.instance.title_font.line_height}"
|
264
|
-
ul.xc Configuration.instance.title_background
|
265
|
-
if Configuration.instance.title_font.exists?
|
266
|
-
ul.font Configuration.instance.title_font.path
|
267
|
-
end
|
268
|
-
ul.pointsize Configuration.instance.title_font.size
|
269
|
-
ul.background Configuration.instance.title_background
|
270
|
-
ul.fill Configuration.instance.title_color
|
271
|
-
ul.gravity 'Center'
|
272
|
-
ul.annotate(0, @title)
|
273
|
-
end
|
274
|
-
convert.flatten
|
275
|
-
convert << file_path
|
276
|
-
end
|
277
|
-
return file_path
|
278
|
-
end
|
279
|
-
|
280
|
-
def create_highlight montage
|
281
|
-
puts "Generating highlight..."
|
282
|
-
thumb = Frame.new @video, @highlight
|
283
|
-
|
284
|
-
thumb.width = thumbnail_width
|
285
|
-
thumb.height = thumbnail_height
|
286
|
-
thumb.filename = File::join(@tempdir, "highlight_thumb.png")
|
287
|
-
thumb.capture
|
288
|
-
thumb.apply_filters
|
289
|
-
|
290
|
-
file_path = File::join(@tempdir, "highlight.png")
|
291
|
-
MiniMagick::Tool::Convert.new do |convert|
|
292
|
-
convert.stack do |a|
|
293
|
-
a.size "#{montage.width}x#{thumbnail_height+20}"
|
294
|
-
a.xc Configuration.instance.highlight_background
|
295
|
-
a.gravity 'Center'
|
296
|
-
a << thumb.filename
|
297
|
-
a.composite
|
298
|
-
end
|
299
|
-
convert.stack do |a|
|
300
|
-
a.size "#{montage.width}x1"
|
301
|
-
a.xc 'Black'
|
302
|
-
end
|
303
|
-
convert.append
|
304
|
-
convert << file_path
|
305
|
-
end
|
306
|
-
|
307
|
-
file_path
|
308
|
-
end
|
309
|
-
|
310
|
-
def add_header_and_footer montage
|
311
|
-
file_path = File::join(@tempdir, filename)
|
312
|
-
header_height = Configuration.instance.header_font.line_height * 3
|
313
|
-
signature_height = Configuration.instance.signature_font.line_height + 8
|
314
|
-
MiniMagick::Tool::Convert.new do |convert|
|
315
|
-
convert.stack do |a|
|
316
|
-
a.size "#{montage.width - 18}x1"
|
317
|
-
a.xc Configuration.instance.header_background
|
318
|
-
a.size.+
|
319
|
-
if Configuration.instance.header_font.exists?
|
320
|
-
a.font Configuration.instance.header_font.path
|
321
|
-
end
|
322
|
-
a.pointsize Configuration.instance.header_font.size
|
323
|
-
a.background Configuration.instance.header_background
|
324
|
-
a.fill Configuration.instance.header_color
|
325
|
-
a.stack do |b|
|
326
|
-
b.gravity 'West'
|
327
|
-
b.stack do |c|
|
328
|
-
c.label 'Filename: '
|
329
|
-
if Configuration.instance.header_font.exists?
|
330
|
-
c.font Configuration.instance.header_font.path
|
331
|
-
end
|
332
|
-
c.label File.basename(@video.full_path)
|
333
|
-
c.append.+
|
334
|
-
end
|
335
|
-
if Configuration.instance.header_font.exists?
|
336
|
-
b.font Configuration.instance.header_font.path
|
337
|
-
end
|
338
|
-
b.label "File size: #{Tools.to_human_size(File.size(@video.full_path))}"
|
339
|
-
b.label "Length: #{@length.to_timestamp}"
|
340
|
-
b.append
|
341
|
-
b.crop "#{montage.width}x#{header_height}+0+0"
|
342
|
-
end
|
343
|
-
a.append
|
344
|
-
a.stack do |b|
|
345
|
-
b.size "#{montage.width}x#{header_height}"
|
346
|
-
b.gravity 'East'
|
347
|
-
b.fill Configuration.instance.header_color
|
348
|
-
b.annotate '+0-1'
|
349
|
-
b << "Dimensions: #{@video.video.width}x#{@video.video.height}\nFormat: #{@video.video.codec(true)} / #{@video.audio ? @video.audio.codec(true) : 'no audio'}\nFPS: #{"%.02f" % @video.video.frame_rate.to_f}"
|
350
|
-
end
|
351
|
-
a.bordercolor Configuration.instance.header_background
|
352
|
-
a.border 9
|
353
|
-
end
|
354
|
-
convert << create_title(montage) if @title
|
355
|
-
convert << create_highlight(montage) if @highlight
|
356
|
-
convert << montage.path
|
357
|
-
convert.append
|
358
|
-
if @signature && @signature.length > 0
|
359
|
-
convert.stack do |a|
|
360
|
-
a.size "#{montage.width}x#{signature_height}"
|
361
|
-
a.gravity 'Center'
|
362
|
-
a.xc Configuration.instance.signature_background
|
363
|
-
if Configuration.instance.signature_font.exists?
|
364
|
-
a.font Configuration.instance.signature_font.path
|
365
|
-
end
|
366
|
-
a.pointsize Configuration.instance.signature_font.size
|
367
|
-
a.fill Configuration.instance.signature_color
|
368
|
-
a.annotate(0, @signature)
|
369
|
-
end
|
370
|
-
convert.append
|
371
|
-
end
|
372
|
-
if @format == :jpg || @format == :jpeg
|
373
|
-
convert.quality(Configuration.instance.quality)
|
374
|
-
end
|
375
|
-
convert << file_path
|
376
|
-
end
|
377
|
-
file_path
|
378
|
-
end
|
379
|
-
end
|
380
|
-
end
|
1
|
+
#
|
2
|
+
# Contact Sheet Composited from the Thumbnails
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
require 'tmpdir'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require 'vcs'
|
10
|
+
|
11
|
+
module VCSRuby
|
12
|
+
class ContactSheet
|
13
|
+
attr_accessor :signature, :title, :highlight
|
14
|
+
attr_accessor :softshadow, :timestamp, :polaroid
|
15
|
+
attr_reader :thumbnail_width, :thumbnail_height, :aspect_ratio, :format
|
16
|
+
attr_reader :length, :from, :to
|
17
|
+
|
18
|
+
def initialize video, capturer
|
19
|
+
@video = video
|
20
|
+
@capturer = capturer
|
21
|
+
@signature = "Created by Video Contact Sheet Ruby"
|
22
|
+
initialize_filename
|
23
|
+
|
24
|
+
if Configuration.instance.verbose?
|
25
|
+
puts "Processing #{File.basename(video.full_path)}..."
|
26
|
+
end
|
27
|
+
|
28
|
+
return unless @video.valid?
|
29
|
+
|
30
|
+
detect_video_properties
|
31
|
+
|
32
|
+
@from = TimeIndex.new 0
|
33
|
+
@to = @video.info.duration
|
34
|
+
|
35
|
+
@timestamp = Configuration.instance.timestamp
|
36
|
+
@softshadow = Configuration.instance.softshadow
|
37
|
+
@polaroid = Configuration.instance.polaroid
|
38
|
+
|
39
|
+
@tempdir = Dir.mktmpdir
|
40
|
+
|
41
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@tempdir) )
|
42
|
+
initialize_geometry(Configuration.instance.rows, Configuration.instance.columns, Configuration.instance.interval)
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize_filename override = nil
|
47
|
+
@out_path = File.dirname(@video.full_path)
|
48
|
+
@out_filename = File.basename(override || @video.full_path,'.*')
|
49
|
+
|
50
|
+
extension = override ? File.extname(override) : ''
|
51
|
+
@format = extension.length > 0 ? extension : '.png'
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_geometry(rows, columns, interval)
|
55
|
+
@has_interval = !!interval
|
56
|
+
@rows = rows
|
57
|
+
@columns = columns
|
58
|
+
@interval = interval
|
59
|
+
end
|
60
|
+
|
61
|
+
def filename
|
62
|
+
"#{@out_filename}#{@format}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def format= format
|
66
|
+
@format = ".#{format.to_s}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def full_path
|
70
|
+
File.join(@out_path, filename)
|
71
|
+
end
|
72
|
+
|
73
|
+
def rows
|
74
|
+
@rows
|
75
|
+
end
|
76
|
+
|
77
|
+
def columns
|
78
|
+
@columns
|
79
|
+
end
|
80
|
+
|
81
|
+
def interval
|
82
|
+
@interval || (@to - @from) / (number_of_caps)
|
83
|
+
end
|
84
|
+
|
85
|
+
def number_of_caps
|
86
|
+
if @has_interval
|
87
|
+
(@to - @from) / @interval
|
88
|
+
else
|
89
|
+
if @rows && @columns
|
90
|
+
@rows * @columns
|
91
|
+
else
|
92
|
+
raise "you need at least 2 parameters from columns, rows and interval"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def thumbnail_width= width
|
98
|
+
@thumbnail_height = (width.to_f / @thumbnail_width * thumbnail_height).to_i
|
99
|
+
@thumbnail_width = width
|
100
|
+
end
|
101
|
+
|
102
|
+
def thumbnail_height= height
|
103
|
+
@thumbnail_width = (height.to_f / @thumbnail_height * thumbnail_width).to_i
|
104
|
+
@thumbnail_height = height
|
105
|
+
end
|
106
|
+
|
107
|
+
def aspect_ratio= aspect_ratio
|
108
|
+
@thumbnail_width = (@thumbnail_height * aspect_ratio).to_i
|
109
|
+
@aspect_ratio = aspect_ratio
|
110
|
+
end
|
111
|
+
|
112
|
+
def from= time
|
113
|
+
if (TimeIndex.new(0) < time) && (time < to) && (time < @length)
|
114
|
+
@from = time
|
115
|
+
else
|
116
|
+
raise "Invalid From Time"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def to= time
|
121
|
+
if (TimeIndex.new(0) < time) && (from < time) && (time < @length)
|
122
|
+
@to = time
|
123
|
+
else
|
124
|
+
raise "Invalid To Time"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def build
|
129
|
+
if (@video.info.duration.total_seconds < 1.0)
|
130
|
+
puts "Video is shorter than 1 sec"
|
131
|
+
else
|
132
|
+
initialize_filters
|
133
|
+
initialize_thumbnails
|
134
|
+
capture_thumbnails
|
135
|
+
|
136
|
+
puts "Composing standard contact sheet..." unless Configuration.instance.quiet?
|
137
|
+
montage = splice_montage(montage_thumbs)
|
138
|
+
|
139
|
+
image = MiniMagick::Image.open(montage)
|
140
|
+
|
141
|
+
puts "Adding header and footer..." unless Configuration.instance.quiet?
|
142
|
+
final = add_header_and_footer image
|
143
|
+
|
144
|
+
puts "Done. Output wrote to '#{filename}'" unless Configuration.instance.quiet?
|
145
|
+
FileUtils.mv(final, full_path)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
def self.finalize(tempdir)
|
151
|
+
proc do
|
152
|
+
puts "Cleaning up..." unless Configuration.instance.quiet?
|
153
|
+
FileUtils.rm_r tempdir
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def initialize_filters
|
158
|
+
@filters = []
|
159
|
+
@filters << :resize_filter
|
160
|
+
@filters << :softshadow_filter if softshadow
|
161
|
+
@filters << :timestamp_filter if timestamp
|
162
|
+
@filters << :polaroid_filter if polaroid
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize_thumbnails
|
166
|
+
@thumbnails = []
|
167
|
+
time = @from + (interval / 2)
|
168
|
+
(1..number_of_caps).each do |i|
|
169
|
+
thumb = Frame.new @video, @capturer, time
|
170
|
+
time = time + interval
|
171
|
+
thumb.width = thumbnail_width
|
172
|
+
thumb.height = thumbnail_height
|
173
|
+
thumb.filename = File::join(@tempdir, "th#{"%03d" % i}.#{@capturer.format_extension}")
|
174
|
+
thumb.filters.push(*@filters)
|
175
|
+
|
176
|
+
@thumbnails << thumb
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def capture_thumbnails
|
181
|
+
puts "Capturing in range [#{from}..#{to}]. Total length: #{@length}" unless Configuration.instance.quiet?
|
182
|
+
|
183
|
+
@thumbnails.each_with_index do |thumbnail, i|
|
184
|
+
puts "Generating capture ##{i + 1}/#{number_of_caps} #{thumbnail.time}..." unless Configuration.instance.quiet?
|
185
|
+
if Configuration.instance.blank_evasion?
|
186
|
+
thumbnail.capture_and_evade interval
|
187
|
+
else
|
188
|
+
thumbnail.capture
|
189
|
+
end
|
190
|
+
thumbnail.apply_filters
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def detect_video_properties
|
195
|
+
detect_length
|
196
|
+
detect_dimensions
|
197
|
+
end
|
198
|
+
|
199
|
+
def detect_length
|
200
|
+
@length = @video.info.duration
|
201
|
+
|
202
|
+
@from = TimeIndex.new 0.0
|
203
|
+
@to = @length
|
204
|
+
end
|
205
|
+
|
206
|
+
def detect_dimensions
|
207
|
+
@thumbnail_width = @video.video.width
|
208
|
+
@thumbnail_height = @video.video.height
|
209
|
+
|
210
|
+
@aspect_ratio = @video.video.aspect_ratio
|
211
|
+
|
212
|
+
if @aspect_ratio == 0 || @aspect_ratio == nil
|
213
|
+
@aspect_ratio = Rational(@video.video.width, @video.video.height)
|
214
|
+
else
|
215
|
+
#recalculate width, for PAR 1:1 this should be the same as before
|
216
|
+
@thumbnail_width = (@aspect_ratio * @video.video.height).to_i
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def montage_thumbs
|
221
|
+
file_path = File::join(@tempdir, 'montage.png')
|
222
|
+
MiniMagick::Tool::Montage.new do |montage|
|
223
|
+
montage.background Configuration.instance.contact_background
|
224
|
+
@thumbnails.each do |thumbnail|
|
225
|
+
montage << thumbnail.filename
|
226
|
+
end
|
227
|
+
montage.geometry "+#{Configuration.instance.padding}+#{Configuration.instance.padding}"
|
228
|
+
# rows or columns can be nil (auto fit)
|
229
|
+
montage.tile "#{@columns}x#{@rows}"
|
230
|
+
montage << file_path
|
231
|
+
end
|
232
|
+
return file_path
|
233
|
+
end
|
234
|
+
|
235
|
+
def splice_montage montage_path
|
236
|
+
if softshadow
|
237
|
+
left = Configuration.instance.padding + 3
|
238
|
+
top = Configuration.instance.padding + 5
|
239
|
+
bottom = right = Configuration.instance.padding
|
240
|
+
else
|
241
|
+
left = right = top = bottom = Configuration.instance.padding
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
file_path = File::join(@tempdir, 'spliced.png')
|
246
|
+
MiniMagick::Tool::Convert.new do |convert|
|
247
|
+
convert << montage_path
|
248
|
+
convert.background Configuration.instance.contact_background
|
249
|
+
|
250
|
+
convert.splice "#{left}x#{top}"
|
251
|
+
convert.gravity 'SouthEast'
|
252
|
+
convert.splice "#{right}x#{bottom}"
|
253
|
+
|
254
|
+
convert << file_path
|
255
|
+
end
|
256
|
+
file_path
|
257
|
+
end
|
258
|
+
|
259
|
+
def create_title montage
|
260
|
+
file_path = File::join(@tempdir, 'title.png')
|
261
|
+
MiniMagick::Tool::Convert.new do |convert|
|
262
|
+
convert.stack do |ul|
|
263
|
+
ul.size "#{montage.width}x#{Configuration.instance.title_font.line_height}"
|
264
|
+
ul.xc Configuration.instance.title_background
|
265
|
+
if Configuration.instance.title_font.exists?
|
266
|
+
ul.font Configuration.instance.title_font.path
|
267
|
+
end
|
268
|
+
ul.pointsize Configuration.instance.title_font.size
|
269
|
+
ul.background Configuration.instance.title_background
|
270
|
+
ul.fill Configuration.instance.title_color
|
271
|
+
ul.gravity 'Center'
|
272
|
+
ul.annotate(0, @title)
|
273
|
+
end
|
274
|
+
convert.flatten
|
275
|
+
convert << file_path
|
276
|
+
end
|
277
|
+
return file_path
|
278
|
+
end
|
279
|
+
|
280
|
+
def create_highlight montage
|
281
|
+
puts "Generating highlight..."
|
282
|
+
thumb = Frame.new @video, @highlight
|
283
|
+
|
284
|
+
thumb.width = thumbnail_width
|
285
|
+
thumb.height = thumbnail_height
|
286
|
+
thumb.filename = File::join(@tempdir, "highlight_thumb.png")
|
287
|
+
thumb.capture
|
288
|
+
thumb.apply_filters
|
289
|
+
|
290
|
+
file_path = File::join(@tempdir, "highlight.png")
|
291
|
+
MiniMagick::Tool::Convert.new do |convert|
|
292
|
+
convert.stack do |a|
|
293
|
+
a.size "#{montage.width}x#{thumbnail_height+20}"
|
294
|
+
a.xc Configuration.instance.highlight_background
|
295
|
+
a.gravity 'Center'
|
296
|
+
a << thumb.filename
|
297
|
+
a.composite
|
298
|
+
end
|
299
|
+
convert.stack do |a|
|
300
|
+
a.size "#{montage.width}x1"
|
301
|
+
a.xc 'Black'
|
302
|
+
end
|
303
|
+
convert.append
|
304
|
+
convert << file_path
|
305
|
+
end
|
306
|
+
|
307
|
+
file_path
|
308
|
+
end
|
309
|
+
|
310
|
+
def add_header_and_footer montage
|
311
|
+
file_path = File::join(@tempdir, filename)
|
312
|
+
header_height = Configuration.instance.header_font.line_height * 3
|
313
|
+
signature_height = Configuration.instance.signature_font.line_height + 8
|
314
|
+
MiniMagick::Tool::Convert.new do |convert|
|
315
|
+
convert.stack do |a|
|
316
|
+
a.size "#{montage.width - 18}x1"
|
317
|
+
a.xc Configuration.instance.header_background
|
318
|
+
a.size.+
|
319
|
+
if Configuration.instance.header_font.exists?
|
320
|
+
a.font Configuration.instance.header_font.path
|
321
|
+
end
|
322
|
+
a.pointsize Configuration.instance.header_font.size
|
323
|
+
a.background Configuration.instance.header_background
|
324
|
+
a.fill Configuration.instance.header_color
|
325
|
+
a.stack do |b|
|
326
|
+
b.gravity 'West'
|
327
|
+
b.stack do |c|
|
328
|
+
c.label 'Filename: '
|
329
|
+
if Configuration.instance.header_font.exists?
|
330
|
+
c.font Configuration.instance.header_font.path
|
331
|
+
end
|
332
|
+
c.label File.basename(@video.full_path)
|
333
|
+
c.append.+
|
334
|
+
end
|
335
|
+
if Configuration.instance.header_font.exists?
|
336
|
+
b.font Configuration.instance.header_font.path
|
337
|
+
end
|
338
|
+
b.label "File size: #{Tools.to_human_size(File.size(@video.full_path))}"
|
339
|
+
b.label "Length: #{@length.to_timestamp}"
|
340
|
+
b.append
|
341
|
+
b.crop "#{montage.width}x#{header_height}+0+0"
|
342
|
+
end
|
343
|
+
a.append
|
344
|
+
a.stack do |b|
|
345
|
+
b.size "#{montage.width}x#{header_height}"
|
346
|
+
b.gravity 'East'
|
347
|
+
b.fill Configuration.instance.header_color
|
348
|
+
b.annotate '+0-1'
|
349
|
+
b << "Dimensions: #{@video.video.width}x#{@video.video.height}\nFormat: #{@video.video.codec(true)} / #{@video.audio ? @video.audio.codec(true) : 'no audio'}\nFPS: #{"%.02f" % @video.video.frame_rate.to_f}"
|
350
|
+
end
|
351
|
+
a.bordercolor Configuration.instance.header_background
|
352
|
+
a.border 9
|
353
|
+
end
|
354
|
+
convert << create_title(montage) if @title
|
355
|
+
convert << create_highlight(montage) if @highlight
|
356
|
+
convert << montage.path
|
357
|
+
convert.append
|
358
|
+
if @signature && @signature.length > 0
|
359
|
+
convert.stack do |a|
|
360
|
+
a.size "#{montage.width}x#{signature_height}"
|
361
|
+
a.gravity 'Center'
|
362
|
+
a.xc Configuration.instance.signature_background
|
363
|
+
if Configuration.instance.signature_font.exists?
|
364
|
+
a.font Configuration.instance.signature_font.path
|
365
|
+
end
|
366
|
+
a.pointsize Configuration.instance.signature_font.size
|
367
|
+
a.fill Configuration.instance.signature_color
|
368
|
+
a.annotate(0, @signature)
|
369
|
+
end
|
370
|
+
convert.append
|
371
|
+
end
|
372
|
+
if @format == :jpg || @format == :jpeg
|
373
|
+
convert.quality(Configuration.instance.quality)
|
374
|
+
end
|
375
|
+
convert << file_path
|
376
|
+
end
|
377
|
+
file_path
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|