ffmprb 0.11.4 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/exp/EXP ADDED
@@ -0,0 +1,7 @@
1
+ ffmpeg -y -noautorotate -i studio3.mp4 -filter_complex "[0:v] scale=iw*min(640/iw\,480/ih):ih*min(640/iw\,480/ih), setsar=1, pad=640:480:(640-iw*min(640/iw\,480/ih))/2:(480-ih*min(640/iw\,480/ih))/2, setsar=1 [tmpco0rl0:v]; [0:a] anull [tmpco0rl0:a]; color=0x000000@0:d=3:s=640x480:r=30 [blpco0rl0:v]; [tmpco0rl0:v] [blpco0rl0:v] concat=2:v=1:a=0 [pdpco0rl0:v]; [pdpco0rl0:v] trim=1:17, setpts=PTS-STARTPTS [pco0rl0:v]; aevalsrc=0:d=3 [blpco0rl0:a]; [tmpco0rl0:a] [blpco0rl0:a] concat=2:v=0:a=1 [pdpco0rl0:a]; [pdpco0rl0:a] atrim=1:17, asetpts=PTS-STARTPTS [pco0rl0:a]; [pco0rl0:v] setpts=0.25*PTS, minterpolate=mi_mode=mci:mc_mode=aobmc:vsbmc=1 [o0rl0:v]; [pco0rl0:a] atempo=4.0 [o0rl0:a]; [o0rl0:v] concat=1:v=1:a=0 [o0o:v]; [o0rl0:a] concat=1:v=0:a=1 [o0o:a]" -map "[o0o:v]" -map "[o0o:a]" -c:a libmp3lame ex-ex.mp4
2
+
3
+ ffmpeg -y -noautorotate -i /tmp/20221228-17-1719wj1.mp4 -filter_complex "[0:v] scale=iw*min(640/iw\,480/ih):ih*min(640/iw\,480/ih), setsar=1, pad=640:480:(640-iw*min(640/iw\,480/ih))/2:(480-ih*min(640/iw\,480/ih))/2, setsar=1 [tmpco0rl0:v]; [0:a] anull [tmpco0rl0:a]; color=0x000000@0:d=3:s=640x480:r=30 [blpco0rl0:v]; [tmpco0rl0:v] [blpco0rl0:v] concat=2:v=1:a=0 [pdpco0rl0:v]; [pdpco0rl0:v] trim=1:7, setpts=PTS-STARTPTS [pco0rl0:v]; aevalsrc=0:d=3 [blpco0rl0:a]; [tmpco0rl0:a] [blpco0rl0:a] concat=2:v=0:a=1 [pdpco0rl0:a]; [pdpco0rl0:a] atrim=1:7, asetpts=PTS-STARTPTS [pco0rl0:a]; [pco0rl0:v] setpts=0.25*PTS, minterpolate=mi_mode=mci:mc_mode=aobmc:vsbmc=1 [o0rl0:v]; [pco0rl0:a] atempo=4.0 [o0rl0:a]; [o0rl0:v] concat=1:v=1:a=0 [o0o:v]; [o0rl0:a] concat=1:v=0:a=1 [o0o:a]" -map "[o0o:v]" -map "[o0o:a]" -c:a libmp3lame ex-ex.mp4
4
+
5
+ ffmpeg -y -noautorotate -i /tmp/20221228-17-1719wj1.mp4 -noautorotate -i /tmp/20221228-17-ov4gbl.flv -filter_complex "[0:v] scale=iw*min(320/iw\,200/ih):ih*min(320/iw\,200/ih), setsar=1, pad=320:200:(320-iw*min(320/iw\,200/ih))/2:(200-ih*min(320/iw\,200/ih))/2, setsar=1, fps=fps=16 [tmpco0rl0:v]; [0:a] anull [tmpco0rl0:a]; color=0x000000@0:d=3:s=320x200:r=16 [blpco0rl0:v]; [tmpco0rl0:v] [blpco0rl0:v] concat=2:v=1:a=0 [pdpco0rl0:v]; [pdpco0rl0:v] trim=0:8, setpts=PTS-STARTPTS [pco0rl0:v]; aevalsrc=0:d=3 [blpco0rl0:a]; [tmpco0rl0:a] [blpco0rl0:a] concat=2:v=0:a=1 [pdpco0rl0:a]; [pdpco0rl0:a] atrim=0:8, asetpts=PTS-STARTPTS [pco0rl0:a]; [pco0rl0:v] setpts=0.25*PTS, minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:vsbmc=1 [o0rl0:v]; [pco0rl0:a] atempo=0.5, atempo=0.5 [o0rl0:a]; [1:v] scale=iw*min(320/iw\,200/ih):ih*min(320/iw\,200/ih), setsar=1, pad=320:200:(320-iw*min(320/iw\,200/ih))/2:(200-ih*min(320/iw\,200/ih))/2, setsar=1, fps=60 [o0rl1:v]; [1:a] anull [o0rl1:a]; [o0rl0:v] [o0rl1:v] concat=2:v=1:a=0 [o0o:v]; [o0rl0:a] [o0rl1:a] concat=2:v=0:a=1 [o0o:a]" -map "[o0o:v]" -map "[o0o:a]" -c:a libmp3lame ex-ex.mp4
6
+
7
+ , fps=fps=16
@@ -0,0 +1,10 @@
1
+ |*inp_opt|
2
+ inp, from, to = inp_opt
3
+ # XXX :: :: ::
4
+ oup = ::File.join(::File.dirname(inp), "#{::File.basename inp, '.*'}-you.mp4")
5
+ cut_opt = {}
6
+ cut_opt[:from] = from.to_i if from
7
+ cut_opt[:to] = to.to_i if to
8
+ output oup, video: {resolution: HD_1080p, fps: 60} do
9
+ roll input(inp).cut cut_opt
10
+ end
@@ -0,0 +1,9 @@
1
+ version: '2.1'
2
+ services:
3
+ experimenter:
4
+ build: ..
5
+ volumes:
6
+ - /mnt/data/comp/drive/data/master/media/tmp/exp:/exp
7
+ environment:
8
+ - FFMPRB_DEBUG=${DEBUG:-no}
9
+ working_dir: /exp
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+
5
+ gem 'ffmprb'
6
+ require 'ffmprb'
7
+
8
+ YOU_VIDEO_OPT = {resolution: '1920x1080', fps: 60}
9
+ int_video_opt = {resolution: '3840x2160', fps: 60}
10
+
11
+ GOP_MP4_RE = /\b(GX(\d\d)(\d\d\d\d)\.MP4)\b/i
12
+ GOP_ZIP_URL_RE = %r[/zip/]i
13
+
14
+
15
+ def dura_to_sec(dura_str)
16
+ dura_str.split(':').reverse.each_with_index.reduce(0) do |sec, (ns, i)|
17
+ sec + ns.to_i*(60**i)
18
+ end
19
+ end
20
+
21
+
22
+ out_dir = Dir.pwd
23
+ av_src_cuts = []
24
+
25
+ Dir.mktmpdir do |tmp_dir|
26
+ Dir.chdir tmp_dir do
27
+ warn "Working in #{tmp_dir} --"
28
+ system "df -h #{tmp_dir}"
29
+ warn "\nEnter lines containing GoP media D/L URLs and cut times:\n\n"
30
+
31
+ dl_q = Queue.new
32
+ fetcher = Thread.new do
33
+ while (url, name, cuts = dl_q.deq)
34
+ srcs = []
35
+ while srcs.empty? # NOTE sometimes (zip) D/L silently fails, see below
36
+ unless system "curl -so #{name} #{url}"
37
+ warn "ERROR downloading #{url}"
38
+ exit 3
39
+ end
40
+ if name == 'tmp.zip'
41
+ zip_lines = `unzip #{name}`.lines
42
+ if $?.success? # NOTE if the D/L in fact has failed, it'll be retried
43
+ zip_lines.each do |line|
44
+ srcs << $1 if
45
+ line =~ GOP_MP4_RE
46
+ end
47
+ end
48
+ File.delete name
49
+ else
50
+ srcs << name
51
+ end
52
+ end
53
+ av_src_cuts << [
54
+ srcs.sort do |a, b|
55
+ a_m = GOP_MP4_RE.match(a)
56
+ b_m = GOP_MP4_RE.match(b)
57
+ if (fst = a_m[3] <=> b_m[3]) != 0
58
+ fst
59
+ else
60
+ a_m[2] <=> b_m[2]
61
+ end
62
+ end,
63
+ cuts
64
+ ]
65
+ end
66
+ end
67
+
68
+ while (url_cut = gets)
69
+ url, *cut = url_cut.chomp.split(' ')
70
+ last_cut = -1
71
+ cuts = cut.map do |ns|
72
+ dura_to_sec(ns).tap do |curr_cut|
73
+ unless curr_cut > last_cut
74
+ warn "ERROR cut times must be ascending (look it up)"
75
+ exit 1
76
+ end
77
+ end
78
+ end
79
+ case url
80
+ when GOP_ZIP_URL_RE
81
+ dl_q.enq [url, 'tmp.zip', cuts]
82
+ when GOP_MP4_RE
83
+ dl_q.enq [url, $1, cuts]
84
+ else
85
+ warn "ERROR invalid URL, cannot go on"
86
+ exit 1
87
+ end
88
+ end
89
+ dl_q.enq nil
90
+
91
+ warn "\nFetching those files..."
92
+ fetcher.join
93
+
94
+ warn av_src_cuts.inspect
95
+
96
+ if av_src_cuts.empty?
97
+ warn "ERROR no inputs given"
98
+ exit 1
99
+ end
100
+
101
+ out_name = av_src_cuts.reduce([]) do |n, sc|
102
+ n + sc[0].map{ |s| File.basename(s, '.*') }
103
+ end.join('-')
104
+
105
+ out_path = File.join(out_dir, "#{out_name}-you.mp4")
106
+ warn "\nCut-catting to #{out_path}..."
107
+
108
+ pipe_cut_threads = av_src_cuts.map do |srcs, cuts|
109
+ [
110
+ (av_pipe = Ffmprb::File.temp_fifo('.flv')),
111
+ cuts.each_slice(2).map { |from, to| {from: from, to: to} },
112
+ Thread.new do
113
+ Ffmprb.process do
114
+ output av_pipe, video: int_video_opt do
115
+ srcs.each do |src|
116
+ roll input src
117
+ end
118
+ end
119
+ end
120
+ end
121
+ ]
122
+ end
123
+
124
+ Ffmprb.process do
125
+ output out_path, video: YOU_VIDEO_OPT do
126
+ pipe_cut_threads.each do |av_pipe, cut_opts, _|
127
+ (cut_opts.empty?? [{}] : cut_opts).each do |cut_opt|
128
+ roll input(av_pipe).cut cut_opt
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ pipe_cut_threads.each do |av_pipe, _, thr|
135
+ thr.join
136
+ File.delete av_pipe
137
+ end
138
+ end
139
+ end
140
+
141
+ warn "\nAll done and clean..."
@@ -0,0 +1,13 @@
1
+ FROM debian:bullseye
2
+
3
+ RUN bash -ec 'apt update; apt -y install ruby-all-dev=1:2.7+2 ffmpeg=7:4.3.5-0+deb11u1 sox=14.4.2+git20190427-2 build-essential=12.9 libmagickwand-dev=8:6.9.11.60+dfsg-1.3 git=1:2.30.2-1 pkg-config=0.29.2-1 curl=7.74.0-1.3+deb11u3'
4
+ RUN gem install bundler -v '~> 2.0'
5
+
6
+ WORKDIR /present
7
+
8
+ COPY Gemfile Gemfile.lock ./
9
+ RUN bundle install
10
+
11
+ COPY . .
12
+
13
+ # XXX ENTRYPOINT bundle exec
@@ -0,0 +1,3 @@
1
+ gem 'ffmprb', :git => 'http://git/ffmprb/.git'
2
+
3
+ source "https://rubygems.org"
@@ -0,0 +1,22 @@
1
+ GIT
2
+ remote: http://git/ffmprb/.git
3
+ revision: cafbbe2c33cb9eca853412f1f96aafbe428abcdc
4
+ specs:
5
+ ffmprb (0.11.5)
6
+ mkfifo (~> 0.1.1)
7
+ thor (~> 0.19.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ mkfifo (0.1.1)
13
+ thor (0.19.4)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ ffmprb!
20
+
21
+ BUNDLED WITH
22
+ 2.1.4
@@ -0,0 +1,8 @@
1
+ #!/bin/bash -e
2
+ test "$#" -ne 0 && echo "Unsupported args: $@" >&2 && exit 145
3
+ cd "$( dirname "${BASH_SOURCE[0]}" )"/..
4
+
5
+ # XXX docker run --rm -v "$( pwd ):/build" -w /build python:3.9 bash -c \
6
+ # "pip install google-api-python-client && pip freeze > requirements.txt"
7
+
8
+ docker run --rm -v "$( pwd ):/build" -w /build --network git_default ruby:2.7 bundle update
@@ -0,0 +1,21 @@
1
+ version: '2.1'
2
+ services:
3
+ present:
4
+ build:
5
+ context: .
6
+ network: git_default
7
+ volumes:
8
+ - .:/present
9
+ # XXX hardcoded
10
+ - /mnt/data/comp/drive/data/master/media:/media
11
+ - creds_fun:/var/fun/creds
12
+ - ../../tmp:/samp
13
+ environment:
14
+ - GOOGLE_CREDENTIAL_STORE=/var/fun/creds/gc/credentials.yml
15
+ - GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID
16
+ - GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET
17
+ - MEDIA_DIR=/media/tmp/exp
18
+ - FFMPRB_DEBUG=$DEBUG
19
+ entrypoint: ["bundle", "exec", "ruby"]
20
+ volumes:
21
+ creds_fun:
@@ -0,0 +1,10 @@
1
+ #!/bin/bash -ex
2
+ cd "$( dirname "${BASH_SOURCE[0]}" )"/..
3
+ # test "$#" -ne 1 && echo "Usage: XXX" >&2 && exit 145
4
+
5
+ COMPOSE_FILE=docker-compose.yml
6
+ COMPOSE_PROJECT_NAME=present_exp
7
+
8
+ docker-compose build --force-rm
9
+
10
+ docker-compose run --rm present exp/present.rb
@@ -0,0 +1,37 @@
1
+ MEDIA_DIR = ENV['MEDIA_DIR'] or abort "MEDIA_DIR needed"
2
+ require 'fileutils' # XXX
3
+
4
+ require 'ffmprb'
5
+ Ffmprb::Util::Thread.timeout = 150
6
+
7
+ VIDEO_OPT = {resolution: Ffmprb::HD_1080p, fps: 30}
8
+
9
+
10
+ # XXX
11
+ def dura_to_sec(dura_str)
12
+ dura_str.split(':').reverse.each_with_index.reduce(0) do |sec, (ns, i)|
13
+ sec + ns.to_i*(60**i)
14
+ end
15
+ end
16
+
17
+ inp_path = File.join(MEDIA_DIR, 'term-samp.mov') # XXX '/samp/term-samp.mov'
18
+ out_path = nil
19
+
20
+ FileUtils.mkdir_p (tmp_dir = File.join(MEDIA_DIR, 'present-tmp'))
21
+ begin
22
+ Dir.chdir tmp_dir do
23
+ warn "Working in #{tmp_dir} --"
24
+ system "df -h #{tmp_dir}"
25
+
26
+ out_path = File.join(MEDIA_DIR, 'present-term.mp4')
27
+ warn "\nCut-catting to #{out_path}..."
28
+
29
+ Ffmprb.process do
30
+ 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
+ end
33
+ end
34
+ end
35
+ ensure
36
+ FileUtils.rm_r tmp_dir
37
+ end
data/exp/run ADDED
@@ -0,0 +1,9 @@
1
+ #!/bin/bash -e
2
+ cd "$( dirname "${BASH_SOURCE[0]}" )"/..
3
+
4
+ export COMPOSE_FILE=exp/docker-compose.yml
5
+ export COMPOSE_PROJECT_NAME=ffmprb_exp
6
+
7
+ docker-compose build --force-rm # XXX --pull
8
+
9
+ docker-compose run --rm experimenter bubu.mp4 bubu2.mp4 bubu1.mp4 < exp/stitch2.ffmprb
@@ -0,0 +1,5 @@
1
+ |in1, in2, ou|
2
+ output(ou, video: {resolution: Ffmprb::HD_1080p, fps: 60}) do
3
+ lay input(in1).cut(from: 36, to: 1430)
4
+ lay input(in2).cut(from: 6)
5
+ end
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ gem 'ffmprb'
5
+ require 'ffmprb'
6
+
7
+ YOU_VIDEO_OPT = {resolution: '1920x1080', fps: 60}
8
+ ZIP_RE = /\.zip$/i
9
+ GOP_RE = /\bGX(\d\d)(\d\d\d\d)\.mp4$/
10
+
11
+ if ARGV.length < 1
12
+ warn "Usage: zip2mp4k60 <{zip|mp4}-file>..."
13
+ exit 1
14
+ end
15
+
16
+ # TODO? maybe, just maybe, provide cut_opts by path[from:to]... ...
17
+
18
+ zip_paths =
19
+ ARGV.map{ |inp| File.expand_path inp }
20
+ out_path =
21
+ File.join(
22
+ File.dirname(zip_paths[0]),
23
+ "#{zip_paths.map{ |zip_path| File.basename zip_path, '.*'}.join '-'}-you.mp4"
24
+ )
25
+
26
+ Dir.mktmpdir do |tmp_dir|
27
+ Dir.chdir tmp_dir do
28
+ zip_paths.each do |zip_path|
29
+ if zip_path =~ ZIP_RE
30
+ system "unzip '#{zip_path}'"
31
+ else
32
+ FileUtils.cp zip_path, '.'
33
+ end
34
+ end
35
+ Ffmprb.process do
36
+ output out_path, video: YOU_VIDEO_OPT do
37
+ paths = Dir['*']
38
+ fail "GoP or don't" unless
39
+ (gops_count = paths.grep(GOP_RE).size) == paths.size
40
+ if gops_count == 0
41
+ paths.sort
42
+ else
43
+ paths.sort do |a, b|
44
+ a_m = GOP_RE.match(a)
45
+ b_m = GOP_RE.match(b)
46
+ if (fst = a_m[2] <=> b_m[2]) != 0
47
+ fst
48
+ else
49
+ a_m[1] <=> b_m[1]
50
+ end
51
+ end
52
+ end.each do |in_path|
53
+ roll input in_path
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,13 @@
1
+ FROM debian:bullseye
2
+
3
+ RUN bash -ec 'apt update; apt -y install ruby-all-dev=1:2.7+2 ffmpeg=7:4.3.5-0+deb11u1 sox=14.4.2+git20190427-2 build-essential=12.9 libmagickwand-dev=8:6.9.11.60+dfsg-1.3 git=1:2.30.2-1 pkg-config=0.29.2-1 curl=7.74.0-1.3+deb11u3'
4
+ RUN gem install bundler -v '~> 2.0'
5
+
6
+ WORKDIR /youtubby
7
+
8
+ COPY Gemfile Gemfile.lock ./
9
+ RUN bundle install
10
+
11
+ COPY . .
12
+
13
+ # XXX ENTRYPOINT bundle exec
@@ -0,0 +1,7 @@
1
+ gem 'ffmprb', :git => 'http://git/ffmprb/.git', :branch => 'pacing'
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'google-apis-youtube_v3'
6
+
7
+ gem 'byebug' # XXX
@@ -0,0 +1,73 @@
1
+ GIT
2
+ remote: http://git/ffmprb/.git
3
+ revision: 90ab02d748e2f86211b6cfd60cf35741deac318c
4
+ branch: pacing
5
+ specs:
6
+ ffmprb (0.12.1)
7
+ mkfifo (~> 0.1.1)
8
+ thor (~> 0.19.1)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ addressable (2.8.1)
14
+ public_suffix (>= 2.0.2, < 6.0)
15
+ byebug (11.1.3)
16
+ declarative (0.0.20)
17
+ faraday (2.7.2)
18
+ faraday-net_http (>= 2.0, < 3.1)
19
+ ruby2_keywords (>= 0.0.4)
20
+ faraday-net_http (3.0.2)
21
+ google-apis-core (0.9.2)
22
+ addressable (~> 2.5, >= 2.5.1)
23
+ googleauth (>= 0.16.2, < 2.a)
24
+ httpclient (>= 2.8.1, < 3.a)
25
+ mini_mime (~> 1.0)
26
+ representable (~> 3.0)
27
+ retriable (>= 2.0, < 4.a)
28
+ rexml
29
+ webrick
30
+ google-apis-youtube_v3 (0.25.0)
31
+ google-apis-core (>= 0.9.1, < 2.a)
32
+ googleauth (1.3.0)
33
+ faraday (>= 0.17.3, < 3.a)
34
+ jwt (>= 1.4, < 3.0)
35
+ memoist (~> 0.16)
36
+ multi_json (~> 1.11)
37
+ os (>= 0.9, < 2.0)
38
+ signet (>= 0.16, < 2.a)
39
+ httpclient (2.8.3)
40
+ jwt (2.6.0)
41
+ memoist (0.16.2)
42
+ mini_mime (1.1.2)
43
+ mkfifo (0.1.1)
44
+ multi_json (1.15.0)
45
+ os (1.1.4)
46
+ public_suffix (5.0.1)
47
+ representable (3.2.0)
48
+ declarative (< 0.1.0)
49
+ trailblazer-option (>= 0.1.1, < 0.2.0)
50
+ uber (< 0.2.0)
51
+ retriable (3.1.2)
52
+ rexml (3.2.5)
53
+ ruby2_keywords (0.0.5)
54
+ signet (0.17.0)
55
+ addressable (~> 2.8)
56
+ faraday (>= 0.17.5, < 3.a)
57
+ jwt (>= 1.5, < 3.0)
58
+ multi_json (~> 1.10)
59
+ thor (0.19.4)
60
+ trailblazer-option (0.1.2)
61
+ uber (0.1.0)
62
+ webrick (1.7.0)
63
+
64
+ PLATFORMS
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ byebug
69
+ ffmprb!
70
+ google-apis-youtube_v3
71
+
72
+ BUNDLED WITH
73
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ # Youtubby
2
+
3
+ ## TODO
4
+
5
+ The first goal is to create a (container-based) script --
6
+ for uploading raw footage -- from GoPro media storage -- to YouTube
7
+ -- that receives:
8
+ a. Google/YouTube creds (in source [exp/.google-\*] config files, plus,
9
+ after a built-in workaround manual auth'n procedure, within a persistent volume)
10
+ b. the name (id?) of the channel as a single command-line parameter
11
+ c. the GoPro media URLs (which apparently don't require auth'n creds to work),
12
+ snatched off the former's web app interface, one per line, complete
13
+ with an optional space-separated series of cut times
14
+ (\<URL\> [\<start hh:mm:ss\> \<end hh:mm:ss\>...])
15
+ -- and produces you-\<GOPs\>-YYYY-MM-DD-HH-MM.mp4 files within the media volume
16
+ as well as "Topublish uploaded on YYYY-MM-DD-HH-MM" *private* videos
17
+ on the channel
18
+
19
+ The temporary location of the runner script is `exp/gop-raw-cut-you-HD60`
20
+ and of the containerised script is `exp/gop-raw-cut-you-HD60.rb`;
21
+ the docker compose project is `youtubby_exp`
@@ -0,0 +1,8 @@
1
+ #!/bin/bash -e
2
+ test "$#" -ne 0 && echo "Unsupported args: $@" >&2 && exit 145
3
+ cd "$( dirname "${BASH_SOURCE[0]}" )"/..
4
+
5
+ # XXX docker run --rm -v "$( pwd ):/build" -w /build python:3.9 bash -c \
6
+ # "pip install google-api-python-client && pip freeze > requirements.txt"
7
+
8
+ docker run --rm -v "$( pwd ):/build" -w /build --network git_default ruby:2.7 bundle update
@@ -0,0 +1,20 @@
1
+ version: '2.1'
2
+ services:
3
+ youtubby:
4
+ build:
5
+ context: .
6
+ network: git_default
7
+ volumes:
8
+ - .:/youtubby
9
+ # XXX hardcoded
10
+ - /mnt/data/comp/drive/data/master/media:/media
11
+ - creds_fun:/var/fun/creds
12
+ environment:
13
+ - GOOGLE_CREDENTIAL_STORE=/var/fun/creds/gc/credentials.yml
14
+ - GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID
15
+ - GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET
16
+ - MEDIA_DIR=/media/tmp/exp
17
+ - FFMPRB_DEBUG=$DEBUG
18
+ entrypoint: ["bundle", "exec", "ruby"]
19
+ volumes:
20
+ creds_fun:
@@ -0,0 +1,13 @@
1
+ #!/bin/bash -ex
2
+ cd "$( dirname "${BASH_SOURCE[0]}" )"/..
3
+ test "$#" -ne 1 && echo "Usage: gop-raw-cut-you-HD60 [CHANNEL] < GOP-URL-CUT-S" >&2 && exit 145
4
+
5
+ COMPOSE_FILE=docker-compose.yml
6
+ COMPOSE_PROJECT_NAME=youtubby_exp
7
+
8
+ docker-compose build --force-rm
9
+
10
+ export GOOGLE_CLIENT_ID="$(< exp/.google-client-id)"
11
+ export GOOGLE_CLIENT_SECRET="$(< exp/.google-client-secret)"
12
+
13
+ docker-compose run --rm youtubby exp/gop-raw-cut-you-HD60.rb "$1"