- PVSM.RU - https://www.pvsm.ru -
На бескрайних просторах интернета немало статей про умные дома, очки дополненной реальности, новомодный «интернет вещей» и другие преспективы вообразимого будущего. Будет ли оно светлым или в «добрых» традициях нуарного киберпанка, время покажет, а пока мы сделаем маленький шаг навстречу ему.
Как вы догадались по заголовку публикации, речь пойдёт о синтезе речи. Я расскажу, как реализовал, c чем столкнулся. Буду не оригинальным, использовал festival.
Подопытный:
— Установка, никаких проблем не должно возникать. Пример [1]
— Примеры скриптов [2]
Скрипт узнает статус и заряд. Сообщает о заряде, если не подключен к зарядке и заряд < 50%.
#!/bin/bash
scripts="$HOME/.bin/festival/"
charge=$(cat /sys/class/power_supply/BAT0/capacity)
procent=$(${scripts}pluralform.sh $charge процент процента процентов)
status=$(cat /sys/class/power_supply/BAT0/status)
text=$(echo "Заряд батареи $charge $procent")
critical="Критический заряд батареи."
if [ "$status" == 'Discharging' ] && [ $charge -gt 10 -a $charge -lt 50 ];
then
${scripts}say.sh "$text"
elif [ "$status" == 'Discharging' ] && [ $charge -lt 10 ];
then
${scripts}say.sh "$critical"
fi
exit
Склонение
#!/usr/bin/env bash
n=$(($1 % 100))
n1=$(($n % 10))
if [ $n -gt 10 -a $n -lt 20 ]; then echo $4;
elif [ $n1 -gt 1 -a $n1 -lt 5 ]; then echo $3;
elif [ $n1 -eq 1 ]; then echo $2;
else echo $4
fi
Скрипт генерирует wav c учётом файлов в dat`е. Запоминает текущую громкость, ставит оптимальную. Проигрывает и возвращает громкость обратно. Удаляет текущий wav-файл.
#!/bin/bash
data="$HOME/.bin/festival/data"
ru="(voice_msu_ru_nsh_clunits)"
volume=$(amixer | grep -o [0-9]* | sed "5 ! d")
i=$(ls -r $data | grep -o [0-9]* | sed "1 ! d")
if test -z "$i";
then i=1;
elif test -f "$data/saytext_$i.wav";
then i=$(($i+1));
fi
echo "$1" | text2wave -o $data/saytext_$i.wav -eval $ru > /dev/null 2>&1
amixer set Master 67% > /dev/null 2>&1
aplay $data/saytext_$i.wav > /dev/null 2>&1
aplay=$(ps -el | grep aplay | wc -l)
if [ $aplay -eq 0 ];
then amixer set Master $volume% > /dev/null 2>&1;
fi
rm -f $data/saytext_$i.wav
Нашёл почти готовое решение gem gmail [3].
Плюсы этого решения:
— Оповещение о кол-ве сообщений по лейблам. Предварительно нужно создать фильтры с лейблами на сайте.
— Возможность прочитать заголовок сообщения и текст (не реализовывал).
# coding: utf-8
require 'gmail'
require 'yaml'
Gmail.new("login", "password") do |gmail|
@festival = "$HOME/.bin/festival/"
@labels = {"INBOX" => "Входящие", "Search job" => "Поиск работы",
"Music" => "Музыка", "Advertising" => "Реклама",
"Education" => "Обучение", "Interesting" => "Интересное"}
def check_letters(gmail)
counts = {}
@labels.each do |k,v|
h = {k => gmail.mailbox(k).count(:unread)}
counts.merge!(h)
end
return counts
end
def read_counts_letters
unless File.exist?(".gmail.yml")
return {}
end
old_counts = YAML::load(File.open(".gmail.yml"))
return old_counts
end
def save_counts_letters(counts)
system("touch .gmail.yml") if File.exist?(".gmail.yml")
File.open(".gmail.yml", "w") do |f|
f.write counts.to_yaml
end
end
def check_new_counts_letters(counts, old_counts)
counts.each do |k, v|
if old_counts.empty? || v < old_counts[k]
count = v
else
count = v - old_counts[k]
end
say_new_counts(k, count)
end
end
def say_new_counts(k, count)
unless count == 0
text = pluralform(count)
count = "Одн+о" if count == 1
all = "У вас #{count} #{text}"
part = "#{count} #{text} в разделе #{@labels[k]}"
if k == "INBOX"
system("#{@festival}say.sh '#{all}'")
else
system("#{@festival}say.sh '#{part}'")
end
end
end
def pluralform(count)
n = count % 100
m = n % 10
msg = ['сообщение',
'сообщения',
'сообщений']
if n > 10 && n < 20
return msg[2]
elsif m > 1 && m < 5
return msg[1]
elsif m == 1
return msg[0]
else
return msg[2]
end
end
counts = check_letters(gmail)
old_counts = read_counts_letters
check_new_counts_letters(counts, old_counts)
save_counts_letters(counts)
gmail.logout
end
Спустя некоторое время, испробовав многие варианты, оставил pulseaudio для управления потоками.
pcm.pulse {
type pulse
}
ctl.pulse {
type pulse
}
pcm.!default {
type pulse
}
ctl.!default {
type pulse
}
Переписал bash script`ы на ruby, написав gem fest [4].
# coding: utf-8
#
class Fest
def say(string, params = {})
init(params)
check_conditions
make_wav(string)
expect_if_paplay_now
check_optimal_volume
play_wav
end
def init(params) # получаем все параметры
@params = params
@path = @params[:path] || '/tmp'
@current_volume = `amixer | grep -o '[0-9]*' | sed "5 ! d"`.to_i
@index = `ls -r #{@path} | grep -o '[0-9]*' | sed "1 ! d"`.to_i
@min_volume = @params[:down_volume[0]] || 20
@max_volume = @params[:down_volume[1]] || 60
@step = @params[:down_volume[2]] || 4
end
def check_optimal_volume # получаем громкость пониженную
@volume = @current_volume - @current_volume / 10 * @step
optimize_min_and_max_volume
end
def optimize_min_and_max_volume
@optimize_volume = (
if @current_volume > @max_volume
@max_volume
elsif @current_volume < @min_volume
@min_volume
else
@current_volume
end
)
end
def check_conditions # проверка условий
check_light
check_home_theater
check_say_wav
end
def check_light # молчим, если экран потушен
exit if @params[:backlight].nil? && `xbacklight`.to_i == 0
end
def check_home_theater # при просмотре фильмов тоже разумеется
xbmc = `ps -el | grep xbmc | wc -l`.to_i
vlc = `ps -el | grep vlc | wc -l`.to_i
kodi = `ps -el | grep kodi | wc -l`.to_i
exit if xbmc > 0 || vlc > 0 || kodi > 0
end
def check_say_wav # index wav file
@index > 0 ? @index += 1 : @index = 1
end
def make_wav(string) # создаём wav (c mp3 возникли проблемы)
system("echo '#{string}' | text2wave -o #{@path}/say_#{@index}.wav
-eval '(#{@params[:language] || 'voice_msu_ru_nsh_clunits'})'
> /dev/null 2>&1")
end
def change_volume(volume) # ставим громкость
system("amixer set Master #{volume}% > /dev/null 2>&1")
end
def expect_if_paplay_now # ожидаем конца сообщения
loop do
break if `ps -el | grep paplay | wc -l`.to_i == 0
sleep 1
end
end
# получаем текущие источники
# пошагово понижаем громкость
def turn_down_volume
@inputs = `pactl list sink-inputs | grep № | grep -o '[0-9]*'`.split("n")
@inputs.each do |input|
volume = @current_volume
loop do
system("pactl set-sink-input-volume #{input} '#{volume * 655}'")
volume -= @step
break if volume < @volume
end
end
end
def play_wav # воспроизводим wav
turn_down_volume
system("paplay #{@path}/say_#{@index}.wav
--volume='#{@optimize_volume * 655}' > /dev/null 2>&1")
return_current_volume
delete_wav
end
# пошагово возвращаем громкость
def return_current_volume
@inputs.each do |input|
volume = @volume
loop do
system("pactl set-sink-input-volume #{input} '#{volume * 655}'")
volume += @step
break if volume > @current_volume
end
end
end
def delete_wav # удаляем wav
system("rm -f #{@path}/say_#{@index}.wav")
end
def pluralform(number, array) # склонение
n = number % 100
m = n % 10
if n > 10 && n < 20
array[2]
elsif m > 1 && m < 5
array[1]
elsif m == 1
array[0]
else
array[2]
end
end
end
PATH=$(echo $PATH)
SHELL=/bin/bash
GEM_HOME=$(gem environment gemdir)
GEM_PATH=$(gem environment gemdir)
DISPLAY=:0
*/10 * * * * ~/.bin/festival/charge
*/20 * * * * ~/.bin/festival/gmail
*/30 * * * * ~/.bin/festival/quotes
— Стабильная работа в crontab
— Музыка играет на заднем фоне
— Плавное понижение громкости и возврат
— Сообщение играет с текущей громкостью (если в пределах min и max)
Неприятные моменты:
— После просмотра фильмов стоит сбрасывать громкость
— Иногда озвучка искажается эхом (редко)
— Нет женского голоса
Все скрипты, кроме gem «fest», старых версий. Переписанные скрипты [5]
Спасибо за внимание.
Автор: AfsmNGhr
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ruby/83669
Ссылки в тексте:
[1] Пример: https://wiki.archlinux.org/index.php/Festival_%28%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9%29
[2] Примеры скриптов: http://ru.festivalspeaker.wikia.com/wiki/%D0%91%D0%B0%D0%BD%D0%BA_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B2_%D0%B4%D0%BB%D1%8F_%D0%B3%D0%BE%D0%BB%D0%BE%D1%81%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE_%D0%B4%D0%B2%D0%B8%D0%B6%D0%BA%D0%B0_Festival
[3] gmail: https://github.com/gmailgem/gmail
[4] fest: https://github.com/AfsmNGhr/fest
[5] Переписанные скрипты: https://github.com/AfsmNGhr/fest-scripts
[6] Источник: http://habrahabr.ru/post/251205/
Нажмите здесь для печати.