ffmprb 0.11.4 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/Dockerfile +11 -5
  3. data/Gemfile +5 -5
  4. data/Gemfile.lock +54 -54
  5. data/README.md +57 -15
  6. data/TODO.md +0 -1
  7. data/bin/dev +12 -0
  8. data/bin/test +9 -3
  9. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png +0 -0
  10. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  11. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png +0 -0
  12. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png +0 -0
  13. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  14. data/coverage/assets/0.12.3/application.css +1 -0
  15. data/coverage/assets/0.12.3/application.js +7 -0
  16. data/coverage/assets/0.12.3/colorbox/border.png +0 -0
  17. data/coverage/assets/0.12.3/colorbox/controls.png +0 -0
  18. data/coverage/assets/0.12.3/colorbox/loading.gif +0 -0
  19. data/coverage/assets/0.12.3/colorbox/loading_background.png +0 -0
  20. data/coverage/assets/0.12.3/favicon_green.png +0 -0
  21. data/coverage/assets/0.12.3/favicon_red.png +0 -0
  22. data/coverage/assets/0.12.3/favicon_yellow.png +0 -0
  23. data/coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  24. data/coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  25. data/coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  26. data/coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  27. data/coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  28. data/coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  29. data/coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  30. data/coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  31. data/coverage/assets/0.12.3/images/ui-icons_222222_256x240.png +0 -0
  32. data/coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png +0 -0
  33. data/coverage/assets/0.12.3/images/ui-icons_454545_256x240.png +0 -0
  34. data/coverage/assets/0.12.3/images/ui-icons_888888_256x240.png +0 -0
  35. data/coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png +0 -0
  36. data/coverage/assets/0.12.3/loading.gif +0 -0
  37. data/coverage/assets/0.12.3/magnify.png +0 -0
  38. data/coverage/index.html +47166 -24254
  39. data/exp/EXP +7 -0
  40. data/exp/av-cut-mp4you60.ffmprb +10 -0
  41. data/exp/docker-compose.yml +9 -0
  42. data/exp/gop-cut-cat-you60 +141 -0
  43. data/exp/present/Dockerfile +13 -0
  44. data/exp/present/Gemfile +3 -0
  45. data/exp/present/Gemfile.lock +22 -0
  46. data/exp/present/bin/up-deps +8 -0
  47. data/exp/present/docker-compose.yml +21 -0
  48. data/exp/present/exp/present +10 -0
  49. data/exp/present/exp/present.rb +37 -0
  50. data/exp/run +9 -0
  51. data/exp/stitch2.ffmprb +5 -0
  52. data/exp/unzip-mp4you60 +58 -0
  53. data/exp/youtubby/Dockerfile +13 -0
  54. data/exp/youtubby/Gemfile +7 -0
  55. data/exp/youtubby/Gemfile.lock +73 -0
  56. data/exp/youtubby/README.md +21 -0
  57. data/exp/youtubby/bin/up-deps +8 -0
  58. data/exp/youtubby/docker-compose.yml +20 -0
  59. data/exp/youtubby/exp/gop-raw-cut-you-HD60 +13 -0
  60. data/exp/youtubby/exp/gop-raw-cut-you-HD60.rb +230 -0
  61. data/exp/youtubby/exp/media-upload +13 -0
  62. data/exp/youtubby/exp/tmp/CURRENT +2 -0
  63. data/exp/youtubby/google_youtube.rb +39 -0
  64. data/exp/youtubby/old-ul.py +181 -0
  65. data/exp/youtubby/old-ul.rb +87 -0
  66. data/exp/youtubby/py-Dockerfile +11 -0
  67. data/exp/youtubby/py-docker-compose.yml +13 -0
  68. data/exp/youtubby/requirements.txt +19 -0
  69. data/exp/youtubby/upload.rb +38 -0
  70. data/exp/zip-cut-mp4you60 +42 -0
  71. data/exp/zip2mp4k60 +27 -0
  72. data/lib/defaults.rb +5 -5
  73. data/lib/ffmprb/execution.rb +1 -1
  74. data/lib/ffmprb/file/threaded_buffered.rb +1 -1
  75. data/lib/ffmprb/file.rb +14 -14
  76. data/lib/ffmprb/filter.rb +96 -60
  77. data/lib/ffmprb/process/input/chain_base.rb +6 -4
  78. data/lib/ffmprb/process/input/channeled.rb +1 -1
  79. data/lib/ffmprb/process/input/cropped.rb +4 -1
  80. data/lib/ffmprb/process/input/cut.rb +2 -2
  81. data/lib/ffmprb/process/input/looping.rb +5 -5
  82. data/lib/ffmprb/process/input/loud.rb +1 -1
  83. data/lib/ffmprb/process/input/paced.rb +35 -0
  84. data/lib/ffmprb/process/input/postprocessed.rb +29 -0
  85. data/lib/ffmprb/process/input/reversed.rb +29 -0
  86. data/lib/ffmprb/process/input.rb +6 -2
  87. data/lib/ffmprb/process/output.rb +36 -23
  88. data/lib/ffmprb/process.rb +3 -6
  89. data/lib/ffmprb/util/proc_vis.rb +4 -3
  90. data/lib/ffmprb/util/thread.rb +8 -7
  91. data/lib/ffmprb/util/threaded_io_buffer.rb +5 -3
  92. data/lib/ffmprb/util.rb +37 -14
  93. data/lib/ffmprb/version.rb +1 -1
  94. data/lib/ffmprb.rb +5 -2
  95. data/tmp/exp/docker-compose.yml +9 -0
  96. data/tmp/exp/src/SAM_3132.MP4 +0 -0
  97. data/tmp/ffmprb-0.11.4.gem +0 -0
  98. metadata +72 -4
@@ -7,9 +7,10 @@ module Ffmprb
7
7
  class << self
8
8
 
9
9
  def resolve(io)
10
- return io unless io.is_a? String # XXX XXX
10
+ return io unless
11
+ io.is_a? String
11
12
 
12
- File.open(io).tap do |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 = pixel_format = nil # NOTE ah, ruby
15
- args.concat %W[-c:v #{encoder}] if (encoder = video.delete(:encoder))
16
- args.concat %W[-pix_fmt #{pixel_format}] if (pixel_format = video.delete(:pixel_format))
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 = nil
27
- args.concat %W[-c:a #{encoder}] if (encoder = audio.delete(:encoder))
28
- args.concat %W[-ar #{sampling_freq}] if (sampling_freq = audio.delete(:sampling_freq))
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 io.is_a? String # XXX XXX
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
- # XXX This method is exceptionally long at the moment. This is not too grand.
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 @reels
64
- fail Error, "Supporting just full_screen for now, sorry." unless @reels.all?(&:full_screen?)
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
- curr_reel.reel.filters_for lbl, video: channel(:video), audio: channel(:audio)
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, channel(:video).fps, "#{lbl_pad}:v"
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, channel(:video).fps, "#{lbl_aux}:v"
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, channel(:video).fps, ["#{lbl_end1}:v", "#{lbl || lbl_aux}:v"], "#{lbl_reel}:v"
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 = {} # XXX this is a spaghetti machine
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
- # XXX check if we're on audio channel
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 # XXX ~~~spaghetti
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 # XXX maybe throw an exception instead?
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 # XXX mark it better
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 = {} # XXX ~~~spaghetti
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
@@ -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
- if value
199
- default.merge(value == true ? {} : value.to_h)
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
 
@@ -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 obj.kind_of? Node # XXX duck typing FTW
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 # XXX optimise
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! # XXX not the best we can do here
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
@@ -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
- Ffmprb.logger.debug{"about to launch #{name}"}
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
- fail $! # XXX I have no idea why I need to give it `$!` -- the docs say I need not
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
- # XXX include Synchro
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 timeouts >= ThreadedIoBuffer.timeout_limit # NOTE the queue has probably overflown
283
+ retry unless # NOTE the queue has probably overflown
284
+ timeouts >= ThreadedIoBuffer.timeout_limit
284
285
 
285
- @reader_failed ||= Error.new("the writer has failed with timeout limit while queuing") # NOTE screw the race condition
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 warn on broken pipes incompatibility with 4.x or something
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 = ['-loglevel', 'debug'] + args if
25
+ args = %w[-loglevel debug] + args if
24
26
  Ffmprb.ffmpeg_debug
25
- sh *ffmpeg_cmd, *args, output: :stderr, limit: limit, timeout: timeout, ignore_broken_pipes: ignore_broken_pipes
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 "`#{cmd_str}`" do
33
- Ffmprb.logger.info "Popening `#{cmd_str}`..."
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 `#{cmd_str}`", timeout: timeout) do |time|
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
- if value.signaled? && value.termsig == Signal.list['PIPE'] # TODO! this doesn't seem to work for ffmpeg 4.x (it ignores SIGPIPEs)
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: #{cmd_str}"
61
+ Ffmprb.logger.info "Ignoring broken pipe: #{cmd_log_line}"
50
62
  else
51
- fail BrokenPipeError, cmd_str
63
+ fail BrokenPipeError, cmd_log_line
52
64
  end
53
65
  else
54
66
  status ||= "sig##{value.termsig}"
55
- fail Error, "#{cmd_str} (#{status}):\n#{stderr_r.read}"
67
+ fail Error, "#{cmd_log_line} (#{status}):\n#{stderr_s}"
56
68
  end
57
69
  end
58
70
  end
59
- Ffmprb.logger.debug{"FINISHED: #{cmd_str}"}
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
- protected
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)
@@ -1,6 +1,6 @@
1
1
  module Ffmprb
2
2
 
3
- VERSION = '0.11.4'
3
+ VERSION = '0.12.1'
4
4
 
5
5
  GEM_GITHUB_URL = 'https://github.com/costa/ffmprb'
6
6
 
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| ::File.basename s.to_s, ::File.extname(s) }.join(':')
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
- proc_vis_node process if respond_to? :proc_vis_node # XXX simply include the ProcVis if it makes into a gem
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
@@ -0,0 +1,9 @@
1
+ version: '2.1'
2
+ services:
3
+ tester:
4
+ build: ../..
5
+ volumes:
6
+ - ../..:/ffmprb
7
+ environment:
8
+ - FFMPRB_DEBUG=1
9
+ command: tmp/exp/src
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.11.4
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: 2020-08-12 00:00:00.000000000 Z
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
- rubyforge_project:
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