Created
July 3, 2017 21:59
-
-
Save aeris/55c620d0d449093f3c8a56a231f525e2 to your computer and use it in GitHub Desktop.
Montage vidéo pour PSES & Ubuntu Party
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 | |
require 'tempfile' | |
require 'open3' | |
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 | |
p args | |
# Open3.popen3(*args) do |stdin, stdout, stderr, thread| | |
# $stdout.print stdout.read | |
# $stderr.print stderr.read | |
# { out: [stdout, $stdout], err: [stderr, $stderr] }.each do |key, stream| | |
# input, output = stream | |
# Thread.new do | |
# p "starting #{key}" | |
# until (line = stream.gets).nil? do | |
# p "reading #{key}" | |
# output.puts line | |
# end | |
# p "end #{key}" | |
# end | |
# end | |
# | |
# thread.join | |
# end | |
system *args | |
end | |
def concat(args) | |
from = args[0..-2] | |
to = "../input/#{args[-1]}.webm" | |
Tempfile.open('concat') do |t| | |
from.each { |f| t.puts "file '#{File.join Dir.pwd, f}'" } | |
t.flush | |
system 'cat', t.path | |
ffmpeg Args.new << ['-f', 'concat', '-safe', 0, '-i', t.path, '-c', 'copy', to] | |
end | |
end | |
concat ARGV |
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 | |
require 'nokogiri' | |
require 'tempfile' | |
require 'time' | |
require 'awesome_print' | |
RESOLUTION = 720 | |
AUDIO_FREQUENCY = 48000 | |
ROOT_DIRECTORY = File.expand_path '.' | |
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', "#{ARGV[0]}.rb" | |
FFMPEG = { | |
audio: { | |
vorbis: %w(-strict -2 -codec:a vorbis -b:a 128k -ac 2), | |
wav: %W(-acodec pcm_s16le -ar #{AUDIO_FREQUENCY} -ac 1) | |
}, | |
video: { | |
480 => %w(-codec:v libvpx -auto-alt-ref 0 -b:v 600k -maxrate 600k -bufsize 1200k -qmin 10 -qmax 42 -threads 8), | |
576 => %w(-codec:v libvpx -auto-alt-ref 0 -b:v 1000k -maxrate 1000k -bufsize 2000k -qmin 10 -qmax 42 -threads 8), | |
720 => %w(-codec:v libvpx -auto-alt-ref 0 -b:v 2000k -maxrate 2000k -bufsize 4000k -qmin 10 -qmax 42 -threads 8) | |
} | |
} | |
class File | |
class <<self | |
alias_method :exists_old?, :exists? | |
end | |
def self.exists?(*args) | |
args.all? { |f| self.exists_old? f } | |
end | |
end | |
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(conference) | |
name = conference[:file] | |
begin_ = File.join INPUT_DIRECTORY, 'begin.svg' | |
svg = File.join PASS1_DIRECTORY, "#{name}.svg" | |
return if !File.exists?(begin_) or File.exists?(svg) | |
licence = conference[:licence] || 'Licence CC-BY-SA' | |
location = conference[:location] | |
author = conference[:author] | |
title = conference[:title] | |
xml = File.open(begin_) { |f| Nokogiri::XML f } | |
xml.at_css('#title tspan').content = title | |
xml.at_css('#location').content = location | |
xml.at_css('#author tspan').content = author | |
xml.at_css('#licence tspan').content = licence if 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) \ | |
<< %W(-vf setdar=4/3,setsar=16/15,scale=-1:#{RESOLUTION}) \ | |
<< FFMPEG[:video][RESOLUTION] \ | |
<< 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" | |
system('inkscape', '-e', png, svg) if File.exists?(svg) and !File.exists?(png) | |
generate_video_from_image png, title, duration if File.exists?(png) and !File.exists?(title) | |
end | |
def generate_video_end(duration=5) | |
svg = File.join INPUT_DIRECTORY, "end.svg" | |
png = File.join PASS2_DIRECTORY, 'end.png' | |
video = File.join PASS2_DIRECTORY, 'end.webm' | |
return if !File.exists?(svg) or File.exists?(video) | |
system 'inkscape', '-e', png, svg unless File.exists? png | |
generate_video_from_image png, video, duration | |
end | |
def extract(conference) | |
name = conference[:file] | |
input = File.join INPUT_DIRECTORY, "#{name}.webm" | |
audio = File.join PASS1_DIRECTORY, "#{name}.wav" | |
video = File.join PASS1_DIRECTORY, "#{name}.webm" | |
return if !File.exists?(input) or File.exists?(audio, video) | |
from = conference[:from] | |
to = conference[:to] | |
to = Time.parse(to) - Time.parse(from) if from and to | |
args = Args.new | |
# Input | |
# args << ['-ss', from] if from | |
# args << ['-t', to] if to | |
args << ['-i', input] | |
# Output 1 : audio | |
args << %w(-vn -ac 1) | |
args << ['-ss', from] if from | |
args << ['-t', to] if to | |
args << audio | |
# Output 2 : video | |
args << %W(-an -codec:v copy) | |
args << ['-ss', from] if from | |
args << ['-t', to] if to | |
args << 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?(audio, video) or File.exists?(output) | |
ffmpeg Args.new << ['-i', audio, '-i', video] \ | |
<< FFMPEG[:audio][:vorbis] << %w(-codec:v copy -threads 8) << 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?(begin_, video, end_) or File.exists?(output) | |
Tempfile.create 'concat' do |f| | |
f.puts [begin_, video, end_].collect { |f| "file #{f}" } | |
f.flush | |
ffmpeg Args.new << ['-f', 'concat', '-safe', '0', '-i', f.path, '-c', 'copy', '-threads', '8', output] | |
end | |
end | |
def process | |
generate_video_end | |
CONFIG.each do |conference| | |
case ARGV[1].to_sym | |
when :pass1 | |
generate_svg_begin conference | |
extract conference | |
# Edit SVG if necessary | |
# Normalize WAV | |
when :pass2 | |
merge conference | |
generate_video_begin conference | |
concat conference | |
end | |
end | |
end | |
process if __FILE__ == $0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment