Created
July 9, 2023 09:20
-
-
Save costa/a0088d8ab9f4b4fe16acce570e3a48e6 to your computer and use it in GitHub Desktop.
SEE (actually, WATCH) https://www.youtube.com/watch?v=l9sZkJ4syhE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|o| | |
# NOTE Run me like this: | <ffmprb> <OUT-PATH> | |
# NOTE A bit of a boilerplate for more independence: | |
# NOTE for previewing | |
# video_output_options = {resolution: Ffmprb::HD_720p, fps: 30, encoder: 'libx264'} | |
# NOTE for further processing: | |
video_output_options = {resolution: Ffmprb::HD_4K, fps: 60, encoder: 'huffyuv'} | |
# NOTE for viewing | |
# video_output_options = {resolution: Ffmprb::HD_1080p, fps: 60, encoder: 'libx265'} | |
# NOTE for dev | |
# video_output_options = {} | |
Ffmprb::Process.input_video_auto_rotate = true # NOTE surprisingly, for the sake of oiPad media...' | |
class FfmprbUnzipper | |
def initialize | |
@tmp_files = [] | |
@tmp_threads = [] | |
at_exit do | |
@tmp_threads.each &:join rescue warn "WARN #{$!}" | |
@tmp_files.each &:unlink | |
end | |
end | |
def concat_to_fifo(zip_path, video: :landscape) | |
video_opt = video && | |
{resolution: Ffmprb::HD_4K, fps: 60, encoder: 'huffyuv'}.tap do |opt| | |
w, h = opt[:resolution].split('x') | |
opt[:resolution] = "#{h}x#{w}" if | |
video == :portrait | |
end | |
Ffmprb::File.temp_fifo("-#{File.basename zip_path}.#{video ? 'avi' : 'wav'}").tap do |concat_tmp| | |
@tmp_files << concat_tmp | |
@tmp_threads << ( | |
Ffmprb::Util::Thread.new do | |
Dir.mktmpdir do |tmp_dir| | |
Ffmprb.process do | |
output concat_tmp, video: video_opt do | |
Dir.chdir tmp_dir do | |
`unzip '#{zip_path}'`.lines.sort.map { |l| $1 if l =~ /extracting:\s*\b(.*\.m\w*)\b/i }.compact | |
end.map{ |c| File.expand_path c, tmp_dir }.each do |path| | |
roll input path, video: video | |
end | |
end | |
end | |
end | |
end | |
) | |
end | |
end | |
end | |
class InputSlideCropper | |
def initialize(inp, cols = 1, rows = 1) | |
fail "not much of a slideshow" unless | |
rows > 0 && cols > 0 && rows + cols > 2 | |
@inp = inp | |
@h1 = 1.0/(@rows = rows) | |
@w1 = 1.0/(@cols = cols) | |
end | |
def slide(col = 1, row = 1) | |
fail "hey, I don't have that many (#{col}, #{row}) cols/rows, just #{@cols}, #{@rows}" unless | |
row <= @rows && col <= @cols | |
@inp.crop top: @h1 * (row - 1), bottom: @h1*(@rows - row), left: @w1 * (col - 1), right: @w1*(@cols - col) | |
end | |
end | |
class InputIntervalCuts < Array | |
attr_reader :time | |
def initialize(inv, int, time=0) | |
@time = time | |
@inv, @int = inv, int | |
end | |
def int(i, from, to) # NOTE! receives times of first [0] input | |
if i # NOTE else means out of the sequence (other-means) insert | |
fail "cannot comprehend: from == nil, but i != nil" unless | |
from # NOTE from == nil means it's silent, otherwise, voiced from-to | |
if last && from == last[1] | |
last[1] += to - from | |
else | |
self << [from, to, @time] | |
end | |
end | |
@time += to - (from || 0) | |
@inv[i].cut from: @int[i] + from - @int[0], to: @int[i] + to - @int[0] if | |
i | |
end | |
def pause(i, from, to, time) | |
@inv[i] | |
.cut(from: from - @int[0] + @int[i], to: to - @int[0] + @int[i]) | |
.pace((to - from)/time) | |
.cut(to: time) | |
.tap do | |
int nil, nil, time | |
end | |
end | |
def each_inp_with_at(ina) | |
each do |from, to, at| | |
ina.each_with_index do |ia, i| | |
yield ia.cut(from: @int[i] + from - @int[0], to: @int[i] + to - @int[0]), at if | |
ia # NOTE inactive (nil) input sources | |
end if # NOTE special from=nil (silent) entries | |
from | |
end | |
end | |
end | |
fuzr = FfmprbUnzipper.new | |
# NOTE the boilerplate ends here, change at will below: | |
inv = [ | |
input('/media/2023/07/05/IMG_0820.MOV') | |
.volume(0.01), | |
input('/media/2023/07/05/Movie Recording 2023-07-05 at 12.18.55.mov') | |
.volume(0.01), | |
input(fuzr.concat_to_fifo('/media/2023/07/05/GX010357.zip')) | |
.volume(0.01), | |
input('/media/2023/07/05/Screen Recording 2023-07-05 at 12.18.55.mov') | |
.volume(0.01) | |
] | |
int = [ 6.35, 2.97, 4.65, 2.00] | |
ina = [ | |
# input('/media/2023/07/05/IMG_0820.MOV', video: false), | |
nil, | |
# input('/media/2023/07/05/Movie Recording 2023-07-05 at 12.18.55.mov', video: false), | |
nil, | |
input(fuzr.concat_to_fifo('/media/2023/07/05/GX010357.zip', video: false)), | |
# input('/media/2023/07/05/Screen Recording 2023-07-05 at 12.18.55.mov', video: false), | |
nil | |
] | |
inp_ols = InputSlideCropper.new( | |
input('/media/2023/06/28/media_ 2023_06_28 proj_ costashapiro.com_works_ad - spaced.png', loop: true), | |
2, 5 | |
) | |
inp_mus = | |
input('/media/3000booking.de/Timboletti - Elektronisk Folke Dans - Guestmix.mp3') | |
.volume(0.09) | |
LENGTH = 454 | |
output o, video: video_output_options do # 12:27 | |
overlays = [] | |
cuts = InputIntervalCuts.new(inv, int) | |
#hl0 - coming in | |
lay cuts.int 0, 80.00, 85.00 | |
overlays << [inp_ols.slide(1, 1), cuts.time, 18] | |
#hl1 | |
lay cuts.int 1, 610.00, 628.50 | |
# | |
lay cuts.int 0, 92.00, 101.00 | |
#hl2 | |
lay cuts.int 1, 365.00, 370.60 | |
lay cuts.int 0, 370.60, 381.15 | |
lay cuts.int 2, 381.15, 382.30 | |
lay cuts.int 1, 382.30, 390.00 | |
# | |
lay cuts.int 0, 153.00, 160.50 | |
# | |
lay cuts.int 0, 165.00, 175.00 | |
# | |
lay cuts.int 0, 195.00, 208.00 | |
#hl0 + coming in | |
overlays << [inp_ols.slide(1, 2), cuts.time, 6] | |
lay cuts.int 0, 58.00, 70.00 | |
lay cuts.int 0, 80.00, 85.00 | |
# | |
lay cuts.int 1, 212.00, 226.20 | |
overlays << [inp_ols.slide(2, 2), cuts.time, 12] | |
lay cuts.int 1, 226.20, 234.75 | |
lay cuts.int 0, 234.75, 238.40 | |
lay cuts.int 1, 238.40, 249.65 | |
overlays << [inp_ols.slide(2, 3), cuts.time, 6] | |
lay cuts.int 0, 249.65, 255.45 | |
lay cuts.int 1, 255.45, 298.80 | |
overlays << [inp_ols.slide(1, 3), cuts.time, 54] | |
lay cuts.int 1, 298.80, 301.20 | |
lay cuts.int 0, 301.20, 326.50 | |
# | |
lay cuts.int 1, 337.80, 340.90 | |
lay cuts.int 2, 340.90, 342.35 | |
lay cuts.int 0, 342.35, 352.65 | |
lay cuts.int 2, 352.65, 353.85 | |
lay cuts.int 0, 353.85, 358.00 | |
# | |
lay cuts.int 1, 365.00, 370.60 | |
lay cuts.int 0, 370.60, 381.15 | |
lay cuts.int 2, 381.15, 382.30 | |
lay cuts.int 1, 382.30, 383.65 | |
lay cuts.int 0, 383.65, 390.00 | |
# | |
lay cuts.int 0, 414.00, 441.00 | |
# | |
lay cuts.int 0, 462.00, 468.20 | |
lay cuts.int 1, 468.20, 471.70 | |
lay cuts.int 2, 471.70, 475.05 | |
lay cuts.int 0, 475.05, 480.70 | |
lay cuts.int 1, 480.70, 486.05 | |
lay cuts.int 0, 486.05, 498.90 | |
# | |
lay cuts.int 0, 527.00, 530.00 | |
lay cuts.int 2, 530.00, 531.40 | |
lay cuts.int 1, 531.40, 545.30 | |
lay cuts.int 2, 545.30, 549.00 | |
overlays << [inp_ols.slide(1, 4), cuts.time, 18] | |
# | |
lay cuts.int 1, 582.95, 592.05 | |
lay cuts.int 3, 592.05, 593.95 | |
lay cuts.int 0, 593.95, 597.75 | |
lay cuts.int 1, 597.75, 600.10 | |
lay cuts.int 0, 600.10, 606.85 | |
lay cuts.int 2, 606.85, 608.15 | |
overlays << [inp_ols.slide(2, 4), cuts.time, 6] | |
lay cuts.int 1, 608.15, 628.50 | |
# | |
lay cuts.int 1, 633.85, 640.25 | |
lay cuts.int 3, 640.25, 642.60 | |
lay cuts.int 0, 642.60, 652.00 | |
overlays << [inp_ols.slide(1, 5), cuts.time, 6] | |
# | |
lay cuts.int 1, 696.00, 720.00 | |
overlays << [inp_ols.slide(2, 5), cuts.time, 12] | |
lay cuts.int 0, 720.00, 736.50 | |
fail "Script-internal error: total LENGTH !~ #{cuts.time}" unless | |
(cuts.time - LENGTH).abs < 1 | |
cuts.each_inp_with_at ina do |inp, at| | |
overlay inp, at: at | |
end | |
# NOTE suppose the logo is at (2, 1), | |
# the overlays are sequential and at least couple of seconds apart | |
# and the logo comes up after the first overlay and disappears before the last | |
inp_logo = inp_ols.slide(2, 1) | |
before_at = 0 | |
overlays.each do |inp, at, len| | |
unless before_at == 0 | |
overlay inp_logo.cut(to: at - before_at - 2), at: before_at + 1 | |
end | |
overlay inp.cut(to: len), at: at | |
before_at = at + len | |
end | |
overlay inp_mus.cut(to: LENGTH - 6*2), at: 6 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment