ffmprb 0.11.3 → 0.11.4

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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +12 -0
  3. data/Gemfile +8 -1
  4. data/Gemfile.lock +121 -0
  5. data/README.md +10 -8
  6. data/TODO.md +1 -0
  7. data/bin/test +7 -0
  8. data/coverage/assets/0.10.0/application.css +799 -0
  9. data/coverage/assets/0.10.0/application.js +1707 -0
  10. data/coverage/assets/0.10.0/colorbox/border.png +0 -0
  11. data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
  12. data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
  13. data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
  14. data/coverage/assets/0.10.0/favicon_green.png +0 -0
  15. data/coverage/assets/0.10.0/favicon_red.png +0 -0
  16. data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
  17. data/coverage/assets/0.10.0/loading.gif +0 -0
  18. data/coverage/assets/0.10.0/magnify.png +0 -0
  19. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  20. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  21. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  22. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  23. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  24. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  25. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  26. data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  27. data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  28. data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  29. data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  30. data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  31. data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  32. data/coverage/assets/0.10.2/application.css +799 -0
  33. data/coverage/assets/0.10.2/application.js +1707 -0
  34. data/coverage/assets/0.10.2/colorbox/border.png +0 -0
  35. data/coverage/assets/0.10.2/colorbox/controls.png +0 -0
  36. data/coverage/assets/0.10.2/colorbox/loading.gif +0 -0
  37. data/coverage/assets/0.10.2/colorbox/loading_background.png +0 -0
  38. data/coverage/assets/0.10.2/favicon_green.png +0 -0
  39. data/coverage/assets/0.10.2/favicon_red.png +0 -0
  40. data/coverage/assets/0.10.2/favicon_yellow.png +0 -0
  41. data/coverage/assets/0.10.2/loading.gif +0 -0
  42. data/coverage/assets/0.10.2/magnify.png +0 -0
  43. data/coverage/assets/0.10.2/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  44. data/coverage/assets/0.10.2/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  45. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  46. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  47. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  48. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  49. data/coverage/assets/0.10.2/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  50. data/coverage/assets/0.10.2/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  51. data/coverage/assets/0.10.2/smoothness/images/ui-icons_222222_256x240.png +0 -0
  52. data/coverage/assets/0.10.2/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  53. data/coverage/assets/0.10.2/smoothness/images/ui-icons_454545_256x240.png +0 -0
  54. data/coverage/assets/0.10.2/smoothness/images/ui-icons_888888_256x240.png +0 -0
  55. data/coverage/assets/0.10.2/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  56. data/coverage/assets/0.12.2/DataTables-1.10.20/images/sort_asc.png +0 -0
  57. data/coverage/assets/0.12.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  58. data/coverage/assets/0.12.2/DataTables-1.10.20/images/sort_both.png +0 -0
  59. data/coverage/assets/0.12.2/DataTables-1.10.20/images/sort_desc.png +0 -0
  60. data/coverage/assets/0.12.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  61. data/coverage/assets/0.12.2/application.css +1 -0
  62. data/coverage/assets/0.12.2/application.js +7 -0
  63. data/coverage/assets/0.12.2/colorbox/border.png +0 -0
  64. data/coverage/assets/0.12.2/colorbox/controls.png +0 -0
  65. data/coverage/assets/0.12.2/colorbox/loading.gif +0 -0
  66. data/coverage/assets/0.12.2/colorbox/loading_background.png +0 -0
  67. data/coverage/assets/0.12.2/favicon_green.png +0 -0
  68. data/coverage/assets/0.12.2/favicon_red.png +0 -0
  69. data/coverage/assets/0.12.2/favicon_yellow.png +0 -0
  70. data/coverage/assets/0.12.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  71. data/coverage/assets/0.12.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  72. data/coverage/assets/0.12.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  73. data/coverage/assets/0.12.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  74. data/coverage/assets/0.12.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  75. data/coverage/assets/0.12.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  76. data/coverage/assets/0.12.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  77. data/coverage/assets/0.12.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  78. data/coverage/assets/0.12.2/images/ui-icons_222222_256x240.png +0 -0
  79. data/coverage/assets/0.12.2/images/ui-icons_2e83ff_256x240.png +0 -0
  80. data/coverage/assets/0.12.2/images/ui-icons_454545_256x240.png +0 -0
  81. data/coverage/assets/0.12.2/images/ui-icons_888888_256x240.png +0 -0
  82. data/coverage/assets/0.12.2/images/ui-icons_cd0a0a_256x240.png +0 -0
  83. data/coverage/assets/0.12.2/loading.gif +0 -0
  84. data/coverage/assets/0.12.2/magnify.png +0 -0
  85. data/coverage/index.html +24412 -0
  86. data/ffmprb.gemspec +34 -14
  87. data/lib/defaults.rb +1 -1
  88. data/lib/ffmprb.rb +3 -3
  89. data/lib/ffmprb/file.rb +8 -8
  90. data/lib/ffmprb/file/sample.rb +2 -2
  91. data/lib/ffmprb/file/threaded_buffered.rb +3 -3
  92. data/lib/ffmprb/filter.rb +4 -1
  93. data/lib/ffmprb/find_silence.rb +5 -2
  94. data/lib/ffmprb/process.rb +5 -3
  95. data/lib/ffmprb/process/input.rb +1 -1
  96. data/lib/ffmprb/process/input/looping.rb +6 -11
  97. data/lib/ffmprb/process/output.rb +9 -6
  98. data/lib/ffmprb/util.rb +5 -3
  99. data/lib/ffmprb/util/proc_vis.rb +1 -1
  100. data/lib/ffmprb/util/thread.rb +6 -5
  101. data/lib/ffmprb/util/threaded_io_buffer.rb +20 -19
  102. data/lib/ffmprb/version.rb +2 -2
  103. data/tmp/output.rb +383 -0
  104. metadata +90 -138
  105. data/.gitignore +0 -10
  106. data/.rspec +0 -4
  107. data/.ruby-version +0 -1
  108. data/.travis.yml +0 -3
  109. data/circle.yml +0 -7
@@ -29,7 +29,7 @@ module Ffmprb
29
29
  def initialize(input, *outputs, keep_outputs_open_on_input_idle_limit: nil)
30
30
  super() # NOTE for the monitor, apparently
31
31
 
32
- Ffmprb.logger.debug "ThreadedIoBuffer initializing with (#{ThreadedIoBuffer.blocks_max}x#{ThreadedIoBuffer.block_size})"
32
+ Ffmprb.logger.debug{"ThreadedIoBuffer initializing with (#{ThreadedIoBuffer.blocks_max}x#{ThreadedIoBuffer.block_size})"}
33
33
 
34
34
  @input = input
35
35
  @outputs = outputs.map do |outp|
@@ -47,10 +47,11 @@ module Ffmprb
47
47
  end
48
48
 
49
49
  Thread.join_children!.tap do
50
- Ffmprb.logger.debug "ThreadedIoBuffer (#{@input.path}->#{@outputs.map(&:io).map(&:path)}) terminated successfully (#{stats})"
50
+ Ffmprb.logger.debug{"ThreadedIoBuffer (#{@input.path}->#{@outputs.map(&:io).map(&:path)}) terminated successfully (#{stats})"}
51
51
  end
52
52
  end
53
53
  end
54
+ # TODO?
54
55
  #
55
56
  # def once(event, &blk)
56
57
  # event = event.to_sym
@@ -58,17 +59,17 @@ module Ffmprb
58
59
  # if @events[event].respond_to? :call
59
60
  # fail Error, "Once upon a time (one #once(event) at a time) please"
60
61
  # elsif @events[event]
61
- # Ffmprb.logger.debug "ThreadedIoBuffer (post-)reacting to #{event}"
62
+ # Ffmprb.logger.debug{"ThreadedIoBuffer (post-)reacting to #{event}"}
62
63
  # @handler_thr = Util::Thread.new "#{event} handler", &blk
63
64
  # else
64
- # Ffmprb.logger.debug "ThreadedIoBuffer subscribing to #{event}"
65
+ # Ffmprb.logger.debug{"ThreadedIoBuffer subscribing to #{event}"}
65
66
  # @events[event] = blk
66
67
  # end
67
68
  # end
68
69
  # handle_synchronously :once
69
70
  #
70
71
  # def reader_done!
71
- # Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (#{stats})"
72
+ # Ffmprb.logger.debug{"ThreadedIoBuffer reader terminated (#{stats})"}
72
73
  # fire! :reader_done
73
74
  # end
74
75
  #
@@ -84,7 +85,7 @@ module Ffmprb
84
85
  #
85
86
  # def fire!(event)
86
87
  # wait_for_handler!
87
- # Ffmprb.logger.debug "ThreadedIoBuffer firing #{event}"
88
+ # Ffmprb.logger.debug{"ThreadedIoBuffer firing #{event}"}
88
89
  # if blk = @events.to_h[event.to_sym]
89
90
  # @handler_thr = Util::Thread.new "#{event} handler", &blk
90
91
  # end
@@ -104,9 +105,9 @@ module Ffmprb
104
105
 
105
106
  def reader_input! # NOTE just for reader thread
106
107
  if @input.respond_to?(:call)
107
- Ffmprb.logger.debug "Opening buffer input"
108
+ Ffmprb.logger.debug{"Opening buffer input"}
108
109
  @input = @input.call
109
- Ffmprb.logger.debug "Opened buffer input: #{@input.path}"
110
+ Ffmprb.logger.debug{"Opened buffer input: #{@input.path}"}
110
111
  end
111
112
  @input
112
113
  end
@@ -137,17 +138,17 @@ module Ffmprb
137
138
  rescue IO::WaitReadable
138
139
  if @keep_outputs_open_on_input_idle_limit && stats.bytes_in > 0 && stats.blocks_buff == 0 && timeouts * ThreadedIoBuffer.io_wait_timeout > @keep_outputs_open_on_input_idle_limit
139
140
  if s.length > 0 # NOTE let's see if it helps outputting an incomplete block
140
- Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) giving a chance to write #{s.length}/#{ThreadedIoBuffer.block_size}b after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{stats.bytes_in}b"
141
+ Ffmprb.logger.debug{"ThreadedIoBuffer reader (from #{input_io.path}) giving a chance to write #{s.length}/#{ThreadedIoBuffer.block_size}b after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{stats.bytes_in}b"}
141
142
  break
142
143
  else
143
- Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) giving up after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{stats.bytes_in}b, closing outputs"
144
+ Ffmprb.logger.debug{"ThreadedIoBuffer reader (from #{input_io.path}) giving up after waiting >#{@keep_outputs_open_on_input_idle_limit}s, after reading #{stats.bytes_in}b, closing outputs"}
144
145
  raise EOFError
145
146
  end
146
147
  else
147
148
  Thread.current.live!
148
149
  timeouts += 1
149
150
  if timeouts > 2 * logged_timeouts
150
- Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) retrying... (#{timeouts} reads): #{$!.class}"
151
+ Ffmprb.logger.debug{"ThreadedIoBuffer reader (from #{input_io.path}) retrying... (#{timeouts} reads): #{$!.class}"}
151
152
  logged_timeouts = timeouts
152
153
  end
153
154
  IO.select [input_io], nil, nil, ThreadedIoBuffer.io_wait_timeout
@@ -165,7 +166,7 @@ module Ffmprb
165
166
  output_enq! s
166
167
  end
167
168
  rescue EOFError
168
- Ffmprb.logger.debug "ThreadedIoBuffer reader (from #{input_io.path}) breaking off"
169
+ Ffmprb.logger.debug{"ThreadedIoBuffer reader (from #{input_io.path}) breaking off"}
169
170
  rescue AllOutputsBrokenError
170
171
  Ffmprb.logger.info "All outputs broken"
171
172
  rescue Exception
@@ -182,7 +183,7 @@ module Ffmprb
182
183
  Ffmprb.logger.error "#{$!.class.name} closing ThreadedIoBuffer input: #{$!.message}"
183
184
  end
184
185
  # reader_done!
185
- Ffmprb.logger.debug "ThreadedIoBuffer reader terminated (#{stats})"
186
+ Ffmprb.logger.debug{"ThreadedIoBuffer reader terminated (#{stats})"}
186
187
  end
187
188
  end
188
189
  end
@@ -191,13 +192,13 @@ module Ffmprb
191
192
  return output.io = output._io unless output._io.respond_to?(:call)
192
193
 
193
194
  output.thr = Thread.new("buffer writer output helper") do
194
- Ffmprb.logger.debug "Opening buffer output"
195
+ Ffmprb.logger.debug{"Opening buffer output"}
195
196
  output.io =
196
197
  Thread.timeout_or_live nil, log: "in the buffer writer helper thread", timeout: ThreadedIoBuffer.timeout do |time|
197
198
  fail Error, "giving up buffer writer init since the reader has failed (#{@reader_failed.message})" if @reader_failed
198
199
  output._io.call
199
200
  end
200
- Ffmprb.logger.debug "Opened buffer output: #{output.io.path}"
201
+ Ffmprb.logger.debug{"Opened buffer output: #{output.io.path}"}
201
202
  end
202
203
  end
203
204
 
@@ -223,7 +224,7 @@ module Ffmprb
223
224
  Thread.current.live!
224
225
  timeouts += 1
225
226
  if timeouts > 2 * logged_timeouts
226
- Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io.path}) retrying... (#{timeouts} writes): #{$!.class}"
227
+ Ffmprb.logger.debug{"ThreadedIoBuffer writer (to #{output_io.path}) retrying... (#{timeouts} writes): #{$!.class}"}
227
228
  logged_timeouts = timeouts
228
229
  end
229
230
  IO.select nil, [output_io], nil, ThreadedIoBuffer.io_wait_timeout
@@ -234,9 +235,9 @@ module Ffmprb
234
235
  retry
235
236
  end
236
237
  end
237
- Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io.path}) breaking off"
238
+ Ffmprb.logger.debug{"ThreadedIoBuffer writer (to #{output_io.path}) breaking off"}
238
239
  rescue Errno::EPIPE
239
- Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io.path}) broken"
240
+ Ffmprb.logger.debug{"ThreadedIoBuffer writer (to #{output_io.path}) broken"}
240
241
  output.broken = true
241
242
  ensure
242
243
  # terminated!
@@ -246,7 +247,7 @@ module Ffmprb
246
247
  Ffmprb.logger.error "#{$!.class.name} closing ThreadedIoBuffer output: #{$!.message}"
247
248
  end
248
249
  output.broken = true
249
- Ffmprb.logger.debug "ThreadedIoBuffer writer (to #{output_io && output_io.path}) terminated (#{stats})"
250
+ Ffmprb.logger.debug{"ThreadedIoBuffer writer (to #{output_io && output_io.path}) terminated (#{stats})"}
250
251
  end
251
252
  end
252
253
  end
@@ -1,8 +1,8 @@
1
1
  module Ffmprb
2
2
 
3
- VERSION = '0.11.3'
3
+ VERSION = '0.11.4'
4
4
 
5
- GEM_GITHUB_URL = 'https://github.com/showbox-oss/ffmprb'
5
+ GEM_GITHUB_URL = 'https://github.com/costa/ffmprb'
6
6
 
7
7
  FIREBASE_AVAILABLE =
8
8
  begin
@@ -0,0 +1,383 @@
1
+ module Ffmprb
2
+
3
+ class Process
4
+
5
+ class Output
6
+
7
+ class << self
8
+
9
+ def video_args(video=nil)
10
+ video = Process.output_video_options.merge(video.to_h)
11
+ [].tap do |args|
12
+ encoder = pixel_format = nil # NOTE ah, ruby
13
+ args.concat %W[-c:v #{encoder}] if (encoder = video.delete(:encoder))
14
+ args.concat %W[-pix_fmt #{pixel_format}] if (pixel_format = video.delete(:pixel_format))
15
+ video.delete :resolution # NOTE is handled otherwise
16
+ video.delete :fps # NOTE is handled otherwise
17
+ Util.assert_options_empty! video
18
+ end
19
+ end
20
+
21
+ def audio_args(audio=nil)
22
+ audio = Process.output_audio_options.merge(audio.to_h)
23
+ [].tap do |args|
24
+ encoder = nil
25
+ args.concat %W[-c:a #{encoder}] if (encoder = audio.delete(:encoder))
26
+ args.concat %W[-ar #{sampling_freq}] if (sampling_freq = audio.delete(:sampling_freq))
27
+ Util.assert_options_empty! audio
28
+ end
29
+ end
30
+
31
+ def resolve(io)
32
+ return io unless io.is_a? String # XXX ::File
33
+
34
+ File.create(io).tap do |file|
35
+ Ffmprb.logger.warn "Output file exists (#{file.path}), will probably overwrite" if file.exist?
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ attr_reader :io
42
+ attr_reader :process
43
+
44
+ def initialize(io, process, video:, audio:)
45
+ @io = self.class.resolve(io)
46
+ @process = process
47
+ @channels = {
48
+ video: video && @io.channel?(:video) && OpenStruct.new(video),
49
+ audio: audio && @io.channel?(:audio) && OpenStruct.new(audio)
50
+ }
51
+ @lays = []
52
+ @overlays = []
53
+ @overducks = []
54
+
55
+ if channel?(:video)
56
+ channel(:video).resolution.to_s.split('x').each do |dim|
57
+ fail Error, "Both dimensions of a resolution must be divisible by 2, sorry about that" unless dim.to_i % 2 == 0
58
+ end
59
+ end
60
+ end
61
+
62
+ # XXX This method is exceptionally long at the moment. This is not too grand.
63
+ # However, structuring the code should be undertaken with care, as not to harm the composition clarity.
64
+ def filters
65
+ if @filters
66
+ Ffmprb.logger.warn "Output filters called for the second time, disregarding any changes to #{self}. If you're an end user, this calls for a bug report, if you're a dev, welcome to Debugland."
67
+ return @filters
68
+ end
69
+ fail Error, "Nothing to roll..." if @lays.empty?
70
+
71
+ idx = process.output_index(self)
72
+
73
+ @filters = []
74
+
75
+ # Concatting
76
+ segments = []
77
+
78
+ @lays.each_with_index do |curr_lay, i|
79
+
80
+ lbl = nil
81
+
82
+ if curr_lay.reel
83
+
84
+ # NOTE mapping input to this lbl
85
+
86
+ lbl = "o#{idx}rl#{i}"
87
+
88
+ # NOTE Image-Padding to match the target resolution
89
+ # TODO full screen only at the moment (see exception above)
90
+
91
+ @filters.concat(
92
+ curr_lay.reel.filters_for lbl, video: channel(:video), audio: channel(:audio)
93
+ )
94
+ end
95
+
96
+ trim_prev_at = curr_lay.after || (curr_lay.transition && 0)
97
+ transition_length = curr_lay.transition ? curr_lay.transition.length : 0
98
+
99
+ if trim_prev_at
100
+
101
+ # NOTE make sure previous lay rolls _long_ enough AND then _just_ enough
102
+
103
+ prev_lbl = segments.pop
104
+
105
+ lbl_pad = "bl#{prev_lbl}#{i}"
106
+ # NOTE generously padding the previous segment to support for all the cases
107
+ @filters.concat(
108
+ Filter.blank_source trim_prev_at + transition_length, channel(:video).resolution, channel(:video).fps, "#{lbl_pad}:v"
109
+ ) if channel?(:video)
110
+ @filters.concat(
111
+ Filter.silent_source trim_prev_at + transition_length, "#{lbl_pad}:a"
112
+ ) if channel?(:audio)
113
+
114
+ if prev_lbl
115
+ lbl_aux = lbl_pad
116
+ lbl_pad = "pd#{prev_lbl}#{i}"
117
+ @filters.concat(
118
+ Filter.concat_v ["#{prev_lbl}:v", "#{lbl_aux}:v"], "#{lbl_pad}:v"
119
+ ) if channel?(:video)
120
+ @filters.concat(
121
+ Filter.concat_a ["#{prev_lbl}:a", "#{lbl_aux}:a"], "#{lbl_pad}:a"
122
+ ) if channel?(:audio)
123
+ end
124
+
125
+ if curr_lay.transition
126
+
127
+ # NOTE Split the previous segment for transition
128
+
129
+ if trim_prev_at > 0
130
+ @filters.concat(
131
+ Filter.split "#{lbl_pad}:v", ["#{lbl_pad}a:v", "#{lbl_pad}b:v"]
132
+ ) if channel?(:video)
133
+ @filters.concat(
134
+ Filter.asplit "#{lbl_pad}:a", ["#{lbl_pad}a:a", "#{lbl_pad}b:a"]
135
+ ) if channel?(:audio)
136
+ lbl_pad, lbl_pad_ = "#{lbl_pad}a", "#{lbl_pad}b"
137
+ else
138
+ lbl_pad, lbl_pad_ = nil, lbl_pad
139
+ end
140
+ end
141
+
142
+ if lbl_pad
143
+
144
+ # NOTE Trim the previous segment finally
145
+
146
+ new_prev_lbl = "tm#{prev_lbl}#{i}a"
147
+
148
+ @filters.concat(
149
+ Filter.trim 0, trim_prev_at, "#{lbl_pad}:v", "#{new_prev_lbl}:v"
150
+ ) if channel?(:video)
151
+ @filters.concat(
152
+ Filter.atrim 0, trim_prev_at, "#{lbl_pad}:a", "#{new_prev_lbl}:a"
153
+ ) if channel?(:audio)
154
+
155
+ segments << new_prev_lbl
156
+ Ffmprb.logger.debug{"Concatting segments: #{new_prev_lbl} pushed"}
157
+ end
158
+
159
+ if curr_lay.transition
160
+
161
+ # NOTE snip the end of the previous segment and combine with this lay
162
+
163
+ lbl_end1 = "o#{idx}tm#{i}b"
164
+ lbl_lay = "o#{idx}tn#{i}"
165
+
166
+ if !lbl # no lay
167
+ lbl_aux = "o#{idx}bk#{i}"
168
+ @filters.concat(
169
+ Filter.blank_source transition_length, channel(:video).resolution, channel(:video).fps, "#{lbl_aux}:v"
170
+ ) if channel?(:video)
171
+ @filters.concat(
172
+ Filter.silent_source transition_length, "#{lbl_aux}:a"
173
+ ) if channel?(:audio)
174
+ end # NOTE else hope lbl is long enough for the transition
175
+
176
+ @filters.concat(
177
+ Filter.trim trim_prev_at, trim_prev_at + transition_length, "#{lbl_pad_}:v", "#{lbl_end1}:v"
178
+ ) if channel?(:video)
179
+ @filters.concat(
180
+ Filter.atrim trim_prev_at, trim_prev_at + transition_length, "#{lbl_pad_}:a", "#{lbl_end1}:a"
181
+ ) if channel?(:audio)
182
+
183
+ # TODO the only supported transition, see #*lay
184
+ @filters.concat(
185
+ Filter.blend_v transition_length, channel(:video).resolution, channel(:video).fps, ["#{lbl_end1}:v", "#{lbl || lbl_aux}:v"], "#{lbl_lay}:v"
186
+ ) if channel?(:video)
187
+ @filters.concat(
188
+ Filter.blend_a transition_length, ["#{lbl_end1}:a", "#{lbl || lbl_aux}:a"], "#{lbl_lay}:a"
189
+ ) if channel?(:audio)
190
+
191
+ lbl = lbl_lay
192
+ end
193
+
194
+ end
195
+
196
+ segments << lbl # NOTE can be nil
197
+ end
198
+
199
+ segments.compact!
200
+
201
+ lbl_out = segments[0]
202
+
203
+ if segments.size > 1
204
+ lbl_out = "o#{idx}o"
205
+
206
+ @filters.concat(
207
+ Filter.concat_v segments.map{|s| "#{s}:v"}, "#{lbl_out}:v"
208
+ ) if channel?(:video)
209
+ @filters.concat(
210
+ Filter.concat_a segments.map{|s| "#{s}:a"}, "#{lbl_out}:a"
211
+ ) if channel?(:audio)
212
+ end
213
+
214
+ # Overlays
215
+
216
+ # NOTE in-process overlays first
217
+
218
+ @overlays.each_with_index do |over_lay, i|
219
+
220
+ # Audio overlaying
221
+
222
+ lbl_nxt = "o#{idx}o#{i}"
223
+
224
+ lbl_over = "o#{idx}l#{i}"
225
+ @filters.concat( # NOTE audio only, see above
226
+ over_lay.reel.filters_for lbl_over, video: false, audio: channel(:audio)
227
+ )
228
+ @filters.concat(
229
+ Filter.copy "#{lbl_out}:v", "#{lbl_nxt}:v"
230
+ ) if channel?(:video)
231
+ @filters.concat(
232
+ Filter.amix_to_first_same_volume ["#{lbl_out}:a", "#{lbl_over}:a"], "#{lbl_nxt}:a"
233
+ ) if channel?(:audio)
234
+
235
+ lbl_out = lbl_nxt
236
+ end
237
+
238
+ # NOTE multi-process overlays last
239
+
240
+ @channel_lbl_ios = {} # XXX this is a spaghetti machine
241
+ @channel_lbl_ios["#{lbl_out}:v"] = io if channel?(:video)
242
+ @channel_lbl_ios["#{lbl_out}:a"] = io if channel?(:audio)
243
+
244
+ @overducks.each_with_index do |over_duck, i|
245
+ # NOTE just audio for now
246
+
247
+ Ffmprb.logger.info "ATTENTION: ducking audio (due to the absence of a simple ffmpeg filter) does not support streaming main input. yet."
248
+
249
+ main_av_o = @channel_lbl_ios["#{lbl_out}:a"]
250
+ fail Error, "Main output does not contain audio to duck" unless main_av_o
251
+
252
+ intermediate_extname = Process.intermediate_channel_extname video: main_av_o.channel?(:video), audio: main_av_o.channel?(:audio)
253
+ 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)
254
+ @channel_lbl_ios.each do |channel_lbl, io|
255
+ @channel_lbl_ios[channel_lbl] = main_av_inter_i if io == main_av_o # XXX ~~~spaghetti
256
+ end
257
+ process.proc_vis_edge process, main_av_o, :remove
258
+ process.proc_vis_edge process, main_av_inter_i
259
+ Ffmprb.logger.debug{"Re-routed the main audio output (#{main_av_inter_i.path}->...->#{main_av_o.path}) through the process of audio ducking"}
260
+
261
+ over_a_i, over_a_o = File.threaded_buffered_fifo(Process.intermediate_channel_extname(audio: true, video: false), proc_vis: process)
262
+ lbl_over = "o#{idx}d#{i}"
263
+ @filters.concat(
264
+ over_duck.reel.filters_for lbl_over, video: false, audio: channel(:audio)
265
+ )
266
+ @channel_lbl_ios["#{lbl_over}:a"] = over_a_i
267
+ process.proc_vis_edge process, over_a_i
268
+ Ffmprb.logger.debug{"Routed and buffering auxiliary output fifos (#{over_a_i.path}>#{over_a_o.path}) for overlay"}
269
+
270
+ inter_i, inter_o = File.threaded_buffered_fifo(intermediate_extname, proc_vis: process)
271
+ Ffmprb.logger.debug{"Allocated fifos to buffer media (#{inter_i.path}>#{inter_o.path}) while finding silence"}
272
+
273
+ ignore_broken_pipes_was = process.ignore_broken_pipes
274
+ process.ignore_broken_pipes = true # NOTE audio ducking process may break the overlay pipe
275
+
276
+ Util::Thread.new "audio ducking" do
277
+ process.proc_vis_edge main_av_inter_o, inter_i # XXX mark it better
278
+ silence = Ffmprb.find_silence(main_av_inter_o, inter_i)
279
+
280
+ silence_res = silence.map{|s| "#{s.start_at}-#{s.end_at}"}.join(', ')
281
+ Ffmprb.logger.debug{"Audio ducking with silence: [#{silence_res}]"}
282
+
283
+
284
+ # NOTE (not) ignoring broken pipes here is hopefully what the caller intended
285
+ Process.duck_audio inter_o, over_a_o, silence, main_av_o,
286
+ process_options: {parent: process, ignore_broken_pipes: ignore_broken_pipes_was, timeout: process.timeout},
287
+ video: channel(:video), audio: channel(:audio)
288
+ end
289
+
290
+ end
291
+
292
+ @filters
293
+ end
294
+
295
+ def args
296
+ fail Error, "Must generate filters first." unless @channel_lbl_ios
297
+
298
+ [].tap do |args|
299
+ io_channel_lbls = {} # XXX ~~~spaghetti
300
+ @channel_lbl_ios.each do |channel_lbl, io|
301
+ (io_channel_lbls[io] ||= []) << channel_lbl
302
+ end
303
+ io_channel_lbls.each do |io, channel_lbls|
304
+ channel_lbls.each do |channel_lbl|
305
+ args.concat ['-map', "[#{channel_lbl}]"]
306
+ end
307
+ args.concat self.class.video_args(channel :video) if channel? :video
308
+ args.concat self.class.audio_args(channel :audio) if channel? :audio
309
+ args << io.path
310
+ end
311
+ end
312
+ end
313
+
314
+ def input(io, video: true, audio: true)
315
+ process.input io, video: video, audio: audio
316
+ end
317
+
318
+ def roll(
319
+ reel,
320
+ after: nil,
321
+ transition: nil
322
+ )
323
+ fail Error, "No reel" unless reel
324
+ fail Error, "No time to roll (after: #{after})..." if after && after.to_f <= 0
325
+ fail Error, "Supporting :transition with :after only at the moment, sorry." unless
326
+ !transition || after || @lays.empty?
327
+ Ffmprb.logger.warn "Overlays are over all the lays, so they'd better be after all the lays in the script." unless @overlays.empty? && @overducks.empty?
328
+
329
+ add_lay reel, after.to_f, transition
330
+ end
331
+ alias :lay :roll
332
+
333
+ def overlay(
334
+ reel,
335
+ at: 0,
336
+ transition: nil,
337
+ duck: nil
338
+ )
339
+ fail Error, "No reel" unless reel
340
+ fail Error, "No time to roll (at: #{at})..." if at.to_f < 0
341
+ fail Error, "Video overlays are not implemented just yet, sorry..." if reel.channel?(:video)
342
+ fail Error, "Don't know how to duck video... yet" if duck != :audio
343
+ fail Error, "No audio to have ducked" unless reel.channel?(:audio)
344
+ Ffmprb.logger.warn "Overlays are over all the lays, so they'd better be after all the lays in the script." if @lays.empty?
345
+ Ffmprb.logger.warn "Ducking overlays are over all the lays, and over all the overlays, so they'd better be after all the lays and the overlays in the script." unless duck || @overducks.empty?
346
+ Ffmprb.logger.warn "Overlays are over all the lays, so they'd better be after all the lays in the script." if @lays.empty?
347
+
348
+ if duck
349
+ add_overduck reel, at.to_f, transition
350
+ else
351
+ add_overlay reel, at.to_f, transition
352
+ end
353
+ end
354
+
355
+ def channel(medium)
356
+ @channels[medium]
357
+ end
358
+
359
+ def channel?(medium)
360
+ !!channel(medium)
361
+ end
362
+
363
+ private
364
+
365
+ def add_lay(reel, after, transition)
366
+ @lays << OpenStruct.new(reel: reel, after: after, transition: transition && Transition.new(**transition))
367
+ end
368
+
369
+ def add_overlay(reel, at, transition)
370
+ @overlays << OpenStruct.new(reel: reel, at: at, transition: transition && Transition.new(**transition))
371
+ end
372
+
373
+ def add_overduck(reel, at, transition)
374
+ @overducks << OpenStruct.new(reel: reel, at: at, transition: transition && Transition.new(**transition))
375
+ end
376
+
377
+ end
378
+
379
+ end
380
+
381
+ end
382
+
383
+ require_relative 'output/transition'