ffmprb 0.7.0 → 0.7.3
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/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: []
|