ffmprb 0.7.0 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Guardfile +1 -1
- data/README.md +4 -2
- data/circle.yml +5 -1
- data/lib/defaults.rb +21 -0
- data/lib/ffmprb/file/sample.rb +48 -0
- data/lib/ffmprb/file.rb +17 -50
- data/lib/ffmprb/filter.rb +46 -28
- data/lib/ffmprb/find_silence.rb +30 -19
- data/lib/ffmprb/process/input.rb +48 -20
- data/lib/ffmprb/process/output.rb +109 -111
- data/lib/ffmprb/process.rb +38 -26
- data/lib/ffmprb/util/thread.rb +87 -5
- data/lib/ffmprb/util/{io_buffer.rb → threaded_io_buffer.rb} +90 -110
- data/lib/ffmprb/util.rb +62 -31
- data/lib/ffmprb/version.rb +1 -1
- data/lib/ffmprb.rb +10 -24
- metadata +5 -3
@@ -4,7 +4,7 @@ module Ffmprb
|
|
4
4
|
|
5
5
|
# XXX the events mechanism is currently unused (and commented out) => synchro mechanism not needed
|
6
6
|
# XXX partially specc'ed in file_spec
|
7
|
-
class
|
7
|
+
class ThreadedIoBuffer
|
8
8
|
# include Synchro
|
9
9
|
|
10
10
|
class << self
|
@@ -20,126 +20,43 @@ module Ffmprb
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# NOTE input/output can be lambdas for single asynchronic io evaluation
|
23
|
-
#
|
24
|
-
|
25
|
-
|
23
|
+
# the labdas must be timeout-interrupt-safe (since they are wrapped in timeout blocks)
|
24
|
+
# NOTE both ios are being opened and closed as soon as possible
|
25
|
+
def initialize(input, output)
|
26
26
|
|
27
27
|
@input = input
|
28
28
|
@output = output
|
29
|
-
@q = SizedQueue.new(blocks_max)
|
29
|
+
@q = SizedQueue.new(self.class.blocks_max)
|
30
30
|
@stat_blocks_max = 0
|
31
31
|
@terminate = false
|
32
32
|
# @events = {}
|
33
33
|
|
34
|
-
|
34
|
+
Thread.new "io buffer main" do
|
35
|
+
init_reader!
|
36
|
+
init_writer_output!
|
37
|
+
init_writer!
|
35
38
|
|
36
|
-
|
37
|
-
begin
|
38
|
-
while s = reader_input!.read(block_size)
|
39
|
-
begin
|
40
|
-
Timeout::timeout(self.class.timeout) do
|
41
|
-
@q.enq s
|
42
|
-
end
|
43
|
-
rescue Timeout::Error # NOTE the queue is probably overflown
|
44
|
-
@terminate = Error.new("The reader has failed with timeout while queuing")
|
45
|
-
# timeout!
|
46
|
-
raise Error, "Looks like we're stuck (#{self.class.timeout}s idle) with #{blocks_max}x#{block_size}B blocks (buffering #{reader_input!.path}->...)..."
|
47
|
-
end
|
48
|
-
@stat_blocks_max = blocks_count if blocks_count > @stat_blocks_max
|
49
|
-
end
|
50
|
-
@terminate = true
|
51
|
-
@q.enq nil
|
52
|
-
ensure
|
53
|
-
begin
|
54
|
-
reader_input!.close if reader_input!.respond_to?(:close)
|
55
|
-
rescue
|
56
|
-
Ffmprb.logger.error "IoBuffer input closing error: #{$!.message}"
|
57
|
-
end
|
58
|
-
# reader_done!
|
59
|
-
Ffmprb.logger.debug "IoBuffer reader terminated (blocks max: #{@stat_blocks_max})"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
init_writer_output!
|
64
|
-
|
65
|
-
# NOTE writes as much output as possible, then terminates when the reader dies
|
66
|
-
|
67
|
-
@writer = Util::Thread.new("buffer writer") do
|
68
|
-
broken = false
|
69
|
-
begin
|
70
|
-
while s = @q.deq
|
71
|
-
next if broken
|
72
|
-
written = 0
|
73
|
-
tries = 1
|
74
|
-
logged_tries = 1/2
|
75
|
-
while !broken
|
76
|
-
raise @terminate if @terminate.kind_of?(Exception)
|
77
|
-
begin
|
78
|
-
output = writer_output!
|
79
|
-
written = output.write_nonblock(s) if output # NOTE will only be nil if @terminate is an exception
|
80
|
-
break if written == s.length # NOTE kinda optimisation
|
81
|
-
s = s[written..-1]
|
82
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
83
|
-
if tries == 2 * logged_tries
|
84
|
-
Ffmprb.logger.debug "IoBuffer writer (to #{output.path}) retrying... (#{tries} writes): #{$!.class}"
|
85
|
-
logged_tries = tries
|
86
|
-
end
|
87
|
-
sleep 0.01
|
88
|
-
rescue Errno::EPIPE
|
89
|
-
broken = true
|
90
|
-
Ffmprb.logger.debug "IoBuffer writer (to #{output.path}) broken"
|
91
|
-
ensure
|
92
|
-
tries += 1
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
ensure
|
97
|
-
# terminated!
|
98
|
-
begin
|
99
|
-
writer_output!.close if !broken && writer_output!.respond_to?(:close)
|
100
|
-
rescue
|
101
|
-
Ffmprb.logger.error "IoBuffer output closing error: #{$!.message}"
|
102
|
-
end
|
103
|
-
Ffmprb.logger.debug "IoBuffer writer terminated (blocks max: #{@stat_blocks_max})"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def flush! # NOTE blocking, closes ios
|
109
|
-
e = nil
|
110
|
-
# [@reader, @writer, @handler_thr].each do |thr|
|
111
|
-
[@reader, @writer, @output_thr].compact.each do |thr|
|
112
|
-
begin
|
113
|
-
thr.join
|
114
|
-
rescue
|
115
|
-
if e
|
116
|
-
Ffmprb.logger.debug "Additionally got (hidden): #{$!.message}"
|
117
|
-
else
|
118
|
-
e = $!
|
119
|
-
end
|
120
|
-
end
|
39
|
+
Thread.join_children!
|
121
40
|
end
|
122
|
-
raise e if e
|
123
41
|
end
|
124
|
-
# handle_synchronously :flush!
|
125
42
|
#
|
126
43
|
# def once(event, &blk)
|
127
44
|
# event = event.to_sym
|
128
45
|
# wait_for_handler!
|
129
46
|
# if @events[event].respond_to? :call
|
130
|
-
#
|
47
|
+
# fail Error, "Once upon a time (one #once(event) at a time) please"
|
131
48
|
# elsif @events[event]
|
132
|
-
# Ffmprb.logger.debug "
|
49
|
+
# Ffmprb.logger.debug "ThreadedIoBuffer (post-)reacting to #{event}"
|
133
50
|
# @handler_thr = Util::Thread.new "#{event} handler", &blk
|
134
51
|
# else
|
135
|
-
# Ffmprb.logger.debug "
|
52
|
+
# Ffmprb.logger.debug "ThreadedIoBuffer subscribing to #{event}"
|
136
53
|
# @events[event] = blk
|
137
54
|
# end
|
138
55
|
# end
|
139
56
|
# handle_synchronously :once
|
140
57
|
#
|
141
58
|
# def reader_done!
|
142
|
-
# Ffmprb.logger.debug "
|
59
|
+
# Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (blocks max: #{@stat_blocks_max})"
|
143
60
|
# fire! :reader_done
|
144
61
|
# end
|
145
62
|
#
|
@@ -155,7 +72,7 @@ module Ffmprb
|
|
155
72
|
#
|
156
73
|
# def fire!(event)
|
157
74
|
# wait_for_handler!
|
158
|
-
# Ffmprb.logger.debug "
|
75
|
+
# Ffmprb.logger.debug "ThreadedIoBuffer firing #{event}"
|
159
76
|
# if blk = @events.to_h[event.to_sym]
|
160
77
|
# @handler_thr = Util::Thread.new "#{event} handler", &blk
|
161
78
|
# end
|
@@ -189,23 +106,86 @@ module Ffmprb
|
|
189
106
|
def init_writer_output!
|
190
107
|
return unless @output.respond_to?(:call)
|
191
108
|
|
192
|
-
@output_thr =
|
109
|
+
@output_thr = Thread.new("buffer writer output helper") do
|
193
110
|
Ffmprb.logger.debug "Opening buffer output"
|
194
|
-
|
195
|
-
|
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}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# NOTE reads all of input, then closes the stream times out on buffer overflow
|
121
|
+
def init_reader!
|
122
|
+
Thread.new("buffer reader") do
|
196
123
|
begin
|
197
|
-
|
198
|
-
|
199
|
-
|
124
|
+
while s = reader_input!.read(self.class.block_size)
|
125
|
+
begin
|
126
|
+
Timeout.timeout(self.class.timeout) do
|
127
|
+
@q.enq s
|
128
|
+
end
|
129
|
+
rescue Timeout::Error # NOTE the queue is probably overflown
|
130
|
+
@terminate = Error.new("The reader has failed with timeout while queuing")
|
131
|
+
# timeout!
|
132
|
+
fail Error, "Looks like we're stuck (#{timeout}s idle) with #{self.class.blocks_max}x#{self.class.block_size}B blocks (buffering #{reader_input!.path}->...)..."
|
133
|
+
end
|
134
|
+
@stat_blocks_max = blocks_count if blocks_count > @stat_blocks_max
|
200
135
|
end
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
136
|
+
@terminate = true
|
137
|
+
@q.enq nil
|
138
|
+
ensure
|
139
|
+
begin
|
140
|
+
reader_input!.close if reader_input!.respond_to?(:close)
|
141
|
+
rescue
|
142
|
+
Ffmprb.logger.error "ThreadedIoBuffer input closing error: #{$!.message}"
|
143
|
+
end
|
144
|
+
# reader_done!
|
145
|
+
Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (blocks max: #{@stat_blocks_max})"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# NOTE writes as much output as possible, then terminates when the reader dies
|
151
|
+
def init_writer!
|
152
|
+
Thread.new("buffer writer") do
|
153
|
+
broken = false
|
154
|
+
begin
|
155
|
+
while s = @q.deq
|
156
|
+
next if broken
|
157
|
+
written = 0
|
158
|
+
tries = 1
|
159
|
+
logged_tries = 1/2
|
160
|
+
while !broken
|
161
|
+
fail @terminate if @terminate.kind_of?(Exception)
|
162
|
+
begin
|
163
|
+
output = writer_output!
|
164
|
+
written = output.write_nonblock(s) if output # NOTE will only be nil if @terminate is an exception
|
165
|
+
break if written == s.length # NOTE kinda optimisation
|
166
|
+
s = s[written..-1]
|
167
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
168
|
+
if tries == 2 * logged_tries
|
169
|
+
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output.path}) retrying... (#{tries} writes): #{$!.class}"
|
170
|
+
logged_tries = tries
|
171
|
+
end
|
172
|
+
sleep 0.01
|
173
|
+
rescue Errno::EPIPE
|
174
|
+
broken = true
|
175
|
+
Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output.path}) broken"
|
176
|
+
ensure
|
177
|
+
tries += 1
|
178
|
+
end
|
179
|
+
end
|
205
180
|
end
|
206
|
-
retry unless @terminate.kind_of?(Exception)
|
207
181
|
ensure
|
208
|
-
|
182
|
+
# terminated!
|
183
|
+
begin
|
184
|
+
writer_output!.close if !broken && writer_output!.respond_to?(:close)
|
185
|
+
rescue
|
186
|
+
Ffmprb.logger.error "ThreadedIoBuffer output closing error: #{$!.message}"
|
187
|
+
end
|
188
|
+
Ffmprb.logger.debug "ThreadedIoBuffer writer terminated (blocks max: #{@stat_blocks_max})"
|
209
189
|
end
|
210
190
|
end
|
211
191
|
end
|
data/lib/ffmprb/util.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# require 'ffmprb/util/synchro'
|
2
2
|
require 'ffmprb/util/thread'
|
3
|
-
require 'ffmprb/util/
|
3
|
+
require 'ffmprb/util/threaded_io_buffer'
|
4
4
|
|
5
5
|
require 'open3'
|
6
6
|
|
@@ -8,50 +8,81 @@ module Ffmprb
|
|
8
8
|
|
9
9
|
module Util
|
10
10
|
|
11
|
+
class TimeLimitError < Error; end
|
12
|
+
|
11
13
|
class << self
|
12
14
|
|
13
15
|
attr_accessor :ffmpeg_cmd, :ffprobe_cmd
|
16
|
+
attr_accessor :cmd_timeout
|
14
17
|
|
15
|
-
def ffprobe(*args)
|
16
|
-
sh *ffprobe_cmd, *args
|
18
|
+
def ffprobe(*args, limit: nil, timeout: cmd_timeout)
|
19
|
+
sh *ffprobe_cmd, *args, limit: limit, timeout: timeout
|
17
20
|
end
|
18
21
|
|
19
|
-
def ffmpeg(*args)
|
22
|
+
def ffmpeg(*args, limit: nil, timeout: cmd_timeout)
|
20
23
|
args = ['-loglevel', 'debug'] + args if Ffmprb.debug
|
21
|
-
sh *ffmpeg_cmd, '-y', *args, output: :stderr
|
24
|
+
sh *ffmpeg_cmd, '-y', *args, output: :stderr, limit: limit, timeout: timeout
|
22
25
|
end
|
23
26
|
|
24
|
-
def sh(*cmd, output: :stdout, log: :stderr)
|
25
|
-
cmd = cmd.
|
26
|
-
cmd_str = cmd.join(' ')
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def sh(*cmd, output: :stdout, log: :stderr, limit: nil, timeout: cmd_timeout)
|
28
|
+
cmd = cmd.map &:to_s unless cmd.size == 1
|
29
|
+
cmd_str = cmd.size != 1 ? cmd.map{|c| "\"#{c}\""}.join(' ') : cmd.first
|
30
|
+
timeout = [timeout, limit].compact.min
|
31
|
+
thr = Thread.new "`#{cmd_str}`" do
|
32
|
+
Ffmprb.logger.info "Popening `#{cmd_str}`..."
|
33
|
+
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
|
34
|
+
begin
|
35
|
+
stdin.close
|
30
36
|
|
31
|
-
|
37
|
+
log_cmd = cmd.first.upcase if log
|
38
|
+
stdout_r = Reader.new(stdout, output == :stdout, log == :stdout && log_cmd)
|
39
|
+
stderr_r = Reader.new(stderr, true, log == :stderr && log_cmd)
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
Thread.timeout_or_live(limit, log: "while waiting for `#{cmd_str}`", timeout: timeout) do |time|
|
42
|
+
fail Error, "#{cmd_str}:\n#{stderr_r.read}" unless
|
43
|
+
wait_thr.value.exitstatus == 0 # NOTE blocking
|
44
|
+
end
|
45
|
+
Ffmprb.logger.debug "FINISHED: #{cmd_str}"
|
37
46
|
|
38
|
-
|
39
|
-
wait_thr.value.exitstatus == 0 # NOTE blocks
|
47
|
+
Thread.join_children! limit, timeout: timeout
|
40
48
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
stdout_r.join if stdout_r
|
46
|
-
stdout_r = nil
|
47
|
-
stderr_r.join if stderr_r
|
48
|
-
rescue
|
49
|
-
Ffmprb.logger.error "Thread joining error: #{$!.message}"
|
50
|
-
stderr_r.join if stdout_r
|
49
|
+
# NOTE only one of them will return non-nil, see above
|
50
|
+
stdout_r.read || stderr_r.read
|
51
|
+
ensure
|
52
|
+
process_dead! wait_thr, cmd_str, limit
|
51
53
|
end
|
52
|
-
Ffmprb.logger.debug "FINISHED: #{cmd_str}"
|
53
54
|
end
|
54
55
|
end
|
56
|
+
thr.value
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def process_dead!(wait_thr, cmd_str, limit)
|
62
|
+
grace = limit ? limit/4 : 1
|
63
|
+
return unless wait_thr.alive?
|
64
|
+
|
65
|
+
# NOTE a simplistic attempt to gracefully terminate a child process
|
66
|
+
# the successful completion is via exception...
|
67
|
+
begin
|
68
|
+
Ffmprb.logger.info "Sorry it came to this, but I'm terminating `#{cmd_str}`(#{wait_thr.pid})..."
|
69
|
+
::Process.kill 'TERM', wait_thr.pid
|
70
|
+
sleep grace
|
71
|
+
Ffmprb.logger.info "Very sorry it came to this, but I'm terminating `#{cmd_str}`(#{wait_thr.pid}) again..."
|
72
|
+
::Process.kill 'TERM', wait_thr.pid
|
73
|
+
sleep grace
|
74
|
+
Ffmprb.logger.warn "Die `#{cmd_str}`(#{wait_thr.pid}), die!.. (killing amok)"
|
75
|
+
::Process.kill 'KILL', wait_thr.pid
|
76
|
+
sleep grace
|
77
|
+
Ffmprb.logger.warn "Checking if `#{cmd_str}`(#{wait_thr.pid}) finally dead..."
|
78
|
+
::Process.kill 0, wait_thr.pid
|
79
|
+
Ffmprb.logger.error "Still alive -- `#{cmd_str}`(#{wait_thr.pid}), giving up..."
|
80
|
+
rescue Errno::ESRCH
|
81
|
+
Ffmprb.logger.info "Apparently `#{cmd_str}`(#{wait_thr.pid}) is dead..."
|
82
|
+
end
|
83
|
+
|
84
|
+
fail Error, "System error or something: waiting for the thread running `#{cmd_str}`(#{wait_thr.pid})..." unless
|
85
|
+
wait_thr.join limit
|
55
86
|
end
|
56
87
|
|
57
88
|
end
|
@@ -65,7 +96,7 @@ module Ffmprb
|
|
65
96
|
super "reader" do
|
66
97
|
begin
|
67
98
|
while s = input.gets
|
68
|
-
Ffmprb.logger.debug log
|
99
|
+
Ffmprb.logger.debug "#{log}: #{s.chomp}" if log
|
69
100
|
@output << s if store
|
70
101
|
end
|
71
102
|
@queue.enq @output
|
@@ -78,7 +109,7 @@ module Ffmprb
|
|
78
109
|
def read
|
79
110
|
case res = @queue.deq
|
80
111
|
when Exception
|
81
|
-
|
112
|
+
fail res
|
82
113
|
when ''
|
83
114
|
nil
|
84
115
|
else
|
data/lib/ffmprb/version.rb
CHANGED
data/lib/ffmprb.rb
CHANGED
@@ -1,41 +1,23 @@
|
|
1
|
-
require 'ffmprb/execution'
|
2
|
-
require 'ffmprb/file'
|
3
|
-
require 'ffmprb/filter'
|
4
|
-
require 'ffmprb/find_silence'
|
5
|
-
require 'ffmprb/process'
|
6
|
-
require 'ffmprb/util'
|
7
|
-
require 'ffmprb/version'
|
8
|
-
|
9
1
|
require 'logger'
|
10
2
|
|
3
|
+
# IMPORTANT NOTE ffmprb uses threads internally, however, it is not "thread-safe"
|
4
|
+
|
11
5
|
module Ffmprb
|
12
6
|
|
13
7
|
ENV_VAR_FALSE_REGEX = /^(0|no?|false)?$/i
|
14
8
|
|
9
|
+
CGA = '320x200'
|
15
10
|
QVGA = '320x240'
|
16
11
|
HD_720p = '1280x720'
|
17
12
|
HD_1080p = '1920x1080'
|
18
13
|
|
19
|
-
class Error < StandardError
|
20
|
-
end
|
21
|
-
|
22
|
-
Util.ffmpeg_cmd = ['ffmpeg']
|
23
|
-
Util.ffprobe_cmd = ['ffprobe']
|
24
|
-
|
25
|
-
Process.duck_audio_hi = 0.9
|
26
|
-
Process.duck_audio_lo = 0.1
|
27
|
-
Process.duck_audio_transition_sec = 1
|
28
|
-
Process.duck_audio_silent_min_sec = 3
|
29
|
-
Filter.silence_noise_max_db = -40
|
30
|
-
|
31
|
-
Util::IoBuffer.blocks_max = 1024
|
32
|
-
Util::IoBuffer.block_size = 64*1024
|
33
|
-
Util::IoBuffer.timeout = 9
|
14
|
+
class Error < StandardError; end
|
34
15
|
|
35
16
|
class << self
|
36
17
|
|
37
18
|
# NOTE the form with the block returns the result of #run
|
38
|
-
# NOTE the form without the block returns the process (before it is run)
|
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?
|
39
21
|
def process(*args, &blk)
|
40
22
|
logger.debug "Starting process with #{args} in #{blk.source_location}"
|
41
23
|
process = Process.new
|
@@ -68,3 +50,7 @@ module Ffmprb
|
|
68
50
|
end
|
69
51
|
|
70
52
|
Ffmprb.debug = ENV.fetch('FFMPRB_DEBUG', '') !~ Ffmprb::ENV_VAR_FALSE_REGEX
|
53
|
+
|
54
|
+
Dir["#{__FILE__.slice /(.*).rb$/, 1}/**/*.rb"].each{|f| require f} # XXX require_sub __FILE__ # or something
|
55
|
+
|
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.7.
|
4
|
+
version: 0.7.3
|
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-
|
12
|
+
date: 2015-10-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mkfifo
|
@@ -147,18 +147,20 @@ files:
|
|
147
147
|
- circle.yml
|
148
148
|
- exe/ffmprb
|
149
149
|
- ffmprb.gemspec
|
150
|
+
- lib/defaults.rb
|
150
151
|
- lib/ffmprb.rb
|
151
152
|
- lib/ffmprb/execution.rb
|
152
153
|
- lib/ffmprb/file.rb
|
154
|
+
- lib/ffmprb/file/sample.rb
|
153
155
|
- lib/ffmprb/filter.rb
|
154
156
|
- lib/ffmprb/find_silence.rb
|
155
157
|
- lib/ffmprb/process.rb
|
156
158
|
- lib/ffmprb/process/input.rb
|
157
159
|
- lib/ffmprb/process/output.rb
|
158
160
|
- lib/ffmprb/util.rb
|
159
|
-
- lib/ffmprb/util/io_buffer.rb
|
160
161
|
- lib/ffmprb/util/synchro.rb
|
161
162
|
- lib/ffmprb/util/thread.rb
|
163
|
+
- lib/ffmprb/util/threaded_io_buffer.rb
|
162
164
|
- lib/ffmprb/version.rb
|
163
165
|
homepage: https://github.com/showbox-oss/ffmprb
|
164
166
|
licenses: []
|