/* * Copyright 2012-2014 Nikolay A. Viguro * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ru.iris.speak; import com.avaje.ebean.Ebean; import com.avaje.ebean.Expr; import javazoom.jl.decoder.JavaLayerException; import javazoom.jl.player.Player; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import ru.iris.common.Config; import ru.iris.common.database.model.Speaks; import ru.iris.common.messaging.JsonEnvelope; import ru.iris.common.messaging.JsonMessaging; import ru.iris.common.messaging.JsonNotification; import ru.iris.common.messaging.model.speak.ChangeVolumeAdvertisement; import ru.iris.common.messaging.model.speak.SpeakAdvertisement; import ru.iris.common.modulestatus.Status; import ru.iris.common.voice.GoogleSynthesiser; import ru.iris.common.voice.IvonaSynthesiser; import ru.iris.common.voice.Synthesiser; import ru.iris.common.voice.YandexSynthesiser; import java.io.*; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; public class SpeakService { private final Logger LOGGER = LogManager.getLogger(SpeakService.class.getName()); private final ArrayBlockingQueue<Speaks> speakqueue = new ArrayBlockingQueue<>(50); private Synthesiser synthesiser = null; private JsonMessaging jsonMessaging; public SpeakService() { Status status = new Status("Speak"); if (status.checkExist()) { status.running(); } else { status.addIntoDB("Speak", "Service that synthesize text to speak"); } try { final Config conf = Config.getInstance(); String tts = conf.get("ttsEngine"); LOGGER.info("Speak service started (TTS: " + tts + ")"); switch (tts) { case "google": synthesiser = new GoogleSynthesiser(conf.get("googleKey")); break; case "yandex": synthesiser = new YandexSynthesiser(conf.get("yandexKey")); break; case "ivona": synthesiser = new IvonaSynthesiser(); break; default: LOGGER.error("Unknown synthesiser: " + tts); return; } synthesiser.setLanguage(conf.get("language")); jsonMessaging = new JsonMessaging(UUID.randomUUID(), "speak"); jsonMessaging.subscribe("event.speak"); jsonMessaging.subscribe("event.speak.volume.set"); // fetch all cached speaks for this server final List<Speaks> speaksList = Ebean.find(Speaks.class) .where() .and(Expr.ne("cache", 0), Expr.or( Expr.eq("device", "all"), Expr.eq("device", "server") ) ).findList(); jsonMessaging.setNotification(new JsonNotification() { @Override public void onNotification(JsonEnvelope envelope) throws IOException, JavaLayerException { if (envelope.getObject() instanceof SpeakAdvertisement) { LOGGER.debug("New speak request arrived"); SpeakAdvertisement advertisement = envelope.getObject(); Speaks speak = new Speaks(); speak.setText(advertisement.getText()); speak.setConfidence(advertisement.getConfidence()); speak.setDevice(advertisement.getDevice()); // push to speak queue speakqueue.add(speak); } else if (envelope.getObject() instanceof ChangeVolumeAdvertisement) { ChangeVolumeAdvertisement advertisement = envelope.getObject(); if (advertisement.getDevice().equals("server") || advertisement.getDevice().equals("all")) { LOGGER.info("Setting sound volume level to " + advertisement.getLevel()); if (advertisement.getLevel() == 0.0) { conf.set("silence", "1"); } else { conf.set("silence", "0"); } } } else { // We received unknown request message. Lets make generic log entry. LOGGER.info("Received request " + " from " + envelope.getSenderInstance() + " to " + envelope.getReceiverInstance() + " at '" + envelope.getSubject() + ": " + envelope.getObject()); } } }); jsonMessaging.start(); // check speak queue and play if needed new Thread(new Runnable() { @Override public void run() { InputStream result; Player player; Speaks speak; try { while (true) { speak = speakqueue.poll(1000, TimeUnit.MILLISECONDS); if (speak == null) continue; LOGGER.debug("Something coming into the pool!"); if (conf.get("silence").equals("0")) { // Here we speak only if destination - all if (speak.getDevice().equals("all") || speak.getDevice().equals("server")) { LOGGER.debug("Confidence: " + speak.getConfidence()); LOGGER.debug("Text: " + speak.getText()); LOGGER.debug("Device: " + speak.getDevice()); long cacheId = 0; for (Speaks speaks : speaksList) { if ( speaks.getText().equals(speak.getText()) && speaks.getConfidence().equals(100D) ) cacheId = speaks.getCache(); } File f = new File("data/cache-" + cacheId + ".mp3"); // check cached and exist file if (cacheId == 0 || (!f.exists() || f.isDirectory())) { LOGGER.debug("Not cached - write!"); long cacheIdent = new Date().getTime(); OutputStream outputStream = new FileOutputStream(new File("data/cache-" + cacheIdent + ".mp3")); LOGGER.debug("Trying to get MP3 data"); result = synthesiser.getMP3Data(speak.getText()); byte[] byteArray = IOUtils.toByteArray(result); InputStream resultForWrite = new ByteArrayInputStream(byteArray); int read; byte[] bytes = new byte[1024]; while ((read = resultForWrite.read(bytes)) != -1) { outputStream.write(bytes, 0, read); } speak.setCache(cacheIdent); speak.save(); resultForWrite.close(); result.close(); cacheId = cacheIdent; speaksList.add(speak); } LOGGER.info("Playing local file: " + "data/cache-" + cacheId + ".mp3"); result = new FileInputStream("data/cache-" + cacheId + ".mp3"); player = new Player(result); player.play(); player.close(); speak.save(); // sleep a little Thread.sleep(1000); } else { LOGGER.info("Ignored. Request to play on device: " + speak.getDevice()); } } else { LOGGER.info("Silence mode enabled. Ignoring speak request."); } } } catch (final Throwable t) { LOGGER.error("Error in Speak: " + t); status.crashed(); t.printStackTrace(); } } }).start(); } catch (final Throwable t) { LOGGER.error("Error in Speak: " + t); status.crashed(); t.printStackTrace(); } } public void stop() { jsonMessaging.close(); } }