diff --git a/examples/daily-multi-translation/Dockerfile b/examples/daily-multi-translation/Dockerfile new file mode 100644 index 000000000..419adca34 --- /dev/null +++ b/examples/daily-multi-translation/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.10-bullseye + +RUN mkdir /app +RUN mkdir /app/assets +RUN mkdir /app/utils +COPY *.py /app/ +COPY requirements.txt /app/ + + +WORKDIR /app +RUN pip3 install -r requirements.txt + +EXPOSE 7860 + +CMD ["python3", "server.py"] diff --git a/examples/daily-multi-translation/README.md b/examples/daily-multi-translation/README.md new file mode 100644 index 000000000..7e27cb217 --- /dev/null +++ b/examples/daily-multi-translation/README.md @@ -0,0 +1,39 @@ +# Daily Multi Translation + +This example shows how to use Daily to stream multiple simultaneous translations using a single transport. Daily provides custom tracks and in this example we will simultaneously translate incoming audio in English to Spanish, French and German, each of them being sent to a custom track. + +## Get started + +```python +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt + +cp env.example .env # and add your credentials + +``` + +## Run the server + +```bash +python server.py +``` + +Then, visit `http://localhost:7860/` in your browser. This will open a Daily Prebuilt room where you will speak in English (make sure you are not muted). + +## Open client + +Next, you need to open the client that will listen to the translations. + +```bash +open index.html +``` + +Once the client is opened, copy the URL of the Daily room created above and join it. You should be able to select which translation you want to hear. + +## Build and test the Docker image + +``` +docker build -t daily-multi-translation . +docker run --env-file .env -p 7860:7860 daily-multi-translation +``` diff --git a/examples/daily-multi-translation/bot.py b/examples/daily-multi-translation/bot.py new file mode 100644 index 000000000..32a157096 --- /dev/null +++ b/examples/daily-multi-translation/bot.py @@ -0,0 +1,164 @@ +# +# Copyright (c) 2024–2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +import asyncio +import os +import sys + +import aiohttp +from dotenv import load_dotenv +from loguru import logger +from runner import configure + +from pipecat.audio.mixers.soundfile_mixer import SoundfileMixer +from pipecat.audio.vad.silero import SileroVADAnalyzer +from pipecat.observers.loggers.transcription_log_observer import TranscriptionLogObserver +from pipecat.pipeline.parallel_pipeline import ParallelPipeline +from pipecat.pipeline.pipeline import Pipeline +from pipecat.pipeline.runner import PipelineRunner +from pipecat.pipeline.task import PipelineParams, PipelineTask +from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext +from pipecat.services.cartesia.tts import CartesiaTTSService +from pipecat.services.deepgram.stt import DeepgramSTTService +from pipecat.services.openai.llm import OpenAILLMService +from pipecat.transports.services.daily import DailyParams, DailyTransport + +load_dotenv(override=True) + +logger.remove(0) +logger.add(sys.stderr, level="DEBUG") + +BACKGROUND_SOUND_FILE = "office-ambience-mono-16000.mp3" + + +async def main(): + async with aiohttp.ClientSession() as session: + (room_url, token) = await configure(session) + + transport = DailyTransport( + room_url, + token, + "Multi translation bot", + DailyParams( + audio_in_enabled=True, + audio_out_enabled=True, + audio_out_mixer={ + "spanish": SoundfileMixer( + sound_files={"office": BACKGROUND_SOUND_FILE}, default_sound="office" + ), + "french": SoundfileMixer( + sound_files={"office": BACKGROUND_SOUND_FILE}, default_sound="office" + ), + "german": SoundfileMixer( + sound_files={"office": BACKGROUND_SOUND_FILE}, default_sound="office" + ), + }, + audio_out_destinations=["spanish", "french", "german"], + vad_analyzer=SileroVADAnalyzer(), + ), + ) + + stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY")) + + tts_spanish = CartesiaTTSService( + api_key=os.getenv("CARTESIA_API_KEY"), + voice_id="cefcb124-080b-4655-b31f-932f3ee743de", + destination="spanish", + ) + tts_french = CartesiaTTSService( + api_key=os.getenv("CARTESIA_API_KEY"), + voice_id="8832a0b5-47b2-4751-bb22-6a8e2149303d", + destination="french", + ) + tts_german = CartesiaTTSService( + api_key=os.getenv("CARTESIA_API_KEY"), + voice_id="38aabb6a-f52b-4fb0-a3d1-988518f4dc06", + destination="german", + ) + + messages_spanish = [ + { + "role": "system", + "content": "You will be provided with a sentence in English, and your task is to only translate it into Spanish.", + }, + ] + messages_french = [ + { + "role": "system", + "content": "You will be provided with a sentence in English, and your task is to only translate it into French.", + }, + ] + messages_german = [ + { + "role": "system", + "content": "You will be provided with a sentence in English, and your task is to only translate it into German.", + }, + ] + + llm_spanish = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY")) + llm_french = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY")) + llm_german = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY")) + + context_spanish = OpenAILLMContext(messages_spanish) + context_aggregator_spanish = llm_spanish.create_context_aggregator(context_spanish) + + context_french = OpenAILLMContext(messages_french) + context_aggregator_french = llm_french.create_context_aggregator(context_french) + + context_german = OpenAILLMContext(messages_german) + context_aggregator_german = llm_german.create_context_aggregator(context_german) + + pipeline = Pipeline( + [ + transport.input(), # Transport user input + stt, + ParallelPipeline( + # Spanish pipeline. + [ + context_aggregator_spanish.user(), + llm_spanish, + tts_spanish, + context_aggregator_spanish.assistant(), + ], + # French pipeline. + [ + context_aggregator_french.user(), + llm_french, + tts_french, + context_aggregator_french.assistant(), + ], + # German pipeline. + [ + context_aggregator_german.user(), + llm_german, + tts_german, + context_aggregator_german.assistant(), + ], + ), + transport.output(), # Transport bot output + ] + ) + + task = PipelineTask( + pipeline, + params=PipelineParams( + audio_in_sample_rate=16000, + audio_out_sample_rate=16000, + allow_interruptions=True, + enable_metrics=True, + enable_usage_metrics=True, + report_only_initial_ttfb=True, + ), + observers=[TranscriptionLogObserver()], + ) + + runner = PipelineRunner() + + await runner.run(task) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/daily-multi-translation/env.example b/examples/daily-multi-translation/env.example new file mode 100644 index 000000000..a780ec7d8 --- /dev/null +++ b/examples/daily-multi-translation/env.example @@ -0,0 +1,5 @@ +DAILY_SAMPLE_ROOM_URL=https://yourdomain.daily.co/yourroom # (for joining the bot to the same room repeatedly for local dev) +DAILY_API_KEY=7df... +OPENAI_API_KEY=sk-PL... +DEEPGRAM_API_KEY=efb... +CARTESIA_API_KEY=aeb... diff --git a/examples/daily-multi-translation/index.html b/examples/daily-multi-translation/index.html new file mode 100644 index 000000000..52fd3d488 --- /dev/null +++ b/examples/daily-multi-translation/index.html @@ -0,0 +1,202 @@ + +
+