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