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
@@ -2,8 +2,8 @@ module Ffmprb
|
|
2
2
|
|
3
3
|
module Util
|
4
4
|
|
5
|
-
#
|
6
|
-
# XXX partially specc'ed in file_spec
|
5
|
+
# TODO the events mechanism is currently unused (and commented out) => synchro mechanism not needed
|
6
|
+
# XXX *partially* specc'ed in file_spec
|
7
7
|
class ThreadedIoBuffer
|
8
8
|
# include Synchro
|
9
9
|
|
@@ -22,19 +22,23 @@ module Ffmprb
|
|
22
22
|
# NOTE input/output can be lambdas for single asynchronic io evaluation
|
23
23
|
# the labdas must be timeout-interrupt-safe (since they are wrapped in timeout blocks)
|
24
24
|
# NOTE both ios are being opened and closed as soon as possible
|
25
|
-
def initialize(input,
|
25
|
+
def initialize(input, *outputs) # XXX SPEC ME!!! multiple outputs!!
|
26
26
|
|
27
27
|
@input = input
|
28
|
-
@
|
29
|
-
|
28
|
+
@outputs = outputs.inject({}) do |hash, out|
|
29
|
+
hash[out] = SizedQueue.new(self.class.blocks_max)
|
30
|
+
hash
|
31
|
+
end
|
30
32
|
@stat_blocks_max = 0
|
31
33
|
@terminate = false
|
32
34
|
# @events = {}
|
33
35
|
|
34
36
|
Thread.new "io buffer main" do
|
35
37
|
init_reader!
|
36
|
-
|
37
|
-
|
38
|
+
outputs.each do |output|
|
39
|
+
init_writer_output! output
|
40
|
+
init_writer! output
|
41
|
+
end
|
38
42
|
|
39
43
|
Thread.join_children!
|
40
44
|
end
|
@@ -81,7 +85,7 @@ module Ffmprb
|
|
81
85
|
# handle_synchronously :fire!
|
82
86
|
#
|
83
87
|
def blocks_count
|
84
|
-
@
|
88
|
+
@outputs.values.map(&:size).max
|
85
89
|
end
|
86
90
|
|
87
91
|
private
|
@@ -95,26 +99,12 @@ module Ffmprb
|
|
95
99
|
@input
|
96
100
|
end
|
97
101
|
|
98
|
-
def writer_output! # NOTE just for writer thread
|
99
|
-
if @
|
100
|
-
@
|
101
|
-
@
|
102
|
-
end
|
103
|
-
@output unless @output.respond_to?(:call)
|
104
|
-
end
|
105
|
-
|
106
|
-
def init_writer_output!
|
107
|
-
return unless @output.respond_to?(:call)
|
108
|
-
|
109
|
-
@output_thr = Thread.new("buffer writer output helper") do
|
110
|
-
Ffmprb.logger.debug "Opening buffer output"
|
111
|
-
@output =
|
112
|
-
Thread.timeout_or_live nil, log: "in the buffer writer helper thread", timeout: self.class.timeout do |time|
|
113
|
-
fail Error, "giving up buffer writer init since the reader has failed (#{@terminate.message})" if @terminate.kind_of?(Exception)
|
114
|
-
@output.call
|
115
|
-
end
|
116
|
-
Ffmprb.logger.debug "Opened buffer output: #{@output.path}"
|
102
|
+
def writer_output!(output) # NOTE just for writer thread
|
103
|
+
if @output_thrs[output]
|
104
|
+
@output_thrs[output].join
|
105
|
+
@output_thrs[output] = nil
|
117
106
|
end
|
107
|
+
@output_ios[output]
|
118
108
|
end
|
119
109
|
|
120
110
|
# NOTE reads all of input, then closes the stream times out on buffer overflow
|
@@ -124,7 +114,7 @@ module Ffmprb
|
|
124
114
|
while s = reader_input!.read(self.class.block_size)
|
125
115
|
begin
|
126
116
|
Timeout.timeout(self.class.timeout) do
|
127
|
-
|
117
|
+
output_enq s
|
128
118
|
end
|
129
119
|
rescue Timeout::Error # NOTE the queue is probably overflown
|
130
120
|
@terminate = Error.new("The reader has failed with timeout while queuing")
|
@@ -134,7 +124,7 @@ module Ffmprb
|
|
134
124
|
@stat_blocks_max = blocks_count if blocks_count > @stat_blocks_max
|
135
125
|
end
|
136
126
|
@terminate = true
|
137
|
-
|
127
|
+
output_enq nil
|
138
128
|
ensure
|
139
129
|
begin
|
140
130
|
reader_input!.close if reader_input!.respond_to?(:close)
|
@@ -147,12 +137,28 @@ module Ffmprb
|
|
147
137
|
end
|
148
138
|
end
|
149
139
|
|
140
|
+
def init_writer_output!(output)
|
141
|
+
@output_ios ||= {}
|
142
|
+
return @output_ios[output] = output unless output.respond_to?(:call)
|
143
|
+
|
144
|
+
@output_thrs ||= {}
|
145
|
+
@output_thrs[output] = Thread.new("buffer writer output helper") do
|
146
|
+
Ffmprb.logger.debug "Opening buffer output"
|
147
|
+
@output_ios[output] =
|
148
|
+
Thread.timeout_or_live nil, log: "in the buffer writer helper thread", timeout: self.class.timeout do |time|
|
149
|
+
fail Error, "giving up buffer writer init since the reader has failed (#{@terminate.message})" if @terminate.kind_of?(Exception)
|
150
|
+
output.call
|
151
|
+
end
|
152
|
+
Ffmprb.logger.debug "Opened buffer output: #{@output_ios[output].path}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
150
156
|
# NOTE writes as much output as possible, then terminates when the reader dies
|
151
|
-
def init_writer!
|
157
|
+
def init_writer!(output)
|
152
158
|
Thread.new("buffer writer") do
|
153
159
|
broken = false
|
154
160
|
begin
|
155
|
-
while s = @
|
161
|
+
while s = @outputs[output].deq
|
156
162
|
next if broken
|
157
163
|
written = 0
|
158
164
|
tries = 1
|
@@ -160,19 +166,19 @@ module Ffmprb
|
|
160
166
|
while !broken
|
161
167
|
fail @terminate if @terminate.kind_of?(Exception)
|
162
168
|
begin
|
163
|
-
|
164
|
-
written =
|
169
|
+
output_io = writer_output!(output)
|
170
|
+
written = output_io.write_nonblock(s) if output # NOTE will only be nil if @terminate is an exception
|
165
171
|
break if written == s.length # NOTE kinda optimisation
|
166
172
|
s = s[written..-1]
|
167
173
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
168
174
|
if tries == 2 * logged_tries
|
169
|
-
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{
|
175
|
+
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io.path}) retrying... (#{tries} writes): #{$!.class}"
|
170
176
|
logged_tries = tries
|
171
177
|
end
|
172
178
|
sleep 0.01
|
173
179
|
rescue Errno::EPIPE
|
174
180
|
broken = true
|
175
|
-
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{
|
181
|
+
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io.path}) broken"
|
176
182
|
ensure
|
177
183
|
tries += 1
|
178
184
|
end
|
@@ -181,7 +187,7 @@ module Ffmprb
|
|
181
187
|
ensure
|
182
188
|
# terminated!
|
183
189
|
begin
|
184
|
-
writer_output
|
190
|
+
writer_output!(output).close if !broken && writer_output!(output).respond_to?(:close)
|
185
191
|
rescue
|
186
192
|
Ffmprb.logger.error "ThreadedIoBuffer output closing error: #{$!.message}"
|
187
193
|
end
|
@@ -195,6 +201,12 @@ module Ffmprb
|
|
195
201
|
# @handler_thr = nil
|
196
202
|
# end
|
197
203
|
|
204
|
+
def output_enq(item)
|
205
|
+
@outputs.values.each do |q|
|
206
|
+
q.enq item
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
198
210
|
end
|
199
211
|
|
200
212
|
end
|
data/lib/ffmprb/util.rb
CHANGED
@@ -19,12 +19,12 @@ module Ffmprb
|
|
19
19
|
sh *ffprobe_cmd, *args, limit: limit, timeout: timeout
|
20
20
|
end
|
21
21
|
|
22
|
-
def ffmpeg(*args, limit: nil, timeout: cmd_timeout)
|
22
|
+
def ffmpeg(*args, limit: nil, timeout: cmd_timeout, ignore_broken_pipe: false)
|
23
23
|
args = ['-loglevel', 'debug'] + args if Ffmprb.debug
|
24
|
-
sh *ffmpeg_cmd,
|
24
|
+
sh *ffmpeg_cmd, *args, output: :stderr, limit: limit, timeout: timeout, ignore_broken_pipe: ignore_broken_pipe
|
25
25
|
end
|
26
26
|
|
27
|
-
def sh(*cmd, output: :stdout, log: :stderr, limit: nil, timeout: cmd_timeout)
|
27
|
+
def sh(*cmd, output: :stdout, log: :stderr, limit: nil, timeout: cmd_timeout, ignore_broken_pipe: false)
|
28
28
|
cmd = cmd.map &:to_s unless cmd.size == 1
|
29
29
|
cmd_str = cmd.size != 1 ? cmd.map{|c| "\"#{c}\""}.join(' ') : cmd.first
|
30
30
|
timeout = [timeout, limit].compact.min
|
@@ -39,8 +39,15 @@ module Ffmprb
|
|
39
39
|
stderr_r = Reader.new(stderr, true, log == :stderr && log_cmd)
|
40
40
|
|
41
41
|
Thread.timeout_or_live(limit, log: "while waiting for `#{cmd_str}`", timeout: timeout) do |time|
|
42
|
-
|
43
|
-
|
42
|
+
value = wait_thr.value
|
43
|
+
status = value.exitstatus # NOTE blocking
|
44
|
+
if status != 0
|
45
|
+
if ignore_broken_pipe && value.signaled? && value.termsig == Signal.list['PIPE']
|
46
|
+
Ffmprb.logger.debug "Ignoring broken pipe: #{cmd_str}"
|
47
|
+
else
|
48
|
+
fail Error, "#{cmd_str} (#{status || "sig##{value.termsig}"}):\n#{stderr_r.read}"
|
49
|
+
end
|
50
|
+
end
|
44
51
|
end
|
45
52
|
Ffmprb.logger.debug "FINISHED: #{cmd_str}"
|
46
53
|
|
data/lib/ffmprb/version.rb
CHANGED
data/lib/ffmprb.rb
CHANGED
@@ -15,13 +15,13 @@ module Ffmprb
|
|
15
15
|
|
16
16
|
class << self
|
17
17
|
|
18
|
-
#
|
19
|
-
# NOTE the form without the block returns the process (before it is run) - advanced use
|
20
|
-
# XXX is this clear enough? Do we really need the second form?
|
18
|
+
# TODO limit:
|
21
19
|
def process(*args, &blk)
|
22
|
-
|
20
|
+
fail Error, "process: nothing ;( gimme a block!" unless blk
|
21
|
+
|
23
22
|
process = Process.new
|
24
|
-
|
23
|
+
|
24
|
+
logger.debug "Starting process with #{args} in #{blk.source_location}"
|
25
25
|
|
26
26
|
process.instance_exec *args, &blk
|
27
27
|
logger.debug "Initialized process with #{args} in #{blk.source_location}"
|
@@ -51,6 +51,6 @@ end
|
|
51
51
|
|
52
52
|
Ffmprb.debug = ENV.fetch('FFMPRB_DEBUG', '') !~ Ffmprb::ENV_VAR_FALSE_REGEX
|
53
53
|
|
54
|
-
Dir["#{__FILE__.slice /(.*).rb$/, 1}/**/*.rb"].each{|f| require f}
|
54
|
+
Dir["#{__FILE__.slice /(.*).rb$/, 1}/**/*.rb"].each{|f| require f}
|
55
55
|
|
56
56
|
require 'defaults'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffmprb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- showbox.com
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-10-
|
12
|
+
date: 2015-10-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mkfifo
|
@@ -156,6 +156,13 @@ files:
|
|
156
156
|
- lib/ffmprb/find_silence.rb
|
157
157
|
- lib/ffmprb/process.rb
|
158
158
|
- lib/ffmprb/process/input.rb
|
159
|
+
- lib/ffmprb/process/input/chain_base.rb
|
160
|
+
- lib/ffmprb/process/input/channeled.rb
|
161
|
+
- lib/ffmprb/process/input/cropped.rb
|
162
|
+
- lib/ffmprb/process/input/cut.rb
|
163
|
+
- lib/ffmprb/process/input/looping.rb
|
164
|
+
- lib/ffmprb/process/input/loud.rb
|
165
|
+
- lib/ffmprb/process/input/temp.rb
|
159
166
|
- lib/ffmprb/process/output.rb
|
160
167
|
- lib/ffmprb/util.rb
|
161
168
|
- lib/ffmprb/util/synchro.rb
|