ffmprb 0.11.4 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Dockerfile +11 -5
- data/Gemfile +5 -5
- data/Gemfile.lock +54 -54
- data/README.md +57 -15
- data/TODO.md +0 -1
- data/bin/dev +12 -0
- data/bin/test +9 -3
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
- data/coverage/assets/0.12.3/application.css +1 -0
- data/coverage/assets/0.12.3/application.js +7 -0
- data/coverage/assets/0.12.3/colorbox/border.png +0 -0
- data/coverage/assets/0.12.3/colorbox/controls.png +0 -0
- data/coverage/assets/0.12.3/colorbox/loading.gif +0 -0
- data/coverage/assets/0.12.3/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.12.3/favicon_green.png +0 -0
- data/coverage/assets/0.12.3/favicon_red.png +0 -0
- data/coverage/assets/0.12.3/favicon_yellow.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.12.3/loading.gif +0 -0
- data/coverage/assets/0.12.3/magnify.png +0 -0
- data/coverage/index.html +47166 -24254
- data/exp/EXP +7 -0
- data/exp/av-cut-mp4you60.ffmprb +10 -0
- data/exp/docker-compose.yml +9 -0
- data/exp/gop-cut-cat-you60 +141 -0
- data/exp/present/Dockerfile +13 -0
- data/exp/present/Gemfile +3 -0
- data/exp/present/Gemfile.lock +22 -0
- data/exp/present/bin/up-deps +8 -0
- data/exp/present/docker-compose.yml +21 -0
- data/exp/present/exp/present +10 -0
- data/exp/present/exp/present.rb +37 -0
- data/exp/run +9 -0
- data/exp/stitch2.ffmprb +5 -0
- data/exp/unzip-mp4you60 +58 -0
- data/exp/youtubby/Dockerfile +13 -0
- data/exp/youtubby/Gemfile +7 -0
- data/exp/youtubby/Gemfile.lock +73 -0
- data/exp/youtubby/README.md +21 -0
- data/exp/youtubby/bin/up-deps +8 -0
- data/exp/youtubby/docker-compose.yml +20 -0
- data/exp/youtubby/exp/gop-raw-cut-you-HD60 +13 -0
- data/exp/youtubby/exp/gop-raw-cut-you-HD60.rb +230 -0
- data/exp/youtubby/exp/media-upload +13 -0
- data/exp/youtubby/exp/tmp/CURRENT +2 -0
- data/exp/youtubby/google_youtube.rb +39 -0
- data/exp/youtubby/old-ul.py +181 -0
- data/exp/youtubby/old-ul.rb +87 -0
- data/exp/youtubby/py-Dockerfile +11 -0
- data/exp/youtubby/py-docker-compose.yml +13 -0
- data/exp/youtubby/requirements.txt +19 -0
- data/exp/youtubby/upload.rb +38 -0
- data/exp/zip-cut-mp4you60 +42 -0
- data/exp/zip2mp4k60 +27 -0
- data/lib/defaults.rb +5 -5
- data/lib/ffmprb/execution.rb +1 -1
- data/lib/ffmprb/file/threaded_buffered.rb +1 -1
- data/lib/ffmprb/file.rb +14 -14
- data/lib/ffmprb/filter.rb +96 -60
- data/lib/ffmprb/process/input/chain_base.rb +6 -4
- data/lib/ffmprb/process/input/channeled.rb +1 -1
- data/lib/ffmprb/process/input/cropped.rb +4 -1
- data/lib/ffmprb/process/input/cut.rb +2 -2
- data/lib/ffmprb/process/input/looping.rb +5 -5
- data/lib/ffmprb/process/input/loud.rb +1 -1
- data/lib/ffmprb/process/input/paced.rb +35 -0
- data/lib/ffmprb/process/input/postprocessed.rb +29 -0
- data/lib/ffmprb/process/input/reversed.rb +29 -0
- data/lib/ffmprb/process/input.rb +6 -2
- data/lib/ffmprb/process/output.rb +36 -23
- data/lib/ffmprb/process.rb +3 -6
- data/lib/ffmprb/util/proc_vis.rb +4 -3
- data/lib/ffmprb/util/thread.rb +8 -7
- data/lib/ffmprb/util/threaded_io_buffer.rb +5 -3
- data/lib/ffmprb/util.rb +37 -14
- data/lib/ffmprb/version.rb +1 -1
- data/lib/ffmprb.rb +5 -2
- data/tmp/exp/docker-compose.yml +9 -0
- data/tmp/exp/src/SAM_3132.MP4 +0 -0
- data/tmp/ffmprb-0.11.4.gem +0 -0
- metadata +72 -4
data/lib/ffmprb/process/input.rb
CHANGED
@@ -7,9 +7,10 @@ module Ffmprb
|
|
7
7
|
class << self
|
8
8
|
|
9
9
|
def resolve(io)
|
10
|
-
return io unless
|
10
|
+
return io unless
|
11
|
+
io.is_a? String
|
11
12
|
|
12
|
-
File.
|
13
|
+
File.access(io).tap do |file|
|
13
14
|
Ffmprb.logger.warn "Input file does no exist (#{file.path}), will probably fail" unless file.exist?
|
14
15
|
end
|
15
16
|
end
|
@@ -110,4 +111,7 @@ require_relative 'input/cropped'
|
|
110
111
|
require_relative 'input/cut'
|
111
112
|
require_relative 'input/looping'
|
112
113
|
require_relative 'input/loud'
|
114
|
+
require_relative 'input/paced'
|
115
|
+
require_relative 'input/postprocessed'
|
116
|
+
require_relative 'input/reversed'
|
113
117
|
require_relative 'input/temp'
|
@@ -6,14 +6,15 @@ module Ffmprb
|
|
6
6
|
|
7
7
|
class << self
|
8
8
|
|
9
|
-
# XXX check for unknown options
|
10
|
-
|
11
9
|
def video_args(video=nil)
|
12
10
|
video = Process.output_video_options.merge(video.to_h)
|
13
11
|
[].tap do |args|
|
14
|
-
encoder =
|
15
|
-
|
16
|
-
|
12
|
+
if (encoder = video.delete(:encoder)) # NOTE extra encoder options possible
|
13
|
+
args.concat "-c:v #{encoder}".split(' ')
|
14
|
+
end
|
15
|
+
if (pixel_format = video.delete(:pixel_format))
|
16
|
+
args.concat %W[-pix_fmt #{pixel_format}]
|
17
|
+
end
|
17
18
|
video.delete :resolution # NOTE is handled otherwise
|
18
19
|
video.delete :fps # NOTE is handled otherwise
|
19
20
|
Util.assert_options_empty! video
|
@@ -23,15 +24,19 @@ module Ffmprb
|
|
23
24
|
def audio_args(audio=nil)
|
24
25
|
audio = Process.output_audio_options.merge(audio.to_h)
|
25
26
|
[].tap do |args|
|
26
|
-
encoder =
|
27
|
-
|
28
|
-
|
27
|
+
if (encoder = audio.delete(:encoder)) # NOTE extra encoder options possible
|
28
|
+
args.concat "-c:a #{encoder}".split(' ')
|
29
|
+
end
|
30
|
+
if (sampling_freq = audio.delete(:sampling_freq))
|
31
|
+
args.concat %W[-ar #{sampling_freq}]
|
32
|
+
end
|
29
33
|
Util.assert_options_empty! audio
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
37
|
def resolve(io)
|
34
|
-
return io unless
|
38
|
+
return io unless
|
39
|
+
io.is_a? String
|
35
40
|
|
36
41
|
File.create(io).tap do |file|
|
37
42
|
Ffmprb.logger.warn "Output file exists (#{file.path}), will probably overwrite" if file.exist?
|
@@ -57,11 +62,15 @@ module Ffmprb
|
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
60
|
-
#
|
65
|
+
# TODO This method is exceptionally long at the moment. This is not too grand.
|
61
66
|
# However, structuring the code should be undertaken with care, as not to harm the composition clarity.
|
62
67
|
def filters
|
63
|
-
fail Error, "Nothing to roll..." unless
|
64
|
-
|
68
|
+
fail Error, "Nothing to roll..." unless
|
69
|
+
@reels
|
70
|
+
fail Error, "Supporting just full_screen for now, sorry." unless
|
71
|
+
@reels.all? &:full_screen?
|
72
|
+
fail Error, "Supporting just a known output FPS" unless
|
73
|
+
!channel(:video) || (video_fps = channel(:video).fps)
|
65
74
|
return @filters if @filters
|
66
75
|
|
67
76
|
idx = process.output_index(self)
|
@@ -80,13 +89,18 @@ module Ffmprb
|
|
80
89
|
# NOTE mapping input to this lbl
|
81
90
|
|
82
91
|
lbl = "o#{idx}rl#{i}"
|
92
|
+
lbl_aux = "t#{lbl}"
|
83
93
|
|
84
94
|
# NOTE Image-Padding to match the target resolution
|
85
95
|
# TODO full screen only at the moment (see exception above)
|
86
96
|
|
87
97
|
Ffmprb.logger.debug{"#{self} asking for filters of #{curr_reel.reel.io.inspect} video: #{channel(:video)}, audio: #{channel(:audio)}"}
|
88
98
|
@filters.concat(
|
89
|
-
|
99
|
+
[
|
100
|
+
*curr_reel.reel.filters_for(lbl_aux, video: channel(:video), audio: channel(:audio)),
|
101
|
+
*(channel?(:video)? Filter.interpolate_v(video_fps, "#{lbl_aux}:v", "#{lbl}:v"): nil),
|
102
|
+
*(channel?(:audio)? Filter.anull("#{lbl_aux}:a", "#{lbl}:a"): nil)
|
103
|
+
]
|
90
104
|
)
|
91
105
|
end
|
92
106
|
|
@@ -103,7 +117,7 @@ module Ffmprb
|
|
103
117
|
# NOTE generously padding the previous segment to support for all the cases
|
104
118
|
@filters.concat(
|
105
119
|
Filter.blank_source trim_prev_at + transition_length,
|
106
|
-
channel(:video).resolution,
|
120
|
+
channel(:video).resolution, video_fps, "#{lbl_pad}:v"
|
107
121
|
) if channel?(:video)
|
108
122
|
@filters.concat(
|
109
123
|
Filter.silent_source trim_prev_at + transition_length, "#{lbl_pad}:a"
|
@@ -164,7 +178,7 @@ module Ffmprb
|
|
164
178
|
if !lbl # no reel
|
165
179
|
lbl_aux = "o#{idx}bk#{i}"
|
166
180
|
@filters.concat(
|
167
|
-
Filter.blank_source transition_length, channel(:video).resolution,
|
181
|
+
Filter.blank_source transition_length, channel(:video).resolution, video_fps, "#{lbl_aux}:v"
|
168
182
|
) if channel?(:video)
|
169
183
|
@filters.concat(
|
170
184
|
Filter.silent_source transition_length, "#{lbl_aux}:a"
|
@@ -180,7 +194,7 @@ module Ffmprb
|
|
180
194
|
|
181
195
|
# TODO the only supported transition, see #*lay
|
182
196
|
@filters.concat(
|
183
|
-
Filter.blend_v transition_length, channel(:video).resolution,
|
197
|
+
Filter.blend_v transition_length, channel(:video).resolution, video_fps, ["#{lbl_end1}:v", "#{lbl || lbl_aux}:v"], "#{lbl_reel}:v"
|
184
198
|
) if channel?(:video)
|
185
199
|
@filters.concat(
|
186
200
|
Filter.blend_a transition_length, ["#{lbl_end1}:a", "#{lbl || lbl_aux}:a"], "#{lbl_reel}:a"
|
@@ -238,7 +252,7 @@ module Ffmprb
|
|
238
252
|
|
239
253
|
# NOTE multi-process overlays last
|
240
254
|
|
241
|
-
@channel_lbl_ios = {} #
|
255
|
+
@channel_lbl_ios = {} # TODO this is a spaghetti machine
|
242
256
|
@channel_lbl_ios["#{lbl_out}:v"] = io if channel?(:video)
|
243
257
|
@channel_lbl_ios["#{lbl_out}:a"] = io if channel?(:audio)
|
244
258
|
|
@@ -252,7 +266,7 @@ module Ffmprb
|
|
252
266
|
Ffmprb.logger.info "ATTENTION: ducking audio (due to the absence of a simple ffmpeg filter) does not support streaming main input. yet."
|
253
267
|
|
254
268
|
# So ducking just audio here, ye?
|
255
|
-
#
|
269
|
+
# TODO! check if we're on audio channel
|
256
270
|
|
257
271
|
main_av_o = @channel_lbl_ios["#{lbl_out}:a"]
|
258
272
|
fail Error, "Main output does not contain audio to duck" unless main_av_o
|
@@ -260,7 +274,7 @@ module Ffmprb
|
|
260
274
|
intermediate_extname = Process.intermediate_channel_extname video: main_av_o.channel?(:video), audio: main_av_o.channel?(:audio)
|
261
275
|
main_av_inter_i, main_av_inter_o = File.threaded_buffered_fifo(intermediate_extname, reader_open_on_writer_idle_limit: Util::ThreadedIoBuffer.timeout * 2, proc_vis: process)
|
262
276
|
@channel_lbl_ios.each do |channel_lbl, io|
|
263
|
-
@channel_lbl_ios[channel_lbl] = main_av_inter_i if io == main_av_o #
|
277
|
+
@channel_lbl_ios[channel_lbl] = main_av_inter_i if io == main_av_o # TODO ~~~spaghetti
|
264
278
|
end
|
265
279
|
process.proc_vis_edge process, main_av_o, :remove
|
266
280
|
process.proc_vis_edge process, main_av_inter_i
|
@@ -278,11 +292,11 @@ module Ffmprb
|
|
278
292
|
inter_i, inter_o = File.threaded_buffered_fifo(intermediate_extname, proc_vis: process)
|
279
293
|
Ffmprb.logger.debug{"Allocated fifos to buffer media (#{inter_i.path}>#{inter_o.path}) while finding silence"}
|
280
294
|
|
281
|
-
ignore_broken_pipes_was = process.ignore_broken_pipes #
|
295
|
+
ignore_broken_pipes_was = process.ignore_broken_pipes # TODO? maybe throw an exception instead?
|
282
296
|
process.ignore_broken_pipes = true # NOTE audio ducking process may break the overlay pipe
|
283
297
|
|
284
298
|
Util::Thread.new "audio ducking" do
|
285
|
-
process.proc_vis_edge main_av_inter_o, inter_i #
|
299
|
+
process.proc_vis_edge main_av_inter_o, inter_i # TODO mark it better
|
286
300
|
silence = Ffmprb.find_silence(main_av_inter_o, inter_i)
|
287
301
|
|
288
302
|
Ffmprb.logger.debug{
|
@@ -295,7 +309,6 @@ module Ffmprb
|
|
295
309
|
video: channel(:video), audio: channel(:audio)
|
296
310
|
end
|
297
311
|
end
|
298
|
-
|
299
312
|
end
|
300
313
|
|
301
314
|
@filters
|
@@ -305,7 +318,7 @@ module Ffmprb
|
|
305
318
|
fail Error, "Must generate filters first." unless @channel_lbl_ios
|
306
319
|
|
307
320
|
[].tap do |args|
|
308
|
-
io_channel_lbls = {} #
|
321
|
+
io_channel_lbls = {} # TODO ~~~spaghetti
|
309
322
|
@channel_lbl_ios.each do |channel_lbl, io|
|
310
323
|
(io_channel_lbls[io] ||= []) << channel_lbl
|
311
324
|
end
|
data/lib/ffmprb/process.rb
CHANGED
@@ -185,7 +185,7 @@ module Ffmprb
|
|
185
185
|
# NOTE must run first
|
186
186
|
def filter_args
|
187
187
|
@filter_args ||= Filter.complex_args(
|
188
|
-
@outputs.map(&:filters).reduce
|
188
|
+
@outputs.map(&:filters).reduce :+
|
189
189
|
)
|
190
190
|
end
|
191
191
|
|
@@ -195,11 +195,8 @@ module Ffmprb
|
|
195
195
|
end
|
196
196
|
|
197
197
|
def channel_params(value, default)
|
198
|
-
|
199
|
-
|
200
|
-
elsif value != false
|
201
|
-
{}
|
202
|
-
end
|
198
|
+
default.merge(value.respond_to?(:to_h)? value.to_h : {}) unless
|
199
|
+
value == false
|
203
200
|
end
|
204
201
|
end
|
205
202
|
|
data/lib/ffmprb/util/proc_vis.rb
CHANGED
@@ -51,7 +51,8 @@ module Ffmprb
|
|
51
51
|
|
52
52
|
def proc_vis_node(obj, op=:upsert)
|
53
53
|
return unless proc_vis_init?
|
54
|
-
fail Error, "Must be a #{Node.name}" unless
|
54
|
+
fail Error, "Must be a #{Node.name}" unless
|
55
|
+
obj.kind_of? Node
|
55
56
|
|
56
57
|
obj._proc_vis = self
|
57
58
|
obj.proc_vis_name.tap do |lbl|
|
@@ -63,7 +64,7 @@ module Ffmprb
|
|
63
64
|
@_proc_vis_nodes[obj] = lbl
|
64
65
|
end
|
65
66
|
end
|
66
|
-
proc_vis_update #
|
67
|
+
proc_vis_update # TODO optimise
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
@@ -115,7 +116,7 @@ module Ffmprb
|
|
115
116
|
prev_t = Time.now
|
116
117
|
while @_proc_vis_upq.deq # NOTE currently, runs forever (nil terminator needed)
|
117
118
|
proc_vis_do_update
|
118
|
-
Thread.current.live! #
|
119
|
+
Thread.current.live! # TODO? not the best we can do here
|
119
120
|
while Time.now - prev_t < UPDATE_PERIOD_SEC
|
120
121
|
@_proc_vis_upq.deq # NOTE drains the queue
|
121
122
|
end
|
data/lib/ffmprb/util/thread.rb
CHANGED
@@ -25,7 +25,7 @@ module Ffmprb
|
|
25
25
|
end
|
26
26
|
rescue Timeout::Error
|
27
27
|
if timeouts > 2 * logged_timeouts
|
28
|
-
Ffmprb.logger.info "A little bit of timeout #{log.respond_to?(:call)? log.call : log} (##{timeouts})"
|
28
|
+
Ffmprb.logger.info "A little bit of timeout #{log.respond_to?(:call)? log.call : log} (##{timeouts}x#{timeout})"
|
29
29
|
logged_timeouts = timeouts
|
30
30
|
end
|
31
31
|
current.live!
|
@@ -40,6 +40,7 @@ module Ffmprb
|
|
40
40
|
end
|
41
41
|
|
42
42
|
attr_reader :name
|
43
|
+
attr_reader :backtrace
|
43
44
|
|
44
45
|
def initialize(name="some", main: false, &blk)
|
45
46
|
orig_caller = caller
|
@@ -48,12 +49,15 @@ module Ffmprb
|
|
48
49
|
@live_children = []
|
49
50
|
@children_mon = Monitor.new
|
50
51
|
@dead_children_q = Queue.new
|
51
|
-
|
52
|
+
@backtrace = (@parent.respond_to?(:backtrace)? @parent.backtrace : []) + caller
|
53
|
+
Ffmprb.logger.debug{"about to launch #{'main ' if main}#{name}"}
|
52
54
|
sync_q = Queue.new
|
53
55
|
super() do
|
56
|
+
self.report_on_exception = false
|
54
57
|
@parent.proc_vis_node self if @parent.respond_to? :proc_vis_node
|
55
58
|
if @parent.respond_to? :child_lives
|
56
59
|
@parent.child_lives self
|
60
|
+
Ffmprb.logger.warn "Not the main: false thread run by a #{self.class.name} thread" if main
|
57
61
|
else
|
58
62
|
Ffmprb.logger.warn "Not the main: true thread run by a not #{self.class.name} thread" unless main
|
59
63
|
end
|
@@ -64,11 +68,11 @@ module Ffmprb
|
|
64
68
|
Ffmprb.logger.debug{"#{name} thread done"}
|
65
69
|
end
|
66
70
|
rescue Exception
|
67
|
-
Ffmprb.logger.warn "#{$!.class.name} raised in #{name} thread: #{$!.message}\nBacktrace:\n\t#{$!.backtrace.join("\n\t")}"
|
71
|
+
Ffmprb.logger.warn "#{$!.class.name} raised in #{name} thread: #{$!.message}\nBacktrace:\n\t#{($!.backtrace + backtrace ).join("\n\t")}"
|
68
72
|
cause = $!
|
69
73
|
Ffmprb.logger.warn "...caused by #{cause.class.name}: #{cause.message}\nBacktrace:\n\t#{cause.backtrace.join("\n\t")}" while
|
70
74
|
cause = cause.cause
|
71
|
-
|
75
|
+
raise $! # TODO? I have no idea why I need to give it `$!` -- the docs say I need not
|
72
76
|
ensure
|
73
77
|
@parent.child_dies self if @parent.respond_to? :child_dies
|
74
78
|
@parent.proc_vis_node self, :remove if @parent.respond_to? :proc_vis_node
|
@@ -111,9 +115,6 @@ module Ffmprb
|
|
111
115
|
fail "System Error" unless thr.join(timeout) # NOTE should not block
|
112
116
|
end
|
113
117
|
end
|
114
|
-
|
115
118
|
end
|
116
|
-
|
117
119
|
end
|
118
|
-
|
119
120
|
end
|
@@ -6,7 +6,7 @@ module Ffmprb
|
|
6
6
|
|
7
7
|
# TODO the events mechanism is currently unused (and commented out) => synchro mechanism not needed
|
8
8
|
class ThreadedIoBuffer
|
9
|
-
#
|
9
|
+
# TODO? include Synchro
|
10
10
|
include ProcVis::Node
|
11
11
|
|
12
12
|
class << self
|
@@ -280,9 +280,11 @@ module Ffmprb
|
|
280
280
|
logged_timeouts = timeouts
|
281
281
|
end
|
282
282
|
|
283
|
-
retry unless
|
283
|
+
retry unless # NOTE the queue has probably overflown
|
284
|
+
timeouts >= ThreadedIoBuffer.timeout_limit
|
284
285
|
|
285
|
-
@reader_failed ||=
|
286
|
+
@reader_failed ||= # NOTE screw the race condition
|
287
|
+
Error.new("the writer has failed with timeout limit while queuing")
|
286
288
|
# timeout!
|
287
289
|
fail Error, "Looks like we're stuck (>#{ThreadedIoBuffer.timeout_limit*ThreadedIoBuffer.timeout}s idle) with #{ThreadedIoBuffer.blocks_max}x#{ThreadedIoBuffer.block_size}b blocks (buffering #{reader_input!.path}->...)..."
|
288
290
|
end
|
data/lib/ffmprb/util.rb
CHANGED
@@ -9,6 +9,8 @@ module Ffmprb
|
|
9
9
|
class BrokenPipeError < Error; end
|
10
10
|
class TimeLimitError < Error; end
|
11
11
|
|
12
|
+
FFMPEG_BROKEN_PIPE_ERROR_RE = /^.*\berror\b.*:.*\bbroken pipe\b.*$/i
|
13
|
+
|
12
14
|
class << self
|
13
15
|
|
14
16
|
attr_accessor :ffmpeg_cmd, :ffmpeg_inputs_max, :ffprobe_cmd
|
@@ -18,19 +20,25 @@ module Ffmprb
|
|
18
20
|
sh *ffprobe_cmd, *args, limit: limit, timeout: timeout
|
19
21
|
end
|
20
22
|
|
21
|
-
# TODO
|
23
|
+
# TODO un-colorise ffmpeg output for logging, also, convert ^M into something
|
22
24
|
def ffmpeg(*args, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: true)
|
23
|
-
args = [
|
25
|
+
args = %w[-loglevel debug] + args if
|
24
26
|
Ffmprb.ffmpeg_debug
|
25
|
-
sh *ffmpeg_cmd, *args,
|
27
|
+
sh *ffmpeg_cmd, *args,
|
28
|
+
output: :stderr,
|
29
|
+
limit: limit,
|
30
|
+
timeout: timeout,
|
31
|
+
ignore_broken_pipes: ignore_broken_pipes,
|
32
|
+
broken_pipe_error_re: FFMPEG_BROKEN_PIPE_ERROR_RE
|
26
33
|
end
|
27
34
|
|
28
|
-
def sh(*cmd, input: nil, output: :stdout, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: false)
|
35
|
+
def sh(*cmd, input: nil, output: :stdout, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: false, broken_pipe_error_re: nil)
|
29
36
|
cmd = cmd.map &:to_s unless cmd.size == 1
|
30
37
|
cmd_str = cmd.size != 1 ? cmd.map{|c| sh_escape c}.join(' ') : cmd.first
|
38
|
+
cmd_log_line = "#{log_hash cmd_str}: `#{cmd_str}`"
|
31
39
|
timeout = [timeout, limit].compact.min
|
32
|
-
thr = Thread.new
|
33
|
-
Ffmprb.logger.info "Popening
|
40
|
+
thr = Thread.new cmd_log_line do
|
41
|
+
Ffmprb.logger.info "Popening #{cmd_log_line}..."
|
34
42
|
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
|
35
43
|
begin
|
36
44
|
stdin.write input if input
|
@@ -39,29 +47,33 @@ module Ffmprb
|
|
39
47
|
log_cmd = cmd.first.upcase
|
40
48
|
stdout_r = Reader.new(stdout, store: output == :stdout, log_with: log_cmd)
|
41
49
|
stderr_r = Reader.new(stderr, store: true, log_with: log_cmd, log_as: output == :stderr && Logger::DEBUG || Logger::INFO)
|
50
|
+
stderr_s = nil
|
42
51
|
|
43
|
-
Thread.timeout_or_live(limit, log: "while waiting for
|
52
|
+
Thread.timeout_or_live(limit, log: "while waiting for #{cmd_log_line}", timeout: timeout) do |time|
|
44
53
|
value = wait_thr.value
|
45
54
|
status = value.exitstatus # NOTE blocking
|
46
55
|
if status != 0
|
47
|
-
|
56
|
+
stderr_s = stderr_r.read
|
57
|
+
if (value.signaled? && value.termsig == Signal.list['PIPE']) ||
|
58
|
+
# NOTE this doesn't seem to work for ffmpeg 4.x (it ignores SIGPIPEs)
|
59
|
+
(broken_pipe_error_re && status == 1 && stderr_s =~ broken_pipe_error_re)
|
48
60
|
if ignore_broken_pipes
|
49
|
-
Ffmprb.logger.info "Ignoring broken pipe: #{
|
61
|
+
Ffmprb.logger.info "Ignoring broken pipe: #{cmd_log_line}"
|
50
62
|
else
|
51
|
-
fail BrokenPipeError,
|
63
|
+
fail BrokenPipeError, cmd_log_line
|
52
64
|
end
|
53
65
|
else
|
54
66
|
status ||= "sig##{value.termsig}"
|
55
|
-
fail Error, "#{
|
67
|
+
fail Error, "#{cmd_log_line} (#{status}):\n#{stderr_s}"
|
56
68
|
end
|
57
69
|
end
|
58
70
|
end
|
59
|
-
Ffmprb.logger.debug{"FINISHED: #{
|
71
|
+
Ffmprb.logger.debug{"FINISHED: #{cmd_log_line}"}
|
60
72
|
|
61
73
|
Thread.join_children! limit, timeout: timeout
|
62
74
|
|
63
75
|
# NOTE only one of them will return non-nil, see above
|
64
|
-
stdout_r.read || stderr_r.read
|
76
|
+
stdout_r.read || stderr_s || stderr_r.read
|
65
77
|
ensure
|
66
78
|
process_dead! wait_thr, cmd_str, limit
|
67
79
|
end
|
@@ -73,7 +85,18 @@ module Ffmprb
|
|
73
85
|
def assert_options_empty!(opts)
|
74
86
|
fail ArgumentError, "Unknown options: #{opts}" unless opts.empty?
|
75
87
|
end
|
76
|
-
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def log_hash(s)
|
92
|
+
n = s.hash
|
93
|
+
%w[bcdfghk aeiuy mnprqstvwxz].reduce '' do |hash, chars|
|
94
|
+
hash + chars[n % chars.length]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def broken_pipe_error_printed?(s)
|
99
|
+
end
|
77
100
|
|
78
101
|
# NOTE a best guess kinda method
|
79
102
|
def sh_escape(str)
|
data/lib/ffmprb/version.rb
CHANGED
data/lib/ffmprb.rb
CHANGED
@@ -13,8 +13,10 @@ module Ffmprb
|
|
13
13
|
|
14
14
|
CGA = '320x200'
|
15
15
|
QVGA = '320x240'
|
16
|
+
HD_480p = '854x480'
|
16
17
|
HD_720p = '1280x720'
|
17
18
|
HD_1080p = '1920x1080'
|
19
|
+
HD_4K = '3840x2160'
|
18
20
|
|
19
21
|
class << self
|
20
22
|
|
@@ -22,9 +24,10 @@ module Ffmprb
|
|
22
24
|
def process(*args, name: nil, **opts, &blk)
|
23
25
|
fail Error, "process: nothing ;( gimme a block!" unless blk
|
24
26
|
|
25
|
-
name ||= blk.source_location.map(&:to_s).map{ |s|
|
27
|
+
name ||= blk.source_location.map(&:to_s).map{ |s| File.basename s.to_s, File.extname(s) }.join(':')
|
26
28
|
process = Process.new(name: name, **opts)
|
27
|
-
|
29
|
+
# TODO simply include the ProcVis if it makes into a gem
|
30
|
+
proc_vis_node process if respond_to? :proc_vis_node
|
28
31
|
logger.debug{"Starting process with #{args} #{opts} in #{blk.source_location}"}
|
29
32
|
|
30
33
|
process.instance_exec *args, &blk
|
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffmprb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Costa Shapiro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mkfifo
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- Rakefile
|
56
56
|
- TODO.md
|
57
57
|
- bin/console
|
58
|
+
- bin/dev
|
58
59
|
- bin/guard
|
59
60
|
- bin/rake
|
60
61
|
- bin/rspec
|
@@ -137,8 +138,70 @@ files:
|
|
137
138
|
- coverage/assets/0.12.2/images/ui-icons_cd0a0a_256x240.png
|
138
139
|
- coverage/assets/0.12.2/loading.gif
|
139
140
|
- coverage/assets/0.12.2/magnify.png
|
141
|
+
- coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png
|
142
|
+
- coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png
|
143
|
+
- coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png
|
144
|
+
- coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png
|
145
|
+
- coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png
|
146
|
+
- coverage/assets/0.12.3/application.css
|
147
|
+
- coverage/assets/0.12.3/application.js
|
148
|
+
- coverage/assets/0.12.3/colorbox/border.png
|
149
|
+
- coverage/assets/0.12.3/colorbox/controls.png
|
150
|
+
- coverage/assets/0.12.3/colorbox/loading.gif
|
151
|
+
- coverage/assets/0.12.3/colorbox/loading_background.png
|
152
|
+
- coverage/assets/0.12.3/favicon_green.png
|
153
|
+
- coverage/assets/0.12.3/favicon_red.png
|
154
|
+
- coverage/assets/0.12.3/favicon_yellow.png
|
155
|
+
- coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png
|
156
|
+
- coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png
|
157
|
+
- coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png
|
158
|
+
- coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png
|
159
|
+
- coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png
|
160
|
+
- coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png
|
161
|
+
- coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png
|
162
|
+
- coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png
|
163
|
+
- coverage/assets/0.12.3/images/ui-icons_222222_256x240.png
|
164
|
+
- coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png
|
165
|
+
- coverage/assets/0.12.3/images/ui-icons_454545_256x240.png
|
166
|
+
- coverage/assets/0.12.3/images/ui-icons_888888_256x240.png
|
167
|
+
- coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png
|
168
|
+
- coverage/assets/0.12.3/loading.gif
|
169
|
+
- coverage/assets/0.12.3/magnify.png
|
140
170
|
- coverage/index.html
|
141
171
|
- exe/ffmprb
|
172
|
+
- exp/EXP
|
173
|
+
- exp/av-cut-mp4you60.ffmprb
|
174
|
+
- exp/docker-compose.yml
|
175
|
+
- exp/gop-cut-cat-you60
|
176
|
+
- exp/present/Dockerfile
|
177
|
+
- exp/present/Gemfile
|
178
|
+
- exp/present/Gemfile.lock
|
179
|
+
- exp/present/bin/up-deps
|
180
|
+
- exp/present/docker-compose.yml
|
181
|
+
- exp/present/exp/present
|
182
|
+
- exp/present/exp/present.rb
|
183
|
+
- exp/run
|
184
|
+
- exp/stitch2.ffmprb
|
185
|
+
- exp/unzip-mp4you60
|
186
|
+
- exp/youtubby/Dockerfile
|
187
|
+
- exp/youtubby/Gemfile
|
188
|
+
- exp/youtubby/Gemfile.lock
|
189
|
+
- exp/youtubby/README.md
|
190
|
+
- exp/youtubby/bin/up-deps
|
191
|
+
- exp/youtubby/docker-compose.yml
|
192
|
+
- exp/youtubby/exp/gop-raw-cut-you-HD60
|
193
|
+
- exp/youtubby/exp/gop-raw-cut-you-HD60.rb
|
194
|
+
- exp/youtubby/exp/media-upload
|
195
|
+
- exp/youtubby/exp/tmp/CURRENT
|
196
|
+
- exp/youtubby/google_youtube.rb
|
197
|
+
- exp/youtubby/old-ul.py
|
198
|
+
- exp/youtubby/old-ul.rb
|
199
|
+
- exp/youtubby/py-Dockerfile
|
200
|
+
- exp/youtubby/py-docker-compose.yml
|
201
|
+
- exp/youtubby/requirements.txt
|
202
|
+
- exp/youtubby/upload.rb
|
203
|
+
- exp/zip-cut-mp4you60
|
204
|
+
- exp/zip2mp4k60
|
142
205
|
- ffmprb.gemspec
|
143
206
|
- lib/defaults.rb
|
144
207
|
- lib/ffmprb.rb
|
@@ -156,6 +219,9 @@ files:
|
|
156
219
|
- lib/ffmprb/process/input/cut.rb
|
157
220
|
- lib/ffmprb/process/input/looping.rb
|
158
221
|
- lib/ffmprb/process/input/loud.rb
|
222
|
+
- lib/ffmprb/process/input/paced.rb
|
223
|
+
- lib/ffmprb/process/input/postprocessed.rb
|
224
|
+
- lib/ffmprb/process/input/reversed.rb
|
159
225
|
- lib/ffmprb/process/input/temp.rb
|
160
226
|
- lib/ffmprb/process/output.rb
|
161
227
|
- lib/ffmprb/util.rb
|
@@ -164,6 +230,9 @@ files:
|
|
164
230
|
- lib/ffmprb/util/thread.rb
|
165
231
|
- lib/ffmprb/util/threaded_io_buffer.rb
|
166
232
|
- lib/ffmprb/version.rb
|
233
|
+
- tmp/exp/docker-compose.yml
|
234
|
+
- tmp/exp/src/SAM_3132.MP4
|
235
|
+
- tmp/ffmprb-0.11.4.gem
|
167
236
|
- tmp/output.rb
|
168
237
|
homepage: https://github.com/costa/ffmprb
|
169
238
|
licenses: []
|
@@ -184,8 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
253
|
- !ruby/object:Gem::Version
|
185
254
|
version: '0'
|
186
255
|
requirements: []
|
187
|
-
|
188
|
-
rubygems_version: 2.5.2.3
|
256
|
+
rubygems_version: 3.0.3
|
189
257
|
signing_key:
|
190
258
|
specification_version: 4
|
191
259
|
summary: ffmprb is your audio/video manipulation pal, based on https://ffmpeg.org
|