ffmprb 0.9.6 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -1
- data/Guardfile +12 -0
- data/README.md +25 -22
- data/ffmprb.gemspec +13 -9
- data/lib/defaults.rb +23 -3
- data/lib/ffmprb.rb +13 -12
- data/lib/ffmprb/execution.rb +1 -1
- data/lib/ffmprb/file.rb +54 -48
- data/lib/ffmprb/file/sample.rb +4 -4
- data/lib/ffmprb/file/threaded_buffered.rb +48 -0
- data/lib/ffmprb/filter.rb +9 -7
- data/lib/ffmprb/find_silence.rb +7 -5
- data/lib/ffmprb/process.rb +67 -39
- data/lib/ffmprb/process/input.rb +37 -18
- data/lib/ffmprb/process/input/looping.rb +83 -46
- data/lib/ffmprb/process/input/temp.rb +5 -12
- data/lib/ffmprb/process/output.rb +68 -64
- data/lib/ffmprb/util.rb +32 -18
- data/lib/ffmprb/util/proc_vis.rb +163 -0
- data/lib/ffmprb/util/thread.rb +20 -12
- data/lib/ffmprb/util/threaded_io_buffer.rb +186 -74
- data/lib/ffmprb/version.rb +9 -1
- metadata +55 -24
data/lib/ffmprb/file/sample.rb
CHANGED
@@ -18,7 +18,7 @@ module Ffmprb
|
|
18
18
|
fail Error, "Can sample either video OR audio UNLESS a block is given" unless block_given? || !!audio != !!video
|
19
19
|
|
20
20
|
cmd = %W[-i #{path}]
|
21
|
-
cmd.concat %W[-deinterlace -an -ss #{at} -
|
21
|
+
cmd.concat %W[-deinterlace -an -ss #{at} -vframes 1 #{video.path}] if video
|
22
22
|
cmd.concat %W[-vn -ss #{at} -t 1 #{audio.path}] if audio
|
23
23
|
Util.ffmpeg *cmd
|
24
24
|
|
@@ -28,11 +28,11 @@ module Ffmprb
|
|
28
28
|
yield *[video || nil, audio || nil].compact
|
29
29
|
ensure
|
30
30
|
begin
|
31
|
-
video.
|
32
|
-
audio.
|
31
|
+
video.unlink if video
|
32
|
+
audio.unlink if audio
|
33
33
|
Ffmprb.logger.debug "Removed sample files"
|
34
34
|
rescue
|
35
|
-
Ffmprb.logger.warn "
|
35
|
+
Ffmprb.logger.warn "#{$!.class.name} removing sample files: #{$!.message}"
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Ffmprb
|
2
|
+
|
3
|
+
class File # NOTE I would rather rename it to Stream at the moment
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def threaded_buffered_fifo(extname='.tmp', reader_open_on_writer_idle_limit: nil, proc_vis: nil)
|
8
|
+
input_fifo_file = temp_fifo(extname)
|
9
|
+
output_fifo_file = temp_fifo(extname)
|
10
|
+
Ffmprb.logger.debug "Opening #{input_fifo_file.path}>#{output_fifo_file.path} for buffering"
|
11
|
+
Util::Thread.new do
|
12
|
+
begin
|
13
|
+
io_buff = Util::ThreadedIoBuffer.new(opener(input_fifo_file, 'r'), opener(output_fifo_file, 'w'), keep_outputs_open_on_input_idle_limit: reader_open_on_writer_idle_limit)
|
14
|
+
if proc_vis
|
15
|
+
proc_vis.proc_vis_edge input_fifo_file, io_buff
|
16
|
+
proc_vis.proc_vis_edge io_buff, output_fifo_file
|
17
|
+
end
|
18
|
+
begin
|
19
|
+
# yield input_fifo_file, output_fifo_file, io_buff if block_given?
|
20
|
+
ensure
|
21
|
+
Util::Thread.join_children!
|
22
|
+
end
|
23
|
+
Ffmprb.logger.debug "IoBuffering from #{input_fifo_file.path} to #{output_fifo_file.path} ended"
|
24
|
+
ensure
|
25
|
+
input_fifo_file.unlink if input_fifo_file
|
26
|
+
output_fifo_file.unlink if output_fifo_file
|
27
|
+
end
|
28
|
+
end
|
29
|
+
Ffmprb.logger.debug "IoBuffering from #{input_fifo_file.path} to #{output_fifo_file.path} started"
|
30
|
+
|
31
|
+
[input_fifo_file, output_fifo_file]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def threaded_buffered_copy_to(*dsts)
|
37
|
+
Util::ThreadedIoBuffer.new(
|
38
|
+
self.class.opener(self, 'r'),
|
39
|
+
*dsts.map{|io| self.class.opener io, 'w'}
|
40
|
+
).tap do |io_buff|
|
41
|
+
proc_vis_edge self, io_buff
|
42
|
+
dsts.each{ |dst| proc_vis_edge io_buff, dst }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/ffmprb/filter.rb
CHANGED
@@ -72,18 +72,18 @@ module Ffmprb
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def concat_v(inputs, output=nil)
|
75
|
-
|
76
|
-
|
75
|
+
return copy(inputs, output) if inputs.size == 1
|
76
|
+
inout "concat=#{inputs.size}:v=1:a=0", inputs, output
|
77
77
|
end
|
78
78
|
|
79
79
|
def concat_a(inputs, output=nil)
|
80
|
-
|
81
|
-
|
80
|
+
return anull(inputs, output) if inputs.size == 1
|
81
|
+
inout "concat=#{inputs.size}:v=0:a=1", inputs, output
|
82
82
|
end
|
83
83
|
|
84
84
|
def concat_av(inputs, output=nil)
|
85
|
-
|
86
|
-
|
85
|
+
fail Error, "must be given an even number of inputs" unless inputs.size.even?
|
86
|
+
inout "concat=#{inputs.size/2}:v=1:a=1", inputs, output
|
87
87
|
end
|
88
88
|
|
89
89
|
def copy(input=nil, output=nil)
|
@@ -233,6 +233,7 @@ module Ffmprb
|
|
233
233
|
volume_exp: volume_exp(volume)
|
234
234
|
end
|
235
235
|
|
236
|
+
# NOTE supposedly volume list is sorted
|
236
237
|
def volume_exp(volume)
|
237
238
|
return volume unless volume.is_a?(Hash)
|
238
239
|
|
@@ -242,6 +243,7 @@ module Ffmprb
|
|
242
243
|
prev_vol = volume[prev_at] || 1.0
|
243
244
|
exp = "#{volume[volume.keys.last]}"
|
244
245
|
volume.each do |at, vol|
|
246
|
+
next if at == 0.0
|
245
247
|
vol_exp =
|
246
248
|
if (vol - prev_vol).abs < 0.001
|
247
249
|
vol
|
@@ -259,7 +261,7 @@ module Ffmprb
|
|
259
261
|
color_source '0xFFFFFF@1', duration, resolution, fps, output
|
260
262
|
end
|
261
263
|
|
262
|
-
def
|
264
|
+
def complex_args(*filters)
|
263
265
|
['-filter_complex', filters.join('; ')] unless filters.empty?
|
264
266
|
end
|
265
267
|
|
data/lib/ffmprb/find_silence.rb
CHANGED
@@ -2,10 +2,12 @@ module Ffmprb
|
|
2
2
|
|
3
3
|
class << self
|
4
4
|
|
5
|
+
# NOTE not for streaming just yet
|
5
6
|
def find_silence(input_file, output_file)
|
6
|
-
|
7
|
+
path = "#{input_file.path}->#{output_file.path}"
|
8
|
+
logger.debug "Finding silence (#{path})"
|
7
9
|
silence = []
|
8
|
-
Util.ffmpeg('-i', input_file.path, *
|
10
|
+
Util.ffmpeg('-i', input_file.path, *find_silence_detect_args, output_file.path).
|
9
11
|
scan(SILENCE_DETECT_REGEX).each do |mark, time|
|
10
12
|
time = time.to_f
|
11
13
|
|
@@ -23,7 +25,7 @@ module Ffmprb
|
|
23
25
|
Ffmprb.warn "Unknown silence mark: #{mark}"
|
24
26
|
end
|
25
27
|
end
|
26
|
-
logger.debug "Found silence (#{
|
28
|
+
logger.debug "Found silence (#{path}): [#{silence.map{|t,v| "#{t}: #{v}"}}]"
|
27
29
|
silence
|
28
30
|
end
|
29
31
|
|
@@ -31,8 +33,8 @@ module Ffmprb
|
|
31
33
|
|
32
34
|
SILENCE_DETECT_REGEX = /\[silencedetect\s.*\]\s*silence_(\w+):\s*(\d+\.?\d*)/
|
33
35
|
|
34
|
-
def
|
35
|
-
Filter.
|
36
|
+
def find_silence_detect_args
|
37
|
+
Filter.complex_args Filter.silencedetect
|
36
38
|
end
|
37
39
|
|
38
40
|
end
|
data/lib/ffmprb/process.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Ffmprb
|
2
2
|
|
3
3
|
class Process
|
4
|
+
include Util::ProcVis::Node
|
4
5
|
|
5
6
|
class << self
|
6
7
|
|
@@ -9,7 +10,8 @@ module Ffmprb
|
|
9
10
|
attr_accessor :duck_audio_transition_length,
|
10
11
|
:duck_audio_transition_in_start, :duck_audio_transition_out_start
|
11
12
|
|
12
|
-
attr_accessor :
|
13
|
+
attr_accessor :input_video_auto_rotate
|
14
|
+
attr_accessor :input_video_fps
|
13
15
|
|
14
16
|
attr_accessor :output_video_resolution
|
15
17
|
attr_accessor :output_video_fps
|
@@ -17,22 +19,36 @@ module Ffmprb
|
|
17
19
|
|
18
20
|
attr_accessor :timeout
|
19
21
|
|
20
|
-
def intermediate_channel_extname(
|
21
|
-
if
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
def intermediate_channel_extname(video:, audio:)
|
23
|
+
if video
|
24
|
+
if audio
|
25
|
+
'.flv'
|
26
|
+
else
|
27
|
+
'.y4m'
|
28
|
+
end
|
27
29
|
else
|
28
|
-
|
30
|
+
if audio
|
31
|
+
'.wav'
|
32
|
+
else
|
33
|
+
fail Error, "I don't know how to channel [#{media.join ', '}]"
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
38
|
+
def input_video_options
|
39
|
+
{
|
40
|
+
auto_rotate: input_video_auto_rotate,
|
41
|
+
fps: input_video_fps
|
42
|
+
}
|
43
|
+
end
|
44
|
+
def input_audio_options
|
45
|
+
{
|
46
|
+
}
|
47
|
+
end
|
32
48
|
def output_video_options
|
33
49
|
{
|
34
|
-
|
35
|
-
|
50
|
+
fps: output_video_fps,
|
51
|
+
resolution: output_video_resolution
|
36
52
|
}
|
37
53
|
end
|
38
54
|
def output_audio_options
|
@@ -88,25 +104,28 @@ module Ffmprb
|
|
88
104
|
end
|
89
105
|
|
90
106
|
attr_accessor :timeout
|
91
|
-
attr_accessor :
|
107
|
+
attr_accessor :name
|
108
|
+
attr_reader :parent
|
109
|
+
attr_accessor :ignore_broken_pipes
|
92
110
|
|
93
111
|
def initialize(*args, **opts)
|
112
|
+
self.timeout = opts.delete(:timeout) || Process.timeout
|
113
|
+
@name = opts.delete(:name)
|
114
|
+
@parent = opts.delete(:parent)
|
115
|
+
parent.proc_vis_node self if parent
|
116
|
+
self.ignore_broken_pipes = opts.delete(:ignore_broken_pipes)
|
117
|
+
fail Error, "Unknown options: #{opts}" unless opts.empty? # XXX refactor into a separate error
|
94
118
|
@inputs, @outputs = [], []
|
95
|
-
self.timeout = opts.delete(:timeout) || self.class.timeout
|
96
|
-
|
97
|
-
self.ignore_broken_pipe = opts.delete(:ignore_broken_pipe)
|
98
|
-
fail Error, "Unknown options: #{opts}" unless opts.empty?
|
99
119
|
end
|
100
120
|
|
101
|
-
def input(io,
|
102
|
-
Input.new(io, self,
|
121
|
+
def input(io, video: true, audio: true)
|
122
|
+
Input.new(io, self,
|
123
|
+
video: channel_params(video, Process.input_video_options),
|
124
|
+
audio: channel_params(audio, Process.input_audio_options)
|
125
|
+
).tap do |inp|
|
126
|
+
fail Error, "Too many inputs to the process, try breaking it down somehow" if @inputs.size > Util.ffmpeg_inputs_max
|
103
127
|
@inputs << inp
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
def temp_input(extname) # XXX SPEC ME
|
108
|
-
input(nil).tap do |inp|
|
109
|
-
inp.temporise! extname
|
128
|
+
proc_vis_edge inp.io, self
|
110
129
|
end
|
111
130
|
end
|
112
131
|
|
@@ -116,11 +135,12 @@ module Ffmprb
|
|
116
135
|
|
117
136
|
def output(io, video: true, audio: true, &blk)
|
118
137
|
Output.new(io, self,
|
119
|
-
video: channel_params(video,
|
120
|
-
audio: channel_params(audio,
|
121
|
-
).tap do |
|
122
|
-
@outputs <<
|
123
|
-
|
138
|
+
video: channel_params(video, Process.output_video_options),
|
139
|
+
audio: channel_params(audio, Process.output_audio_options)
|
140
|
+
).tap do |outp|
|
141
|
+
@outputs << outp
|
142
|
+
proc_vis_edge self, outp.io
|
143
|
+
outp.instance_exec &blk if blk
|
124
144
|
end
|
125
145
|
end
|
126
146
|
|
@@ -132,13 +152,17 @@ module Ffmprb
|
|
132
152
|
def run(limit: nil) # TODO (async: false)
|
133
153
|
# NOTE this is both for the future async: option and according to
|
134
154
|
# the threading policy (a parent death will be noticed and handled by children)
|
135
|
-
thr = Util::Thread.new do
|
155
|
+
thr = Util::Thread.new main: !parent do
|
156
|
+
proc_vis_node Thread.current
|
136
157
|
# NOTE yes, an exception can occur anytime, and we'll just die, it's ok, see above
|
137
158
|
# XXX just to return something -- no apparent practical use
|
138
159
|
cmd = command
|
139
|
-
|
160
|
+
opts = {limit: limit, timeout: timeout}
|
161
|
+
opts[:ignore_broken_pipes] = ignore_broken_pipes unless ignore_broken_pipes.nil?
|
162
|
+
Util.ffmpeg(*cmd, **opts).tap do |res|
|
140
163
|
Util::Thread.join_children! limit, timeout: timeout
|
141
164
|
end
|
165
|
+
proc_vis_node Thread.current, :remove
|
142
166
|
end
|
143
167
|
thr.value if thr.join limit # NOTE should not block for more than limit
|
144
168
|
end
|
@@ -146,19 +170,24 @@ module Ffmprb
|
|
146
170
|
private
|
147
171
|
|
148
172
|
def command
|
149
|
-
|
173
|
+
input_args + filter_args + output_args
|
150
174
|
end
|
151
175
|
|
152
|
-
def
|
153
|
-
|
176
|
+
def input_args
|
177
|
+
filter_args # NOTE must run first
|
178
|
+
@input_args ||= @inputs.map(&:args).reduce(:+)
|
154
179
|
end
|
155
180
|
|
156
|
-
|
157
|
-
|
181
|
+
# NOTE must run first
|
182
|
+
def filter_args
|
183
|
+
@filter_args ||= Filter.complex_args(
|
184
|
+
@outputs.map(&:filters).reduce(:+)
|
185
|
+
)
|
158
186
|
end
|
159
187
|
|
160
|
-
def
|
161
|
-
|
188
|
+
def output_args
|
189
|
+
filter_args # NOTE must run first
|
190
|
+
@output_args ||= @outputs.map(&:args).reduce(:+)
|
162
191
|
end
|
163
192
|
|
164
193
|
def channel_params(value, default)
|
@@ -168,7 +197,6 @@ module Ffmprb
|
|
168
197
|
{}
|
169
198
|
end
|
170
199
|
end
|
171
|
-
|
172
200
|
end
|
173
201
|
|
174
202
|
end
|
data/lib/ffmprb/process/input.rb
CHANGED
@@ -7,15 +7,29 @@ module Ffmprb
|
|
7
7
|
class << self
|
8
8
|
|
9
9
|
def resolve(io)
|
10
|
-
return io unless io.is_a? String
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
return io unless io.is_a? String # XXX XXX
|
11
|
+
|
12
|
+
File.open(io).tap do |file|
|
13
|
+
Ffmprb.logger.warn "Input file does no exist (#{file.path}), will probably fail" unless file.exist?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# XXX check for unknown options
|
18
|
+
|
19
|
+
def video_args(video=nil)
|
20
|
+
video = Process.input_video_options.merge(video.to_h)
|
21
|
+
[].tap do |args|
|
22
|
+
fps = nil # NOTE ah, ruby
|
23
|
+
args.concat %W[-noautorotate] unless video.delete(:auto_rotate)
|
24
|
+
args.concat %W[-r #{fps}] if (fps = video.delete(:fps))
|
25
|
+
fail "Unknown input video options: #{video}" unless video.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def audio_args(audio=nil)
|
30
|
+
audio = Process.input_audio_options.merge(audio.to_h)
|
31
|
+
[].tap do |args|
|
32
|
+
fail "Unknown input audio options: #{audio}" unless audio.empty?
|
19
33
|
end
|
20
34
|
end
|
21
35
|
|
@@ -24,10 +38,13 @@ module Ffmprb
|
|
24
38
|
attr_accessor :io
|
25
39
|
attr_reader :process
|
26
40
|
|
27
|
-
def initialize(io, process,
|
41
|
+
def initialize(io, process, video:, audio:)
|
28
42
|
@io = self.class.resolve(io)
|
29
43
|
@process = process
|
30
|
-
@
|
44
|
+
@channels = {
|
45
|
+
video: video && @io.channel?(:video) && OpenStruct.new(video),
|
46
|
+
audio: audio && @io.channel?(:audio) && OpenStruct.new(audio)
|
47
|
+
}
|
31
48
|
end
|
32
49
|
|
33
50
|
|
@@ -36,14 +53,12 @@ module Ffmprb
|
|
36
53
|
end
|
37
54
|
|
38
55
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
opts << value unless value == true
|
56
|
+
def args
|
57
|
+
[].tap do |args|
|
58
|
+
args.concat self.class.video_args(channel :video) if channel? :video
|
59
|
+
args.concat self.class.audio_args(channel :audio) if channel? :audio
|
60
|
+
args.concat ['-i', io.path]
|
45
61
|
end
|
46
|
-
opts << '-i' << io.path
|
47
62
|
end
|
48
63
|
|
49
64
|
def filters_for(lbl, video:, audio:)
|
@@ -68,6 +83,10 @@ module Ffmprb
|
|
68
83
|
io.channel? medium
|
69
84
|
end
|
70
85
|
|
86
|
+
def channel(medium)
|
87
|
+
@channels[medium]
|
88
|
+
end
|
89
|
+
|
71
90
|
|
72
91
|
def chain_copy(src_input)
|
73
92
|
src_input
|
@@ -4,7 +4,8 @@ module Ffmprb
|
|
4
4
|
|
5
5
|
class Input
|
6
6
|
|
7
|
-
def loop(times=
|
7
|
+
def loop(times=Util.ffmpeg_inputs_max)
|
8
|
+
Ffmprb.logger.warn "Looping more than #{Util.ffmpeg_inputs_max} times is 'unstable': either use double looping or ask for this feature" if times > Util.ffmpeg_inputs_max
|
8
9
|
Looping.new self, times
|
9
10
|
end
|
10
11
|
|
@@ -14,83 +15,119 @@ module Ffmprb
|
|
14
15
|
|
15
16
|
def initialize(unfiltered, times)
|
16
17
|
super unfiltered
|
18
|
+
|
17
19
|
@times = times
|
18
20
|
|
19
|
-
@raw = unfiltered
|
21
|
+
@raw = @_unfiltered = unfiltered
|
22
|
+
# NOTE find the actual input io (not a filter)
|
20
23
|
@raw = @raw.unfiltered while @raw.respond_to? :unfiltered
|
21
|
-
@src_io = @raw.io
|
22
|
-
@raw.temporise!
|
23
|
-
@aux_input = @raw.process.temp_input(@src_io.extname)
|
24
24
|
end
|
25
25
|
|
26
26
|
def filters_for(lbl, video:, audio:)
|
27
27
|
|
28
|
+
# The plan:
|
29
|
+
# 1) Create and route an aux input which would hold the filtered, looped and parameterised stream off the raw input (keep the raw input)
|
30
|
+
# 2) Tee+buffer the original raw input io: one stream goes back into the process throw the raw input io replacement fifo; the other is fed into the filtering process
|
31
|
+
# 3) Which uses the same underlying filters to produce a filtered and parameterised stream, which is fed into the looping process through a N-Tee+buffer
|
32
|
+
# 4) Invoke the looping process which just concatenates its N inputs and produces the new raw input (the aux input)
|
33
|
+
# XXX
|
34
|
+
# -) If the consumer is broken of the:
|
35
|
+
# a. raw input - the Tee+buffer is resilient - unless the f-p-l breaks too;
|
36
|
+
# b. the f-p-l stream - the looping process fails, the N-Tee+buffer breaks, the filtering process fails, and the Tee+buffer may fail
|
37
|
+
|
28
38
|
# Looping
|
39
|
+
# NOTE all the processing is done before looping
|
29
40
|
|
30
|
-
|
31
|
-
video: OpenStruct.new, audio: OpenStruct.new
|
41
|
+
aux_input(video: video, audio: audio).filters_for lbl,
|
42
|
+
video: OpenStruct.new, audio: OpenStruct.new
|
32
43
|
end
|
33
44
|
|
34
45
|
protected
|
35
46
|
|
36
|
-
def
|
37
|
-
fail Error, "Double looping is not supported... yet" unless @src_io # TODO video & audio params check
|
38
|
-
src_io = @src_io
|
39
|
-
@src_io = nil
|
47
|
+
def aux_input(video:, audio:)
|
40
48
|
|
41
|
-
|
49
|
+
# NOTE (2)
|
50
|
+
# NOTE replace the raw input io with a copy io, getting original fifo/file
|
51
|
+
intermediate_extname = Process.intermediate_channel_extname(video: @raw.io.channel?(:video), audio: @raw.io.channel?(:audio))
|
52
|
+
src_io = @raw.temporise_io!(intermediate_extname)
|
53
|
+
if src_io.extname != intermediate_extname
|
54
|
+
meh_src_io, src_io = src_io, File.temp_fifo(intermediate_extname)
|
55
|
+
Util::Thread.new "source converter" do
|
56
|
+
Ffmprb.process do
|
42
57
|
|
43
|
-
|
44
|
-
|
45
|
-
|
58
|
+
inp = input(meh_src_io)
|
59
|
+
output(src_io) do
|
60
|
+
lay inp
|
61
|
+
end
|
46
62
|
|
47
|
-
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
cpy_io = File.temp_fifo(src_io.extname)
|
67
|
+
Ffmprb.logger.debug "(L2) Temporising the raw input (#{src_io.path}) and creating copy (#{cpy_io.path})"
|
48
68
|
|
49
|
-
|
50
|
-
Util::ThreadedIoBuffer.new(
|
51
|
-
File.async_opener(buff_raw_io, 'r'),
|
52
|
-
File.async_opener(raw.io, 'w')
|
53
|
-
)
|
69
|
+
src_io.threaded_buffered_copy_to @raw.io, cpy_io
|
54
70
|
|
55
|
-
|
71
|
+
# NOTE (3)
|
72
|
+
# NOTE preprocessed and filtered fifo
|
73
|
+
dst_io = File.temp_fifo(intermediate_extname)
|
74
|
+
@raw.process.proc_vis_node dst_io
|
56
75
|
|
57
76
|
Util::Thread.new "looping input processor" do
|
58
|
-
Ffmprb.logger.debug "Processing before looping"
|
59
|
-
|
60
|
-
process = Process.new
|
61
|
-
in1 = process.input(src_io)
|
62
|
-
process.output(dst_io, video: video, audio: audio).
|
63
|
-
lay in1.copy(unfiltered)
|
64
|
-
process.output(buff_raw_io,
|
65
|
-
video: OpenStruct.new, audio: OpenStruct.new # NOTE raw input copy
|
66
|
-
).
|
67
|
-
lay in1
|
68
|
-
process.run # TODO limit:
|
77
|
+
# Ffmprb.logger.debug "Processing before looping"
|
69
78
|
|
79
|
+
Ffmprb.logger.debug "(L3) Pre-processing into (#{dst_io.path})"
|
80
|
+
Ffmprb.process @_unfiltered, parent: @raw.process do |unfiltered| # TODO limit:
|
81
|
+
|
82
|
+
inp = input(cpy_io)
|
83
|
+
output(dst_io, video: video, audio: audio) do
|
84
|
+
lay inp.copy(unfiltered)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
70
88
|
end
|
71
89
|
|
72
|
-
|
90
|
+
# Ffmprb.logger.debug "Preprocessed (from #{src_io.path}) looping input: #{dst_io.path}, output: #{io.io.path}, and raw input copy will go through #{buff_raw_io.path} to #{@raw.io.path}..."
|
91
|
+
|
92
|
+
buff_ios = (1..times).map{File.temp_fifo intermediate_extname}
|
73
93
|
Ffmprb.logger.debug "Preprocessed #{dst_io.path} will be teed to #{buff_ios.map(&:path).join '; '}"
|
74
|
-
|
75
|
-
|
76
|
-
*buff_ios
|
77
|
-
|
94
|
+
looping = true
|
95
|
+
Util::Thread.new "cloning buffer watcher" do
|
96
|
+
dst_io.threaded_buffered_copy_to *buff_ios
|
97
|
+
Util::Thread.join_children!
|
78
98
|
|
79
|
-
|
99
|
+
Ffmprb.logger.warn "Looping ~from #{src_io.path} finished before its consumer: if you just wanted to loop input #{Util.ffmpeg_inputs_max} times, that's fine, but if you expected it to loop indefinitely... #{Util.ffmpeg_inputs_max} is the maximum #loop can do at the moment, and it may just not be enough in this case (workaround by concatting or file a complaint at https://github.com/showbox-oss/ffmprb/issues please)." if looping && times == Util.ffmpeg_inputs_max
|
100
|
+
end
|
101
|
+
|
102
|
+
# Ffmprb.logger.debug "Concatenation of #{buff_ios.map(&:path).join '; '} will go to #{@io.io.path} to be fed to this process"
|
103
|
+
|
104
|
+
# NOTE additional (filtered, processed and looped) input io
|
105
|
+
aux_io = File.temp_fifo(intermediate_extname)
|
106
|
+
|
107
|
+
# NOTE (4)
|
80
108
|
|
81
109
|
Util::Thread.new "looper" do
|
82
110
|
Ffmprb.logger.debug "Looping #{buff_ios.size} times"
|
83
111
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
112
|
+
Ffmprb.logger.debug "(L4) Looping (#{buff_ios.map &:path}) into (#{aux_io.path})"
|
113
|
+
begin
|
114
|
+
Ffmprb.process parent: @raw.process do # NOTE may not write its entire output, it's ok
|
115
|
+
|
116
|
+
ins = buff_ios.map{ |i| input i }
|
117
|
+
output(aux_io, video: nil, audio: nil) do
|
118
|
+
ins.each{ |i| lay i }
|
119
|
+
end
|
90
120
|
|
121
|
+
end
|
122
|
+
ensure
|
123
|
+
looping = false # NOTE see the above warning
|
124
|
+
end
|
91
125
|
end
|
92
126
|
|
93
|
-
|
127
|
+
# NOTE (1)
|
128
|
+
|
129
|
+
Ffmprb.logger.debug "(L1) Creating a new input (#{aux_io.path}) to the process"
|
130
|
+
@raw.process.input(aux_io)
|
94
131
|
end
|
95
132
|
|
96
133
|
end
|