added voice

This commit is contained in:
Saifeddine ALOUI 2023-12-25 01:17:28 +01:00
parent 0b8d2cf8bd
commit bb43498c89
8 changed files with 403 additions and 236 deletions

View File

@ -38,7 +38,7 @@ from datetime import datetime
from typing import List, Tuple from typing import List, Tuple
import time import time
import numpy as np import numpy as np
from lollms.utilities import find_first_available_file_index from lollms.utilities import find_first_available_file_index, convert_language_name
if not PackageManager.check_package_installed("requests"): if not PackageManager.check_package_installed("requests"):
PackageManager.install_package("requests") PackageManager.install_package("requests")
@ -48,24 +48,6 @@ import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
def convert_language_name(language_name):
# Remove leading and trailing spaces
language_name = language_name.strip()
# Convert to lowercase
language_name = language_name.lower().replace(".","")
# Define a dictionary mapping language names to their codes
language_codes = {
"english": "en",
"spanish": "es",
"french": "fr",
"german": "de",
# Add more language names and codes as needed
}
# Return the corresponding language code if found, or None otherwise
return language_codes.get(language_name,"en")
def terminate_thread(thread): def terminate_thread(thread):
if thread: if thread:
@ -1211,7 +1193,7 @@ class LoLLMsAPI(LollmsApplication):
try: try:
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
if self.tts is None: if self.tts is None:
self.tts = LollmsXTTS(self, voice_samples_path=Path(personality.audio_samples[0]).parent) self.tts = LollmsXTTS(self, voice_samples_path=Path(__file__).parent.parent/"voices")
except: except:
self.warning(f"Personality {personality.name} request using custom voice but couldn't load XTTS") self.warning(f"Personality {personality.name} request using custom voice but couldn't load XTTS")
except Exception as ex: except Exception as ex:
@ -2079,9 +2061,10 @@ class LoLLMsAPI(LollmsApplication):
) )
if self.config.auto_read and len(self.personality.audio_samples)>0: if self.config.auto_read and len(self.personality.audio_samples)>0:
try: try:
self.process_chunk("Generating voice output",MSG_TYPE.MSG_TYPE_STEP_START,client_id=client_id)
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
if self.tts is None: if self.tts is None:
self.tts = LollmsXTTS(self, voice_samples_path=Path(self.personality.audio_samples[0]).parent) self.tts = LollmsXTTS(self, voice_samples_path=Path(__file__).parent.parent/"voices")
language = convert_language_name(self.personality.language) language = convert_language_name(self.personality.language)
self.tts.set_speaker_folder(Path(self.personality.audio_samples[0]).parent) self.tts.set_speaker_folder(Path(self.personality.audio_samples[0]).parent)
fn = self.personality.name.lower().replace(' ',"_").replace('.','') fn = self.personality.name.lower().replace(' ',"_").replace('.','')
@ -2089,11 +2072,12 @@ class LoLLMsAPI(LollmsApplication):
url = f"audio/{fn}" url = f"audio/{fn}"
self.tts.tts_to_file(self.connections[client_id]["generated_text"], Path(self.personality.audio_samples[0]).name, f"{fn}", language=language) self.tts.tts_to_file(self.connections[client_id]["generated_text"], Path(self.personality.audio_samples[0]).name, f"{fn}", language=language)
fl = f""" fl = f"""
<audio controls autoplay> <audio controls>
<source src="{url}" type="audio/wav"> <source src="{url}" type="audio/wav">
Your browser does not support the audio element. Your browser does not support the audio element.
</audio> </audio>
""" """
self.process_chunk("Generating voice output",MSG_TYPE.MSG_TYPE_STEP_END,client_id=client_id)
self.process_chunk(fl,MSG_TYPE.MSG_TYPE_CHUNK,client_id=client_id) self.process_chunk(fl,MSG_TYPE.MSG_TYPE_CHUNK,client_id=client_id)
""" """

23
app.py
View File

@ -27,7 +27,7 @@ import traceback
import webbrowser import webbrowser
from pathlib import Path from pathlib import Path
from lollms.com import NotificationType, NotificationDisplayType from lollms.com import NotificationType, NotificationDisplayType
from lollms.utilities import AdvancedGarbageCollector, reinstall_pytorch_with_cuda from lollms.utilities import AdvancedGarbageCollector, reinstall_pytorch_with_cuda, convert_language_name
def run_update_script(args=None): def run_update_script(args=None):
update_script = Path(__file__).parent/"update_script.py" update_script = Path(__file__).parent/"update_script.py"
@ -484,6 +484,10 @@ try:
"/import_multiple_discussions", "import_multiple_discussions", self.import_multiple_discussions, methods=["POST"] "/import_multiple_discussions", "import_multiple_discussions", self.import_multiple_discussions, methods=["POST"]
) )
self.add_endpoint(
"/read", "read", self.read, methods=["POST"]
)
self.add_endpoint( self.add_endpoint(
"/get_presets", "get_presets", self.get_presets, methods=["GET"] "/get_presets", "get_presets", self.get_presets, methods=["GET"]
) )
@ -753,6 +757,23 @@ try:
presets.append(preset) presets.append(preset)
return jsonify(presets) return jsonify(presets)
def read(self):
# Get the JSON data from the POST request.
data = request.get_json()
try:
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
if self.tts is None:
self.tts = LollmsXTTS(self, voice_samples_path=Path(__file__).parent/"voices")
language = convert_language_name(self.personality.language)
self.tts.set_speaker_folder(Path(__file__).parent/"voices")
fn = self.personality.name.lower().replace(' ',"_").replace('.','')
fn = f"playground_voice.wav"
url = f"audio/{fn}"
self.tts.tts_to_file(data['text'], "main_voice.wav", f"{fn}", language=language)
return jsonify({"url": url})
except:
return jsonify({"url": None})
def add_preset(self): def add_preset(self):
# Get the JSON data from the POST request. # Get the JSON data from the POST request.
preset_data = request.get_json() preset_data = request.get_json()

@ -1 +1 @@
Subproject commit ab25c6c6aab13bbfa98bc1409e7d0a69808a1f7d Subproject commit 2df38174ccfdd1bae63140f4cc1a7aa024f9a844

BIN
voices/main_voice.wav Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

2
web/dist/index.html vendored
View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI - Welcome</title> <title>LoLLMS WebUI - Welcome</title>
<script type="module" crossorigin src="/assets/index-3d66646d.js"></script> <script type="module" crossorigin src="/assets/index-537a0c2f.js"></script>
<link rel="stylesheet" href="/assets/index-ad8f7b32.css"> <link rel="stylesheet" href="/assets/index-ad8f7b32.css">
</head> </head>
<body> <body>

View File

@ -0,0 +1,139 @@
<template>
<div class="painting-component">
<canvas
ref="canvas"
:width="canvasWidth"
:height="canvasHeight"
@mousedown="startPainting"
@mousemove="paint"
@mouseup="stopPainting"
@mouseleave="stopPainting"
></canvas>
<div class="controls">
<label for="stroke-width">Stroke Width:</label>
<input type="number" id="stroke-width" v-model="strokeWidth" min="1" max="50" />
<label for="stroke-color">Stroke Color:</label>
<input type="color" id="stroke-color" v-model="strokeColor" />
<button @click="undoStroke">Undo</button>
<button @click="saveDrawing">Save</button>
<button @click="loadDrawing">Load</button>
</div>
</div>
</template>
<script>
export default {
name: 'PaintingComponent',
data() {
return {
canvasWidth: 800,
canvasHeight: 600,
strokeWidth: 5,
strokeColor: '#000000',
isPainting: false,
strokes: [],
undoneStrokes: [],
canvas: null,
ctx: null,
};
},
mounted() {
this.initCanvas();
},
methods: {
initCanvas() {
this.canvas = this.$refs.canvas;
this.ctx = this.canvas.getContext('2d');
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
},
startPainting(event) {
const { offsetX, offsetY } = event;
this.ctx.beginPath();
this.ctx.moveTo(offsetX, offsetY);
this.isPainting = true;
},
paint(event) {
if (this.isPainting) {
const { offsetX, offsetY } = event;
this.ctx.lineTo(offsetX, offsetY);
this.ctx.strokeStyle = this.strokeColor;
this.ctx.lineWidth = this.strokeWidth;
this.ctx.stroke();
this.undoneStrokes = []; // Clear the undone strokes as a new stroke is made
}
},
stopPainting() {
if (this.isPainting) {
this.ctx.closePath();
this.isPainting = false;
this.strokes.push(this.canvas.toDataURL()); // Save the current state
}
},
undoStroke() {
if (this.strokes.length > 0) {
this.undoneStrokes.push(this.strokes.pop());
this.reloadCanvas();
}
},
reloadCanvas() {
if (this.strokes.length > 0) {
const lastStroke = new Image();
lastStroke.onload = () => {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.drawImage(lastStroke, 0, 0);
};
lastStroke.src = this.strokes[this.strokes.length - 1];
} else {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
}
},
saveDrawing() {
const dataURL = this.canvas.toDataURL("image/png");
const link = document.createElement('a');
link.download = 'painting.png';
link.href = dataURL;
link.click();
},
loadDrawing() {
const input = document.createElement('input');
input.type = 'file';
input.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = readerEvent => {
const img = new Image();
img.onload = () => {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.drawImage(img, 0, 0);
this.strokes.push(this.canvas.toDataURL());
};
img.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
};
input.click();
}
}
};
</script>
<style scoped>
.painting-component {
border: 1px solid #ccc;
padding: 10px;
}
.controls {
margin-top: 10px;
}
.controls label {
margin-right: 5px;
}
.controls button {
margin-left: 10px;
}
</style>

View File

@ -22,6 +22,13 @@
class="w-6 hover:text-secondary duration-75 active:scale-90 cursor-pointer"> class="w-6 hover:text-secondary duration-75 active:scale-90 cursor-pointer">
<i data-feather="volume-2"></i> <i data-feather="volume-2"></i>
</button> </button>
<button
title="read"
@click.stop="read()"
:class="{ 'text-red-500': isTalking }"
class="w-6 hover:text-secondary duration-75 active:scale-90 cursor-pointer">
<i data-feather="voicemail"></i>
</button>
<button v-show="!generating" id="export-button" @click="exportText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="upload"></i></button> <button v-show="!generating" id="export-button" @click="exportText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="upload"></i></button>
<button v-show="!generating" id="import-button" @click="importText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="download"></i></button> <button v-show="!generating" id="import-button" @click="importText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="download"></i></button>
@ -478,6 +485,18 @@ export default {
// This event will be triggered when the voices are loaded // This event will be triggered when the voices are loaded
this.voices = this.speechSynthesis.getVoices(); this.voices = this.speechSynthesis.getVoices();
}, },
read(){
this.generating=true
axios.post("./read",{text:this.text}).then(response => {
console.log(response.data.url)
let url = response.data.url
this.text+=`\n<audio controls>\n<source src="${url}" type="audio/wav">\nYour browser does not support the audio element.\n</audio>`
this.generating=false
}).catch(ex=>{
this.$refs.toast.showToast(`Error: ${ex}`,4,false)
this.generating=false
});
},
speak() { speak() {
if (this.msg) { if (this.msg) {
this.speechSynthesis.cancel(); this.speechSynthesis.cancel();