ffmprb 0.7.5 → 0.9.0
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.
- 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?
|