go-скрипт который делает аудиокнигу из текстового файла используя один из лучших синтезаторов речи — Ivona от Amazon

    Указываем в скрипте путь на книгу в txt — на выходе получаем папку с озвучкой хорошим синтезом.

    Люблю потреблять контент ушами — в это время можно заниматься спортом или давать отдых глазам. Я привык что любая нормальная книга имеет аудио-версию, в худшем случае — любительскую, однако это не всегда так. Сегодня я несколько часов перебирал разные онлайн-сервисы и Андроид-программы для проговаривания русского текста — из приглянувшегося только Pocket который использует Google TTS и Google Books с тем же движком но отчего то другим качеством голоса. Также интересен SpeechKit от Яндекс. Но вроде лучший синтез у Ivona — эту компанию в 2013 году купил Амазон. Гитхаб нашел с десяток скриптов для дергания готового звука, но полностью готового решения для озвучки целой книги не оказалось. Используя неофициальную Go-библиотеку для IVONA Speech Cloud API за несколько часов написал скрипт — впервые использую Go, удобно что все зависимости в одном файле, даже библиотеку с Гитхаба подтягивает автоматически.

    Скрипт создает один ogg-файл на абзац — у меня была книга в plaintext, конечно логичнее было бы разбивать по главам, а целиком книгу скормить не получилось — есть лимит на входное количество символов, около десяти минут озвучки на нормальной скорости. Выходной формат можно поменять на mp3. Имена файлов нормально отсортированы по порядку. При работе скрипта в консоли показывается текущий абзац отправленный на озвучку.



    Для работы скрипта нужны access key и secret key — я оставил тут свои, но если перестанет работать — вы можете бесплатно получить новые ключи тут.

    package main
    import (
    	"log"
    	"fmt"
    	"io/ioutil"
    	"strings"
    	ivona "github.com/jpadilla/ivona-go"
    )
    func main() {
    	client := ivona.New("GDNAICTDMLSLU5426OAA", "2qUFTF8ZF9wqy7xoGBY+YXLEu+M2Qqalf/pSrd9m")
    	text, err := ioutil.ReadFile("/home/vitaly/Desktop/test.txt")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	arrayOfParagraphs := strings.Split(string(text), "\n\n")
    	i := 0
    	for _,paragraph := range arrayOfParagraphs {
                    paragraph = strings.TrimSpace(paragraph)
    		if (len(paragraph) < 1) { // against empty lines
    			continue
    		}
    		log.Printf("%v\n", paragraph)
    		options := ivona.NewSpeechOptions(paragraph)
    		options.Voice.Language = "ru-RU"
    		options.Voice.Name = "Maxim"
    		options.Voice.Gender = "Male"
    		options.OutputFormat.Codec = "OGG"
    		r, err := client.CreateSpeech(options)
    		if err != nil {
    			log.Fatal(err)
    		}
    
    		i++
    		file := fmt.Sprintf("/home/vitaly/Desktop/ivona/tts%04d.ogg", i) // files like 0001.ogg
    		ioutil.WriteFile(file, r.Audio, 0644)
    	}
    }
    
    

    После замены пути к книге и пути к выходной папке (а возможно и заменив символ по которому делается сплит файла а также указав английский вместо русского) запускаем скрипт — минут за десять получаем около сотни страниц готового tts:
    go run ivona-tts.go
    

    Это мой первый go код, приветствую вашу критику.

    P.S.: Первым делом лучше искать уже озвученную человеком версию книги.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 36
    • +1
      Использую Vocalizer и для русского и для английского языка, очень качественный синтез, рекомендую
      4pda.ru/forum/index.php?showtopic=200728
      • 0
        А можно демо русского и английского?
        • 0
          Да, как замена Google TTS
          • +2
            Я имел в виду можете ли вы дать линк на Ютуб например который говорит именно тем голосом который вам больше всего нравится?
            • –3
              www.youtube.com/watch?v=KHxAap53Hd8
              Похоже на это, но у меня Milena Premium-High и звучит на телефоне получше, а для английских книг Ava Premium-High использую
              Профит в том, что не надо ничего кодировать, форматы переводить, стоит только открыть читалку(FBReader к примеру) и есть возможность слушать любые тексты
              • +1
                Попробуйте ту же Татьяну из Ivona, можете приятно удивиться. Когда-то сравнивали как раз на книгах, и голоса от Nuance заметно им проигрывали, особенно в витиеватой художественной литературе. В т.ч. на телефоне неплохо работает.
                • +1
                  Спасибо, стоит изучить вопрос, я пользуюсь вокалайзером уже около двух лет, звучит хорошо, поэтому не смотрел за обновлениями
                • +2
                  Мне мужские голоса в любом синтезе кажутся гораздо лучше…
                  • +1
                    Попробовал Ивону. Таки да, мужской голос лучше.

                    Причём что забавно: тестировал на «Белой берёзе», и Максим читает «стоИт берёза», а Татьяна читает «стОит берёза». Это заговор! :D Впрочем, без «ё» они одинаково ошибаются в определении слова.

                    Тестовый текст
                    Белая берёза
                    Под моим окном
                    Принакрылась снегом,
                    Точно серебром.

                    На пушистых ветках
                    Снежною каймой
                    Распустились кисти
                    Белой бахромой.

                    И стоит берёза
                    В сонной тишине,
                    И горят снежинки
                    В золотом огне.
        • +3
          Это мой первый go код, приветствую вашу критику.

          Не стоит хардкорить пути и ключи прямо в исходники. Для этого есть flag и path/filepath (не стоит обходиться обычной конкатерацией). А в bufio есть сканнер для чтения по словам/строкам/символам, хотя и не принципиально для файлов среднего размера. io/ioutil.WriteFile может возвратить ошибку (например место кончилось или ещё чего) и её стоит обработать.

          Парочка усовершенствований
          package main
          
          import (
          	ivona "github.com/jpadilla/ivona-go"
          
          	"bufio"
          	"flag"
          	"fmt"
          	"io/ioutil"
          	"log"
          	"os"
          	"path/filepath"
          	"strings"
          )
          
          const format = "tts%04d.ogg" // files like 0001.ogg
          
          var dir = flag.String("dir", ".", "like /home/vitaly/Desktop/")
          var textFile = flag.String("text", "", "path to text file, like /home/vitaly/Desktop/test.txt")
          var key = flag.String("key", "GDNAICTDMLSLU5426OAA", "access key for ivona api")
          var secret = flag.String("secret", "2qUFTF8ZF9wqy7xoGBY+YXLEu+M2Qqalf/pSrd9m", "secret key for ivona api")
          
          func main() {
          	flag.Parse()
          
          	conf := os.Stdin // если нет конфига, но читаем стандартный ввод
          	if *textFile != "" {
          		conf, err := os.Open(*textFile)
          		if err != nil {
          			log.Fatal(err)
          		}
          		defer conf.Close()
          	}
          
          	client := ivona.New(*key, *secret)
          	scanner := bufio.NewScanner(conf)
          	for i := 1; scanner.Scan(); i++ {
          		paragraph := strings.TrimSpace(scanner.Text())
          		if paragraph == "" { // пропускаем пустые строки
          			continue
          		}
          		log.Printf("process: %v\n", paragraph)
          
          		options := ivona.NewSpeechOptions(paragraph)
          		options.Voice.Language = "ru-RU"
          		options.Voice.Name = "Maxim"
          		options.Voice.Gender = "Male"
          		options.OutputFormat.Codec = "OGG"
          
          		r, err := client.CreateSpeech(options)
          		if err != nil {
          			log.Fatal(err)
          		}
          
          		file := filepath.Join(*dir, fmt.Sprintf(format, i))
          		err = ioutil.WriteFile(file, r.Audio, 0644)
          		if err != nil {
          			log.Fatalln("writing result file:", err)
          		}
          	}
          
          	if err := scanner.Err(); err != nil {
          		log.Fatalln("reading text file:", err)
          	}
          }
          


          diff
          4d3
          < 	"fmt"
          5a5,8
          > 
          > 	"bufio"
          > 	"flag"
          > 	"fmt"
          7a11,12
          > 	"os"
          > 	"path/filepath"
          10a16,22
          > const format = "tts%04d.ogg" // files like 0001.ogg
          > 
          > var dir = flag.String("dir", ".", "like /home/vitaly/Desktop/")
          > var textFile = flag.String("text", "", "path to text file, like /home/vitaly/Desktop/test.txt")
          > var key = flag.String("key", "GDNAICTDMLSLU5426OAA", "access key for ivona api")
          > var secret = flag.String("secret", "2qUFTF8ZF9wqy7xoGBY+YXLEu+M2Qqalf/pSrd9m", "secret key for ivona api")
          > 
          12,15c24,32
          < 	client := ivona.New("GDNAICTDMLSLU5426OAA", "2qUFTF8ZF9wqy7xoGBY+YXLEu+M2Qqalf/pSrd9m")
          < 	text, err := ioutil.ReadFile("/home/vitaly/Desktop/test.txt")
          < 	if err != nil {
          < 		log.Fatal(err)
          ---
          > 	flag.Parse()
          > 
          > 	conf := os.Stdin // если нет конфига, но читаем стандартный ввод
          > 	if *textFile != "" {
          > 		conf, err := os.Open(*textFile)
          > 		if err != nil {
          > 			log.Fatal(err)
          > 		}
          > 		defer conf.Close()
          18,21c35,39
          < 	arrayOfParagraphs := strings.Split(string(text), "\n")
          < 	i := 0
          < 	for _, paragraph := range arrayOfParagraphs {
          < 		if len(paragraph) < 4 { // against empty lines
          ---
          > 	client := ivona.New(*key, *secret)
          > 	scanner := bufio.NewScanner(conf)
          > 	for i := 1; scanner.Scan(); i++ {
          > 		paragraph := strings.TrimSpace(scanner.Text())
          > 		if paragraph == "" { // пропускаем пустые строки
          24c42,43
          < 		log.Printf("%v\n", paragraph)
          ---
          > 		log.Printf("process: %v\n", paragraph)
          > 
          29a49
          > 
          35,37c55,63
          < 		i++
          < 		file := fmt.Sprintf("/home/vitaly/Desktop/ivona/tts%04d.ogg", i) // files like 0001.ogg
          < 		ioutil.WriteFile(file, r.Audio, 0644)
          ---
          > 		file := filepath.Join(*dir, fmt.Sprintf(format, i))
          > 		err = ioutil.WriteFile(file, r.Audio, 0644)
          > 		if err != nil {
          > 			log.Fatalln("writing result file:", err)
          > 		}
          > 	}
          > 
          > 	if err := scanner.Err(); err != nil {
          > 		log.Fatalln("reading text file:", err)
          

          • +1
            у вас только конфиг из файла никогда читаться не будет
            • 0
              И правда. Вот фикс:
              	var err error
              	if *textFile != "" {
              		conf, err = os.Open(*textFile)
              
            • +2
              Спасибо.
            • –3
              Только Go всё-таки не скриптовый язык.
              • 0
                Сам сейчас периодически пользую go
                и как ни странно, как скриптовый, это позволяет наличие большого количества либ. Да и запуск не такой уж и долгий. Хотя конечно лучше собирать. Но для изучения подойдет и просто go run
                • –1
                  Тогда ждём статьи про C-скрипты, чего уж…
                  • +5
                    Вы удивитесь — bellard.org/tcc
                    C script supported: just add '#!/usr/local/bin/tcc -run' at the first line of your C source, and execute it directly from the command line.
                    • 0
                      С Go можно даже так
                      //usr/bin/go run $0 $@; exit $?
                      package main
                      
                      import "fmt"
                      
                      func main() {
                      	fmt.Println("Hello world!")
                      }
                      
                      

                      Однако, по моему разумению, слово скрипт указывает на скриптовый язык.
              • +4
                Зачем конвертировать если можно сразу читать голосом? На андроиде это умеют многие приложения, например fbreader. На iOS и того проще — чтение текста встроенная системная функция, можно активировать в настройках чтение (в любой программе) по двойному свайпу сверху вниз.
                • 0
                  Голос так себе, не знаю почему автор его выбрал, есть получше (имею в виду русский)
                  • +1
                    Спасибо за IVONA, не слышал. Наличие Java API радует, заберу к себе в умный дом
                    • +1
                      Умеют ли современные синтезаторы речи решать проблему с е/ё?
                      • +2
                        Попробовал скрипт автора, и могу сказать что в опробованной книге кусочек из фразы: «Я и осел здесь, чтобы отвязаться от вас.» слово осел было прочитано как осёл. Т.е. проблему вроде как решают, но местами неправильно
                      • +1
                        У меня возник вопрос, а если в тексте встречается предложение в котором есть и русский текст и английский?
                        Один будет в пролете?
                        • +1
                          Будет прочитан весь текст, но не факт, что произношение будет правильным. У API Ivona есть возможность задавать лексиконы, которые позволяют подстроить произношение слова/фразы или произнести сокращения полностью. Я думаю, этим механизмом можно воспользоваться для подобных случаев.
                          • +1
                            Не знаю, как в Ivona, а в RHVoice, который я использовала с «Балаболкой», есть возмощность XML-тегами переключать язык произношения и другие параметры.
                            • +1
                              У них на сайте тестовый текст содержит фразы со смесью языков, вроде:
                              Я один из голосов программы преобразования текста в речь IVONA. Введите фразу здесь и нажмите Play.
                              Произносит без проблем.
                            • +1
                              Спасибо! Две недели назад как раз искала максимально качественный TTS. Пока лучшее из того, что нашла для русского языка — «Балаболка». Попробую поэкспериментировать с Ivona :)
                              • +1
                                if (len(paragraph) < 1) {
                                

                                Скобки в Go не нужны.
                                log.Printf("%v\n", paragraph)
                                

                                В принципе ок, особенно для скрипта. Но т.к. точно известно, что у вас строка, то быстрее будет с %s.
                                • +1
                                  Наступил на грабли) Для пробы скачал с lib.ru наугад стихотворение Бунина, оказалось в старинной транскрипции, с ятями. Очень неожиданное прочтение получилось. А так — действительно впечатляет, хорошее звучание и интонации естественные (я специально стихи для пробы выбрал). Пожалуй попробую себе в машину что-нибудь сделать послушать.
                                  Только вот извините, но хардкодить входные/выходные файлы — жуткий моветон. Я тоже go первый раз в жизни вижу, однако если вы разобрались с остальным, то узнать как передать параметры в командной строке уж совсем не проблема. Еще я не понял как оно работает с импортом модулей с гитхаба, мне пришлось руками ставить.
                                  • +1
                                    Интересное решение, но не проще ли использовать уже готовое решение с лучшим синтезом для русского языка chitatel.pro
                                    Попробовать можно синтез от ЦРТ на: voicefabric.ru
                                    Небольшой отрывок из Достоевского: «в начале июля, в чрезвычайно жаркое время, под вечер, один молодой человек вышел из своей каморки, которую нанимал от жильцов в С — м переулке»
                                    Ивона: пОд вечер
                                    Читатель: под вЕчер

                                    И, ксати, к вопросу о женских голосах, рекомендую попробовать голос Юлия.
                                    • 0
                                      Я видел эти сайты и пробовал их до написания своего скрипта — на мой вкус голос Ivona лучше. Хотя логично было бы предположить что русский синтез должен быть лучше у русских компаний вроде Яндекса или Центра Речевых Технологий, наверняка в будущем и те и те улучшат свои продукты.
                                      • +1
                                        было бы интересно провести Mos-оценку. Синтезировать один и тот же текст Ivona и ЦРТ и выложить на голосование.
                                        • 0
                                          Да тут и сравнивать нечего, Ivona пока вне конкуренции
                                        • 0
                                          Статья, в которой приведено сравнение синтеза ЦРТ и Яндекса: habrahabr.ru/post/272655
                                      • 0
                                        https://www.ivona.com/us/about-us/voice-portfolio/

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.