[Из песочницы] Говорящий пингвин

На бескрайних просторах интернета немало статей про умные дома, очки дополненной реальности, новомодный «интернет вещей» и другие преспективы вообразимого будущего. Будет ли оно светлым или в «добрых» традициях нуарного киберпанка, время покажет, а пока мы сделаем маленький шаг навстречу ему.Как вы догадались по заголовку публикации, речь пойдёт о синтезе речи. Я расскажу, как реализовал, c чем столкнулся. Буду не оригинальным, использовал festival.НачалоПодопытный: Ноутбук HP Звук alsa (позднее поставил pulseaudio)  — Установка, никаких проблем не должно возникать. Пример— Примеры скриптовОповещение заряда батареи Скрипт узнает статус и заряд. Сообщает о заряде, если не подключен к зарядке и заряд < 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 Проверка почты gmail Нашёл почти готовое решение gem gmail.Плюсы этого решения: — Оповещение о кол-ве сообщений по лейблам. Предварительно нужно создать фильтры с лейблами на сайте.— Возможность прочитать заголовок сообщения и текст (не реализовывал).

# 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 Возникшие проблемы При старте системы с задачами в crontab устройство было занято (в ручном режиме без проблем); Перепады громкости. Переосмысление Спустя некоторое время, испробовав многие варианты, оставил pulseaudio для управления потоками. pcm.pulse { type pulse }

ctl.pulse { type pulse }

pcm.! default { type pulse }

ctl.! default { type pulse } Переписал bash script`ы на ruby, написав gem fest. # 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 Crontab 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)Неприятные моменты:— После просмотра фильмов стоит сбрасывать громкость— Иногда озвучка искажается эхом (редко)— Нет женского голоса

P.S. Все скрипты, кроме gem «fest», старых версий. Переписанные скриптыСпасибо за внимание.

© Habrahabr.ru