ffmprb 0.9.6 → 0.10.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.
@@ -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} -r 1 -vcodec mjpeg -f mjpeg #{video.path}] if video
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.remove if video
32
- audio.remove if 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 "Error removing sample files: #{$!.message}"
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
- inout "concat=%{inputs_count}:v=1:a=0", inputs, output,
76
- inputs_count: (inputs.empty?? nil : inputs.size)
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
- inout "concat=%{inputs_count}:v=0:a=1", inputs, output,
81
- inputs_count: (inputs.empty?? nil : inputs.size)
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
- inout "concat=%{inputs_count}:v=1:a=1", inputs, output,
86
- inputs_count: (inputs.empty? || inputs.size % 2 != 0 ? nil : inputs.size/2) # XXX meh
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 complex_options(*filters)
264
+ def complex_args(*filters)
263
265
  ['-filter_complex', filters.join('; ')] unless filters.empty?
264
266
  end
265
267
 
@@ -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
- logger.debug "Finding silence (#{input_file.path}->#{output_file.path})"
7
+ path = "#{input_file.path}->#{output_file.path}"
8
+ logger.debug "Finding silence (#{path})"
7
9
  silence = []
8
- Util.ffmpeg('-i', input_file.path, *find_silence_detect_options, output_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 (#{input_file.path}->#{output_file.path}): [#{silence.map{|t,v| "#{t}: #{v}"}}]"
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 find_silence_detect_options
35
- Filter.complex_options Filter.silencedetect
36
+ def find_silence_detect_args
37
+ Filter.complex_args Filter.silencedetect
36
38
  end
37
39
 
38
40
  end
@@ -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 :input_options
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(*media)
21
- if media == [:video]
22
- '.y4m'
23
- elsif media == [:audio]
24
- '.wav'
25
- elsif media.sort == [:audio, :video]
26
- '.flv'
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
- fail Error, "I don't know how to channel [#{media.join ', '}]"
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
- resolution: output_video_resolution,
35
- fps: output_video_fps
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 :ignore_broken_pipe
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, **opts)
102
- Input.new(io, self, **self.class.input_options.merge(opts)).tap do |inp|
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
- end
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, self.class.output_video_options),
120
- audio: channel_params(audio, self.class.output_audio_options)
121
- ).tap do |out|
122
- @outputs << out
123
- out.instance_exec &blk if blk
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
- Util.ffmpeg(*cmd, limit: limit, timeout: timeout, ignore_broken_pipe: @ignore_broken_pipe).tap do |res|
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
- input_options + filter_options + output_options
173
+ input_args + filter_args + output_args
150
174
  end
151
175
 
152
- def input_options
153
- @inputs.map(&:options).flatten(1)
176
+ def input_args
177
+ filter_args # NOTE must run first
178
+ @input_args ||= @inputs.map(&:args).reduce(:+)
154
179
  end
155
180
 
156
- def filter_options
157
- Filter.complex_options @outputs.map(&:filters).reduce(:+)
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 output_options
161
- @outputs.map(&:options).flatten(1)
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
@@ -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
- case io
13
- when /^\/\w/
14
- File.open(io).tap do |file|
15
- Ffmprb.logger.warn "Input file does no exist (#{file.path}), will probably fail" unless file.exist?
16
- end
17
- else
18
- fail Error, "Cannot resolve input: #{io}"
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, **opts)
41
+ def initialize(io, process, video:, audio:)
28
42
  @io = self.class.resolve(io)
29
43
  @process = process
30
- @opts = opts
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 options
40
- opts = []
41
- @opts.map do |name, value|
42
- next unless value
43
- opts << "-#{name}"
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=31)
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
- loop_unfiltered(video: video, audio: audio).filters_for lbl,
31
- video: OpenStruct.new, audio: OpenStruct.new # NOTE the processing is done before looping
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 loop_unfiltered(video:, audio:)
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
- Ffmprb.logger.debug "Validating limitations..."
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
- raw = unfiltered
44
- raw = raw.unfiltered while raw.respond_to? :unfiltered
45
- fail Error, "Something is wrong (double looping?)" unless raw == @raw
58
+ inp = input(meh_src_io)
59
+ output(src_io) do
60
+ lay inp
61
+ end
46
62
 
47
- dst_io = File.temp_fifo(src_io.extname)
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
- buff_raw_io = File.temp_fifo(src_io.extname)
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
- Ffmprb.logger.debug "Preprocessed looping input will be #{dst_io.path} and raw input copy will go through #{buff_raw_io.path} to #{raw.io.path}..."
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
- buff_ios = (0..times).map{File.temp_fifo src_io.extname}
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
- Util::ThreadedIoBuffer.new(
75
- File.async_opener(dst_io, 'r'),
76
- *buff_ios.map{|io| File.async_opener io, 'w'}
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
- Ffmprb.logger.debug "Concatenation of #{buff_ios.map(&:path).join '; '} will go to #{@aux_input.io.path} to be fed to this process"
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
- process = Process.new(ignore_broken_pipe: true) # NOTE may not write its entire output, it's ok
85
- ins = buff_ios.map{|i| process.input i}
86
- process.output(@aux_input.io, video: nil, audio: nil) do
87
- ins.each{|i| lay i}
88
- end
89
- process.run # TODO limit:
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
- self.unfiltered = @aux_input
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