vcs_ruby 1.1.9 → 1.1.10
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.
- 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
|