ffmprb 0.11.3 → 0.11.4

Sign up to get free protection for your applications and to get access to all the features.
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'