ffmprb 0.12.1 → 0.12.3

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.
@@ -1,13 +1,104 @@
1
+
2
+ # The general idea of (a video production pipeline for) "vlogging":
3
+ # -1. think of something nice to shoot
4
+ # - maybe even write down a scenario and produce "the picture"
5
+ # 0. shoot the damn footage with your cam/mic/what-have-you
6
+ # - press Record on your GoPro (video stored on device)
7
+ # 1. upload the raw media files to a (cloud) storage
8
+ # - GoPro media storage subscription (video files accessible at)
9
+ # 2. take note of, sort and clean the raw footage up manually
10
+ # - cut and concatenate the media files into cohesive "scenes" (ffmprb script)
11
+ # 3. upload the HD renders of the raw scenes
12
+ # - to a "cache" storage (ghlr media), plus, for the public access (YouTube)
13
+ # 4. compose a video from the raw scene cuts and additional media
14
+ # - create a (ffmprb) montage script using the media
15
+ # 5. upload and premiere the video piece versions (final cut, general public)
16
+ # - to "ghlr media sharing" (fut.), plus, for public "discovery" (YouTube)
17
+ # 6. profit?
18
+
19
+ # # empty (micro) case is raw publish
20
+
21
+ # NOTE on simprender:
22
+ # a title-back-summary effect
23
+ # XXX
24
+ # -16(2) -8(2) -4(2) ^0 2(2), -4(1) [1]
25
+ # crop
26
+ # ?soundtrack?
27
+ # cut(from 24, to: 56).pace(16).reverse
28
+ # cut(from 8, to: 24).pace(8).reverse
29
+ # cut(from 0, to: 8).pace(4).reverse
30
+ # cut(from: 0, to: 4).pace(2)
31
+ # cut(from: 0, to: 4).pace(4).reverse
32
+ #
33
+ # 2.0
34
+ # crop(0.48).cut(from: M).pace(m).reverse
35
+ # ...
36
+ # crop(0.2).cut(from: 12, to: 28).pace(16).reverse
37
+ # crop(0.18).cut(from: 4, to: 12).pace(8).reverse
38
+ # crop(0.15).cut(from: 0, to: 4).pace(4).reverse
39
+ # crop(0.1).cut(from: 0, to: 4).pace(2)
40
+ # crop(0.05).cut(from: 0, to: 4).pace(4).reverse
41
+ #
42
+ # 3.0 (aka 1.11)
43
+ # crop(0.49).cut(from: M).pace(m).reverse
44
+ # ...
45
+ # crop(0.2).cut(from: 12, to: 28).pace(16).reverse
46
+ # crop(0.25).cut(from: 4, to: 12).pace(8).reverse
47
+ # crop(0.14).cut(from: 0, to: 4).pace(4).reverse
48
+
49
+ # NOTE looking good?
50
+ REV_CUT_CROP_EXP_BASE = 1.333
51
+
52
+ # NOTE unreasonable to exhaust
53
+ REV_CUT_CROP_EXP_PRE_CUTS =
54
+ (2..64).reduce [] do |a, k|
55
+ if (3.6..3600.0).include? (int = REV_CUT_CROP_EXP_BASE**k)
56
+ a << (a[-1] || 0) + int
57
+ else
58
+ a
59
+ end
60
+ end.reverse
61
+
62
+ # NOTE reasonable to look good
63
+ REV_CUT_CROP_LENGTH_MIN_S = REV_CUT_CROP_EXP_PRE_CUTS[-4]
64
+
65
+ # XXX Can also go hard on with blending to complement the rewind effect...
66
+
67
+ def rev_cut_crop_seq(length, inp)
68
+ fail "supply a block that receives the next reel and returns the next input" unless
69
+ block_given?
70
+
71
+ return unless
72
+ length > REV_CUT_CROP_LENGTH_MIN_S
73
+
74
+ REV_CUT_CROP_EXP_PRE_CUTS.each_with_index do |k, i|
75
+ next if
76
+ (n = REV_CUT_CROP_EXP_PRE_CUTS[i + 1] || 0) > length
77
+ cut = {from: n}
78
+ cut[:to] = k unless
79
+ k > length
80
+ pace = REV_CUT_CROP_EXP_BASE**(REV_CUT_CROP_EXP_PRE_CUTS.length - i + 1)
81
+ # XXX Math.log(1 + (REV_CUT_CROP_EXP_PRE_CUTS.length - i + 2) / 5.0) / 4 # NOTE magick!
82
+ crop = 0.5*(1 - Math.sqrt(1/pace)) # NOTE bitrate! (x2)
83
+ # inp = yield(inp.crop(crop).cut(cut).pace(pace).reverse)
84
+ yield inp.crop(crop).cut(cut).pace(pace).reverse
85
+ end
86
+ # XXX
87
+ # inp = yield(inp.crop(0.1).cut(from: 0, to: 4).pace(2))
88
+ # inp = yield(inp.crop(0.05).cut(from: 0, to: 4).pace(4).reverse)
89
+ end
90
+
1
91
  channel = ARGV.shift || 'default'
2
92
 
93
+ # XXX too complicated? maybe 3 scripts instead?
3
94
  abort "USAGE: gop-raw-cut-you-HD60.rb [CHANNEL]" unless
4
95
  ARGV.empty?
5
96
 
6
97
  MEDIA_DIR = ENV['MEDIA_DIR'] or
7
98
  abort "MEDIA_DIR needed"
8
99
 
9
- time_s = Time.now.strftime('%y-%m-%d-%H-%M')
10
- title = "Topublish uploaded at #{time_s}"
100
+ now_time = Time.now
101
+ title = "XXX Topublish (uploaded at #{now_time.strftime '%y-%m-%d-%H-%M'})"
11
102
 
12
103
  require 'cgi'
13
104
 
@@ -26,14 +117,16 @@ end
26
117
  require 'fileutils'
27
118
 
28
119
  require 'ffmprb'
29
- # Ffmprb.debug = true # XXX
120
+ Ffmprb.debug = true # XXX
30
121
  Ffmprb::Util::Thread.timeout = 150
31
122
 
32
123
  int_video_opt = {resolution: Ffmprb::HD_4K, fps: 60}
33
- fin_video_opt = {resolution: Ffmprb::HD_1080p, fps: 60, encoder: 'libx264 -crf 31'} # XXX -preset veryslow
34
- YOU_VIDEO_OPT = {resolution: Ffmprb::HD_4K, fps: 60, encoder: 'libx264'}
124
+ raw_video_opt = {resolution: Ffmprb::HD_4K, fps: 60, encoder: 'libx264 -crf 15'} # XXX -preset superfast
125
+ fin_video_opt = {resolution: Ffmprb::HD_1080p, fps: 60, encoder: 'libx265 -crf 19'} # XXX 21 -preset veryslow
126
+ YOU_VIDEO_OPT = {resolution: Ffmprb::HD_4K, fps: 60, encoder: 'libx264 -crf 17'} # XXX 15 -preset superfast
35
127
 
36
- GOP_MP4_RE = /\b(GX(\d\d)(\d\d\d\d)\.MP4)\b/i
128
+ GOP_FILE_PREFIX = 'GX'
129
+ GOP_MP4_RE = /\b(#{GOP_FILE_PREFIX}(\d\d)(\d\d\d\d)\.MP4)\b/i
37
130
  GOP_ZIP_URL_RE = %r[/zip/]i
38
131
 
39
132
 
@@ -43,10 +136,11 @@ def dura_to_sec(dura_str)
43
136
  end
44
137
  end
45
138
 
46
- def out_path(name)
47
- File.join MEDIA_DIR, "#{name}.mp4"
139
+ def date_media_path(date, name)
140
+ File.join MEDIA_DIR, '%04d' % date.year, '%02d' % date.month, '%02d' % date.day, "#{name}.mp4"
48
141
  end
49
142
 
143
+ raw_time = nil
50
144
 
51
145
  FileUtils.mkdir_p (tmp_dir = File.join(MEDIA_DIR, 'gop-raw-cut-you-tmp'))
52
146
  begin
@@ -56,12 +150,13 @@ begin
56
150
  warn "\nEnter lines containing GoP media D/L URLs and cut times:\n\n"
57
151
 
58
152
  av_src_cuts = []
153
+ int_av_path = nil
59
154
  dl_q = Queue.new
60
155
 
61
- shots = []
62
156
  fetcher = Thread.new do
63
157
  while (url, cuts = dl_q.deq)
64
158
  srcs = []
159
+ shot = nil
65
160
  while srcs.empty? # NOTE sometimes (zip) D/L silently fails, see below
66
161
  name =
67
162
  case CGI.unescape url
@@ -80,16 +175,28 @@ begin
80
175
  zip_lines.each do |line|
81
176
  if line =~ GOP_MP4_RE
82
177
  srcs << $1
83
- shots << $3
178
+ shot = $3
84
179
  end
85
180
  end
86
181
  end
87
182
  File.delete name
88
183
  else
89
184
  srcs << name
90
- shots << $3
185
+ shot = $3
91
186
  end
92
187
  end
188
+
189
+ # NOTE assuming srcs is not empty, for the first src
190
+ unless raw_time
191
+ raw_time =
192
+ Ffmprb::File.access(srcs[0]).creation_time ||
193
+ now_time # NOTE well...
194
+ int_av_path = date_media_path(raw_time, "#{GOP_FILE_PREFIX}-#{shot}")
195
+ # XXX
196
+ # break if
197
+ # (Ffmprb::File.access(int_av_path).length rescue 0) > 0
198
+ end
199
+
93
200
  av_src_cuts << [
94
201
  srcs.sort do |a, b|
95
202
  a_m = GOP_MP4_RE.match(a)
@@ -103,6 +210,9 @@ begin
103
210
  cuts
104
211
  ]
105
212
  end
213
+
214
+ abort "ERROR no inputs given" unless
215
+ int_av_path
106
216
  end
107
217
 
108
218
  while (url_cut = gets)
@@ -123,20 +233,17 @@ begin
123
233
  warn "\nFetching those files..."
124
234
  fetcher.join
125
235
 
126
- abort "ERROR no inputs given" if
127
- av_src_cuts.empty?
128
-
129
- out_name = "GX-#{shots.uniq.join '-'}-#{time_s}"
130
- you_out_path = "_you_#{out_name}.mp4"
131
- warn "\nCut-catting to out (#{out_name}) paths + you..."
236
+ out_path = date_media_path(now_time, "#{GOP_FILE_PREFIX}-#{raw_time.strftime '%y-%m-%d-%H-%M'}-simprender")
237
+ you_out_path = "_you_toul.mp4"
238
+ warn "\nCut-catting to cache (#{int_av_path})"
132
239
 
133
240
  pipe_cut_threads = av_src_cuts.map do |srcs, cuts|
134
241
  [
135
- (av_pipe = Ffmprb::File.temp_fifo('.flv')),
242
+ (tmp_av_stream = Ffmprb::File.temp_fifo('.flv')),
136
243
  cuts.each_slice(2).map { |from, to| {from: from, to: to} },
137
244
  Thread.new do
138
245
  Ffmprb.process do
139
- output av_pipe, video: int_video_opt do
246
+ output tmp_av_stream, video: int_video_opt do
140
247
  srcs.each do |src|
141
248
  roll input src
142
249
  end
@@ -146,47 +253,80 @@ begin
146
253
  ]
147
254
  end
148
255
 
149
- Ffmprb::File.temp_fifo('.flv') do |av_pipe|
256
+ Ffmprb::File.temp_fifo('.flv') do |av_stream|
150
257
  thr = Thread.new do
151
- Ffmprb.process do
152
- inp_cut_opts =
153
- pipe_cut_threads.map do |av_pipe, cut_opts, _|
154
- (cut_opts.empty?? [{}] : cut_opts).map do |cut_opt|
155
- [input(av_pipe), cut_opt]
156
- end
157
- end.reduce :+
158
- output av_pipe, video: YOU_VIDEO_OPT do
159
- inp_cut_opts.each do |inp, cut_opt|
160
- roll inp.cut cut_opt
258
+ unless pipe_cut_threads.empty?
259
+ FileUtils.mkdir_p File.dirname int_av_path
260
+ Ffmprb.process do
261
+ inp_cut_opts =
262
+ pipe_cut_threads.map do |tmp_av_stream, cut_opts, _|
263
+ (cut_opts.empty?? [{}] : cut_opts).map do |cut_opt|
264
+ [input(tmp_av_stream), cut_opt]
265
+ end
266
+ end.reduce :+
267
+ # XXX output int_av_path, video: raw_video_opt do
268
+ output av_stream, video: raw_video_opt do
269
+ inp_cut_opts.each do |inp, cut_opt|
270
+ roll inp.cut cut_opt
271
+ end
161
272
  end
162
273
  end
163
- # [Rational(1)/8, Rational(1)/4, Rational(1)/2, 1, 2, 4, 8].each do |r| # XXX
164
- # output out_path("#{out_name}x#{r.to_f}"), video: fin_video_opt do
165
- # inp_cut_opts.each do |inp, cut_opt|
166
- # roll inp.cut(cut_opt).pace r
167
- # end
168
- # end
169
- # end
170
274
 
171
- # XXX
172
- # output out_path("#{out_name}x-8"), video: fin_video_opt do
173
- # inp_cut_opts.each do |inp, cut_opt|
174
- # roll inp.cut(cut_opt).reverse.pace 8
175
- # end
176
- # end
177
- # output out_path("#{out_name}x8-"), video: fin_video_opt do
178
- # inp_cut_opts.each do |inp, cut_opt|
179
- # roll inp.cut(cut_opt).pace(8).reverse
180
- # end
181
- # end
275
+ # XXX this is flawed:
276
+ # the simple threads fail because of oom docker signals (9)
277
+ # (not because of broken pipes)
278
+ pipe_cut_threads.each do |tmp_av_stream, _, thr|
279
+ begin
280
+ thr.join
281
+ rescue
282
+ warn "WARN errors-a-happening: #{$!}"
283
+ end
284
+ tmp_av_stream.unlink
285
+ end
182
286
  end
287
+
288
+ # XXX
289
+ # warn "\nComposing and rendering to out (#{out_path}) + you..."
290
+ # Ffmprb.process do
291
+ # output av_stream, video: int_video_opt do
292
+ # # XXX roll in1.crop(0.25).cut(from: Ffmprb::File.access(int_av_path).length - 512).pace(16).reverse
293
+
294
+ # rev_cut_crop_seq(Ffmprb::File.access(int_av_path).length, input(int_av_path)) do |reel|
295
+ # roll reel
296
+ # end
297
+ # roll input(int_av_path)
298
+ # end
299
+ # end
300
+ # [Rational(1)/8, Rational(1)/4, Rational(1)/2, 1, 2, 4, 8].each do |r| # XXX
301
+ # output out_path("#{out_name}x#{r.to_f}"), video: fin_video_opt do
302
+ # inp_cut_opts.each do |inp, cut_opt|
303
+ # roll inp.cut(cut_opt).pace r
304
+ # end
305
+ # end
306
+ # end
307
+
308
+ # XXX
309
+ # output out_path("#{out_name}x-8"), video: fin_video_opt do
310
+ # inp_cut_opts.each do |inp, cut_opt|
311
+ # roll inp.cut(cut_opt).reverse.pace 8
312
+ # end
313
+ # end
314
+ # output out_path("#{out_name}x8-"), video: fin_video_opt do
315
+ # inp_cut_opts.each do |inp, cut_opt|
316
+ # roll inp.cut(cut_opt).pace(8).reverse
317
+ # end
318
+ # end
183
319
  end
320
+ # XXX bad romance: abort from any thread
184
321
  begin
322
+ FileUtils.mkdir_p File.dirname out_path
185
323
  Ffmprb.process do
186
- in1 = input(av_pipe)
324
+ in1 = input(av_stream)
187
325
  output you_out_path, video: YOU_VIDEO_OPT do
188
- roll in1.pace(16).reverse
189
- roll in1.pp
326
+ roll in1
327
+ end
328
+ output out_path, video: fin_video_opt do
329
+ roll in1 # XXX .pp
190
330
  end
191
331
  end
192
332
  ensure
@@ -195,18 +335,6 @@ begin
195
335
  end
196
336
 
197
337
 
198
- # XXX this is flawed:
199
- # the simple threads fail because of oom docker signals (9)
200
- # (not because of broken pipes)
201
- pipe_cut_threads.each do |av_pipe, _, thr|
202
- begin
203
- thr.join
204
- rescue
205
- warn "WARN errors-a-happening: #{$!}"
206
- end
207
- av_pipe.unlink
208
- end
209
-
210
338
  metadata = {
211
339
  snippet: {
212
340
  title: title
@@ -22,9 +22,9 @@ def google_youtube(cb_uri, user_id, &blk)
22
22
  Google::Apis::YoutubeV3::AUTH_YOUTUBE,
23
23
  Google::Auth::Stores::FileTokenStore.new(file: GOOGLE_CREDENTIAL_STORE)
24
24
  )
25
- youtube.client_options.send_timeout_sec =
26
- youtube.client_options.open_timeout_sec =
27
- youtube.client_options.read_timeout_sec = YOUTUBE_TIMEOUT_SEC
25
+ youtube.client_options.send_timeout =
26
+ youtube.client_options.open_timeout =
27
+ youtube.client_options.read_timeout = YOUTUBE_TIMEOUT
28
28
  youtube.authorization =
29
29
  if (credentials = authorizer.get_credentials(user_id))
30
30
  credentials
data/ffmprb.gemspec CHANGED
@@ -47,6 +47,8 @@ Gem::Specification.new do |spec|
47
47
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
48
48
  spec.require_paths = ['lib']
49
49
 
50
+ spec.required_ruby_version = '>= 2.6' # TODO check 3.X
51
+
50
52
  # NOTE I'm not happy with this dependency, and there's nothing crossplatform (= for windoze too) at the moment
51
53
  spec.add_dependency 'mkfifo', '~> 0.1.1'
52
54
  # NOTE make it into an optional dependency? Nah for now
data/lib/defaults.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Ffmprb
2
2
 
3
3
  File.image_extname_regex = /^\.(jpe?g|a?png|y4m)$/i
4
- File.sound_extname_regex = /^\.(mp3|wav)$/i
4
+ File.sound_extname_regex = /^\.(mp3|wav|m4a)$/i
5
5
  File.movie_extname_regex = /^\.(mp4|flv|mov)$/i
6
6
 
7
7
  Filter.silence_noise_max_db = -40
@@ -2,8 +2,11 @@ module Ffmprb
2
2
 
3
3
  class File
4
4
 
5
+ AUDIO_SAMPLE_MIN = 0.5
6
+
5
7
  def sample(
6
8
  at: 0.01,
9
+ duration: 0,
7
10
  video: true,
8
11
  audio: true,
9
12
  &blk
@@ -11,15 +14,22 @@ module Ffmprb
11
14
  audio = File.temp('.wav') if audio == true
12
15
  video = File.temp('.png') if video == true
13
16
 
14
- Ffmprb.logger.debug{"Snap shooting files, video path: #{video ? video.path : 'NONE'}, audio path: #{audio ? audio.path : 'NONE'}"}
17
+ Ffmprb.logger.debug{"Snap shooting files, video: #{video && video.path}, audio: #{audio && audio.path}"}
15
18
 
16
- fail Error, "Incorrect output extname (must be image)" unless !video || video.channel?(:video) && !video.channel?(:audio)
17
- fail Error, "Incorrect audio extname (must be sound)" unless !audio || audio.channel?(:audio) && !audio.channel?(:video)
18
- fail Error, "Can sample either video OR audio UNLESS a block is given" unless block_given? || !!audio != !!video
19
+ fail Error, "Incorrect output extname (must be image)" unless
20
+ !video || video.channel?(:video) && !video.channel?(:audio)
21
+ fail Error, "Incorrect audio extname (must be sound)" unless
22
+ !audio || audio.channel?(:audio) && !audio.channel?(:video)
23
+ fail Error, "Can sample either video OR audio UNLESS a block is given" unless
24
+ block_given? || !!audio != !!video
25
+ fail Error, "Can sample video just for 0 sec (an image snapshot)" unless
26
+ !video || duration == 0
19
27
 
20
28
  cmd = %W[-i #{path}]
21
29
  cmd.concat %W[-deinterlace -an -ss #{at} -vframes 1 #{video.path}] if video
22
- cmd.concat %W[-vn -ss #{at} -t 1 #{audio.path}] if audio
30
+ audio_duration = [AUDIO_SAMPLE_MIN, duration].max
31
+ audio_at = [0, at - audio_duration / 2].max
32
+ cmd.concat %W[-vn -ss #{audio_at} -t #{audio_duration} #{audio.path}] if audio
23
33
  Util.ffmpeg *cmd
24
34
 
25
35
  return video || audio unless block_given?
@@ -42,7 +52,5 @@ module Ffmprb
42
52
  def sample_audio(*audio, at: 0.01, &blk)
43
53
  sample at: at, video: false, audio: (audio.first || true), &blk
44
54
  end
45
-
46
55
  end
47
-
48
56
  end
data/lib/ffmprb/file.rb CHANGED
@@ -95,7 +95,8 @@ module Ffmprb
95
95
 
96
96
  def initialize(path:, mode:)
97
97
  @mode = mode.to_sym
98
- fail Error, "Open for read, create for write, ??? for #{@mode}" unless %i[read write].include?(@mode)
98
+ fail Error, "Open for read, create for write, ??? for #{@mode}" unless
99
+ %i[read write].include?(@mode)
99
100
  @path = path
100
101
  @path.close if @path && @path.respond_to?(:close) # NOTE we operate on closed files
101
102
  path! # NOTE early (exception) raiser
@@ -147,11 +148,20 @@ module Ffmprb
147
148
  end
148
149
  end
149
150
 
150
- def resolution
151
- v_stream = probe['streams'].first
151
+ def resolution(force=false)
152
+ v_stream = probe(force)['streams'].first
152
153
  "#{v_stream['width']}x#{v_stream['height']}"
153
154
  end
154
155
 
156
+ def fps(force=false)
157
+ v_stream = probe(force)['streams'].first
158
+ Rational v_stream['r_frame_rate'] || v_stream['avg_frame_rate']
159
+ end
160
+
161
+ def creation_time(force=false)
162
+ Time.parse probe(force)['format']['tags']['creation_time']
163
+ end
164
+
155
165
 
156
166
  # Manipulation
157
167
 
@@ -191,9 +201,7 @@ module Ffmprb
191
201
  fail Error, "This doesn't look like a ffprobable file" unless probe['streams']
192
202
  end
193
203
  end
194
-
195
204
  end
196
-
197
205
  end
198
206
 
199
207
  require_relative 'file/sample'
data/lib/ffmprb/filter.rb CHANGED
@@ -49,6 +49,7 @@ module Ffmprb
49
49
  inout 'asplit', inputs, outputs
50
50
  end
51
51
 
52
+ # TODO? fix "Queue input is backward in time"
52
53
  def areverse(input=nil, output=nil)
53
54
  inout 'areverse', input, output
54
55
  end
@@ -184,7 +185,7 @@ module Ffmprb
184
185
  inout 'fps=fps=%{fps}', input, output, fps: fps
185
186
  end
186
187
 
187
- def interpolate_v(fps, input=nil, output=nil)
188
+ def framerate(fps, input=nil, output=nil)
188
189
  inout 'framerate=fps=%{fps}', input, output, fps: fps
189
190
  end
190
191
  # TODO other effects like... minterpolate=fps=%{fps}:mi_mode=mci:mc_mode=aobmc:vsbmc=1
@@ -220,8 +221,11 @@ module Ffmprb
220
221
  inout 'setsar=%{ratio}', input, output, ratio: ratio
221
222
  end
222
223
 
223
- def setpts(ratio, input=nil, output=nil)
224
- inout 'setpts=%{r_fps}*PTS', input, output, r_fps: 1.0/ratio
224
+ def setpts_framerate(ratio, fps, input=nil, output=nil)
225
+ inout [
226
+ inout('setpts=%{r_fps}*PTS', r_fps: 1.0/ratio),
227
+ *framerate(fps),
228
+ ].join(', '), input, output
225
229
  end
226
230
 
227
231
  def scale(resolution, input=nil, output=nil)
@@ -300,10 +304,18 @@ module Ffmprb
300
304
  end
301
305
 
302
306
 
303
- def complex_args(*filters)
304
- [].tap do |args|
305
- args << '-filter_complex' << filters.join('; ') unless
306
- filters.empty?
307
+ def complex_args(*filters, script_file: nil)
308
+ if filters.empty?
309
+ []
310
+ else
311
+ filter_complex_opt = filters.join('; ')
312
+ if script_file
313
+ script_file.write filter_complex_opt
314
+ script_file.close
315
+ ['-filter_complex_script', script_file.path]
316
+ else
317
+ ['-filter_complex', filter_complex_opt]
318
+ end
307
319
  end
308
320
  end
309
321
 
@@ -25,6 +25,11 @@ module Ffmprb
25
25
  # Doing nothing
26
26
  unfiltered.filters_for lbl, video: video, audio: audio
27
27
  end
28
+
29
+
30
+ def channel(medium)
31
+ io.channel medium
32
+ end
28
33
  end
29
34
  end
30
35
  end
@@ -56,11 +56,7 @@ module Ffmprb
56
56
  ]
57
57
  end
58
58
  end
59
-
60
59
  end
61
-
62
60
  end
63
-
64
61
  end
65
-
66
62
  end
@@ -19,13 +19,15 @@ module Ffmprb
19
19
  end
20
20
 
21
21
  def filters_for(lbl, video:, audio:)
22
+ fail Error, "pacing requires fps" unless
23
+ video.fps
22
24
 
23
25
  # Pacing
24
26
 
25
27
  lbl_aux = "pc#{lbl}"
26
28
  super(lbl_aux, video: video, audio: audio) +
27
29
  [
28
- *((video && channel?(:video))? Filter.setpts(@ratio, "#{lbl_aux}:v", "#{lbl}:v"): nil),
30
+ *((video && channel?(:video))? Filter.setpts_framerate(@ratio, video.fps, "#{lbl_aux}:v", "#{lbl}:v"): nil),
29
31
  *((audio && channel?(:audio))? Filter.atempo(@ratio, "#{lbl_aux}:a", "#{lbl}:a"): nil)
30
32
  ]
31
33
  end
@@ -40,8 +40,8 @@ module Ffmprb
40
40
  attr_reader :process
41
41
 
42
42
  def initialize(io, process, video:, audio:)
43
- @io = self.class.resolve(io)
44
43
  @process = process
44
+ @io = self.class.resolve(io)
45
45
  @channels = {
46
46
  video: video && @io.channel?(:video) && OpenStruct.new(video),
47
47
  audio: audio && @io.channel?(:audio) && OpenStruct.new(audio)
@@ -76,18 +76,18 @@ module Ffmprb
76
76
  Filter.copy "#{in_lbl}:v", "#{lbl}:v"
77
77
  end
78
78
  elsif video
79
- fail Error, "No video stream to provide"
79
+ fail Error, "No video stream to provide ('#{io.path}')"
80
80
  end),
81
81
  *(if audio && channel?(:audio)
82
82
  Filter.anull "#{in_lbl}:a", "#{lbl}:a"
83
83
  elsif audio
84
- fail Error, "No audio stream to provide"
84
+ fail Error, "No audio stream to provide ('#{io.path}')"
85
85
  end)
86
86
  ]
87
87
  end
88
88
 
89
89
  def channel?(medium)
90
- io.channel? medium
90
+ !!channel(medium)
91
91
  end
92
92
 
93
93
  def channel(medium)
@@ -89,19 +89,13 @@ module Ffmprb
89
89
  # NOTE mapping input to this lbl
90
90
 
91
91
  lbl = "o#{idx}rl#{i}"
92
- lbl_aux = "t#{lbl}"
93
92
 
94
93
  # NOTE Image-Padding to match the target resolution
95
94
  # TODO full screen only at the moment (see exception above)
96
95
 
97
96
  Ffmprb.logger.debug{"#{self} asking for filters of #{curr_reel.reel.io.inspect} video: #{channel(:video)}, audio: #{channel(:audio)}"}
98
- @filters.concat(
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
- ]
104
- )
97
+ # NOTE may require changes if fps is different (and ffmpeg freezes)
98
+ @filters.concat curr_reel.reel.filters_for(lbl, video: channel(:video), audio: channel(:audio))
105
99
  end
106
100
 
107
101
  trim_prev_at = curr_reel.after || (curr_reel.transition && 0)
@@ -398,9 +392,6 @@ module Ffmprb
398
392
  (@overlays ||= []) <<
399
393
  OpenStruct.new(reel: reel, at: at, duck: duck)
400
394
  end
401
-
402
395
  end
403
-
404
396
  end
405
-
406
397
  end