Last active
June 11, 2020 01:08
-
-
Save j6s/096ff1e1ce7fc21887d61387f0f81f7b to your computer and use it in GitHub Desktop.
merge_to_opus.rb
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 'digest' | |
# | |
# A script that merges multiple audio files to a single OPUS File with chapter | |
# markers based on the input files. | |
# Every input file will be one chapter in the output OPUS File. | |
# The ouptut file is hardcoded to be 'merged.opus' | |
# | |
# For more information about OPUS Chapter marks: | |
# => https://wiki.xiph.org/Chapter_Extension | |
# | |
# For more information about the ffmpeg concat filter: | |
# => https://trac.ffmpeg.org/wiki/Concatenate | |
# => https://ffmpeg.org/ffmpeg-filters.html#concat | |
# | |
# USAGE: ./merge_to_opus.rb file1.flac file2.flac file3.flac ... | |
# EXAMPLE: ./merge_to_opus Chapter_*.flac | |
# AUTHOR: Johannes Hertenstein, 2017 | |
# LICENSE: WTFPL | |
# | |
# | |
# General settings. | |
# The output file format and options can be set here. | |
# | |
options = [ | |
'-acodec libopus', | |
'-b:a 64000', | |
'-vbr on', | |
'-compression_level 10', | |
'-safe 0' | |
] | |
# | |
# Formats a time in seconds according to the OPUS chapter marks specification. | |
# The output will always be a string of fixed length with the following structure: | |
# HH:MM:SS.SSS | |
# | |
def format_time(seconds) | |
minutes = 0 | |
hours = 0 | |
while seconds >= 60 | |
minutes += 1 | |
seconds -= 60 | |
end | |
while minutes >= 60 | |
hours += 1 | |
minutes -= 60 | |
end | |
hours = hours.to_s.rjust(2,'0') | |
minutes = minutes.to_s.rjust(2,'0') | |
seconds = sprintf('%.3f', seconds).rjust(6, '0') | |
return "#{hours}:#{minutes}:#{seconds}" | |
end | |
total_length=0 | |
chapter=1 | |
files = ARGV | |
files.each do |file| | |
# Find out the time of the current chapter | |
seconds = `ffprobe -i #{file} -show_entries format=duration -v quiet -of csv="p=0"`.to_i | |
time = format_time(total_length) | |
total_length += seconds | |
# Extract a chapter name from the filename | |
name = file.split('.')[0...-1].join('.') | |
# format the chapter number | |
num = chapter.to_s.rjust(3, '0') | |
# Add the metadata options to ffmpegs optionlist | |
options.push("-metadata CHAPTER#{num}=#{time}") | |
options.push("-metadata CHAPTER#{num}NAME=#{name}") | |
chapter += 1 | |
end | |
# build filter_complex map. This map specifies which stream of which input | |
# file should be mapped to which stream of the output. For us this is easy: | |
# The input files have one audio stream as does the output. | |
filter_complex_map = (0...files.length).map{ |i| "[#{i}:a:0]" }.join(' ') | |
filter_complex = filter_complex_map + ' concat=n=' + files.length.to_s + ':v=0:a=1 [a]' | |
input_files = files.map{ |f| "-i #{f}" }.join(" \\\n\t ") | |
# Finally: Execute ffmpeg | |
cmd = "ffmpeg #{input_files} " + | |
" -filter_complex \"#{filter_complex}\" \\\n\t " + | |
" -map '[a]' \\\n\t " + | |
options.join(" \\\n\t ") + | |
" \\\n\t merged.opus " | |
puts "$ #{cmd}" | |
system(cmd) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This version does not support filenames with spaces. I added support for such filenames by enclosing some of the generated arguments in (escaped) parentheses :) See the diff below. Maybe merge this with your version? :)