Browse Source

Add translation example from chinese, french to english (#681)

This adds:
- a VAD node to detect voice within an audio to avoid having too much
noise
- Bump distil-whisper to whisper-turbo
- Use Argotranslate and OPUS-MT for translation
- Add example dataflow within the example folder

## Get started

```bash
cd examples/translation
dora up

dora build dataflow_zh_en_terminal.yml
dora start dataflow_zh_en_terminal.yml --detach

python pretty_print.py
```
tags/v0.3.7rc0
Haixuan Xavier Tao GitHub 1 year ago
parent
commit
4e51cd8cd1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
30 changed files with 768 additions and 56 deletions
  1. +1
    -0
      examples/translation/.gitignore
  2. +24
    -0
      examples/translation/README.md
  3. +45
    -0
      examples/translation/dataflow_en_zh.yml
  4. +45
    -0
      examples/translation/dataflow_en_zh_terminal.yml
  5. +46
    -0
      examples/translation/dataflow_en_zh_terminal_argo.yml
  6. +46
    -0
      examples/translation/dataflow_fr_en.yml
  7. +46
    -0
      examples/translation/dataflow_zh_en.yml
  8. +42
    -0
      examples/translation/dataflow_zh_en_terminal.yml
  9. +46
    -0
      examples/translation/pretty_print.py
  10. +2
    -0
      node-hub/dora-argotranslate/README.md
  11. +11
    -0
      node-hub/dora-argotranslate/dora_argotranslate/__init__.py
  12. +43
    -0
      node-hub/dora-argotranslate/dora_argotranslate/main.py
  13. +25
    -0
      node-hub/dora-argotranslate/pyproject.toml
  14. +9
    -0
      node-hub/dora-argotranslate/tests/test_translate.py
  15. +82
    -6
      node-hub/dora-distil-whisper/dora_distil_whisper/main.py
  16. +1
    -0
      node-hub/dora-distil-whisper/pyproject.toml
  17. +9
    -44
      node-hub/dora-microphone/dora_microphone/main.py
  18. +1
    -0
      node-hub/dora-opus/README.md
  19. +11
    -0
      node-hub/dora-opus/dora_opus/__init__.py
  20. +78
    -0
      node-hub/dora-opus/dora_opus/main.py
  21. +26
    -0
      node-hub/dora-opus/pyproject.toml
  22. +9
    -0
      node-hub/dora-opus/tests/test_translate.py
  23. +16
    -5
      node-hub/dora-parler/dora_parler/main.py
  24. +1
    -0
      node-hub/dora-parler/pyproject.toml
  25. +1
    -1
      node-hub/dora-qwenvl/dora_qwenvl/main.py
  26. +3
    -0
      node-hub/dora-vad/README.md
  27. +11
    -0
      node-hub/dora-vad/dora_vad/__init__.py
  28. +53
    -0
      node-hub/dora-vad/dora_vad/main.py
  29. +26
    -0
      node-hub/dora-vad/pyproject.toml
  30. +9
    -0
      node-hub/dora-vad/tests/test_translate.py

+ 1
- 0
examples/translation/.gitignore View File

@@ -0,0 +1 @@
*.pt

+ 24
- 0
examples/translation/README.md View File

@@ -0,0 +1,24 @@
# Dora argo example

Make sure to have, dora, pip and cargo installed.

```bash
dora up

## For chinese
dora build dataflow_zh_en_terminal.yml
dora start dataflow_zh_en_terminal.yml --detach

python pretty_print.py

dora stop


## For chinese
dora build dataflow_en_zh_terminal.yml
dora start dataflow_en_zh_terminal.yml --detach

python pretty_print.py

dora stop
```

+ 45
- 0
examples/translation/dataflow_en_zh.yml View File

@@ -0,0 +1,45 @@
nodes:
- id: dora-microphone
build: pip install -e ../../node-hub/dora-microphone
path: dora-microphone
outputs:
- audio

- id: dora-vad
build: pip install -e ../../node-hub/dora-vad
path: dora-vad
inputs:
audio: dora-microphone/audio
outputs:
- audio

- id: dora-distil-whisper
build: pip install -e ../../node-hub/dora-distil-whisper
path: dora-distil-whisper
inputs:
input: dora-vad/audio
outputs:
- text
env:
TARGET_LANGUAGE: english
TRANSLATE: false

- id: dora-argotranslate
build: pip install -e ../../node-hub/dora-argotranslate
path: dora-argotranslate
inputs:
text: dora-distil-whisper/text
outputs:
- text
env:
SOURCE_LANGUAGE: en
TARGET_LANGUAGE: zh

- id: dora-rerun
build: cargo build -p dora-rerun --release
path: dora-rerun
inputs:
text: dora-argotranslate/text
env:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

+ 45
- 0
examples/translation/dataflow_en_zh_terminal.yml View File

@@ -0,0 +1,45 @@
nodes:
- id: dora-microphone
build: pip install -e ../../node-hub/dora-microphone
path: dora-microphone
outputs:
- audio

- id: dora-vad
build: pip install -e ../../node-hub/dora-vad
path: dora-vad
inputs:
audio: dora-microphone/audio
outputs:
- audio

- id: dora-distil-whisper
build: pip install -e ../../node-hub/dora-distil-whisper
path: dora-distil-whisper
inputs:
input: dora-vad/audio
outputs:
- text
env:
TARGET_LANGUAGE: english
TRANSLATE: false

- id: dora-opus
build: pip install -e ../../node-hub/dora-opus
path: dora-opus
inputs:
text: dora-distil-whisper/text
outputs:
- text
env:
SOURCE_LANGUAGE: en
TARGET_LANGUAGE: zh

- id: pretty-print
path: dynamic
inputs:
translated_text: dora-opus/text
original_text: dora-distil-whisper/text
env:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

+ 46
- 0
examples/translation/dataflow_en_zh_terminal_argo.yml View File

@@ -0,0 +1,46 @@
nodes:
- id: dora-microphone
build: pip install -e ../../node-hub/dora-microphone
path: dora-microphone
outputs:
- audio

- id: dora-vad
build: pip install -e ../../node-hub/dora-vad
path: dora-vad
inputs:
audio: dora-microphone/audio
outputs:
- audio

- id: dora-distil-whisper
build: pip install -e ../../node-hub/dora-distil-whisper
path: dora-distil-whisper
inputs:
input: dora-vad/audio
outputs:
- text
env:
TARGET_LANGUAGE: english
TRANSLATE: false

- id: dora-argotranslate
build: pip install -e ../../node-hub/dora-argotranslate
path: dora-argotranslate
inputs:
text: dora-distil-whisper/text
outputs:
- text
env:
SOURCE_LANGUAGE: en
TARGET_LANGUAGE: zh

- id: pretty-print
build: cargo build -p dora-rerun --release
path: dynamic
inputs:
translated_text: dora-argotranslate/text
original_text: dora-distil-whisper/text
env:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

+ 46
- 0
examples/translation/dataflow_fr_en.yml View File

@@ -0,0 +1,46 @@
nodes:
- id: dora-microphone
build: pip install -e ../../node-hub/dora-microphone
path: dora-microphone
outputs:
- audio

- id: dora-vad
build: pip install -e ../../node-hub/dora-vad
path: dora-vad
inputs:
audio: dora-microphone/audio
outputs:
- audio

- id: dora-distil-whisper
build: pip install -e ../../node-hub/dora-distil-whisper
path: dora-distil-whisper
inputs:
input: dora-vad/audio
outputs:
- text
env:
TARGET_LANGUAGE: french
TRANSLATE: false

- id: dora-argotranslate
build: pip install -e ../../node-hub/dora-argotranslate
path: dora-argotranslate
inputs:
text: dora-distil-whisper/text
outputs:
- text
env:
SOURCE_LANGUAGE: fr
TARGET_LANGUAGE: en

- id: dora-rerun
build: cargo build -p dora-rerun --release
path: dora-rerun
inputs:
translated_text: dora-argotranslate/text
original_text: dora-distil-whisper/text
env:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

+ 46
- 0
examples/translation/dataflow_zh_en.yml View File

@@ -0,0 +1,46 @@
nodes:
- id: dora-microphone
build: pip install -e ../../node-hub/dora-microphone
path: dora-microphone
outputs:
- audio

- id: dora-vad
build: pip install -e ../../node-hub/dora-vad
path: dora-vad
inputs:
audio: dora-microphone/audio
outputs:
- audio

- id: dora-distil-whisper
build: pip install -e ../../node-hub/dora-distil-whisper
path: dora-distil-whisper
inputs:
input: dora-vad/audio
outputs:
- text
env:
TARGET_LANGUAGE: chinese
TRANSLATE: false

- id: dora-opus
build: pip install -e ../../node-hub/dora-opus
path: dora-opus
inputs:
text: dora-distil-whisper/text
outputs:
- text
env:
SOURCE_LANGUAGE: zh
TARGET_LANGUAGE: en

- id: plot
build: cargo build -p dora-rerun --release
path: dora-rerun
inputs:
translated_text: dora-opus/text
original_text: dora-distil-whisper/text
env:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

+ 42
- 0
examples/translation/dataflow_zh_en_terminal.yml View File

@@ -0,0 +1,42 @@
nodes:
- id: dora-microphone
build: pip install -e ../../node-hub/dora-microphone
path: dora-microphone
outputs:
- audio

- id: dora-vad
build: pip install -e ../../node-hub/dora-vad
path: dora-vad
inputs:
audio: dora-microphone/audio
outputs:
- audio

- id: dora-distil-whisper
build: pip install -e ../../node-hub/dora-distil-whisper
path: dora-distil-whisper
inputs:
input: dora-vad/audio
outputs:
- text
env:
TARGET_LANGUAGE: chinese
TRANSLATE: false

- id: dora-opus
build: pip install -e ../../node-hub/dora-opus
path: dora-opus
inputs:
text: dora-distil-whisper/text
outputs:
- text
env:
SOURCE_LANGUAGE: zh
TARGET_LANGUAGE: en

- id: pretty-print
path: dynamic
inputs:
translated_text: dora-opus/text
original_text: dora-distil-whisper/text

+ 46
- 0
examples/translation/pretty_print.py View File

@@ -0,0 +1,46 @@
import os
import shutil


def clear_screen():
# Clear the screen based on the operating system
os.system("cls" if os.name == "nt" else "clear")


def print_centered(texts):
# Get terminal size
terminal_size = shutil.get_terminal_size()

# Print newlines to move cursor to the middle vertically
for k, v in texts.items():
print(k)
print("\n" * 1)
# Calculate horizontal padding and print the centered text
for l in v:
print(l.center(terminal_size.columns))
print("\n" * 1)


from dora import Node

node = Node("pretty-print")

previous_texts = {}

clear_screen()
print("Waiting for speech...")
for event in node:
if event["type"] == "INPUT":
# The sentence to be printed
sentence = event["value"][0].as_py()
if event["id"] not in previous_texts.keys():

previous_texts[event["id"]] = ["", "", sentence]
else:
previous_texts[event["id"]] += [sentence]
previous_texts[event["id"]] = previous_texts[event["id"]][-3:]
# Clear the screen
clear_screen()

# Print the sentence in the middle of the terminal
print_centered(previous_texts)

+ 2
- 0
node-hub/dora-argotranslate/README.md View File

@@ -0,0 +1,2 @@
# Dora text translation Node using Argo translate


+ 11
- 0
node-hub/dora-argotranslate/dora_argotranslate/__init__.py View File

@@ -0,0 +1,11 @@
import os

# Define the path to the README file relative to the package directory
readme_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "README.md")

# Read the content of the README file
try:
with open(readme_path, "r", encoding="utf-8") as f:
__doc__ = f.read()
except FileNotFoundError:
__doc__ = "README file not found."

+ 43
- 0
node-hub/dora-argotranslate/dora_argotranslate/main.py View File

@@ -0,0 +1,43 @@
import os

os.environ["ARGOS_DEVICE_TYPE"] = "auto"

from dora import Node
import pyarrow as pa
import argostranslate.package
import argostranslate.translate

from_code = os.getenv("SOURCE_LANGUAGE", "fr")
to_code = os.getenv("TARGET_LANGUAGE", "en")

# Download and install Argos Translate package
argostranslate.package.update_package_index()
available_packages = argostranslate.package.get_available_packages()
package_to_install = next(
filter(
lambda x: x.from_code == from_code and x.to_code == to_code, available_packages
)
)
argostranslate.package.install_from_path(package_to_install.download())


def main():
node = Node()
while True:
event = node.next()
if event is None:
break
if event["type"] == "INPUT" and event["id"] == "text":
text = event["value"][0].as_py()
translatedText = argostranslate.translate.translate(
text,
from_code,
to_code,
)
print(text, flush=True)
print("translated: " + translatedText, flush=True)
node.send_output(
"text",
pa.array([translatedText]),
{"language": to_code},
)

+ 25
- 0
node-hub/dora-argotranslate/pyproject.toml View File

@@ -0,0 +1,25 @@
[tool.poetry]
name = "dora-argotranslate"
version = "0.3.6"
description = "Dora Node for Text translating using Argostranslate"
readme = "README.md"
authors = [
"Haixuan Xavier Tao <tao.xavier@outlook.com>",
"Enzo Le Van <dev@enzo-le-van.fr>",
"Félix Huang <felix.huang.net@gmail.com>",
]

packages = [{ include = "dora_argotranslate" }]

[tool.poetry.dependencies]
dora-rs = "^0.3.6"
numpy = "< 2.0.0"
python = "^3.7"
argostranslate = "^1.9.6"

[tool.poetry.scripts]
dora-argotranslate = "dora_argotranslate.main:main"

[build-system]
requires = ["poetry-core>=1.8.0"]
build-backend = "poetry.core.masonry.api"

+ 9
- 0
node-hub/dora-argotranslate/tests/test_translate.py View File

@@ -0,0 +1,9 @@
import pytest


def test_import_main():
from dora_argotranslate.main import main

# Check that everything is working, and catch dora Runtime Exception as we're not running in a dora dataflow.
with pytest.raises(RuntimeError):
main()

+ 82
- 6
node-hub/dora-distil-whisper/dora_distil_whisper/main.py View File

@@ -2,34 +2,110 @@ import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
from dora import Node
import pyarrow as pa
import os
from pathlib import Path

DEFAULT_PATH = "openai/whisper-large-v3-turbo"
TARGET_LANGUAGE = os.getenv("TARGET_LANGUAGE", "chinese")
TRANSLATE = bool(os.getenv("TRANSLATE", "False"))


MODEL_NAME_OR_PATH = os.getenv("MODEL_NAME_OR_PATH", DEFAULT_PATH)

if bool(os.getenv("USE_MODELSCOPE_HUB")) is True:
from modelscope import snapshot_download

if not Path(MODEL_NAME_OR_PATH).exists():
MODEL_NAME_OR_PATH = snapshot_download(MODEL_NAME_OR_PATH)

device = "cuda:0" if torch.cuda.is_available() else "cpu"
torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32

model_id = "distil-whisper/distil-large-v3"

model = AutoModelForSpeechSeq2Seq.from_pretrained(
model_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True, use_safetensors=True
MODEL_NAME_OR_PATH,
torch_dtype=torch_dtype,
low_cpu_mem_usage=True,
use_safetensors=True,
)
model.to(device)

processor = AutoProcessor.from_pretrained(model_id)
processor = AutoProcessor.from_pretrained(MODEL_NAME_OR_PATH)
pipe = pipeline(
"automatic-speech-recognition",
model=model,
tokenizer=processor.tokenizer,
feature_extractor=processor.feature_extractor,
max_new_tokens=128,
max_new_tokens=400,
torch_dtype=torch_dtype,
device=device,
)

BAD_SENTENCES = [
"字幕",
"字幕志愿",
"中文字幕",
"我",
"你",
"THANK YOU",
" Thank you.",
" www.microsoft.com",
" The",
" BANG",
" Silence.",
" Sous-titrage Société Radio-Canada",
" Sous",
" Sous-",
]


def cut_repetition(text, min_repeat_length=4, max_repeat_length=50):
# Check if the text is primarily Chinese (you may need to adjust this threshold)
if sum(1 for char in text if "\u4e00" <= char <= "\u9fff") / len(text) > 0.5:
# Chinese text processing
for repeat_length in range(
min_repeat_length, min(max_repeat_length, len(text) // 2)
):
for i in range(len(text) - repeat_length * 2 + 1):
chunk1 = text[i : i + repeat_length]
chunk2 = text[i + repeat_length : i + repeat_length * 2]

if chunk1 == chunk2:
return text[: i + repeat_length]
else:
# Non-Chinese (space-separated) text processing
words = text.split()
for repeat_length in range(
min_repeat_length, min(max_repeat_length, len(words) // 2)
):
for i in range(len(words) - repeat_length * 2 + 1):
chunk1 = " ".join(words[i : i + repeat_length])
chunk2 = " ".join(words[i + repeat_length : i + repeat_length * 2])

if chunk1 == chunk2:
return " ".join(words[: i + repeat_length])

return text


def main():
node = Node()
for event in node:
if event["type"] == "INPUT":
audio = event["value"].to_numpy()
result = pipe(audio)
node.send_output("text", pa.array([result["text"]]))
confg = (
{"language": TARGET_LANGUAGE, "task": "translate"}
if TRANSLATE
else {
"language": TARGET_LANGUAGE,
}
)
result = pipe(
audio,
generate_kwargs=confg,
)
if result["text"] in BAD_SENTENCES:
continue
text = cut_repetition(result["text"])
print(text, flush=True)
node.send_output("text", pa.array([text]), {"language": TARGET_LANGUAGE})

+ 1
- 0
node-hub/dora-distil-whisper/pyproject.toml View File

@@ -20,6 +20,7 @@ transformers = "^4.0.0"
accelerate = "^0.29.2"
torch = "^2.2.0"
python = "^3.7"
modelscope = "^1.18.1"

[tool.poetry.scripts]
dora-distil-whisper = "dora_distil_whisper.main:main"


+ 9
- 44
node-hub/dora-microphone/dora_microphone/main.py View File

@@ -2,66 +2,31 @@ import sounddevice as sd
import numpy as np
import pyarrow as pa
import time as tm
from enum import Enum
import os

from dora import Node


class RecordingState(Enum):
"""Enum for recording states."""

PENDING = 0
RUNNING = 1
SILENCE = 2


def detect_speech(audio_data, threshold):
"""Check if the amplitude of the audio signal exceeds the threshold."""
return np.any(np.abs(audio_data) > threshold)
MAX_DURATION = float(os.getenv("MAX_DURATION", "0.1"))
SAMPLE_RATE = int(os.getenv("SAMPLE_RATE", "16000"))


def main():
# Parameters
threshold = 500 # Threshold for detecting speech (adjust this as needed)
SAMPLE_RATE = 16000
silence_duration = 0.5 # Duration of silence before stopping the recording

# Initialize buffer and recording flag
buffer = []
state = RecordingState.PENDING
silence_start_time = tm.time()
start_recording_time = tm.time()
max_duration = 20
node = Node()

# pylint: disable=unused-argument
def callback(indata, frames, time, status):
nonlocal buffer, state, silence_start_time, node, max_duration, start_recording_time
nonlocal buffer, node, start_recording_time

is_speaking = detect_speech(indata[:, 0], threshold)
if is_speaking:
if state == RecordingState.PENDING:
buffer = []
state = RecordingState.RUNNING
start_recording_time = tm.time()
buffer.extend(indata[:, 0])
elif not is_speaking and state == RecordingState.RUNNING:
silence_start_time = tm.time() # Reset silence timer
buffer.extend(indata[:, 0])
state = RecordingState.SILENCE
elif (
state == RecordingState.RUNNING or state == RecordingState.SILENCE
) and tm.time() - start_recording_time > max_duration:
if tm.time() - start_recording_time > MAX_DURATION:
audio_data = np.array(buffer).ravel().astype(np.float32) / 32768.0
node.send_output("audio", pa.array(audio_data))
state = RecordingState.PENDING
elif not is_speaking and state == RecordingState.SILENCE:
if tm.time() - silence_start_time > silence_duration:
audio_data = np.array(buffer).ravel().astype(np.float32) / 32768.0
node.send_output("audio", pa.array(audio_data))
state = RecordingState.PENDING
else:
buffer.extend(indata[:, 0])
buffer = []
start_recording_time = tm.time()
else:
buffer.extend(indata[:, 0])

# Start recording
with sd.InputStream(


+ 1
- 0
node-hub/dora-opus/README.md View File

@@ -0,0 +1 @@
# Dora text translation Node using OPUS MT

+ 11
- 0
node-hub/dora-opus/dora_opus/__init__.py View File

@@ -0,0 +1,11 @@
import os

# Define the path to the README file relative to the package directory
readme_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "README.md")

# Read the content of the README file
try:
with open(readme_path, "r", encoding="utf-8") as f:
__doc__ = f.read()
except FileNotFoundError:
__doc__ = "README file not found."

+ 78
- 0
node-hub/dora-opus/dora_opus/main.py View File

@@ -0,0 +1,78 @@
import os
from pathlib import Path
from dora import Node
import pyarrow as pa
import numpy as np
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

from_code = os.getenv("SOURCE_LANGUAGE", "zh")
to_code = os.getenv("TARGET_LANGUAGE", "en")
DEFAULT_PATH = f"Helsinki-NLP/opus-mt-{from_code}-{to_code}"


MODEL_NAME_OR_PATH = os.getenv("MODEL_NAME_OR_PATH", DEFAULT_PATH)

if bool(os.getenv("USE_MODELSCOPE_HUB")) is True:
from modelscope import snapshot_download

if not Path(MODEL_NAME_OR_PATH).exists():
MODEL_NAME_OR_PATH = snapshot_download(MODEL_NAME_OR_PATH)

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH)

model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)


def cut_repetition(text, min_repeat_length=4, max_repeat_length=50):
# Check if the text is primarily Chinese (you may need to adjust this threshold)
if sum(1 for char in text if "\u4e00" <= char <= "\u9fff") / len(text) > 0.5:
# Chinese text processing
for repeat_length in range(
min_repeat_length, min(max_repeat_length, len(text) // 2)
):
for i in range(len(text) - repeat_length * 2 + 1):
chunk1 = text[i : i + repeat_length]
chunk2 = text[i + repeat_length : i + repeat_length * 2]

if chunk1 == chunk2:
return text[: i + repeat_length]
else:
# Non-Chinese (space-separated) text processing
words = text.split()
for repeat_length in range(
min_repeat_length, min(max_repeat_length, len(words) // 2)
):
for i in range(len(words) - repeat_length * 2 + 1):
chunk1 = " ".join(words[i : i + repeat_length])
chunk2 = " ".join(words[i + repeat_length : i + repeat_length * 2])

if chunk1 == chunk2:
return " ".join(words[: i + repeat_length])

return text


def main():
node = Node()
while True:
event = node.next()
if event is None:
break
if event["type"] == "INPUT" and event["id"] == "text":
text = [str(event["value"][0].as_py())]
translated = (
model.generate(**tokenizer(text, return_tensors="pt", padding=True))
.to("cpu")
.detach()
.numpy()
.ravel()
)

array = np.array(tokenizer.decode(translated, skip_special_tokens=True))
array = np.array(array).ravel()
array = [cut_repetition(array[0])]
node.send_output(
"text",
pa.array(array),
{"language": to_code},
)

+ 26
- 0
node-hub/dora-opus/pyproject.toml View File

@@ -0,0 +1,26 @@
[tool.poetry]
name = "dora-opus"
version = "0.3.6"
description = "Dora Node for Text translating using Opus"
readme = "README.md"
authors = [
"Haixuan Xavier Tao <tao.xavier@outlook.com>",
"Enzo Le Van <dev@enzo-le-van.fr>",
"Félix Huang <felix.huang.net@gmail.com>",
]

packages = [{ include = "dora_opus" }]

[tool.poetry.dependencies]
dora-rs = "^0.3.6"
numpy = "< 2.0.0"
python = "^3.7"
transformers = "^4.45"
modelscope = "^1.18.1"

[tool.poetry.scripts]
dora-opus = "dora_opus.main:main"

[build-system]
requires = ["poetry-core>=1.8.0"]
build-backend = "poetry.core.masonry.api"

+ 9
- 0
node-hub/dora-opus/tests/test_translate.py View File

@@ -0,0 +1,9 @@
import pytest


def test_import_main():
from dora_opus.main import main

# Check that everything is working, and catch dora Runtime Exception as we're not running in a dora dataflow.
with pytest.raises(RuntimeError):
main()

+ 16
- 5
node-hub/dora-parler/dora_parler/main.py View File

@@ -1,6 +1,7 @@
from threading import Thread
from dora import Node

import os
from pathlib import Path
import numpy as np
import torch
import time
@@ -18,16 +19,25 @@ from transformers import (
device = "cuda:0" # if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
torch_dtype = torch.float16 if device != "cpu" else torch.float32

repo_id = "ylacombe/parler-tts-mini-jenny-30H"
DEFAULT_PATH = "ylacombe/parler-tts-mini-jenny-30H"


MODEL_NAME_OR_PATH = os.getenv("MODEL_NAME_OR_PATH", DEFAULT_PATH)

if bool(os.getenv("USE_MODELSCOPE_HUB")) is True:
from modelscope import snapshot_download

if not Path(MODEL_NAME_OR_PATH).exists():
MODEL_NAME_OR_PATH = snapshot_download(MODEL_NAME_OR_PATH)

model = ParlerTTSForConditionalGeneration.from_pretrained(
repo_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True
MODEL_NAME_OR_PATH, torch_dtype=torch_dtype, low_cpu_mem_usage=True
).to(device)
model.generation_config.cache_implementation = "static"
model.forward = torch.compile(model.forward, mode="default")

tokenizer = AutoTokenizer.from_pretrained(repo_id)
feature_extractor = AutoFeatureExtractor.from_pretrained(repo_id)
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH)
feature_extractor = AutoFeatureExtractor.from_pretrained(MODEL_NAME_OR_PATH)

SAMPLE_RATE = feature_extractor.sampling_rate
SEED = 42
@@ -59,6 +69,7 @@ def play_audio(audio_array):

class InterruptStoppingCriteria(StoppingCriteria):
def __init__(self):
super().__init__()
self.stop_signal = False

def __call__(


+ 1
- 0
node-hub/dora-parler/pyproject.toml View File

@@ -20,6 +20,7 @@ torchaudio = "^2.2.2"
sentencepiece = "^0.1.99"
python = "^3.7"
pyaudio = "^0.2.14"
modelscope = "^1.18.1"


[tool.poetry.scripts]


+ 1
- 1
node-hub/dora-qwenvl/dora_qwenvl/main.py View File

@@ -12,7 +12,7 @@ DEFAULT_PATH = "Qwen/Qwen2-VL-2B-Instruct"

MODEL_NAME_OR_PATH = os.getenv("MODEL_NAME_OR_PATH", DEFAULT_PATH)

if bool(os.getenv("MODELSCOPE")) is True:
if bool(os.getenv("USE_MODELSCOPE_HUB")) is True:
from modelscope import snapshot_download

if not Path(MODEL_NAME_OR_PATH).exists():


+ 3
- 0
node-hub/dora-vad/README.md View File

@@ -0,0 +1,3 @@
# Speech Activity Detection(VAD)

This is using Silero VAD.

+ 11
- 0
node-hub/dora-vad/dora_vad/__init__.py View File

@@ -0,0 +1,11 @@
import os

# Define the path to the README file relative to the package directory
readme_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "README.md")

# Read the content of the README file
try:
with open(readme_path, "r", encoding="utf-8") as f:
__doc__ = f.read()
except FileNotFoundError:
__doc__ = "README file not found."

+ 53
- 0
node-hub/dora-vad/dora_vad/main.py View File

@@ -0,0 +1,53 @@
from dora import Node
import pyarrow as pa
import numpy as np
import os
from silero_vad import load_silero_vad, get_speech_timestamps
import torch

model = load_silero_vad()
MIN_SILENCE_DURATION_MS = int(os.getenv("MIN_SILENCE_DURATION_MS", "200"))
MIN_SPEECH_DURATION_MS = int(os.getenv("MIN_SPEECH_DURATION_MS", "1000"))

MIN_AUDIO_SAMPLING_DURAION_S = int(os.getenv("MAX_AUDIO_DURATION_S", "20"))
MAX_AUDIO_DURAION_S = int(os.getenv("MAX_AUDIO_DURATION_S", "75"))


def main():
node = Node()
last_audios = []
while True:
event = node.next()
if event is None:
break
if event["type"] == "INPUT" and event["id"] == "audio":
audio = event["value"].to_numpy()
last_audios += [audio]
last_audios = last_audios[-100:]
audio = np.concatenate(last_audios)
speech_timestamps = get_speech_timestamps(
torch.from_numpy(audio),
model,
threshold=0.2,
min_speech_duration_ms=MIN_SPEECH_DURATION_MS,
min_silence_duration_ms=MIN_SILENCE_DURATION_MS,
)

# Check ig there is timestamp
if (
len(speech_timestamps) > 0
and len(last_audios) > MIN_AUDIO_SAMPLING_DURAION_S
):

# Check if the audio is not cut at the end. And only return if there is a long time spent
if speech_timestamps[-1]["end"] == len(audio):
continue
else:
audio = audio[0 : speech_timestamps[-1]["end"]]
node.send_output("audio", pa.array(audio))
last_audios = [audio[speech_timestamps[-1]["end"] :]]

# If there is no sound for too long return the audio
elif len(last_audios) > 75:
node.send_output("audio", pa.array(audio))
last_audios = []

+ 26
- 0
node-hub/dora-vad/pyproject.toml View File

@@ -0,0 +1,26 @@
[tool.poetry]
name = "dora-vad"
version = "0.3.6"
description = "Dora Node for Text translating using Argostranslate"
readme = "README.md"
authors = [
"Haixuan Xavier Tao <tao.xavier@outlook.com>",
"Enzo Le Van <dev@enzo-le-van.fr>",
"Félix Huang <felix.huang.net@gmail.com>",
]

packages = [{ include = "dora_vad" }]

[tool.poetry.dependencies]
dora-rs = "^0.3.6"
numpy = "< 2.0.0"
python = "^3.7"
silero-vad = "^5.1"


[tool.poetry.scripts]
dora-vad = "dora_vad.main:main"

[build-system]
requires = ["poetry-core>=1.8.0"]
build-backend = "poetry.core.masonry.api"

+ 9
- 0
node-hub/dora-vad/tests/test_translate.py View File

@@ -0,0 +1,9 @@
import pytest


def test_import_main():
from dora_vad.main import main

# Check that everything is working, and catch dora Runtime Exception as we're not running in a dora dataflow.
with pytest.raises(RuntimeError):
main()

Loading…
Cancel
Save