Skip to content

Instantly share code, notes, and snippets.

@aeris
Created June 24, 2015 22:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aeris/536da296f83cd3995c8f to your computer and use it in GitHub Desktop.
Save aeris/536da296f83cd3995c8f to your computer and use it in GitHub Desktop.
Automatize video encoding for publication
#!/usr/bin/env ruby
# Licence : AGPLv3+
require 'nokogiri'
require 'tempfile'
require 'time'
RESOLUTION = :p576
AUDIO_FREQUENCY = 44100
ROOT_DIRECTORY = File.expand_path (ARGV[0] || '.')
INPUT_DIRECTORY = File.join ROOT_DIRECTORY, 'input'
PASS1_DIRECTORY = File.join ROOT_DIRECTORY, 'pass1'
PASS2_DIRECTORY = File.join ROOT_DIRECTORY, 'pass2'
OUTPUT_DIRECTORY = File.join ROOT_DIRECTORY, 'output'
[PASS1_DIRECTORY, PASS2_DIRECTORY, OUTPUT_DIRECTORY].each do |dir|
Dir.mkdir dir unless Dir.exists? dir
end
CONFIG = eval File.read(File.join INPUT_DIRECTORY, 'config.rb')
FFMPEG = {
audio: {
vorbis: %w(-strict -2 -codec:a vorbis -b:a 128k -ac 2),
wav: %W(-acodec pcm_s16le -f s16le -ar #{AUDIO_FREQUENCY} -ac 1)
},
video: {
p480: %w(-codec:v libvpx -b:v 600k -maxrate 600k -bufsize 1200k -qmin 10 -qmax 42 -vf scale=h=480 -threads 8),
p576: %w(-codec:v libvpx -b:v 1000k -maxrate 1000k -bufsize 2000k -qmin 10 -qmax 42 -vf scale=h=576 -threads 8),
p720: %w(-codec:v libvpx -b:v 2000k -maxrate 2000k -bufsize 4000k -qmin 10 -qmax 42 -vf scale=h=720 -threads 8)
}
}
class Args
def initialize
@args = []
end
def <<(args)
case (args)
when Array then
@args += args.collect { |a| a.to_s }
else
@args << args.to_s
end
self
end
def to_a
@args
end
end
def ffmpeg(args)
args = args.to_a
args = %w(ffmpeg -y) + args
#args = %w(avconv -y) + args
puts args.join ' '
unless system *args
puts args.join ' '
exit -1
end
end
def generate_svg_begin(file, conference)
name = conference[:file]
svg = File.join PASS1_DIRECTORY, "#{name}.svg"
return if File.exists? svg
licence = conference[:licence] || file[:licence] || ''
location = conference[:location] || file[:location] || ''
author = conference[:author] || ''
title = conference[:title] || ''
xml = Nokogiri::XML File.open File.join INPUT_DIRECTORY, 'begin.svg'
xml.at_css('#title').content = title
xml.at_css('#location').content = location
xml.at_css('#author').content = author
xml.at_css('#licence').content = licence
File.write svg, xml.to_xml
end
def generate_video_from_image(image, video, duration=5)
ffmpeg Args.new << %w(-loop 1 -f image2 -i) << image \
<< %W(-acodec pcm_s16le -f s16le -ac 2 -ar #{AUDIO_FREQUENCY} -i /dev/zero) \
<< ['-t', duration] \
<< %w(-map 0:0 -map 1:0) \
<< FFMPEG[:video][RESOLUTION] << %w(-vf setdar=4/3 -vf setsar=16/15) \
<< FFMPEG[:audio][:vorbis] << video
end
def generate_video_begin(conference, duration=5)
name = conference[:file]
svg = File.join PASS1_DIRECTORY, "#{name}.svg"
png = File.join PASS2_DIRECTORY, "#{name}.png"
title = File.join PASS2_DIRECTORY, "#{name}-title.webm"
return if File.exists? title
system 'inkscape', '-e', png, svg
generate_video_from_image png, title, duration
end
def generate_video_end(duration=5)
svg = File.join INPUT_DIRECTORY, "end.svg"
png = File.join PASS1_DIRECTORY, 'end.png'
video = File.join PASS2_DIRECTORY, 'end.webm'
return if File.exists? video
system 'inkscape', '-e', png, svg unless File.exists? png
generate_video_from_image png, video, duration
end
def extract(file, conference)
name = conference[:file]
input = File.join INPUT_DIRECTORY, file
audio = File.join PASS1_DIRECTORY, "#{name}.wav"
video = File.join PASS1_DIRECTORY, "#{name}.webm"
return if File.exists? audio and File.exists? video
from = conference[:from]
to = conference[:to]
to = Time.parse(to) - Time.parse(from) if from and to
args = Args.new
args << ['-ss', from] if from
args << ['-i', input]
args << ['-to', to] if to
#args << '-vn' << FFMPEG[:audio][:wav] << audio
args << %w(-vn -ac 1) << audio
args << ['-t', to] if to
args << '-an' << FFMPEG[:video][RESOLUTION] << video
ffmpeg args
end
def merge(conference)
name = conference[:file]
audio = File.join PASS1_DIRECTORY, "#{name}.wav"
video = File.join PASS1_DIRECTORY, "#{name}.webm"
output = File.join PASS2_DIRECTORY, "#{name}.webm"
return if File.exists? output
ffmpeg Args.new << ['-i', audio, '-i', video] \
<< FFMPEG[:audio][:vorbis] << %w(-codec:v copy) << output
end
def concat(conference)
name = conference[:file]
begin_ = File.join PASS2_DIRECTORY, "#{name}-title.webm"
video = File.join PASS2_DIRECTORY, "#{name}.webm"
end_ = File.join PASS2_DIRECTORY, 'end.webm'
output = File.join OUTPUT_DIRECTORY, "#{name}.webm"
return if File.exists? output
Tempfile.create 'concat' do |f|
f.write "file #{begin_}\n"
f.write "file #{video}\n"
f.write "file #{end_}\n"
f.flush
ffmpeg Args.new << ['-f', 'concat', '-i', f.path, '-c', 'copy', output]
end
#ffmpeg Args.new << ['-i', "concat:#{begin_}|#{video}|#{end_}", '-c', 'copy', output]
end
generate_video_end
CONFIG.each do |filename, file|
puts filename
file[:conferences].each do |conference|
puts conference[:file]
# Pass 1
generate_svg_begin file, conference
#extract filename, conference
# Edit SVG if necessary
# Normalize WAV
# Pass 2
generate_video_begin conference
#merge conference
concat conference
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment