ffmprb 0.12.1 → 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,8 @@
1
1
  GIT
2
2
  remote: http://git/ffmprb/.git
3
- revision: cafbbe2c33cb9eca853412f1f96aafbe428abcdc
3
+ revision: d114fb42b56d213aa671578deec948ac687f0a0f
4
4
  specs:
5
- ffmprb (0.11.5)
5
+ ffmprb (0.12.1)
6
6
  mkfifo (~> 0.1.1)
7
7
  thor (~> 0.19.1)
8
8
 
@@ -27,8 +27,14 @@ begin
27
27
  warn "\nCut-catting to #{out_path}..."
28
28
 
29
29
  Ffmprb.process do
30
+ inp = input(inp_path)
30
31
  output out_path, video: VIDEO_OPT do
31
- roll input(inp_path).cut to: 60 # XXX .crop(top: 0.35, bottom: 0.15, left: 0.15, right: 0.35).cut from: 10, to: 20
32
+ roll inp.cut to: 16
33
+ roll inp.cut(from: 16, to: 32).pace(2)
34
+ roll inp.cut(from: 32, to: 64).pace(4)
35
+ roll inp.cut(from: 64, to: 80).pace(2)
36
+ roll inp.cut from: 80, to: 160
37
+ # roll input(inp_path).cut to: 60 # XXX .crop(top: 0.35, bottom: 0.15, left: 0.15, right: 0.35).cut from: 10, to: 20
32
38
  end
33
39
  end
34
40
  end
data/exp/youtubby/Gemfile CHANGED
@@ -1,7 +1,5 @@
1
- gem 'ffmprb', :git => 'http://git/ffmprb/.git', :branch => 'pacing'
1
+ gem 'ffmprb', :git => 'http://git/ffmprb/.git', :branch => 'pace-fix'
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem 'google-apis-youtube_v3'
6
-
7
- gem 'byebug' # XXX
@@ -1,9 +1,9 @@
1
1
  GIT
2
2
  remote: http://git/ffmprb/.git
3
- revision: 90ab02d748e2f86211b6cfd60cf35741deac318c
4
- branch: pacing
3
+ revision: aebc01b05ca81ce3a0c249631792d65feb854854
4
+ branch: pace-fix
5
5
  specs:
6
- ffmprb (0.12.1)
6
+ ffmprb (0.12.2)
7
7
  mkfifo (~> 0.1.1)
8
8
  thor (~> 0.19.1)
9
9
 
@@ -12,7 +12,6 @@ GEM
12
12
  specs:
13
13
  addressable (2.8.1)
14
14
  public_suffix (>= 2.0.2, < 6.0)
15
- byebug (11.1.3)
16
15
  declarative (0.0.20)
17
16
  faraday (2.7.2)
18
17
  faraday-net_http (>= 2.0, < 3.1)
@@ -65,7 +64,6 @@ PLATFORMS
65
64
  ruby
66
65
 
67
66
  DEPENDENCIES
68
- byebug
69
67
  ffmprb!
70
68
  google-apis-youtube_v3
71
69
 
@@ -0,0 +1,356 @@
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
+
91
+ channel = ARGV.shift || 'default'
92
+
93
+ abort "USAGE: gop-raw-cut-you-HD60.rb [CHANNEL]" unless
94
+ ARGV.empty?
95
+
96
+ MEDIA_DIR = ENV['MEDIA_DIR'] or
97
+ abort "MEDIA_DIR needed"
98
+
99
+ now_time = Time.now
100
+ title = "XXX Topublish (uploaded at #{now_time.strftime '%y-%m-%d-%H-%M'})"
101
+
102
+ require 'cgi'
103
+
104
+ WE_URI = 'http://localhost'
105
+
106
+ require_relative '../google_youtube'
107
+
108
+ youtube = google_youtube(WE_URI, channel) do |auth_url|
109
+ puts "Open the following URL in your browser and authorize the application."
110
+ puts "(you'll have to copy the code= URL param when redrircted to #{WE_URI})"
111
+ puts auth_url
112
+ puts "Paste the authorization code."
113
+ $stdin.gets.chomp
114
+ end
115
+
116
+ require 'fileutils'
117
+
118
+ require 'ffmprb'
119
+ Ffmprb.debug = true # XXX
120
+ Ffmprb::Util::Thread.timeout = 150
121
+
122
+ int_video_opt = {resolution: Ffmprb::HD_4K, fps: 60}
123
+ raw_video_opt = {resolution: Ffmprb::HD_4K, fps: 60, encoder: 'libx264 -crf 15'} # XXX -preset superfast
124
+ fin_video_opt = {resolution: Ffmprb::HD_1080p, fps: 60, encoder: 'libx265 -crf 19'} # XXX 21 -preset veryslow
125
+ YOU_VIDEO_OPT = {resolution: Ffmprb::HD_4K, fps: 60, encoder: 'libx264 -crf 17'} # XXX 15 -preset superfast
126
+
127
+ GOP_FILE_PREFIX = 'GX'
128
+ GOP_MP4_RE = /\b(#{GOP_FILE_PREFIX}(\d\d)(\d\d\d\d)\.MP4)\b/i
129
+ GOP_ZIP_URL_RE = %r[/zip/]i
130
+
131
+
132
+ def dura_to_sec(dura_str)
133
+ dura_str.split(':').reverse.each_with_index.reduce(0) do |sec, (ns, i)|
134
+ sec + ns.to_i*(60**i)
135
+ end
136
+ end
137
+
138
+ def date_media_path(date, name)
139
+ File.join MEDIA_DIR, '%04d' % date.year, '%02d' % date.month, '%02d' % date.day, "#{name}.mp4"
140
+ end
141
+
142
+ raw_time = nil
143
+
144
+ warn "FYI"
145
+ system "df -h"
146
+ warn "\nGo to https://plus.gopro.com/media-library" +
147
+ "\nEnter GoP video D/L URLs followed (same line) by cut times, space-separated:\n\n"
148
+
149
+ av_src_cuts = []
150
+ int_av_path = nil
151
+ dl_q = Queue.new
152
+
153
+ fetcher = Thread.new do
154
+ while (url, cuts = dl_q.deq)
155
+ srcs = []
156
+ shot = nil
157
+ while srcs.empty? # NOTE sometimes (zip) D/L silently fails, see below
158
+ name =
159
+ case CGI.unescape url
160
+ when GOP_ZIP_URL_RE
161
+ 'tmp.zip'
162
+ when GOP_MP4_RE
163
+ $1
164
+ else
165
+ abort "ERROR invalid URL, cannot go on"
166
+ end
167
+ abort "ERROR downloading #{url}" unless
168
+ system "curl -so #{name} '#{url}'"
169
+ if name == 'tmp.zip'
170
+ zip_lines = `unzip -o #{name}`.lines
171
+ if $?.success? # NOTE if the D/L in fact has failed, it'll be retried
172
+ zip_lines.each do |line|
173
+ if line =~ GOP_MP4_RE
174
+ srcs << $1
175
+ shot = $3
176
+ end
177
+ end
178
+ end
179
+ File.delete name
180
+ else
181
+ srcs << name
182
+ shot = $3
183
+ end
184
+ end
185
+
186
+ # NOTE assuming srcs is not empty, for the first src
187
+ unless raw_time
188
+ raw_time =
189
+ Ffmprb::File.access(srcs[0]).creation_time ||
190
+ now_time # NOTE well...
191
+ int_av_path = date_media_path(raw_time, "#{GOP_FILE_PREFIX}-#{shot}")
192
+ # XXX
193
+ # break if
194
+ # (Ffmprb::File.access(int_av_path).length rescue 0) > 0
195
+ end
196
+
197
+ av_src_cuts << [
198
+ srcs.sort do |a, b|
199
+ a_m = GOP_MP4_RE.match(a)
200
+ b_m = GOP_MP4_RE.match(b)
201
+ if (fst = a_m[3] <=> b_m[3]) != 0
202
+ fst
203
+ else
204
+ a_m[2] <=> b_m[2]
205
+ end
206
+ end,
207
+ cuts
208
+ ]
209
+ end
210
+
211
+ abort "ERROR no inputs given" unless
212
+ int_av_path
213
+ end
214
+
215
+ while (url_cut = gets)
216
+ url, *cut = url_cut.chomp.split(' ')
217
+ next if url.split('#')[0].empty?
218
+
219
+ last_cut = -1
220
+ cuts = cut.map do |ns|
221
+ dura_to_sec(ns).tap do |curr_cut|
222
+ abort "ERROR cut times must be ascending (look it up)" unless
223
+ curr_cut > last_cut
224
+ end
225
+ end
226
+ dl_q.enq [url, cuts]
227
+ end
228
+ dl_q.enq nil
229
+
230
+ warn "\nFetching those files..."
231
+ fetcher.join
232
+
233
+ out_path = date_media_path(now_time, "#{GOP_FILE_PREFIX}-#{raw_time.strftime '%y-%m-%d-%H-%M'}-simprender")
234
+
235
+ Ffmprb::File.temp '.mp4' do |you_out_path|
236
+ warn "\nCut-catting to cache (#{int_av_path})"
237
+
238
+ pipe_cut_threads = av_src_cuts.map do |srcs, cuts|
239
+ [
240
+ (tmp_av_stream = Ffmprb::File.temp_fifo('.flv')),
241
+ cuts.each_slice(2).map { |from, to| {from: from, to: to} },
242
+ Thread.new do
243
+ Ffmprb.process do
244
+ output tmp_av_stream, video: int_video_opt do
245
+ srcs.each do |src|
246
+ roll input src
247
+ end
248
+ end
249
+ end
250
+ end
251
+ ]
252
+ end
253
+
254
+ Ffmprb::File.temp_fifo('.flv') do |av_stream|
255
+ thr = Thread.new do
256
+ unless pipe_cut_threads.empty?
257
+ FileUtils.mkdir_p File.dirname int_av_path
258
+ Ffmprb.process do
259
+ inp_cut_opts =
260
+ pipe_cut_threads.map do |tmp_av_stream, cut_opts, _|
261
+ (cut_opts.empty?? [{}] : cut_opts).map do |cut_opt|
262
+ [input(tmp_av_stream), cut_opt]
263
+ end
264
+ end.reduce :+
265
+ # XXX output int_av_path, video: raw_video_opt do
266
+ output av_stream, video: raw_video_opt do
267
+ inp_cut_opts.each do |inp, cut_opt|
268
+ roll inp.cut cut_opt
269
+ end
270
+ end
271
+ end
272
+
273
+ # XXX this is flawed:
274
+ # the simple threads fail because of oom docker signals (9)
275
+ # (not because of broken pipes)
276
+ pipe_cut_threads.each do |tmp_av_stream, _, thr|
277
+ begin
278
+ thr.join
279
+ rescue
280
+ warn "WARN errors-a-happening: #{$!}"
281
+ end
282
+ tmp_av_stream.unlink
283
+ end
284
+ end
285
+
286
+ # XXX
287
+ # warn "\nComposing and rendering to out (#{out_path}) + you..."
288
+ # Ffmprb.process do
289
+ # output av_stream, video: int_video_opt do
290
+ # # XXX roll in1.crop(0.25).cut(from: Ffmprb::File.access(int_av_path).length - 512).pace(16).reverse
291
+
292
+ # rev_cut_crop_seq(Ffmprb::File.access(int_av_path).length, input(int_av_path)) do |reel|
293
+ # roll reel
294
+ # end
295
+ # roll input(int_av_path)
296
+ # end
297
+ # end
298
+ # [Rational(1)/8, Rational(1)/4, Rational(1)/2, 1, 2, 4, 8].each do |r| # XXX
299
+ # output out_path("#{out_name}x#{r.to_f}"), video: fin_video_opt do
300
+ # inp_cut_opts.each do |inp, cut_opt|
301
+ # roll inp.cut(cut_opt).pace r
302
+ # end
303
+ # end
304
+ # end
305
+
306
+ # XXX
307
+ # output out_path("#{out_name}x-8"), video: fin_video_opt do
308
+ # inp_cut_opts.each do |inp, cut_opt|
309
+ # roll inp.cut(cut_opt).reverse.pace 8
310
+ # end
311
+ # end
312
+ # output out_path("#{out_name}x8-"), video: fin_video_opt do
313
+ # inp_cut_opts.each do |inp, cut_opt|
314
+ # roll inp.cut(cut_opt).pace(8).reverse
315
+ # end
316
+ # end
317
+ end
318
+ # XXX bad romance: abort from any thread
319
+ begin
320
+ FileUtils.mkdir_p File.dirname out_path
321
+ Ffmprb.process do
322
+ in1 = input(av_stream)
323
+ output you_out_path, video: YOU_VIDEO_OPT do
324
+ roll in1
325
+ end
326
+ output out_path, video: fin_video_opt do
327
+ roll in1 # XXX .pp
328
+ end
329
+ end
330
+ ensure
331
+ thr.join
332
+ end
333
+ end
334
+
335
+
336
+ metadata = {
337
+ snippet: {
338
+ title: title
339
+ },
340
+ status: {
341
+ privacy_status: 'private'
342
+ }
343
+ }
344
+
345
+ video =
346
+ youtube.insert_video('snippet,status', metadata, upload_source: you_out_path, content_type: 'video/mp4')
347
+
348
+ if video.status.upload_status == 'uploaded'
349
+ warn "OK"
350
+ else
351
+ warn "FAIL"
352
+ end
353
+ end
354
+ ensure
355
+ FileUtils.rm_r tmp_dir
356
+ end
@@ -10,4 +10,6 @@ docker-compose build --force-rm
10
10
  export GOOGLE_CLIENT_ID="$(< exp/.google-client-id)"
11
11
  export GOOGLE_CLIENT_SECRET="$(< exp/.google-client-secret)"
12
12
 
13
- docker-compose run --rm youtubby exp/gop-raw-cut-you-HD60.rb "$1"
13
+ # XXX docker-compose run --rm youtubby exp/gop-raw-cut-you-HD60.rb "$1"
14
+ docker-compose run --rm youtubby exp/gop-raw-cut-rcc-join-you-HD60.rb "$1"
15
+