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/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