Skip to content

Instantly share code, notes, and snippets.

@costa
Created July 9, 2023 09:20
Show Gist options
  • Save costa/a0088d8ab9f4b4fe16acce570e3a48e6 to your computer and use it in GitHub Desktop.
Save costa/a0088d8ab9f4b4fe16acce570e3a48e6 to your computer and use it in GitHub Desktop.
|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