├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.rdoc ├── Rakefile ├── VERSION ├── bin ├── text2mp3 └── tts ├── example ├── gen_hello_world_en.rb └── play_hello_world_en.rb ├── lib └── tts.rb ├── spec └── tts_spec.rb └── tts.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 18 | # 19 | # * Create a file at ~/.gitignore 20 | # * Include files you want ignored 21 | # * Run: git config --global core.excludesfile ~/.gitignore 22 | # 23 | # After doing this, these files will be ignored in all your git projects, 24 | # saving you from having to 'pollute' every project you touch with them 25 | # 26 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 27 | # 28 | # For MacOS: 29 | # 30 | #.DS_Store 31 | 32 | # For TextMate 33 | #*.tmproj 34 | #tmtags 35 | 36 | # For emacs: 37 | #*~ 38 | #\#* 39 | #.\#* 40 | 41 | # For vim: 42 | #*.swp 43 | 44 | # For redcar: 45 | #.redcar 46 | 47 | # For rubinius: 48 | #*.rbc 49 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | group :development do 4 | gem "rspec" 5 | gem "bundler", "~> 2.0" 6 | gem "jeweler" 7 | # gem "rcov", ">= 0" 8 | end 9 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | addressable (2.4.0) 5 | builder (3.2.3) 6 | descendants_tracker (0.0.4) 7 | thread_safe (~> 0.3, >= 0.3.1) 8 | diff-lcs (1.3) 9 | faraday (0.9.2) 10 | multipart-post (>= 1.2, < 3) 11 | git (1.5.0) 12 | github_api (0.16.0) 13 | addressable (~> 2.4.0) 14 | descendants_tracker (~> 0.0.4) 15 | faraday (~> 0.8, < 0.10) 16 | hashie (>= 3.4) 17 | mime-types (>= 1.16, < 3.0) 18 | oauth2 (~> 1.0) 19 | hashie (3.6.0) 20 | highline (2.0.2) 21 | jeweler (2.3.9) 22 | builder 23 | bundler 24 | git (>= 1.2.5) 25 | github_api (~> 0.16.0) 26 | highline (>= 1.6.15) 27 | nokogiri (>= 1.5.10) 28 | psych 29 | rake 30 | rdoc 31 | semver2 32 | jwt (2.1.0) 33 | mime-types (2.99.3) 34 | mini_portile2 (2.4.0) 35 | multi_json (1.13.1) 36 | multi_xml (0.6.0) 37 | multipart-post (2.1.0) 38 | nokogiri (1.10.3) 39 | mini_portile2 (~> 2.4.0) 40 | oauth2 (1.4.1) 41 | faraday (>= 0.8, < 0.16.0) 42 | jwt (>= 1.0, < 3.0) 43 | multi_json (~> 1.3) 44 | multi_xml (~> 0.5) 45 | rack (>= 1.2, < 3) 46 | psych (3.1.0) 47 | rack (2.0.7) 48 | rake (12.3.2) 49 | rdoc (6.1.1) 50 | rspec (3.8.0) 51 | rspec-core (~> 3.8.0) 52 | rspec-expectations (~> 3.8.0) 53 | rspec-mocks (~> 3.8.0) 54 | rspec-core (3.8.0) 55 | rspec-support (~> 3.8.0) 56 | rspec-expectations (3.8.3) 57 | diff-lcs (>= 1.2.0, < 2.0) 58 | rspec-support (~> 3.8.0) 59 | rspec-mocks (3.8.0) 60 | diff-lcs (>= 1.2.0, < 2.0) 61 | rspec-support (~> 3.8.0) 62 | rspec-support (3.8.0) 63 | semver2 (3.4.2) 64 | thread_safe (0.3.6) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | bundler (~> 2.0) 71 | jeweler 72 | rspec 73 | 74 | BUNDLED WITH 75 | 2.0.1 76 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Yiling Cao / Charlie Revett 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = tts 2 | 3 | Using the Google Translate service as the speech engine, this gem generates a .mp3 voice file from any given string. 4 | 5 | == Usage 6 | 7 | require 'tts' 8 | # Will download "Hello World!.mp3" to your current directory 9 | # Supported languages: ["zh", "en", "it", "fr"] 10 | "Hello World!".to_file "en" 11 | 12 | # i18n 13 | "人民代表代表人民".to_file "zh" 14 | 15 | # Save the file to a specific location 16 | "Light rain with highs of 5 degrees".to_file "en", "~/weather.mp3" 17 | 18 | # Supports large text files, as the gem will batch up the requests to Google (as each request max 100 chars) 19 | text = "People living on the east coast of England have been warned to stay away from their homes because of further high tides expected later. The tidal surge that hit the UK is said to have been the worst for 60 years, with thousands abandoning their homes." 20 | text.to_file "en" 21 | 22 | #Direct playback (require mpg123 installed and in PATH with a POSIX system) 23 | "Established in 1853, the University of Melbourne is a public-spirited institution that makes distinctive contributions to society in research, learning and teaching and engagement.".play 24 | 25 | #Direct playback in other language (2 times) 26 | "Oggi il tempo è buono, andiamo in gita di esso.".play("it", 2) 27 | 28 | #RTL Arabic language 29 | "اليوم كان الطقس جيدا، ونحن نذهب في نزهة منه.".play("ar") 30 | 31 | == Direct play dependencies 32 | You need to install `mpg123` 33 | 34 | sudo apt-get install mpg123 #for debain based 35 | brew install mpg123 #mac 36 | 37 | == CLI 38 | 39 | tts "ruby is great" # play the string. 40 | text2mp3 "中国上海天气不错" "zh" #create mp3 file. 41 | 42 | == Versions 43 | 44 | * 0.4 fixed issue that long text unable to generated. 45 | * 0.5 added all supported languages, added direct playback feature fixed some broken rspec. 46 | * 0.7 added CLI support 47 | * 0.7.1 fixed new google API 48 | 49 | == Copyright 50 | 51 | Copyright (c) 2011 Yiling Cao / and other supporters! Check LICENSE.txt 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rubygems' 3 | require 'bundler' 4 | 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 17 | gem.name = "tts" 18 | gem.homepage = "http://github.com/c2h2/tts" 19 | gem.license = "MIT" 20 | gem.summary = "Ruby convert text to mp3" 21 | gem.description = "(tts) Text -> Mp3 made easy." 22 | gem.email = "yiling.cao@gmail.com" 23 | gem.authors = ["Yiling Cao"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rspec/core/rake_task' 29 | 30 | task :default => :spec 31 | 32 | RSpec::Core::RakeTask.new(:spec) do |t| 33 | t.pattern = Dir.glob('spec/**/*_spec.rb') 34 | t.rspec_opts = '--format progress -c' 35 | end 36 | 37 | require 'rdoc/task' 38 | RDoc::Task.new do |rdoc| 39 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 40 | 41 | rdoc.rdoc_dir = 'rdoc' 42 | rdoc.title = "tts #{version}" 43 | rdoc.rdoc_files.include('README*') 44 | rdoc.rdoc_files.include('lib/**/*.rb') 45 | end 46 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.8.2 2 | -------------------------------------------------------------------------------- /bin/text2mp3: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'tts' 3 | 4 | if ARGV.size == 1 5 | ARGV[0].to_file "en" 6 | exit 0 7 | elsif ARGV.size == 2 8 | ARGV[0].to_file ARGV[1] 9 | exit 0 10 | else 11 | puts 'Usage: text2mp3 "Your Text"' 12 | puts 'OR: text2mp3 "Your Text" "Language Code"' 13 | exit 1 14 | end 15 | -------------------------------------------------------------------------------- /bin/tts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'tts' 3 | 4 | if ARGV.size == 1 5 | ARGV[0].play "en" 6 | exit 0 7 | elsif ARGV.size == 2 8 | ARGV[0].play ARGV[1] 9 | exit 0 10 | else 11 | puts 'Usage: tts "Your Text"' 12 | puts 'OR: tts "Your Text" "Language Code"' 13 | exit 1 14 | end 15 | -------------------------------------------------------------------------------- /example/gen_hello_world_en.rb: -------------------------------------------------------------------------------- 1 | require 'tts' 2 | 3 | 4 | "Hello World".to_file "en" 5 | -------------------------------------------------------------------------------- /example/play_hello_world_en.rb: -------------------------------------------------------------------------------- 1 | require 'tts' 2 | 3 | 4 | "Hello World".play("en", 2) 5 | -------------------------------------------------------------------------------- /lib/tts.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'open-uri' 3 | require 'uri' 4 | require 'tempfile' 5 | require 'cgi' 6 | 7 | module Tts 8 | @@default_url = "http://translate.google.com/translate_tts" 9 | @@user_agent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24" 10 | @@referer = "http://translate.google.com/" 11 | 12 | def self.server_url url=nil 13 | return @@default_url if url.nil? 14 | @@default_url = url 15 | end 16 | 17 | def to_file lang, file_name=nil 18 | parts = validate_text_length(self) 19 | file_name = self[0..20].generate_file_name if file_name.nil? 20 | parts.each do |part| 21 | url = part.to_url(lang) 22 | fetch_mp3(url, file_name) 23 | end 24 | end 25 | 26 | def validate_text_length text 27 | if text.length > 100 28 | chunk_text(text) 29 | else 30 | [text] 31 | end 32 | end 33 | 34 | def chunk_text text 35 | chunks = [] 36 | words = split_into_words(text) 37 | chunk = '' 38 | words.each do |word| 39 | if (chunk.length + word.length) > 100 40 | chunks << chunk.strip! 41 | chunk = '' 42 | end 43 | chunk += "#{word} " 44 | end 45 | chunks << chunk.strip! 46 | end 47 | 48 | def split_into_words text 49 | text.gsub(/\s+/m, ' ').strip.split(" ") 50 | end 51 | 52 | def generate_file_name 53 | to_valid_fn + ".mp3" 54 | end 55 | 56 | def to_valid_fn 57 | gsub(/[\x00\/\\:\*\?\"<>\|]/, '_') 58 | end 59 | 60 | def to_url lang 61 | langs = ['af', 'ar', 'az', 'be', 'bg', 'bn', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en', 'en_us', 'en_gb', 'en_au', 'eo', 'es', 'et', 'eu', 'fa', 'fi', 'fr', 'ga', 'gl', 'gu', 'hi', 'hr', 'ht', 'hu', 'id', 'is', 'it', 'iw', 'ja', 'ka', 'kn', 'ko', 'la', 'lt', 'lv', 'mk', 'ms', 'mt', 'nl', 'no', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sq', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tl', 'tr', 'uk', 'ur', 'vi', 'yi', 'zh', 'zh-CN', 'zh-TW'] 62 | raise "Not accepted language, accpeted are #{langs * ","}" unless langs.include? lang 63 | base = "#{Tts.server_url}?tl=#{lang}&ie=UTF-8&client=tw-ob&q=#{CGI.escape(self)}" 64 | end 65 | 66 | def fetch_mp3 url, file_name 67 | begin 68 | content = URI.open(url, "User-Agent" => @@user_agent, "Referer" => @@referer).read 69 | 70 | File.open(temp_file_name, "wb") do |f| 71 | f.puts content 72 | end 73 | merge_mp3_file(file_name) 74 | rescue => e 75 | $stderr.puts("Internet error! #{e.message}") 76 | exit(1) 77 | end 78 | end 79 | 80 | def temp_file_name 81 | @@temp_file ||= Tempfile.new.path 82 | end 83 | 84 | def play_file_name 85 | @@play_file_file ||= Tempfile.new.path 86 | end 87 | 88 | def merge_mp3_file file_name 89 | `cat #{temp_file_name} >> "#{file_name}" && rm #{temp_file_name}` 90 | end 91 | 92 | def play lang="en", times=1, pause_gap = 1 93 | #test if mpg123 exists? 94 | `which mpg123` 95 | if $?.to_i != 0 96 | puts "mpg123 executable NOT found. This function only work with POSIX systems.\n Install mpg123 with `brew install mpg123` or `apt-get install mpg123`" 97 | exit 1 98 | end 99 | self.to_file(lang, play_file_name) 100 | times.times{|i| `mpg123 --no-control -q #{play_file_name}`} 101 | File.delete(play_file_name) 102 | end 103 | 104 | end 105 | 106 | class String 107 | include Tts 108 | end 109 | -------------------------------------------------------------------------------- /spec/tts_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path("../../lib/tts", __FILE__) 3 | require "rspec" 4 | 5 | describe "to_valid_fn method" do 6 | # fn.gsub(/[\x00\/\\:\*\?\"<>\|]/, '_') 7 | it "should replace * with _" do 8 | expect("hello*nice".to_valid_fn).to eq "hello_nice" 9 | expect("hello*nice*hello".to_valid_fn).to eq "hello_nice_hello" 10 | end 11 | 12 | it "should replace / with _" do 13 | expect("hello/nice".to_valid_fn).to eq "hello_nice" 14 | expect("hello/nice/hello".to_valid_fn).to eq "hello_nice_hello" 15 | end 16 | 17 | it "should replace / with _" do 18 | expect("hello:nice".to_valid_fn).to eq "hello_nice" 19 | expect("hello:nice:hello".to_valid_fn).to eq "hello_nice_hello" 20 | end 21 | 22 | it "should replace / with _" do 23 | expect("hello?nice".to_valid_fn).to eq "hello_nice" 24 | expect("hello?nice?hello".to_valid_fn).to eq "hello_nice_hello" 25 | end 26 | 27 | it "should replace / with _" do 28 | expect("hello|nice".to_valid_fn).to eq "hello_nice" 29 | expect("hello|nice?hello".to_valid_fn).to eq "hello_nice_hello" 30 | end 31 | 32 | end 33 | 34 | describe 'to_url method' do 35 | it "to_url should return a correct string" do 36 | expect("hello".to_url("en")).to eq "http://translate.google.com/translate_tts?tl=en&ie=UTF-8&client=tw-ob&q=hello" 37 | end 38 | 39 | it "to_url should return a correct string with chinese char" do 40 | expect("人民广场".to_url("zh")).to eq "http://translate.google.com/translate_tts?tl=zh&ie=UTF-8&client=tw-ob&q=%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA" 41 | end 42 | 43 | end 44 | 45 | describe 'to_file method' do 46 | it "to_file should generate a mp3 file for a correct string" do 47 | "hello".to_file("en") 48 | expect(File.exist?("hello.mp3")).to be true 49 | File.delete("hello.mp3") 50 | end 51 | 52 | it "to_file should generate a mp3 file with given name for a correct string" do 53 | "hello".to_file("en", "my_hello.mp3") 54 | expect(File.exist?("my_hello.mp3")).to be true 55 | File.delete("my_hello.mp3") 56 | end 57 | 58 | it "to_file should generate a mp3 file for a correct chinese string" do 59 | "人民广场到了".to_file("zh") 60 | expect(File.exist?("人民广场到了.mp3")).to be true 61 | File.delete("人民广场到了.mp3") 62 | end 63 | 64 | it "to_file should generate a mp3 file for a correct simplified chinese string" do 65 | "人民广场马上到了".to_file("zh-CN") 66 | expect(File.exist?("人民广场马上到了.mp3")).to be true 67 | File.delete("人民广场马上到了.mp3") 68 | end 69 | 70 | it "to_file should fail a non-exist language" do 71 | expect{"人民广场马上到了".to_file("dummy")}.to raise_error(RuntimeError) 72 | expect(File.exist?("人民广场马上到了.mp3")).to be false 73 | end 74 | 75 | end 76 | 77 | describe 'generate a correct mp3 file with long text and play via mpg123' do 78 | it "should playback a correct test mp3 file" do 79 | "Testing sound with the ruby gem TTS... if you hear the sound correctly, This single test is passed. Thank you very much for you patience...".play 80 | end 81 | 82 | it "should playback a correct Chinese test mp3 file" do 83 | "谢谢测试。测试通过".play("zh") 84 | end 85 | end 86 | 87 | describe 'set a server url' do 88 | before(:all) do 89 | Tts.server_url "http://127.0.0.1:3001/translate_tts" 90 | end 91 | 92 | it "to_url should return a correct string" do 93 | expect("hello".to_url("en")).to eq "http://127.0.0.1:3001/translate_tts?tl=en&ie=UTF-8&client=tw-ob&q=hello" 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /tts.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: tts 0.7.1 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "tts".freeze 9 | s.version = "0.8.2" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib".freeze] 13 | s.authors = ["Yiling Cao".freeze] 14 | s.date = "2019-05-11" 15 | s.description = "(tts) Text -> Mp3 made easy.".freeze 16 | s.email = "yiling.cao@gmail.com".freeze 17 | s.executables = ["text2mp3".freeze, "tts".freeze] 18 | s.extra_rdoc_files = [ 19 | "LICENSE.txt", 20 | "README.rdoc" 21 | ] 22 | s.files = [ 23 | "Gemfile", 24 | "Gemfile.lock", 25 | "LICENSE.txt", 26 | "README.rdoc", 27 | "Rakefile", 28 | "VERSION", 29 | "bin/text2mp3", 30 | "bin/tts", 31 | "example/gen_hello_world_en.rb", 32 | "example/play_hello_world_en.rb", 33 | "lib/tts.rb", 34 | "spec/tts_spec.rb", 35 | "tts.gemspec" 36 | ] 37 | s.homepage = "http://github.com/c2h2/tts".freeze 38 | s.licenses = ["MIT".freeze] 39 | s.rubygems_version = "3.0.3".freeze 40 | s.summary = "Ruby convert text to mp3".freeze 41 | 42 | if s.respond_to? :specification_version then 43 | s.specification_version = 4 44 | 45 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 46 | s.add_development_dependency(%q.freeze, [">= 0"]) 47 | s.add_development_dependency(%q.freeze, ["~> 2.0"]) 48 | s.add_development_dependency(%q.freeze, [">= 0"]) 49 | else 50 | s.add_dependency(%q.freeze, [">= 0"]) 51 | s.add_dependency(%q.freeze, ["~> 2.0"]) 52 | s.add_dependency(%q.freeze, [">= 0"]) 53 | end 54 | else 55 | s.add_dependency(%q.freeze, [">= 0"]) 56 | s.add_dependency(%q.freeze, ["~> 2.0"]) 57 | s.add_dependency(%q.freeze, [">= 0"]) 58 | end 59 | end 60 | 61 | --------------------------------------------------------------------------------