Outils de montage des vidéos

converter.rb 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #!/usr/bin/env ruby
  2. require 'nokogiri'
  3. require 'tempfile'
  4. require 'time'
  5. require 'awesome_print'
  6. RESOLUTION = 720
  7. AUDIO_FREQUENCY = 48000
  8. ROOT_DIRECTORY = File.expand_path '.'
  9. INPUT_DIRECTORY = File.join ROOT_DIRECTORY, 'input'
  10. PASS1_DIRECTORY = File.join ROOT_DIRECTORY, 'pass1'
  11. PASS2_DIRECTORY = File.join ROOT_DIRECTORY, 'pass2'
  12. OUTPUT_DIRECTORY = File.join ROOT_DIRECTORY, 'output'
  13. [PASS1_DIRECTORY, PASS2_DIRECTORY, OUTPUT_DIRECTORY].each do |dir|
  14. Dir.mkdir dir unless Dir.exists? dir
  15. end
  16. CONFIG = eval File.read File.join 'input', "#{ARGV[0]}.rb"
  17. FFMPEG = {
  18. audio: {
  19. vorbis: %w(-strict -2 -codec:a vorbis -b:a 128k -ac 2),
  20. wav: %W(-acodec pcm_s16le -ar #{AUDIO_FREQUENCY} -ac 1)
  21. },
  22. video: {
  23. 480 => %w(-codec:v libvpx -auto-alt-ref 0 -b:v 600k -maxrate 600k -bufsize 1200k -qmin 10 -qmax 42),
  24. 576 => %w(-codec:v libvpx -auto-alt-ref 0 -b:v 1000k -maxrate 1000k -bufsize 2000k -qmin 10 -qmax 42),
  25. 720 => %w(-codec:v libvpx -auto-alt-ref 0 -b:v 2000k -maxrate 2000k -bufsize 4000k -qmin 10 -qmax 42)
  26. }
  27. }
  28. class File
  29. class <<self
  30. alias_method :exists_old?, :exists?
  31. end
  32. def self.exists?(*args)
  33. args.all? { |f| self.exists_old? f }
  34. end
  35. end
  36. class Args
  37. def initialize
  38. @args = []
  39. end
  40. def <<(args)
  41. case (args)
  42. when Array then
  43. @args += args.collect { |a| a.to_s }
  44. else
  45. @args << args.to_s
  46. end
  47. self
  48. end
  49. def to_a
  50. @args
  51. end
  52. end
  53. def ffmpeg(args)
  54. args = args.to_a
  55. args = %w(ffmpeg -y -hide_banner) + args
  56. #args = %w(avconv -y) + args
  57. puts args.join ' '
  58. unless system *args
  59. puts args.join ' '
  60. exit -1
  61. end
  62. end
  63. def generate_svg_begin(conference)
  64. name = conference[:file]
  65. begin_ = File.join INPUT_DIRECTORY, 'begin.svg'
  66. svg = File.join PASS1_DIRECTORY, "#{name}.svg"
  67. return if File.exists?(svg)
  68. licence = conference[:licence] || 'Licence CC-BY-SA'
  69. location = conference[:location]
  70. author = conference[:author]
  71. title = conference[:title]
  72. xml = File.open(begin_) { |f| Nokogiri::XML f }
  73. xml.at_css('#title tspan').content = title
  74. xml.at_css('#location').content = location
  75. xml.at_css('#author tspan').content = author
  76. xml.at_css('#licence tspan').content = licence if licence
  77. File.write svg, xml.to_xml
  78. end
  79. def generate_video_from_image(image, video, duration=5)
  80. ffmpeg Args.new << %w(-loop 1 -f image2 -i) << image \
  81. << %W(-acodec pcm_s16le -f s16le -ac 2 -ar #{AUDIO_FREQUENCY} -i /dev/zero) \
  82. << ['-t', duration] \
  83. << %w(-map 0:0 -map 1:0) \
  84. << %W(-vf setdar=4/3,setsar=16/15,scale=-1:#{RESOLUTION}) \
  85. << FFMPEG[:video][RESOLUTION] \
  86. << FFMPEG[:audio][:vorbis] << video
  87. end
  88. def generate_video_from_svg(svg, video, duration=5)
  89. svg = File.join PASS1_DIRECTORY, "#{name}.svg"
  90. png = File.join PASS2_DIRECTORY, "#{name}.png"
  91. title = File.join PASS2_DIRECTORY, "#{name}-title.webm"
  92. system('inkscape', '-e', png, svg) unless File.exists?(png)
  93. generate_video_from_image png, title, duration unless File.exists?(title)
  94. end
  95. def generate_video_begin(conference, duration=5)
  96. name = conference[:file]
  97. svg = File.join PASS1_DIRECTORY, "#{name}.svg"
  98. png = File.join PASS2_DIRECTORY, "#{name}.png"
  99. title = File.join PASS2_DIRECTORY, "#{name}-title.webm"
  100. system('inkscape', '-e', png, svg) unless File.exists?(png)
  101. generate_video_from_image png, title, duration unless File.exists?(title)
  102. end
  103. def generate_video_end(duration=5)
  104. svg = File.join INPUT_DIRECTORY, "end.svg"
  105. png = File.join PASS2_DIRECTORY, 'end.png'
  106. video = File.join PASS2_DIRECTORY, 'end.webm'
  107. return if File.exists?(video)
  108. system 'inkscape', '-e', png, svg unless File.exists? png
  109. generate_video_from_image png, video, duration
  110. end
  111. def extract(conference)
  112. name = conference[:file]
  113. input = File.join INPUT_DIRECTORY, "#{name}.webm"
  114. full = File.join PASS1_DIRECTORY, "#{name}-full.webm"
  115. audio = File.join PASS1_DIRECTORY, "#{name}.wav"
  116. video = File.join PASS1_DIRECTORY, "#{name}.webm"
  117. #return if File.exists?(audio, video)
  118. from = conference[:from]
  119. to = conference[:to]
  120. to = Time.parse(to) - Time.parse(from) if from and to
  121. args = Args.new
  122. # Input
  123. args << ['-ss', from] if from
  124. args << ['-t', to] if to
  125. args << ['-i', input]
  126. args << %w[-codec:v copy -codec:a copy]
  127. args << full
  128. ffmpeg args unless File.exists? full
  129. args = Args.new
  130. # Input
  131. args << ['-i', full]
  132. # Output 1 : audio
  133. args << %w(-vn -ac 1)
  134. args << audio
  135. # Output 2 : video
  136. args << %W(-an -codec:v copy)
  137. args << video
  138. ffmpeg args unless File.exists?(audio, video)
  139. end
  140. def merge(conference)
  141. name = conference[:file]
  142. audio = File.join PASS1_DIRECTORY, "#{name}.wav"
  143. video = File.join PASS1_DIRECTORY, "#{name}.webm"
  144. output = File.join PASS2_DIRECTORY, "#{name}.webm"
  145. return if File.exists?(output)
  146. ffmpeg Args.new << ['-i', audio, '-i', video] \
  147. << FFMPEG[:audio][:vorbis] << %w(-codec:v copy) << output
  148. end
  149. def concat(conference)
  150. name = conference[:file]
  151. begin_ = File.join PASS2_DIRECTORY, "#{name}-title.webm"
  152. video = File.join PASS2_DIRECTORY, "#{name}.webm"
  153. end_ = File.join PASS2_DIRECTORY, 'end.webm'
  154. output = File.join OUTPUT_DIRECTORY, "#{name}.webm"
  155. return if !File.exists?(begin_, video, end_) or File.exists?(output)
  156. Tempfile.create 'concat' do |f|
  157. f.puts [begin_, video, end_].collect { |f| "file #{f}" }
  158. f.flush
  159. ffmpeg Args.new << ['-f', 'concat', '-safe', '0', '-i', f.path, '-c', 'copy', output]
  160. end
  161. end
  162. def process
  163. generate_video_end
  164. CONFIG.each do |conference|
  165. case ARGV[1].to_sym
  166. when :pass1
  167. generate_svg_begin conference
  168. extract conference
  169. # Edit SVG if necessary
  170. # Normalize WAV
  171. when :pass2
  172. merge conference
  173. generate_video_begin conference
  174. concat conference
  175. end
  176. end
  177. end
  178. process if __FILE__ == $0
  179. #generate_video_from_image 'pass2/malicia_sexe-heure-numerique-warning.png', 'pass2/malicia_sexe-heure-numerique-warning.webm'