Skip to content

Instantly share code, notes, and snippets.

@hyuki
Last active May 4, 2020 01:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hyuki/76edfdff6ffddcdbee83ac1883e515ce to your computer and use it in GitHub Desktop.
Save hyuki/76edfdff6ffddcdbee83ac1883e515ce to your computer and use it in GitHub Desktop.
txt-to-mp3: Convert txt to mp3 with Amazon Polly. txt-to-mp3 (テキストファイルを分割してAmazon Pollyでmp3に変換し、ffmpegを使って合成するRubyスクリプト)
MP3DIR: ./mp3
WHO: Mizuki
<!-- opening.mp3 -->
おはようございます。
<!-- chime1.mp3 -->
こんにちは。
<!-- chime2.mp3 -->
おやすみなさい。
<!-- ending.mp3 -->
#!/usr/bin/env ruby
require 'fileutils'
require 'tmpdir'
require 'pp'
require 'yaml'
APPNAME = 'txt-to-mp3'
if ARGV.length != 3
puts "Usage: txt-to-mp3 config.yaml input.txt output.mp3"
puts
puts "input.txt の例"
puts <<-"EOD"
■input.txt の例:
<!-- opening.mp3 -->
おはようございます。
<!-- chime1.mp3 -->
こんにちは。
<!-- chime2.mp3 -->
おやすみなさい。
<!-- ending.mp3 -->
■config.yaml の例:
MP3DIR: ./mp3
WHO: Mizuki
EOD
abort
end
CONFIG = YAML.load_file(ARGV[0])
def extract_mp3(line)
if line.match(/^<!-- (.+\.mp3) -->/)
$1
else
nil
end
end
def split_txt_to_sections(input_txt)
mp3dir = CONFIG['MP3DIR']
if not Dir.exist?(mp3dir)
abort "#{APPNAME}: #{mp3dir} is not found."
end
sections = []
lines = IO.readlines(input_txt)
cursection = ''
lines.each do |line|
mp3 = extract_mp3(line)
if mp3
sections << { :type => :text, :content => cursection }
cursection = ''
sections << { :type => :mp3, :content => "#{mp3dir}/#{mp3}" }
else
cursection += line
end
end
if cursection.size > 0
sections << { :type => :text, :content => cursection }
end
sections
end
def sections_to_files(sections, tmpdir)
index = 1
files = []
sections.each do |section|
if section[:type] == :text
filename = sprintf("#{tmpdir}/_%d.txt", index)
open(filename, "w") do |f|
f.print section[:content]
end
files << { :type => :text, :filename => filename }
index += 1
elsif section[:type] == :mp3
files << { :type => :mp3, :filename => section[:content] }
else
abort "#{APPNAME}: sections_to_files: invalid type: " + section[:type].to_s
end
end
files
end
def convert_files_to_mp3files(files)
mp3files = []
files.each do |file|
if file[:type] == :text
filename = file[:filename]
mp3filename = filename + '.mp3'
puts "Converting: " + filename
cmd = "aws polly synthesize-speech --output-format mp3 --text file://#{filename} --voice-id #{CONFIG['WHO']} #{mp3filename}"
puts cmd
err = system(cmd)
puts err
mp3files << mp3filename
elsif file[:type] == :mp3
mp3files << file[:filename]
else
abort "#{APPNAME}: convert_txt_to_mp3: invalid type: " + file[:type].to_s
end
end
mp3files
end
def main(input_txt, output_mp3)
puts "Input text file: #{input_txt}"
puts "Output mp3 file: #{output_mp3}"
puts "MP3 dir: #{CONFIG['MP3DIR']}"
Dir.mktmpdir do |tmpdir|
sections = split_txt_to_sections(input_txt)
files = sections_to_files(sections, tmpdir)
mp3files = convert_files_to_mp3files(files)
mp3s = mp3files.join('|')
cmd = "ffmpeg -y -i \"concat:#{mp3s}\" #{output_mp3}"
puts cmd
result = system(cmd)
puts result
end
end
main(ARGV[1], ARGV[2])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment