Upload 27 files
Browse files- .gitattributes +9 -0
- assets/logo.png +3 -0
- assets/schema.png +0 -0
- examples/0x0006de19.wav +3 -0
- examples/0x0008d8e5.wav +3 -0
- examples/0x000f7780.wav +3 -0
- examples/0x000faf94.wav +3 -0
- examples/0x0011ce99.wav +3 -0
- examples/0x0011cedc.wav +3 -0
- examples/dialogue_1.wav +3 -0
- examples/dialogue_9.wav +3 -0
- requirements.txt +7 -0
- src/__pycache__/asr.cpython-310.pyc +0 -0
- src/__pycache__/asr.cpython-311.pyc +0 -0
- src/__pycache__/translation.cpython-310.pyc +0 -0
- src/__pycache__/translation.cpython-311.pyc +0 -0
- src/__pycache__/tts.cpython-310.pyc +0 -0
- src/__pycache__/tts.cpython-311.pyc +0 -0
- src/content/about_tab.py +16 -0
- src/content/contact_tab.py +8 -0
- src/content/front_image.py +29 -0
- src/content/method_tab.py +60 -0
- src/content/transcription_tab.py +25 -0
- src/models/asr.py +33 -0
- src/models/pipeline.py +23 -0
- src/models/translation.py +32 -0
- src/models/tts/xtts.py +30 -0
- src/utils.py +6 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,12 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/logo.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/0x0006de19.wav filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/0x0008d8e5.wav filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/0x000f7780.wav filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/0x000faf94.wav filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/0x0011ce99.wav filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
examples/0x0011cedc.wav filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
examples/dialogue_1.wav filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
examples/dialogue_9.wav filter=lfs diff=lfs merge=lfs -text
|
assets/logo.png
ADDED
|
Git LFS Details
|
assets/schema.png
ADDED
|
examples/0x0006de19.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:22799d5aaf1f1c10bd98b5117585dbf862268336fb0ed43b6213db5014cfd762
|
| 3 |
+
size 200464
|
examples/0x0008d8e5.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bf4cbd5261c35939b2bcd26fbdcc5ee7f4d861986f67feb4b7e1698a8deb3880
|
| 3 |
+
size 214576
|
examples/0x000f7780.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:daff7d700b3b7da0d4c1d4b1d26ef6a0978ed9bd563d802563fd7535738bfc7f
|
| 3 |
+
size 317124
|
examples/0x000faf94.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:890b88c846f65fe12ca9f7c25cdb0d6b81c3dba6297ae2f21b9e812a82ef74e1
|
| 3 |
+
size 525982
|
examples/0x0011ce99.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:381520ad242a2f2b5292117b264706d5f9a8f02d86125dc953742dd725cae514
|
| 3 |
+
size 419672
|
examples/0x0011cedc.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2da3034a48fe6b51a766900804aa8a88c0c3859c22383cd8948ede4d8fe1d79a
|
| 3 |
+
size 796462
|
examples/dialogue_1.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:45d53cc727fe1d3ee851a8cb339e41b201b067094cdbe13b2c98cf2bf54d96de
|
| 3 |
+
size 1763754
|
examples/dialogue_9.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c6d82ae45a082cdee06a54aed78666506b4570885b8674d83707e2823ba1f975
|
| 3 |
+
size 882044
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
coqui-tts==0.26.2
|
| 2 |
+
deepl==1.22.0
|
| 3 |
+
dotenv==0.9.9
|
| 4 |
+
gradio==5.34.2
|
| 5 |
+
librosa==0.11.0
|
| 6 |
+
torch==2.7.1
|
| 7 |
+
transformers==4.51.3
|
src/__pycache__/asr.cpython-310.pyc
ADDED
|
Binary file (1.54 kB). View file
|
|
|
src/__pycache__/asr.cpython-311.pyc
ADDED
|
Binary file (2.5 kB). View file
|
|
|
src/__pycache__/translation.cpython-310.pyc
ADDED
|
Binary file (954 Bytes). View file
|
|
|
src/__pycache__/translation.cpython-311.pyc
ADDED
|
Binary file (1.44 kB). View file
|
|
|
src/__pycache__/tts.cpython-310.pyc
ADDED
|
Binary file (1.2 kB). View file
|
|
|
src/__pycache__/tts.cpython-311.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
src/content/about_tab.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
def about_tab():
|
| 4 |
+
with gr.TabItem("ℹ️ **O projekcie**"):
|
| 5 |
+
gr.Markdown("## O DubAI")
|
| 6 |
+
gr.Markdown("""
|
| 7 |
+
DubAI to aplikacja typu end-to-end umożliwiająca:
|
| 8 |
+
|
| 9 |
+
- transkrypcję mowy (ASR),
|
| 10 |
+
- tłumaczenie na inny język,
|
| 11 |
+
- oraz syntezę mowy z zachowaniem brzmienia głosu.
|
| 12 |
+
|
| 13 |
+
**Zastosowania:** dubbing gier, AI-asystenci, tworzenie voice-overów.
|
| 14 |
+
|
| 15 |
+
**Technologie:** Whisper, DeepL, XTTS, Gradio, Python.
|
| 16 |
+
""")
|
src/content/contact_tab.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
def contact_tab():
|
| 4 |
+
with gr.TabItem("📩 Kontakt"):
|
| 5 |
+
gr.Markdown("## Formularz kontaktowy")
|
| 6 |
+
gr.Textbox(label="📧 Twój email")
|
| 7 |
+
gr.Textbox(label="💬 Wiadomość", lines=4)
|
| 8 |
+
gr.Button("✉️ Wyślij (do zaimplementowania)")
|
src/content/front_image.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from src.utils import get_base64_image
|
| 3 |
+
|
| 4 |
+
def image_tab():
|
| 5 |
+
gr.HTML("""
|
| 6 |
+
<style>
|
| 7 |
+
.full-width-img-container {
|
| 8 |
+
width: 100%;
|
| 9 |
+
overflow: hidden;
|
| 10 |
+
margin: 0;
|
| 11 |
+
padding: 0;
|
| 12 |
+
}
|
| 13 |
+
.full-width-img {
|
| 14 |
+
width: 100%;
|
| 15 |
+
height: auto;
|
| 16 |
+
display: block;
|
| 17 |
+
}
|
| 18 |
+
</style>
|
| 19 |
+
""")
|
| 20 |
+
|
| 21 |
+
img_data = get_base64_image("assets/logo.png")
|
| 22 |
+
logo_html = f"""
|
| 23 |
+
<div class="full-width-img-container">
|
| 24 |
+
<img src="{img_data}"
|
| 25 |
+
class="full-width-img"
|
| 26 |
+
alt="Logo" />
|
| 27 |
+
</div>
|
| 28 |
+
"""
|
| 29 |
+
gr.HTML(logo_html)
|
src/content/method_tab.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from src.utils import get_base64_image
|
| 3 |
+
|
| 4 |
+
def method_tab():
|
| 5 |
+
with gr.Tab("⚙️ Metoda"):
|
| 6 |
+
with gr.Column(elem_classes="method-tab"):
|
| 7 |
+
gr.HTML('<div style="font-size: 40px; font-family: Myriad, sans-serif; font-weight: bold; text-align: left;">⚙️ Metoda</div>')
|
| 8 |
+
|
| 9 |
+
gr.HTML("""
|
| 10 |
+
<div style="font-size: 18px; font-family: Myriad, sans-serif;">
|
| 11 |
+
Proces rozpoczęto od oczyszczenia nagrań z zakłóceń tła przy użyciu modelu Demucs, który umożliwił skuteczne wydzielenie sygnału mowy. Następnie zastosowano Mossformer2 do separacji mówców, co pozwoliło rozdzielić wypowiedzi różnych osób w nagraniu. Transkrypcja i segmentacja audio zostały przeprowadzone za pomocą modelu Whisper, znanego z wysokiej jakości rozpoznawania mowy. Otrzymane wypowiedzi pogrupowano przy użyciu algorytmu DBSCAN, co ułatwiło identyfikację spójnych segmentów konwersacyjnych.
|
| 12 |
+
<br><br>
|
| 13 |
+
Do tłumaczenia wykorzystano polski wielojęzyczny model językowy Bielik, który dobrze radzi sobie z przekładem tekstów. Jakość tłumaczeń oceniano przy użyciu metryk COMET, BERTScore i BLEU, zapewniających obiektywną ocenę pod względem zgodności semantycznej i płynności.
|
| 14 |
+
<br><br>
|
| 15 |
+
Syntezę mowy zrealizowano przy użyciu modelu XTTS-v2, obsługującego 17 języków i umożliwiającego generowanie naturalnego głosu na podstawie jedynie 6-sekundowego nagrania referencyjnego. Został on wybrany ze względu na dobrą jakość dźwięku, szybkość działania oraz lepszą kompatybilność z językiem polskim niż inne dostępne modele. Na końcu przeprowadzono subiektywną ocenę końcowego dźwięku w formie testów odsłuchowych.
|
| 16 |
+
<br><br>
|
| 17 |
+
Na poniższym schemacie przedstawiono szczegółowy przebieg procesu.
|
| 18 |
+
</div>
|
| 19 |
+
""")
|
| 20 |
+
|
| 21 |
+
img_data = get_base64_image("assets/schema.png")
|
| 22 |
+
schema_html = f"""
|
| 23 |
+
<div style="display: flex; justify-content: center; padding: 20px;">
|
| 24 |
+
<img src="{img_data}"
|
| 25 |
+
style="
|
| 26 |
+
width: 100%;
|
| 27 |
+
max-width: 1000px;
|
| 28 |
+
border: none;
|
| 29 |
+
object-fit: contain;
|
| 30 |
+
"
|
| 31 |
+
alt="Schemat procesu"
|
| 32 |
+
/>
|
| 33 |
+
</div>
|
| 34 |
+
"""
|
| 35 |
+
gr.HTML(schema_html)
|
| 36 |
+
gr.HTML('<div style="text-align: center; font-size: 18px; font-family: Myriad, sans-serif;"><strong>Schemat 1.</strong> Przebieg procesu przetwarzania nagrania.</div>')
|
| 37 |
+
|
| 38 |
+
gr.HTML('<div style="font-size: 40px; font-family: Myriad, sans-serif; font-weight: bold; text-align: left;">📊 Wyniki</div>')
|
| 39 |
+
|
| 40 |
+
gr.HTML('<div style="font-size: 40px; font-family: Myriad, sans-serif; font-weight: bold; text-align: left;">🎵 Nagrania przykładowe</div>')
|
| 41 |
+
with gr.Row():
|
| 42 |
+
with gr.Column():
|
| 43 |
+
gr.Audio("examples/0x000f7780.wav", label="Nagranie 1", type="filepath")
|
| 44 |
+
with gr.Column():
|
| 45 |
+
gr.Audio("examples/0x000faf94.wav", label="Nagranie 2", type="filepath")
|
| 46 |
+
with gr.Row():
|
| 47 |
+
with gr.Column():
|
| 48 |
+
gr.Audio("examples/0x0006de19.wav", label="Nagranie 3", type="filepath")
|
| 49 |
+
with gr.Column():
|
| 50 |
+
gr.Audio("examples/0x0008d8e5.wav", label="Nagranie 4", type="filepath")
|
| 51 |
+
with gr.Row():
|
| 52 |
+
with gr.Column():
|
| 53 |
+
gr.Audio("examples/0x0011ce99.wav", label="Nagranie 5", type="filepath")
|
| 54 |
+
with gr.Column():
|
| 55 |
+
gr.Audio("examples/0x0011cedc.wav", label="Nagranie 6", type="filepath")
|
| 56 |
+
with gr.Row():
|
| 57 |
+
with gr.Column():
|
| 58 |
+
gr.Audio("examples/dialogue_1.wav", label="Dialog 1", type="filepath")
|
| 59 |
+
with gr.Column():
|
| 60 |
+
gr.Audio("examples/dialogue_9.wav", label="Dialog 2", type="filepath")
|
src/content/transcription_tab.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
def transcription_tab(full_pipeline):
|
| 4 |
+
with gr.Tab("🎙️ Aplikacja"):
|
| 5 |
+
with gr.Column(elem_classes="method-tab"):
|
| 6 |
+
gr.HTML('<div style="font-size: 40px; font-family: Myriad, sans-serif; font-weight: bold; text-align: left;">🎙️ Aplikacja</div>')
|
| 7 |
+
gr.HTML("""
|
| 8 |
+
<div style="font-size: 18px; font-family: Myriad, sans-serif;">
|
| 9 |
+
Jesteś ciekawy, jak Twój głos brzmi w innym języku? Możesz to sprawdzić! W ramach projektu udostępniamy wersję demonstracyjną naszej aplikacji. Wystarczy nagrać swoją wypowiedź w języku polskim, a aplikacja automatycznie przetłumaczy ją na język angielski i wygeneruje syntezę głosu. Wystarczy kliknąć przycisk "Uruchom", aby rozpocząć proces.
|
| 10 |
+
<br><br>
|
| 11 |
+
A może masz już gotowy plik audio z wypowiedzią w języku polskim? Możesz go przesłać, przeciągając go do pola poniżej lub klikając na nie, aby otworzyć okno wyboru pliku. Aplikacja obsługuje pliki w formacie WAV.
|
| 12 |
+
<br><br>
|
| 13 |
+
Architektura owej wersji demonstracyjnej jest okrojoną wersją naszego pełnego rozwiązania, jednak zawiera najważniejsze funkcjonalności pozwalające na relatywnie szybkie działanie. Składa się ona z modelu transkrypcji mowy Whisper Large v3 Turbo, modułu tłumaczenia wykorzystującego API od DeepL, oraz modelu syntezy mowy XTTS.
|
| 14 |
+
""")
|
| 15 |
+
|
| 16 |
+
audio_input = gr.Audio(type="filepath", label="🎙️ Nagranie mowy (PL)")
|
| 17 |
+
tts_output = gr.Audio(label="🗣️ Synteza głosu (EN)", type="filepath")
|
| 18 |
+
|
| 19 |
+
run_btn = gr.Button("▶️ Uruchom")
|
| 20 |
+
|
| 21 |
+
run_btn.click(
|
| 22 |
+
fn=full_pipeline,
|
| 23 |
+
inputs=audio_input,
|
| 24 |
+
outputs=[tts_output]
|
| 25 |
+
)
|
src/models/asr.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import librosa
|
| 2 |
+
from transformers import WhisperProcessor, WhisperForConditionalGeneration
|
| 3 |
+
from typing import Optional
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class ASRService:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 10 |
+
self.processor, self.model = self._load_model()
|
| 11 |
+
|
| 12 |
+
def _load_model(self):
|
| 13 |
+
"""Ładowanie modelu Whisper"""
|
| 14 |
+
model_name = "openai/whisper-large-v3-turbo"
|
| 15 |
+
processor = WhisperProcessor.from_pretrained(model_name)
|
| 16 |
+
model = WhisperForConditionalGeneration.from_pretrained(model_name).to(self.device)
|
| 17 |
+
model.config.forced_decoder_ids = processor.get_decoder_prompt_ids(language="pl", task="transcribe")
|
| 18 |
+
return processor, model
|
| 19 |
+
|
| 20 |
+
def transcribe(self, audio_path: str) -> str:
|
| 21 |
+
"""Transkrypcja audio na tekst"""
|
| 22 |
+
sample, sr = librosa.load(audio_path, sr=16000)
|
| 23 |
+
input_features = self.processor(
|
| 24 |
+
sample,
|
| 25 |
+
sampling_rate=sr,
|
| 26 |
+
return_tensors="pt"
|
| 27 |
+
).input_features.to(self.device)
|
| 28 |
+
|
| 29 |
+
predicted_ids = self.model.generate(input_features)
|
| 30 |
+
return self.processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
|
| 31 |
+
|
| 32 |
+
# Singleton pattern
|
| 33 |
+
ASR = ASRService()
|
src/models/pipeline.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from src.models.tts.xtts import XTTS
|
| 2 |
+
from src.models.asr import ASR
|
| 3 |
+
from src.models.translation import TRANSLATOR
|
| 4 |
+
import os
|
| 5 |
+
OUTPUT_WAV_PATH = "output.wav"
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def PIPELINE(audio_file: str) -> str:
|
| 9 |
+
# TRANSCRIPTION
|
| 10 |
+
transcript = ASR.transcribe(audio_file)
|
| 11 |
+
# TRANSLATION
|
| 12 |
+
translation = TRANSLATOR.translate(transcript, source_lang="pl", target_lang="EN")
|
| 13 |
+
# SYNTHESIS
|
| 14 |
+
if os.path.exists(OUTPUT_WAV_PATH):
|
| 15 |
+
os.remove(OUTPUT_WAV_PATH)
|
| 16 |
+
XTTS.synthesize(
|
| 17 |
+
text=translation,
|
| 18 |
+
reference_wav=audio_file,
|
| 19 |
+
output_path=OUTPUT_WAV_PATH,
|
| 20 |
+
language="en"
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
return OUTPUT_WAV_PATH
|
src/models/translation.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import deepl
|
| 2 |
+
import os
|
| 3 |
+
from typing import Optional
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class TranslationService:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.api_key = os.getenv("DEEPL_API_KEY")
|
| 13 |
+
if not self.api_key:
|
| 14 |
+
raise ValueError("Zmienna środowiskowa DEEPL_API_KEY nie jest ustawiona!")
|
| 15 |
+
self.translator = deepl.Translator(self.api_key)
|
| 16 |
+
|
| 17 |
+
def translate(
|
| 18 |
+
self,
|
| 19 |
+
text: str,
|
| 20 |
+
source_lang: str = "PL",
|
| 21 |
+
target_lang: str = "EN"
|
| 22 |
+
) -> str:
|
| 23 |
+
"""Tłumaczenie tekstu"""
|
| 24 |
+
result = self.translator.translate_text(
|
| 25 |
+
text,
|
| 26 |
+
source_lang=source_lang,
|
| 27 |
+
target_lang=target_lang
|
| 28 |
+
)
|
| 29 |
+
return result.text
|
| 30 |
+
|
| 31 |
+
# Singleton pattern
|
| 32 |
+
TRANSLATOR = TranslationService()
|
src/models/tts/xtts.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from TTS.api import TTS
|
| 2 |
+
import torch
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
class TTSService:
|
| 6 |
+
def __init__(self):
|
| 7 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 8 |
+
self.model = self._load_model()
|
| 9 |
+
|
| 10 |
+
def _load_model(self):
|
| 11 |
+
"""Ładowanie modelu XTTS v2"""
|
| 12 |
+
return TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(self.device)
|
| 13 |
+
|
| 14 |
+
def synthesize(
|
| 15 |
+
self,
|
| 16 |
+
text: str,
|
| 17 |
+
reference_wav: str,
|
| 18 |
+
output_path: str,
|
| 19 |
+
language: str = "en"
|
| 20 |
+
) -> None:
|
| 21 |
+
"""Generacja pliku audio z tekstu"""
|
| 22 |
+
self.model.tts_to_file(
|
| 23 |
+
text=text,
|
| 24 |
+
speaker_wav=reference_wav,
|
| 25 |
+
language=language,
|
| 26 |
+
file_path=output_path
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# Singleton pattern
|
| 30 |
+
XTTS = TTSService()
|
src/utils.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
|
| 3 |
+
def get_base64_image(img_path):
|
| 4 |
+
with open(img_path, "rb") as f:
|
| 5 |
+
encoded = base64.b64encode(f.read()).decode("utf-8")
|
| 6 |
+
return f"data:image/png;base64,{encoded}"
|