diff --git a/src/main/java/org/myrobotlab/service/NovelAI.java b/src/main/java/org/myrobotlab/service/NovelAI.java new file mode 100644 index 0000000000..e7581d980a --- /dev/null +++ b/src/main/java/org/myrobotlab/service/NovelAI.java @@ -0,0 +1,110 @@ +package org.myrobotlab.service; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.myrobotlab.io.FileIO; +import org.myrobotlab.logging.Level; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; +import org.myrobotlab.service.config.NovelAIConfig; +import org.myrobotlab.service.data.AudioData; +import org.slf4j.Logger; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class NovelAI extends AbstractSpeechSynthesis { + + private static final long serialVersionUID = 1L; + + public final static Logger log = LoggerFactory.getLogger(NovelAI.class); + + private transient OkHttpClient client = new OkHttpClient(); + + public NovelAI(String n, String id) { + super(n, id); + client = new OkHttpClient(); + } + + /** + * The methods apply and getConfig can be used, if more complex configuration + * handling is needed. By default, the framework takes care of most of it, + * including subscription handling. + * + *
+  @Override
+  public ServiceConfig apply(ServiceConfig c) {
+    super.apply(c)
+    return c;
+  }
+  
+  @Override
+  public ServiceConfig getConfig() {
+    super.getConfig()
+    return config;
+  }
+   * 
+ **/ + + public static void main(String[] args) { + try { + + LoggingFactory.init(Level.INFO); + + Runtime.start("novelai", "NovelAI"); + Runtime.start("webgui", "WebGui"); + + } catch (Exception e) { + log.error("main threw", e); + } + } + + @Override + public AudioData generateAudioData(AudioData audioData, String toSpeak) throws Exception { + + String baseUrl = "https://api.novelai.net/ai/generate-voice?voice=-1&seed=" + config.voice + "&opus=false&version=v2&text="; + String encodedText = URLEncoder.encode(toSpeak, StandardCharsets.UTF_8.toString()); + String url = baseUrl + encodedText; + + Request request = new Request.Builder().url(url).build(); + + try { + Response response = client.newCall(request).execute(); + + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + byte[] bytes = responseBody.bytes(); + FileIO.toFile(audioData.getFileName(), bytes); + } + } else { + error("request failed with code: " + response.code()); + } + } catch (Exception e) { + error(e); + } + return audioData; + } + + @Override + public void loadVoices() throws Exception { + addVoice("Aini", "female", "en", "Aini"); + addVoice("Ligeia", "female", "en", "Ligeia"); + addVoice("Orea", "female", "en", "Orea"); + addVoice("Claea", "female", "en", "Claea"); + addVoice("Lim", "female", "en", "Lim"); + addVoice("Aurae", "female", "en", "Aurae"); + addVoice("Naia", "female", "en", "Naia"); + + addVoice("Aulon", "male", "en", "Aulon"); + addVoice("Elei", "male", "en", "Elei"); + addVoice("Ogma", "male", "en", "Ogma"); + addVoice("Raid", "male", "en", "Raid"); + addVoice("Pega", "male", "en", "Pega"); + addVoice("Lam", "male", "en", "Lam"); + } +} diff --git a/src/main/java/org/myrobotlab/service/config/NovelAIConfig.java b/src/main/java/org/myrobotlab/service/config/NovelAIConfig.java new file mode 100644 index 0000000000..d881e47714 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/NovelAIConfig.java @@ -0,0 +1,18 @@ +package org.myrobotlab.service.config; + +import org.myrobotlab.framework.Plan; + +public class NovelAIConfig extends SpeechSynthesisConfig { + + + public NovelAIConfig() { + voice = "Aini"; + } + + @Override + public Plan getDefault(Plan plan, String name) { + super.getDefault(plan, name); + return plan; + } + +} diff --git a/src/main/java/org/myrobotlab/service/meta/NovelAIMeta.java b/src/main/java/org/myrobotlab/service/meta/NovelAIMeta.java new file mode 100644 index 0000000000..f94e15d086 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/meta/NovelAIMeta.java @@ -0,0 +1,33 @@ +package org.myrobotlab.service.meta; + +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.meta.abstracts.MetaData; +import org.slf4j.Logger; + +public class NovelAIMeta extends MetaData { + private static final long serialVersionUID = 1L; + public final static Logger log = LoggerFactory.getLogger(NovelAIMeta.class); + + /** + * This class is contains all the meta data details of a service. It's peers, + * dependencies, and all other meta data related to the service. + * + */ + public NovelAIMeta() { + + // add a cool description + addDescription("service to interface with NovelAI"); + + // false will prevent it being seen in the ui + setAvailable(true); + + // add it to one or many categories + addCategory("speech"); + + // add a sponsor to this service + // the person who will do maintenance + // setSponsor("GroG"); + + } + +} diff --git a/src/main/resources/resource/NovelAI.png b/src/main/resources/resource/NovelAI.png new file mode 100644 index 0000000000..d82b4fdd81 Binary files /dev/null and b/src/main/resources/resource/NovelAI.png differ diff --git a/src/main/resources/resource/WebGui/app/service/js/NovelAIGui.js b/src/main/resources/resource/WebGui/app/service/js/NovelAIGui.js new file mode 100644 index 0000000000..bdd8a1714c --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/NovelAIGui.js @@ -0,0 +1,58 @@ +angular.module('mrlapp.service.NovelAIGui', []).controller('NovelAIGuiCtrl', ['peer', '$scope', 'mrl', '$uibModal', function(peer, $scope, mrl, $uibModal) { + console.info('NovelAIGuiCtrl') + var _self = this + var msg = this.msg + $scope.autoClear = true + $scope.textArea = false + $scope.spoken = '' + + // new selected voice "container" - since it comes from a map next leaves are + // key & value ... value contains the entire voice selected + $scope.newVoice = { + selected: null + } + + this.updateState = function(service) { + $scope.service = service + if (service.voice) { + $scope.newVoice.selected = { + 'key': service.voice.name, + 'value': service.voice + } + } + $scope.$apply() + } + + this.onMsg = function(inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { + case 'onState': + _self.updateState(data) + break + case 'onStartSpeaking': + $scope.spoken = data + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + $scope.speak = function(text) { + msg.send("speak", text) + + if ($scope.autoClear) { + $scope.text = '' + } + } + + $scope.setVoice = function(text) { + console.log($scope.service.voice.name) + msg.send("setVoice", text.name) + } + + msg.subscribe('publishStartSpeaking') + msg.subscribe(this) +} +]) diff --git a/src/main/resources/resource/WebGui/app/service/views/NovelAIGui.html b/src/main/resources/resource/WebGui/app/service/views/NovelAIGui.html new file mode 100644 index 0000000000..7e5fe0e471 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/NovelAIGui.html @@ -0,0 +1,67 @@ +
+
+

{{spoken}}

+
+
+
+
+ + + + + + + + +
+ + NovelAI requires AWS Keys + + mute + +
+ + + +
+
+ +