ffmprb 0.7.5 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +2 -2
- data/README.md +121 -15
- data/ffmprb.gemspec +2 -1
- data/lib/defaults.rb +5 -1
- data/lib/ffmprb/file.rb +2 -4
- data/lib/ffmprb/filter.rb +97 -65
- data/lib/ffmprb/process/input/chain_base.rb +29 -0
- data/lib/ffmprb/process/input/channeled.rb +40 -0
- data/lib/ffmprb/process/input/cropped.rb +70 -0
- data/lib/ffmprb/process/input/cut.rb +66 -0
- data/lib/ffmprb/process/input/looping.rb +102 -0
- data/lib/ffmprb/process/input/loud.rb +42 -0
- data/lib/ffmprb/process/input/temp.rb +26 -0
- data/lib/ffmprb/process/input.rb +39 -180
- data/lib/ffmprb/process/output.rb +140 -119
- data/lib/ffmprb/process.rb +68 -27
- data/lib/ffmprb/util/synchro.rb +1 -1
- data/lib/ffmprb/util/thread.rb +2 -2
- data/lib/ffmprb/util/threaded_io_buffer.rb +48 -36
- data/lib/ffmprb/util.rb +12 -5
- data/lib/ffmprb/version.rb +1 -1
- data/lib/ffmprb.rb +6 -6
- metadata +9 -2
@@ -1,24 +1,45 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
1
3
|
module Ffmprb
|
2
4
|
|
3
5
|
class Process
|
4
6
|
|
5
7
|
class Output
|
6
8
|
|
7
|
-
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def audio_cmd_options(audio=Process.output_audio_options)
|
12
|
+
audio ||= {}
|
13
|
+
[].tap do |options|
|
14
|
+
options.concat %W[-c:a #{audio[:encoder]}] if audio[:encoder]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO unique labeling does not justify idx, will be refactored
|
21
|
+
def initialize(io, idx, video:, audio:)
|
8
22
|
@io = resolve(io)
|
9
|
-
@
|
10
|
-
@channels =
|
11
|
-
|
12
|
-
|
23
|
+
@idx = idx
|
24
|
+
@channels = {
|
25
|
+
video: video && @io.channel?(:video) && OpenStruct.new(video),
|
26
|
+
audio: audio && @io.channel?(:audio) && OpenStruct.new(audio)
|
27
|
+
}
|
28
|
+
if channel?(:video)
|
29
|
+
channel(:video).resolution.to_s.split('x').each do |dim|
|
30
|
+
fail Error, "Both dimensions of a resolution must be divisible by 2, sorry about that" unless dim.to_i % 2 == 0
|
31
|
+
end
|
32
|
+
end
|
13
33
|
end
|
14
34
|
|
15
35
|
# XXX This method is exceptionally long at the moment. This is not too grand.
|
16
36
|
# However, structuring the code should be undertaken with care, as not to harm the composition clarity.
|
17
|
-
def
|
37
|
+
def filters
|
18
38
|
fail Error, "Nothing to roll..." unless @reels
|
19
39
|
fail Error, "Supporting just full_screen for now, sorry." unless @reels.all?(&:full_screen?)
|
40
|
+
return @filters if @filters
|
20
41
|
|
21
|
-
filters = []
|
42
|
+
@filters = []
|
22
43
|
|
23
44
|
# Concatting
|
24
45
|
segments = []
|
@@ -31,24 +52,18 @@ module Ffmprb
|
|
31
52
|
|
32
53
|
# NOTE mapping input to this lbl
|
33
54
|
|
34
|
-
lbl = "rl#{i}"
|
35
|
-
lbl_aux = "sp#{i}"
|
55
|
+
lbl = "o#{@idx}rl#{i}"
|
36
56
|
|
37
|
-
# NOTE Image-
|
38
|
-
#
|
57
|
+
# NOTE Image-Padding to match the target resolution
|
58
|
+
# TODO full screen only at the moment (see exception above)
|
39
59
|
|
40
|
-
filters.concat(
|
41
|
-
curr_reel.reel.filters_for
|
60
|
+
@filters.concat(
|
61
|
+
curr_reel.reel.filters_for lbl, video: channel(:video), audio: channel(:audio)
|
42
62
|
)
|
43
|
-
filters.concat(
|
44
|
-
Filter.scale_pad_fps target_width, target_height, target_fps, "#{lbl_aux}:v", "#{lbl}:v"
|
45
|
-
) if channel?(:video)
|
46
|
-
filters.concat(
|
47
|
-
Filter.anull "#{lbl_aux}:a", "#{lbl}:a"
|
48
|
-
) if channel?(:audio)
|
49
63
|
end
|
50
64
|
|
51
65
|
trim_prev_at = curr_reel.after || (curr_reel.transition && 0)
|
66
|
+
transition_length = curr_reel.transition ? curr_reel.transition.length : 0
|
52
67
|
|
53
68
|
if trim_prev_at
|
54
69
|
|
@@ -58,20 +73,21 @@ module Ffmprb
|
|
58
73
|
|
59
74
|
lbl_pad = "bl#{prev_lbl}#{i}"
|
60
75
|
# NOTE generously padding the previous segment to support for all the cases
|
61
|
-
filters.concat(
|
62
|
-
Filter.blank_source trim_prev_at +
|
76
|
+
@filters.concat(
|
77
|
+
Filter.blank_source trim_prev_at + transition_length,
|
78
|
+
channel(:video).resolution, channel(:video).fps, "#{lbl_pad}:v"
|
63
79
|
) if channel?(:video)
|
64
|
-
filters.concat(
|
65
|
-
Filter.silent_source trim_prev_at +
|
80
|
+
@filters.concat(
|
81
|
+
Filter.silent_source trim_prev_at + transition_length, "#{lbl_pad}:a"
|
66
82
|
) if channel?(:audio)
|
67
83
|
|
68
84
|
if prev_lbl
|
69
85
|
lbl_aux = lbl_pad
|
70
86
|
lbl_pad = "pd#{prev_lbl}#{i}"
|
71
|
-
filters.concat(
|
87
|
+
@filters.concat(
|
72
88
|
Filter.concat_v ["#{prev_lbl}:v", "#{lbl_aux}:v"], "#{lbl_pad}:v"
|
73
89
|
) if channel?(:video)
|
74
|
-
filters.concat(
|
90
|
+
@filters.concat(
|
75
91
|
Filter.concat_a ["#{prev_lbl}:a", "#{lbl_aux}:a"], "#{lbl_pad}:a"
|
76
92
|
) if channel?(:audio)
|
77
93
|
end
|
@@ -81,10 +97,10 @@ module Ffmprb
|
|
81
97
|
# NOTE Split the previous segment for transition
|
82
98
|
|
83
99
|
if trim_prev_at > 0
|
84
|
-
filters.concat(
|
100
|
+
@filters.concat(
|
85
101
|
Filter.split "#{lbl_pad}:v", ["#{lbl_pad}a:v", "#{lbl_pad}b:v"]
|
86
102
|
) if channel?(:video)
|
87
|
-
filters.concat(
|
103
|
+
@filters.concat(
|
88
104
|
Filter.asplit "#{lbl_pad}:a", ["#{lbl_pad}a:a", "#{lbl_pad}b:a"]
|
89
105
|
) if channel?(:audio)
|
90
106
|
lbl_pad, lbl_pad_ = "#{lbl_pad}a", "#{lbl_pad}b"
|
@@ -99,10 +115,10 @@ module Ffmprb
|
|
99
115
|
|
100
116
|
new_prev_lbl = "tm#{prev_lbl}#{i}a"
|
101
117
|
|
102
|
-
filters.concat(
|
118
|
+
@filters.concat(
|
103
119
|
Filter.trim 0, trim_prev_at, "#{lbl_pad}:v", "#{new_prev_lbl}:v"
|
104
120
|
) if channel?(:video)
|
105
|
-
filters.concat(
|
121
|
+
@filters.concat(
|
106
122
|
Filter.atrim 0, trim_prev_at, "#{lbl_pad}:a", "#{new_prev_lbl}:a"
|
107
123
|
) if channel?(:audio)
|
108
124
|
|
@@ -114,27 +130,34 @@ module Ffmprb
|
|
114
130
|
|
115
131
|
# NOTE snip the end of the previous segment and combine with this reel
|
116
132
|
|
117
|
-
lbl_end1 = "tm#{i}b"
|
118
|
-
lbl_reel = "tn#{i}"
|
133
|
+
lbl_end1 = "o#{@idx}tm#{i}b"
|
134
|
+
lbl_reel = "o#{@idx}tn#{i}"
|
135
|
+
|
119
136
|
if !lbl # no reel
|
120
|
-
lbl_aux = "bk#{i}"
|
121
|
-
filters.concat(
|
122
|
-
Filter.blank_source
|
137
|
+
lbl_aux = "o#{@idx}bk#{i}"
|
138
|
+
@filters.concat(
|
139
|
+
Filter.blank_source transition_length, channel(:video).resolution, channel(:video).fps, "#{lbl_aux}:v"
|
123
140
|
) if channel?(:video)
|
124
|
-
filters.concat(
|
125
|
-
Filter.silent_source
|
141
|
+
@filters.concat(
|
142
|
+
Filter.silent_source transition_length, "#{lbl_aux}:a"
|
126
143
|
) if channel?(:audio)
|
127
144
|
end # NOTE else hope lbl is long enough for the transition
|
128
|
-
|
129
|
-
|
145
|
+
|
146
|
+
@filters.concat(
|
147
|
+
Filter.trim trim_prev_at, trim_prev_at + transition_length, "#{lbl_pad_}:v", "#{lbl_end1}:v"
|
130
148
|
) if channel?(:video)
|
131
|
-
filters.concat(
|
132
|
-
Filter.atrim trim_prev_at, trim_prev_at +
|
149
|
+
@filters.concat(
|
150
|
+
Filter.atrim trim_prev_at, trim_prev_at + transition_length, "#{lbl_pad_}:a", "#{lbl_end1}:a"
|
133
151
|
) if channel?(:audio)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
152
|
+
|
153
|
+
# TODO the only supported transition, see #*lay
|
154
|
+
@filters.concat(
|
155
|
+
Filter.blend_v transition_length, channel(:video).resolution, channel(:video).fps, ["#{lbl_end1}:v", "#{lbl || lbl_aux}:v"], "#{lbl_reel}:v"
|
156
|
+
) if channel?(:video)
|
157
|
+
@filters.concat(
|
158
|
+
Filter.blend_a transition_length, ["#{lbl_end1}:a", "#{lbl || lbl_aux}:a"], "#{lbl_reel}:a"
|
159
|
+
) if channel?(:audio)
|
160
|
+
|
138
161
|
lbl = lbl_reel
|
139
162
|
end
|
140
163
|
|
@@ -145,12 +168,12 @@ module Ffmprb
|
|
145
168
|
|
146
169
|
segments.compact!
|
147
170
|
|
148
|
-
lbl_out =
|
171
|
+
lbl_out = "o#{@idx}o"
|
149
172
|
|
150
|
-
filters.concat(
|
173
|
+
@filters.concat(
|
151
174
|
Filter.concat_v segments.map{|s| "#{s}:v"}, "#{lbl_out}:v"
|
152
175
|
) if channel?(:video)
|
153
|
-
filters.concat(
|
176
|
+
@filters.concat(
|
154
177
|
Filter.concat_a segments.map{|s| "#{s}:a"}, "#{lbl_out}:a"
|
155
178
|
) if channel?(:audio)
|
156
179
|
|
@@ -159,22 +182,22 @@ module Ffmprb
|
|
159
182
|
# NOTE in-process overlays first
|
160
183
|
|
161
184
|
@overlays.to_a.each_with_index do |over_reel, i|
|
162
|
-
next if over_reel.duck #
|
185
|
+
next if over_reel.duck # NOTE this is currently a single case of multi-process... process
|
163
186
|
|
164
187
|
fail Error, "Video overlays are not implemented just yet, sorry..." if over_reel.reel.channel?(:video)
|
165
188
|
|
166
189
|
# Audio overlaying
|
167
190
|
|
168
|
-
lbl_nxt = "
|
191
|
+
lbl_nxt = "o#{@idx}o#{i}"
|
169
192
|
|
170
|
-
lbl_over = "
|
171
|
-
filters.concat( # NOTE audio only, see above
|
172
|
-
over_reel.reel.filters_for lbl_over,
|
193
|
+
lbl_over = "o#{@idx}l#{i}"
|
194
|
+
@filters.concat( # NOTE audio only, see above
|
195
|
+
over_reel.reel.filters_for lbl_over, video: false, audio: channel(:audio)
|
173
196
|
)
|
174
|
-
filters.concat(
|
197
|
+
@filters.concat(
|
175
198
|
Filter.copy "#{lbl_out}:v", "#{lbl_nxt}:v"
|
176
199
|
) if channel?(:video)
|
177
|
-
filters.concat(
|
200
|
+
@filters.concat(
|
178
201
|
Filter.amix_to_first_same_volume ["#{lbl_out}:a", "#{lbl_over}:a"], "#{lbl_nxt}:a"
|
179
202
|
) if channel?(:audio)
|
180
203
|
|
@@ -183,67 +206,77 @@ module Ffmprb
|
|
183
206
|
|
184
207
|
# NOTE multi-process overlays last
|
185
208
|
|
186
|
-
channel_lbl_ios = {} # XXX this is a spaghetti machine
|
187
|
-
channel_lbl_ios["#{lbl_out}:v"] = @io if channel?(:video)
|
188
|
-
channel_lbl_ios["#{lbl_out}:a"] = @io if channel?(:audio)
|
209
|
+
@channel_lbl_ios = {} # XXX this is a spaghetti machine
|
210
|
+
@channel_lbl_ios["#{lbl_out}:v"] = @io if channel?(:video)
|
211
|
+
@channel_lbl_ios["#{lbl_out}:a"] = @io if channel?(:audio)
|
189
212
|
|
190
|
-
#
|
213
|
+
# TODO supporting just "full" overlays for now, see exception in #add_reel
|
191
214
|
@overlays.to_a.each_with_index do |over_reel, i|
|
192
215
|
|
193
|
-
#
|
216
|
+
# NOTE this is currently a single case of multi-process... process
|
194
217
|
if over_reel.duck
|
195
218
|
fail Error, "Don't know how to duck video... yet" if over_reel.duck != :audio
|
196
219
|
|
197
220
|
# So ducking just audio here, ye?
|
221
|
+
# XXX check if we're on audio channel
|
198
222
|
|
199
|
-
|
200
|
-
fail Error, "Main output does not contain audio to duck" unless
|
223
|
+
main_av_o = @channel_lbl_ios["#{lbl_out}:a"]
|
224
|
+
fail Error, "Main output does not contain audio to duck" unless main_av_o
|
201
225
|
# XXX#181845 must really seperate channels for streaming (e.g. mp4 wouldn't stream through the fifo)
|
202
|
-
|
203
|
-
|
204
|
-
|
226
|
+
# NOTE what really must be done here (optimisation & compatibility):
|
227
|
+
# - output v&a through non-compressed pipes
|
228
|
+
# - v-output will be input to the new v+a merging+encoding process
|
229
|
+
# - a-output will go through the ducking process below and its output will be input to the m+e process above
|
230
|
+
# - v-output will have to use another thread-buffered pipe
|
231
|
+
main_av_inter_o = File.temp_fifo(main_av_o.extname)
|
232
|
+
@channel_lbl_ios.each do |channel_lbl, io|
|
233
|
+
@channel_lbl_ios[channel_lbl] = main_av_inter_o if io == main_av_o # XXX ~~~spaghetti
|
205
234
|
end
|
206
|
-
Ffmprb.logger.debug "Re-routed the main audio output (#{
|
235
|
+
Ffmprb.logger.debug "Re-routed the main audio output (#{main_av_inter_o.path}->...->#{main_av_o.path}) through the process of audio ducking"
|
207
236
|
|
208
|
-
|
209
|
-
lbl_over = "
|
210
|
-
filters.concat(
|
211
|
-
over_reel.reel.filters_for lbl_over,
|
237
|
+
over_a_i, over_a_o = File.threaded_buffered_fifo(Process.intermediate_channel_extname :audio)
|
238
|
+
lbl_over = "o#{@idx}l#{i}"
|
239
|
+
@filters.concat(
|
240
|
+
over_reel.reel.filters_for lbl_over, video: false, audio: channel(:audio)
|
212
241
|
)
|
213
|
-
channel_lbl_ios["#{lbl_over}:a"] =
|
214
|
-
Ffmprb.logger.debug "Routed and buffering an auxiliary output fifos (#{
|
242
|
+
@channel_lbl_ios["#{lbl_over}:a"] = over_a_i
|
243
|
+
Ffmprb.logger.debug "Routed and buffering an auxiliary output fifos (#{over_a_i.path}>#{over_a_o.path}) for overlay"
|
215
244
|
|
216
|
-
inter_i, inter_o = File.threaded_buffered_fifo(
|
245
|
+
inter_i, inter_o = File.threaded_buffered_fifo(main_av_inter_o.extname)
|
217
246
|
Ffmprb.logger.debug "Allocated fifos to buffer media (#{inter_i.path}>#{inter_o.path}) while finding silence"
|
218
247
|
|
219
248
|
Util::Thread.new "audio ducking" do
|
220
|
-
silence = Ffmprb.find_silence(
|
249
|
+
silence = Ffmprb.find_silence(main_av_inter_o, inter_i)
|
221
250
|
|
222
251
|
Ffmprb.logger.debug "Audio ducking with silence: [#{silence.map{|s| "#{s.start_at}-#{s.end_at}"}.join ', '}]"
|
223
252
|
|
224
|
-
Process.duck_audio inter_o,
|
225
|
-
video: (channel?(:video)? {resolution: target_resolution, fps: target_fps}: false)
|
253
|
+
Process.duck_audio inter_o, over_a_o, silence, main_av_o, video: channel(:video), audio: channel(:audio)
|
226
254
|
end
|
227
255
|
end
|
228
256
|
|
229
257
|
end
|
230
258
|
|
231
|
-
|
259
|
+
@filters
|
260
|
+
end
|
232
261
|
|
233
|
-
|
234
|
-
|
235
|
-
(io_channel_lbls[io] ||= []) << channel_lbl
|
236
|
-
end
|
237
|
-
io_channel_lbls.each do |io, channel_lbls|
|
238
|
-
channel_lbls.each do |channel_lbl|
|
239
|
-
options << '-map' << "[#{channel_lbl}]"
|
240
|
-
# XXX temporary patchwork
|
241
|
-
options << '-c:a' << 'libmp3lame' if channel_lbls.size > 1 && channel_lbl =~ /:a$/
|
242
|
-
end
|
243
|
-
options << io.path
|
244
|
-
end
|
262
|
+
def options
|
263
|
+
fail Error, "Must generate filters first." unless @channel_lbl_ios
|
245
264
|
|
265
|
+
options = []
|
266
|
+
|
267
|
+
io_channel_lbls = {} # XXX ~~~spaghetti
|
268
|
+
@channel_lbl_ios.each do |channel_lbl, io|
|
269
|
+
(io_channel_lbls[io] ||= []) << channel_lbl
|
270
|
+
end
|
271
|
+
io_channel_lbls.each do |io, channel_lbls|
|
272
|
+
channel_lbls.each do |channel_lbl|
|
273
|
+
options << '-map' << "[#{channel_lbl}]"
|
274
|
+
end
|
275
|
+
options.concat self.class.audio_cmd_options(channel :audio) if channel? :audio
|
276
|
+
options << io.path
|
246
277
|
end
|
278
|
+
|
279
|
+
options
|
247
280
|
end
|
248
281
|
|
249
282
|
def roll(
|
@@ -263,29 +296,24 @@ module Ffmprb
|
|
263
296
|
def overlay(
|
264
297
|
reel,
|
265
298
|
at: 0,
|
266
|
-
transition: nil,
|
267
299
|
duck: nil
|
268
300
|
)
|
269
301
|
fail Error, "Nothing to overlay..." unless reel
|
270
302
|
fail Error, "Nothing to lay over yet..." if @reels.to_a.empty?
|
271
303
|
fail Error, "Ducking overlays should come last... for now" if !duck && @overlays.to_a.last && @overlays.to_a.last.duck
|
272
304
|
|
273
|
-
|
274
|
-
OpenStruct.new(reel: reel, at: at, duck: duck)
|
305
|
+
add_snip reel, at, duck
|
275
306
|
end
|
276
307
|
|
277
|
-
def channel
|
278
|
-
@channels
|
308
|
+
def channel(medium)
|
309
|
+
@channels[medium]
|
279
310
|
end
|
280
311
|
|
281
|
-
def channel?(medium
|
282
|
-
|
283
|
-
|
284
|
-
(!@channels || @channels.include?(medium)) && @io.channel?(medium) &&
|
285
|
-
reels_channel?(medium)
|
312
|
+
def channel?(medium)
|
313
|
+
!!channel(medium)
|
286
314
|
end
|
287
315
|
|
288
|
-
|
316
|
+
protected
|
289
317
|
|
290
318
|
def resolve(io)
|
291
319
|
return io unless io.is_a? String
|
@@ -300,7 +328,7 @@ module Ffmprb
|
|
300
328
|
end
|
301
329
|
end
|
302
330
|
|
303
|
-
|
331
|
+
private
|
304
332
|
|
305
333
|
def reels_channel?(medium)
|
306
334
|
@reels.to_a.all?{|r| !r.reel || r.reel.channel?(medium)}
|
@@ -310,29 +338,22 @@ module Ffmprb
|
|
310
338
|
fail Error, "No time to roll..." if after && after.to_f <= 0
|
311
339
|
fail Error, "Partial (not coming last in process) overlays are currently unsupported, sorry." unless @overlays.to_a.empty?
|
312
340
|
|
313
|
-
# NOTE limited functionality
|
314
|
-
|
341
|
+
# NOTE limited functionality: transition = {effect => duration}
|
342
|
+
# TODO temporary obviously, see rendering
|
343
|
+
trans =
|
344
|
+
if transition
|
345
|
+
fail "Unsupported (yet) transition, sorry." unless
|
346
|
+
transition.size == 1 && transition[:blend]
|
347
|
+
OpenStruct.new length: transition[:blend].to_f
|
348
|
+
end
|
315
349
|
|
316
350
|
(@reels ||= []) <<
|
317
|
-
OpenStruct.new(reel: reel, after: after, transition:
|
351
|
+
OpenStruct.new(reel: reel, after: after, transition: trans, full_screen?: full_screen)
|
318
352
|
end
|
319
353
|
|
320
|
-
def
|
321
|
-
@
|
322
|
-
|
323
|
-
end
|
324
|
-
end
|
325
|
-
def target_height
|
326
|
-
@target_height ||= @resolution.to_s.split('x')[1].to_i.tap do |height|
|
327
|
-
raise Error, "Height (#{height}) must be divisible by 2, sorry" unless height % 2 == 0
|
328
|
-
end
|
329
|
-
end
|
330
|
-
def target_resolution
|
331
|
-
"#{target_width}x#{target_height}"
|
332
|
-
end
|
333
|
-
|
334
|
-
def target_fps
|
335
|
-
@fps
|
354
|
+
def add_snip(reel, at, duck)
|
355
|
+
(@overlays ||= []) <<
|
356
|
+
OpenStruct.new(reel: reel, at: at, duck: duck)
|
336
357
|
end
|
337
358
|
|
338
359
|
end
|
data/lib/ffmprb/process.rb
CHANGED
@@ -9,6 +9,10 @@ module Ffmprb
|
|
9
9
|
attr_accessor :duck_audio_transition_length,
|
10
10
|
:duck_audio_transition_in_start, :duck_audio_transition_out_start
|
11
11
|
|
12
|
+
attr_accessor :output_video_resolution
|
13
|
+
attr_accessor :output_video_fps
|
14
|
+
attr_accessor :output_audio_encoder
|
15
|
+
|
12
16
|
attr_accessor :timeout
|
13
17
|
|
14
18
|
def intermediate_channel_extname(*media)
|
@@ -23,20 +27,35 @@ module Ffmprb
|
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
30
|
+
def output_video_options
|
31
|
+
{
|
32
|
+
resolution: output_video_resolution,
|
33
|
+
fps: output_video_fps
|
34
|
+
}
|
35
|
+
end
|
36
|
+
def output_audio_options
|
37
|
+
{
|
38
|
+
encoder: output_audio_encoder
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# NOTE Temporarily, av_main_i/o and not a_main_i/o
|
26
43
|
def duck_audio(av_main_i, a_overlay_i, silence, av_main_o,
|
27
|
-
volume_lo: duck_audio_volume_lo,
|
44
|
+
volume_lo: duck_audio_volume_lo,
|
45
|
+
volume_hi: duck_audio_volume_hi,
|
28
46
|
silent_min: duck_audio_silent_min,
|
29
|
-
video
|
47
|
+
video:, # NOTE Temporarily, video should not be here
|
48
|
+
audio:
|
30
49
|
)
|
31
|
-
Ffmprb.process
|
50
|
+
Ffmprb.process do
|
32
51
|
|
33
|
-
in_main = input(
|
34
|
-
in_over = input(
|
35
|
-
output(
|
52
|
+
in_main = input(av_main_i)
|
53
|
+
in_over = input(a_overlay_i)
|
54
|
+
output(av_main_o, video: video, audio: audio) do
|
36
55
|
roll in_main
|
37
56
|
|
38
57
|
ducked_overlay_volume = {0.0 => volume_lo}
|
39
|
-
|
58
|
+
silence.each do |silent|
|
40
59
|
next if silent.end_at && silent.start_at && (silent.end_at - silent.start_at) < silent_min
|
41
60
|
|
42
61
|
transition_in_start = silent.start_at + Process.duck_audio_transition_in_start
|
@@ -63,57 +82,79 @@ module Ffmprb
|
|
63
82
|
|
64
83
|
attr_reader :timeout
|
65
84
|
|
66
|
-
def initialize(*args, **opts
|
67
|
-
@inputs = []
|
68
|
-
@timeout = opts
|
85
|
+
def initialize(*args, **opts)
|
86
|
+
@inputs, @outputs = [], []
|
87
|
+
@timeout = opts.delete(:timeout) || self.class.timeout
|
88
|
+
|
89
|
+
@ignore_broken_pipe = opts.delete(:ignore_broken_pipe) # XXX SPEC ME
|
90
|
+
fail Error, "Unknown options: #{opts}" unless opts.empty?
|
69
91
|
end
|
70
92
|
|
71
|
-
def input(io
|
72
|
-
Input.new(io,
|
93
|
+
def input(io)
|
94
|
+
Input.new(io, self).tap do |inp|
|
73
95
|
@inputs << inp
|
74
96
|
end
|
75
97
|
end
|
76
98
|
|
77
|
-
def
|
78
|
-
|
99
|
+
def temp_input(extname) # XXX SPEC ME
|
100
|
+
input(nil).tap do |inp|
|
101
|
+
inp.temporise! extname
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def input_label(input)
|
106
|
+
@inputs.index input
|
107
|
+
end
|
79
108
|
|
80
|
-
|
81
|
-
|
109
|
+
def output(io, video: true, audio: true, &blk)
|
110
|
+
Output.new(io, @outputs.size,
|
111
|
+
video: channel_params(video, self.class.output_video_options),
|
112
|
+
audio: channel_params(audio, self.class.output_audio_options)
|
113
|
+
).tap do |out|
|
114
|
+
@outputs << out
|
115
|
+
out.instance_exec &blk if blk
|
82
116
|
end
|
83
117
|
end
|
84
118
|
|
85
119
|
# NOTE the one and the only entry-point processing function which spawns threads etc
|
86
|
-
def run(limit: nil) # (async: false)
|
120
|
+
def run(limit: nil) # TODO (async: false)
|
87
121
|
# NOTE this is both for the future async: option and according to
|
88
122
|
# the threading policy (a parent death will be noticed and handled by children)
|
89
123
|
thr = Util::Thread.new do
|
90
124
|
# NOTE yes, an exception can occur anytime, and we'll just die, it's ok, see above
|
91
|
-
|
125
|
+
# XXX just to return something -- no apparent practical use
|
126
|
+
cmd = command
|
127
|
+
Util.ffmpeg(*cmd, limit: limit, timeout: timeout, ignore_broken_pipe: @ignore_broken_pipe).tap do |res|
|
92
128
|
Util::Thread.join_children! limit, timeout: timeout
|
93
129
|
end
|
94
130
|
end
|
95
131
|
thr.value if thr.join limit # NOTE should not block for more than limit
|
96
132
|
end
|
97
133
|
|
98
|
-
def [](obj)
|
99
|
-
case obj
|
100
|
-
when Input
|
101
|
-
@inputs.find_index(obj)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
134
|
private
|
106
135
|
|
107
136
|
def command
|
108
|
-
input_options + output_options
|
137
|
+
input_options + filter_options + output_options
|
109
138
|
end
|
110
139
|
|
111
140
|
def input_options
|
112
141
|
@inputs.map(&:options).flatten(1)
|
113
142
|
end
|
114
143
|
|
144
|
+
def filter_options
|
145
|
+
Filter.complex_options @outputs.map(&:filters).reduce(:+)
|
146
|
+
end
|
147
|
+
|
115
148
|
def output_options
|
116
|
-
@
|
149
|
+
@outputs.map(&:options).flatten(1)
|
150
|
+
end
|
151
|
+
|
152
|
+
def channel_params(value, default)
|
153
|
+
if value
|
154
|
+
default.merge(value == true ? {} : value.to_h)
|
155
|
+
elsif value != false
|
156
|
+
{}
|
157
|
+
end
|
117
158
|
end
|
118
159
|
|
119
160
|
end
|
data/lib/ffmprb/util/synchro.rb
CHANGED
data/lib/ffmprb/util/thread.rb
CHANGED
@@ -5,7 +5,7 @@ module Ffmprb
|
|
5
5
|
# NOTE doesn't have specs (and not too proud about it)
|
6
6
|
class Thread < ::Thread
|
7
7
|
|
8
|
-
class Error <
|
8
|
+
class Error < Ffmprb::Error; end
|
9
9
|
class ParentError < Error; end
|
10
10
|
|
11
11
|
class << self
|
@@ -70,7 +70,7 @@ module Ffmprb
|
|
70
70
|
sync_q.deq
|
71
71
|
end
|
72
72
|
|
73
|
-
#
|
73
|
+
# TODO protected: none of these methods should be called by a user code, the only public methods are above
|
74
74
|
|
75
75
|
def live!
|
76
76
|
fail ParentError if @parent.status.nil?
|