Created
June 24, 2015 22:19
-
-
Save aeris/536da296f83cd3995c8f to your computer and use it in GitHub Desktop.
Automatize video encoding for publication
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
#!/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