From baafa402c23aea410569202aa03acb0f314369a3 Mon Sep 17 00:00:00 2001 From: Mustafa <109312699+MSCetin37@users.noreply.github.com> Date: Tue, 12 Nov 2024 21:51:45 -0800 Subject: [PATCH] Add support for Audio and Video summarization to Docsum (#865) * v2a services Signed-off-by: Mustafa * add a2t - llm Signed-off-by: Mustafa * update whisper serve Signed-off-by: Mustafa * updates Signed-off-by: Mustafa * add data service Signed-off-by: Mustafa * gateway Signed-off-by: Mustafa * clean gateway & orchestrator Signed-off-by: Mustafa * updates Signed-off-by: Mustafa * updates Signed-off-by: Mustafa * adding functional tests Signed-off-by: Mustafa * updates Signed-off-by: Mustafa * updates Signed-off-by: Mustafa * updates read me file Signed-off-by: Mustafa * name changes Signed-off-by: Mustafa * update readme file Signed-off-by: Mustafa * update readme file Signed-off-by: Mustafa * update readme file Signed-off-by: Mustafa * update readme file Signed-off-by: Mustafa * update readme file Signed-off-by: Mustafa * update max token option Signed-off-by: Mustafa * update the test files Signed-off-by: Mustafa * readme updtes Signed-off-by: Mustafa * readme updtes Signed-off-by: Mustafa * clean code Signed-off-by: Mustafa * update dataprep-compose-cd.yaml file Signed-off-by: Mustafa * merge and sync Signed-off-by: Mustafa * merge and sync gateway Signed-off-by: Mustafa * adding the copyright header Signed-off-by: Mustafa * update the end of file char Signed-off-by: Mustafa * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update gateway Signed-off-by: Mustafa * update gateway-docsum Signed-off-by: Mustafa * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix test files Signed-off-by: Mustafa * fix test files Signed-off-by: Mustafa * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * merge & sync Signed-off-by: Mustafa * Update dataprep-compose.yaml * update test Signed-off-by: Mustafa * update dataprep-compose Signed-off-by: Mustafa --------- Signed-off-by: Mustafa Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Abolfazl Shahbazi <12436063+ashahba@users.noreply.github.com> Co-authored-by: ZePan110 --- .../docker/compose/dataprep-compose.yaml | 12 + comps/__init__.py | 2 + comps/asr/whisper/dependency/whisper_model.py | 18 +- .../asr/whisper/dependency/whisper_server.py | 9 +- comps/cores/mega/gateway.py | 40 +-- comps/cores/proto/api_protocol.py | 8 + comps/cores/proto/docarray.py | 10 + comps/dataprep/multimedia2text/Dockerfile | 30 +++ comps/dataprep/multimedia2text/README.md | 220 ++++++++++++++++ .../multimedia2text/audio2text/Dockerfile | 37 +++ .../multimedia2text/audio2text/audio2text.py | 88 +++++++ .../audio2text/check_a2t_server.py | 86 +++++++ .../multimedia2text/check_multimedia2text.py | 154 +++++++++++ comps/dataprep/multimedia2text/data/README.md | 31 +++ .../multimedia2text/data/intel_short.mp4 | Bin 0 -> 74987 bytes .../multimedia2text/data/intel_short.wav | Bin 0 -> 3596 bytes .../multimedia2text/multimedia2text.py | 90 +++++++ .../multimedia2text/video2audio/Dockerfile | 31 +++ .../video2audio/check_v2a_microserver.py | 92 +++++++ .../video2audio/video2audio.py | 88 +++++++ .../video2audio/video2audio_microservice.py | 88 +++++++ tests/asr/test_asr_whisper.sh | 9 +- tests/dataprep/test_dataprep_multimedia.sh | 242 ++++++++++++++++++ 23 files changed, 1350 insertions(+), 35 deletions(-) create mode 100644 comps/dataprep/multimedia2text/Dockerfile create mode 100644 comps/dataprep/multimedia2text/README.md create mode 100644 comps/dataprep/multimedia2text/audio2text/Dockerfile create mode 100644 comps/dataprep/multimedia2text/audio2text/audio2text.py create mode 100644 comps/dataprep/multimedia2text/audio2text/check_a2t_server.py create mode 100644 comps/dataprep/multimedia2text/check_multimedia2text.py create mode 100644 comps/dataprep/multimedia2text/data/README.md create mode 100644 comps/dataprep/multimedia2text/data/intel_short.mp4 create mode 100644 comps/dataprep/multimedia2text/data/intel_short.wav create mode 100644 comps/dataprep/multimedia2text/multimedia2text.py create mode 100644 comps/dataprep/multimedia2text/video2audio/Dockerfile create mode 100644 comps/dataprep/multimedia2text/video2audio/check_v2a_microserver.py create mode 100644 comps/dataprep/multimedia2text/video2audio/video2audio.py create mode 100644 comps/dataprep/multimedia2text/video2audio/video2audio_microservice.py create mode 100644 tests/dataprep/test_dataprep_multimedia.sh diff --git a/.github/workflows/docker/compose/dataprep-compose.yaml b/.github/workflows/docker/compose/dataprep-compose.yaml index 2e5d6f9cd..7908e8c26 100644 --- a/.github/workflows/docker/compose/dataprep-compose.yaml +++ b/.github/workflows/docker/compose/dataprep-compose.yaml @@ -51,3 +51,15 @@ services: build: dockerfile: comps/dataprep/neo4j/llama_index/Dockerfile image: ${REGISTRY:-opea}/dataprep-neo4j-llamaindex:${TAG:-latest} + dataprep-multimedia2text: + build: + dockerfile: comps/dataprep/multimedia2text/Dockerfile + image: ${REGISTRY:-opea}/dataprep-multimedia2text:${TAG:-latest} + dataprep-video2audio: + build: + dockerfile: comps/dataprep/multimedia2text/video2audio/Dockerfile + image: ${REGISTRY:-opea}/dataprep-video2audio:${TAG:-latest} + dataprep-audio2text: + build: + dockerfile: comps/dataprep/multimedia2text/audio2text/Dockerfile + image: ${REGISTRY:-opea}/dataprep-audio2text:${TAG:-latest} diff --git a/comps/__init__.py b/comps/__init__.py index 153acad49..ee7caaf63 100644 --- a/comps/__init__.py +++ b/comps/__init__.py @@ -36,6 +36,8 @@ ScoreDoc, PIIRequestDoc, PIIResponseDoc, + Audio2text, + DocSumDoc, ) # Constants diff --git a/comps/asr/whisper/dependency/whisper_model.py b/comps/asr/whisper/dependency/whisper_model.py index cc16f1637..94f1c7ce5 100644 --- a/comps/asr/whisper/dependency/whisper_model.py +++ b/comps/asr/whisper/dependency/whisper_model.py @@ -14,7 +14,14 @@ class WhisperModel: """Convert audio to text.""" - def __init__(self, model_name_or_path="openai/whisper-small", language="english", device="cpu", hpu_max_len=8192): + def __init__( + self, + model_name_or_path="openai/whisper-small", + language="english", + device="cpu", + hpu_max_len=8192, + return_timestamps=False, + ): if device == "hpu": # Explicitly link HPU with Torch from optimum.habana.transformers.modeling_utils import adapt_transformers_to_gaudi @@ -31,6 +38,7 @@ def __init__(self, model_name_or_path="openai/whisper-small", language="english" self.language = language self.hpu_max_len = hpu_max_len + self.return_timestamps = return_timestamps if device == "hpu": self._warmup_whisper_hpu_graph("https://github.com/Spycsh/assets/raw/main/ljspeech_60s_audio.wav") @@ -104,7 +112,7 @@ def _warmup_whisper_hpu_graph(self, url): ) ), language=self.language, - return_timestamps=True, + return_timestamps=self.return_timestamps, ) def audio2text(self, audio_path): @@ -167,7 +175,7 @@ def audio2text(self, audio_path): ) ), language=self.language, - return_timestamps=True, + return_timestamps=self.return_timestamps, ) # pylint: disable=E1101 result = self.processor.tokenizer.batch_decode(predicted_ids, skip_special_tokens=True, normalize=True)[0] @@ -180,7 +188,9 @@ def audio2text(self, audio_path): if __name__ == "__main__": - asr = WhisperModel(model_name_or_path="openai/whisper-small", language="english", device="cpu") + asr = WhisperModel( + model_name_or_path="openai/whisper-small", language="english", device="cpu", return_timestamps=True + ) # Test multilanguage asr asr.language = "chinese" diff --git a/comps/asr/whisper/dependency/whisper_server.py b/comps/asr/whisper/dependency/whisper_server.py index 1a5c760d2..481bf0da0 100644 --- a/comps/asr/whisper/dependency/whisper_server.py +++ b/comps/asr/whisper/dependency/whisper_server.py @@ -39,6 +39,7 @@ async def audio_to_text(request: Request): audio = AudioSegment.from_file(file_name) audio = audio.set_frame_rate(16000) + audio.export(f"{file_name}", format="wav") try: asr_result = asr.audio2text(file_name) @@ -57,8 +58,14 @@ async def audio_to_text(request: Request): parser.add_argument("--model_name_or_path", type=str, default="openai/whisper-small") parser.add_argument("--language", type=str, default="english") parser.add_argument("--device", type=str, default="cpu") + parser.add_argument("--return_timestamps", type=str, default=True) args = parser.parse_args() - asr = WhisperModel(model_name_or_path=args.model_name_or_path, language=args.language, device=args.device) + asr = WhisperModel( + model_name_or_path=args.model_name_or_path, + language=args.language, + device=args.device, + return_timestamps=args.return_timestamps, + ) uvicorn.run(app, host=args.host, port=args.port) diff --git a/comps/cores/mega/gateway.py b/comps/cores/mega/gateway.py index 7d075eae0..4d44f66f7 100644 --- a/comps/cores/mega/gateway.py +++ b/comps/cores/mega/gateway.py @@ -17,10 +17,11 @@ ChatCompletionResponse, ChatCompletionResponseChoice, ChatMessage, + DocSumChatCompletionRequest, EmbeddingRequest, UsageInfo, ) -from ..proto.docarray import LLMParams, LLMParamsDoc, RerankedDoc, RerankerParms, RetrieverParms, TextDoc +from ..proto.docarray import DocSumDoc, LLMParams, LLMParamsDoc, RerankedDoc, RerankerParms, RetrieverParms, TextDoc from .constants import MegaServiceEndpoint, ServiceRoleType, ServiceType from .micro_service import MicroService @@ -409,34 +410,20 @@ async def handle_request(self, request: Request): class DocSumGateway(Gateway): def __init__(self, megaservice, host="0.0.0.0", port=8888): super().__init__( - megaservice, host, port, str(MegaServiceEndpoint.DOC_SUMMARY), ChatCompletionRequest, ChatCompletionResponse + megaservice, + host, + port, + str(MegaServiceEndpoint.DOC_SUMMARY), + input_datatype=DocSumChatCompletionRequest, + output_datatype=ChatCompletionResponse, ) - async def handle_request(self, request: Request, files: List[UploadFile] = File(default=None)): - data = await request.form() + async def handle_request(self, request: Request): + data = await request.json() stream_opt = data.get("stream", True) - chat_request = ChatCompletionRequest.parse_obj(data) - file_summaries = [] - if files: - for file in files: - file_path = f"/tmp/{file.filename}" - - import aiofiles - - async with aiofiles.open(file_path, "wb") as f: - await f.write(await file.read()) - docs = read_text_from_file(file, file_path) - os.remove(file_path) - if isinstance(docs, list): - file_summaries.extend(docs) - else: - file_summaries.append(docs) - - if file_summaries: - prompt = self._handle_message(chat_request.messages) + "\n".join(file_summaries) - else: - prompt = self._handle_message(chat_request.messages) + chat_request = ChatCompletionRequest.model_validate(data) + prompt = self._handle_message(chat_request.messages) parameters = LLMParams( max_tokens=chat_request.max_tokens if chat_request.max_tokens else 1024, top_k=chat_request.top_k if chat_request.top_k else 10, @@ -446,10 +433,9 @@ async def handle_request(self, request: Request, files: List[UploadFile] = File( presence_penalty=chat_request.presence_penalty if chat_request.presence_penalty else 0.0, repetition_penalty=chat_request.repetition_penalty if chat_request.repetition_penalty else 1.03, streaming=stream_opt, - language=chat_request.language if chat_request.language else "auto", ) result_dict, runtime_graph = await self.megaservice.schedule( - initial_inputs={"query": prompt}, llm_parameters=parameters + initial_inputs={data["type"]: prompt}, llm_parameters=parameters ) for node, response in result_dict.items(): # Here it suppose the last microservice in the megaservice is LLM. diff --git a/comps/cores/proto/api_protocol.py b/comps/cores/proto/api_protocol.py index cf8b2ca1d..d8d469ffb 100644 --- a/comps/cores/proto/api_protocol.py +++ b/comps/cores/proto/api_protocol.py @@ -269,6 +269,14 @@ class ChatCompletionRequest(BaseModel): request_type: Literal["chat"] = "chat" +class DocSumChatCompletionRequest(BaseModel): + llm_params: Optional[ChatCompletionRequest] = None + text: Optional[str] = None + audio: Optional[str] = None + video: Optional[str] = None + type: Optional[str] = None + + class AudioChatCompletionRequest(BaseModel): audio: str messages: Optional[ diff --git a/comps/cores/proto/docarray.py b/comps/cores/proto/docarray.py index 71b6f15ec..712b461b2 100644 --- a/comps/cores/proto/docarray.py +++ b/comps/cores/proto/docarray.py @@ -20,6 +20,10 @@ class TextDoc(BaseDoc, TopologyInfo): text: str = None +class Audio2text(BaseDoc, TopologyInfo): + query: str = None + + class FactualityDoc(BaseDoc): reference: str text: str @@ -74,6 +78,12 @@ class Base64ByteStrDoc(BaseDoc): byte_str: str +class DocSumDoc(BaseDoc): + text: Optional[str] = None + audio: Optional[str] = None + video: Optional[str] = None + + class DocPath(BaseDoc): path: str chunk_size: int = 1500 diff --git a/comps/dataprep/multimedia2text/Dockerfile b/comps/dataprep/multimedia2text/Dockerfile new file mode 100644 index 000000000..54b39b72f --- /dev/null +++ b/comps/dataprep/multimedia2text/Dockerfile @@ -0,0 +1,30 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Use the official Python 3.11 slim image as the base image +FROM python:3.11-slim + +# Set environment variables +ENV LANG=C.UTF-8 + +# Install necessary packages and clean up to reduce image size +RUN apt-get update -y && apt-get install -y --no-install-recommends --fix-missing \ + build-essential \ + libgl1-mesa-glx \ + libjemalloc-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create a directory for the user and set it as the working directory +WORKDIR /home/user + +# Copy the application code and requirements file to the container +COPY comps /home/user/comps +COPY requirements.txt /home/user/requirements.txt +COPY ./comps/dataprep/multimedia2text/multimedia2text.py /home/user/multimedia2text.py + +# Install Python dependencies +RUN python -m pip install --no-cache-dir -r requirements.txt + +# Define the entry point for the container +ENTRYPOINT ["python", "multimedia2text.py"] diff --git a/comps/dataprep/multimedia2text/README.md b/comps/dataprep/multimedia2text/README.md new file mode 100644 index 000000000..3adef100e --- /dev/null +++ b/comps/dataprep/multimedia2text/README.md @@ -0,0 +1,220 @@ +# Multimedia to Text Services + +This guide provides instructions on how to build and run various Docker services for converting multimedia content to text. The services include: + +1. **Whisper Service**: Converts audio to text. +2. **A2T Service**: Another service for audio to text conversion. +3. **Video to Audio Service**: Extracts audio from video files. +4. **Multimedia2Text Service**: Transforms multimedia data to text data. + +## Prerequisites + +1. **Docker**: Ensure you have Docker installed and running on your system. You can download and install Docker from the [official Docker website](https://www.docker.com/get-started). + +2. **Proxy Settings**: If you are behind a corporate firewall, make sure you have the necessary proxy settings configured. This will ensure that Docker and other tools can access the internet. + +3. **Python**: If you want to validate services using the provided Python scripts, ensure you have Python 3.11 installed. The current validation tests have been tested with Python 3.11. You can check your Python version by running the following command in your terminal: + ```bash + python --version + ``` + +## Getting Started + +First, navigate to the `GenAIComps` directory: + +```bash +cd GenAIComps +``` + +### Whisper Service + +The Whisper Service converts audio files to text. Follow these steps to build and run the service: + +#### Build + +```bash +docker build -t opea/whisper:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/asr/whisper/dependency/Dockerfile . +``` + +#### Run + +```bash +docker run -d -p 7066:7066 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy opea/whisper:latest +``` + +### A2T Service + +The A2T Service is another service for converting audio to text. Follow these steps to build and run the service: + +#### Build + +```bash +docker build -t opea/a2t:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/dataprep/multimedia2text/audio2text/Dockerfile . +``` + +#### Run + +```bash +host_ip=$(hostname -I | awk '{print $1}') + +docker run -d -p 9099:9099 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e A2T_ENDPOINT=http://$host_ip:7066 opea/a2t:latest +``` + +### Video to Audio Service + +The Video to Audio Service extracts audio from video files. Follow these steps to build and run the service: + +#### Build + +```bash +docker build -t opea/v2a:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/dataprep/multimedia2text/video2audio/Dockerfile . +``` + +#### Run + +```bash +docker run -d -p 7078:7078 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy opea/v2a:latest +``` + +### Multimedia2Text Service + +The Multimedia2Text Service transforms multimedia data to text data. Follow these steps to build and run the service: + +#### Build + +```bash +docker build -t opea/multimedia2text:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/dataprep/multimedia2text/Dockerfile . +``` + +#### Run + +```bash +host_ip=$(hostname -I | awk '{print $1}') + +docker run -d -p 7079:7079 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy \ + -e A2T_ENDPOINT=http://$host_ip:7066 \ + -e V2A_ENDPOINT=http://$host_ip:7078 \ + opea/multimedia2text:latest +``` + +## Validate Microservices + +After building and running the services, you can validate them using the provided Python scripts. Below are the steps to validate each service: + +### Whisper Service + +Run the following command to validate the Whisper Service: + +```bash +python comps/asr/whisper/dependency/check_whisper_server.py +``` + +Expected output: + +``` +{'asr_result': 'who is pat gelsinger'} +``` + +### Audio2Text Service + +Run the following command to validate the Audio2Text Service: + +```bash +python comps/dataprep/multimedia2text/audio2text/check_a2t_server.py +``` + +Expected output: + +``` +Test passed successfully! +``` + +_Note: The `id` value will be different._ + +### Video2Audio Service + +Run the following command to validate the Video2Audio Service: + +```bash +python comps/dataprep/multimedia2text/video2audio/check_v2a_microserver.py +``` + +Expected output: + +``` +========= Audio file saved as ====== +comps/dataprep/multimedia2text/video2audio/converted_audio.wav +==================================== +``` + +### Multimedia2Text Service + +Run the following command to validate the Multimedia2Text Service: + +```bash +python comps/dataprep/multimedia2text/check_multimedia2text.py +``` + +Expected output: + +``` +Running test: Whisper service +>>> Whisper service Test Passed ... + +Running test: Audio2Text service +>>> Audio2Text service Test Passed ... + +Running test: Video2Text service +>>> Video2Text service Test Passed ... + +Running test: Multimedia2text service +>>> Multimedia2text service test for text data type passed ... +>>> Multimedia2text service test for audio data type passed ... +>>> Multimedia2text service test for video data type passed ... +``` + +## How to Stop/Remove Services + +To stop and remove the Docker containers and images associated with the multimedia-to-text services, follow these steps: + +1. **List Running Containers**: First, list all running Docker containers to identify the ones you want to stop and remove. + + ```bash + docker ps + ``` + +2. **Stop Containers**: Use the `docker stop` command followed by the container IDs or names to stop the running containers. + + ```bash + docker stop + ``` + + If you want to stop all running containers at once, you can use: + + ```bash + docker stop $(docker ps -q) + ``` + +3. **Remove Containers**: After stopping the containers, use the `docker rm` command followed by the container IDs or names to remove them. + + ```bash + docker rm + ``` + + Optionally, you can remove the stopped containers to free up resources: + + ```bash + docker rm $(docker ps -a -q) + ``` + +4. **Remove Images**: If you also want to remove the Docker images, use the `docker rmi` command followed by the image IDs or names. + + ```bash + docker rmi + ``` + + To remove all unused images, you can use: + + ```bash + docker image prune -a + ``` diff --git a/comps/dataprep/multimedia2text/audio2text/Dockerfile b/comps/dataprep/multimedia2text/audio2text/Dockerfile new file mode 100644 index 000000000..57707260f --- /dev/null +++ b/comps/dataprep/multimedia2text/audio2text/Dockerfile @@ -0,0 +1,37 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Use the official Python 3.11 slim image as the base image +FROM python:3.11-slim + +# Create a new user and set up the home directory +RUN useradd -m -s /bin/bash user && \ + mkdir -p /home/user && \ + chown -R user /home/user/ +USER user + +# Set environment variables +ENV LANG=C.UTF-8 +ARG ARCH=cpu + +# Copy the application code and requirements file to the container +COPY comps /home/user/comps +COPY requirements.txt /home/user/requirements.txt + +# Install Python dependencies +RUN pip install --no-cache-dir --upgrade pip && \ + if [ "${ARCH}" = "cpu" ]; then \ + pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu && \ + pip install --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu -r /home/user/requirements.txt ; \ + else \ + pip install --no-cache-dir -r /home/user/requirements.txt ; \ + fi + +# Set the PYTHONPATH environment variable +ENV PYTHONPATH=$PYTHONPATH:/home/user + +# Set the working directory +WORKDIR /home/user/comps/dataprep/multimedia2text/audio2text + +# Define the entry point for the container +ENTRYPOINT ["python", "audio2text.py"] diff --git a/comps/dataprep/multimedia2text/audio2text/audio2text.py b/comps/dataprep/multimedia2text/audio2text/audio2text.py new file mode 100644 index 000000000..650c5704c --- /dev/null +++ b/comps/dataprep/multimedia2text/audio2text/audio2text.py @@ -0,0 +1,88 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import json +import os + +import requests + +from comps import CustomLogger + +# Initialize custom logger +logger = CustomLogger("a2t") +logflag = os.getenv("LOGFLAG", False) + +from comps import ( + Audio2text, + Base64ByteStrDoc, + ServiceType, + TextDoc, + opea_microservices, + register_microservice, + register_statistics, +) + + +# Register the microservice +@register_microservice( + name="opea_service@a2t", + service_type=ServiceType.ASR, + endpoint="/v1/audio/transcriptions", + host="0.0.0.0", + port=9099, + input_datatype=Base64ByteStrDoc, + output_datatype=Audio2text, +) +@register_statistics(names=["opea_service@a2t"]) +async def audio_to_text(audio: Base64ByteStrDoc): + """Convert audio to text and return the transcription. + + Args: + audio (Base64ByteStrDoc): The incoming request containing the audio in base64 format. + + Returns: + TextDoc: The response containing the transcription text. + """ + try: + # Validate the input + if not audio or not audio.byte_str: + raise ValueError("Invalid input: 'audio' or 'audio.byte_str' is missing.") + + byte_str = audio.byte_str + inputs = {"audio": byte_str} + + if logflag: + logger.info(f"Inputs: {inputs}") + + # Send the POST request to the ASR endpoint + response = requests.post(url=f"{a2t_endpoint}/v1/asr", data=json.dumps(inputs), proxies={"http": None}) + response.raise_for_status() # Raise an error for bad status codes + + if logflag: + logger.info(f"Response: {response.json()}") + + # Return the transcription result + return Audio2text(query=response.json()["asr_result"]) # .text + + except requests.RequestException as e: + logger.error(f"Request to ASR endpoint failed: {e}") + raise + except Exception as e: + logger.error(f"An error occurred during audio to text conversion: {e}") + raise + + +if __name__ == "__main__": + try: + # Get the ASR endpoint from environment variables or use the default + a2t_endpoint = os.getenv("A2T_ENDPOINT", "http://localhost:7066") + + # Log initialization message + logger.info("[a2t - router] A2T initialized.") + + # Start the microservice + opea_microservices["opea_service@a2t"].start() + + except Exception as e: + logger.error(f"Failed to start the microservice: {e}") + raise diff --git a/comps/dataprep/multimedia2text/audio2text/check_a2t_server.py b/comps/dataprep/multimedia2text/audio2text/check_a2t_server.py new file mode 100644 index 000000000..8009fc543 --- /dev/null +++ b/comps/dataprep/multimedia2text/audio2text/check_a2t_server.py @@ -0,0 +1,86 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import base64 +import json +import os + +import requests + +# Get the root folder of the current script +root_folder = os.path.dirname(os.path.abspath(__file__)) + + +def audio_to_text(path_to_audio): + """Convert an audio file to text by sending a request to the server. + + Args: + path_to_audio (str): Path to the audio file. + + Returns: + str: The transcribed text. + """ + file_name = os.path.join(root_folder, path_to_audio) + + # Read the audio file and encode it in base64 + with open(file_name, "rb") as f: + audio_base64_str = base64.b64encode(f.read()).decode("utf-8") + + endpoint = "http://localhost:9099/v1/audio/transcriptions" + inputs = {"byte_str": audio_base64_str} + + # Send the POST request to the server + response = requests.post(url=endpoint, data=json.dumps(inputs), proxies={"http": None}) + + # Check if the request was successful + response.raise_for_status() + + # Return the transcribed text + return response.json()["query"] + + +def check_response(response): + """Check the response from the server and print the result. + + Args: + response (str): The transcribed text from the server. + """ + expected_response = "well" + assert response == expected_response, f"Expected '{expected_response}', but got '{response}'" + print("Test passed successfully!") + + +def read_config(): + """Read the configuration parameters from the input file. + + Returns: + argparse.Namespace: Parsed arguments. + """ + # Create an argument parser + parser = argparse.ArgumentParser(description="Process configuration parameters.") + + # Add argument for the audio file path + parser.add_argument( + "--path_to_audio", + help="Location of the audio file that will be converted to text.", + required=False, + default=os.path.join(root_folder, "../data/intel_short.wav"), + ) + + # Parse the arguments + args = parser.parse_args() + + # Return the parsed arguments + return args + + +if __name__ == "__main__": + # Read the configuration parameters + args = read_config() + + # Convert audio to text + response = audio_to_text(args.path_to_audio) + + # Check the response + check_response(response) diff --git a/comps/dataprep/multimedia2text/check_multimedia2text.py b/comps/dataprep/multimedia2text/check_multimedia2text.py new file mode 100644 index 000000000..9aeb735a7 --- /dev/null +++ b/comps/dataprep/multimedia2text/check_multimedia2text.py @@ -0,0 +1,154 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import ast +import base64 +import json +import os + +import requests + +# Get the root folder of the current script +root_folder = os.path.dirname(os.path.abspath(__file__)) + + +def get_base64_str(file_name): + """Convert a file to a base64 encoded string. + + Args: + file_name (str): Path to the file. + + Returns: + str: Base64 encoded string of the file content. + """ + with open(file_name, "rb") as f: + return base64.b64encode(f.read()).decode("utf-8") + + +def post_request(endpoint, inputs): + """Send a POST request to the specified endpoint. + + Args: + endpoint (str): The URL of the endpoint. + inputs (dict): The data to be sent in the request. + + Returns: + requests.Response: The response from the server. + """ + return requests.post(url=endpoint, data=json.dumps(inputs), proxies={"http": None}) + + +def input_data_for_test(document_type): + """Generate input data for testing based on the document type. + + Args: + document_type (str): The type of document ("text", "audio", or "video"). + + Returns: + str: The input data for testing. + + Raises: + ValueError: If the document type is invalid. + """ + if document_type == "text": + input_data = "THIS IS A TEST >>>> and a number of states are starting to adopt them voluntarily special correspondent john delenco of education week reports it takes just 10 minutes to cross through gillette wyoming this small city sits in the northeast corner of the state surrounded by 100s of miles of prairie but schools here in campbell county are on the edge of something big the next generation science standards you are going to build a strand of dna and you are going to decode it and figure out what that dna actually says for christy mathis at sage valley junior high school the new standards are about learning to think like a scientist there is a lot of really good stuff in them every standard is a performance task it is not you know the child needs to memorize these things it is the student needs to be able to do some pretty intense stuff we are analyzing we are critiquing we are." + elif document_type == "audio": + input_data = get_base64_str(os.path.join(root_folder, "data/intel_short.wav")) + elif document_type == "video": + input_data = get_base64_str(os.path.join(root_folder, "data/intel_short.mp4")) + else: + raise ValueError("Invalid document type") + + return input_data + + +def test_whisper_service(): + """Test the Whisper service. + + Raises: + AssertionError: If the service does not return a 200 status code. + """ + print("Running test: Whisper service") + document_type = "audio" + endpoint = "http://localhost:7066/v1/asr" + inputs = {"audio": input_data_for_test(document_type)} + response = post_request(endpoint, inputs) + assert ( + response.status_code == 200 + ), f"Whisper service failed to get response from the server. Status code: {response.status_code}" + + # If the response status code is 200, print "Test passed" + print(">>> Whisper service Test Passed ... ") + print() + + +def test_audio2text(): + """Test the Audio2Text service. + + Raises: + AssertionError: If the service does not return a 200 status code. + """ + print("Running test: Audio2Text service") + document_type = "audio" + endpoint = "http://localhost:9099/v1/audio/transcriptions" + inputs = {"byte_str": input_data_for_test(document_type)} + response = post_request(endpoint, inputs) + assert ( + response.status_code == 200 + ), f"Audio2Text service failed to get response from the server. Status code: {response.status_code}" + + # If the response status code is 200, print "Test passed" + print(">>> Audio2Text service Test Passed ... ") + print() + + +def test_video2text(): + """Test the Video2Text service. + + Raises: + AssertionError: If the service does not return a 200 status code. + """ + print("Running test: Video2Text service") + document_type = "video" + endpoint = "http://localhost:7078/v1/video2audio" + inputs = {"byte_str": input_data_for_test(document_type)} + response = post_request(endpoint, inputs) + assert ( + response.status_code == 200 + ), f"Video2Text service failed to get response from the server. Status code: {response.status_code}" + + # If the response status code is 200, print "Test passed" + print(">>> Video2Text service Test Passed ... ") + print() + + +def test_multimedia2text_data(): + """Test the multimedia2text service for different document types. + + Raises: + AssertionError: If the service does not return a 200 status code. + """ + print("Running test: Multimedia2text service") + for document_type in ["text", "audio", "video"]: + endpoint = "http://localhost:7079/v1/multimedia2text" + inputs = {document_type: input_data_for_test(document_type)} + response = post_request(endpoint, inputs) + assert ( + response.status_code == 200 + ), f"{document_type} service failed to get response from the server. Status code: {response.status_code}" + + # If the response status code is 200, print "Test passed" + print(f">>> Multimedia2text service test for {document_type} data type passed ... ") + print() + + +if __name__ == "__main__": + # Run the tests and print the results + try: + test_whisper_service() + test_audio2text() + test_video2text() + test_multimedia2text_data() + + except AssertionError as e: + print(f"Test failed: {e}") diff --git a/comps/dataprep/multimedia2text/data/README.md b/comps/dataprep/multimedia2text/data/README.md new file mode 100644 index 000000000..89330dbac --- /dev/null +++ b/comps/dataprep/multimedia2text/data/README.md @@ -0,0 +1,31 @@ +# Test Data for Document Summarization + +## Overview + +This document provides information about the test data used for the Document Summarization application. + +## Source of Test Data + +The data used for testing originated from the following video: + +[YouTube Video](https://www.youtube.com/watch?v=HUpnCtJRTg4) + +## Description of Test Data + +1. **Video File**: We extracted a 1-second segment from the above video and saved it as `intel_short.mp4`. +2. **Audio File**: The audio was extracted from the `intel_short.mp4` video file and saved as `intel_short.wav`. + +These files are used to test the functionality of the Document Summarization application, including the conversion of multimedia content to text. + +## Files + +- `intel_short.mp4`: A 1-second video segment extracted from the YouTube video. +- `intel_short.wav`: An audio file converted from the `intel_short.mp4` video file. + +## Usage + +These files can be used to validate the multimedia-to-text services provided by the Document Summarization application. Ensure that the files are placed in the appropriate directory as specified in the application's configuration. + +## License + +The original video content is subject to the terms and conditions of YouTube and the content creator. The extracted segments are used solely for testing and validation purposes. diff --git a/comps/dataprep/multimedia2text/data/intel_short.mp4 b/comps/dataprep/multimedia2text/data/intel_short.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..6b72f4122a3bea69c50ce7442f3ed54167c8d8af GIT binary patch literal 74987 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
!hBu8 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%0A9YnQ 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 P^*6=M$Ic67=Oyqz_fFVE literal 0 HcmV?d00001 diff --git a/comps/dataprep/multimedia2text/data/intel_short.wav b/comps/dataprep/multimedia2text/data/intel_short.wav new file mode 100644 index 0000000000000000000000000000000000000000..21657414d1d9f3a152f2099c316fb16b47b9ae55 GIT binary patch literal 3596 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 literal 0 HcmV?d00001 diff --git a/comps/dataprep/multimedia2text/multimedia2text.py b/comps/dataprep/multimedia2text/multimedia2text.py new file mode 100644 index 000000000..68f0181c9 --- /dev/null +++ b/comps/dataprep/multimedia2text/multimedia2text.py @@ -0,0 +1,90 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import json +import os + +import requests + +from comps import CustomLogger + +# Initialize custom logger +logger = CustomLogger("multimedia2text") + +from comps import Audio2text, DocSumDoc, ServiceType, opea_microservices, register_microservice, register_statistics + + +# Register the microservice +@register_microservice( + name="opea_service@multimedia2text", + service_type=ServiceType.ASR, + endpoint="/v1/multimedia2text", + host="0.0.0.0", + port=7079, + input_datatype=DocSumDoc, + output_datatype=Audio2text, +) +@register_statistics(names=["opea_service@multimedia2text"]) +async def audio_to_text(input: DocSumDoc): + """Convert video or audio input to text using external services. + + Args: + input (DocSumDoc): Input document containing video, audio, or text data. + + Returns: + Audio2text: Object containing the ASR result or input text. + """ + response_to_return = None + + # Process video input + if input.video is not None: + logger.info(f"Processing video input at {v2a_endpoint}/v1/video2audio") + inputs = {"byte_str": input.video} + response = requests.post(url=f"{v2a_endpoint}/v1/video2audio", data=json.dumps(inputs), proxies={"http": None}) + response.raise_for_status() # Ensure the request was successful + input.audio = response.json().get("byte_str") + if input.audio is None: + logger.error("Failed to extract audio from video") + raise ValueError("Failed to extract audio from video") + + # Process audio input + if input.audio is not None: + logger.info(f"Processing audio input at {a2t_endpoint}/v1/asr") + inputs = {"audio": input.audio} + response = requests.post(url=f"{a2t_endpoint}/v1/asr", data=json.dumps(inputs), proxies={"http": None}) + response.raise_for_status() # Ensure the request was successful + response_to_return = response.json().get("asr_result") + if response_to_return is None: + logger.error("Failed to get ASR result from audio") + raise ValueError("Failed to get ASR result from audio") + + # Process text input + if input.text is not None: + logger.info("Processing text input") + response_to_return = input.text + + if response_to_return is None: + logger.warning("No valid input provided") + response_to_return = "No input" + else: + logger.info("Data Processing completeed") + + return Audio2text(query=response_to_return) + + +if __name__ == "__main__": + try: + # Get the V2T endpoint from environment variables or use the default + v2a_endpoint = os.getenv("V2A_ENDPOINT", "http://localhost:7078") + # Get the A2T endpoint from environment variables or use the default + a2t_endpoint = os.getenv("A2T_ENDPOINT", "http://localhost:7066") + + # Log initialization message + logger.info("[multimedia2text - router] multimedia2text initialized.") + + # Start the microservice + opea_microservices["opea_service@multimedia2text"].start() + + except Exception as e: + logger.error(f"Failed to start the multimedia2text microservice: {e}") + raise diff --git a/comps/dataprep/multimedia2text/video2audio/Dockerfile b/comps/dataprep/multimedia2text/video2audio/Dockerfile new file mode 100644 index 000000000..32b2fe8ee --- /dev/null +++ b/comps/dataprep/multimedia2text/video2audio/Dockerfile @@ -0,0 +1,31 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Use the official Python 3.11 slim image as the base image +FROM python:3.11-slim + +# Set environment variables +ENV LANG=C.UTF-8 + +# Install necessary packages +RUN apt-get update -y && apt-get install -y --no-install-recommends --fix-missing \ + build-essential \ + libgl1-mesa-glx \ + libjemalloc-dev + +# Create a directory for the user +RUN mkdir -p /home/user + +# Copy the application code to the container +COPY comps /home/user/comps +COPY requirements.txt /home/user/requirements.txt +COPY ./comps/dataprep/multimedia2text/video2audio/video2audio_microservice.py /home/user/video2audio_microservice.py + +# Install Python dependencies +RUN python -m pip install --no-cache-dir -r /home/user/requirements.txt moviepy + +# Set the working directory +WORKDIR /home/user/ + +# Define the entry point for the container +ENTRYPOINT ["python", "video2audio_microservice.py"] diff --git a/comps/dataprep/multimedia2text/video2audio/check_v2a_microserver.py b/comps/dataprep/multimedia2text/video2audio/check_v2a_microserver.py new file mode 100644 index 000000000..d8499faa1 --- /dev/null +++ b/comps/dataprep/multimedia2text/video2audio/check_v2a_microserver.py @@ -0,0 +1,92 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import base64 +import json +import os + +import requests + +# Get the root folder of the current script +root_folder = os.path.dirname(os.path.abspath(__file__)) + + +def video_to_audio(path_to_video): + """Convert a video file to an audio file in base64 format by sending a request to the server. + + Args: + path_to_video (str): Path to the video file. + + Returns: + str: Base64 encoded audio file. + """ + file_name = os.path.join(root_folder, path_to_video) + + # Read the video file and encode it in base64 + with open(file_name, "rb") as f: + video_base64_str = base64.b64encode(f.read()).decode("utf-8") + + # Define the endpoint and payload + endpoint = "http://localhost:7078/v1/video2audio" + inputs = {"byte_str": video_base64_str} + + # Send the POST request to the server + response = requests.post(url=endpoint, data=json.dumps(inputs), proxies={"http": None}) + + # Check if the request was successful + response.raise_for_status() + + # Extract the base64 encoded audio from the response + audio_base64 = response.json()["byte_str"] + + return audio_base64 + + +def read_config(): + """Function to read the configuration parameters from the input file. + Returns the parsed arguments. + + Returns: + argparse.Namespace: Parsed arguments. + """ + # Create an argument parser + parser = argparse.ArgumentParser(description="Process configuration parameters.") + + # Add argument for the video file path + parser.add_argument( + "--path_to_video", + help="Location of the video file that will be converted to audio.", + required=False, + default=os.path.join(root_folder, "../data/intel_short.mp4"), + ) + + # Add argument for the audio file path + parser.add_argument( + "--path_to_audio", + help="Location to save the extracted audio file.", + required=False, + default=os.path.join(root_folder, "converted_audio.wav"), + ) + + # Parse the arguments + args = parser.parse_args() + + # Return the parsed arguments + return args + + +if __name__ == "__main__": + # Read the configuration parameters + args = read_config() + + # Extract audio from video + audio_base64 = video_to_audio(args.path_to_video) + + # Save the extracted audio to a file + with open(args.path_to_audio, "wb") as f: + f.write(base64.b64decode(audio_base64)) + + print("========= Audio file saved as ======") + print(args.path_to_audio) + print("====================================") diff --git a/comps/dataprep/multimedia2text/video2audio/video2audio.py b/comps/dataprep/multimedia2text/video2audio/video2audio.py new file mode 100644 index 000000000..0f454f1c3 --- /dev/null +++ b/comps/dataprep/multimedia2text/video2audio/video2audio.py @@ -0,0 +1,88 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import base64 +import uuid +from os import path, remove + +from moviepy.editor import VideoFileClip + +# Get the root folder of the current script +root_folder = path.dirname(path.abspath(__file__)) + + +class Video2Audio: + """Class to convert video files to audio files and handle base64 encoding.""" + + def __init__(self): + pass + + def validate_file_exists(self, file_path): + """Validate if the given file exists. + + Args: + file_path (str): Path to the file. + + Raises: + FileNotFoundError: If the file does not exist. + """ + if not path.isfile(file_path): + raise FileNotFoundError(f"The file {file_path} does not exist.") + + def convert_video_to_audio(self, path_to_video, audio_file_name): + """Extract mp3 audio file from mp4 video file. + + Args: + path_to_video (str): Path to the video file. + audio_file_name (str): Path to save the extracted audio file. + """ + # Validate the video file exists + self.validate_file_exists(path_to_video) + + # Extract audio from video + clip = VideoFileClip(path_to_video) + clip.audio.write_audiofile(audio_file_name) + print(f"Audio extracted and saved to {audio_file_name}") + + def convert_base64(self, file_name): + """Convert a file to a base64 encoded string and remove the file. + + Args: + file_name (str): Path to the file to be encoded. + + Returns: + str: Base64 encoded string of the file content. + """ + # Validate the file exists + self.validate_file_exists(file_name) + + # Read the file and encode it in base64 + with open(file_name, "rb") as f: + base64_str = base64.b64encode(f.read()).decode("utf-8") + + # Remove the file after encoding + remove(file_name) + + return base64_str + + def convert_video_to_audio_base64(self, video_file_name): + """Convert a video file to an audio file and return the audio file as a base64 encoded string. + + Args: + video_file_name (str): Path to the video file. + + Returns: + str: Base64 encoded string of the extracted audio file. + """ + # Generate a unique identifier for the audio file + uid = str(uuid.uuid4()) + audio_file_name = uid + ".mp3" + + # Convert the video to audio + self.convert_video_to_audio(video_file_name, audio_file_name) + + # Convert the audio file to a base64 encoded string + base64_str = self.convert_base64(audio_file_name) + + return base64_str diff --git a/comps/dataprep/multimedia2text/video2audio/video2audio_microservice.py b/comps/dataprep/multimedia2text/video2audio/video2audio_microservice.py new file mode 100644 index 000000000..f1b4b906a --- /dev/null +++ b/comps/dataprep/multimedia2text/video2audio/video2audio_microservice.py @@ -0,0 +1,88 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import base64 +import json +import os +import uuid + +import requests + +from comps import ( + Base64ByteStrDoc, + CustomLogger, + ServiceType, + opea_microservices, + register_microservice, + register_statistics, +) +from comps.dataprep.multimedia2text.video2audio.video2audio import Video2Audio + +# Initialize custom logger +logger = CustomLogger("video2audio") +logflag = os.getenv("LOGFLAG", False) + + +# Register the microservice +@register_microservice( + name="opea_service@video2audio", + service_type=ServiceType.DATAPREP, + endpoint="/v1/video2audio", + host="0.0.0.0", + port=7078, + input_datatype=Base64ByteStrDoc, + output_datatype=Base64ByteStrDoc, +) +@register_statistics(names=["opea_service@video2audio"]) +async def audio_to_text(request: Base64ByteStrDoc): + """Convert video to audio and return the result in base64 format. + + Args: + request (Base64ByteStrDoc): The incoming request containing the video in base64 format. + + Returns: + Base64ByteStrDoc: The response containing the audio in base64 format. + """ + try: + # Generate a unique identifier for the video file + uid = str(uuid.uuid4()) + file_name = uid + ".mp4" + + logger.info("Received request for video to audio conversion.") + byte_str = request.byte_str + + # Decode the base64 string and save it as a video file + with open(file_name, "wb") as f: + f.write(base64.b64decode(byte_str)) + + # Convert the video file to audio and get the result in base64 format + response = v2a.convert_video_to_audio_base64(file_name) + + # Remove the temporary video file + os.remove(file_name) + + logger.info("Successfully converted video to audio.") + return Base64ByteStrDoc(byte_str=response) + + except requests.RequestException as e: + logger.error(f"Request to video-to-audio endpoint failed: {e}") + raise + except Exception as e: + logger.error(f"An error occurred during video to audio conversion: {e}") + raise + + +if __name__ == "__main__": + try: + # Initialize the Video2Audio instance + v2a = Video2Audio() + + # Log initialization message + logger.info("[video2audio - router] VIDEO2AUDIO initialized.") + + # Start the microservice + opea_microservices["opea_service@video2audio"].start() + + except Exception as e: + logger.error(f"Failed to start the microservice: {e}") + raise diff --git a/tests/asr/test_asr_whisper.sh b/tests/asr/test_asr_whisper.sh index 226898a8c..d0928cf34 100644 --- a/tests/asr/test_asr_whisper.sh +++ b/tests/asr/test_asr_whisper.sh @@ -10,14 +10,17 @@ ip_address=$(hostname -I | awk '{print $1}') function build_docker_images() { cd $WORKPATH echo $(pwd) - docker build --no-cache -t opea/whisper:comps -f comps/asr/whisper/dependency/Dockerfile . + docker build --no-cache -t opea/whisper:comps --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/asr/whisper/dependency/Dockerfile . + if [ $? -ne 0 ]; then echo "opea/whisper built fail" exit 1 else echo "opea/whisper built successful" fi - docker build --no-cache -t opea/asr:comps -f comps/asr/whisper/Dockerfile . + + docker build --no-cache -t opea/asr:comps --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f comps/asr/whisper/Dockerfile . + if [ $? -ne 0 ]; then echo "opea/asr built fail" exit 1 @@ -30,7 +33,7 @@ function start_service() { unset http_proxy docker run -d --name="test-comps-asr-whisper" -e http_proxy=$http_proxy -e https_proxy=$https_proxy -p 7066:7066 --ipc=host opea/whisper:comps docker run -d --name="test-comps-asr" -e ASR_ENDPOINT=http://$ip_address:7066 -e http_proxy=$http_proxy -e https_proxy=$https_proxy -p 9089:9099 --ipc=host opea/asr:comps - sleep 3m + sleep 60s } function validate_microservice() { diff --git a/tests/dataprep/test_dataprep_multimedia.sh b/tests/dataprep/test_dataprep_multimedia.sh new file mode 100644 index 000000000..30592c86a --- /dev/null +++ b/tests/dataprep/test_dataprep_multimedia.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# set -xe + +IMAGE_REPO=${IMAGE_REPO:-"opea"} +IMAGE_TAG=${IMAGE_TAG:-"latest"} +echo "REGISTRY=IMAGE_REPO=${IMAGE_REPO}" +echo "TAG=IMAGE_TAG=${IMAGE_TAG}" + +WORKPATH=$(dirname "$PWD") +LOG_PATH="$WORKPATH/tests" + +host_ip=$(hostname -I | awk '{print $1}') + +export REGISTRY=${IMAGE_REPO} +export TAG=${IMAGE_TAG} +export no_proxy="${no_proxy},${host_ip}" + +export V2A_SERVICE_HOST_IP=${host_ip} +export V2A_ENDPOINT=http://$host_ip:7078 + +export A2T_ENDPOINT=http://$host_ip:7066 +export A2T_SERVICE_HOST_IP=${host_ip} +export A2T_SERVICE_PORT=9099 + +export DATA_ENDPOINT=http://$host_ip:7079 +export DATA_SERVICE_HOST_IP=${host_ip} +export DATA_SERVICE_PORT=7079 + +# Get the root folder of the current script +ROOT_FOLDER=$(dirname "$(readlink -f "$0")") + +function build_docker_images() { + cd $WORKPATH + echo "Current working directory: $(pwd)" + + # Array of Docker build configurations + declare -A docker_builds=( + ["opea/whisper:comps"]="comps/asr/whisper/dependency/Dockerfile" + ["opea/a2t:comps"]="comps/dataprep/multimedia2text/audio2text/Dockerfile" + ["opea/v2a:comps"]="comps/dataprep/multimedia2text/video2audio/Dockerfile" + ["opea/multimedia2text:comps"]="comps/dataprep/multimedia2text/Dockerfile" + ) + + # Loop through the array and build each Docker image + for image in "${!docker_builds[@]}"; do + dockerfile=${docker_builds[$image]} + echo "Building Docker image: $image from Dockerfile: $dockerfile" + + docker build --no-cache -t $image --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy -f $dockerfile . + + if [ $? -ne 0 ]; then + echo "$image build failed" + exit 1 + else + echo "$image build successful" + fi + done + + # List Docker images and wait for 1 second + docker images && sleep 1s +} + +function start_services() { + + docker run -d -p 7066:7066 --name="test-comps-mm-whisper-service" --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy opea/whisper:comps + if [ $? -ne 0 ]; then + echo "opea/whisper service fail to start" + exit 1 + else + echo "opea/whisper start successful" + fi + + + docker run -d -p 9099:9099 --name="test-comps-mm-a2t-service" --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e A2T_ENDPOINT=http://$host_ip:7066 opea/a2t:comps + if [ $? -ne 0 ]; then + echo "opea/a2t service fail to start" + exit 1 + else + echo "opea/a2t start successful" + fi + + docker run -d -p 7078:7078 --name="test-comps-mm-v2a-service" --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy opea/v2a:comps + if [ $? -ne 0 ]; then + echo "opea/v2a service fail to start" + exit 1 + else + echo "opea/v2a start successful" + fi + + + docker run -d -p 7079:7079 --name="test-comps-mm-multimedia2text-service" --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy \ + -e A2T_ENDPOINT=http://$host_ip:7066 \ + -e V2A_ENDPOINT=http://$host_ip:7078 \ + opea/multimedia2text:comps + + if [ $? -ne 0 ]; then + echo "opea/multimedia2text service fail to start" + exit 1 + else + echo "opea/multimedia2text start successful" + fi + + sleep 120s + +} + +function validate_services() { + local URL="$1" + local EXPECTED_RESULT="$2" + local SERVICE_NAME="$3" + local DOCKER_NAME="$4" + local INPUT_DATA="$5" + + local HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST -d "$INPUT_DATA" -H 'Content-Type: application/json' "$URL") + + echo "===========================================" + + if [ "$HTTP_STATUS" -eq 200 ]; then + echo "[ $SERVICE_NAME ] HTTP status is 200. Checking content..." + + local CONTENT=$(curl -s -X POST -d "$INPUT_DATA" -H 'Content-Type: application/json' "$URL" | tee ${LOG_PATH}/${SERVICE_NAME}.log) + + if echo "$CONTENT" | grep -q "$EXPECTED_RESULT"; then + echo "[ $SERVICE_NAME ] Content is as expected." + else + echo "EXPECTED_RESULT==> $EXPECTED_RESULT" + echo "CONTENT==> $CONTENT" + echo "[ $SERVICE_NAME ] Content does not match the expected result: $CONTENT" + docker logs ${DOCKER_NAME} >> ${LOG_PATH}/${SERVICE_NAME}.log + exit 1 + + fi + else + echo "[ $SERVICE_NAME ] HTTP status is not 200. Received status was $HTTP_STATUS" + docker logs ${DOCKER_NAME} >> ${LOG_PATH}/${SERVICE_NAME}.log + exit 1 + fi + sleep 1s + +} + +get_base64_str() { + local file_name=$1 + base64 -w 0 "$file_name" +} + +# Function to generate input data for testing based on the document type +input_data_for_test() { + local document_type=$1 + case $document_type in + ("text") + echo "THIS IS A TEST >>>> and a number of states are starting to adopt them voluntarily special correspondent john delenco of education week reports it takes just 10 minutes to cross through gillette wyoming this small city sits in the northeast corner of the state surrounded by 100s of miles of prairie but schools here in campbell county are on the edge of something big the next generation science standards you are going to build a strand of dna and you are going to decode it and figure out what that dna actually says for christy mathis at sage valley junior high school the new standards are about learning to think like a scientist there is a lot of really good stuff in them every standard is a performance task it is not you know the child needs to memorize these things it is the student needs to be able to do some pretty intense stuff we are analyzing we are critiquing we are." + ;; + ("audio") + # get_base64_str "$ROOT_FOLDER/data/test.wav" + get_base64_str "$WORKPATH/comps/dataprep/multimedia2text/data/intel_short.wav" + ;; + ("video") + # get_base64_str "$ROOT_FOLDER/data/test.mp4" + get_base64_str "$WORKPATH/comps/dataprep/multimedia2text/data/intel_short.mp4" + ;; + (*) + echo "Invalid document type" >&2 + exit 1 + ;; + esac +} + +function validate_microservices() { + # Check if the microservices are running correctly. + + # whisper microservice + ulimit -s 65536 + validate_services \ + "${host_ip}:7066/v1/asr" \ + '{"asr_result":"well"}' \ + "whisper-service" \ + "whisper-service" \ + "{\"audio\": \"$(input_data_for_test "audio")\"}" + + # Audio2Text service + validate_services \ + "${host_ip}:9099/v1/audio/transcriptions" \ + '"query":"well"' \ + "a2t" \ + "a2t-service" \ + "{\"byte_str\": \"$(input_data_for_test "audio")\"}" + + # Video2Audio service + validate_services \ + "${host_ip}:7078/v1/video2audio" \ + "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjI5LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAIAAAN3wAtLS0tLS0tLS0tLS1LS0tLS0tLS0tLS0tpaWlpaWlpaWlpaWlph4eHh4eHh4eHh4eHpaWlpaWlpaWlpaWlpcPDw8PDw8PDw8PDw+Hh4eHh4eHh4eHh4eH///////////////8AAAAATGF2YzU4LjU0AAAAAAAAAAAAAAAAJAYwAAAAAAAADd9L18KaAAAAAAAAAAAAAAAAAAAAAP/7kGQAAAMhClSVMEACMOAabaCMAREA" \ + "v2a" \ + "v2a-service" \ + "{\"byte_str\": \"$(input_data_for_test "video")\"}" + + # Docsum Data service - video + validate_services \ + "${host_ip}:7079/v1/multimedia2text" \ + '"query":"well"' \ + "multimedia2text-service" \ + "multimedia2text" \ + "{\"video\": \"$(input_data_for_test "video")\"}" + + # Docsum Data service - audio + validate_services \ + "${host_ip}:7079/v1/multimedia2text" \ + '"query":"well"' \ + "multimedia2text-service" \ + "multimedia2text" \ + "{\"audio\": \"$(input_data_for_test "audio")\"}" + + # Docsum Data service - text + validate_services \ + "${host_ip}:7079/v1/multimedia2text" \ + "THIS IS A TEST >>>> and a number of states are starting to adopt them voluntarily special correspondent john delenco" \ + "multimedia2text-service" \ + "multimedia2text" \ + "{\"text\": \"$(input_data_for_test "text")\"}" + +} + +function stop_docker() { + cid=$(docker ps -aq --filter "name=test-comps-mm-*") + if [[ ! -z "$cid" ]]; then docker stop $cid && docker rm $cid && sleep 1s; fi + echo "All specified services have been stopped and removed." +} + +function main() { + + stop_docker + if [[ "$IMAGE_REPO" == "opea" ]]; then build_docker_images; fi + start_services + validate_microservices + stop_docker + echo y | docker system prune +} + +main