ffmprb 0.11.4 → 0.12.1

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.
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