From f5d8056eada308f5d4be222dff84fc8b2e2f9fef Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 22 Sep 2023 21:27:15 +0200 Subject: [PATCH 001/331] fixed bug appearing torch installation using pip --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 247e608..dd6ef61 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ The following command will pull and install the latest commit from this reposito - **PyTorch version**: Python 1.11.0 - **CUDA version**: Cuda-toolkit 11.3.1 +In order to run `scraibe` properly, it is recommended to reinstall `pytoch` using: + + pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 torchaudio==0.11.0+cu113 --extra-index-url https://download.pytorch.org/whl/cu113 + +This ensures that the right torchaudio version is installed. Important: For the `Pyannote` model, you need to be granted access to Hugging Face. Check the [Pyannote model page](https://huggingface.co/pyannote/speaker-diarization) to get access to the model. From ef7a4273e6aaff0d0b0f2be4ee13815381808795 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 22 Sep 2023 21:27:41 +0200 Subject: [PATCH 002/331] fixed versioning whichs brokes do to pypi issues --- scraibe/version.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scraibe/version.py b/scraibe/version.py index b3cf626..bce39ee 100644 --- a/scraibe/version.py +++ b/scraibe/version.py @@ -3,10 +3,9 @@ import subprocess as sp MAJOR = 0 MINOR = 1 -MICRO = 0 -MICRO_POST = 0 +MICRO = 1 ISRELEASED = False -VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO, MICRO_POST) +VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO) # Return the git revision as a string # taken from numpy/numpy From 8e90b4c8a04ed3c46222b374dfa40706ad991e6f Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Tue, 26 Sep 2023 09:57:42 +0200 Subject: [PATCH 003/331] Update .pyannotetoken --- scraibe/.pyannotetoken | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/.pyannotetoken b/scraibe/.pyannotetoken index 42ba269..8b13789 100644 --- a/scraibe/.pyannotetoken +++ b/scraibe/.pyannotetoken @@ -1 +1 @@ -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 \ No newline at end of file + From 4ee4ad1ff5a558292e72ac912d94701aaa82884e Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Tue, 26 Sep 2023 09:57:57 +0200 Subject: [PATCH 004/331] Update .pyannotetoken From f2d2b7219d2dc639217ceb859ab8b55451bd153f Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Tue, 26 Sep 2023 10:00:16 +0200 Subject: [PATCH 005/331] Delete scraibe/.pyannotetoken --- scraibe/.pyannotetoken | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scraibe/.pyannotetoken diff --git a/scraibe/.pyannotetoken b/scraibe/.pyannotetoken deleted file mode 100644 index 8b13789..0000000 --- a/scraibe/.pyannotetoken +++ /dev/null @@ -1 +0,0 @@ - From 73dd9e7f11f864c6ce64620ca1ab6521bf97da5c Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Tue, 26 Sep 2023 10:00:46 +0200 Subject: [PATCH 006/331] Delete scraibe/.pyannotetoken --- scraibe/.pyannotetoken | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scraibe/.pyannotetoken diff --git a/scraibe/.pyannotetoken b/scraibe/.pyannotetoken deleted file mode 100644 index 42ba269..0000000 --- a/scraibe/.pyannotetoken +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file From 24e8321b8ce8b4cd2bca4debb8c8352b2cefa1ab Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Tue, 26 Sep 2023 10:01:17 +0200 Subject: [PATCH 007/331] Delete scraibe/.pyannotetoken --- scraibe/.pyannotetoken | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scraibe/.pyannotetoken diff --git a/scraibe/.pyannotetoken b/scraibe/.pyannotetoken deleted file mode 100644 index 42ba269..0000000 --- a/scraibe/.pyannotetoken +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file From e6db5e5bb97d5ed44730d405b76c7fc1a5488f63 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 26 Sep 2023 08:11:24 +0000 Subject: [PATCH 008/331] removed wrong import --- scraibe/app/gradio_app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scraibe/app/gradio_app.py b/scraibe/app/gradio_app.py index fa3e8fb..f86d027 100644 --- a/scraibe/app/gradio_app.py +++ b/scraibe/app/gradio_app.py @@ -34,7 +34,6 @@ Usage: import json import os -from tkinter import CURRENT import gradio as gr from tqdm import tqdm From 135acab0cedef62a12b9d8d3801360db447bf9cc Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 26 Sep 2023 08:14:40 +0000 Subject: [PATCH 009/331] removed wrong input --- scraibe/transcript_exporter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index ac037a1..3c68d87 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -1,7 +1,5 @@ import json import time -from traceback import print_stack - from typing import Union From b612f1004b190cd3eb3b1922cda8dccff06e76c6 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 26 Sep 2023 09:50:59 +0000 Subject: [PATCH 010/331] fixed image not showing up --- scraibe/app/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/app/header.html b/scraibe/app/header.html index f174bfd..5fcedb6 100644 --- a/scraibe/app/header.html +++ b/scraibe/app/header.html @@ -54,7 +54,7 @@

ScrAIbe

From ae9c24f92907036d62d1384add35fe9c1889d952 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 27 Sep 2023 08:40:58 +0000 Subject: [PATCH 011/331] fixed wrong logo import --- scraibe/app/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/app/header.html b/scraibe/app/header.html index 5fcedb6..21a4464 100644 --- a/scraibe/app/header.html +++ b/scraibe/app/header.html @@ -54,7 +54,7 @@

ScrAIbe

From 7b8019c57ffc83d7136c4a9ad0bfa84c9f91690c Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 29 Sep 2023 11:57:31 +0200 Subject: [PATCH 012/331] fixed gradio app lodo dismissed --- scraibe/app/Logo_KIDA_bmel_green.svg | 171 --------------------------- scraibe/app/gradio_app.py | 6 +- scraibe/app/header.html | 2 +- scraibe/app/logo.svg | 37 ++++++ 4 files changed, 43 insertions(+), 173 deletions(-) delete mode 100644 scraibe/app/Logo_KIDA_bmel_green.svg create mode 100644 scraibe/app/logo.svg diff --git a/scraibe/app/Logo_KIDA_bmel_green.svg b/scraibe/app/Logo_KIDA_bmel_green.svg deleted file mode 100644 index c59c351..0000000 --- a/scraibe/app/Logo_KIDA_bmel_green.svg +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scraibe/app/gradio_app.py b/scraibe/app/gradio_app.py index f86d027..2b6bbc4 100644 --- a/scraibe/app/gradio_app.py +++ b/scraibe/app/gradio_app.py @@ -353,6 +353,10 @@ def gradio_Interface(model : Scraibe = None): # Define components hname = os.path.join(CURRENT_PATH, "header.html") header = open(hname, "r").read() + + # ugly hack to get the logo to work + header = header.replace("/file=logo.svg", f"/file={CURRENT_PATH}/logo.svg" ) + gr.HTML(header, visible= True, show_label=False) with gr.Row(): @@ -384,7 +388,7 @@ def gradio_Interface(model : Scraibe = None): interactive= True, visible= False) video1 = gr.Video(source="upload", type="filepath", label="Upload Video", interactive= True, visible= False) - video2 = gr.Video(source="webcam", label="Record Video", type="filepath", + video2 = gr.Video(source="webcam", label="Record Video", type="filepath",include_audio= True, interactive= True, visible= False) file_in = gr.Files(label="Upload File or Files", interactive= True, visible= False) diff --git a/scraibe/app/header.html b/scraibe/app/header.html index 21a4464..4b12136 100644 --- a/scraibe/app/header.html +++ b/scraibe/app/header.html @@ -54,7 +54,7 @@

ScrAIbe

diff --git a/scraibe/app/logo.svg b/scraibe/app/logo.svg new file mode 100644 index 0000000..54d12d7 --- /dev/null +++ b/scraibe/app/logo.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8cfc4ac9dba2f26b00c32153edd02e46d5038b56 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 29 Sep 2023 12:02:25 +0200 Subject: [PATCH 013/331] updated keywords --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 75d48dd..64d30b9 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ if __name__ == "__main__": 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10'], - keywords = ['transcription', 'speech recognition', 'whisper', 'pyannote', 'audio', + keywords = ['transcription', 'speech recognition', 'whisper', 'pyannote', 'audio', 'ScrAIbe', 'scraibe', 'speech-to-text', 'speech-to-text transcription', 'speech-to-text recognition', 'voice-to-speech'], package_data={'scraibe.app' : ["*.html", "*.svg"]}, From 9db419cf540e619fa475b50f3a4f6e06000469da Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 29 Sep 2023 12:29:13 +0200 Subject: [PATCH 014/331] changed output str for multiple file inputs --- scraibe/app/gradio_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scraibe/app/gradio_app.py b/scraibe/app/gradio_app.py index 2b6bbc4..cf80b7e 100644 --- a/scraibe/app/gradio_app.py +++ b/scraibe/app/gradio_app.py @@ -117,7 +117,7 @@ class GradioTranscriptionInterface: out = '' out_dict = {} for i, r in enumerate(result): - out += f"TRANSCRIPT {i} FOR ({source_names[i]}):\n\n" + out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" out += str(r) out += "\n\n" @@ -158,7 +158,7 @@ class GradioTranscriptionInterface: out = '' for i, res in enumerate(result): - out += f"TRANSCRIPT {i} FOR ({source_names[i]}):\n\n" + out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" out += str(res) out += "\n\n" From 98f41beab58430bed9fe25afa264cb3cdd656dda Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 29 Sep 2023 13:37:16 +0200 Subject: [PATCH 015/331] changed dockerfile to the docker hub version --- Dockerfile | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 154982b..53583d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,17 @@ #pytorch Image FROM pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime + +# Labels + +LABEL maintainer="Jacob Schmieder" +LABEL email="Jacob.Schmieder@dbfz.de" +LABEL version="0.1.2" +LABEL description="Scraibe is a tool for automatic speech recognition and speaker diarization. \ + It is based on the Hugging Face Transformers library and the Pyannote library. \ + It is designed to be used with the Whisper model, a lightweight model for automatic \ + speech recognition and speaker diarization." +LABEL url="https://github.com/JSchmie/ScrAIbe" + # Install dependencies WORKDIR /app ARG hf_token @@ -10,17 +22,23 @@ ENV AUTOT_CACHE /app/models ENV PYANNOTE_CACHE /app/models/pyannote #Copy all necessary files COPY requirements.txt /app/requirements.txt -COPY scraibe /app/Scraibe +COPY README.md /app/README.md +COPY models /app/models +COPY scraibe /app/scraibe COPY setup.py /app/setup.py #Installing all necessary Dependencies and Running the Application with a personalised Hugging-Face-Token + +RUN apt update && apt-get install -y libsm6 libxrender1 libfontconfig1 +RUN conda update --all + RUN conda install pip RUN conda install -y ffmpeg RUN conda install -c conda-forge libsndfile RUN pip install torchaudio==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html RUN pip install /app/ RUN pip install markupsafe==2.0.1 --force-reinstall -RUN Scraibe --hf_token $hf_token +RUN scraibe --whisper-model-name small # Expose port EXPOSE 7860 # Run the application -ENTRYPOINT ["scraibe"] +ENTRYPOINT ["scraibe" ,"--whisper-model-name", "small"] From 994dfadf178f631dcfaa9beb3b0537535b6d094d Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 4 Oct 2023 14:59:15 +0200 Subject: [PATCH 016/331] updated docker file --- Dockerfile | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 53583d1..95db093 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime LABEL maintainer="Jacob Schmieder" LABEL email="Jacob.Schmieder@dbfz.de" -LABEL version="0.1.2" +LABEL version="0.1.1.dev" LABEL description="Scraibe is a tool for automatic speech recognition and speaker diarization. \ It is based on the Hugging Face Transformers library and the Pyannote library. \ It is designed to be used with the Whisper model, a lightweight model for automatic \ @@ -14,7 +14,7 @@ LABEL url="https://github.com/JSchmie/ScrAIbe" # Install dependencies WORKDIR /app -ARG hf_token +ARG model_name=medium #Enviorment Dependncies ENV TRANSFORMERS_CACHE /app/models ENV HF_HOME /app/models @@ -26,8 +26,8 @@ COPY README.md /app/README.md COPY models /app/models COPY scraibe /app/scraibe COPY setup.py /app/setup.py -#Installing all necessary Dependencies and Running the Application with a personalised Hugging-Face-Token +#Installing all necessary Dependencies and Running the Application with a personalised Hugging-Face-Token RUN apt update && apt-get install -y libsm6 libxrender1 libfontconfig1 RUN conda update --all @@ -35,10 +35,12 @@ RUN conda install pip RUN conda install -y ffmpeg RUN conda install -c conda-forge libsndfile RUN pip install torchaudio==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html -RUN pip install /app/ +RUN pip install -r requirements.txt RUN pip install markupsafe==2.0.1 --force-reinstall -RUN scraibe --whisper-model-name small + +RUN python3 -m 'scraibe.cli' --whisper-model-name $model_name # Expose port EXPOSE 7860 # Run the application -ENTRYPOINT ["scraibe" ,"--whisper-model-name", "small"] + +ENTRYPOINT ["python3", "-m", "scraibe.cli" ,"--whisper-model-name", "$model_name"] \ No newline at end of file From 56e33b3a72171f99343cd0e9f283a93431516b00 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 4 Oct 2023 15:06:48 +0200 Subject: [PATCH 017/331] updated requirements to reduce error rate --- requirements.txt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index ef74c29..aed43e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ openai-whisper==20230314 - +numpy~=1.23.5 pyannote.audio~=2.1.1 pyannote.core~=4.5 pyannote.database~=4.1.3 @@ -14,12 +14,5 @@ tqdm>=4.65.0 gradio~=3.36.1 gradio-client~=0.2.7 -# add pytorch to override the one installed by pyannote.audio - -torch~=1.11.0 -torchvision~=0.12.0 -torchaudio~=0.11.0 -#optional: -#sphinx~=5.0.2 From a73cf22c14197c3741b430e326be8313a04d6ecf Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 4 Oct 2023 16:20:47 +0200 Subject: [PATCH 018/331] updated readme --- README.md | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index dd6ef61..85e6d1e 100644 --- a/README.md +++ b/README.md @@ -28,18 +28,24 @@ The following command will pull and install the latest commit from this reposito - **Python version**: Python 3.8 - **PyTorch version**: Python 1.11.0 - **CUDA version**: Cuda-toolkit 11.3.1 +- **OS**: Linux -In order to run `scraibe` properly, it is recommended to reinstall `pytoch` using: +In order to run `scraibe` properly, it is recommended to install `pytoch` using: pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 torchaudio==0.11.0+cu113 --extra-index-url https://download.pytorch.org/whl/cu113 This ensures that the right torchaudio version is installed. +We recommend using the CPU Version of Pytorch for a smooth ScrAIbe installation across both Windows and MacOS platforms. Should you face any issues, please contact us. + + pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu + Important: For the `Pyannote` model, you need to be granted access to Hugging Face. Check the [Pyannote model page](https://huggingface.co/pyannote/speaker-diarization) to get access to the model. Additionally, you need to generate a [Hugging Face token](https://huggingface.co/docs/hub/security-tokens). + ## Usage We've developed ScrAIbe with several access points to cater to diverse user needs. @@ -101,6 +107,8 @@ For the full list of options, run: scraibe -h +The HuggingFace token will be saved after its initial run and can be found at `path/to/scraibe/.pyannotetoken`. It does not need to be called each time you execute `scraibe`. + ### Gradio App The Gradio App is a user-friendly interface for ScrAIbe. It enables you to run the model without any coding knowledge. Therefore, you can run the app in your browser and upload your audio file, or you can make the Framework avail on your network and run it on your local machine. @@ -110,12 +118,12 @@ The Gradio App is a user-friendly interface for ScrAIbe. It enables you to run t To run the Gradio App on your local machine, just use the following command: ``` -scraibe --start_server --port 7860 --hf_token hf_yourhftoken +scraibe --start-server --port 7860 --hf-token hf_yourhftoken ``` -- `--start_server`: Command to start the Gradio App. +- `--start-server`: Command to start the Gradio App. - `--port`: Flag for connecting the container internal port to the port on your local machine. -- `--hf_token`: Flag for entering your personal HuggingFace token in the container. +- `--hf-token`: Flag for entering your personal HuggingFace token in the container. When the app is running, it will show you at which address you can access it. The default address is: http://127.0.0.1:7860 or http://0.0.0.0:7860 @@ -129,25 +137,31 @@ An example is shown below: ### Running a Docker container Another option to run ScrAIbe is to use a Docker container. This option is especially useful if you want to run the model on a server or if you would like to use the GPU without dealing with CUDA. -After you have installed Docker, you can execute the following commands in the terminal. - -First, you need to build the Docker image. Therefore, you need to enter your HuggingFace token and the image name. +To get our Container, you can pull it from Docker Hub: ``` -docker build . --build-arg="hf_token=[enter your HuggingFace token] " -t scraibe +docker pull hadr0n/scraibe:tagname ``` +We provide different tags for different versions of ScrAIbe, including different whisper models. +The current tags are: -After the image is built, you can run the container with the following command: +| Tagname | Description | +| --- | --- | +|`0.1.1.dev-large`|Uses ScrAibe Verison 0.1.1 and the whisper model `large-v2`| +|`0.1.1.dev-medium`|Uses ScrAibe Verison 0.1.1 and the whisper model `medium`| +|`0.1.1.dev-small`|Uses ScrAibe Verison 0.1.1 and the whisper model `small`| +|`0.1.1.dev-base`|Uses ScrAibe Verison 0.1.1 and the whisper model `base`| +|`0.1.1.dev-tiny`|Uses ScrAibe Verison 0.1.1 and the whisper model `tiny`| + +By running the container, you get access to the CLI and the Gradio App. +Here an example command for running the container with the Gradio App: ``` -sudo docker run -it -p 7860:7860 --name [container name][image name] --hf_token [enter your HuggingFace token] --start_server - +docker run -p 7860:7860 --name [container name] hadr0n/scraibe:tagname --start-server --server-name 0.0.0.0 ``` + - `-p`: Flag for connecting the container internal port to the port on your local machine. -- `--hf_token`: Flag for entering your personal HuggingFace token in the container. -- `--start_server`: Command to start the Gradio App. - -Inside the container, the `cli` is used. Therefore, you can use the same commands as in the command-line interface. +- `--server-name 0.0.0.0` is used to make the Gradio App available on your network. #### Enabling GPU usage @@ -156,7 +170,7 @@ For further information, check: https://docs.nvidia.com/datacenter/cloud-native/ To enable GPU usage, you need to add the following flag to the `docker run` command: ``` -docker run -it -p 7860:7860 --gpus 'all,capabilities=utility' --name [container name][image name] --hf_token [enter your HuggingFace token] --start_server +docker run -it -p 7860:7860 --gpus all --name [container name]hadr0n/scraibe:tagname --start-server --server-name 0.0.0.0 ``` For further guidance, check: https://blog.roboflow.com/use-the-gpu-in-docker/ From 11da0dfa31cfe154e52fc583a916ea12c20dcc1c Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 12:07:17 +0200 Subject: [PATCH 019/331] add sphinx documentation workflow --- .github/workflows/documentation.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..4bae9db --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,27 @@ +name: documentation + +on: [push, pull_request, workflow_dispatch] + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + - name: Install dependencies + run: | + pip install sphinx sphinx_rtd_theme myst_parser + - name: Sphinx build + run: | + sphinx-build doc _build + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} + with: + publish_branch: sphinx_action + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: _build/ + force_orphan: true \ No newline at end of file From a3f1e413bdf0fdea2bef2e6a9743201f341e012f Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 12:13:38 +0200 Subject: [PATCH 020/331] change source and dest --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4bae9db..d120ca8 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,7 @@ jobs: pip install sphinx sphinx_rtd_theme myst_parser - name: Sphinx build run: | - sphinx-build doc _build + sphinx-build -M html scraibe docs - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} From 6a07f660d25028d062f505e378e1fb7a495ec2d6 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 12:19:45 +0200 Subject: [PATCH 021/331] changed version of checkout --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index d120ca8..98cfa19 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -9,7 +9,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v3 - name: Install dependencies run: | From ceb1172e139a9c7a4832d5e223c3ff212e272ae3 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 12:21:13 +0200 Subject: [PATCH 022/331] changed source folder --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 98cfa19..7f2682b 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,7 @@ jobs: pip install sphinx sphinx_rtd_theme myst_parser - name: Sphinx build run: | - sphinx-build -M html scraibe docs + sphinx-build -M html . docs - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} From 98d26406a2a0f4f4032b78ac522991add185314a Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:15:03 +0200 Subject: [PATCH 023/331] changed path to pictures --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dd6ef61..f187d65 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ During post-diarization, each audio segment is processed by the OpenAI `Whisper` The following graphic illustrates the whole pipeline: -![Pipeline](Pictures/pipeline.png#gh-dark-mode-only) -![Pipeline](Pictures/pipeline_light.png#gh-light-mode-only) +![Pipeline](./Pictures/pipeline.png#gh-dark-mode-only) +![Pipeline](./Pictures/pipeline_light.png#gh-light-mode-only) ## Install `ScrAIbe` : @@ -123,7 +123,7 @@ The default address is: http://127.0.0.1:7860 or http://0.0.0.0:7860 After the app is running, you can upload your audio file and select the desired options. An example is shown below: -![Gradio App](Pictures/gradio_app.png) +![Gradio App](./Pictures//gradio_app.png) ### Running a Docker container @@ -199,6 +199,6 @@ ScrAIbe is licensed under GNU General Public License. Special thanks go to the KIDA project and the BMEL (Bundesministerium für Ernährung und Landwirtschaft), especially to the AI Consultancy Team. -![KIDA](Pictures/kida_dark.png#gh-dark-mode-only)   ![BMEL](Pictures/BMEL_dark.png#gh-dark-mode-only)      ![DBFZ](Pictures/DBFZ_dark.png#gh-dark-mode-only)       ![MRI](Pictures/MRI.png#gh-dark-mode-only) +![KIDA](./Pictures/kida_dark.png#gh-dark-mode-only)   ![BMEL](./Pictures/BMEL_dark.png#gh-dark-mode-only)      ![DBFZ](./Pictures/DBFZ_dark.png#gh-dark-mode-only)       ![MRI](./Pictures/MRI.png#gh-dark-mode-only) -![KIDA](Pictures/kida.png#gh-light-mode-only)   ![BMEL](Pictures/BMEL.jpg#gh-light-mode-only)      ![DBFZ](Pictures/DBFZ.png#gh-light-mode-only)       ![MRI](Pictures/MRI.png#gh-light-mode-only) +![KIDA](./Pictures/kida.png#gh-light-mode-only)   ![BMEL](./Pictures/BMEL.jpg#gh-light-mode-only)      ![DBFZ](./Pictures/DBFZ.png#gh-light-mode-only)       ![MRI](./Pictures/MRI.png#gh-light-mode-only) From 68e4aaa82fb9431a0dbf64da182538464887ad95 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:23:20 +0200 Subject: [PATCH 024/331] added for sphinx support --- source/conf.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++ source/index.rst | 19 ++++++++++ source/modules.rst | 7 ++++ 3 files changed, 112 insertions(+) create mode 100644 source/conf.py create mode 100644 source/index.rst create mode 100644 source/modules.rst diff --git a/source/conf.py b/source/conf.py new file mode 100644 index 0000000..37d26d1 --- /dev/null +++ b/source/conf.py @@ -0,0 +1,86 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import shutil + +sys.path.insert(0, os.path.abspath('../')) + + +# -- Project information ----------------------------------------------------- + +project = 'ScrAIbe: Streamlined Conversation Recording with Automated Intelligence Based Environment' +copyright = '2023, Jacob Schmieder' +author = 'Jacob Schmieder' + +# The full version, including alpha/beta/rc tags +release = '0.1.1' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinx.ext.napoleon', + 'myst_parser'] + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = True +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# Add source file parsers +source_suffix = { + '.rst': 'restructuredtext', + '.txt': 'markdown', + '.md': 'markdown', +} + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/source/index.rst b/source/index.rst new file mode 100644 index 0000000..df0525a --- /dev/null +++ b/source/index.rst @@ -0,0 +1,19 @@ +Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligence Based Environment's documentation! +===================================================================================================================== + +.. automodule:: scraibe + :members: +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + ../README.md + modules + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/source/modules.rst b/source/modules.rst new file mode 100644 index 0000000..5876262 --- /dev/null +++ b/source/modules.rst @@ -0,0 +1,7 @@ +scraibe +======= + +.. toctree:: + :maxdepth: 4 + + scraibe From 9fcc20bc96b68254824ddba4116b18706562b5b3 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:24:45 +0200 Subject: [PATCH 025/331] changed ci to support sphinx autodoc --- .github/workflows/documentation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7f2682b..860500b 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,9 @@ jobs: pip install sphinx sphinx_rtd_theme myst_parser - name: Sphinx build run: | - sphinx-build -M html . docs + sphinx-apidoc -o source scraibe/ + sphinx-build source docs + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} From 92ff7af76677fc6a859a6ad4d471796d8084d558 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:29:18 +0200 Subject: [PATCH 026/331] test ci --- .github/workflows/documentation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 860500b..94327f6 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,12 +18,12 @@ jobs: run: | sphinx-apidoc -o source scraibe/ sphinx-build source docs - + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} with: - publish_branch: sphinx_action + publish_branch: est_shinx_actions github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: _build/ force_orphan: true \ No newline at end of file From 5a07825db43856cbe231800f5da88465fbb918a9 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:30:13 +0200 Subject: [PATCH 027/331] test2 --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 94327f6..6c55dd2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -23,7 +23,7 @@ jobs: uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} with: - publish_branch: est_shinx_actions + publish_branch: test_sphinx_actions github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: _build/ force_orphan: true \ No newline at end of file From 4eef06fc83994d90d7a4d907f1349329ff0acc91 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:34:03 +0200 Subject: [PATCH 028/331] changed branch name --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 6c55dd2..08c4855 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -23,7 +23,7 @@ jobs: uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} with: - publish_branch: test_sphinx_actions + publish_branch: gh-pages github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: _build/ force_orphan: true \ No newline at end of file From d2bc0928e070269470896f1e20eae826689b4cdb Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 15:58:31 +0200 Subject: [PATCH 029/331] changed var name --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 08c4855..9aedbb0 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -24,6 +24,6 @@ jobs: if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} with: publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.TOKEN_GH }} publish_dir: _build/ force_orphan: true \ No newline at end of file From 14be0a2456793ae0150d1b6db243482e8bdb5412 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 16:04:17 +0200 Subject: [PATCH 030/331] add make html --- .github/workflows/documentation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 9aedbb0..25e3085 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,8 +17,8 @@ jobs: - name: Sphinx build run: | sphinx-apidoc -o source scraibe/ - sphinx-build source docs - + sphinx-build source docs + make html - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} From cec01417ca241e2df646b7a80a6c74630983c867 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 16:05:59 +0200 Subject: [PATCH 031/331] change publish dir --- .github/workflows/documentation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 25e3085..40c7c68 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,12 +18,11 @@ jobs: run: | sphinx-apidoc -o source scraibe/ sphinx-build source docs - make html - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} with: publish_branch: gh-pages github_token: ${{ secrets.TOKEN_GH }} - publish_dir: _build/ + publish_dir: ./docs force_orphan: true \ No newline at end of file From 7aafc4f48b102ed85193de2dcd5b950232949669 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 6 Oct 2023 16:22:45 +0200 Subject: [PATCH 032/331] added documentation page --- .github/workflows/documentation.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 40c7c68..9455895 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,7 +17,7 @@ jobs: - name: Sphinx build run: | sphinx-apidoc -o source scraibe/ - sphinx-build source docs + sphinx-build -M html source docs - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} diff --git a/README.md b/README.md index f187d65..8ecf2b4 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ For further guidance, check: https://blog.roboflow.com/use-the-gpu-in-docker/ ## Documentation -For further insights, check the [documentation page](). +For further insights, check the [documentation page](https://jschmie.github.io/ScrAIbe/). ## Contributions From ebefc27cad3839e147aa69dfb1593f9eae7b5a23 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sat, 7 Oct 2023 18:43:37 +0200 Subject: [PATCH 033/331] changed path to docs/html --- .github/workflows/documentation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 9455895..bb74e46 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -24,5 +24,5 @@ jobs: with: publish_branch: gh-pages github_token: ${{ secrets.TOKEN_GH }} - publish_dir: ./docs - force_orphan: true \ No newline at end of file + publish_dir: ./docs/html + force_orphan: true From 10e8c3137e48785136d58fc14f020664a86252d3 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sat, 7 Oct 2023 18:51:53 +0200 Subject: [PATCH 034/331] Update documentation.yml --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index bb74e46..8fb6de5 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,6 +18,7 @@ jobs: run: | sphinx-apidoc -o source scraibe/ sphinx-build -M html source docs + make html - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/sphinx_action' }} From e9510394aa247516397313df7cfd8310562f2938 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:10:46 +0200 Subject: [PATCH 035/331] added make file to make sphinx work --- Makefile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) From 44644de687585f0106deb565f2edcf6e4ebf3af5 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:16:49 +0200 Subject: [PATCH 036/331] added torch dep --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 8fb6de5..1f3f3c8 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -14,6 +14,7 @@ jobs: - name: Install dependencies run: | pip install sphinx sphinx_rtd_theme myst_parser + pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu - name: Sphinx build run: | sphinx-apidoc -o source scraibe/ From 9bdd9584a97734db88b62f3d961140e26933c256 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:18:33 +0200 Subject: [PATCH 037/331] added python version --- .github/workflows/documentation.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 1f3f3c8..35d97f3 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -11,6 +11,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v3 + with: + python-version: 3.8 - name: Install dependencies run: | pip install sphinx sphinx_rtd_theme myst_parser From 03cc6c9f3aba6be09e977a511cec5c2390996dee Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:19:59 +0200 Subject: [PATCH 038/331] added other requirements --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 35d97f3..91a1f63 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,6 +16,7 @@ jobs: - name: Install dependencies run: | pip install sphinx sphinx_rtd_theme myst_parser + pip install -r requirements.txt pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu - name: Sphinx build run: | From be8bae3da8433cb697c295b66a6e9e41d4c216cf Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:27:40 +0200 Subject: [PATCH 039/331] changed sphinx error due to bug --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 91a1f63..8418117 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -15,7 +15,7 @@ jobs: python-version: 3.8 - name: Install dependencies run: | - pip install sphinx sphinx_rtd_theme myst_parser + pip install sphinx~=7.2 sphinx_rtd_theme~=1.3.0 myst_parser~=2.0.0 pip install -r requirements.txt pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu - name: Sphinx build From 39c2ca358cec199efb18e36a992bde2c9b0981b2 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:29:24 +0200 Subject: [PATCH 040/331] changed python version and tryed to update pip --- .github/workflows/documentation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 8418117..925876c 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,9 +12,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v3 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies run: | + pip install --upgrade pip pip install sphinx~=7.2 sphinx_rtd_theme~=1.3.0 myst_parser~=2.0.0 pip install -r requirements.txt pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu From 4d8012ae01fe78deacfc30b61092b350fb4497d4 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sun, 8 Oct 2023 19:42:52 +0200 Subject: [PATCH 041/331] removed dep from config.py --- source/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/conf.py b/source/conf.py index 37d26d1..ba51ab3 100644 --- a/source/conf.py +++ b/source/conf.py @@ -12,8 +12,6 @@ # import os import sys -import shutil - sys.path.insert(0, os.path.abspath('../')) From 23747b31e6556e132e0c13bed2934a161add0ef6 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Fri, 13 Oct 2023 08:57:48 +0200 Subject: [PATCH 042/331] Update index.rst --- source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index df0525a..0ba34d6 100644 --- a/source/index.rst +++ b/source/index.rst @@ -7,7 +7,7 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen :maxdepth: 2 :caption: Contents: - ../README.md + . ./README.md modules From f6e9f27d1288aa65da629e98bde368a672716b0f Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Fri, 13 Oct 2023 09:44:26 +0200 Subject: [PATCH 043/331] Update index.rst --- source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index 0ba34d6..da1adf3 100644 --- a/source/index.rst +++ b/source/index.rst @@ -7,7 +7,7 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen :maxdepth: 2 :caption: Contents: - . ./README.md + modules From 7ba3d10e0b363392f51cba6e2fb03356ab2d2ead Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 17:53:13 +0200 Subject: [PATCH 044/331] Update documentation.yml Changed pip install from myst_parser to myst-parser --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 925876c..5a2d741 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install sphinx~=7.2 sphinx_rtd_theme~=1.3.0 myst_parser~=2.0.0 + pip install sphinx sphinx_rtd_theme myst-parser pip install -r requirements.txt pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu - name: Sphinx build From 9ce7924a9a1d8c3ff2df7bfd75c02676631ae3ba Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 18:02:09 +0200 Subject: [PATCH 045/331] try to resolve pip error --- .github/workflows/documentation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 5a2d741..df1eed1 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,9 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install sphinx sphinx_rtd_theme myst-parser + pip install --upgrade sphinx sphinx_rtd_theme myst-parser + pip install --upgrade markdown-it-py[plugins] + pip install --upgrade mdit-py-plugins pip install -r requirements.txt pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu - name: Sphinx build From 4f6cc87ca4a29426bdee7e76cb84c9d6c61d5393 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 18:05:45 +0200 Subject: [PATCH 046/331] Update documentation.yml --- .github/workflows/documentation.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index df1eed1..ef21696 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,11 +16,12 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip + pip install -r requirements.txt + pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu pip install --upgrade sphinx sphinx_rtd_theme myst-parser pip install --upgrade markdown-it-py[plugins] pip install --upgrade mdit-py-plugins - pip install -r requirements.txt - pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu + - name: Sphinx build run: | sphinx-apidoc -o source scraibe/ From 87d356cb33777bdbeecbe9fa1ce627c5f6d16130 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 18:14:45 +0200 Subject: [PATCH 047/331] avoid cuda --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index ef21696..2e0ec4e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,7 +17,7 @@ jobs: run: | pip install --upgrade pip pip install -r requirements.txt - pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu + pip install --upgrade --force-reinstall torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu --extra-index-url https://download.pytorch.org/whl/cpu pip install --upgrade sphinx sphinx_rtd_theme myst-parser pip install --upgrade markdown-it-py[plugins] pip install --upgrade mdit-py-plugins From 6bd3d4c758ef1600f33267e593af561ebca4b13a Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 18:20:09 +0200 Subject: [PATCH 048/331] added libsndfile1-dev --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 2e0ec4e..15266ba 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -15,6 +15,7 @@ jobs: python-version: 3.9 - name: Install dependencies run: | + apt-get install libsndfile1-dev pip install --upgrade pip pip install -r requirements.txt pip install --upgrade --force-reinstall torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu --extra-index-url https://download.pytorch.org/whl/cpu From 5d6adc92d4271a5a26b7e6b62bf45a23ba21c28b Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 18:24:31 +0200 Subject: [PATCH 049/331] added sudo idk if it will work --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 15266ba..9844332 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -15,7 +15,7 @@ jobs: python-version: 3.9 - name: Install dependencies run: | - apt-get install libsndfile1-dev + sudo apt-get install libsndfile1-dev pip install --upgrade pip pip install -r requirements.txt pip install --upgrade --force-reinstall torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu --extra-index-url https://download.pytorch.org/whl/cpu From 7076160f493fb6c6fb4a40491fc2fc7dfe84002a Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Sun, 15 Oct 2023 18:34:04 +0200 Subject: [PATCH 050/331] added numpy dep --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 9844332..d7be839 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -19,6 +19,7 @@ jobs: pip install --upgrade pip pip install -r requirements.txt pip install --upgrade --force-reinstall torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu --extra-index-url https://download.pytorch.org/whl/cpu + pip install numpy==1.25 pip install --upgrade sphinx sphinx_rtd_theme myst-parser pip install --upgrade markdown-it-py[plugins] pip install --upgrade mdit-py-plugins From cc6223c82992d3348942001818f1cb40df6715d9 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 10:54:24 +0200 Subject: [PATCH 051/331] added readme back in --- source/index.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/index.rst b/source/index.rst index da1adf3..8c8c0ba 100644 --- a/source/index.rst +++ b/source/index.rst @@ -1,16 +1,18 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligence Based Environment's documentation! ===================================================================================================================== +Readme File +=========== + +../README.md + + .. automodule:: scraibe :members: .. toctree:: :maxdepth: 2 :caption: Contents: - - modules - - Indices and tables ================== From 53bee386b54354a0cae0afb07ca26748321e93b1 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 13:22:27 +0200 Subject: [PATCH 052/331] changed Readme position --- source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index 8c8c0ba..e2e71fe 100644 --- a/source/index.rst +++ b/source/index.rst @@ -4,7 +4,7 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen Readme File =========== -../README.md + ../README.md .. automodule:: scraibe From ff25545c20b6033edc2e9192f19223de3bcba636 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 13:36:43 +0200 Subject: [PATCH 053/331] try to include readme.md --- source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index e2e71fe..cebfd43 100644 --- a/source/index.rst +++ b/source/index.rst @@ -4,7 +4,7 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen Readme File =========== - ../README.md +.. mdinclude::../README.md .. automodule:: scraibe From 7c26f679ac8539115fc109705f103fb078e49e5b Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 14:00:11 +0200 Subject: [PATCH 054/331] changed to include --- source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index cebfd43..93164b5 100644 --- a/source/index.rst +++ b/source/index.rst @@ -4,7 +4,7 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen Readme File =========== -.. mdinclude::../README.md +.. include:: ../README.md .. automodule:: scraibe From 19ada49f46bebec71815035d8e5749abb3f496f6 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 14:29:33 +0200 Subject: [PATCH 055/331] changed position of Readme --- source/index.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index 93164b5..6191c6d 100644 --- a/source/index.rst +++ b/source/index.rst @@ -4,7 +4,6 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen Readme File =========== -.. include:: ../README.md .. automodule:: scraibe @@ -13,6 +12,11 @@ Readme File :maxdepth: 2 :caption: Contents: + .. include:: ../README.md + :parser: myst_parser.sphinx_ + + modules + Indices and tables ================== From 87da03d74e5e44d91adaa77f69a3e35ece9be3e5 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 14:36:56 +0200 Subject: [PATCH 056/331] moved parser --- source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index 6191c6d..eaecbd1 100644 --- a/source/index.rst +++ b/source/index.rst @@ -13,7 +13,7 @@ Readme File :caption: Contents: .. include:: ../README.md - :parser: myst_parser.sphinx_ + :parser: myst_parser.sphinx_ modules From 3647753a509f655ee1df2c9eabd6094de5a97a23 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 16:20:56 +0200 Subject: [PATCH 057/331] fixed Readme --- .github/workflows/documentation.yml | 3 +++ source/index.rst | 13 +++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index d7be839..15a4784 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -26,6 +26,9 @@ jobs: - name: Sphinx build run: | + cp README.md ./source/README.md + cp LICENSE ./source/LICENSE + cp Pictures ./source/Pictures sphinx-apidoc -o source scraibe/ sphinx-build -M html source docs make html diff --git a/source/index.rst b/source/index.rst index eaecbd1..1eaceef 100644 --- a/source/index.rst +++ b/source/index.rst @@ -1,22 +1,19 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligence Based Environment's documentation! ===================================================================================================================== -Readme File -=========== - - - .. automodule:: scraibe - :members: + :members: + .. toctree:: :maxdepth: 2 :caption: Contents: - .. include:: ../README.md - :parser: myst_parser.sphinx_ + ../README.md modules + ../LICENSE + Indices and tables ================== From c27fb6ff05d058b69b39c8cd06322fef6a58ae78 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 16:24:54 +0200 Subject: [PATCH 058/331] fixed recursive cp for pictures --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 15a4784..1e52fb0 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -28,7 +28,7 @@ jobs: run: | cp README.md ./source/README.md cp LICENSE ./source/LICENSE - cp Pictures ./source/Pictures + cp -r Pictures ./source/Pictures sphinx-apidoc -o source scraibe/ sphinx-build -M html source docs make html From 96f7dce6a1584cb6f8ab7b9b73d31f61ee66332b Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 16 Oct 2023 16:34:02 +0200 Subject: [PATCH 059/331] fixed LICENSE --- README.md | 2 +- source/index.rst | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ecf2b4..257a8ab 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ For queries contact [Jacob Schmieder](Jacob.Schmieder@dbfz.de) ## License -ScrAIbe is licensed under GNU General Public License. +ScrAIbe is licensed under [GNU General Public License](LICENSE). ## Acknowledgments diff --git a/source/index.rst b/source/index.rst index 1eaceef..9ac31fa 100644 --- a/source/index.rst +++ b/source/index.rst @@ -12,7 +12,6 @@ Welcome to ScrAIbe: Streamlined Conversation Recording with Automated Intelligen modules - ../LICENSE Indices and tables ================== From f2877d7ad4ac99216d80148c6902b1bf35543bb5 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 8 Nov 2023 17:07:30 +0100 Subject: [PATCH 060/331] try to implement sleep --- scraibe/app/gradio_app.py | 57 ++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/scraibe/app/gradio_app.py b/scraibe/app/gradio_app.py index cf80b7e..086db17 100644 --- a/scraibe/app/gradio_app.py +++ b/scraibe/app/gradio_app.py @@ -34,10 +34,12 @@ Usage: import json import os +import re import gradio as gr +import threading from tqdm import tqdm - +import time from scraibe import Scraibe, Transcript theme = gr.themes.Soft( @@ -63,6 +65,19 @@ LANGUAGES = [ CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + +# Global variable to track user activity +USER_ACTIVE = True + +# Lock to synchronize access to user_active variable +user_active_lock = threading.Lock() + +# Function to reset the user activity flag +def reset_user_activity(): + global USER_ACTIVE + with user_active_lock: + USER_ACTIVE = True + class GradioTranscriptionInterface: """ Interface handling the interaction between Gradio UI and the Audio Transcription system. @@ -205,8 +220,7 @@ class GradioTranscriptionInterface: return json.dumps(out, indent=4) else: - gr.Error("Please provide a valid audio file.") - + gr.Error("Please provide a valid audio file.") #### # Gradio Interface @@ -218,8 +232,11 @@ def gradio_Interface(model : Scraibe = None): model = Scraibe() pipe = GradioTranscriptionInterface(model) - + def select_task(choice): + # tell the app that it is still in use + reset_user_activity() + if choice == 'Auto Transcribe': return (gr.update(visible = True), @@ -241,6 +258,10 @@ def gradio_Interface(model : Scraibe = None): gr.update(visible = False)) def select_origin(choice): + + # tell the app that it is still in use + reset_user_activity() + if choice == "Upload Audio": return (gr.update(visible = True), @@ -292,6 +313,10 @@ def gradio_Interface(model : Scraibe = None): file_in, progress = gr.Progress(track_tqdm= True)): # get *args which are not None + + # # tell the app that it is still in use + reset_user_activity() + progress(0, desc='Starting task...') source = audio1 or audio2 or video1 or video2 or file_in @@ -346,10 +371,28 @@ def gradio_Interface(model : Scraibe = None): trans = trans.annotate(*annoation.split(",")) return gr.update(value = str(trans)),gr.update(value = trans.get_json()) + + # Create a thread to monitor user activity + def monitor_activity(): + global USER_ACTIVE + while True: + time.sleep(60) # Check user activity every second + with user_active_lock: + + if not USER_ACTIVE: + del model + print("Model deleted empty memory") + break + USER_ACTIVE = False + + # Start the monitoring thread + activity_thread = threading.Thread(target=monitor_activity) + activity_thread.daemon = True + activity_thread.start() with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: - + # Define components hname = os.path.join(CURRENT_PATH, "header.html") header = open(hname, "r").read() @@ -358,7 +401,7 @@ def gradio_Interface(model : Scraibe = None): header = header.replace("/file=logo.svg", f"/file={CURRENT_PATH}/logo.svg" ) gr.HTML(header, visible= True, show_label=False) - + with gr.Row(): with gr.Column(): @@ -433,6 +476,8 @@ def gradio_Interface(model : Scraibe = None): annotate.click(fn = annotate_output, inputs=[annoation, out_json], outputs=[out_txt, out_json]) + + return demo From ea03bf1f06c7448fd7d51eb70b819cc725ee0d20 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 10 Nov 2023 15:23:50 +0100 Subject: [PATCH 061/331] added variable to store params for possible reload --- scraibe/autotranscript.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scraibe/autotranscript.py b/scraibe/autotranscript.py index b3545e4..2664e3f 100644 --- a/scraibe/autotranscript.py +++ b/scraibe/autotranscript.py @@ -75,6 +75,11 @@ class Scraibe: Path to pyannote diarization model or model itself. **kwargs: Additional keyword arguments for whisper and pyannote diarization models. + e.g.: + + - verbose: If True, the class will print additional information. + - save_kwargs: If True, the keyword arguments will be saved + for autotranscribe. So you can unload the class and reload it again. """ @@ -98,6 +103,15 @@ class Scraibe: else: self.verbose = False + # Save kwargs for autotranscribe if you want to unload the class and load it again. + if kwargs.get('save_setup'): + self.params = dict(whisper_model = whisper_model, + dia_model = dia_model, + **kwargs) + else: + self.params = {} + + def autotranscribe(self, audio_file : Union[str, torch.Tensor, ndarray], remove_original : bool = False, **kwargs) -> Transcript: From b42d1d1faaa58d400e8f908eb8e6ff8f700017fc Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 10 Nov 2023 15:43:01 +0100 Subject: [PATCH 062/331] tryed to unload model but it does not work jet --- scraibe/app/gradio_app.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/scraibe/app/gradio_app.py b/scraibe/app/gradio_app.py index 086db17..6913643 100644 --- a/scraibe/app/gradio_app.py +++ b/scraibe/app/gradio_app.py @@ -32,13 +32,15 @@ Usage: """ + import json +from math import pi import os -import re import gradio as gr import threading from tqdm import tqdm + import time from scraibe import Scraibe, Transcript @@ -226,11 +228,23 @@ class GradioTranscriptionInterface: # Gradio Interface #### -def gradio_Interface(model : Scraibe = None): +def gradio_Interface(model : Scraibe = None, timeout = 1): + """ + Gradio Web interface for audio transcription. + + :param model: Scraibe model, defaults to None + :type model: Scraibe, optional + :param timeout: Time until model is unloaded, defaults to 600 seconds + :type timeout: int, optional + :return: Gradio Interface + :rtype: gradio.Interface + """ if model is None: model = Scraibe() - + + save_model_params = model.params + pipe = GradioTranscriptionInterface(model) def select_task(choice): @@ -314,6 +328,10 @@ def gradio_Interface(model : Scraibe = None): progress = gr.Progress(track_tqdm= True)): # get *args which are not None + if not "model" in locals(): + gr.Warning("Model unloaded due to inactivity. Reloading the model, please wait.") + model = Scraibe(**save_model_params) + pipe = GradioTranscriptionInterface(model) # # tell the app that it is still in use reset_user_activity() @@ -373,21 +391,23 @@ def gradio_Interface(model : Scraibe = None): return gr.update(value = str(trans)),gr.update(value = trans.get_json()) # Create a thread to monitor user activity - def monitor_activity(): + def monitor_activity(model, pipe, timeout=timeout): global USER_ACTIVE while True: - time.sleep(60) # Check user activity every second + time.sleep(timeout) # Check user activity every second with user_active_lock: if not USER_ACTIVE: del model + del pipe print("Model deleted empty memory") + gr.Warning("Model unloaded due to inactivity. Please reload the model to continue.") break - USER_ACTIVE = False + USER_ACTIVE = False # Start the monitoring thread - activity_thread = threading.Thread(target=monitor_activity) + activity_thread = threading.Thread(target=monitor_activity, args=(model, pipe)) activity_thread.daemon = True activity_thread.start() @@ -401,7 +421,7 @@ def gradio_Interface(model : Scraibe = None): header = header.replace("/file=logo.svg", f"/file={CURRENT_PATH}/logo.svg" ) gr.HTML(header, visible= True, show_label=False) - + with gr.Row(): with gr.Column(): @@ -476,8 +496,6 @@ def gradio_Interface(model : Scraibe = None): annotate.click(fn = annotate_output, inputs=[annoation, out_json], outputs=[out_txt, out_json]) - - return demo From fafe5c2709ebd8c550a2dab63fc3b9b7ee7212bb Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 15 Nov 2023 16:35:26 +0100 Subject: [PATCH 063/331] test for multithreading --- test_multithreading.py | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test_multithreading.py diff --git a/test_multithreading.py b/test_multithreading.py new file mode 100644 index 0000000..c3ab051 --- /dev/null +++ b/test_multithreading.py @@ -0,0 +1,73 @@ +import time + +from scraibe import Scraibe +import threading +import torch +import gc + +model = None +last_used = time.time() +transcribe_active = threading.Event() + +def transcribe_thread(audio): + + global model + transcribe_active.set() + print(model.autotranscribe(audio)) + transcribe_active.clear() + +def model_thread(): + global model, last_used + model = Scraibe(dia_model= "models/pyannote/config.yaml") + last_used = time.time() + +def interaction_thread(): + global model + while True: + command = input("Enter a command ('q' to quit, 'reload' to reload model): ") + print("Command entered:", command, command.lower() == 'reload') + if command.lower() == 'q': + break + elif command.lower() == 'reload': + print("Reloading model...", model) + if model is None: + model_runner = threading.Thread(target=model_thread) + model_runner.start() + model_runner.join() + else: + print("Model is already loaded.") + else: + transcribe = threading.Thread(target=transcribe_thread, args=(command,)) + transcribe.start() + transcribe.join() + +def delete_unused_model(model_runner): + global model, last_used, transcribe_active + while True: + if not transcribe_active.is_set() and (time.time() - last_used > 30) and model is not None: + + del model + model = None + + gc.collect() + torch.cuda.empty_cache() + + model_runner.join() + print("Model deleted", threading.active_count()) + time.sleep(1) + +if __name__ == "__main__": + + lock = threading.Lock() + + interaction = threading.Thread(target=interaction_thread) + model_runner = threading.Thread(target=model_thread) + model_deleter = threading.Thread(target=delete_unused_model, args=(model_runner,)) + + model_runner.start() + model_deleter.start() + + # Ensure the model is initialized before starting the interaction + model_runner.join() + interaction.start() + interaction.join() \ No newline at end of file From 105161b6a601e3464ac0371a3090ff102831016f Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 17 Nov 2023 14:12:53 +0100 Subject: [PATCH 064/331] test successed next step to implement in gradio app --- test_multithreading.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/test_multithreading.py b/test_multithreading.py index c3ab051..6f9834d 100644 --- a/test_multithreading.py +++ b/test_multithreading.py @@ -1,3 +1,4 @@ +import os import time from scraibe import Scraibe @@ -22,29 +23,38 @@ def model_thread(): last_used = time.time() def interaction_thread(): - global model + global model, model_runner while True: command = input("Enter a command ('q' to quit, 'reload' to reload model): ") - print("Command entered:", command, command.lower() == 'reload') + if command.lower() == 'q': break elif command.lower() == 'reload': print("Reloading model...", model) if model is None: + transcribe_active.clear() #black magic model_runner = threading.Thread(target=model_thread) model_runner.start() model_runner.join() + else: print("Model is already loaded.") else: - transcribe = threading.Thread(target=transcribe_thread, args=(command,)) - transcribe.start() - transcribe.join() + if os.path.exists(command): + transcribe = threading.Thread(target=transcribe_thread, args=(command,)) + transcribe.start() + transcribe.join() + + else: + print("File does not exist.") def delete_unused_model(model_runner): global model, last_used, transcribe_active + while True: - if not transcribe_active.is_set() and (time.time() - last_used > 30) and model is not None: + print("Checking for unused model...", transcribe_active.is_set()) + _unload_porperty = (not transcribe_active.is_set() and (time.time() - last_used > 30) and model is not None) + if _unload_porperty: del model model = None @@ -53,8 +63,9 @@ def delete_unused_model(model_runner): torch.cuda.empty_cache() model_runner.join() + print("Model deleted", threading.active_count()) - time.sleep(1) + time.sleep(10) if __name__ == "__main__": From f691790c00021abffdf741910c9247300e5dec49 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 17 Nov 2023 15:23:11 +0100 Subject: [PATCH 065/331] aded deamon process --- test_multithreading.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test_multithreading.py b/test_multithreading.py index 6f9834d..fb4e301 100644 --- a/test_multithreading.py +++ b/test_multithreading.py @@ -2,6 +2,8 @@ import os import time from scraibe import Scraibe + +import multiprocessing import threading import torch import gc @@ -52,7 +54,6 @@ def delete_unused_model(model_runner): global model, last_used, transcribe_active while True: - print("Checking for unused model...", transcribe_active.is_set()) _unload_porperty = (not transcribe_active.is_set() and (time.time() - last_used > 30) and model is not None) if _unload_porperty: @@ -64,7 +65,7 @@ def delete_unused_model(model_runner): model_runner.join() - print("Model deleted", threading.active_count()) + print("Model deleted") time.sleep(10) if __name__ == "__main__": @@ -72,8 +73,8 @@ if __name__ == "__main__": lock = threading.Lock() interaction = threading.Thread(target=interaction_thread) - model_runner = threading.Thread(target=model_thread) - model_deleter = threading.Thread(target=delete_unused_model, args=(model_runner,)) + model_runner = threading.Thread(target=model_thread, daemon=True) + model_deleter = threading.Thread(target=delete_unused_model, args=(model_runner,), daemon=True) model_runner.start() model_deleter.start() From 0a7200cc96179336ae3bc3a5925c12230c12aac3 Mon Sep 17 00:00:00 2001 From: Tryndaron <84569139+Tryndaron@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:21:59 +0100 Subject: [PATCH 066/331] Add files via upload --- test_audio.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 test_audio.py diff --git a/test_audio.py b/test_audio.py new file mode 100644 index 0000000..64fa8c1 --- /dev/null +++ b/test_audio.py @@ -0,0 +1,83 @@ +import pytest +#from scraibe import Transcriber +#from unittest.mock import patch, mock_open +#import unittest +#import os +from .audio import AudioProcessor +import torch + + + + + +test_waveform = torch.tensor([]).to('cuda') +test_sr = 16000 +SAMPLE_RATE = 16000 +NORMALIZATION_FACTOR = 32768 + + +@pytest.fixture +def probe_audio_processor(): + return AudioProcessor(test_waveform, test_sr) + + + + + + +def test_AudioProcessor_init(probe_audio_processor): + assert isinstance(probe_audio_processor, AudioProcessor) + assert probe_audio_processor.waveform.device == test_waveform.device + assert torch.equal(probe_audio_processor.waveform, test_waveform) + assert probe_audio_processor.sr == test_sr + + + +def test_cut(): + waveform = torch.Tensor(10, 3) + sr = 16000 + start = 4 + end = 7 + assert AudioProcessor(waveform, sr).cut(start, end).size() == int((end - start) * test_sr) + + + +""" def test_cut(probe_audio_processor): + start = 10 + end = 100 + test_segment = probe_audio_processor.cut(start, end) + print(test_segment) + erwartetes_segment = int((end - start) * test_sr) + print(test_segment.size()) + assert len(test_segment) == erwartetes_segment + """ + + + + + +def test_audio_processor_invalid_sr(): + with pytest.raises(ValueError): + AudioProcessor(test_waveform, [44100,48000]) + + +def test_audio_processor_SAMPLE_RATE(): + probe_audio_processor = AudioProcessor(test_waveform) + assert probe_audio_processor.sr == SAMPLE_RATE + + + + + + + + + + + + + + + + + From bbb2c848e31e04cee3635956e1e84a897de38519 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 20 Nov 2023 15:01:51 +0100 Subject: [PATCH 067/331] rework structure of gradio app --- scraibe/__init__.py | 3 +- scraibe/app/__init__.py | 7 +- scraibe/app/activity_tracker.py | 37 +++ scraibe/app/global_var.py | 9 + scraibe/app/gradio_app.py | 504 -------------------------------- scraibe/app/interactions.py | 145 +++++++++ scraibe/app/interface.py | 129 ++++++++ scraibe/app/stg.py | 157 ++++++++++ 8 files changed, 484 insertions(+), 507 deletions(-) create mode 100644 scraibe/app/activity_tracker.py create mode 100644 scraibe/app/global_var.py delete mode 100644 scraibe/app/gradio_app.py create mode 100644 scraibe/app/interactions.py create mode 100644 scraibe/app/interface.py create mode 100644 scraibe/app/stg.py diff --git a/scraibe/__init__.py b/scraibe/__init__.py index a3a2b17..3fd77e8 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -7,8 +7,7 @@ from .diarisation import * from .version import get_version as _get_version from .misc import * -from .app.gradio_app import * -from .app.qtfaststart import * +from .app import * from .cli import * diff --git a/scraibe/app/__init__.py b/scraibe/app/__init__.py index dc00e7a..9e04a48 100644 --- a/scraibe/app/__init__.py +++ b/scraibe/app/__init__.py @@ -1,2 +1,7 @@ from .qtfaststart import * -from .gradio_app import * \ No newline at end of file +from .activity_tracker import * +from .interface import * +from .stg import * +from .interactions import * +from .global_var import * +from .app import * \ No newline at end of file diff --git a/scraibe/app/activity_tracker.py b/scraibe/app/activity_tracker.py new file mode 100644 index 0000000..5cced3b --- /dev/null +++ b/scraibe/app/activity_tracker.py @@ -0,0 +1,37 @@ +""" +This file contains the functions which are related to monitoring the actual app usage. +Therefore, the app is to be more efficient in the usage of the resources. +By for example, unloading or reloading the model. +""" +import time +import threading +import torch +import gc +import gradio as gr + + +timeout = 30 #seconds +USER_ACTIVE = True +user_active_lock = threading.Lock() # dummy for now + +# Create a thread to monitor user activity +def monitor_activity(model, pipe, timeout=timeout): + global USER_ACTIVE + + while True: + time.sleep(timeout) # Check user activity every second + with user_active_lock: + + if not USER_ACTIVE: + del model + del pipe + + gc.collect() + torch.cuda.empty_cache() + + + + print("Model deleted empty memory") + gr.Warning("Model unloaded due to inactivity. Please reload the model to continue.") + break + USER_ACTIVE = False \ No newline at end of file diff --git a/scraibe/app/global_var.py b/scraibe/app/global_var.py new file mode 100644 index 0000000..191e3e6 --- /dev/null +++ b/scraibe/app/global_var.py @@ -0,0 +1,9 @@ +""" +Stores global variables for the app. +""" + +# Global variable to store the model +MODEL = None + +# Global variable to track user activity +USER_ACTIVE = False \ No newline at end of file diff --git a/scraibe/app/gradio_app.py b/scraibe/app/gradio_app.py deleted file mode 100644 index 6913643..0000000 --- a/scraibe/app/gradio_app.py +++ /dev/null @@ -1,504 +0,0 @@ -""" -Gradio Audio Transcription App. --------------------------------- - -This module provides an interface to transcribe audio files using the -Scraibe model. Users can either upload an audio file or record their speech -live for transcription. The application supports multiple languages and provides -options to specify the number of speakers and the language of the audio. - -Attributes: - LANGUAGES (list): A list of supported languages for transcription. - -Usage: - Run this script to start the Gradio web interface for audio transcription. - -""" - -""" -Gradio Audio Transcription App. --------------------------------- - -This module provides an interface to transcribe audio files using the -Scraibe model. Users can either upload an audio file or record their speech -live for transcription. The application supports multiple languages and provides -options to specify the number of speakers and the language of the audio. - -Attributes: - LANGUAGES (list): A list of supported languages for transcription. - -Usage: - Run this script to start the Gradio web interface for audio transcription. - -""" - - -import json -from math import pi -import os - -import gradio as gr -import threading -from tqdm import tqdm - -import time -from scraibe import Scraibe, Transcript - -theme = gr.themes.Soft( - primary_hue="green", - secondary_hue='orange', - neutral_hue="gray", -) - -LANGUAGES = [ - "Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", - "Bosnian", "Bulgarian", "Catalan", "Chinese", "Croatian", - "Czech", "Danish", "Dutch", "English", "Estonian", - "Finnish", "French", "Galician", "German", "Greek", - "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", - "Italian", "Japanese", "Kannada", "Kazakh", "Korean", - "Latvian", "Lithuanian", "Macedonian", "Malay", "Marathi", - "Maori", "Nepali", "Norwegian", "Persian", "Polish", - "Portuguese", "Romanian", "Russian", "Serbian", "Slovak", - "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", - "Tamil", "Thai", "Turkish", "Ukrainian", "Urdu", - "Vietnamese", "Welsh" -] - -CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) - - -# Global variable to track user activity -USER_ACTIVE = True - -# Lock to synchronize access to user_active variable -user_active_lock = threading.Lock() - -# Function to reset the user activity flag -def reset_user_activity(): - global USER_ACTIVE - with user_active_lock: - USER_ACTIVE = True - -class GradioTranscriptionInterface: - """ - Interface handling the interaction between Gradio UI and the Audio Transcription system. - """ - - def __init__(self, model: Scraibe): - """ - Initializes the GradioTranscriptionInterface with a transcription model. - - Args: - model (Scraibe): Model responsible for audio transcription tasks. - """ - self.model = model - - def auto_transcribe(self, source, - num_speakers : int, - translation : bool, - language : str): - """ - Shortcut method for the Scraibe task. - - Returns: - tuple: Transcribed text (str), JSON output (dict) - """ - - kwargs = { - "num_speakers": num_speakers if num_speakers != 0 else None, - "language": language if language != "None" else None, - "task": 'translate' if translation else None - } - if isinstance(source, str): - try: - result = self.model.autotranscribe(source, **kwargs) - except ValueError: - raise gr.Error("Couldn't detect any speech in the provided audio. \ - Please try again!") - - return str(result), result.get_json() - - elif isinstance(source, list): - source_names = [s.split("/")[-1] for s in source] - result = [] - for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): - try: - res = self.model.autotranscribe(s, **kwargs) - except ValueError: - _name = s.split("/")[-1] - res = f"NO TRANSCRIPT FOUND FOR {_name}" - gr.Warning(f"Couldn't detect any speech in {_name} will skip this file.") - result.append(res) - - out = '' - out_dict = {} - for i, r in enumerate(result): - out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" - out += str(r) - out += "\n\n" - - if isinstance(r, str): - out_dict[source_names[i]] = r - else: - out_dict[source_names[i]] = r.get_dict() - - return out, json.dumps(out_dict, indent=4) - - else: - raise gr.Error("Please provide a valid audio file.") - - - def transcribe(self, source, translation, language): - """ - Shortcut method for the Transcribe task. - - Returns: - str: Transcribed text. - """ - kwargs = { - "language": language if language != "None" else None, - "task": 'translate' if translation == "Yes" else None - } - - if isinstance(source, str): - result = self.model.transcribe(source, **kwargs) - - return str(result) - - elif isinstance(source, list): - source_names = [s.split("/")[-1] for s in source] - result = [] - for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): - res = self.model.transcribe(s, **kwargs) - result.append(res) - - out = '' - for i, res in enumerate(result): - out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" - out += str(res) - out += "\n\n" - - return out - - else: - raise gr.Error("Please provide a valid audio file.") - - def perform_diarisation(self, source, num_speakers): - """ - Shortcut method for the Diarisation task. - - Returns: - str: JSON output of diarisation result. - """ - kwargs = { - "num_speakers": num_speakers if num_speakers != 0 else None, - } - - if isinstance(source, str): - try: - result = self.model.diarization(source, **kwargs) - except ValueError: - raise gr.Error("Couldn't detect any speech in the provided audio. \ - Please try again!") - - return json.dumps(result, indent=2) - elif isinstance(source, list): - source_names = [s.split("/")[-1] for s in source] - result = [] - for s in tqdm(source, total=len(source),desc = "Performing diarisation"): - try: - res = self.model.diarization(s, **kwargs) - except ValueError: - res = f"NO DIARISATION FOUND FOR {s}" - gr.Warning(f"Couldn't detect any speech in {s} will skip this file.") - result.append(res) - - out = {} - - for i, res in enumerate(result): - out[source_names[i]] = res - - return json.dumps(out, indent=4) - - else: - gr.Error("Please provide a valid audio file.") - -#### -# Gradio Interface -#### - -def gradio_Interface(model : Scraibe = None, timeout = 1): - """ - Gradio Web interface for audio transcription. - - :param model: Scraibe model, defaults to None - :type model: Scraibe, optional - :param timeout: Time until model is unloaded, defaults to 600 seconds - :type timeout: int, optional - :return: Gradio Interface - :rtype: gradio.Interface - """ - - if model is None: - model = Scraibe() - - save_model_params = model.params - - pipe = GradioTranscriptionInterface(model) - - def select_task(choice): - # tell the app that it is still in use - reset_user_activity() - - if choice == 'Auto Transcribe': - - return (gr.update(visible = True), - gr.update(visible = True), - gr.update(visible = True)) - - - elif choice == 'Transcribe': - - return (gr.update(visible = False), - gr.update(visible = True), - gr.update(visible = True)) - - - elif choice == 'Diarisation': - - return (gr.update(visible = True), - gr.update(visible = False), - gr.update(visible = False)) - - def select_origin(choice): - - # tell the app that it is still in use - reset_user_activity() - - if choice == "Upload Audio": - - return (gr.update(visible = True), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None)) - - elif choice == "Record Audio": - - return (gr.update(visible = False, value = None), - gr.update(visible = True), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None)) - - elif choice == "Upload Video": - - return (gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = True), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None)) - - elif choice == "Record Video": - - return (gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = True), - gr.update(visible = False, value = None)) - - elif choice == "File or Files": - - return (gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = True)) - - def run_scribe(task, - num_speakers, - translate, - language, - audio1, - audio2, - video1, - video2, - file_in, - progress = gr.Progress(track_tqdm= True)): - # get *args which are not None - - if not "model" in locals(): - gr.Warning("Model unloaded due to inactivity. Reloading the model, please wait.") - model = Scraibe(**save_model_params) - pipe = GradioTranscriptionInterface(model) - # # tell the app that it is still in use - reset_user_activity() - - progress(0, desc='Starting task...') - source = audio1 or audio2 or video1 or video2 or file_in - - if isinstance(source, list): - source = [s.name for s in source] - if len(source) == 1: - source = source[0] - - if task == 'Auto Transcribe': - - out_str , out_json = pipe.auto_transcribe(source = source, - num_speakers = num_speakers, - translation = translate, - language = language) - - if isinstance(source, str): - return (gr.update(value = out_str, visible = True), - gr.update(value = out_json, visible = True), - gr.update(visible = True), - gr.update(visible = True)) - else: - return (gr.update(value = out_str, visible = True), - gr.update(value = out_json, visible = True), - gr.update(visible = False), - gr.update(visible = False)) - - elif task == 'Transcribe': - - out = pipe.transcribe(source = source, - translation = translate, - language = language) - - return (gr.update(value = out, visible = True), - gr.update(value = None, visible = False), - gr.update(visible = False), - gr.update(visible = False)) - - elif task == 'Diarisation': - - out = pipe.perform_diarisation(source = source, - num_speakers = num_speakers) - - return (gr.update(value = None, visible = False), - gr.update(value = out, visible = True), - gr.update(visible = False), - gr.update(visible = False)) - - def annotate_output(annoation : str, out_json : dict): - # get *args which are not None - - trans = Transcript.from_json(out_json) - trans = trans.annotate(*annoation.split(",")) - - return gr.update(value = str(trans)),gr.update(value = trans.get_json()) - - # Create a thread to monitor user activity - def monitor_activity(model, pipe, timeout=timeout): - global USER_ACTIVE - - while True: - time.sleep(timeout) # Check user activity every second - with user_active_lock: - - if not USER_ACTIVE: - del model - del pipe - print("Model deleted empty memory") - gr.Warning("Model unloaded due to inactivity. Please reload the model to continue.") - break - USER_ACTIVE = False - - # Start the monitoring thread - activity_thread = threading.Thread(target=monitor_activity, args=(model, pipe)) - activity_thread.daemon = True - activity_thread.start() - - with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: - - # Define components - hname = os.path.join(CURRENT_PATH, "header.html") - header = open(hname, "r").read() - - # ugly hack to get the logo to work - header = header.replace("/file=logo.svg", f"/file={CURRENT_PATH}/logo.svg" ) - - gr.HTML(header, visible= True, show_label=False) - - with gr.Row(): - - with gr.Column(): - - task = gr.Radio(["Auto Transcribe", "Transcribe", "Diarisation"], label="Task", - value= 'Auto Transcribe') - - num_speakers = gr.Number(value=0, label= "Number of speakers (optional)", - info = "Number of speakers in the audio file. If you don't know,\ - leave it at 0.", visible= True) - - translate = gr.Checkbox(label="Translation", choices=[True, False], value = False, - info="Select 'Yes' to have the output translated into English.", - visible= True) - - language = gr.Dropdown(LANGUAGES, - label="Language (optional)", value = "None", - info="Language of the audio file. If you don't know,\ - leave it at None.", visible= True) - - input = gr.Radio(["Upload Audio", "Record Audio", "Upload Video","Record Video" - ,"File or Files"], label="Input Type", value="Upload Audio") - - audio1 = gr.Audio(source="upload", type="filepath", label="Upload Audio", - interactive= True, visible= True) - audio2 = gr.Audio(source="microphone", label="Record Audio", type="filepath", - interactive= True, visible= False) - video1 = gr.Video(source="upload", type="filepath", label="Upload Video", - interactive= True, visible= False) - video2 = gr.Video(source="webcam", label="Record Video", type="filepath",include_audio= True, - interactive= True, visible= False) - file_in = gr.Files(label="Upload File or Files", interactive= True, visible= False) - - submit = gr.Button() - - with gr.Column(): - - out_txt = gr.Textbox(label="Output", - visible= True, show_copy_button=True) - - out_json = gr.JSON(label="JSON Output", - visible= False, show_copy_button=True) - - annoation = gr.Textbox(label="Name your speaker's", - info= "Please provide a list of the speakers arranged \ - in the order in which they appear in the input. Use comma ',' \ - as a seperator. Be aware that the first name is given \ - to SPEAKER_00 the second to SPEAKER_01 and so on.", - visible= False, interactive= True) - - annotate = gr.Button(value="Annotate", visible= False, interactive= True) - - # Define usage of components - input.change(fn=select_origin, inputs=[input], - outputs=[audio1, audio2, video1, video2, file_in]) - - task.change(fn=select_task, inputs=[task], - outputs=[num_speakers, translate, language]) - - translate.change(fn= lambda x : gr.update(value = x), - inputs=[translate], outputs=[translate]) - num_speakers.change(fn= lambda x : gr.update(value = x), - inputs=[num_speakers], outputs=[num_speakers]) - language.change(fn= lambda x : gr.update(value = x), - inputs=[language], outputs=[language]) - - submit.click(fn = run_scribe, - inputs=[task, num_speakers, translate, language, audio1, - audio2, video1, video2, file_in], - outputs=[out_txt, out_json, annoation, annotate]) - - annotate.click(fn = annotate_output, inputs=[annoation, out_json], - outputs=[out_txt, out_json]) - - return demo - - -if __name__ == "__main__": - - gradio_Interface().queue().launch() \ No newline at end of file diff --git a/scraibe/app/interactions.py b/scraibe/app/interactions.py new file mode 100644 index 0000000..10659c0 --- /dev/null +++ b/scraibe/app/interactions.py @@ -0,0 +1,145 @@ +""" +This file contains ervery function that will be called when the user interacts with the +UI like pressing a button or uploading a file. +""" + +from math import pi +import gradio as gr +import scraibe.app.global_var as gv +from scraibe import Transcript + +def select_task(choice): + # tell the app that it is still in use + if choice == 'Auto Transcribe': + + return (gr.update(visible = True), + gr.update(visible = True), + gr.update(visible = True)) + + + elif choice == 'Transcribe': + + return (gr.update(visible = False), + gr.update(visible = True), + gr.update(visible = True)) + + + elif choice == 'Diarisation': + + return (gr.update(visible = True), + gr.update(visible = False), + gr.update(visible = False)) + +def select_origin(choice): + + # tell the app that it is still in use + if choice == "Upload Audio": + + return (gr.update(visible = True), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None)) + + elif choice == "Record Audio": + + return (gr.update(visible = False, value = None), + gr.update(visible = True), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None)) + + elif choice == "Upload Video": + + return (gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = True), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None)) + + elif choice == "Record Video": + + return (gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = True), + gr.update(visible = False, value = None)) + + elif choice == "File or Files": + + return (gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = False, value = None), + gr.update(visible = True)) + +def run_scraibe(task, + num_speakers, + translate, + language, + audio1, + audio2, + video1, + video2, + file_in, + progress = gr.Progress(track_tqdm= True)): + + # get *args which are not None + + pipe = gv.MODEL + + progress(0, desc='Starting task...') + source = audio1 or audio2 or video1 or video2 or file_in + + if isinstance(source, list): + source = [s.name for s in source] + if len(source) == 1: + source = source[0] + + if task == 'Auto Transcribe': + + out_str , out_json = pipe.auto_transcribe(source = source, + num_speakers = num_speakers, + translation = translate, + language = language) + + if isinstance(source, str): + return (gr.update(value = out_str, visible = True), + gr.update(value = out_json, visible = True), + gr.update(visible = True), + gr.update(visible = True)) + else: + return (gr.update(value = out_str, visible = True), + gr.update(value = out_json, visible = True), + gr.update(visible = False), + gr.update(visible = False)) + + elif task == 'Transcribe': + + out = pipe.transcribe(source = source, + translation = translate, + language = language) + + return (gr.update(value = out, visible = True), + gr.update(value = None, visible = False), + gr.update(visible = False), + gr.update(visible = False)) + + elif task == 'Diarisation': + + out = pipe.perform_diarisation(source = source, + num_speakers = num_speakers) + + return (gr.update(value = None, visible = False), + gr.update(value = out, visible = True), + gr.update(visible = False), + gr.update(visible = False)) + +def annotate_output(annoation : str, out_json : dict): + # get *args which are not None + + trans = Transcript.from_json(out_json) + trans = trans.annotate(*annoation.split(",")) + + return gr.update(value = str(trans)),gr.update(value = trans.get_json()) + diff --git a/scraibe/app/interface.py b/scraibe/app/interface.py new file mode 100644 index 0000000..ef9d818 --- /dev/null +++ b/scraibe/app/interface.py @@ -0,0 +1,129 @@ +""" +This file contains the actual gradio Interface which is used to interact with the user. +""" + +import gradio as gr +import os + +import scraibe.app.global_var as gv +from .interactions import * +from .stg import * + +from scraibe import Scraibe + +theme = gr.themes.Soft( + primary_hue="green", + secondary_hue='orange', + neutral_hue="gray", +) + + +LANGUAGES = [ + "Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", + "Bosnian", "Bulgarian", "Catalan", "Chinese", "Croatian", + "Czech", "Danish", "Dutch", "English", "Estonian", + "Finnish", "French", "Galician", "German", "Greek", + "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", + "Italian", "Japanese", "Kannada", "Kazakh", "Korean", + "Latvian", "Lithuanian", "Macedonian", "Malay", "Marathi", + "Maori", "Nepali", "Norwegian", "Persian", "Polish", + "Portuguese", "Romanian", "Russian", "Serbian", "Slovak", + "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", + "Tamil", "Thai", "Turkish", "Ukrainian", "Urdu", + "Vietnamese", "Welsh" +] + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + + +def gradio_Interface(pipe : Scraibe = None): + + if pipe is not None: + gv.MODEL = GradioTranscriptionInterface(pipe) + + with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: + + # Define components + hname = os.path.join(CURRENT_PATH, "header.html") + header = open(hname, "r").read() + + # ugly hack to get the logo to work + header = header.replace("/file=logo.svg", f"/file={CURRENT_PATH}/logo.svg" ) + + gr.HTML(header, visible= True, show_label=False) + + with gr.Row(): + + with gr.Column(): + + task = gr.Radio(["Auto Transcribe", "Transcribe", "Diarisation"], label="Task", + value= 'Auto Transcribe') + + num_speakers = gr.Number(value=0, label= "Number of speakers (optional)", + info = "Number of speakers in the audio file. If you don't know,\ + leave it at 0.", visible= True) + + translate = gr.Checkbox(label="Translation", choices=[True, False], value = False, + info="Select 'Yes' to have the output translated into English.", + visible= True) + + language = gr.Dropdown(LANGUAGES, + label="Language (optional)", value = "None", + info="Language of the audio file. If you don't know,\ + leave it at None.", visible= True) + + input = gr.Radio(["Upload Audio", "Record Audio", "Upload Video","Record Video" + ,"File or Files"], label="Input Type", value="Upload Audio") + + audio1 = gr.Audio(source="upload", type="filepath", label="Upload Audio", + interactive= True, visible= True) + audio2 = gr.Audio(source="microphone", label="Record Audio", type="filepath", + interactive= True, visible= False) + video1 = gr.Video(source="upload", type="filepath", label="Upload Video", + interactive= True, visible= False) + video2 = gr.Video(source="webcam", label="Record Video", type="filepath",include_audio= True, + interactive= True, visible= False) + file_in = gr.Files(label="Upload File or Files", interactive= True, visible= False) + + submit = gr.Button() + + with gr.Column(): + + out_txt = gr.Textbox(label="Output", + visible= True, show_copy_button=True) + + out_json = gr.JSON(label="JSON Output", + visible= False, show_copy_button=True) + + annoation = gr.Textbox(label="Name your speaker's", + info= "Please provide a list of the speakers arranged \ + in the order in which they appear in the input. Use comma ',' \ + as a seperator. Be aware that the first name is given \ + to SPEAKER_00 the second to SPEAKER_01 and so on.", + visible= False, interactive= True) + + annotate = gr.Button(value="Annotate", visible= False, interactive= True) + + # Define usage of components + input.change(fn=select_origin, inputs=[input], + outputs=[audio1, audio2, video1, video2, file_in]) + + task.change(fn=select_task, inputs=[task], + outputs=[num_speakers, translate, language]) + + translate.change(fn= lambda x : gr.update(value = x), + inputs=[translate], outputs=[translate]) + num_speakers.change(fn= lambda x : gr.update(value = x), + inputs=[num_speakers], outputs=[num_speakers]) + language.change(fn= lambda x : gr.update(value = x), + inputs=[language], outputs=[language]) + + submit.click(fn = run_scraibe, + inputs=[task, num_speakers, translate, language, audio1, + audio2, video1, video2, file_in], + outputs=[out_txt, out_json, annoation, annotate]) + + annotate.click(fn = annotate_output, inputs=[annoation, out_json], + outputs=[out_txt, out_json]) + + return demo \ No newline at end of file diff --git a/scraibe/app/stg.py b/scraibe/app/stg.py new file mode 100644 index 0000000..9b227a1 --- /dev/null +++ b/scraibe/app/stg.py @@ -0,0 +1,157 @@ +""" +stg - scraibe to gradio interface + +This file contains the code for the scraibe to gradio interface. +It makes adds gradio interactions to the scraibe class in the back. + +""" + +import json +import gradio as gr +from tqdm import tqdm +from scraibe import Scraibe + + +class GradioTranscriptionInterface: + """ + Interface handling the interaction between Gradio UI and the Audio Transcription system. + """ + + def __init__(self, model: Scraibe): + """ + Initializes the GradioTranscriptionInterface with a transcription model. + + Args: + model (Scraibe): Model responsible for audio transcription tasks. + """ + self.model = model + + def auto_transcribe(self, source, + num_speakers : int, + translation : bool, + language : str): + """ + Shortcut method for the Scraibe task. + + Returns: + tuple: Transcribed text (str), JSON output (dict) + """ + + kwargs = { + "num_speakers": num_speakers if num_speakers != 0 else None, + "language": language if language != "None" else None, + "task": 'translate' if translation else None + } + if isinstance(source, str): + try: + result = self.model.autotranscribe(source, **kwargs) + except ValueError: + raise gr.Error("Couldn't detect any speech in the provided audio. \ + Please try again!") + + return str(result), result.get_json() + + elif isinstance(source, list): + source_names = [s.split("/")[-1] for s in source] + result = [] + for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): + try: + res = self.model.autotranscribe(s, **kwargs) + except ValueError: + _name = s.split("/")[-1] + res = f"NO TRANSCRIPT FOUND FOR {_name}" + gr.Warning(f"Couldn't detect any speech in {_name} will skip this file.") + result.append(res) + + out = '' + out_dict = {} + for i, r in enumerate(result): + out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" + out += str(r) + out += "\n\n" + + if isinstance(r, str): + out_dict[source_names[i]] = r + else: + out_dict[source_names[i]] = r.get_dict() + + return out, json.dumps(out_dict, indent=4) + + else: + raise gr.Error("Please provide a valid audio file.") + + + def transcribe(self, source, translation, language): + """ + Shortcut method for the Transcribe task. + + Returns: + str: Transcribed text. + """ + kwargs = { + "language": language if language != "None" else None, + "task": 'translate' if translation == "Yes" else None + } + + if isinstance(source, str): + result = self.model.transcribe(source, **kwargs) + + return str(result) + + elif isinstance(source, list): + source_names = [s.split("/")[-1] for s in source] + result = [] + for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): + res = self.model.transcribe(s, **kwargs) + result.append(res) + + out = '' + for i, res in enumerate(result): + out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" + out += str(res) + out += "\n\n" + + return out + + else: + raise gr.Error("Please provide a valid audio file.") + + def perform_diarisation(self, source, num_speakers): + """ + Shortcut method for the Diarisation task. + + Returns: + str: JSON output of diarisation result. + """ + kwargs = { + "num_speakers": num_speakers if num_speakers != 0 else None, + } + + if isinstance(source, str): + try: + result = self.model.diarization(source, **kwargs) + except ValueError: + raise gr.Error("Couldn't detect any speech in the provided audio. \ + Please try again!") + + return json.dumps(result, indent=2) + elif isinstance(source, list): + source_names = [s.split("/")[-1] for s in source] + result = [] + for s in tqdm(source, total=len(source),desc = "Performing diarisation"): + try: + res = self.model.diarization(s, **kwargs) + except ValueError: + res = f"NO DIARISATION FOUND FOR {s}" + gr.Warning(f"Couldn't detect any speech in {s} will skip this file.") + result.append(res) + + out = {} + + for i, res in enumerate(result): + out[source_names[i]] = res + + return json.dumps(out, indent=4) + + else: + gr.Error("Please provide a valid audio file.") From 93e5ce15f95dabe126501d982c88c5b928949cfe Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sat, 25 Nov 2023 15:17:12 +0100 Subject: [PATCH 068/331] make gradio working with treads --- scraibe/app/__init__.py | 2 +- scraibe/app/global_var.py | 11 +++++++++- scraibe/app/interactions.py | 19 +++++++++++----- scraibe/app/interface.py | 7 +----- scraibe/app/multi.py | 44 +++++++++++++++++++++++++++++++++++++ scraibe/app/stg.py | 39 +++++++++++++++++++++++++------- 6 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 scraibe/app/multi.py diff --git a/scraibe/app/__init__.py b/scraibe/app/__init__.py index 9e04a48..fa8f8f7 100644 --- a/scraibe/app/__init__.py +++ b/scraibe/app/__init__.py @@ -1,5 +1,5 @@ from .qtfaststart import * -from .activity_tracker import * +from .multi import * from .interface import * from .stg import * from .interactions import * diff --git a/scraibe/app/global_var.py b/scraibe/app/global_var.py index 191e3e6..6d8f3cf 100644 --- a/scraibe/app/global_var.py +++ b/scraibe/app/global_var.py @@ -3,7 +3,16 @@ Stores global variables for the app. """ # Global variable to store the model +from threading import Event + +import time + + MODEL = None +MODEL_THREAD_PARAMS = None +MODEL_THREAD = None # Global variable to track user activity -USER_ACTIVE = False \ No newline at end of file +LAST_USED = time.time() +TIMEOUT = 30 #seconds +TRANSCRIBE_ACTIVE = Event() \ No newline at end of file diff --git a/scraibe/app/interactions.py b/scraibe/app/interactions.py index 10659c0..6151d64 100644 --- a/scraibe/app/interactions.py +++ b/scraibe/app/interactions.py @@ -3,10 +3,12 @@ This file contains ervery function that will be called when the user interacts w UI like pressing a button or uploading a file. """ -from math import pi +import time import gradio as gr import scraibe.app.global_var as gv from scraibe import Transcript +from scraibe.app.stg import GradioTranscriptionInterface +import threading def select_task(choice): # tell the app that it is still in use @@ -84,11 +86,18 @@ def run_scraibe(task, file_in, progress = gr.Progress(track_tqdm= True)): - # get *args which are not None + # get *args which are not None - pipe = gv.MODEL - - progress(0, desc='Starting task...') + if gv.MODEL is None and gv.MODEL_THREAD_PARAMS is not None: + progress(0, desc='Model was not loaded to conserve resources. Loading model...') + time.sleep(1) + gv.MODEL_THREAD = threading.Thread(**gv.MODEL_THREAD_PARAMS) + gv.MODEL_THREAD.start() + gv.MODEL_THREAD.join() + + pipe = GradioTranscriptionInterface() + + progress(0.1, desc='Starting task...') source = audio1 or audio2 or video1 or video2 or file_in if isinstance(source, list): diff --git a/scraibe/app/interface.py b/scraibe/app/interface.py index ef9d818..ddf10ee 100644 --- a/scraibe/app/interface.py +++ b/scraibe/app/interface.py @@ -9,8 +9,6 @@ import scraibe.app.global_var as gv from .interactions import * from .stg import * -from scraibe import Scraibe - theme = gr.themes.Soft( primary_hue="green", secondary_hue='orange', @@ -36,10 +34,7 @@ LANGUAGES = [ CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) -def gradio_Interface(pipe : Scraibe = None): - - if pipe is not None: - gv.MODEL = GradioTranscriptionInterface(pipe) +def gradio_Interface(): with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py new file mode 100644 index 0000000..4aa0c09 --- /dev/null +++ b/scraibe/app/multi.py @@ -0,0 +1,44 @@ +""" +This file contains the functions which are related to monitoring the actual app usage. +Therefore, the app is to be more efficient in the usage of the resources. +By for example, unloading or reloading the model. +""" + +import time +import gc +from typing import Union +import torch + +import scraibe.app.global_var as gv +from scraibe.autotranscript import Scraibe + + +def load_model_thread(model : Union[Scraibe, dict] = None): + if model is None: + gv.MODEL = Scraibe() + elif type(model) is Scraibe: + gv.MODEL = model + elif type(model) is dict: + gv.MODEL = Scraibe(**model) + else: + raise TypeError("model must be of type Scraibe, or dict") + + gv.LAST_USED = time.time() + +# Create a thread to monitor user activity +def delete_unused_model(): + while True: + + _unload_porperty = (not gv.TRANSCRIBE_ACTIVE.is_set() and (time.time() - gv.LAST_USED > gv.TIMEOUT) and gv.MODEL is not None) + + if _unload_porperty: + + del gv.MODEL + gv.MODEL = None + + gc.collect() + torch.cuda.empty_cache() + + gv.MODEL_THREAD.join() + + time.sleep(int(gv.TIMEOUT/5)) diff --git a/scraibe/app/stg.py b/scraibe/app/stg.py index 9b227a1..0215903 100644 --- a/scraibe/app/stg.py +++ b/scraibe/app/stg.py @@ -9,7 +9,8 @@ It makes adds gradio interactions to the scraibe class in the back. import json import gradio as gr from tqdm import tqdm -from scraibe import Scraibe + +import scraibe.app.global_var as gv class GradioTranscriptionInterface: @@ -17,14 +18,14 @@ class GradioTranscriptionInterface: Interface handling the interaction between Gradio UI and the Audio Transcription system. """ - def __init__(self, model: Scraibe): + def __init__(self): """ Initializes the GradioTranscriptionInterface with a transcription model. Args: model (Scraibe): Model responsible for audio transcription tasks. """ - self.model = model + self.model = gv.MODEL def auto_transcribe(self, source, num_speakers : int, @@ -37,6 +38,8 @@ class GradioTranscriptionInterface: tuple: Transcribed text (str), JSON output (dict) """ + gv.TRANSCRIBE_ACTIVE.set() + kwargs = { "num_speakers": num_speakers if num_speakers != 0 else None, "language": language if language != "None" else None, @@ -46,9 +49,11 @@ class GradioTranscriptionInterface: try: result = self.model.autotranscribe(source, **kwargs) except ValueError: + gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Couldn't detect any speech in the provided audio. \ Please try again!") - + + gv.TRANSCRIBE_ACTIVE.clear() return str(result), result.get_json() elif isinstance(source, list): @@ -74,10 +79,14 @@ class GradioTranscriptionInterface: out_dict[source_names[i]] = r else: out_dict[source_names[i]] = r.get_dict() + + + gv.TRANSCRIBE_ACTIVE.clear() return out, json.dumps(out_dict, indent=4) else: + gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Please provide a valid audio file.") @@ -88,14 +97,17 @@ class GradioTranscriptionInterface: Returns: str: Transcribed text. """ + + gv.TRANSCRIBE_ACTIVE.set() + kwargs = { "language": language if language != "None" else None, "task": 'translate' if translation == "Yes" else None } - + if isinstance(source, str): result = self.model.transcribe(source, **kwargs) - + gv.TRANSCRIBE_ACTIVE.clear() return str(result) elif isinstance(source, list): @@ -111,9 +123,12 @@ class GradioTranscriptionInterface: out += str(res) out += "\n\n" + gv.TRANSCRIBE_ACTIVE.clear() + return out else: + gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Please provide a valid audio file.") def perform_diarisation(self, source, num_speakers): @@ -123,6 +138,9 @@ class GradioTranscriptionInterface: Returns: str: JSON output of diarisation result. """ + + gv.TRANSCRIBE_ACTIVE.set() + kwargs = { "num_speakers": num_speakers if num_speakers != 0 else None, } @@ -131,9 +149,10 @@ class GradioTranscriptionInterface: try: result = self.model.diarization(source, **kwargs) except ValueError: + gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Couldn't detect any speech in the provided audio. \ Please try again!") - + gv.TRANSCRIBE_ACTIVE.clear() return json.dumps(result, indent=2) elif isinstance(source, list): source_names = [s.split("/")[-1] for s in source] @@ -142,6 +161,7 @@ class GradioTranscriptionInterface: try: res = self.model.diarization(s, **kwargs) except ValueError: + res = f"NO DIARISATION FOUND FOR {s}" gr.Warning(f"Couldn't detect any speech in {s} will skip this file.") result.append(res) @@ -150,8 +170,11 @@ class GradioTranscriptionInterface: for i, res in enumerate(result): out[source_names[i]] = res - + + gv.TRANSCRIBE_ACTIVE.clear() + return json.dumps(out, indent=4) else: + gv.TRANSCRIBE_ACTIVE.clear() gr.Error("Please provide a valid audio file.") From db435c1fddf4d451e4946e8fe21ede751eda1d62 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sat, 25 Nov 2023 15:18:09 +0100 Subject: [PATCH 069/331] removed file --- scraibe/app/activity_tracker.py | 37 --------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 scraibe/app/activity_tracker.py diff --git a/scraibe/app/activity_tracker.py b/scraibe/app/activity_tracker.py deleted file mode 100644 index 5cced3b..0000000 --- a/scraibe/app/activity_tracker.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -This file contains the functions which are related to monitoring the actual app usage. -Therefore, the app is to be more efficient in the usage of the resources. -By for example, unloading or reloading the model. -""" -import time -import threading -import torch -import gc -import gradio as gr - - -timeout = 30 #seconds -USER_ACTIVE = True -user_active_lock = threading.Lock() # dummy for now - -# Create a thread to monitor user activity -def monitor_activity(model, pipe, timeout=timeout): - global USER_ACTIVE - - while True: - time.sleep(timeout) # Check user activity every second - with user_active_lock: - - if not USER_ACTIVE: - del model - del pipe - - gc.collect() - torch.cuda.empty_cache() - - - - print("Model deleted empty memory") - gr.Warning("Model unloaded due to inactivity. Please reload the model to continue.") - break - USER_ACTIVE = False \ No newline at end of file From 32b27442e61078c5ea89b0208fc5537f8fb27d1d Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Sat, 25 Nov 2023 16:38:13 +0100 Subject: [PATCH 070/331] testing if multiprosessing works better --- test_multiprocessing.py | 105 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test_multiprocessing.py diff --git a/test_multiprocessing.py b/test_multiprocessing.py new file mode 100644 index 0000000..ad9edc2 --- /dev/null +++ b/test_multiprocessing.py @@ -0,0 +1,105 @@ +import multiprocessing +import os +import threading +import queue +import time +import torch +from scraibe import Scraibe + +def input_thread(input_queue, processed_event): + while True: + processed_event.wait() # Wait for the previous input to be processed + processed_event.clear() # Clear the event for the next input + inp = input("Enter the path to the audio file ('q' to quit, 'reload' to reload model): ") + input_queue.put(inp) + +def clear_queue(queue): + while not queue.empty(): + try: + queue.get_nowait() + except queue.Empty: + continue + +def model_worker(request_queue, last_active_time, response_queue,loaded_event, running_event): + + loaded_event.set() + + model = Scraibe(dia_model="models/pyannote/config.yaml") + + while True: + audio_path = request_queue.get() + if audio_path == "STOP": + break + running_event.set() + transcription = model.autotranscribe(audio_path) + running_event.clear() + response_queue.put(transcription) + last_active_time.value = time.time() + + del model + torch.cuda.empty_cache() + clear_queue(request_queue) + clear_queue(response_queue) + loaded_event.clear() + + +def start_model_worker(request_queue, last_active_time, response_queue,loaded_event, running_event): + model_process = multiprocessing.Process(target=model_worker, args=(request_queue, last_active_time, response_queue,loaded_event, running_event)) + model_process.start() + return model_process + +def timer_thread(request_queue, last_active_time,loaded_event, running_event, timeout=30): + while True: + time.sleep(timeout) + + if time.time() - last_active_time.value > timeout and loaded_event.is_set() and not running_event.is_set(): + print(f"No activity for the last {timeout} seconds. Stopping the model worker.", flush=True) + request_queue.put("STOP") + +if __name__ == "__main__": + request_queue = multiprocessing.Queue() + response_queue = multiprocessing.Queue() + input_queue = queue.Queue() + last_active_time = multiprocessing.Value('d', time.time()) + loaded_event = multiprocessing.Event() + running_event = multiprocessing.Event() + + processed_event = multiprocessing.Event() + processed_event.set() # Initially set to allow the first input + + model_process = start_model_worker(request_queue, last_active_time, response_queue,loaded_event ,running_event) + timer = threading.Thread(target=timer_thread, args=(request_queue, last_active_time, loaded_event, running_event), daemon=True) + input_handler = threading.Thread(target=input_thread, args=(input_queue,processed_event)) + + timer.start() + input_handler.start() + + while True: + + audio_file_path = input_queue.get() # Get input from the input thread + print(audio_file_path) + + if audio_file_path.lower() == 'q': + request_queue.put("STOP") + model_process.join() + break + elif audio_file_path.lower() == 'reload': + if loaded_event.is_set(): + request_queue.put("STOP") + model_process.join() + model_process = start_model_worker(request_queue, last_active_time, response_queue, loaded_event, running_event) + print("Model reloaded.") + elif not os.path.exists(audio_file_path): + print("File does not exist.") + else: + if not loaded_event.is_set(): + model_process = start_model_worker(request_queue, last_active_time, response_queue, loaded_event, running_event) + request_queue.put(audio_file_path) + transcription = response_queue.get() + print(transcription) + + processed_event.set() # Signal that the input has been processed + + model_process.join() + timer.join() + input_handler.join() From 7c8185e94bff2265be5a5b108bc33b4e5978bf03 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:04:51 +0100 Subject: [PATCH 071/331] Create mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/mirror_to_gitlab.yml diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml new file mode 100644 index 0000000..88f1c8a --- /dev/null +++ b/.github/workflows/mirror_to_gitlab.yml @@ -0,0 +1,21 @@ +name: Mirror and run GitLab CI + +on: [push, delete] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Mirror + trigger CI + uses: SvanBoxel/gitlab-mirror-and-ci-action@master + with: + args: "https://git-dmz.thuenen.de/kida/i2-skills-beratungsstelle/scraibe" + env: + FOLLOW_TAGS: "false" + FORCE_PUSH: "false" + GITLAB_HOSTNAME: "git-dmz.thuenen.de" + GITLAB_USERNAME: ${{ secrets.GITLAB_USERNAME }} + GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }} // Generate here: https://gitlab.com/profile/personal_access_tokens + GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} // https://gitlab.com///edit + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} // https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret From 034ef1a711ae160bc0fdf67a8592ee3e31fc66af Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:29:39 +0100 Subject: [PATCH 072/331] Update mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index 88f1c8a..52b8d86 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -18,4 +18,4 @@ jobs: GITLAB_USERNAME: ${{ secrets.GITLAB_USERNAME }} GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }} // Generate here: https://gitlab.com/profile/personal_access_tokens GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} // https://gitlab.com///edit - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} // https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} // https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret From 942055eafca224f6fa9e565593f1ce9f6b1c76e9 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:31:40 +0100 Subject: [PATCH 073/331] Update mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index 52b8d86..4c18d68 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -16,6 +16,6 @@ jobs: FORCE_PUSH: "false" GITLAB_HOSTNAME: "git-dmz.thuenen.de" GITLAB_USERNAME: ${{ secrets.GITLAB_USERNAME }} - GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }} // Generate here: https://gitlab.com/profile/personal_access_tokens - GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} // https://gitlab.com///edit - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} // https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret + GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }} + GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} From 072168334d64698d8443249315fe7eb03a8add71 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:42:14 +0100 Subject: [PATCH 074/331] Update mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index 4c18d68..eea0809 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -13,7 +13,7 @@ jobs: args: "https://git-dmz.thuenen.de/kida/i2-skills-beratungsstelle/scraibe" env: FOLLOW_TAGS: "false" - FORCE_PUSH: "false" + FORCE_PUSH: "true" GITLAB_HOSTNAME: "git-dmz.thuenen.de" GITLAB_USERNAME: ${{ secrets.GITLAB_USERNAME }} GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }} From 32f0f070f965ed79cb849dec49ed04e96ac164c2 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:45:01 +0100 Subject: [PATCH 075/331] Update mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index eea0809..a32712e 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -12,7 +12,7 @@ jobs: with: args: "https://git-dmz.thuenen.de/kida/i2-skills-beratungsstelle/scraibe" env: - FOLLOW_TAGS: "false" + FOLLOW_TAGS: "true" FORCE_PUSH: "true" GITLAB_HOSTNAME: "git-dmz.thuenen.de" GITLAB_USERNAME: ${{ secrets.GITLAB_USERNAME }} From 65ca71fc9171e90f7e4188c8c91e604d9de1dc2a Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:50:09 +0100 Subject: [PATCH 076/331] Update mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index a32712e..74eaca0 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -6,7 +6,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 + with: + - fetch-depth: 0 - name: Mirror + trigger CI uses: SvanBoxel/gitlab-mirror-and-ci-action@master with: From 2499dd1d17f13d06e3b36f80940dcbf34ad6d69e Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Thu, 7 Dec 2023 13:51:51 +0100 Subject: [PATCH 077/331] Update mirror_to_gitlab.yml --- .github/workflows/mirror_to_gitlab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index 74eaca0..b100359 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - - fetch-depth: 0 + fetch-depth: 0 - name: Mirror + trigger CI uses: SvanBoxel/gitlab-mirror-and-ci-action@master with: From 9eb9f5af8d8a531d431bb3ae3d7dd8cf6b0f16ec Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Thu, 7 Dec 2023 16:22:52 +0100 Subject: [PATCH 078/331] Make everything work in processes and adding config to customize instance --- scraibe/app/config.yml | 48 +++++++++++++++++++ scraibe/app/global_var.py | 20 +++++--- scraibe/app/interactions.py | 49 ++++++++++---------- scraibe/app/multi.py | 91 +++++++++++++++++++++++++++---------- scraibe/app/stg.py | 87 ++++++++++++++++++----------------- scraibe/app/utils.py | 42 +++++++++++++++++ 6 files changed, 241 insertions(+), 96 deletions(-) create mode 100644 scraibe/app/config.yml create mode 100644 scraibe/app/utils.py diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml new file mode 100644 index 0000000..16d296c --- /dev/null +++ b/scraibe/app/config.yml @@ -0,0 +1,48 @@ +launch: + # The following are the default values for the launch configuration + # for more informations look at https://www.gradio.app/docs/interface + server_port: 8080 + server_name: 0.0.0.0 + inbrowser: true + inline: false + max-threads: 40 + quiet: false + auth: + enabled: false + username: admin + password: admin + auth_message: "Please enter your credentials" + show_error : false + favicon_path : null + ssl_keyfile : null + ssl_certfile : null + ssl_keyfile_password : null + ssl_verify : false + quiet : false + show_api : false + allowed_paths : null + blocked_paths : null + root_path : null + app_kwargs : null + state_session_capacity : 1000 + share_server_address : null + share_server_protocol : null + share : false + debug : false +queue: + # The following are the default values for the queue configuration + # for more informations look at hhttps://www.gradio.app/docs/interface + status_update_rate : 'auto' + api_open : null + max_size : null + concurrency_count : null + default_concurrency_limit : 'not_set' +layout: + header: scraibe/app/header.html + footer: null + logo: scraibe/app/logo.svg +model: + whisper_model : null + dia_model: null +advanced: + timeout: 300 #seconds e.g. 5 minutes diff --git a/scraibe/app/global_var.py b/scraibe/app/global_var.py index 6d8f3cf..99f6eea 100644 --- a/scraibe/app/global_var.py +++ b/scraibe/app/global_var.py @@ -3,16 +3,22 @@ Stores global variables for the app. """ # Global variable to store the model -from threading import Event - +import multiprocessing +import os import time +import yaml +REQUEST_QUEUE = multiprocessing.Queue() # audio file path as string +RESPONSE_QUEUE = multiprocessing.Queue() # transcription as string +LAST_ACTIVE_TIME = multiprocessing.Value('d', time.time()) # time of last activity +LOADED_EVENT = multiprocessing.Event() # model loaded event +RUNNING_EVENT = multiprocessing.Event() # model running event -MODEL = None -MODEL_THREAD_PARAMS = None -MODEL_THREAD = None +MODEL_PARAMS = None # model parameters +MODEL_PROCESS = None # model process to handle globally # Global variable to track user activity LAST_USED = time.time() -TIMEOUT = 30 #seconds -TRANSCRIBE_ACTIVE = Event() \ No newline at end of file +TIMEOUT = None #seconds + +DEFAULT_APP_CONIFG_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config.yml") diff --git a/scraibe/app/interactions.py b/scraibe/app/interactions.py index 6151d64..1719388 100644 --- a/scraibe/app/interactions.py +++ b/scraibe/app/interactions.py @@ -3,12 +3,12 @@ This file contains ervery function that will be called when the user interacts w UI like pressing a button or uploading a file. """ +from re import M import time import gradio as gr import scraibe.app.global_var as gv from scraibe import Transcript -from scraibe.app.stg import GradioTranscriptionInterface -import threading +from .multi import start_model_worker def select_task(choice): # tell the app that it is still in use @@ -84,33 +84,37 @@ def run_scraibe(task, video1, video2, file_in, - progress = gr.Progress(track_tqdm= True)): + progress = gr.Progress(track_tqdm=False)): # get *args which are not None + if gv.MODEL_PROCESS is None or not gv.MODEL_PROCESS.is_alive(): + #progress(0.0, desc='Loading model...') + gv.MODEL_PROCESS = start_model_worker(gv.MODEL_PARAMS, + gv.REQUEST_QUEUE, + gv.LAST_ACTIVE_TIME, + gv.RESPONSE_QUEUE, + gv.LOADED_EVENT, + gv.RUNNING_EVENT) - if gv.MODEL is None and gv.MODEL_THREAD_PARAMS is not None: - progress(0, desc='Model was not loaded to conserve resources. Loading model...') - time.sleep(1) - gv.MODEL_THREAD = threading.Thread(**gv.MODEL_THREAD_PARAMS) - gv.MODEL_THREAD.start() - gv.MODEL_THREAD.join() - - pipe = GradioTranscriptionInterface() - - progress(0.1, desc='Starting task...') + # progress(0.1, desc='Starting task...') source = audio1 or audio2 or video1 or video2 or file_in if isinstance(source, list): source = [s.name for s in source] if len(source) == 1: source = source[0] - + + config = dict(source = source, + task = task, + num_speakers = num_speakers, + translate = translate, + language = language) + + gv.REQUEST_QUEUE.put(config) + if task == 'Auto Transcribe': - - out_str , out_json = pipe.auto_transcribe(source = source, - num_speakers = num_speakers, - translation = translate, - language = language) + + out_str , out_json = gv.RESPONSE_QUEUE.get() if isinstance(source, str): return (gr.update(value = out_str, visible = True), @@ -125,9 +129,7 @@ def run_scraibe(task, elif task == 'Transcribe': - out = pipe.transcribe(source = source, - translation = translate, - language = language) + out = gv.RESPONSE_QUEUE.get() return (gr.update(value = out, visible = True), gr.update(value = None, visible = False), @@ -136,8 +138,7 @@ def run_scraibe(task, elif task == 'Diarisation': - out = pipe.perform_diarisation(source = source, - num_speakers = num_speakers) + out = gv.RESPONSE_QUEUE.get() return (gr.update(value = None, visible = False), gr.update(value = out, visible = True), diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py index 4aa0c09..17fd1bb 100644 --- a/scraibe/app/multi.py +++ b/scraibe/app/multi.py @@ -4,41 +4,86 @@ Therefore, the app is to be more efficient in the usage of the resources. By for example, unloading or reloading the model. """ + + import time import gc from typing import Union +import multiprocessing import torch +import signal -import scraibe.app.global_var as gv -from scraibe.autotranscript import Scraibe +from gradio import Warning +from scraibe.autotranscript import Scraibe +from .stg import GradioTranscriptionInterface + +def init_worker(): + signal.signal(signal.SIGINT, signal.SIG_IGN) -def load_model_thread(model : Union[Scraibe, dict] = None): - if model is None: - gv.MODEL = Scraibe() - elif type(model) is Scraibe: - gv.MODEL = model - elif type(model) is dict: - gv.MODEL = Scraibe(**model) +def clear_queue(queue): + while not queue.empty(): + try: + queue.get_nowait() + except queue.Empty: + continue + +def model_worker(model_params : Union[Scraibe, dict], + request_queue, + last_active_time, + response_queue, + loaded_event, + running_event, + *args, **kwargs): + + loaded_event.set() + + if model_params is None: + _model = Scraibe() + elif type(model_params) is Scraibe: + _model = model_params + elif type(model_params) is dict: + _model = Scraibe(**model_params) else: raise TypeError("model must be of type Scraibe, or dict") - gv.LAST_USED = time.time() - -# Create a thread to monitor user activity -def delete_unused_model(): + model = GradioTranscriptionInterface(_model) + while True: - _unload_porperty = (not gv.TRANSCRIBE_ACTIVE.is_set() and (time.time() - gv.LAST_USED > gv.TIMEOUT) and gv.MODEL is not None) + req = request_queue.get() - if _unload_porperty: + if req == "STOP": - del gv.MODEL - gv.MODEL = None - - gc.collect() - torch.cuda.empty_cache() + break + elif type(req) is dict: + runner = model.get_task_from_str(req.pop("task")) + running_event.set() + transcription = runner(**req) + running_event.clear() + response_queue.put(transcription) + last_active_time.value = time.time() + else: + raise TypeError("request must be of type dict") - gv.MODEL_THREAD.join() - - time.sleep(int(gv.TIMEOUT/5)) + del model + torch.cuda.empty_cache() + gc.collect() + clear_queue(request_queue) + clear_queue(response_queue) + loaded_event.clear() + +def start_model_worker(model_params, request_queue, last_active_time, response_queue,loaded_event, running_event, *args, **kwargs): + context = multiprocessing.get_context('spawn') + model_process = context.Process(target=model_worker, args=(model_params, request_queue, last_active_time, response_queue,loaded_event, running_event, *args), kwargs=kwargs) + model_process.start() + return model_process + +def timer_thread(request_queue, last_active_time,loaded_event, running_event, timeout=30): + while True: + time.sleep(timeout) + + if time.time() - last_active_time.value > timeout and loaded_event.is_set() and not running_event.is_set(): + print(f"No activity for the last {timeout} seconds. Stopping the model worker.", flush=True) + request_queue.put("STOP") + Warning("Model worker stopped due to inactivity.") \ No newline at end of file diff --git a/scraibe/app/stg.py b/scraibe/app/stg.py index 0215903..1b9caf7 100644 --- a/scraibe/app/stg.py +++ b/scraibe/app/stg.py @@ -18,19 +18,19 @@ class GradioTranscriptionInterface: Interface handling the interaction between Gradio UI and the Audio Transcription system. """ - def __init__(self): + def __init__(self, model): """ Initializes the GradioTranscriptionInterface with a transcription model. Args: model (Scraibe): Model responsible for audio transcription tasks. """ - self.model = gv.MODEL + self.model = model - def auto_transcribe(self, source, + def autotranscribe(self, source, num_speakers : int, - translation : bool, - language : str): + translate : bool, + language : str,*args ,**kwargs): """ Shortcut method for the Scraibe task. @@ -38,22 +38,18 @@ class GradioTranscriptionInterface: tuple: Transcribed text (str), JSON output (dict) """ - gv.TRANSCRIBE_ACTIVE.set() - - kwargs = { + _kwargs = { "num_speakers": num_speakers if num_speakers != 0 else None, "language": language if language != "None" else None, - "task": 'translate' if translation else None + "task": 'translate' if translate else None } if isinstance(source, str): try: - result = self.model.autotranscribe(source, **kwargs) + result = self.model.autotranscribe(source, **_kwargs) except ValueError: - gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Couldn't detect any speech in the provided audio. \ Please try again!") - - gv.TRANSCRIBE_ACTIVE.clear() + return str(result), result.get_json() elif isinstance(source, list): @@ -61,7 +57,7 @@ class GradioTranscriptionInterface: result = [] for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): try: - res = self.model.autotranscribe(s, **kwargs) + res = self.model.autotranscribe(s, **_kwargs) except ValueError: _name = s.split("/")[-1] res = f"NO TRANSCRIPT FOUND FOR {_name}" @@ -79,42 +75,36 @@ class GradioTranscriptionInterface: out_dict[source_names[i]] = r else: out_dict[source_names[i]] = r.get_dict() - - - gv.TRANSCRIBE_ACTIVE.clear() return out, json.dumps(out_dict, indent=4) else: - gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Please provide a valid audio file.") - def transcribe(self, source, translation, language): + def transcribe(self, source, translate, language,*args ,**kwargs): """ Shortcut method for the Transcribe task. Returns: str: Transcribed text. """ - - gv.TRANSCRIBE_ACTIVE.set() - - kwargs = { + + _kwargs = { "language": language if language != "None" else None, - "task": 'translate' if translation == "Yes" else None + "task": 'translate' if translate == "Yes" else None } if isinstance(source, str): - result = self.model.transcribe(source, **kwargs) - gv.TRANSCRIBE_ACTIVE.clear() + result = self.model.transcribe(source, **_kwargs) + return str(result) elif isinstance(source, list): source_names = [s.split("/")[-1] for s in source] result = [] for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): - res = self.model.transcribe(s, **kwargs) + res = self.model.transcribe(s, **_kwargs) result.append(res) out = '' @@ -123,15 +113,12 @@ class GradioTranscriptionInterface: out += str(res) out += "\n\n" - gv.TRANSCRIBE_ACTIVE.clear() - return out else: - gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Please provide a valid audio file.") - def perform_diarisation(self, source, num_speakers): + def diarisation(self, source, num_speakers, *args ,**kwargs): """ Shortcut method for the Diarisation task. @@ -139,27 +126,24 @@ class GradioTranscriptionInterface: str: JSON output of diarisation result. """ - gv.TRANSCRIBE_ACTIVE.set() - - kwargs = { + _kwargs = { "num_speakers": num_speakers if num_speakers != 0 else None, } if isinstance(source, str): try: - result = self.model.diarization(source, **kwargs) + result = self.model.diarization(source, **_kwargs) except ValueError: - gv.TRANSCRIBE_ACTIVE.clear() raise gr.Error("Couldn't detect any speech in the provided audio. \ Please try again!") - gv.TRANSCRIBE_ACTIVE.clear() + return json.dumps(result, indent=2) elif isinstance(source, list): source_names = [s.split("/")[-1] for s in source] result = [] for s in tqdm(source, total=len(source),desc = "Performing diarisation"): try: - res = self.model.diarization(s, **kwargs) + res = self.model.diarization(s, **_kwargs) except ValueError: res = f"NO DIARISATION FOUND FOR {s}" @@ -171,10 +155,29 @@ class GradioTranscriptionInterface: for i, res in enumerate(result): out[source_names[i]] = res - gv.TRANSCRIBE_ACTIVE.clear() - return json.dumps(out, indent=4) else: - gv.TRANSCRIBE_ACTIVE.clear() - gr.Error("Please provide a valid audio file.") + gr.Error("Please provide a valid audio file.") + + def get_task_from_str(self, task): + """ + Returns the coresponing task function based on the task string. + + params: + task (str): Task string. Can be one of the following: + - 'Auto Transcribe' + - 'Transcribe' + - 'Diarisation' + """ + + if task == 'Auto Transcribe': + return self.autotranscribe + elif task == 'Transcribe': + return self.transcribe + elif task == 'Diarisation': + return self.diarisation + else: + raise ValueError("Invalid task string.") + + diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py new file mode 100644 index 0000000..b41a88f --- /dev/null +++ b/scraibe/app/utils.py @@ -0,0 +1,42 @@ +import scraibe.app.global_var as gv +import yaml + +def load_config(original_config_path = gv.DEFAULT_APP_CONIFG_PATH, override_yaml_path=None, **kwargs): + + + # Load the original configuration + with open(original_config_path, 'r') as file: + config = yaml.safe_load(file) + + # Override with another YAML file if provided + if override_yaml_path: + with open(override_yaml_path, 'r') as file: + override_config = yaml.safe_load(file) + apply_overrides(config, override_config) + + # Apply overrides from kwargs + apply_overrides(config, kwargs) + + return config + +def apply_overrides(orig_dict, override_dict): + """ Recursively apply overrides to the configuration. """ + for key, value in override_dict.items(): + if isinstance(value, dict): + # If the value is a dict, apply recursively + apply_overrides(orig_dict.get(key, {}), value) + else: + # If the value is not a dict, search for the key and update + if update_nested_key(orig_dict, key, value): + continue # Key was found and updated + orig_dict[key] = value # Key not found, update at this level + +def update_nested_key(d, key, value): + """ Recursively search and update the key in nested dictionary. """ + if key in d: + d[key] = value + return True + for k, v in d.items(): + if isinstance(v, dict) and update_nested_key(v, key, value): + return True + return False \ No newline at end of file From 4379d1e185be9bcaaddbc3ef4704fe5686dab532 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 8 Dec 2023 14:30:26 +0100 Subject: [PATCH 079/331] removes signal --- scraibe/app/multi.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py index 17fd1bb..ce61f70 100644 --- a/scraibe/app/multi.py +++ b/scraibe/app/multi.py @@ -11,16 +11,11 @@ import gc from typing import Union import multiprocessing import torch -import signal from gradio import Warning from scraibe.autotranscript import Scraibe from .stg import GradioTranscriptionInterface -def init_worker(): - signal.signal(signal.SIGINT, signal.SIG_IGN) - - def clear_queue(queue): while not queue.empty(): try: From 42f86ba2ca7e4682f34471e754483b2320e40bb4 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 8 Dec 2023 14:30:43 +0100 Subject: [PATCH 080/331] changed pyannote path --- scraibe/app/config.yml | 2 +- scraibe/cli.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml index 16d296c..4eda2e4 100644 --- a/scraibe/app/config.yml +++ b/scraibe/app/config.yml @@ -11,7 +11,7 @@ launch: enabled: false username: admin password: admin - auth_message: "Please enter your credentials" + auth_message: null show_error : false favicon_path : null ssl_keyfile : null diff --git a/scraibe/cli.py b/scraibe/cli.py index b05da92..c023f38 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -9,7 +9,7 @@ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter import json from .autotranscript import Scraibe -from .app.gradio_app import gradio_Interface +from .app.app import gradio_Interface from whisper.tokenizer import LANGUAGES , TO_LANGUAGE_CODE from torch.cuda import is_available From 4ca9aa195a327a58a048792d9260b04ff6e24463 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 8 Dec 2023 14:30:56 +0100 Subject: [PATCH 081/331] added utils file --- scraibe/app/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scraibe/app/__init__.py b/scraibe/app/__init__.py index fa8f8f7..a38ad86 100644 --- a/scraibe/app/__init__.py +++ b/scraibe/app/__init__.py @@ -4,4 +4,5 @@ from .interface import * from .stg import * from .interactions import * from .global_var import * +from .utils import * from .app import * \ No newline at end of file From f494895376d35992fe3a1c8256263c1d776d8fb7 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 8 Dec 2023 14:31:17 +0100 Subject: [PATCH 082/331] removes depencency --- scraibe/app/global_var.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/app/global_var.py b/scraibe/app/global_var.py index 99f6eea..5599320 100644 --- a/scraibe/app/global_var.py +++ b/scraibe/app/global_var.py @@ -6,7 +6,6 @@ Stores global variables for the app. import multiprocessing import os import time -import yaml REQUEST_QUEUE = multiprocessing.Queue() # audio file path as string RESPONSE_QUEUE = multiprocessing.Queue() # transcription as string @@ -22,3 +21,4 @@ LAST_USED = time.time() TIMEOUT = None #seconds DEFAULT_APP_CONIFG_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config.yml") + From 9a8cdb2a64061dead12de67aac7d64ae2e7ba096 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 8 Dec 2023 14:31:48 +0100 Subject: [PATCH 083/331] added utils to handle config file (does not work yet) --- scraibe/app/utils.py | 208 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 175 insertions(+), 33 deletions(-) diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py index b41a88f..cff8b9e 100644 --- a/scraibe/app/utils.py +++ b/scraibe/app/utils.py @@ -1,42 +1,184 @@ -import scraibe.app.global_var as gv +import os +import warnings import yaml -def load_config(original_config_path = gv.DEFAULT_APP_CONIFG_PATH, override_yaml_path=None, **kwargs): +import scraibe.app.global_var as gv + + +class ConfigLoader: + def __init__(self, config): + + self.config = config + def restore_defaults_for_keys(self, keys): + """ + Restores specified keys to their default values, including nested keys. + + Args: + keys (list): A list of keys or paths to keys (for nested dictionaries) to restore to default values. + Each key or path should be a list of keys leading to the desired key. + """ + default_config = self.get_default_config() + + + self.apply_overrides(self.config, default_config, keys) + + + + @classmethod + def load_config(cls, yaml_path = None, **kwargs): + """ + Load the configuration file and apply overrides. + + Args: + yaml_path (str): Path to the YAML file containing overrides. + **kwargs: Additional overrides as keyword arguments. + + Returns: + Config: A Config object with the loaded configuration. + """ + + # Load the original configuration + config = cls.get_default_config() - # Load the original configuration - with open(original_config_path, 'r') as file: - config = yaml.safe_load(file) + # Override with another YAML file if provided + if yaml_path: + with open(yaml_path, 'r') as file: + override_config = yaml.safe_load(file) + cls.apply_overrides(config, override_config) - # Override with another YAML file if provided - if override_yaml_path: - with open(override_yaml_path, 'r') as file: - override_config = yaml.safe_load(file) - apply_overrides(config, override_config) + # Apply overrides from kwargs + cls.apply_overrides(config, kwargs) + return cls(config) + + @staticmethod + def apply_overrides(orig_dict, override_dict, specific_keys=None): + """ Recursively apply overrides to the configuration, only for specific keys. """ + if specific_keys is None: + specific_keys = override_dict.keys() # If no specific keys provided, apply to all keys - # Apply overrides from kwargs - apply_overrides(config, kwargs) + + for key, value in override_dict.items(): + + if key not in specific_keys: + + continue # Skip keys not in the specific keys set + + if isinstance(value, dict): + # If the value is a dict, apply recursively + sub_dict = orig_dict.get(key, {}) + ConfigLoader.apply_overrides(sub_dict, value, specific_keys) + orig_dict[key] = sub_dict + else: + # Apply override for this key + print("HI iam here", key, value) + orig_dict[key] = value + print("HI", orig_dict) - return config + # @staticmethod + # def apply_overrides(orig_dict, override_dict): + + # """ Recursively apply overrides to the configuration. """ + # for key, value in override_dict.items(): + # if isinstance(value, dict): + # # If the value is a dict, apply recursively + # ConfigLoader.apply_overrides(orig_dict.get(key, {}), value) + # else: + # # If the value is not a dict, search for the key and update + # if ConfigLoader.update_nested_key(orig_dict, key, value): + # continue # Key was found and updated + # orig_dict[key] = value # Key not found, update at this level -def apply_overrides(orig_dict, override_dict): - """ Recursively apply overrides to the configuration. """ - for key, value in override_dict.items(): - if isinstance(value, dict): - # If the value is a dict, apply recursively - apply_overrides(orig_dict.get(key, {}), value) - else: - # If the value is not a dict, search for the key and update - if update_nested_key(orig_dict, key, value): - continue # Key was found and updated - orig_dict[key] = value # Key not found, update at this level - -def update_nested_key(d, key, value): - """ Recursively search and update the key in nested dictionary. """ - if key in d: - d[key] = value - return True - for k, v in d.items(): - if isinstance(v, dict) and update_nested_key(v, key, value): + @staticmethod + def update_nested_key(d, key, value): + """ Recursively search and update the key in nested dictionary. """ + + if key in d: + d[key] = value return True - return False \ No newline at end of file + for k, v in d.items(): + if isinstance(v, dict) and ConfigLoader.update_nested_key(v, key, value): + return True + return False + + @staticmethod + def get_default_config(): + """ Return the default configuration. """ + with open(gv.DEFAULT_APP_CONIFG_PATH , 'r') as file: + config = yaml.safe_load(file) + return config + + +class AppConfig(ConfigLoader): + + def __init__(self, config): + + self.config = config + + self.set_global_vars_from_config() + self.set_launch_options() + self.set_layout_options() + + self.lauch = self.config.get("launch") + self.model = self.config.get("model") + self.advanced = self.config.get("advanced") + self.queue = self.config.get("queue") + self.layout = self.config.get("layout") + + def set_global_vars_from_config(self): + """ + Sets the global variables from a configuration dictionary. + + Args: + config (dict): A dictionary containing the parameters for the model. Modify the default parameters in the config.yml file. + + Returns: + None + + """ + + gv.MODEL_PARAMS = self.config.get('model') + gv.TIMEOUT = self.config.get("advanced").get('timeout') + + def set_launch_options(self): + + launch_options = self.config.get("launch") + + if launch_options.get('auth').pop('enabled'): + self.config['launch']['auth'] = (launch_options.get('auth').pop('username'), + launch_options.get('auth').pop('password')) + else: + self.config['launch']['auth'] = None + + def set_layout_options(self): + self.config['layout']['header'] = self.check_and_set_path(self.config['layout'], 'header') + self.config['layout']['footer'] = self.check_and_set_path(self.config['layout'], 'footer') + self.config['layout']['logo'] = self.check_and_set_path(self.config['layout'], 'logo') + + + @staticmethod + def check_and_set_path(config_item, key): + """ + Check if the file exists at the given path. If not, try with CURRENT_PATH. + Raise FileNotFoundError if the file still doesn't exist. + """ + _current_path = os.path.dirname(os.path.realpath(__file__)) # Define your CURRENT_PATH + + file_path = config_item.get(key) + if file_path is None: + return None + if not os.path.exists(file_path): + new_path = os.path.join(_current_path, file_path) + if not os.path.exists(new_path): + warnings.warn(f"{key.capitalize()} file not found: {config_item[key]} \n" \ + "fall back to default.") + else: + config_item[key] = new_path + + return config_item[key] + + + + + + \ No newline at end of file From 98c1c7dfa5c2c900a0f0267986539b6ab412c8b9 Mon Sep 17 00:00:00 2001 From: Steffen Albrecht Date: Fri, 8 Dec 2023 14:57:32 +0100 Subject: [PATCH 084/331] adding function to remove whisper hallucinations --- scraibe/transcript_exporter.py | 117 +++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index 3c68d87..c40a993 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -5,6 +5,110 @@ from typing import Union ALPHABET = [*"abcdefghijklmnopqrstuvwxyz"] +WHISPER_HALLUCINATIONS={ + "en": [ + " www.mooji.org", + ], + "nl": [ + " Ondertitels ingediend door de Amara.org gemeenschap", + " Ondertiteld door de Amara.org gemeenschap", + " Ondertiteling door de Amara.org gemeenschap" + ], + "de": [ + " Untertitelung aufgrund der Amara.org-Community" + " Untertitel im Auftrag des ZDF für funk, 2017", + " Untertitel von Stephanie Geiges", + " Untertitel der Amara.org-Community", + " Untertitel im Auftrag des ZDF, 2017", + " Untertitel im Auftrag des ZDF, 2020", + " Untertitel im Auftrag des ZDF, 2018", + " Untertitel im Auftrag des ZDF, 2021", + " Untertitelung im Auftrag des ZDF, 2021", + " Copyright WDR 2021", + " Copyright WDR 2020", + " Copyright WDR 2019", + " SWR 2021", + " SWR 2020", + ], + "fr": [ + " Sous-titres réalisés para la communauté d'Amara.org", + " Sous-titres réalisés par la communauté d'Amara.org", + " Sous-titres fait par Sous-titres par Amara.org", + " Sous-titres réalisés par les SousTitres d'Amara.org", + " Sous-titres par Amara.org", + " Sous-titres par la communauté d'Amara.org", + " Sous-titres réalisés pour la communauté d'Amara.org", + " Sous-titres réalisés par la communauté de l'Amara.org", + " Sous-Titres faits par la communauté d'Amara.org", + " Sous-titres par l'Amara.org", + " Sous-titres fait par la communauté d'Amara.org" + " Sous-titrage ST' 501", + " Sous-titrage ST'501", + " Cliquez-vous sur les sous-titres et abonnez-vous à la chaîne d'Amara.org", + " ❤️ par SousTitreur.com", + ], + "it": [ + " Sottotitoli creati dalla comunità Amara.org", + " Sottotitoli di Sottotitoli di Amara.org", + " Sottotitoli e revisione al canale di Amara.org", + " Sottotitoli e revisione a cura di Amara.org", + " Sottotitoli e revisione a cura di QTSS", + " Sottotitoli e revisione a cura di QTSS.", + " Sottotitoli a cura di QTSS", + ], + "es": [ + " Subtítulos realizados por la comunidad de Amara.org", + " Subtitulado por la comunidad de Amara.org", + " Subtítulos por la comunidad de Amara.org", + " Subtítulos creados por la comunidad de Amara.org", + " Subtítulos en español de Amara.org", + " Subtítulos hechos por la comunidad de Amara.org", + " Subtitulos por la comunidad de Amara.org" + " Más información www.alimmenta.com", + " www.mooji.org", + ], + "gl": [ + " Subtítulos realizados por la comunidad de Amara.org" + ], + "pt": [ + " Legendas pela comunidade Amara.org", + " Legendas pela comunidade de Amara.org", + " Legendas pela comunidade do Amara.org", + " Legendas pela comunidade das Amara.org", + " Transcrição e Legendas pela comunidade de Amara.org" + ], + "la": [ + " Sottotitoli creati dalla comunità Amara.org", + " Sous-titres réalisés para la communauté d'Amara.org" + ], + "ln": [ + " Sous-titres réalisés para la communauté d'Amara.org" + ], + "pl": [ + " Napisy stworzone przez społeczność Amara.org", + " Napisy wykonane przez społeczność Amara.org", + " Zdjęcia i napisy stworzone przez społeczność Amara.org", + " napisy stworzone przez społeczność Amara.org", + " Tłumaczenie i napisy stworzone przez społeczność Amara.org", + " Napisy stworzone przez społeczności Amara.org", + " Tłumaczenie stworzone przez społeczność Amara.org", + " Napisy robione przez społeczność Amara.org" + " www.multi-moto.eu", + ], + "ru": [ + " Редактор субтитров А.Синецкая Корректор А.Егорова" + ], + "tr": [ + " Yorumlarınızıza abone olmayı unutmayın.", + ], + "su": [ + " Sottotitoli creati dalla comunità Amara.org" + ], + "zh": [ + "字幕由Amara.org社区提供", + "小編字幕由Amara.org社區提供" + ] +} class Transcript: """ @@ -23,6 +127,7 @@ class Transcript: """ self.transcript = transcript + self._remove_hallucinations() self.speakers = self._extract_speakers() self.segments = self._extract_segments() self.annotation = {} @@ -62,6 +167,18 @@ class Transcript: return self + def _remove_hallucinations(self) -> None: + segments_to_drop=[] + + for id in self.transcript: + for language, snippets in WHISPER_HALLUCINATIONS.items(): + for snippet in snippets: + self.transcript[id]['text']=self.transcript[id]['text'].replace(snippet,'') + if self.transcript[id]['text'] == '': segments_to_drop.append(id) + + for id in segments_to_drop: + del self.transcript[id] + def _extract_speakers(self) -> list: """ Extracts the unique speaker names from the transcript. From 920a85a94a02c5a1bf541f30b1ca3f32a9cfc66d Mon Sep 17 00:00:00 2001 From: Steffen Albrecht Date: Fri, 8 Dec 2023 15:14:29 +0100 Subject: [PATCH 085/331] adding credit for list of known hallucinations --- scraibe/transcript_exporter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index c40a993..5920fd9 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -5,6 +5,8 @@ from typing import Union ALPHABET = [*"abcdefghijklmnopqrstuvwxyz"] +# List of known hallucinations - taken from: +# https://github.com/openai/whisper/discussions/928 WHISPER_HALLUCINATIONS={ "en": [ " www.mooji.org", From 667ccd29af6e7e75e1df194f022e473fb5bd3f5a Mon Sep 17 00:00:00 2001 From: Steffen Albrecht Date: Mon, 11 Dec 2023 12:04:08 +0100 Subject: [PATCH 086/331] extend list of known hallucinations --- scraibe/transcript_exporter.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index 5920fd9..b717d7f 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -5,7 +5,7 @@ from typing import Union ALPHABET = [*"abcdefghijklmnopqrstuvwxyz"] -# List of known hallucinations - taken from: +# List of known hallucinations - adapted from: # https://github.com/openai/whisper/discussions/928 WHISPER_HALLUCINATIONS={ "en": [ @@ -18,12 +18,17 @@ WHISPER_HALLUCINATIONS={ ], "de": [ " Untertitelung aufgrund der Amara.org-Community" + " Untertitelung im Auftrag des ZDF für funk, 2016", + " Untertitelung im Auftrag des ZDF f\u00fcr funk, 2016", " Untertitel im Auftrag des ZDF für funk, 2017", + " Untertitel im Auftrag des ZDF f\u00fcr funk, 2017", + " Untertitel im Auftrag des ZDF für funk, 2018", " Untertitel von Stephanie Geiges", " Untertitel der Amara.org-Community", " Untertitel im Auftrag des ZDF, 2017", - " Untertitel im Auftrag des ZDF, 2020", " Untertitel im Auftrag des ZDF, 2018", + " Untertitel im Auftrag des ZDF, 2019", + " Untertitel im Auftrag des ZDF, 2020", " Untertitel im Auftrag des ZDF, 2021", " Untertitelung im Auftrag des ZDF, 2021", " Copyright WDR 2021", From 7c200d4506b7c21711346cd0de1ab55dc3f76aec Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 13 Dec 2023 17:40:32 +0100 Subject: [PATCH 087/331] config.yml updating and reloaading default works --- scraibe/app/config.yml | 6 ++--- scraibe/app/utils.py | 51 ++++++++++++++++-------------------------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml index 4eda2e4..ad6ce98 100644 --- a/scraibe/app/config.yml +++ b/scraibe/app/config.yml @@ -8,9 +8,9 @@ launch: max-threads: 40 quiet: false auth: - enabled: false - username: admin - password: admin + auth_enabled: false + auth_username: admin + auth_password: admin auth_message: null show_error : false favicon_path : null diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py index cff8b9e..17950c5 100644 --- a/scraibe/app/utils.py +++ b/scraibe/app/utils.py @@ -10,7 +10,7 @@ class ConfigLoader: self.config = config - def restore_defaults_for_keys(self, keys): + def restore_defaults_for_keys(self, *args): """ Restores specified keys to their default values, including nested keys. @@ -20,8 +20,8 @@ class ConfigLoader: """ default_config = self.get_default_config() - - self.apply_overrides(self.config, default_config, keys) + for key in args: + self.apply_overrides(self.config, default_config, key) @@ -52,42 +52,29 @@ class ConfigLoader: return cls(config) @staticmethod - def apply_overrides(orig_dict, override_dict, specific_keys=None): + def apply_overrides(orig_dict, override_dict, specific=None): """ Recursively apply overrides to the configuration, only for specific keys. """ - if specific_keys is None: - specific_keys = override_dict.keys() # If no specific keys provided, apply to all keys - - for key, value in override_dict.items(): - if key not in specific_keys: - - continue # Skip keys not in the specific keys set - if isinstance(value, dict): # If the value is a dict, apply recursively sub_dict = orig_dict.get(key, {}) - ConfigLoader.apply_overrides(sub_dict, value, specific_keys) + ConfigLoader.apply_overrides(sub_dict, value, specific) orig_dict[key] = sub_dict else: # Apply override for this key - print("HI iam here", key, value) - orig_dict[key] = value - print("HI", orig_dict) - - # @staticmethod - # def apply_overrides(orig_dict, override_dict): - - # """ Recursively apply overrides to the configuration. """ - # for key, value in override_dict.items(): - # if isinstance(value, dict): - # # If the value is a dict, apply recursively - # ConfigLoader.apply_overrides(orig_dict.get(key, {}), value) - # else: - # # If the value is not a dict, search for the key and update - # if ConfigLoader.update_nested_key(orig_dict, key, value): - # continue # Key was found and updated - # orig_dict[key] = value # Key not found, update at this level + if specific is None: + # If no specific keys are provided, update the key + # If the value is not a dict, search for the key and update + if ConfigLoader.update_nested_key(orig_dict, key, value): + continue # Key was found and updated + orig_dict[key] = value # Key not found, update at this level + + elif key in specific: + # If specific keys are provided, only update if the key is in the list + if ConfigLoader.update_nested_key(orig_dict, specific, value): + continue # Key was found and updated + orig_dict[specific] = value @staticmethod def update_nested_key(d, key, value): @@ -144,8 +131,8 @@ class AppConfig(ConfigLoader): launch_options = self.config.get("launch") - if launch_options.get('auth').pop('enabled'): - self.config['launch']['auth'] = (launch_options.get('auth').pop('username'), + if launch_options.get('auth').pop('auth_enabled'): + self.config['launch']['auth'] = (launch_options.get('auth').pop('auth_username'), launch_options.get('auth').pop('password')) else: self.config['launch']['auth'] = None From 8db033607044d66455bb79a0d3d814545cdcf405 Mon Sep 17 00:00:00 2001 From: Steffen Albrecht Date: Thu, 14 Dec 2023 14:54:03 +0100 Subject: [PATCH 088/331] moved known hallucinations from dict to list in external py file --- scraibe/hallucinations.py | 95 +++++++++++++++++++++++++ scraibe/transcript_exporter.py | 126 +++------------------------------ 2 files changed, 105 insertions(+), 116 deletions(-) create mode 100644 scraibe/hallucinations.py diff --git a/scraibe/hallucinations.py b/scraibe/hallucinations.py new file mode 100644 index 0000000..a337ec0 --- /dev/null +++ b/scraibe/hallucinations.py @@ -0,0 +1,95 @@ +# List of known hallucinations - adapted from: +# https://github.com/openai/whisper/discussions/928 +KNOWN_HALLUCINATIONS=[ + # en + " www.mooji.org" + # nl + " Ondertitels ingediend door de Amara.org gemeenschap", + " Ondertiteld door de Amara.org gemeenschap", + " Ondertiteling door de Amara.org gemeenschap" + # de + " Untertitelung aufgrund der Amara.org-Community" + " Untertitelung im Auftrag des ZDF für funk, 2016", + " Untertitelung im Auftrag des ZDF f\u00fcr funk, 2016", + " Untertitel im Auftrag des ZDF für funk, 2017", + " Untertitel im Auftrag des ZDF f\u00fcr funk, 2017", + " Untertitel im Auftrag des ZDF für funk, 2018", + " Untertitel von Stephanie Geiges", + " Untertitel der Amara.org-Community", + " Untertitel im Auftrag des ZDF, 2017", + " Untertitel im Auftrag des ZDF, 2018", + " Untertitel im Auftrag des ZDF, 2019", + " Untertitel im Auftrag des ZDF, 2020", + " Untertitel im Auftrag des ZDF, 2021", + " Untertitelung im Auftrag des ZDF, 2021", + " Copyright WDR 2021", + " Copyright WDR 2020", + " Copyright WDR 2019", + " SWR 2021", + " SWR 2020", + # fr + " Sous-titres réalisés para la communauté d'Amara.org", + " Sous-titres réalisés par la communauté d'Amara.org", + " Sous-titres fait par Sous-titres par Amara.org", + " Sous-titres réalisés par les SousTitres d'Amara.org", + " Sous-titres par Amara.org", + " Sous-titres par la communauté d'Amara.org", + " Sous-titres réalisés pour la communauté d'Amara.org", + " Sous-titres réalisés par la communauté de l'Amara.org", + " Sous-Titres faits par la communauté d'Amara.org", + " Sous-titres par l'Amara.org", + " Sous-titres fait par la communauté d'Amara.org" + " Sous-titrage ST' 501", + " Sous-titrage ST'501", + " Cliquez-vous sur les sous-titres et abonnez-vous à la chaîne d'Amara.org", + " ❤️ par SousTitreur.com", + # it + " Sottotitoli creati dalla comunità Amara.org", + " Sottotitoli di Sottotitoli di Amara.org", + " Sottotitoli e revisione al canale di Amara.org", + " Sottotitoli e revisione a cura di Amara.org", + " Sottotitoli e revisione a cura di QTSS", + " Sottotitoli e revisione a cura di QTSS.", + " Sottotitoli a cura di QTSS", + " Subtítulos realizados por la comunidad de Amara.org", + " Subtitulado por la comunidad de Amara.org", + " Subtítulos por la comunidad de Amara.org", + " Subtítulos creados por la comunidad de Amara.org", + " Subtítulos en español de Amara.org", + " Subtítulos hechos por la comunidad de Amara.org", + " Subtitulos por la comunidad de Amara.org" + " Más información www.alimmenta.com", + " www.mooji.org", + # gl + " Subtítulos realizados por la comunidad de Amara.org" + # pt + " Legendas pela comunidade Amara.org", + " Legendas pela comunidade de Amara.org", + " Legendas pela comunidade do Amara.org", + " Legendas pela comunidade das Amara.org", + " Transcrição e Legendas pela comunidade de Amara.org" + # la + " Sottotitoli creati dalla comunità Amara.org", + " Sous-titres réalisés para la communauté d'Amara.org" + # ln + " Sous-titres réalisés para la communauté d'Amara.org" + # pl + " Napisy stworzone przez społeczność Amara.org", + " Napisy wykonane przez społeczność Amara.org", + " Zdjęcia i napisy stworzone przez społeczność Amara.org", + " napisy stworzone przez społeczność Amara.org", + " Tłumaczenie i napisy stworzone przez społeczność Amara.org", + " Napisy stworzone przez społeczności Amara.org", + " Tłumaczenie stworzone przez społeczność Amara.org", + " Napisy robione przez społeczność Amara.org" + " www.multi-moto.eu", + # ru + " Редактор субтитров А.Синецкая Корректор А.Егорова" + # tr + " Yorumlarınızıza abone olmayı unutmayın.", + # su + " Sottotitoli creati dalla comunità Amara.org" + # zh + "字幕由Amara.org社区提供", + "小編字幕由Amara.org社區提供" +] \ No newline at end of file diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index b717d7f..1ce43d4 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -2,120 +2,12 @@ import json import time from typing import Union - + +from .hallucinations import KNOWN_HALLUCINATIONS + ALPHABET = [*"abcdefghijklmnopqrstuvwxyz"] -# List of known hallucinations - adapted from: -# https://github.com/openai/whisper/discussions/928 -WHISPER_HALLUCINATIONS={ - "en": [ - " www.mooji.org", - ], - "nl": [ - " Ondertitels ingediend door de Amara.org gemeenschap", - " Ondertiteld door de Amara.org gemeenschap", - " Ondertiteling door de Amara.org gemeenschap" - ], - "de": [ - " Untertitelung aufgrund der Amara.org-Community" - " Untertitelung im Auftrag des ZDF für funk, 2016", - " Untertitelung im Auftrag des ZDF f\u00fcr funk, 2016", - " Untertitel im Auftrag des ZDF für funk, 2017", - " Untertitel im Auftrag des ZDF f\u00fcr funk, 2017", - " Untertitel im Auftrag des ZDF für funk, 2018", - " Untertitel von Stephanie Geiges", - " Untertitel der Amara.org-Community", - " Untertitel im Auftrag des ZDF, 2017", - " Untertitel im Auftrag des ZDF, 2018", - " Untertitel im Auftrag des ZDF, 2019", - " Untertitel im Auftrag des ZDF, 2020", - " Untertitel im Auftrag des ZDF, 2021", - " Untertitelung im Auftrag des ZDF, 2021", - " Copyright WDR 2021", - " Copyright WDR 2020", - " Copyright WDR 2019", - " SWR 2021", - " SWR 2020", - ], - "fr": [ - " Sous-titres réalisés para la communauté d'Amara.org", - " Sous-titres réalisés par la communauté d'Amara.org", - " Sous-titres fait par Sous-titres par Amara.org", - " Sous-titres réalisés par les SousTitres d'Amara.org", - " Sous-titres par Amara.org", - " Sous-titres par la communauté d'Amara.org", - " Sous-titres réalisés pour la communauté d'Amara.org", - " Sous-titres réalisés par la communauté de l'Amara.org", - " Sous-Titres faits par la communauté d'Amara.org", - " Sous-titres par l'Amara.org", - " Sous-titres fait par la communauté d'Amara.org" - " Sous-titrage ST' 501", - " Sous-titrage ST'501", - " Cliquez-vous sur les sous-titres et abonnez-vous à la chaîne d'Amara.org", - " ❤️ par SousTitreur.com", - ], - "it": [ - " Sottotitoli creati dalla comunità Amara.org", - " Sottotitoli di Sottotitoli di Amara.org", - " Sottotitoli e revisione al canale di Amara.org", - " Sottotitoli e revisione a cura di Amara.org", - " Sottotitoli e revisione a cura di QTSS", - " Sottotitoli e revisione a cura di QTSS.", - " Sottotitoli a cura di QTSS", - ], - "es": [ - " Subtítulos realizados por la comunidad de Amara.org", - " Subtitulado por la comunidad de Amara.org", - " Subtítulos por la comunidad de Amara.org", - " Subtítulos creados por la comunidad de Amara.org", - " Subtítulos en español de Amara.org", - " Subtítulos hechos por la comunidad de Amara.org", - " Subtitulos por la comunidad de Amara.org" - " Más información www.alimmenta.com", - " www.mooji.org", - ], - "gl": [ - " Subtítulos realizados por la comunidad de Amara.org" - ], - "pt": [ - " Legendas pela comunidade Amara.org", - " Legendas pela comunidade de Amara.org", - " Legendas pela comunidade do Amara.org", - " Legendas pela comunidade das Amara.org", - " Transcrição e Legendas pela comunidade de Amara.org" - ], - "la": [ - " Sottotitoli creati dalla comunità Amara.org", - " Sous-titres réalisés para la communauté d'Amara.org" - ], - "ln": [ - " Sous-titres réalisés para la communauté d'Amara.org" - ], - "pl": [ - " Napisy stworzone przez społeczność Amara.org", - " Napisy wykonane przez społeczność Amara.org", - " Zdjęcia i napisy stworzone przez społeczność Amara.org", - " napisy stworzone przez społeczność Amara.org", - " Tłumaczenie i napisy stworzone przez społeczność Amara.org", - " Napisy stworzone przez społeczności Amara.org", - " Tłumaczenie stworzone przez społeczność Amara.org", - " Napisy robione przez społeczność Amara.org" - " www.multi-moto.eu", - ], - "ru": [ - " Редактор субтитров А.Синецкая Корректор А.Егорова" - ], - "tr": [ - " Yorumlarınızıza abone olmayı unutmayın.", - ], - "su": [ - " Sottotitoli creati dalla comunità Amara.org" - ], - "zh": [ - "字幕由Amara.org社区提供", - "小編字幕由Amara.org社區提供" - ] -} + class Transcript: """ @@ -175,12 +67,14 @@ class Transcript: return self def _remove_hallucinations(self) -> None: + """ + Removes all occurances of known hallucinations from all segments of the transcript. + Segments that are identical to empty strings afterwards are removed from the transcript. + """ segments_to_drop=[] - for id in self.transcript: - for language, snippets in WHISPER_HALLUCINATIONS.items(): - for snippet in snippets: - self.transcript[id]['text']=self.transcript[id]['text'].replace(snippet,'') + for snippet in KNOWN_HALLUCINATIONS: + self.transcript[id]['text']=self.transcript[id]['text'].replace(snippet,'') if self.transcript[id]['text'] == '': segments_to_drop.append(id) for id in segments_to_drop: From 2e0989b7e414d22bb6abd21937a65171453f962f Mon Sep 17 00:00:00 2001 From: Tryndaron <84569139+Tryndaron@users.noreply.github.com> Date: Tue, 2 Jan 2024 08:22:53 +0100 Subject: [PATCH 089/331] Add files via upload test file for diarisation functionality --- test_diarisation.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test_diarisation.py diff --git a/test_diarisation.py b/test_diarisation.py new file mode 100644 index 0000000..55b7276 --- /dev/null +++ b/test_diarisation.py @@ -0,0 +1,27 @@ +import pytest +import os +from unittest import mock +from scraibe import Diariser + + + +@pytest.fixture +def diariser_instance(): + with mock.patch.object(Diariser, '_get_token', return_value = 'personal Hugging-Face token') + return Diariser('pyannote') + + + +def test_Diariser_init(diariser_instance): + assert diariser_instance.model == 'pyannote' + + +def test_diarisation_function(diariser_instance): + with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): + diarization_output = diariser_instance.diarization('example_audio_file.wav') + assert diarization_output == 'diarization_result' + + + + + From f90d16859e7117b691b36cfd8d46c8720a463355 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 5 Jan 2024 11:56:49 +0100 Subject: [PATCH 090/331] test --- test_audio.py => scraibe/test/test_audio.py | 0 test_diarisation.py => scraibe/test/test_diarisation.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test_audio.py => scraibe/test/test_audio.py (100%) rename test_diarisation.py => scraibe/test/test_diarisation.py (100%) diff --git a/test_audio.py b/scraibe/test/test_audio.py similarity index 100% rename from test_audio.py rename to scraibe/test/test_audio.py diff --git a/test_diarisation.py b/scraibe/test/test_diarisation.py similarity index 100% rename from test_diarisation.py rename to scraibe/test/test_diarisation.py From ffe4dc8c58acebea5fe653c68d6ddc61c167a470 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 5 Jan 2024 12:32:03 +0100 Subject: [PATCH 091/331] test --- scraibe/test/test_diarisation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 55b7276..7825552 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -16,6 +16,7 @@ def test_Diariser_init(diariser_instance): assert diariser_instance.model == 'pyannote' + def test_diarisation_function(diariser_instance): with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): diarization_output = diariser_instance.diarization('example_audio_file.wav') From f501d76e999180373a82c883927ee395be3091e4 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 5 Jan 2024 13:04:42 +0100 Subject: [PATCH 092/331] test --- scraibe/test/test_diarisation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 7825552..71f8023 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -26,3 +26,4 @@ def test_diarisation_function(diariser_instance): + From ee8c98004a114a3d91c62c64ece4d9208e58d6b0 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 5 Jan 2024 14:24:20 +0100 Subject: [PATCH 093/331] test --- scraibe/test/test_diarisation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 71f8023..7825552 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -26,4 +26,3 @@ def test_diarisation_function(diariser_instance): - From 6f6c31a41250b1e92ba9c2343965710aa84687db Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 5 Jan 2024 15:15:05 +0100 Subject: [PATCH 094/331] test --- scraibe/test/test_diarisation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 7825552..76fd584 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -24,5 +24,3 @@ def test_diarisation_function(diariser_instance): - - From fd7e899e4c5978adeaefb124c3fa8d380f931143 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 5 Jan 2024 15:26:08 +0100 Subject: [PATCH 095/331] test2 --- scraibe/test/test_transcriber.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 scraibe/test/test_transcriber.py diff --git a/scraibe/test/test_transcriber.py b/scraibe/test/test_transcriber.py new file mode 100644 index 0000000..b5d2801 --- /dev/null +++ b/scraibe/test/test_transcriber.py @@ -0,0 +1,22 @@ +import pytest +from unittest.mock import patch +from scraibe import Transcriber + + + + +@pytest.mark.parametrize("audio_file, expected_transcription",[("path_to_test_audiofile", "test_transcription")] ) +@patch("scraibe.Transcriber.load_model") + +def test_transcriber(mock_load_model, audio_file, expected_transcription): + mock_model = mock_load_model.return_value + mock_model.transcribe.return_value ={"text": expected_transcription} + + transcriber = Transcriber.load_model(model="medium") + + transcription_result = transcriber.transcribe(audio=audio_file) + + assert transcription_result == expected_transcription + + + From abec1e172630c2e8cb05edd1b3080258d2e6c1bb Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 9 Jan 2024 13:49:14 +0100 Subject: [PATCH 096/331] Docstrings --- scraibe/test/test_audio.py | 16 ++++++++++++++++ scraibe/test/test_diarisation.py | 10 ++++++++++ scraibe/test/test_transcriber.py | 7 +++++++ 3 files changed, 33 insertions(+) diff --git a/scraibe/test/test_audio.py b/scraibe/test/test_audio.py index 64fa8c1..a0c37ad 100644 --- a/scraibe/test/test_audio.py +++ b/scraibe/test/test_audio.py @@ -18,6 +18,11 @@ NORMALIZATION_FACTOR = 32768 @pytest.fixture def probe_audio_processor(): + """_summary_ + + Returns: + _type_: _description_ + """ return AudioProcessor(test_waveform, test_sr) @@ -26,6 +31,11 @@ def probe_audio_processor(): def test_AudioProcessor_init(probe_audio_processor): + """_summary_ + + Args: + probe_audio_processor (_type_): _description_ + """ assert isinstance(probe_audio_processor, AudioProcessor) assert probe_audio_processor.waveform.device == test_waveform.device assert torch.equal(probe_audio_processor.waveform, test_waveform) @@ -34,6 +44,8 @@ def test_AudioProcessor_init(probe_audio_processor): def test_cut(): + """_summary_ + """ waveform = torch.Tensor(10, 3) sr = 16000 start = 4 @@ -57,11 +69,15 @@ def test_cut(): def test_audio_processor_invalid_sr(): + """_summary_ + """ with pytest.raises(ValueError): AudioProcessor(test_waveform, [44100,48000]) def test_audio_processor_SAMPLE_RATE(): + """_summary_ + """ probe_audio_processor = AudioProcessor(test_waveform) assert probe_audio_processor.sr == SAMPLE_RATE diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 76fd584..148b1a8 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -13,11 +13,21 @@ def diariser_instance(): def test_Diariser_init(diariser_instance): + """_summary_ + + Args: + diariser_instance (_type_): _description_ + """ assert diariser_instance.model == 'pyannote' def test_diarisation_function(diariser_instance): + """_summary_ + + Args: + diariser_instance (_type_): _description_ + """ with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): diarization_output = diariser_instance.diarization('example_audio_file.wav') assert diarization_output == 'diarization_result' diff --git a/scraibe/test/test_transcriber.py b/scraibe/test/test_transcriber.py index b5d2801..bb08efe 100644 --- a/scraibe/test/test_transcriber.py +++ b/scraibe/test/test_transcriber.py @@ -9,6 +9,13 @@ from scraibe import Transcriber @patch("scraibe.Transcriber.load_model") def test_transcriber(mock_load_model, audio_file, expected_transcription): + """_summary_ + + Args: + mock_load_model (_type_): _description_ + audio_file (_type_): _description_ + expected_transcription (_type_): _description_ + """ mock_model = mock_load_model.return_value mock_model.transcribe.return_value ={"text": expected_transcription} From 570048a2e0b5e9798103934dc5f196b7159045b5 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 23 Jan 2024 13:08:59 +0100 Subject: [PATCH 097/331] Docstrings I applied the Changes which were mentioned in the tests_audio.py file --- scraibe/test/test_audio.py | 42 +++++++++++++++----------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/scraibe/test/test_audio.py b/scraibe/test/test_audio.py index a0c37ad..5a35055 100644 --- a/scraibe/test/test_audio.py +++ b/scraibe/test/test_audio.py @@ -1,29 +1,25 @@ import pytest -#from scraibe import Transcriber -#from unittest.mock import patch, mock_open -#import unittest -#import os from .audio import AudioProcessor import torch +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - -test_waveform = torch.tensor([]).to('cuda') -test_sr = 16000 +test_waveform = torch.tensor([]).to(device) +TEST_SR = 16000 SAMPLE_RATE = 16000 NORMALIZATION_FACTOR = 32768 @pytest.fixture def probe_audio_processor(): - """_summary_ + """Creates a dummy AudioProcessor Object Returns: - _type_: _description_ + AudioProcessor Object with given parameters test_waveform and TEST_SR """ - return AudioProcessor(test_waveform, test_sr) + return AudioProcessor(test_waveform, TEST_SR) @@ -31,10 +27,11 @@ def probe_audio_processor(): def test_AudioProcessor_init(probe_audio_processor): - """_summary_ + """ + testing if the audio_processor Object gets initialized correctly - Args: - probe_audio_processor (_type_): _description_ + Args: probe_audio_processor Object + """ assert isinstance(probe_audio_processor, AudioProcessor) assert probe_audio_processor.waveform.device == test_waveform.device @@ -44,39 +41,32 @@ def test_AudioProcessor_init(probe_audio_processor): def test_cut(): - """_summary_ + """Test for the test_cut Method for fixed parameters """ waveform = torch.Tensor(10, 3) sr = 16000 start = 4 end = 7 - assert AudioProcessor(waveform, sr).cut(start, end).size() == int((end - start) * test_sr) + assert AudioProcessor(waveform, sr).cut(start, end).size() == int((end - start) * TEST_SR) -""" def test_cut(probe_audio_processor): - start = 10 - end = 100 - test_segment = probe_audio_processor.cut(start, end) - print(test_segment) - erwartetes_segment = int((end - start) * test_sr) - print(test_segment.size()) - assert len(test_segment) == erwartetes_segment - """ + + def test_audio_processor_invalid_sr(): - """_summary_ + """Testing the audio_processor Object with invalid Sample rate """ with pytest.raises(ValueError): AudioProcessor(test_waveform, [44100,48000]) def test_audio_processor_SAMPLE_RATE(): - """_summary_ + """Making sure Sample Rate of Audio_processor Sample Rate matches global Sample Rate """ probe_audio_processor = AudioProcessor(test_waveform) assert probe_audio_processor.sr == SAMPLE_RATE From 033ce8c92ce91b3676c822117031ea9dcb810dc8 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 23 Jan 2024 13:21:42 +0100 Subject: [PATCH 098/331] Diarisation basics tests for the Diarisation object --- scraibe/test/test_diarisation.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 148b1a8..b911b88 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -7,26 +7,31 @@ from scraibe import Diariser @pytest.fixture def diariser_instance(): + """Creates a instance of the Diariser Object for further testing + + Returns: + _type_: _description_ + """ with mock.patch.object(Diariser, '_get_token', return_value = 'personal Hugging-Face token') return Diariser('pyannote') def test_Diariser_init(diariser_instance): - """_summary_ + """Tests if the Diariser gets initiated correctly Args: - diariser_instance (_type_): _description_ + diariser_instance """ assert diariser_instance.model == 'pyannote' def test_diarisation_function(diariser_instance): - """_summary_ + """tests if the Diariser object with an example audio File Args: - diariser_instance (_type_): _description_ + diariser_instance """ with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): diarization_output = diariser_instance.diarization('example_audio_file.wav') From 6217e3a9b3d74dd0b2bdbf061b2cb4dcca442c40 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 24 Jan 2024 15:58:37 +0100 Subject: [PATCH 099/331] adapt everything to work with new config file --- scraibe/app/config.yml | 21 +++++++++-------- scraibe/app/interface.py | 18 +++++++------- scraibe/app/multi.py | 14 +++++++++-- scraibe/app/utils.py | 51 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml index ad6ce98..9f6a826 100644 --- a/scraibe/app/config.yml +++ b/scraibe/app/config.yml @@ -1,10 +1,13 @@ launch: # The following are the default values for the launch configuration # for more informations look at https://www.gradio.app/docs/interface - server_port: 8080 + server_port: 7860 server_name: 0.0.0.0 - inbrowser: true inline: false + inbrowser: true + share : false + debug : false + max-threads: 40 quiet: false auth: @@ -12,7 +15,9 @@ launch: auth_username: admin auth_password: admin auth_message: null + prevent_thread_lock : false show_error : false + show_tips : true favicon_path : null ssl_keyfile : null ssl_certfile : null @@ -22,21 +27,17 @@ launch: show_api : false allowed_paths : null blocked_paths : null - root_path : null + root_path : '' app_kwargs : null - state_session_capacity : 1000 - share_server_address : null - share_server_protocol : null - share : false - debug : false + queue: # The following are the default values for the queue configuration # for more informations look at hhttps://www.gradio.app/docs/interface + concurrency_count : 1 status_update_rate : 'auto' api_open : null max_size : null - concurrency_count : null - default_concurrency_limit : 'not_set' + layout: header: scraibe/app/header.html footer: null diff --git a/scraibe/app/interface.py b/scraibe/app/interface.py index ddf10ee..fce582c 100644 --- a/scraibe/app/interface.py +++ b/scraibe/app/interface.py @@ -31,21 +31,18 @@ LANGUAGES = [ "Vietnamese", "Welsh" ] -CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) -def gradio_Interface(): + +def gradio_Interface(layout = None,): with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: # Define components - hname = os.path.join(CURRENT_PATH, "header.html") - header = open(hname, "r").read() - # ugly hack to get the logo to work - header = header.replace("/file=logo.svg", f"/file={CURRENT_PATH}/logo.svg" ) - - gr.HTML(header, visible= True, show_label=False) + + if layout.get('header') is not None: + gr.HTML(layout.get('header'), visible= True, show_label=False) with gr.Row(): @@ -98,7 +95,10 @@ def gradio_Interface(): visible= False, interactive= True) annotate = gr.Button(value="Annotate", visible= False, interactive= True) - + + if layout.get('footer') is not None: + gr.HTML(layout.get('footer'), visible= True, show_label=False) + # Define usage of components input.change(fn=select_origin, inputs=[input], outputs=[audio1, audio2, video1, video2, file_in]) diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py index ce61f70..ec9f17e 100644 --- a/scraibe/app/multi.py +++ b/scraibe/app/multi.py @@ -68,13 +68,23 @@ def model_worker(model_params : Union[Scraibe, dict], clear_queue(response_queue) loaded_event.clear() -def start_model_worker(model_params, request_queue, last_active_time, response_queue,loaded_event, running_event, *args, **kwargs): +def start_model_worker(model_params, + request_queue, + last_active_time, + response_queue, + loaded_event, + running_event, + *args, **kwargs): context = multiprocessing.get_context('spawn') model_process = context.Process(target=model_worker, args=(model_params, request_queue, last_active_time, response_queue,loaded_event, running_event, *args), kwargs=kwargs) model_process.start() return model_process -def timer_thread(request_queue, last_active_time,loaded_event, running_event, timeout=30): +def timer_thread(request_queue, + last_active_time, + loaded_event, + running_event, + timeout=30): while True: time.sleep(timeout) diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py index 17950c5..35d2f8e 100644 --- a/scraibe/app/utils.py +++ b/scraibe/app/utils.py @@ -1,9 +1,12 @@ +from email import header +from math import e import os import warnings import yaml import scraibe.app.global_var as gv +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) class ConfigLoader: def __init__(self, config): @@ -42,6 +45,7 @@ class ConfigLoader: config = cls.get_default_config() # Override with another YAML file if provided + if yaml_path: with open(yaml_path, 'r') as file: override_config = yaml.safe_load(file) @@ -106,7 +110,7 @@ class AppConfig(ConfigLoader): self.set_launch_options() self.set_layout_options() - self.lauch = self.config.get("launch") + self.launch = self.config.get("launch") self.model = self.config.get("model") self.advanced = self.config.get("advanced") self.queue = self.config.get("queue") @@ -141,7 +145,50 @@ class AppConfig(ConfigLoader): self.config['layout']['header'] = self.check_and_set_path(self.config['layout'], 'header') self.config['layout']['footer'] = self.check_and_set_path(self.config['layout'], 'footer') self.config['layout']['logo'] = self.check_and_set_path(self.config['layout'], 'logo') - + + def get_layout(self): + + if not os.path.exists(self.config['layout']['header']) and \ + self.config['layout']['header'] == "scraibe/app/header.html": + + hname = os.path.join(CURRENT_PATH, "header.html") + + header = open(hname).read() + + elif not os.path.exists(self.config['layout']['header']) and self.config['layout']['header'] != "scraibe/app/header.html": + warnings.warn(f"Header file not found: {self.config['layout']['header']} \n" \ + "fall back to default.") + + hname = os.path.join(CURRENT_PATH, "header.html") + + header = open(hname).read() + elif os.path.exists(self.config['layout']['header']): + header = open(self.config['layout']['header']).read() + else: + warnings.warn(f"Header file not found: {self.config['layout']['header']}") + header = None + + + if header != None: + if self.config['layout']['logo'] == "scraibe/app/logo.svg": + header = header.replace("/file=logo.svg", f"/file={os.path.join(CURRENT_PATH, 'logo.svg')}") + elif self.config['layout']['logo'] != "scraibe/app/logo.svg": + header = header.replace("/file=logo.svg", f"/file={self.config['layout']['logo']}") + else: + warnings.warn(f"Logo file not found: {self.config['layout']['logo']}") + + + if self.config['layout']['footer'] != None: + if os.path.exists(self.config['layout']['footer']): + footer = open(self.config['layout']['footer']).read() + elif self.config['layout']['footer'] == None: + footer = None + else: + warnings.warn(f"Footer file not found: {self.config['layout']['footer']}") + else: + footer = None + return {'header' : header , + 'footer' : footer} @staticmethod def check_and_set_path(config_item, key): From ea7117545d0a481bcd2aa0de11cf7bff6c303e77 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 24 Jan 2024 16:18:42 +0100 Subject: [PATCH 100/331] fixed wrong dict entry --- scraibe/app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py index 35d2f8e..c9693ef 100644 --- a/scraibe/app/utils.py +++ b/scraibe/app/utils.py @@ -137,7 +137,7 @@ class AppConfig(ConfigLoader): if launch_options.get('auth').pop('auth_enabled'): self.config['launch']['auth'] = (launch_options.get('auth').pop('auth_username'), - launch_options.get('auth').pop('password')) + launch_options.get('auth').pop('auth_password')) else: self.config['launch']['auth'] = None From 5c16b625270b84b6a987d579a4ef825b30d9e39e Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Wed, 24 Jan 2024 16:20:13 +0100 Subject: [PATCH 101/331] fixed typo --- scraibe/app/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml index 9f6a826..8e908e6 100644 --- a/scraibe/app/config.yml +++ b/scraibe/app/config.yml @@ -7,8 +7,7 @@ launch: inbrowser: true share : false debug : false - - max-threads: 40 + max_threads: 40 quiet: false auth: auth_enabled: false From c65dc51541d554db8ac25d0ab07358b99da3da5f Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Thu, 25 Jan 2024 09:47:54 +0100 Subject: [PATCH 102/331] typos fixed --- scraibe/app/config.yml | 4 ++-- scraibe/app/utils.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml index 8e908e6..9e42db3 100644 --- a/scraibe/app/config.yml +++ b/scraibe/app/config.yml @@ -4,7 +4,7 @@ launch: server_port: 7860 server_name: 0.0.0.0 inline: false - inbrowser: true + inbrowser: false share : false debug : false max_threads: 40 @@ -16,7 +16,7 @@ launch: auth_message: null prevent_thread_lock : false show_error : false - show_tips : true + show_tips : false favicon_path : null ssl_keyfile : null ssl_certfile : null diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py index c9693ef..816bc4c 100644 --- a/scraibe/app/utils.py +++ b/scraibe/app/utils.py @@ -1,5 +1,3 @@ -from email import header -from math import e import os import warnings import yaml From ef7bd6e15c603d2f6b25a821c94319e1eb377b27 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Thu, 25 Jan 2024 16:08:53 +0100 Subject: [PATCH 103/331] made cli work with new interface --- scraibe/app/app_starter.py | 28 ++++++++++ scraibe/cli.py | 106 ++++++++++++++++++++----------------- scraibe/misc.py | 15 ++++++ 3 files changed, 100 insertions(+), 49 deletions(-) create mode 100644 scraibe/app/app_starter.py diff --git a/scraibe/app/app_starter.py b/scraibe/app/app_starter.py new file mode 100644 index 0000000..9ed1d0b --- /dev/null +++ b/scraibe/app/app_starter.py @@ -0,0 +1,28 @@ +""" +This script is used to start the Gradio interface for audio transcription. +A configuration file can be passed to the script to configure the interface. +If no configuration file is passed, the default configuration is used. +The main Reason for this script is to allow the use of multiprocessing in the app. +""" + +import multiprocessing +from scraibe.misc import ParseKwargs +from argparse import ArgumentParser + +parser = ArgumentParser() + +parser.add_argument("--server-config", type=str, default= None, + help="Path to the configy.yml file.") + +parser.add_argument('--server-kwargs', nargs='*', action=ParseKwargs, default={}, + help='Keyword arguments for the Gradio app.') + +args = parser.parse_args() + +if __name__ == '__main__': + + multiprocessing.set_start_method('spawn') + + from scraibe.app.app import app + + app(config = args.server_config, **args.server_kwargs) \ No newline at end of file diff --git a/scraibe/cli.py b/scraibe/cli.py index c023f38..f4b49f7 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -5,10 +5,11 @@ The function includes arguments for specifying the audio files, model paths, output formats, and other options necessary for transcription. """ import os -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Action import json from .autotranscript import Scraibe +from .misc import ParseKwargs from .app.app import gradio_Interface from whisper.tokenizer import LANGUAGES , TO_LANGUAGE_CODE @@ -41,13 +42,15 @@ def cli(): help="List of audio files to transcribe.") group.add_argument('--start-server', action='store_true', - help='Start the Gradio app.') + help='Start the Gradio app.' \ + 'If set, all other arguments are ignored' \ + 'besides --server-config or --server-kwargs.') - parser.add_argument("--port", type=int, default= None, - help="Port to run the Gradio app on. Defaults to 7860.") + parser.add_argument("--server-config", type=str, default= None, + help="Path to the configy.yml file.") - parser.add_argument("--server-name", type=str, default= None, - help="Name of the Gradio app. If empty 127.0.0.1 or 0.0.0.0 will be used.") + parser.add_argument('--server-kwargs', nargs='*', action=ParseKwargs, default={}, + help='Keyword arguments for the Gradio app.') parser.add_argument("--whisper-model-name", default="medium", help="Name of the Whisper model to use.") @@ -66,7 +69,8 @@ def cli(): help="Device to use for PyTorch inference.") parser.add_argument("--num-threads", type=int, default=0, - help="Number of threads used by torch for CPU inference; overrides MKL_NUM_THREADS/OMP_NUM_THREADS.") + help="Number of threads used by torch for CPU inference; '\ + 'overrides MKL_NUM_THREADS/OMP_NUM_THREADS.") parser.add_argument("--output-directory", "-o", type=str, default=".", help="Directory to save the transcription outputs.") @@ -113,55 +117,59 @@ def cli(): if arg_dict["whisper_model_directory"]: class_kwargs["download_root"] = arg_dict.pop("whisper_model_directory") - model = Scraibe(**class_kwargs) - - - if arg_dict["audio_files"]: - audio_files = arg_dict.pop("audio_files") + if not start_server: - if task == "autotranscribe" or task == "autotranscribe+translate": - for audio in audio_files: - if task == "autotranscribe+translate": - task = "translate" - else: - task = "transcribe" - - out = model.autotranscribe(audio,task = task, language=arg_dict.pop("language"), verbose = arg_dict.pop("verbose_output")) - basename = audio.split("/")[-1].split(".")[0] - print(f'Saving {basename}.{out_format} to {out_folder}') - out.save(os.path.join(out_folder, f"{basename}.{out_format}")) - - elif task == "diarization": - for audio in audio_files: - if arg_dict.pop("verbose_output"): - print(f"Verbose not implemented for diarization.") - - out = model.diarization(audio) - basename = audio.split("/")[-1].split(".")[0] - path = os.path.join(out_folder, f"{basename}.{out_format}") - - print(f'Saving {basename}.{out_format} to {out_folder}') - - with open(path, "w") as f: - json.dump(json.dumps(out, indent= 1), f) + model = Scraibe(**class_kwargs) - elif task == "transcribe" or task == "translate": + if arg_dict["audio_files"]: + audio_files = arg_dict.pop("audio_files") - for audio in audio_files: - - out = model.transcribe(audio, task = task, - language= arg_dict.pop("language"), - verbose = arg_dict.pop("verbose_output")) - basename = audio.split("/")[-1].split(".")[0] - path = os.path.join(out_folder, f"{basename}.{out_format}") - with open(path, "w") as f: - f.write(out) + if task == "autotranscribe" or task == "autotranscribe+translate": + for audio in audio_files: + if task == "autotranscribe+translate": + task = "translate" + else: + task = "transcribe" + + out = model.autotranscribe(audio,task = task, language=arg_dict.pop("language"), verbose = arg_dict.pop("verbose_output")) + basename = audio.split("/")[-1].split(".")[0] + print(f'Saving {basename}.{out_format} to {out_folder}') + out.save(os.path.join(out_folder, f"{basename}.{out_format}")) + + elif task == "diarization": + for audio in audio_files: + if arg_dict.pop("verbose_output"): + print(f"Verbose not implemented for diarization.") + + out = model.diarization(audio) + basename = audio.split("/")[-1].split(".")[0] + path = os.path.join(out_folder, f"{basename}.{out_format}") + + print(f'Saving {basename}.{out_format} to {out_folder}') + + with open(path, "w") as f: + json.dump(json.dumps(out, indent= 1), f) + + elif task == "transcribe" or task == "translate": + for audio in audio_files: - if start_server: # unfinished code + out = model.transcribe(audio, task = task, + language= arg_dict.pop("language"), + verbose = arg_dict.pop("verbose_output")) + basename = audio.split("/")[-1].split(".")[0] + path = os.path.join(out_folder, f"{basename}.{out_format}") + with open(path, "w") as f: + f.write(out) + + + else: # unfinished code + import subprocess + import sys - gradio_Interface(model).queue().launch(server_port=args.port, server_name=args.server_name) + execute_path = os.path.join(os.path.dirname(__file__), "app/app_starter.py") + subprocess.run([sys.executable, execute_path]) if __name__ == "__main__": cli() \ No newline at end of file diff --git a/scraibe/misc.py b/scraibe/misc.py index b1afeea..ae9136e 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -1,6 +1,7 @@ import os import yaml from pyannote.audio.core.model import CACHE_DIR as PYANNOTE_CACHE_DIR +from argparse import Action CACHE_DIR = os.getenv( "AUTOT_CACHE", @@ -38,3 +39,17 @@ def config_diarization_yaml(file_path: str, path_to_segmentation: str = None) -> with open(file_path, "w") as stream: yaml.dump(yml, stream) + +class ParseKwargs(Action): + """ + Custom argparse action to parse keyword arguments. + """ + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, dict()) + for value in values: + key, value = value.split('=') + try: + value = eval(value) + except: + pass + getattr(namespace, self.dest)[key] = value \ No newline at end of file From ea68b5de5f447c1a9bde22ea1d51bf6b27bd70d6 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 10:31:50 +0100 Subject: [PATCH 104/331] add .yml to package data --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 64d30b9..1e2c641 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -from calendar import c import pkg_resources import os from setuptools import setup, find_packages @@ -21,6 +20,8 @@ with open(verfile, "r") as fp: build_version = "SCRAIBE_BUILD" in os.environ +version["ISRELEASED"] = True if "ISRELEASED" in os.environ else False + if __name__ == "__main__": setup( @@ -53,7 +54,7 @@ if __name__ == "__main__": keywords = ['transcription', 'speech recognition', 'whisper', 'pyannote', 'audio', 'ScrAIbe', 'scraibe', 'speech-to-text', 'speech-to-text transcription', 'speech-to-text recognition', 'voice-to-speech'], - package_data={'scraibe.app' : ["*.html", "*.svg"]}, + package_data={'scraibe.app' : ["*.html", "*.svg","*.yml"]}, entry_points={'console_scripts': ['scraibe = scraibe.cli:cli']} From 250c95535e0b1e1e248248d9f5f729dee0b174ef Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 13:31:02 +0100 Subject: [PATCH 105/331] fixed unused import --- scraibe/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index f4b49f7..618f6d8 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -5,7 +5,7 @@ The function includes arguments for specifying the audio files, model paths, output formats, and other options necessary for transcription. """ import os -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Action +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter import json from .autotranscript import Scraibe From 74eba1c641c7bf397078914ee3c3ae97b9242b22 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 13:31:32 +0100 Subject: [PATCH 106/331] added slightly more robust erroir haneling using local models --- scraibe/diarisation.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index f90bcdb..3e6047c 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -27,7 +27,9 @@ Usage: diarisation_output = model.diarization("path/to/audiofile.wav") """ +import warnings import os +import yaml from pathlib import Path from typing import TypeVar, Union @@ -213,7 +215,39 @@ class Diariser: model = 'pyannote/speaker-diarization' elif not os.path.exists(model) and use_auth_token is not None: model = 'pyannote/speaker-diarization' - + elif os.path.exists(model) and not use_auth_token: + # check if model can be found locally nearby the config file + with open(model, 'r') as file: + config = yaml.safe_load(file) + + path_to_model = config['pipeline']['params']['segmentation'] + + if not os.path.exists(path_to_model): + warnings.warn(f"Model not found at {path_to_model}. "\ + "Trying to find it nearby the config file.") + + pwd = file.split("/")[:-1] + path_to_model = os.path.join(pwd, "pytorch_model.bin") + + if not os.path.exists(path_to_model): + warnings.warn(f"Model not found at {path_to_model}. \ + 'Trying to find it nearby .bin files instead.") + # list elementes with the ending .bin + bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] + if len(bin_files) == 1: + path_to_model = os.path.join(pwd, bin_files[0]) + else: + warnings.warn("Found more than one .bin file. "\ + "or none. Please specify the path to the model " \ + "or setup a huggingface token.") + + warnings.warn(f"Found model at {path_to_model} overwriting config file.") + + config['pipeline']['params']['segmentation'] = path_to_model + + with open(model, 'w') as file: + yaml.dump(config, file) + _model = Pipeline.from_pretrained(model, use_auth_token = use_auth_token, cache_dir = cache_dir, From 25217533cbe526f979a15074ff3c2f97ef37dbd9 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 13:37:28 +0100 Subject: [PATCH 107/331] removed qtfaststart beacaue it is not used --- scraibe/app/__init__.py | 1 - scraibe/app/qtfaststart.py | 319 ------------------------------------- 2 files changed, 320 deletions(-) delete mode 100644 scraibe/app/qtfaststart.py diff --git a/scraibe/app/__init__.py b/scraibe/app/__init__.py index a38ad86..bdf5464 100644 --- a/scraibe/app/__init__.py +++ b/scraibe/app/__init__.py @@ -1,4 +1,3 @@ -from .qtfaststart import * from .multi import * from .interface import * from .stg import * diff --git a/scraibe/app/qtfaststart.py b/scraibe/app/qtfaststart.py deleted file mode 100644 index e57eb20..0000000 --- a/scraibe/app/qtfaststart.py +++ /dev/null @@ -1,319 +0,0 @@ -""" -This file contains a modified version of qtfaststart by qtfaststart -https://github.com/danielgtaylor/qtfaststart/tree/master - -All credit goes to the original author. -Copyright (C) 2008 - 2013 Daniel G. Taylor -Permission is hereby granted, free of charge, to any person obtaining a copy of this -software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies -or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. -""" - -import logging -import os -import struct -import collections -import io - -# define error classes -class FastStartException(Exception): - """ - Raised when something bad happens during processing. - """ - pass - -class FastStartSetupError(FastStartException): - """ - Rasised when asked to process a file that does not need processing - """ - pass - -class MalformedFileError(FastStartException): - """ - Raised when the input file is setup in an unexpected way - """ - pass - -class UnsupportedFormatError(FastStartException): - """ - Raised when a movie file is recognized as a format not supported. - """ - pass - -# define constants -CHUNK_SIZE = 8192 - -log = logging.getLogger("qtfaststart") - -# Older versions of Python require this to be defined -if not hasattr(os, 'SEEK_CUR'): - os.SEEK_CUR = 1 - -Atom = collections.namedtuple('Atom', 'name position size') - -def read_atom(datastream): - """ - Read an atom and return a tuple of (size, type) where size is the size - in bytes (including the 8 bytes already read) and type is a "fourcc" - like "ftyp" or "moov". - """ - size, type = struct.unpack(">L4s", datastream.read(8)) - type = type.decode('ascii') - return size, type - - -def _read_atom_ex(datastream): - """ - Read an Atom from datastream - """ - pos = datastream.tell() - atom_size, atom_type = read_atom(datastream) - if atom_size == 1: - atom_size, = struct.unpack(">Q", datastream.read(8)) - return Atom(atom_type, pos, atom_size) - - -def get_index(datastream): - """ - Return an index of top level atoms, their absolute byte-position in the - file and their size in a list: - - index = [ - ("ftyp", 0, 24), - ("moov", 25, 2658), - ("free", 2683, 8), - ... - ] - - The tuple elements will be in the order that they appear in the file. - """ - log.debug("Getting index of top level atoms...") - - index = list(_read_atoms(datastream)) - _ensure_valid_index(index) - - return index - - -def _read_atoms(datastream): - """ - Read atoms until an error occurs - """ - while datastream: - try: - atom = _read_atom_ex(datastream) - log.debug("%s: %s" % (atom.name, atom.size)) - except: - break - - yield atom - - if atom.size == 0: - if atom.name == "mdat": - # Some files may end in mdat with no size set, which generally - # means to seek to the end of the file. We can just stop indexing - # as no more entries will be found! - break - else: - # Weird, but just continue to try to find more atoms - continue - - datastream.seek(atom.position + atom.size) - - -def _ensure_valid_index(index): - """ - Ensure the minimum viable atoms are present in the index. - - Raise FastStartException if not. - """ - top_level_atoms = set([item.name for item in index]) - for key in ["moov", "mdat"]: - if key not in top_level_atoms: - log.error("%s atom not found, is this a valid MOV/MP4 file?" % key) - raise FastStartException() - - -def find_atoms(size, datastream): - """ - Compatibilty interface for _find_atoms_ex - """ - fake_parent = Atom('fake', datastream.tell()-8, size+8) - for atom in _find_atoms_ex(fake_parent, datastream): - yield atom.name - - -def _find_atoms_ex(parent_atom, datastream): - """ - Yield either "stco" or "co64" Atoms from datastream. - datastream will be 8 bytes into the stco or co64 atom when the value - is yielded. - - It is assumed that datastream will be at the end of the atom after - the value has been yielded and processed. - - parent_atom is the parent atom, a 'moov' or other ancestor of CO - atoms in the datastream. - """ - stop = parent_atom.position + parent_atom.size - - while datastream.tell() < stop: - try: - atom = _read_atom_ex(datastream) - except: - log.exception("Error reading next atom!") - raise FastStartException() - - if atom.name in ["trak", "mdia", "minf", "stbl"]: - # Known ancestor atom of stco or co64, search within it! - for res in _find_atoms_ex(atom, datastream): - yield res - elif atom.name in ["stco", "co64"]: - yield atom - else: - # Ignore this atom, seek to the end of it. - datastream.seek(atom.position + atom.size) - - -def process(infilename, limit=float('inf')): - """ - Convert a Quicktime/MP4 file for streaming by moving the metadata to - the front of the file. This method writes a new file. - - If limit is set to something other than zero it will be used as the - number of bytes to write of the atoms following the moov atom. This - is very useful to create a small sample of a file with full headers, - which can then be used in bug reports and such. - """ - if isinstance(infilename, str): - datastream = open(infilename, "rb") - elif isinstance(infilename, bytes): - datastream = io.BytesIO(infilename) - else: - raise TypeError("infilename must be a filename, bytes or file-like object") - # Get the top level atom index - index = get_index(datastream) - - mdat_pos = 999999 - free_size = 0 - - # Make sure moov occurs AFTER mdat, otherwise no need to run! - for atom in index: - # The atoms are guaranteed to exist from get_index above! - if atom.name == "moov": - moov_atom = atom - moov_pos = atom.position - elif atom.name == "mdat": - mdat_pos = atom.position - elif atom.name == "free" and atom.position < mdat_pos: - # This free atom is before the mdat! - free_size += atom.size - log.info("Removing free atom at %d (%d bytes)" % (atom.position, atom.size)) - elif atom.name == "\x00\x00\x00\x00" and atom.position < mdat_pos: - # This is some strange zero atom with incorrect size - free_size += 8 - log.info("Removing strange zero atom at %s (8 bytes)" % atom.position) - - # Offset to shift positions - offset = moov_atom.size - free_size - - if moov_pos < mdat_pos: - # moov appears to be in the proper place, don't shift by moov size - offset -= moov_atom.size - if not free_size: - # No free atoms and moov is correct, we are done! - log.error("This file appears to already be setup for streaming!") - # Stupid hack to retrun the non-processed file: - if isinstance(infilename, str): - return open(infilename, "rb").read() - elif isinstance(infilename, bytes): - return io.BytesIO(infilename).read() - - # Read and fix moov - moov = _patch_moov(datastream, moov_atom, offset) - - log.info("Writing output...") - outfile = b'' - - # Write ftype - for atom in index: - if atom.name == "ftyp": - log.debug("Writing ftyp... (%d bytes)" % atom.size) - datastream.seek(atom.position) - outfile += datastream.read(atom.size) - - # Write moov - _bytes = moov.getvalue() - log.debug("Writing moov... (%d bytes)" % len(_bytes)) - outfile += _bytes - - # Write the rest - atoms = [item for item in index if item.name not in ["ftyp", "moov", "free"]] - for atom in atoms: - log.debug("Writing %s... (%d bytes)" % (atom.name, atom.size)) - datastream.seek(atom.position) - - # for compatability, allow '0' to mean no limit - cur_limit = limit or float('inf') - cur_limit = min(cur_limit, atom.size) - - for chunk in get_chunks(datastream, CHUNK_SIZE, cur_limit): - outfile += chunk - - return outfile - - -def _patch_moov(datastream, atom, offset): - datastream.seek(atom.position) - moov = io.BytesIO(datastream.read(atom.size)) - - # reload the atom from the fixed stream - atom = _read_atom_ex(moov) - - for atom in _find_atoms_ex(atom, moov): - # Read either 32-bit or 64-bit offsets - ctype, csize = dict( - stco=('L', 4), - co64=('Q', 8), - )[atom.name] - - # Get number of entries - version, entry_count = struct.unpack(">2L", moov.read(8)) - - log.info("Patching %s with %d entries" % (atom.name, entry_count)) - - entries_pos = moov.tell() - - struct_fmt = ">%(entry_count)s%(ctype)s" % vars() - - # Read entries - entries = struct.unpack(struct_fmt, moov.read(csize * entry_count)) - - # Patch and write entries - offset_entries = [entry + offset for entry in entries] - moov.seek(entries_pos) - moov.write(struct.pack(struct_fmt, *offset_entries)) - return moov - -def get_chunks(stream, chunk_size, limit): - remaining = limit - while remaining: - chunk = stream.read(min(remaining, chunk_size)) - if not chunk: - return - remaining -= len(chunk) - yield chunk From c33c0f1f5369038bc47eab5c5cb13995fb9d4592 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 15:09:11 +0100 Subject: [PATCH 108/331] add app file and add gitignore --- .dockerignore | 6 ++++ .gitignore | 6 ++++ scraibe/app/app.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 scraibe/app/app.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1155cba --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +scraibe/*__pycache__ +scraibe/app/*__pycache__ +scraibe/.pyannotetoken +.git +.gitignore +.github diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18c7986 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +transcibe.py +scraibe/*__pycache__ +scraibe/app/*__pycache__ +scraibe/.pyannotetoken + + diff --git a/scraibe/app/app.py b/scraibe/app/app.py new file mode 100644 index 0000000..798e2be --- /dev/null +++ b/scraibe/app/app.py @@ -0,0 +1,72 @@ +""" +Gradio App. +-------------------------------- + +This module provides an interface to transcribe audio files using the +Scraibe model. Users can either upload an audio file or record their speech +live for transcription. The application supports multiple languages and provides +options to specify the number of speakers and the language of the audio. + +Attributes: + LANGUAGES (list): A list of supported languages for transcription. + +Usage: + Run this script to start the Gradio web interface for audio transcription. + +""" + + +#### +# Gradio Interface +#### + +from threading import Thread + +import scraibe.app.global_var as gv +from .interface import gradio_Interface +from .multi import * +from .utils import * + + +def app(config : str = None, **kwargs): + """ + Launches the Gradio interface for audio transcription. + + Args: + interface_params (dict): A dictionary of parameters for the Gradio interface. + queue_params (dict): A dictionary of parameters for the queue. + launch_params (dict): A dictionary of parameters for launching the interface. + + Returns: + None + + """ + + # Load the configuration + + config = AppConfig.load_config(config, **kwargs) + + + gv.MODEL_PROCESS = start_model_worker(gv.MODEL_PARAMS, + gv.REQUEST_QUEUE, + gv.LAST_ACTIVE_TIME, + gv.RESPONSE_QUEUE, + gv.LOADED_EVENT, + gv.RUNNING_EVENT) + + timer = Thread(target=timer_thread, args=(gv.REQUEST_QUEUE, + gv.LAST_ACTIVE_TIME, + gv.LOADED_EVENT, + gv.RUNNING_EVENT), daemon=True) + layout = config.get_layout() + + timer.start() + + print("Starting Gradio Web Interface") + + gradio_Interface(layout).queue(**config.queue).launch(**config.launch) + + timer.join() + gv.MODEL_PROCESS.join() + + print('') \ No newline at end of file From 8ba3ee146b566b138215ed7e05b7e60b6c97e922 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 15:28:50 +0100 Subject: [PATCH 109/331] Redifine ParseKwargs to not import scraibe to early on --- scraibe/app/app_starter.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scraibe/app/app_starter.py b/scraibe/app/app_starter.py index 9ed1d0b..e8542c2 100644 --- a/scraibe/app/app_starter.py +++ b/scraibe/app/app_starter.py @@ -6,8 +6,21 @@ The main Reason for this script is to allow the use of multiprocessing in the ap """ import multiprocessing -from scraibe.misc import ParseKwargs -from argparse import ArgumentParser +from argparse import ArgumentParser, Action + +class ParseKwargs(Action): + """ + Custom argparse action to parse keyword arguments. has to bne redifined here because of multiprocessing. + """ + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, dict()) + for value in values: + key, value = value.split('=') + try: + value = eval(value) + except: + pass + getattr(namespace, self.dest)[key] = value parser = ArgumentParser() From b0858e464775941e2624ba3c4953a7f32d941bbf Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 15:33:16 +0100 Subject: [PATCH 110/331] parse args to app_starter.py --- scraibe/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index 618f6d8..2654b9c 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -169,7 +169,10 @@ def cli(): execute_path = os.path.join(os.path.dirname(__file__), "app/app_starter.py") - subprocess.run([sys.executable, execute_path]) + config = arg_dict.pop("server_config") + server_kwargs = arg_dict.pop("server_kwargs") + + subprocess.run([sys.executable, execute_path, f"--server-config={config}", f"--server-kwargs={server_kwargs}"]) if __name__ == "__main__": cli() \ No newline at end of file From c1ed0547b87c9e8f3a9edc88d56b911b23576d0b Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 15:38:53 +0100 Subject: [PATCH 111/331] handle exeptions when dict is empty --- scraibe/cli.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index 2654b9c..1c7f320 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -172,7 +172,14 @@ def cli(): config = arg_dict.pop("server_config") server_kwargs = arg_dict.pop("server_kwargs") - subprocess.run([sys.executable, execute_path, f"--server-config={config}", f"--server-kwargs={server_kwargs}"]) + if not config: + subprocess.run([sys.executable, execute_path, f"--server-kwargs={server_kwargs}"]) + elif not server_kwargs: + subprocess.run([sys.executable, execute_path, f"--server-config={config}"]) + elif not config and not server_kwargs: + subprocess.run([sys.executable, execute_path]) + else: + subprocess.run([sys.executable, execute_path, f"--server-config={config}", f"--server-kwargs={server_kwargs}"]) if __name__ == "__main__": cli() \ No newline at end of file From ff47d058c826e99b3bca37de106906bfd4eec11b Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 15:46:51 +0100 Subject: [PATCH 112/331] fixed typo --- scraibe/diarisation.py | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index 3e6047c..c62bda0 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -219,34 +219,34 @@ class Diariser: # check if model can be found locally nearby the config file with open(model, 'r') as file: config = yaml.safe_load(file) + + path_to_model = config['pipeline']['params']['segmentation'] + + if not os.path.exists(path_to_model): + warnings.warn(f"Model not found at {path_to_model}. "\ + "Trying to find it nearby the config file.") - path_to_model = config['pipeline']['params']['segmentation'] + pwd = model.split("/")[:-1] + path_to_model = os.path.join(pwd, "pytorch_model.bin") if not os.path.exists(path_to_model): - warnings.warn(f"Model not found at {path_to_model}. "\ - "Trying to find it nearby the config file.") - - pwd = file.split("/")[:-1] - path_to_model = os.path.join(pwd, "pytorch_model.bin") - - if not os.path.exists(path_to_model): - warnings.warn(f"Model not found at {path_to_model}. \ - 'Trying to find it nearby .bin files instead.") - # list elementes with the ending .bin - bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] - if len(bin_files) == 1: - path_to_model = os.path.join(pwd, bin_files[0]) - else: - warnings.warn("Found more than one .bin file. "\ - "or none. Please specify the path to the model " \ - "or setup a huggingface token.") - - warnings.warn(f"Found model at {path_to_model} overwriting config file.") - - config['pipeline']['params']['segmentation'] = path_to_model - - with open(model, 'w') as file: - yaml.dump(config, file) + warnings.warn(f"Model not found at {path_to_model}. \ + 'Trying to find it nearby .bin files instead.") + # list elementes with the ending .bin + bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] + if len(bin_files) == 1: + path_to_model = os.path.join(pwd, bin_files[0]) + else: + warnings.warn("Found more than one .bin file. "\ + "or none. Please specify the path to the model " \ + "or setup a huggingface token.") + + warnings.warn(f"Found model at {path_to_model} overwriting config file.") + + config['pipeline']['params']['segmentation'] = path_to_model + + with open(model, 'w') as file: + yaml.dump(config, file) _model = Pipeline.from_pretrained(model, use_auth_token = use_auth_token, From b45a6011193f81cf64859c990faf7440af5e7022 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 15:56:10 +0100 Subject: [PATCH 113/331] fixed path --- scraibe/diarisation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index c62bda0..570ac29 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -227,6 +227,8 @@ class Diariser: "Trying to find it nearby the config file.") pwd = model.split("/")[:-1] + pwd = "/".join(pwd) + path_to_model = os.path.join(pwd, "pytorch_model.bin") if not os.path.exists(path_to_model): From 483204efda38325a61c199ec7b9d5445c5bb5996 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 16:15:58 +0100 Subject: [PATCH 114/331] removed print --- scraibe/app/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scraibe/app/app.py b/scraibe/app/app.py index 798e2be..a50dfc2 100644 --- a/scraibe/app/app.py +++ b/scraibe/app/app.py @@ -67,6 +67,4 @@ def app(config : str = None, **kwargs): gradio_Interface(layout).queue(**config.queue).launch(**config.launch) timer.join() - gv.MODEL_PROCESS.join() - - print('') \ No newline at end of file + gv.MODEL_PROCESS.join() \ No newline at end of file From 41be3fdee2f894882c200cec0cdd92f7b1e25f9a Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 26 Jan 2024 16:21:47 +0100 Subject: [PATCH 115/331] use TIMEOUT param --- scraibe/app/app.py | 4 +++- scraibe/app/multi.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scraibe/app/app.py b/scraibe/app/app.py index a50dfc2..d1c27d7 100644 --- a/scraibe/app/app.py +++ b/scraibe/app/app.py @@ -57,7 +57,9 @@ def app(config : str = None, **kwargs): timer = Thread(target=timer_thread, args=(gv.REQUEST_QUEUE, gv.LAST_ACTIVE_TIME, gv.LOADED_EVENT, - gv.RUNNING_EVENT), daemon=True) + gv.RUNNING_EVENT, + gv.TIMEOUT), daemon=True) + layout = config.get_layout() timer.start() diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py index ec9f17e..487cade 100644 --- a/scraibe/app/multi.py +++ b/scraibe/app/multi.py @@ -84,7 +84,7 @@ def timer_thread(request_queue, last_active_time, loaded_event, running_event, - timeout=30): + timeout): while True: time.sleep(timeout) From f872fb1caa836e795b2ccc31bfb8af71ac76ac0b Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 29 Jan 2024 12:43:14 +0100 Subject: [PATCH 116/331] Autotrancsipt test --- scraibe/test/test_autotranscript.py | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 scraibe/test/test_autotranscript.py diff --git a/scraibe/test/test_autotranscript.py b/scraibe/test/test_autotranscript.py new file mode 100644 index 0000000..8f85353 --- /dev/null +++ b/scraibe/test/test_autotranscript.py @@ -0,0 +1,55 @@ +import pytest +import torch +from scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor +from unittest.mock import MagicMOck, patch + + +""" +@pytest.fixture +def example_audio_file(tmp_path): + audio_path = tmp_path +""" +@pytest.fixture +def create_scraibe_instance(): + return Scraibe() + + + +def test_scraibe_init(create_scraibe_instance): + model = create_scraibe_instance + assert isinstance(model.transcriber, Transcriber) + assert isinstance(model.diariser, Diariser) + + +def test_scraibe_autotranscribe(create_scraibe_instance, example_audio_file): + model = create_scraibe_instance + transcript = example_audio_file + assert isinstance(transcript, Transcript) + +def test_scraibe_diarization(create_scraibe_instance, example_audio_file): + model = create_scraibe_instance + diarisation_result = model.diarisation(example_audio_file) + assert isinstance(diarisation_result, dict) + + +def test_scraibe_transcribe(create_scraibe_instance, example_audio_file): + model = create_scraibe_instance + transcription_result = model.transcribe(example_audio_file) + assert isinstance(transcription_result, str) + + +def test_remove_audio_file(create_scraibe_instance, example_audio_file): + model = create_scraibe_instance + with pytest.raises(ValueError): + model.remove_audio_file("non_existing_audio_file") + + model.remove_audio_file(example_audio_file) + assert not os.path.exists(example_audio_file) + + +def test_get_audio_file(create_scraibe_instance, example_audio_file): + model = create_scraibe_instance + audio_file = os.path.exist(example_audio_file) + assert isinstance(audio_file, AudioProcessor) + assert isinstance(audio_file.waveform, torch.Tensor) + assert isinstance(audio_file.sr, torch.Tensor) From 6975986ed4e3b4c7d03bd14b6d19cb702cccf028 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 29 Jan 2024 12:32:23 +0000 Subject: [PATCH 117/331] added docstings and typings --- scraibe/app/app.py | 56 +++++++++--- scraibe/app/app_starter.py | 43 ++++++++-- scraibe/app/config.yml | 1 - scraibe/app/global_var.py | 39 ++++++--- scraibe/app/interactions.py | 2 - scraibe/app/interface.py | 30 ++++++- scraibe/app/multi.py | 103 +++++++++++++++++----- scraibe/app/stg.py | 108 +++++++++++++++++------- scraibe/app/utils.py | 164 +++++++++++++++++++++++++++--------- 9 files changed, 410 insertions(+), 136 deletions(-) diff --git a/scraibe/app/app.py b/scraibe/app/app.py index d1c27d7..76b189a 100644 --- a/scraibe/app/app.py +++ b/scraibe/app/app.py @@ -1,21 +1,35 @@ """ -Gradio App. --------------------------------- +Gradio App +---------- This module provides an interface to transcribe audio files using the Scraibe model. Users can either upload an audio file or record their speech live for transcription. The application supports multiple languages and provides -options to specify the number of speakers and the language of the audio. +options to specify the number of speakers and the language of the audio. It also +enables efficient management of resources by loading and unloading AI models +based on usage. -Attributes: - LANGUAGES (list): A list of supported languages for transcription. +The configuration is managed via a 'config.yml' file, which allows customization +of various aspects of the application, including the Gradio interface, queue +management, and model parameters. + +Configuration Sections in 'config.yml': +- launch: Settings for launching the interface, such as server port, authentication, SSL configuration. +- queue: Configuration for managing request handling and concurrency. +- layout: Customization options for the interface layout, like headers, footers, and logos. +- model: Specifications for different AI models used in transcription. +- advanced: Advanced settings, including session timeout duration. + +Note: + The .queue function of the Gradio interface is currently experiencing issues + and might not work as expected. Usage: Run this script to start the Gradio web interface for audio transcription. - """ + #### # Gradio Interface #### @@ -31,18 +45,27 @@ from .utils import * def app(config : str = None, **kwargs): """ Launches the Gradio interface for audio transcription. - + + Initializes the Gradio web interface with settings from a YAML configuration file + and/or keyword arguments. The function manages AI models, handling their loading + into RAM and unloading after a session or specified timeout. + + The `kwargs` are used to override or supplement values from the `config.yml` file. + They should follow the structure of `config.yml`, which includes sections like + 'launch', 'queue', 'layout', 'model', and 'advanced'. + Args: - interface_params (dict): A dictionary of parameters for the Gradio interface. - queue_params (dict): A dictionary of parameters for the queue. - launch_params (dict): A dictionary of parameters for launching the interface. - + config (str): Path to the YAML configuration file. Default settings are used + if not provided. + **kwargs: Keyword arguments corresponding to the configuration sections. Each + argument should be a dictionary reflecting the structure of its + respective section in `config.yml`. + Returns: None - """ - - # Load the configuration + + # Load and override configuration from the YAML file with kwargs config = AppConfig.load_config(config, **kwargs) @@ -54,19 +77,24 @@ def app(config : str = None, **kwargs): gv.LOADED_EVENT, gv.RUNNING_EVENT) + # Set the timer thread to manage model loading and unloading timer = Thread(target=timer_thread, args=(gv.REQUEST_QUEUE, gv.LAST_ACTIVE_TIME, gv.LOADED_EVENT, gv.RUNNING_EVENT, gv.TIMEOUT), daemon=True) + # Set the layout for the Gradio interface layout = config.get_layout() + # start the timer thread timer.start() print("Starting Gradio Web Interface") + # Launch the Gradio interface gradio_Interface(layout).queue(**config.queue).launch(**config.launch) + # Wait for the timer thread to finish timer.join() gv.MODEL_PROCESS.join() \ No newline at end of file diff --git a/scraibe/app/app_starter.py b/scraibe/app/app_starter.py index e8542c2..b1597f0 100644 --- a/scraibe/app/app_starter.py +++ b/scraibe/app/app_starter.py @@ -1,18 +1,47 @@ -""" -This script is used to start the Gradio interface for audio transcription. -A configuration file can be passed to the script to configure the interface. -If no configuration file is passed, the default configuration is used. -The main Reason for this script is to allow the use of multiprocessing in the app. +"""Starts the Gradio interface for audio transcription with optional configuration. + +This script, app_starter.py, initializes and runs a Gradio interface for audio +transcription tasks. It allows users to provide a configuration file for custom +settings. If no configuration file is specified, default settings are applied. +The script is designed to support multiprocessing for improved performance. + +Attributes: + args (argparse.Namespace): Parsed command line arguments. + +Example: + To run the script with custom server configuration and keyword arguments: + $ python app_starter.py --server-config path/to/config.yml --server-kwargs key1=val1 key2=val2 """ import multiprocessing from argparse import ArgumentParser, Action class ParseKwargs(Action): - """ - Custom argparse action to parse keyword arguments. has to bne redifined here because of multiprocessing. + """Custom action for argparse to parse keyword arguments for Gradio app configuration. + + This action parses a series of keyword arguments and converts them into a + dictionary, which is then used to configure the Gradio application. It + supports dynamic types by attempting to evaluate the argument values. + + Attributes: + dest (str): The name of the attribute to be added to the object returned by parse_args(). """ def __call__(self, parser, namespace, values, option_string=None): + """Parses keyword arguments and updates the namespace with these arguments as a dictionary. + + For each value provided, this method splits the string on the '=' character + to separate keys and values, attempting to evaluate the values for Python + literals. If evaluation fails, the raw string is used as the value. + + Args: + parser (ArgumentParser): The ArgumentParser object that called this method. + namespace (Namespace): An argparse.Namespace object that will be returned by parse_args(). + values (list of str): List of strings, each representing a key-value pair in 'key=value' format. + option_string (Optional[str]): The option string that was used to invoke this action. + + Raises: + ValueError: If any string in values does not contain the '=' character, indicating an invalid format. + """ setattr(namespace, self.dest, dict()) for value in values: key, value = value.split('=') diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml index 9e42db3..8b25af5 100644 --- a/scraibe/app/config.yml +++ b/scraibe/app/config.yml @@ -22,7 +22,6 @@ launch: ssl_certfile : null ssl_keyfile_password : null ssl_verify : false - quiet : false show_api : false allowed_paths : null blocked_paths : null diff --git a/scraibe/app/global_var.py b/scraibe/app/global_var.py index 5599320..b1c1c80 100644 --- a/scraibe/app/global_var.py +++ b/scraibe/app/global_var.py @@ -1,24 +1,37 @@ """ -Stores global variables for the app. +global_var.py + +This module stores global variables for the app. + +Global variables: + REQUEST_QUEUE (multiprocessing.Queue): A queue to store audio file paths as strings. + RESPONSE_QUEUE (multiprocessing.Queue): A queue to store transcriptions as strings. + LAST_ACTIVE_TIME (multiprocessing.Value): A value to store the time of the last activity. + LOADED_EVENT (multiprocessing.Event): An event to indicate when the model is loaded. + RUNNING_EVENT (multiprocessing.Event): An event to indicate when the model is running. + MODEL_PARAMS (Optional[dict]): A dictionary to store the model parameters. + MODEL_PROCESS (Optional[multiprocessing.Process]): A process to handle the model globally. + LAST_USED (float): A float to track the time of the last user activity. + TIMEOUT (Optional[int]): An integer to store the timeout in seconds. + DEFAULT_APP_CONIFG_PATH (str): A string to store the default path to the app configuration file. """ -# Global variable to store the model import multiprocessing import os import time +from typing import Optional -REQUEST_QUEUE = multiprocessing.Queue() # audio file path as string -RESPONSE_QUEUE = multiprocessing.Queue() # transcription as string -LAST_ACTIVE_TIME = multiprocessing.Value('d', time.time()) # time of last activity -LOADED_EVENT = multiprocessing.Event() # model loaded event -RUNNING_EVENT = multiprocessing.Event() # model running event +REQUEST_QUEUE: multiprocessing.Queue = multiprocessing.Queue() # audio file path as string +RESPONSE_QUEUE: multiprocessing.Queue = multiprocessing.Queue() # transcription as string +LAST_ACTIVE_TIME: multiprocessing.Value = multiprocessing.Value('d', time.time()) # time of last activity +LOADED_EVENT: multiprocessing.Event = multiprocessing.Event() # model loaded event +RUNNING_EVENT: multiprocessing.Event = multiprocessing.Event() # model running event -MODEL_PARAMS = None # model parameters -MODEL_PROCESS = None # model process to handle globally +MODEL_PARAMS: Optional[dict] = None # model parameters +MODEL_PROCESS: Optional[multiprocessing.Process] = None # model process to handle globally # Global variable to track user activity -LAST_USED = time.time() -TIMEOUT = None #seconds - -DEFAULT_APP_CONIFG_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config.yml") +LAST_USED: float = time.time() +TIMEOUT: Optional[int] = None # seconds +DEFAULT_APP_CONIFG_PATH: str = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config.yml") \ No newline at end of file diff --git a/scraibe/app/interactions.py b/scraibe/app/interactions.py index 1719388..9206e75 100644 --- a/scraibe/app/interactions.py +++ b/scraibe/app/interactions.py @@ -3,8 +3,6 @@ This file contains ervery function that will be called when the user interacts w UI like pressing a button or uploading a file. """ -from re import M -import time import gradio as gr import scraibe.app.global_var as gv from scraibe import Transcript diff --git a/scraibe/app/interface.py b/scraibe/app/interface.py index fce582c..bab29a9 100644 --- a/scraibe/app/interface.py +++ b/scraibe/app/interface.py @@ -1,11 +1,20 @@ """ -This file contains the actual gradio Interface which is used to interact with the user. +This module contains the gradio Interface which is used to interact with the user. + +The interface is themed with a soft color scheme, with primary colors of green and orange, and a neutral color of gray. + +A list of languages is also defined in this module, which may be used elsewhere in the application. + +Classes: + Soft: A class from the gradio library used to theme the interface. + +Variables: + theme (gr.themes.Soft): The theme for the gradio interface. + LANGUAGES (list of str): A list of languages supported by the application. """ import gradio as gr -import os -import scraibe.app.global_var as gv from .interactions import * from .stg import * @@ -35,7 +44,20 @@ LANGUAGES = [ def gradio_Interface(layout = None,): - + """ + Creates a gradio interface for audio transcription. + + The interface includes options for the user to select the task, number of speakers, translation, language, and input type. + It also provides options for the user to upload or record audio/video, or upload files. + The output of the transcription is displayed in a textbox, and the JSON output in a JSON viewer. + The user can also annotate the output by naming the speakers. + + Args: + layout (dict, optional): A dictionary containing layout information. Defaults to None. + + Returns: + gr.Blocks: A gradio Blocks object representing the interface. + """ with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: # Define components diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py index 487cade..fe636a0 100644 --- a/scraibe/app/multi.py +++ b/scraibe/app/multi.py @@ -1,14 +1,30 @@ """ -This file contains the functions which are related to monitoring the actual app usage. -Therefore, the app is to be more efficient in the usage of the resources. -By for example, unloading or reloading the model. -""" +This module contains functions for managing and optimizing the resource usage of the application. +The functions in this module monitor the application's usage and make adjustments to improve efficiency. +This includes managing the loading and unloading of the model based on the application's activity. +This dynamic management of resources helps to ensure that the application uses only the resources it needs, +improving overall performance and reducing unnecessary resource consumption. + +Functions: + clear_queue(queue): Clears all items from the queue. + model_worker(model_params, request_queue, last_active_time, + response_queue, loaded_event, running_event, *args, **kwargs): Manages the model worker process. + +Modules: + time: Provides various time-related functions. + gc: Provides an interface to the garbage collector. + multiprocessing: Provides support for parallel execution of code. + torch: Provides tensor computation and deep learning functionality. + gradio: Provides a simple way to create interactive UIs for Python functions. + scraibe.autotranscript: Provides automatic transcription functionality. + .stg: Contains the GradioTranscriptionInterface class. +""" import time import gc -from typing import Union +from typing import Union, Any import multiprocessing import torch @@ -24,12 +40,27 @@ def clear_queue(queue): continue def model_worker(model_params : Union[Scraibe, dict], - request_queue, - last_active_time, - response_queue, - loaded_event, - running_event, - *args, **kwargs): + request_queue: multiprocessing.Queue, + last_active_time: multiprocessing.Value, + response_queue: multiprocessing.Queue, + loaded_event: multiprocessing.Event, + running_event: multiprocessing.Event, + *args: Any, **kwargs: Any) -> None: + """ + Manages the model worker process. + + The model worker process is responsible for running the model and returning the results. + + Args: + model_params (Union[Scraibe, dict]): The parameters for the Scraibe model. + request_queue (multiprocessing.Queue): The queue for incoming requests. + last_active_time (multiprocessing.Value): The last time the model was active. + response_queue (multiprocessing.Queue): The queue for outgoing responses. + loaded_event (multiprocessing.Event): An event that signals when the model is loaded. + running_event (multiprocessing.Event): An event that signals when the model is running. + *args: Additional arguments. + **kwargs: Additional keyword arguments. + """ loaded_event.set() @@ -68,23 +99,49 @@ def model_worker(model_params : Union[Scraibe, dict], clear_queue(response_queue) loaded_event.clear() -def start_model_worker(model_params, - request_queue, - last_active_time, - response_queue, - loaded_event, - running_event, - *args, **kwargs): +def start_model_worker(model_params: Union[Scraibe, dict], + request_queue: multiprocessing.Queue, + last_active_time: multiprocessing.Value, + response_queue: multiprocessing.Queue, + loaded_event: multiprocessing.Event, + running_event: multiprocessing.Event, + *args: Any, **kwargs: Any) -> multiprocessing.Process: + """ + Starts the model worker process. + + Args: + model_params (Union[Scraibe, dict]): The parameters for the Scraibe model. + request_queue (multiprocessing.Queue): The queue for incoming requests. + last_active_time (multiprocessing.Value): The last time the model was active. + response_queue (multiprocessing.Queue): The queue for outgoing responses. + loaded_event (multiprocessing.Event): An event that signals when the model is loaded. + running_event (multiprocessing.Event): An event that signals when the model is running. + *args: Additional arguments. + **kwargs: Additional keyword arguments. + + Returns: + multiprocessing.Process: The model worker process. + """ context = multiprocessing.get_context('spawn') model_process = context.Process(target=model_worker, args=(model_params, request_queue, last_active_time, response_queue,loaded_event, running_event, *args), kwargs=kwargs) model_process.start() return model_process -def timer_thread(request_queue, - last_active_time, - loaded_event, - running_event, - timeout): +def timer_thread(request_queue: multiprocessing.Queue, + last_active_time: multiprocessing.Value, + loaded_event: multiprocessing.Event, + running_event: multiprocessing.Event, + timeout: int) -> None: + """ + Monitors the model worker process and stops it after a period of inactivity. + + Args: + request_queue (multiprocessing.Queue): The queue for incoming requests. + last_active_time (multiprocessing.Value): The last time the model was active. + loaded_event (multiprocessing.Event): An event that signals when the model is loaded. + running_event (multiprocessing.Event): An event that signals when the model is running. + timeout (int): The period of inactivity after which the model worker process is stopped. + """ while True: time.sleep(timeout) diff --git a/scraibe/app/stg.py b/scraibe/app/stg.py index 1b9caf7..4ce31a1 100644 --- a/scraibe/app/stg.py +++ b/scraibe/app/stg.py @@ -1,41 +1,65 @@ """ -stg - scraibe to gradio interface +stg - Scraibe to Gradio Interface -This file contains the code for the scraibe to gradio interface. -It makes adds gradio interactions to the scraibe class in the back. +This module provides an interface between the Scraibe transcription system and the Gradio user interface. +It defines a class, GradioTranscriptionInterface, that wraps the Scraibe model and provides methods for performing transcription tasks through the Gradio UI. +Modules: + json: Used for encoding and decoding JSON data. + gradio as gr: Used for creating the Gradio UI. + tqdm: Used for displaying progress bars. + scraibe.app.global_var as gv: Contains global variables for the Scraibe app. """ - import json import gradio as gr from tqdm import tqdm +from typing import Any, Dict, Union, Tuple, List + -import scraibe.app.global_var as gv class GradioTranscriptionInterface: """ - Interface handling the interaction between Gradio UI and the Audio Transcription system. + A class that provides an interface between the Gradio UI and the Scraibe transcription system. + + This class wraps the Scraibe model and provides methods for performing transcription tasks through the Gradio UI. + These tasks include auto transcription, transcription, and diarisation. + + Attributes: + model (Scraibe): The Scraibe model for performing transcription tasks. """ - def __init__(self, model): + def __init__(self, model) -> None: """ - Initializes the GradioTranscriptionInterface with a transcription model. + Initializes the GradioTranscriptionInterface with a Scraibe model. Args: - model (Scraibe): Model responsible for audio transcription tasks. + model (Scraibe): The Scraibe model for performing transcription tasks. + *args (Any): Additional positional arguments. + **kwargs (Dict[str, Any]): Additional keyword arguments. """ - self.model = model + + self.model = model - def autotranscribe(self, source, - num_speakers : int, - translate : bool, - language : str,*args ,**kwargs): + def autotranscribe(self, source: Union[str, List[str]], + num_speakers: int, + translate: bool, + language: str, + *args: Any, **kwargs: Dict[str, Any]) -> Tuple[str, Union[str, dict]]: """ - Shortcut method for the Scraibe task. + Performs auto transcription on the given source. + + Args: + source (Union[str, List[str]]): The source to transcribe. This can be a string representing a single source, + or a list of strings representing multiple sources. + num_speakers (int): The number of speakers in the source. + translate (bool): Whether to translate the transcription. + language (str): The language of the source. + *args (Any): Additional positional arguments. + **kwargs (Dict[str, Any]): Additional keyword arguments. Returns: - tuple: Transcribed text (str), JSON output (dict) + Tuple[str, Union[str, dict]]: A tuple containing the transcribed text (str) and the JSON output (str or dict). """ _kwargs = { @@ -82,12 +106,23 @@ class GradioTranscriptionInterface: raise gr.Error("Please provide a valid audio file.") - def transcribe(self, source, translate, language,*args ,**kwargs): + def transcribe(self, source: Union[str, List[str]], + translate: bool, + language: str, + *args: Any, **kwargs: Dict[str, Any]) -> str: """ - Shortcut method for the Transcribe task. + Performs transcription on the given source. + + Args: + source (Union[str, List[str]]): The source to transcribe. + This can be a string representing a single source, or a list of strings representing multiple sources. + translate (bool): Whether to translate the transcription. + language (str): The language of the source. + *args (Any): Additional positional arguments. + **kwargs (Dict[str, Any]): Additional keyword arguments. Returns: - str: Transcribed text. + str: The transcribed text. """ _kwargs = { @@ -118,13 +153,24 @@ class GradioTranscriptionInterface: else: raise gr.Error("Please provide a valid audio file.") - def diarisation(self, source, num_speakers, *args ,**kwargs): + def diarisation(self, source: Union[str, List[str]], + num_speakers: int, + *args: Any, **kwargs: Dict[str, Any]) -> str: """ - Shortcut method for the Diarisation task. + Performs diarisation on the given source. + + Args: + source (Union[str, List[str]]): The source to perform diarisation on. + This can be a string representing a single source, + or a list of strings representing multiple sources. + num_speakers (int): The number of speakers in the source. + *args (Any): Additional positional arguments. + **kwargs (Dict[str, Any]): Additional keyword arguments. Returns: - str: JSON output of diarisation result. + str: The JSON output of the diarisation result. """ + _kwargs = { "num_speakers": num_speakers if num_speakers != 0 else None, @@ -160,16 +206,16 @@ class GradioTranscriptionInterface: else: gr.Error("Please provide a valid audio file.") - def get_task_from_str(self, task): + def get_task_from_str(self, task: str) -> callable: + """ + Returns the corresponding task function based on the given task string. + + Args: + task (str): The task string. This can be one of the following: 'Auto Transcribe', 'Transcribe', 'Diarisation'. + + Returns: + callable: The corresponding task function. """ - Returns the coresponing task function based on the task string. - - params: - task (str): Task string. Can be one of the following: - - 'Auto Transcribe' - - 'Transcribe' - - 'Diarisation' - """ if task == 'Auto Transcribe': return self.autotranscribe diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py index 816bc4c..593db98 100644 --- a/scraibe/app/utils.py +++ b/scraibe/app/utils.py @@ -1,22 +1,51 @@ +""" +utils.py + +This module contains two classes, ConfigLoader and AppConfig, which are used to manage application-specific configuration settings. + +The ConfigLoader class provides methods for loading a configuration file, applying overrides, and restoring default values for specified keys. It also includes methods for recursively updating nested keys and getting the default configuration. + +The AppConfig class extends ConfigLoader and provides additional methods for setting global variables, launch options, and layout options from the configuration. It also includes methods for checking and setting file paths, and getting layout options. + +Classes: + ConfigLoader: Manages application-specific configuration settings. + AppConfig: Extends ConfigLoader to provide additional methods for managing application-specific configuration settings. +""" import os import warnings import yaml +from typing import Any, Dict, Optional import scraibe.app.global_var as gv CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) class ConfigLoader: - def __init__(self, config): - - self.config = config - - def restore_defaults_for_keys(self, *args): - """ - Restores specified keys to their default values, including nested keys. + """A class that extends ConfigLoader to manage application-specific configuration settings. + + This class provides methods for setting global variables, launch options, and layout options from the configuration. + + Attributes: + config (Dict[str, Any]): The current configuration settings. + launch (Dict[str, Any]): The launch configuration settings. + model (Dict[str, Any]): The model configuration settings. + advanced (Dict[str, Any]): The advanced configuration settings. + queue (Dict[str, Any]): The queue configuration settings. + layout (Dict[str, Any]): The layout configuration settings. + """ + def __init__(self, config: Dict[str, Any]): + """Initializes a new instance of the ConfigLoader class. Args: - keys (list): A list of keys or paths to keys (for nested dictionaries) to restore to default values. + config (dict): The configuration dictionary. + """ + self.config = config + + def restore_defaults_for_keys(self, *args: str): + """Restores specified keys to their default values, including nested keys. + + Args: + *args (str): A list of keys or paths to keys (for nested dictionaries) to restore to default values. Each key or path should be a list of keys leading to the desired key. """ default_config = self.get_default_config() @@ -27,16 +56,15 @@ class ConfigLoader: @classmethod - def load_config(cls, yaml_path = None, **kwargs): - """ - Load the configuration file and apply overrides. + def load_config(cls, yaml_path: Optional[str] = None, **kwargs: Any) -> 'ConfigLoader': + """Load the configuration file and apply overrides. Args: - yaml_path (str): Path to the YAML file containing overrides. + yaml_path (str, optional): Path to the YAML file containing overrides. **kwargs: Additional overrides as keyword arguments. Returns: - Config: A Config object with the loaded configuration. + ConfigLoader: A ConfigLoader object with the loaded configuration. """ # Load the original configuration @@ -54,8 +82,14 @@ class ConfigLoader: return cls(config) @staticmethod - def apply_overrides(orig_dict, override_dict, specific=None): - """ Recursively apply overrides to the configuration, only for specific keys. """ + def apply_overrides(orig_dict: Dict[str, Any], override_dict: Dict[str, Any], specific: Optional[str] = None): + """Recursively apply overrides to the configuration, only for specific keys. + + Args: + orig_dict (Dict[str, Any]): The original dictionary. + override_dict (Dict[str, Any]): The override dictionary. + specific (str, optional): The specific key to override. + """ for key, value in override_dict.items(): if isinstance(value, dict): @@ -80,7 +114,16 @@ class ConfigLoader: @staticmethod def update_nested_key(d, key, value): - """ Recursively search and update the key in nested dictionary. """ + """Recursively search and update the key in nested dictionary. + + Args: + d (Dict[str, Any]): The dictionary. + key (str): The key to update. + value (Any): The new value. + + Returns: + bool: True if the key was found and updated, False otherwise. + """ if key in d: d[key] = value @@ -92,16 +135,35 @@ class ConfigLoader: @staticmethod def get_default_config(): - """ Return the default configuration. """ + """Return the default configuration. + + Returns: + Dict[str, Any]: The default configuration. + """ with open(gv.DEFAULT_APP_CONIFG_PATH , 'r') as file: config = yaml.safe_load(file) return config class AppConfig(ConfigLoader): - - def __init__(self, config): - + """A class that extends ConfigLoader to manage application-specific configuration settings. + + This class provides methods for setting global variables, launch options, and layout options from the configuration. + + Attributes: + config (dict): The current configuration settings. + launch (dict): The launch configuration settings. + model (dict): The model configuration settings. + advanced (dict): The advanced configuration settings. + queue (dict): The queue configuration settings. + layout (dict): The layout configuration settings. + """ + def __init__(self, config : Dict[str, Any]): + """Initializes a new instance of the AppConfig class. + + Args: + config (dict): The configuration dictionary. + """ self.config = config self.set_global_vars_from_config() @@ -114,23 +176,28 @@ class AppConfig(ConfigLoader): self.queue = self.config.get("queue") self.layout = self.config.get("layout") - def set_global_vars_from_config(self): - """ - Sets the global variables from a configuration dictionary. - + def set_global_vars_from_config(self) -> None: + """Sets the global variables from a configuration dictionary. + Args: config (dict): A dictionary containing the parameters for the model. Modify the default parameters in the config.yml file. - + Returns: None - """ gv.MODEL_PARAMS = self.config.get('model') gv.TIMEOUT = self.config.get("advanced").get('timeout') - def set_launch_options(self): - + def set_launch_options(self) -> None: + """Sets the launch options from a configuration dictionary. + + Args: + None + + Returns: + None + """ launch_options = self.config.get("launch") if launch_options.get('auth').pop('auth_enabled'): @@ -139,13 +206,28 @@ class AppConfig(ConfigLoader): else: self.config['launch']['auth'] = None - def set_layout_options(self): + def set_layout_options(self) -> None: + """Sets the layout options from a configuration dictionary. + + Args: + None + + Returns: + None + """ self.config['layout']['header'] = self.check_and_set_path(self.config['layout'], 'header') self.config['layout']['footer'] = self.check_and_set_path(self.config['layout'], 'footer') self.config['layout']['logo'] = self.check_and_set_path(self.config['layout'], 'logo') - def get_layout(self): - + def get_layout(self) -> Dict[str, str]: + """Gets the layout options from a configuration dictionary. + + Args: + None + + Returns: + dict: A dictionary containing the header and footer layout options. + """ if not os.path.exists(self.config['layout']['header']) and \ self.config['layout']['header'] == "scraibe/app/header.html": @@ -189,10 +271,16 @@ class AppConfig(ConfigLoader): 'footer' : footer} @staticmethod - def check_and_set_path(config_item, key): - """ - Check if the file exists at the given path. If not, try with CURRENT_PATH. + def check_and_set_path(config_item: dict, key: str) -> Optional[str]: + """Check if the file exists at the given path. If not, try with CURRENT_PATH. Raise FileNotFoundError if the file still doesn't exist. + + Args: + config_item (dict): The configuration item. + key (str): The key to check in the configuration item. + + Returns: + str: The path to the file if it exists, None otherwise. """ _current_path = os.path.dirname(os.path.realpath(__file__)) # Define your CURRENT_PATH @@ -207,10 +295,4 @@ class AppConfig(ConfigLoader): else: config_item[key] = new_path - return config_item[key] - - - - - - \ No newline at end of file + return config_item[key] \ No newline at end of file From 9c0766fc41a3ede97fcc580a817db16ac7779f84 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 9 Feb 2024 11:35:38 +0100 Subject: [PATCH 118/331] updated dependencies now scraibe works with torch 2 --- scraibe/diarisation.py | 16 ++++++++++++---- scraibe/misc.py | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index f90bcdb..1a33817 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -34,6 +34,8 @@ from typing import TypeVar, Union from pyannote.audio import Pipeline from pyannote.audio.pipelines.speaker_diarization import SpeakerDiarization from torch import Tensor +from torch import device as torch_device +from torch.cuda import is_available, current_device from .misc import PYANNOTE_DEFAULT_PATH, PYANNOTE_DEFAULT_CONFIG Annotation = TypeVar('Annotation') @@ -184,6 +186,7 @@ class Diariser: cache_token: bool = True, cache_dir: Union[Path, str] = PYANNOTE_DEFAULT_PATH, hparams_file: Union[str, Path] = None, + device: str = None, *args, **kwargs ) -> Pipeline: @@ -198,6 +201,7 @@ class Diariser: cache_token: Whether to cache the token locally for future use. cache_dir: Directory for caching models. hparams_file: Path to a YAML file containing hyperparameters. + device: Device to load the model on. args: Additional arguments only to avoid errors. kwargs: Additional keyword arguments only to avoid errors. @@ -205,20 +209,24 @@ class Diariser: Pipeline: A pyannote.audio Pipeline object, encapsulating the loaded model. """ + if cache_token and use_auth_token is not None: cls._save_token(use_auth_token) if not os.path.exists(model) and use_auth_token is None: use_auth_token = cls._get_token() - model = 'pyannote/speaker-diarization' - elif not os.path.exists(model) and use_auth_token is not None: - model = 'pyannote/speaker-diarization' - + _model = Pipeline.from_pretrained(model, use_auth_token = use_auth_token, cache_dir = cache_dir, hparams_file = hparams_file,) + # try to move the model to the device + if device is None: + device = "cuda" if is_available() else "cpu" + + _model = _model.to(torch_device(device)) # torch_device is renamed from torch.device to avoid name conflict + if _model is None: raise ValueError('Unable to load model either from local cache' \ 'or from huggingface.co models. Please check your token' \ diff --git a/scraibe/misc.py b/scraibe/misc.py index b1afeea..c912478 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -12,7 +12,9 @@ if CACHE_DIR != PYANNOTE_CACHE_DIR: WHISPER_DEFAULT_PATH = os.path.join(CACHE_DIR, "whisper") PYANNOTE_DEFAULT_PATH = os.path.join(CACHE_DIR, "pyannote") -PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") +PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ + if os.path.exists(os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml")) \ + else 'pyannote/speaker-diarization-3.1' def config_diarization_yaml(file_path: str, path_to_segmentation: str = None) -> None: """Configure diarization pipeline from a YAML file. From df79a78a47bc1aabf3f92f9df9703f9b4261d212 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Fri, 9 Feb 2024 12:17:43 +0100 Subject: [PATCH 119/331] updated dependency list --- requirements.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index aed43e8..8cf1782 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,12 @@ -openai-whisper==20230314 +torch~=2.2.0 + +openai-whisper~=20231117 numpy~=1.23.5 -pyannote.audio~=2.1.1 -pyannote.core~=4.5 -pyannote.database~=4.1.3 +pyannote.audio~=3.1.1 +pyannote.core~=5.0.0 +pyannote.database~=5.0.1 pyannote.metrics~=3.2.1 -pyannote.pipeline~=2.3 +pyannote.pipeline~=3.0.1 setuptools~=65.6.3 setuptools-rust~=1.5.2 From c70e66ead07b45e75da7dd87630eefe3502a5717 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 11:43:34 +0100 Subject: [PATCH 120/331] removed App here --- scraibe/app/__init__.py | 7 - scraibe/app/app.py | 100 ------------ scraibe/app/app_starter.py | 70 --------- scraibe/app/config.yml | 47 ------ scraibe/app/global_var.py | 37 ----- scraibe/app/header.html | 66 -------- scraibe/app/interactions.py | 153 ------------------ scraibe/app/interface.py | 146 ------------------ scraibe/app/logo.svg | 37 ----- scraibe/app/multi.py | 151 ------------------ scraibe/app/stg.py | 229 --------------------------- scraibe/app/utils.py | 298 ------------------------------------ 12 files changed, 1341 deletions(-) delete mode 100644 scraibe/app/__init__.py delete mode 100644 scraibe/app/app.py delete mode 100644 scraibe/app/app_starter.py delete mode 100644 scraibe/app/config.yml delete mode 100644 scraibe/app/global_var.py delete mode 100644 scraibe/app/header.html delete mode 100644 scraibe/app/interactions.py delete mode 100644 scraibe/app/interface.py delete mode 100644 scraibe/app/logo.svg delete mode 100644 scraibe/app/multi.py delete mode 100644 scraibe/app/stg.py delete mode 100644 scraibe/app/utils.py diff --git a/scraibe/app/__init__.py b/scraibe/app/__init__.py deleted file mode 100644 index bdf5464..0000000 --- a/scraibe/app/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .multi import * -from .interface import * -from .stg import * -from .interactions import * -from .global_var import * -from .utils import * -from .app import * \ No newline at end of file diff --git a/scraibe/app/app.py b/scraibe/app/app.py deleted file mode 100644 index 76b189a..0000000 --- a/scraibe/app/app.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Gradio App ----------- - -This module provides an interface to transcribe audio files using the -Scraibe model. Users can either upload an audio file or record their speech -live for transcription. The application supports multiple languages and provides -options to specify the number of speakers and the language of the audio. It also -enables efficient management of resources by loading and unloading AI models -based on usage. - -The configuration is managed via a 'config.yml' file, which allows customization -of various aspects of the application, including the Gradio interface, queue -management, and model parameters. - -Configuration Sections in 'config.yml': -- launch: Settings for launching the interface, such as server port, authentication, SSL configuration. -- queue: Configuration for managing request handling and concurrency. -- layout: Customization options for the interface layout, like headers, footers, and logos. -- model: Specifications for different AI models used in transcription. -- advanced: Advanced settings, including session timeout duration. - -Note: - The .queue function of the Gradio interface is currently experiencing issues - and might not work as expected. - -Usage: - Run this script to start the Gradio web interface for audio transcription. -""" - - - -#### -# Gradio Interface -#### - -from threading import Thread - -import scraibe.app.global_var as gv -from .interface import gradio_Interface -from .multi import * -from .utils import * - - -def app(config : str = None, **kwargs): - """ - Launches the Gradio interface for audio transcription. - - Initializes the Gradio web interface with settings from a YAML configuration file - and/or keyword arguments. The function manages AI models, handling their loading - into RAM and unloading after a session or specified timeout. - - The `kwargs` are used to override or supplement values from the `config.yml` file. - They should follow the structure of `config.yml`, which includes sections like - 'launch', 'queue', 'layout', 'model', and 'advanced'. - - Args: - config (str): Path to the YAML configuration file. Default settings are used - if not provided. - **kwargs: Keyword arguments corresponding to the configuration sections. Each - argument should be a dictionary reflecting the structure of its - respective section in `config.yml`. - - Returns: - None - """ - - # Load and override configuration from the YAML file with kwargs - - config = AppConfig.load_config(config, **kwargs) - - - gv.MODEL_PROCESS = start_model_worker(gv.MODEL_PARAMS, - gv.REQUEST_QUEUE, - gv.LAST_ACTIVE_TIME, - gv.RESPONSE_QUEUE, - gv.LOADED_EVENT, - gv.RUNNING_EVENT) - - # Set the timer thread to manage model loading and unloading - timer = Thread(target=timer_thread, args=(gv.REQUEST_QUEUE, - gv.LAST_ACTIVE_TIME, - gv.LOADED_EVENT, - gv.RUNNING_EVENT, - gv.TIMEOUT), daemon=True) - - # Set the layout for the Gradio interface - layout = config.get_layout() - - # start the timer thread - timer.start() - - print("Starting Gradio Web Interface") - - # Launch the Gradio interface - gradio_Interface(layout).queue(**config.queue).launch(**config.launch) - - # Wait for the timer thread to finish - timer.join() - gv.MODEL_PROCESS.join() \ No newline at end of file diff --git a/scraibe/app/app_starter.py b/scraibe/app/app_starter.py deleted file mode 100644 index b1597f0..0000000 --- a/scraibe/app/app_starter.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Starts the Gradio interface for audio transcription with optional configuration. - -This script, app_starter.py, initializes and runs a Gradio interface for audio -transcription tasks. It allows users to provide a configuration file for custom -settings. If no configuration file is specified, default settings are applied. -The script is designed to support multiprocessing for improved performance. - -Attributes: - args (argparse.Namespace): Parsed command line arguments. - -Example: - To run the script with custom server configuration and keyword arguments: - $ python app_starter.py --server-config path/to/config.yml --server-kwargs key1=val1 key2=val2 -""" - -import multiprocessing -from argparse import ArgumentParser, Action - -class ParseKwargs(Action): - """Custom action for argparse to parse keyword arguments for Gradio app configuration. - - This action parses a series of keyword arguments and converts them into a - dictionary, which is then used to configure the Gradio application. It - supports dynamic types by attempting to evaluate the argument values. - - Attributes: - dest (str): The name of the attribute to be added to the object returned by parse_args(). - """ - def __call__(self, parser, namespace, values, option_string=None): - """Parses keyword arguments and updates the namespace with these arguments as a dictionary. - - For each value provided, this method splits the string on the '=' character - to separate keys and values, attempting to evaluate the values for Python - literals. If evaluation fails, the raw string is used as the value. - - Args: - parser (ArgumentParser): The ArgumentParser object that called this method. - namespace (Namespace): An argparse.Namespace object that will be returned by parse_args(). - values (list of str): List of strings, each representing a key-value pair in 'key=value' format. - option_string (Optional[str]): The option string that was used to invoke this action. - - Raises: - ValueError: If any string in values does not contain the '=' character, indicating an invalid format. - """ - setattr(namespace, self.dest, dict()) - for value in values: - key, value = value.split('=') - try: - value = eval(value) - except: - pass - getattr(namespace, self.dest)[key] = value - -parser = ArgumentParser() - -parser.add_argument("--server-config", type=str, default= None, - help="Path to the configy.yml file.") - -parser.add_argument('--server-kwargs', nargs='*', action=ParseKwargs, default={}, - help='Keyword arguments for the Gradio app.') - -args = parser.parse_args() - -if __name__ == '__main__': - - multiprocessing.set_start_method('spawn') - - from scraibe.app.app import app - - app(config = args.server_config, **args.server_kwargs) \ No newline at end of file diff --git a/scraibe/app/config.yml b/scraibe/app/config.yml deleted file mode 100644 index 8b25af5..0000000 --- a/scraibe/app/config.yml +++ /dev/null @@ -1,47 +0,0 @@ -launch: - # The following are the default values for the launch configuration - # for more informations look at https://www.gradio.app/docs/interface - server_port: 7860 - server_name: 0.0.0.0 - inline: false - inbrowser: false - share : false - debug : false - max_threads: 40 - quiet: false - auth: - auth_enabled: false - auth_username: admin - auth_password: admin - auth_message: null - prevent_thread_lock : false - show_error : false - show_tips : false - favicon_path : null - ssl_keyfile : null - ssl_certfile : null - ssl_keyfile_password : null - ssl_verify : false - show_api : false - allowed_paths : null - blocked_paths : null - root_path : '' - app_kwargs : null - -queue: - # The following are the default values for the queue configuration - # for more informations look at hhttps://www.gradio.app/docs/interface - concurrency_count : 1 - status_update_rate : 'auto' - api_open : null - max_size : null - -layout: - header: scraibe/app/header.html - footer: null - logo: scraibe/app/logo.svg -model: - whisper_model : null - dia_model: null -advanced: - timeout: 300 #seconds e.g. 5 minutes diff --git a/scraibe/app/global_var.py b/scraibe/app/global_var.py deleted file mode 100644 index b1c1c80..0000000 --- a/scraibe/app/global_var.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -global_var.py - -This module stores global variables for the app. - -Global variables: - REQUEST_QUEUE (multiprocessing.Queue): A queue to store audio file paths as strings. - RESPONSE_QUEUE (multiprocessing.Queue): A queue to store transcriptions as strings. - LAST_ACTIVE_TIME (multiprocessing.Value): A value to store the time of the last activity. - LOADED_EVENT (multiprocessing.Event): An event to indicate when the model is loaded. - RUNNING_EVENT (multiprocessing.Event): An event to indicate when the model is running. - MODEL_PARAMS (Optional[dict]): A dictionary to store the model parameters. - MODEL_PROCESS (Optional[multiprocessing.Process]): A process to handle the model globally. - LAST_USED (float): A float to track the time of the last user activity. - TIMEOUT (Optional[int]): An integer to store the timeout in seconds. - DEFAULT_APP_CONIFG_PATH (str): A string to store the default path to the app configuration file. -""" - -import multiprocessing -import os -import time -from typing import Optional - -REQUEST_QUEUE: multiprocessing.Queue = multiprocessing.Queue() # audio file path as string -RESPONSE_QUEUE: multiprocessing.Queue = multiprocessing.Queue() # transcription as string -LAST_ACTIVE_TIME: multiprocessing.Value = multiprocessing.Value('d', time.time()) # time of last activity -LOADED_EVENT: multiprocessing.Event = multiprocessing.Event() # model loaded event -RUNNING_EVENT: multiprocessing.Event = multiprocessing.Event() # model running event - -MODEL_PARAMS: Optional[dict] = None # model parameters -MODEL_PROCESS: Optional[multiprocessing.Process] = None # model process to handle globally - -# Global variable to track user activity -LAST_USED: float = time.time() -TIMEOUT: Optional[int] = None # seconds - -DEFAULT_APP_CONIFG_PATH: str = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config.yml") \ No newline at end of file diff --git a/scraibe/app/header.html b/scraibe/app/header.html deleted file mode 100644 index 4b12136..0000000 --- a/scraibe/app/header.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - -
-

ScrAIbe

-
- - - -
-
-
-

- Upload, record, or provide a video with audio for transcription. Our toolkit is designed to transcribe content from multiple languages accurately. The integrated speaker diarisation feature identifies different speakers, ensuring a smooth transcription experience. For optimal results, indicate the number of speakers and the original language of the content. -

-

What would you like to do next?

-
diff --git a/scraibe/app/interactions.py b/scraibe/app/interactions.py deleted file mode 100644 index 9206e75..0000000 --- a/scraibe/app/interactions.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -This file contains ervery function that will be called when the user interacts with the -UI like pressing a button or uploading a file. -""" - -import gradio as gr -import scraibe.app.global_var as gv -from scraibe import Transcript -from .multi import start_model_worker - -def select_task(choice): - # tell the app that it is still in use - if choice == 'Auto Transcribe': - - return (gr.update(visible = True), - gr.update(visible = True), - gr.update(visible = True)) - - - elif choice == 'Transcribe': - - return (gr.update(visible = False), - gr.update(visible = True), - gr.update(visible = True)) - - - elif choice == 'Diarisation': - - return (gr.update(visible = True), - gr.update(visible = False), - gr.update(visible = False)) - -def select_origin(choice): - - # tell the app that it is still in use - if choice == "Upload Audio": - - return (gr.update(visible = True), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None)) - - elif choice == "Record Audio": - - return (gr.update(visible = False, value = None), - gr.update(visible = True), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None)) - - elif choice == "Upload Video": - - return (gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = True), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None)) - - elif choice == "Record Video": - - return (gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = True), - gr.update(visible = False, value = None)) - - elif choice == "File or Files": - - return (gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = False, value = None), - gr.update(visible = True)) - -def run_scraibe(task, - num_speakers, - translate, - language, - audio1, - audio2, - video1, - video2, - file_in, - progress = gr.Progress(track_tqdm=False)): - - # get *args which are not None - if gv.MODEL_PROCESS is None or not gv.MODEL_PROCESS.is_alive(): - #progress(0.0, desc='Loading model...') - gv.MODEL_PROCESS = start_model_worker(gv.MODEL_PARAMS, - gv.REQUEST_QUEUE, - gv.LAST_ACTIVE_TIME, - gv.RESPONSE_QUEUE, - gv.LOADED_EVENT, - gv.RUNNING_EVENT) - - # progress(0.1, desc='Starting task...') - source = audio1 or audio2 or video1 or video2 or file_in - - if isinstance(source, list): - source = [s.name for s in source] - if len(source) == 1: - source = source[0] - - config = dict(source = source, - task = task, - num_speakers = num_speakers, - translate = translate, - language = language) - - gv.REQUEST_QUEUE.put(config) - - if task == 'Auto Transcribe': - - out_str , out_json = gv.RESPONSE_QUEUE.get() - - if isinstance(source, str): - return (gr.update(value = out_str, visible = True), - gr.update(value = out_json, visible = True), - gr.update(visible = True), - gr.update(visible = True)) - else: - return (gr.update(value = out_str, visible = True), - gr.update(value = out_json, visible = True), - gr.update(visible = False), - gr.update(visible = False)) - - elif task == 'Transcribe': - - out = gv.RESPONSE_QUEUE.get() - - return (gr.update(value = out, visible = True), - gr.update(value = None, visible = False), - gr.update(visible = False), - gr.update(visible = False)) - - elif task == 'Diarisation': - - out = gv.RESPONSE_QUEUE.get() - - return (gr.update(value = None, visible = False), - gr.update(value = out, visible = True), - gr.update(visible = False), - gr.update(visible = False)) - -def annotate_output(annoation : str, out_json : dict): - # get *args which are not None - - trans = Transcript.from_json(out_json) - trans = trans.annotate(*annoation.split(",")) - - return gr.update(value = str(trans)),gr.update(value = trans.get_json()) - diff --git a/scraibe/app/interface.py b/scraibe/app/interface.py deleted file mode 100644 index bab29a9..0000000 --- a/scraibe/app/interface.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -This module contains the gradio Interface which is used to interact with the user. - -The interface is themed with a soft color scheme, with primary colors of green and orange, and a neutral color of gray. - -A list of languages is also defined in this module, which may be used elsewhere in the application. - -Classes: - Soft: A class from the gradio library used to theme the interface. - -Variables: - theme (gr.themes.Soft): The theme for the gradio interface. - LANGUAGES (list of str): A list of languages supported by the application. -""" - -import gradio as gr - -from .interactions import * -from .stg import * - -theme = gr.themes.Soft( - primary_hue="green", - secondary_hue='orange', - neutral_hue="gray", -) - - -LANGUAGES = [ - "Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", - "Bosnian", "Bulgarian", "Catalan", "Chinese", "Croatian", - "Czech", "Danish", "Dutch", "English", "Estonian", - "Finnish", "French", "Galician", "German", "Greek", - "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", - "Italian", "Japanese", "Kannada", "Kazakh", "Korean", - "Latvian", "Lithuanian", "Macedonian", "Malay", "Marathi", - "Maori", "Nepali", "Norwegian", "Persian", "Polish", - "Portuguese", "Romanian", "Russian", "Serbian", "Slovak", - "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", - "Tamil", "Thai", "Turkish", "Ukrainian", "Urdu", - "Vietnamese", "Welsh" -] - - - - -def gradio_Interface(layout = None,): - """ - Creates a gradio interface for audio transcription. - - The interface includes options for the user to select the task, number of speakers, translation, language, and input type. - It also provides options for the user to upload or record audio/video, or upload files. - The output of the transcription is displayed in a textbox, and the JSON output in a JSON viewer. - The user can also annotate the output by naming the speakers. - - Args: - layout (dict, optional): A dictionary containing layout information. Defaults to None. - - Returns: - gr.Blocks: A gradio Blocks object representing the interface. - """ - with gr.Blocks(theme=theme,title='ScrAIbe: Automatic Audio Transcription') as demo: - - # Define components - - - if layout.get('header') is not None: - gr.HTML(layout.get('header'), visible= True, show_label=False) - - with gr.Row(): - - with gr.Column(): - - task = gr.Radio(["Auto Transcribe", "Transcribe", "Diarisation"], label="Task", - value= 'Auto Transcribe') - - num_speakers = gr.Number(value=0, label= "Number of speakers (optional)", - info = "Number of speakers in the audio file. If you don't know,\ - leave it at 0.", visible= True) - - translate = gr.Checkbox(label="Translation", choices=[True, False], value = False, - info="Select 'Yes' to have the output translated into English.", - visible= True) - - language = gr.Dropdown(LANGUAGES, - label="Language (optional)", value = "None", - info="Language of the audio file. If you don't know,\ - leave it at None.", visible= True) - - input = gr.Radio(["Upload Audio", "Record Audio", "Upload Video","Record Video" - ,"File or Files"], label="Input Type", value="Upload Audio") - - audio1 = gr.Audio(source="upload", type="filepath", label="Upload Audio", - interactive= True, visible= True) - audio2 = gr.Audio(source="microphone", label="Record Audio", type="filepath", - interactive= True, visible= False) - video1 = gr.Video(source="upload", type="filepath", label="Upload Video", - interactive= True, visible= False) - video2 = gr.Video(source="webcam", label="Record Video", type="filepath",include_audio= True, - interactive= True, visible= False) - file_in = gr.Files(label="Upload File or Files", interactive= True, visible= False) - - submit = gr.Button() - - with gr.Column(): - - out_txt = gr.Textbox(label="Output", - visible= True, show_copy_button=True) - - out_json = gr.JSON(label="JSON Output", - visible= False, show_copy_button=True) - - annoation = gr.Textbox(label="Name your speaker's", - info= "Please provide a list of the speakers arranged \ - in the order in which they appear in the input. Use comma ',' \ - as a seperator. Be aware that the first name is given \ - to SPEAKER_00 the second to SPEAKER_01 and so on.", - visible= False, interactive= True) - - annotate = gr.Button(value="Annotate", visible= False, interactive= True) - - if layout.get('footer') is not None: - gr.HTML(layout.get('footer'), visible= True, show_label=False) - - # Define usage of components - input.change(fn=select_origin, inputs=[input], - outputs=[audio1, audio2, video1, video2, file_in]) - - task.change(fn=select_task, inputs=[task], - outputs=[num_speakers, translate, language]) - - translate.change(fn= lambda x : gr.update(value = x), - inputs=[translate], outputs=[translate]) - num_speakers.change(fn= lambda x : gr.update(value = x), - inputs=[num_speakers], outputs=[num_speakers]) - language.change(fn= lambda x : gr.update(value = x), - inputs=[language], outputs=[language]) - - submit.click(fn = run_scraibe, - inputs=[task, num_speakers, translate, language, audio1, - audio2, video1, video2, file_in], - outputs=[out_txt, out_json, annoation, annotate]) - - annotate.click(fn = annotate_output, inputs=[annoation, out_json], - outputs=[out_txt, out_json]) - - return demo \ No newline at end of file diff --git a/scraibe/app/logo.svg b/scraibe/app/logo.svg deleted file mode 100644 index 54d12d7..0000000 --- a/scraibe/app/logo.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scraibe/app/multi.py b/scraibe/app/multi.py deleted file mode 100644 index fe636a0..0000000 --- a/scraibe/app/multi.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -This module contains functions for managing and optimizing the resource usage of the application. - -The functions in this module monitor the application's usage and make adjustments to improve efficiency. -This includes managing the loading and unloading of the model based on the application's activity. -This dynamic management of resources helps to ensure that the application uses only the resources it needs, -improving overall performance and reducing unnecessary resource consumption. - -Functions: - clear_queue(queue): Clears all items from the queue. - model_worker(model_params, request_queue, last_active_time, - response_queue, loaded_event, running_event, *args, **kwargs): Manages the model worker process. - -Modules: - time: Provides various time-related functions. - gc: Provides an interface to the garbage collector. - multiprocessing: Provides support for parallel execution of code. - torch: Provides tensor computation and deep learning functionality. - gradio: Provides a simple way to create interactive UIs for Python functions. - scraibe.autotranscript: Provides automatic transcription functionality. - .stg: Contains the GradioTranscriptionInterface class. -""" - - -import time -import gc -from typing import Union, Any -import multiprocessing -import torch - -from gradio import Warning -from scraibe.autotranscript import Scraibe -from .stg import GradioTranscriptionInterface - -def clear_queue(queue): - while not queue.empty(): - try: - queue.get_nowait() - except queue.Empty: - continue - -def model_worker(model_params : Union[Scraibe, dict], - request_queue: multiprocessing.Queue, - last_active_time: multiprocessing.Value, - response_queue: multiprocessing.Queue, - loaded_event: multiprocessing.Event, - running_event: multiprocessing.Event, - *args: Any, **kwargs: Any) -> None: - """ - Manages the model worker process. - - The model worker process is responsible for running the model and returning the results. - - Args: - model_params (Union[Scraibe, dict]): The parameters for the Scraibe model. - request_queue (multiprocessing.Queue): The queue for incoming requests. - last_active_time (multiprocessing.Value): The last time the model was active. - response_queue (multiprocessing.Queue): The queue for outgoing responses. - loaded_event (multiprocessing.Event): An event that signals when the model is loaded. - running_event (multiprocessing.Event): An event that signals when the model is running. - *args: Additional arguments. - **kwargs: Additional keyword arguments. - """ - - loaded_event.set() - - if model_params is None: - _model = Scraibe() - elif type(model_params) is Scraibe: - _model = model_params - elif type(model_params) is dict: - _model = Scraibe(**model_params) - else: - raise TypeError("model must be of type Scraibe, or dict") - - model = GradioTranscriptionInterface(_model) - - while True: - - req = request_queue.get() - - if req == "STOP": - - break - elif type(req) is dict: - runner = model.get_task_from_str(req.pop("task")) - running_event.set() - transcription = runner(**req) - running_event.clear() - response_queue.put(transcription) - last_active_time.value = time.time() - else: - raise TypeError("request must be of type dict") - - del model - torch.cuda.empty_cache() - gc.collect() - clear_queue(request_queue) - clear_queue(response_queue) - loaded_event.clear() - -def start_model_worker(model_params: Union[Scraibe, dict], - request_queue: multiprocessing.Queue, - last_active_time: multiprocessing.Value, - response_queue: multiprocessing.Queue, - loaded_event: multiprocessing.Event, - running_event: multiprocessing.Event, - *args: Any, **kwargs: Any) -> multiprocessing.Process: - """ - Starts the model worker process. - - Args: - model_params (Union[Scraibe, dict]): The parameters for the Scraibe model. - request_queue (multiprocessing.Queue): The queue for incoming requests. - last_active_time (multiprocessing.Value): The last time the model was active. - response_queue (multiprocessing.Queue): The queue for outgoing responses. - loaded_event (multiprocessing.Event): An event that signals when the model is loaded. - running_event (multiprocessing.Event): An event that signals when the model is running. - *args: Additional arguments. - **kwargs: Additional keyword arguments. - - Returns: - multiprocessing.Process: The model worker process. - """ - context = multiprocessing.get_context('spawn') - model_process = context.Process(target=model_worker, args=(model_params, request_queue, last_active_time, response_queue,loaded_event, running_event, *args), kwargs=kwargs) - model_process.start() - return model_process - -def timer_thread(request_queue: multiprocessing.Queue, - last_active_time: multiprocessing.Value, - loaded_event: multiprocessing.Event, - running_event: multiprocessing.Event, - timeout: int) -> None: - """ - Monitors the model worker process and stops it after a period of inactivity. - - Args: - request_queue (multiprocessing.Queue): The queue for incoming requests. - last_active_time (multiprocessing.Value): The last time the model was active. - loaded_event (multiprocessing.Event): An event that signals when the model is loaded. - running_event (multiprocessing.Event): An event that signals when the model is running. - timeout (int): The period of inactivity after which the model worker process is stopped. - """ - while True: - time.sleep(timeout) - - if time.time() - last_active_time.value > timeout and loaded_event.is_set() and not running_event.is_set(): - print(f"No activity for the last {timeout} seconds. Stopping the model worker.", flush=True) - request_queue.put("STOP") - Warning("Model worker stopped due to inactivity.") \ No newline at end of file diff --git a/scraibe/app/stg.py b/scraibe/app/stg.py deleted file mode 100644 index 4ce31a1..0000000 --- a/scraibe/app/stg.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -stg - Scraibe to Gradio Interface - -This module provides an interface between the Scraibe transcription system and the Gradio user interface. -It defines a class, GradioTranscriptionInterface, that wraps the Scraibe model and provides methods for performing transcription tasks through the Gradio UI. - -Modules: - json: Used for encoding and decoding JSON data. - gradio as gr: Used for creating the Gradio UI. - tqdm: Used for displaying progress bars. - scraibe.app.global_var as gv: Contains global variables for the Scraibe app. -""" -import json -import gradio as gr -from tqdm import tqdm -from typing import Any, Dict, Union, Tuple, List - - - - -class GradioTranscriptionInterface: - """ - A class that provides an interface between the Gradio UI and the Scraibe transcription system. - - This class wraps the Scraibe model and provides methods for performing transcription tasks through the Gradio UI. - These tasks include auto transcription, transcription, and diarisation. - - Attributes: - model (Scraibe): The Scraibe model for performing transcription tasks. - """ - - def __init__(self, model) -> None: - """ - Initializes the GradioTranscriptionInterface with a Scraibe model. - - Args: - model (Scraibe): The Scraibe model for performing transcription tasks. - *args (Any): Additional positional arguments. - **kwargs (Dict[str, Any]): Additional keyword arguments. - """ - - self.model = model - - def autotranscribe(self, source: Union[str, List[str]], - num_speakers: int, - translate: bool, - language: str, - *args: Any, **kwargs: Dict[str, Any]) -> Tuple[str, Union[str, dict]]: - """ - Performs auto transcription on the given source. - - Args: - source (Union[str, List[str]]): The source to transcribe. This can be a string representing a single source, - or a list of strings representing multiple sources. - num_speakers (int): The number of speakers in the source. - translate (bool): Whether to translate the transcription. - language (str): The language of the source. - *args (Any): Additional positional arguments. - **kwargs (Dict[str, Any]): Additional keyword arguments. - - Returns: - Tuple[str, Union[str, dict]]: A tuple containing the transcribed text (str) and the JSON output (str or dict). - """ - - _kwargs = { - "num_speakers": num_speakers if num_speakers != 0 else None, - "language": language if language != "None" else None, - "task": 'translate' if translate else None - } - if isinstance(source, str): - try: - result = self.model.autotranscribe(source, **_kwargs) - except ValueError: - raise gr.Error("Couldn't detect any speech in the provided audio. \ - Please try again!") - - return str(result), result.get_json() - - elif isinstance(source, list): - source_names = [s.split("/")[-1] for s in source] - result = [] - for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): - try: - res = self.model.autotranscribe(s, **_kwargs) - except ValueError: - _name = s.split("/")[-1] - res = f"NO TRANSCRIPT FOUND FOR {_name}" - gr.Warning(f"Couldn't detect any speech in {_name} will skip this file.") - result.append(res) - - out = '' - out_dict = {} - for i, r in enumerate(result): - out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" - out += str(r) - out += "\n\n" - - if isinstance(r, str): - out_dict[source_names[i]] = r - else: - out_dict[source_names[i]] = r.get_dict() - - return out, json.dumps(out_dict, indent=4) - - else: - raise gr.Error("Please provide a valid audio file.") - - - def transcribe(self, source: Union[str, List[str]], - translate: bool, - language: str, - *args: Any, **kwargs: Dict[str, Any]) -> str: - """ - Performs transcription on the given source. - - Args: - source (Union[str, List[str]]): The source to transcribe. - This can be a string representing a single source, or a list of strings representing multiple sources. - translate (bool): Whether to translate the transcription. - language (str): The language of the source. - *args (Any): Additional positional arguments. - **kwargs (Dict[str, Any]): Additional keyword arguments. - - Returns: - str: The transcribed text. - """ - - _kwargs = { - "language": language if language != "None" else None, - "task": 'translate' if translate == "Yes" else None - } - - if isinstance(source, str): - result = self.model.transcribe(source, **_kwargs) - - return str(result) - - elif isinstance(source, list): - source_names = [s.split("/")[-1] for s in source] - result = [] - for s in tqdm(source, total=len(source),desc = "Transcribing audio files"): - res = self.model.transcribe(s, **_kwargs) - result.append(res) - - out = '' - for i, res in enumerate(result): - out += f"TRANSCRIPT FOR {source_names[i]}:\n\n" - out += str(res) - out += "\n\n" - - return out - - else: - raise gr.Error("Please provide a valid audio file.") - - def diarisation(self, source: Union[str, List[str]], - num_speakers: int, - *args: Any, **kwargs: Dict[str, Any]) -> str: - """ - Performs diarisation on the given source. - - Args: - source (Union[str, List[str]]): The source to perform diarisation on. - This can be a string representing a single source, - or a list of strings representing multiple sources. - num_speakers (int): The number of speakers in the source. - *args (Any): Additional positional arguments. - **kwargs (Dict[str, Any]): Additional keyword arguments. - - Returns: - str: The JSON output of the diarisation result. - """ - - - _kwargs = { - "num_speakers": num_speakers if num_speakers != 0 else None, - } - - if isinstance(source, str): - try: - result = self.model.diarization(source, **_kwargs) - except ValueError: - raise gr.Error("Couldn't detect any speech in the provided audio. \ - Please try again!") - - return json.dumps(result, indent=2) - elif isinstance(source, list): - source_names = [s.split("/")[-1] for s in source] - result = [] - for s in tqdm(source, total=len(source),desc = "Performing diarisation"): - try: - res = self.model.diarization(s, **_kwargs) - except ValueError: - - res = f"NO DIARISATION FOUND FOR {s}" - gr.Warning(f"Couldn't detect any speech in {s} will skip this file.") - result.append(res) - - out = {} - - for i, res in enumerate(result): - out[source_names[i]] = res - - return json.dumps(out, indent=4) - - else: - gr.Error("Please provide a valid audio file.") - - def get_task_from_str(self, task: str) -> callable: - """ - Returns the corresponding task function based on the given task string. - - Args: - task (str): The task string. This can be one of the following: 'Auto Transcribe', 'Transcribe', 'Diarisation'. - - Returns: - callable: The corresponding task function. - """ - - if task == 'Auto Transcribe': - return self.autotranscribe - elif task == 'Transcribe': - return self.transcribe - elif task == 'Diarisation': - return self.diarisation - else: - raise ValueError("Invalid task string.") - - diff --git a/scraibe/app/utils.py b/scraibe/app/utils.py deleted file mode 100644 index 593db98..0000000 --- a/scraibe/app/utils.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -utils.py - -This module contains two classes, ConfigLoader and AppConfig, which are used to manage application-specific configuration settings. - -The ConfigLoader class provides methods for loading a configuration file, applying overrides, and restoring default values for specified keys. It also includes methods for recursively updating nested keys and getting the default configuration. - -The AppConfig class extends ConfigLoader and provides additional methods for setting global variables, launch options, and layout options from the configuration. It also includes methods for checking and setting file paths, and getting layout options. - -Classes: - ConfigLoader: Manages application-specific configuration settings. - AppConfig: Extends ConfigLoader to provide additional methods for managing application-specific configuration settings. -""" -import os -import warnings -import yaml -from typing import Any, Dict, Optional - -import scraibe.app.global_var as gv - -CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) - -class ConfigLoader: - """A class that extends ConfigLoader to manage application-specific configuration settings. - - This class provides methods for setting global variables, launch options, and layout options from the configuration. - - Attributes: - config (Dict[str, Any]): The current configuration settings. - launch (Dict[str, Any]): The launch configuration settings. - model (Dict[str, Any]): The model configuration settings. - advanced (Dict[str, Any]): The advanced configuration settings. - queue (Dict[str, Any]): The queue configuration settings. - layout (Dict[str, Any]): The layout configuration settings. - """ - def __init__(self, config: Dict[str, Any]): - """Initializes a new instance of the ConfigLoader class. - - Args: - config (dict): The configuration dictionary. - """ - self.config = config - - def restore_defaults_for_keys(self, *args: str): - """Restores specified keys to their default values, including nested keys. - - Args: - *args (str): A list of keys or paths to keys (for nested dictionaries) to restore to default values. - Each key or path should be a list of keys leading to the desired key. - """ - default_config = self.get_default_config() - - for key in args: - self.apply_overrides(self.config, default_config, key) - - - - @classmethod - def load_config(cls, yaml_path: Optional[str] = None, **kwargs: Any) -> 'ConfigLoader': - """Load the configuration file and apply overrides. - - Args: - yaml_path (str, optional): Path to the YAML file containing overrides. - **kwargs: Additional overrides as keyword arguments. - - Returns: - ConfigLoader: A ConfigLoader object with the loaded configuration. - """ - - # Load the original configuration - config = cls.get_default_config() - - # Override with another YAML file if provided - - if yaml_path: - with open(yaml_path, 'r') as file: - override_config = yaml.safe_load(file) - cls.apply_overrides(config, override_config) - - # Apply overrides from kwargs - cls.apply_overrides(config, kwargs) - return cls(config) - - @staticmethod - def apply_overrides(orig_dict: Dict[str, Any], override_dict: Dict[str, Any], specific: Optional[str] = None): - """Recursively apply overrides to the configuration, only for specific keys. - - Args: - orig_dict (Dict[str, Any]): The original dictionary. - override_dict (Dict[str, Any]): The override dictionary. - specific (str, optional): The specific key to override. - """ - for key, value in override_dict.items(): - - if isinstance(value, dict): - # If the value is a dict, apply recursively - sub_dict = orig_dict.get(key, {}) - ConfigLoader.apply_overrides(sub_dict, value, specific) - orig_dict[key] = sub_dict - else: - # Apply override for this key - if specific is None: - # If no specific keys are provided, update the key - # If the value is not a dict, search for the key and update - if ConfigLoader.update_nested_key(orig_dict, key, value): - continue # Key was found and updated - orig_dict[key] = value # Key not found, update at this level - - elif key in specific: - # If specific keys are provided, only update if the key is in the list - if ConfigLoader.update_nested_key(orig_dict, specific, value): - continue # Key was found and updated - orig_dict[specific] = value - - @staticmethod - def update_nested_key(d, key, value): - """Recursively search and update the key in nested dictionary. - - Args: - d (Dict[str, Any]): The dictionary. - key (str): The key to update. - value (Any): The new value. - - Returns: - bool: True if the key was found and updated, False otherwise. - """ - - if key in d: - d[key] = value - return True - for k, v in d.items(): - if isinstance(v, dict) and ConfigLoader.update_nested_key(v, key, value): - return True - return False - - @staticmethod - def get_default_config(): - """Return the default configuration. - - Returns: - Dict[str, Any]: The default configuration. - """ - with open(gv.DEFAULT_APP_CONIFG_PATH , 'r') as file: - config = yaml.safe_load(file) - return config - - -class AppConfig(ConfigLoader): - """A class that extends ConfigLoader to manage application-specific configuration settings. - - This class provides methods for setting global variables, launch options, and layout options from the configuration. - - Attributes: - config (dict): The current configuration settings. - launch (dict): The launch configuration settings. - model (dict): The model configuration settings. - advanced (dict): The advanced configuration settings. - queue (dict): The queue configuration settings. - layout (dict): The layout configuration settings. - """ - def __init__(self, config : Dict[str, Any]): - """Initializes a new instance of the AppConfig class. - - Args: - config (dict): The configuration dictionary. - """ - self.config = config - - self.set_global_vars_from_config() - self.set_launch_options() - self.set_layout_options() - - self.launch = self.config.get("launch") - self.model = self.config.get("model") - self.advanced = self.config.get("advanced") - self.queue = self.config.get("queue") - self.layout = self.config.get("layout") - - def set_global_vars_from_config(self) -> None: - """Sets the global variables from a configuration dictionary. - - Args: - config (dict): A dictionary containing the parameters for the model. Modify the default parameters in the config.yml file. - - Returns: - None - """ - - gv.MODEL_PARAMS = self.config.get('model') - gv.TIMEOUT = self.config.get("advanced").get('timeout') - - def set_launch_options(self) -> None: - """Sets the launch options from a configuration dictionary. - - Args: - None - - Returns: - None - """ - launch_options = self.config.get("launch") - - if launch_options.get('auth').pop('auth_enabled'): - self.config['launch']['auth'] = (launch_options.get('auth').pop('auth_username'), - launch_options.get('auth').pop('auth_password')) - else: - self.config['launch']['auth'] = None - - def set_layout_options(self) -> None: - """Sets the layout options from a configuration dictionary. - - Args: - None - - Returns: - None - """ - self.config['layout']['header'] = self.check_and_set_path(self.config['layout'], 'header') - self.config['layout']['footer'] = self.check_and_set_path(self.config['layout'], 'footer') - self.config['layout']['logo'] = self.check_and_set_path(self.config['layout'], 'logo') - - def get_layout(self) -> Dict[str, str]: - """Gets the layout options from a configuration dictionary. - - Args: - None - - Returns: - dict: A dictionary containing the header and footer layout options. - """ - if not os.path.exists(self.config['layout']['header']) and \ - self.config['layout']['header'] == "scraibe/app/header.html": - - hname = os.path.join(CURRENT_PATH, "header.html") - - header = open(hname).read() - - elif not os.path.exists(self.config['layout']['header']) and self.config['layout']['header'] != "scraibe/app/header.html": - warnings.warn(f"Header file not found: {self.config['layout']['header']} \n" \ - "fall back to default.") - - hname = os.path.join(CURRENT_PATH, "header.html") - - header = open(hname).read() - elif os.path.exists(self.config['layout']['header']): - header = open(self.config['layout']['header']).read() - else: - warnings.warn(f"Header file not found: {self.config['layout']['header']}") - header = None - - - if header != None: - if self.config['layout']['logo'] == "scraibe/app/logo.svg": - header = header.replace("/file=logo.svg", f"/file={os.path.join(CURRENT_PATH, 'logo.svg')}") - elif self.config['layout']['logo'] != "scraibe/app/logo.svg": - header = header.replace("/file=logo.svg", f"/file={self.config['layout']['logo']}") - else: - warnings.warn(f"Logo file not found: {self.config['layout']['logo']}") - - - if self.config['layout']['footer'] != None: - if os.path.exists(self.config['layout']['footer']): - footer = open(self.config['layout']['footer']).read() - elif self.config['layout']['footer'] == None: - footer = None - else: - warnings.warn(f"Footer file not found: {self.config['layout']['footer']}") - else: - footer = None - return {'header' : header , - 'footer' : footer} - - @staticmethod - def check_and_set_path(config_item: dict, key: str) -> Optional[str]: - """Check if the file exists at the given path. If not, try with CURRENT_PATH. - Raise FileNotFoundError if the file still doesn't exist. - - Args: - config_item (dict): The configuration item. - key (str): The key to check in the configuration item. - - Returns: - str: The path to the file if it exists, None otherwise. - """ - _current_path = os.path.dirname(os.path.realpath(__file__)) # Define your CURRENT_PATH - - file_path = config_item.get(key) - if file_path is None: - return None - if not os.path.exists(file_path): - new_path = os.path.join(_current_path, file_path) - if not os.path.exists(new_path): - warnings.warn(f"{key.capitalize()} file not found: {config_item[key]} \n" \ - "fall back to default.") - else: - config_item[key] = new_path - - return config_item[key] \ No newline at end of file From 5e71667119c1f884bb00e3206293adfeae145bec Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 11:43:59 +0100 Subject: [PATCH 121/331] removed app --- scraibe/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scraibe/__init__.py b/scraibe/__init__.py index 3fd77e8..233cd4f 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -7,8 +7,6 @@ from .diarisation import * from .version import get_version as _get_version from .misc import * -from .app import * - from .cli import * __version__ = _get_version() From 45ee0b00b4f6f7799fbe38f530abda556b8eba9f Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 11:45:17 +0100 Subject: [PATCH 122/331] removed start server for now --- scraibe/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index 1c7f320..7cc7b1d 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -10,7 +10,7 @@ import json from .autotranscript import Scraibe from .misc import ParseKwargs -from .app.app import gradio_Interface + from whisper.tokenizer import LANGUAGES , TO_LANGUAGE_CODE from torch.cuda import is_available @@ -164,6 +164,7 @@ def cli(): else: # unfinished code + raise NotImplementedError("Currently not Working") import subprocess import sys From 6aa25dfec10e8d93427560ccc799c34ab1720b6b Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 12:35:52 +0100 Subject: [PATCH 123/331] resolve merge conflict --- scraibe/misc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scraibe/misc.py b/scraibe/misc.py index ae9136e..992e40c 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -13,7 +13,9 @@ if CACHE_DIR != PYANNOTE_CACHE_DIR: WHISPER_DEFAULT_PATH = os.path.join(CACHE_DIR, "whisper") PYANNOTE_DEFAULT_PATH = os.path.join(CACHE_DIR, "pyannote") -PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") +PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ + if os.path.exists(os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml")) \ + else 'pyannote/speaker-diarization-3.1' def config_diarization_yaml(file_path: str, path_to_segmentation: str = None) -> None: """Configure diarization pipeline from a YAML file. From 8af2294814bbc85659c2a8c78a66edf90a8b6267 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 12:40:51 +0100 Subject: [PATCH 124/331] avoid merge conflict --- scraibe/diarisation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index 570ac29..e598d30 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -212,9 +212,7 @@ class Diariser: if not os.path.exists(model) and use_auth_token is None: use_auth_token = cls._get_token() - model = 'pyannote/speaker-diarization' - elif not os.path.exists(model) and use_auth_token is not None: - model = 'pyannote/speaker-diarization' + elif os.path.exists(model) and not use_auth_token: # check if model can be found locally nearby the config file with open(model, 'r') as file: From 723dd7d0032c4455f1eba8eb54a7fecfaad12fe1 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Mon, 12 Feb 2024 12:45:28 +0100 Subject: [PATCH 125/331] Delete test_multiprocessing.py removed for merge --- test_multiprocessing.py | 105 ---------------------------------------- 1 file changed, 105 deletions(-) delete mode 100644 test_multiprocessing.py diff --git a/test_multiprocessing.py b/test_multiprocessing.py deleted file mode 100644 index ad9edc2..0000000 --- a/test_multiprocessing.py +++ /dev/null @@ -1,105 +0,0 @@ -import multiprocessing -import os -import threading -import queue -import time -import torch -from scraibe import Scraibe - -def input_thread(input_queue, processed_event): - while True: - processed_event.wait() # Wait for the previous input to be processed - processed_event.clear() # Clear the event for the next input - inp = input("Enter the path to the audio file ('q' to quit, 'reload' to reload model): ") - input_queue.put(inp) - -def clear_queue(queue): - while not queue.empty(): - try: - queue.get_nowait() - except queue.Empty: - continue - -def model_worker(request_queue, last_active_time, response_queue,loaded_event, running_event): - - loaded_event.set() - - model = Scraibe(dia_model="models/pyannote/config.yaml") - - while True: - audio_path = request_queue.get() - if audio_path == "STOP": - break - running_event.set() - transcription = model.autotranscribe(audio_path) - running_event.clear() - response_queue.put(transcription) - last_active_time.value = time.time() - - del model - torch.cuda.empty_cache() - clear_queue(request_queue) - clear_queue(response_queue) - loaded_event.clear() - - -def start_model_worker(request_queue, last_active_time, response_queue,loaded_event, running_event): - model_process = multiprocessing.Process(target=model_worker, args=(request_queue, last_active_time, response_queue,loaded_event, running_event)) - model_process.start() - return model_process - -def timer_thread(request_queue, last_active_time,loaded_event, running_event, timeout=30): - while True: - time.sleep(timeout) - - if time.time() - last_active_time.value > timeout and loaded_event.is_set() and not running_event.is_set(): - print(f"No activity for the last {timeout} seconds. Stopping the model worker.", flush=True) - request_queue.put("STOP") - -if __name__ == "__main__": - request_queue = multiprocessing.Queue() - response_queue = multiprocessing.Queue() - input_queue = queue.Queue() - last_active_time = multiprocessing.Value('d', time.time()) - loaded_event = multiprocessing.Event() - running_event = multiprocessing.Event() - - processed_event = multiprocessing.Event() - processed_event.set() # Initially set to allow the first input - - model_process = start_model_worker(request_queue, last_active_time, response_queue,loaded_event ,running_event) - timer = threading.Thread(target=timer_thread, args=(request_queue, last_active_time, loaded_event, running_event), daemon=True) - input_handler = threading.Thread(target=input_thread, args=(input_queue,processed_event)) - - timer.start() - input_handler.start() - - while True: - - audio_file_path = input_queue.get() # Get input from the input thread - print(audio_file_path) - - if audio_file_path.lower() == 'q': - request_queue.put("STOP") - model_process.join() - break - elif audio_file_path.lower() == 'reload': - if loaded_event.is_set(): - request_queue.put("STOP") - model_process.join() - model_process = start_model_worker(request_queue, last_active_time, response_queue, loaded_event, running_event) - print("Model reloaded.") - elif not os.path.exists(audio_file_path): - print("File does not exist.") - else: - if not loaded_event.is_set(): - model_process = start_model_worker(request_queue, last_active_time, response_queue, loaded_event, running_event) - request_queue.put(audio_file_path) - transcription = response_queue.get() - print(transcription) - - processed_event.set() # Signal that the input has been processed - - model_process.join() - timer.join() - input_handler.join() From a48829b7cdc17804b9434077475d7fa64d0ca77c Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Mon, 12 Feb 2024 12:46:06 +0100 Subject: [PATCH 126/331] Delete test_multithreading.py --- test_multithreading.py | 85 ------------------------------------------ 1 file changed, 85 deletions(-) delete mode 100644 test_multithreading.py diff --git a/test_multithreading.py b/test_multithreading.py deleted file mode 100644 index fb4e301..0000000 --- a/test_multithreading.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -import time - -from scraibe import Scraibe - -import multiprocessing -import threading -import torch -import gc - -model = None -last_used = time.time() -transcribe_active = threading.Event() - -def transcribe_thread(audio): - - global model - transcribe_active.set() - print(model.autotranscribe(audio)) - transcribe_active.clear() - -def model_thread(): - global model, last_used - model = Scraibe(dia_model= "models/pyannote/config.yaml") - last_used = time.time() - -def interaction_thread(): - global model, model_runner - while True: - command = input("Enter a command ('q' to quit, 'reload' to reload model): ") - - if command.lower() == 'q': - break - elif command.lower() == 'reload': - print("Reloading model...", model) - if model is None: - transcribe_active.clear() #black magic - model_runner = threading.Thread(target=model_thread) - model_runner.start() - model_runner.join() - - else: - print("Model is already loaded.") - else: - if os.path.exists(command): - transcribe = threading.Thread(target=transcribe_thread, args=(command,)) - transcribe.start() - transcribe.join() - - else: - print("File does not exist.") - -def delete_unused_model(model_runner): - global model, last_used, transcribe_active - - while True: - _unload_porperty = (not transcribe_active.is_set() and (time.time() - last_used > 30) and model is not None) - if _unload_porperty: - - del model - model = None - - gc.collect() - torch.cuda.empty_cache() - - model_runner.join() - - print("Model deleted") - time.sleep(10) - -if __name__ == "__main__": - - lock = threading.Lock() - - interaction = threading.Thread(target=interaction_thread) - model_runner = threading.Thread(target=model_thread, daemon=True) - model_deleter = threading.Thread(target=delete_unused_model, args=(model_runner,), daemon=True) - - model_runner.start() - model_deleter.start() - - # Ensure the model is initialized before starting the interaction - model_runner.join() - interaction.start() - interaction.join() \ No newline at end of file From b61c87270f63b015df72659a9fd485a848b05a5c Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 13:41:54 +0100 Subject: [PATCH 127/331] fixed whisper Versioning --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8cf1782..74def0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ torch~=2.2.0 -openai-whisper~=20231117 +openai-whisper==20231117 numpy~=1.23.5 pyannote.audio~=3.1.1 pyannote.core~=5.0.0 From 09576866b3c0f4c409d4b1c1992443b764ce43dc Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 13:46:48 +0100 Subject: [PATCH 128/331] changed numpy version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 74def0e..7ee1553 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ torch~=2.2.0 openai-whisper==20231117 -numpy~=1.23.5 +numpy>=1.26.4 pyannote.audio~=3.1.1 pyannote.core~=5.0.0 pyannote.database~=5.0.1 From b2b2726dbc5f9cfd1883e4362ba36cc2fd676035 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 14:23:02 +0100 Subject: [PATCH 129/331] updated dep in sphinx action --- .github/workflows/documentation.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 1e52fb0..9f5141f 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,8 +18,6 @@ jobs: sudo apt-get install libsndfile1-dev pip install --upgrade pip pip install -r requirements.txt - pip install --upgrade --force-reinstall torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu --extra-index-url https://download.pytorch.org/whl/cpu - pip install numpy==1.25 pip install --upgrade sphinx sphinx_rtd_theme myst-parser pip install --upgrade markdown-it-py[plugins] pip install --upgrade mdit-py-plugins From ceb0c57369e9c6f9af7f02c105a96da271d06fd0 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 15:28:35 +0100 Subject: [PATCH 130/331] add extra require to install webui --- setup.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 1e2c641..d4640f9 100644 --- a/setup.py +++ b/setup.py @@ -34,9 +34,14 @@ if __name__ == "__main__": open(os.path.join(os.path.dirname(__file__), "requirements.txt")) ) ], - dependency_links=[ - 'https://download.pytorch.org/whl/cu113', - ], + extras_require= { + "app" : "scraibe-webui @ https://github.com/JSchmie/ScrAIbe-WebUI" + } + , + # dependency_links=[ + # 'https://download.pytorch.org/whl/cu113', + # ], + url= github_url, license='GPL-3', From e7d078d1da42b493b8926e8ada95871000bee313 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 15:43:24 +0100 Subject: [PATCH 131/331] updated dependencies --- requirements.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7ee1553..8fb4cfb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,13 +8,6 @@ pyannote.database~=5.0.1 pyannote.metrics~=3.2.1 pyannote.pipeline~=3.0.1 -setuptools~=65.6.3 -setuptools-rust~=1.5.2 - tqdm>=4.65.0 -gradio~=3.36.1 -gradio-client~=0.2.7 - - From 719b3902d6f1ee93c0fd33ea65d9751fa2454de6 Mon Sep 17 00:00:00 2001 From: Jaikinator Date: Mon, 12 Feb 2024 17:31:45 +0100 Subject: [PATCH 132/331] updated setup.py --- setup.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index d4640f9..c2b37d4 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ -import pkg_resources import os +import re from setuptools import setup, find_packages module_name = "scraibe" @@ -22,6 +22,11 @@ build_version = "SCRAIBE_BUILD" in os.environ version["ISRELEASED"] = True if "ISRELEASED" in os.environ else False +############### load requirements ############### + +with open(os.path.join(os.path.dirname(__file__), "requirements.txt")) as f: + requirements = [line.strip() for line in f if line.strip() and not line.startswith('#')] + if __name__ == "__main__": setup( @@ -30,10 +35,7 @@ if __name__ == "__main__": packages=find_packages(), python_requires=">=3.8", readme="README.md", - install_requires = [str(r) for r in pkg_resources.parse_requirements( - open(os.path.join(os.path.dirname(__file__), "requirements.txt")) - ) - ], + install_requires = requirements, extras_require= { "app" : "scraibe-webui @ https://github.com/JSchmie/ScrAIbe-WebUI" } From 69b5e45a061b67d116ec174adef056b0bfc2e875 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 13 Feb 2024 09:29:40 +0000 Subject: [PATCH 133/331] change order of packages to prevent install flaiures --- requirements.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8fb4cfb..69b52fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,12 @@ -torch~=2.2.0 +tqdm>=4.65.0 +numpy>=1.26.4 openai-whisper==20231117 -numpy>=1.26.4 + pyannote.audio~=3.1.1 pyannote.core~=5.0.0 pyannote.database~=5.0.1 pyannote.metrics~=3.2.1 pyannote.pipeline~=3.0.1 -tqdm>=4.65.0 - - +torch~=2.2.0 \ No newline at end of file From 30bf2b3ba386b7a7c70dbeb16acbd414c8813845 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 13 Feb 2024 09:45:54 +0000 Subject: [PATCH 134/331] lower mandatory torch version to aviod install conflicts --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 69b52fc..5872774 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,4 @@ pyannote.database~=5.0.1 pyannote.metrics~=3.2.1 pyannote.pipeline~=3.0.1 -torch~=2.2.0 \ No newline at end of file +torch>=2.0.0 \ No newline at end of file From cb1634734b30950ca66cd96e9e4351e401855718 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 13 Feb 2024 09:50:53 +0000 Subject: [PATCH 135/331] added manifest.in to setup action --- MANIFEST.IN | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 MANIFEST.IN diff --git a/MANIFEST.IN b/MANIFEST.IN new file mode 100644 index 0000000..e48ab26 --- /dev/null +++ b/MANIFEST.IN @@ -0,0 +1,4 @@ +recursive-include pyannote *.py +recursive-include pyannote *.yaml +global-exclude *.pyc +global-exclude __pycache__ \ No newline at end of file From b075271b89363bda1141893418b2a0471e8af320 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 13 Feb 2024 09:54:55 +0000 Subject: [PATCH 136/331] fixed wording --- MANIFEST.IN | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.IN b/MANIFEST.IN index e48ab26..6607396 100644 --- a/MANIFEST.IN +++ b/MANIFEST.IN @@ -1,4 +1,4 @@ -recursive-include pyannote *.py -recursive-include pyannote *.yaml +recursive-include scraibe *.py +recursive-include scraibe *.yaml global-exclude *.pyc global-exclude __pycache__ \ No newline at end of file From fdb16f2f45cb79d574d3bc0ba850c76b3b7d5352 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 15 Mar 2024 12:54:08 +0100 Subject: [PATCH 137/331] fixed comments --- scraibe/test/test_audio.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scraibe/test/test_audio.py b/scraibe/test/test_audio.py index 5a35055..2a0ce53 100644 --- a/scraibe/test/test_audio.py +++ b/scraibe/test/test_audio.py @@ -3,10 +3,9 @@ from .audio import AudioProcessor import torch -device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - -test_waveform = torch.tensor([]).to(device) +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +TEST_WAVEFORM = torch.tensor([]).to(device) TEST_SR = 16000 SAMPLE_RATE = 16000 NORMALIZATION_FACTOR = 32768 @@ -43,11 +42,10 @@ def test_AudioProcessor_init(probe_audio_processor): def test_cut(): """Test for the test_cut Method for fixed parameters """ - waveform = torch.Tensor(10, 3) - sr = 16000 + start = 4 end = 7 - assert AudioProcessor(waveform, sr).cut(start, end).size() == int((end - start) * TEST_SR) + assert AudioProcessor(TEST_WAVEFORM, TEST_SR).cut(start, end).size() == int((end - start) * TEST_SR) From e40860c92fad790caed9d1610905eec60433276b Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 21 Mar 2024 09:29:37 +0100 Subject: [PATCH 138/331] docstring added and variable type --- scraibe/test/test_diarisation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index b911b88..8b63151 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -10,7 +10,7 @@ def diariser_instance(): """Creates a instance of the Diariser Object for further testing Returns: - _type_: _description_ + Object: Diariser Object for handling the diarization process of an audio file using a pretrained model from pyannote.audio. Diarization is the task of determining "who spoke when." """ with mock.patch.object(Diariser, '_get_token', return_value = 'personal Hugging-Face token') return Diariser('pyannote') @@ -21,17 +21,17 @@ def test_Diariser_init(diariser_instance): """Tests if the Diariser gets initiated correctly Args: - diariser_instance + diariser_instance (obj): instance of the Diariser object """ assert diariser_instance.model == 'pyannote' def test_diarisation_function(diariser_instance): - """tests if the Diariser object with an example audio File + """tests if the Diariser works object with an example audio File Args: - diariser_instance + diariser_instance (obj): instance of the Diariser object """ with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): diarization_output = diariser_instance.diarization('example_audio_file.wav') From 22ca00fea953786e0576e0812d0d629311e110e1 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 21 Mar 2024 11:06:01 +0100 Subject: [PATCH 139/331] Docstrings and global Variables got fixed --- scraibe/test/test_audio.py | 66 +++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/scraibe/test/test_audio.py b/scraibe/test/test_audio.py index 2a0ce53..6b46c1b 100644 --- a/scraibe/test/test_audio.py +++ b/scraibe/test/test_audio.py @@ -4,8 +4,8 @@ import torch -device = torch.device("cuda" if torch.cuda.is_available() else "cpu") -TEST_WAVEFORM = torch.tensor([]).to(device) +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") +TEST_WAVEFORM = torch.tensor([]).to(DEVICE) TEST_SR = 16000 SAMPLE_RATE = 16000 NORMALIZATION_FACTOR = 32768 @@ -13,12 +13,16 @@ NORMALIZATION_FACTOR = 32768 @pytest.fixture def probe_audio_processor(): - """Creates a dummy AudioProcessor Object + """Fixture for creating an instance of the AudioProcessor class with test waveform and sample rate. + + This fixture is used to create an instance of the AudioProcessor class with a predfined test waveform and sample rate (TEST_SR). It returns the instantiated AudioProcessor , which can bes used as a + dependency in other test functions. + Returns: - AudioProcessor Object with given parameters test_waveform and TEST_SR + AudioProcessor (obj): An instance of the AudioProcessor class with the test waveform and sample rate. """ - return AudioProcessor(test_waveform, TEST_SR) + return AudioProcessor(TEST_WAVEFORM, TEST_SR) @@ -27,20 +31,40 @@ def probe_audio_processor(): def test_AudioProcessor_init(probe_audio_processor): """ - testing if the audio_processor Object gets initialized correctly + Test the initialization of the AudioProcessor class. + + This test verifies that the AUdioProcessor class is correctly initialized with the provided waveform and sample rate. It checks whether the instantiated AhdioProcessor object has the correct attributes + and whether the waveform and sample rate match the expected values. + + Args: + probe_audio_processor (obj): An instance of the AudioProcessor class to be tested. + + + Returns: + None + + - Args: probe_audio_processor Object """ assert isinstance(probe_audio_processor, AudioProcessor) - assert probe_audio_processor.waveform.device == test_waveform.device - assert torch.equal(probe_audio_processor.waveform, test_waveform) - assert probe_audio_processor.sr == test_sr + assert probe_audio_processor.waveform.device == TEST_WAVEFORM.device + assert torch.equal(probe_audio_processor.waveform, TEST_WAVEFORM) + assert probe_audio_processor.sr == TEST_SR def test_cut(): - """Test for the test_cut Method for fixed parameters + """Test the cut function of the AudioProcessor class. + + This test verifies that the cut function correctly extracts a segment of audio data from + the waveform, given start and end indices. It checks whether the size of the extracted segment matches + the expected size based on the provided start and end indices and the sample rate. + + Returns: + None + + """ start = 4 @@ -57,16 +81,28 @@ def test_cut(): def test_audio_processor_invalid_sr(): - """Testing the audio_processor Object with invalid Sample rate + """Test the behavior of AudioProcessor when an invalid smaple rate is provided. + + This test verifies that the AudioProcessor constructor raises a ValueError when an invalid sample rate is provided. It uses the pytest.raises context manager to check if the ValueError is raised when initializing an + AudioProcessor object with an invalid sample rate. + + Returns: + None """ with pytest.raises(ValueError): - AudioProcessor(test_waveform, [44100,48000]) + AudioProcessor(TEST_WAVEFORM, [44100,48000]) def test_audio_processor_SAMPLE_RATE(): - """Making sure Sample Rate of Audio_processor Sample Rate matches global Sample Rate + """Test the default sample rate of the AudioProcessor class. + + This test verifies that the default sample rate of the AudioProcessor class matches the expected value defined by the constant SAMPLE_RATE. It instantiates an AudioProcessor object with a test waveform + and checks whether the sample rate attribute (sr) of the AudioProcessor object equals the predefined constant SAMPLE_RATE. + + Returns: + None """ - probe_audio_processor = AudioProcessor(test_waveform) + probe_audio_processor = AudioProcessor(TEST_WAVEFORM) assert probe_audio_processor.sr == SAMPLE_RATE From 2117353332f454f2808d54aac4cf76340e8a1528 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 22 Mar 2024 11:05:39 +0100 Subject: [PATCH 140/331] docstring added --- scraibe/test/test_diarisation.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/scraibe/test/test_diarisation.py b/scraibe/test/test_diarisation.py index 8b63151..1976016 100644 --- a/scraibe/test/test_diarisation.py +++ b/scraibe/test/test_diarisation.py @@ -7,10 +7,13 @@ from scraibe import Diariser @pytest.fixture def diariser_instance(): - """Creates a instance of the Diariser Object for further testing + """Fixture for creating an instance of the Diariser class with mocked token. + + This fixture is used to create an instance of the the Diariser class with a mocked token returned by the _get_token method. It patches the _get_token method of the Diariser class + using unit.test.mock.patch.object, ensuring that it returns a predetrmined value ('personal Hugging-Face token'). The mocked Diariser object is retunrned and can be used as a dependency in otehr tests. Returns: - Object: Diariser Object for handling the diarization process of an audio file using a pretrained model from pyannote.audio. Diarization is the task of determining "who spoke when." + Diariser(Obj): An instance of the Diariser class with a mocked token. """ with mock.patch.object(Diariser, '_get_token', return_value = 'personal Hugging-Face token') return Diariser('pyannote') @@ -18,20 +21,37 @@ def diariser_instance(): def test_Diariser_init(diariser_instance): - """Tests if the Diariser gets initiated correctly + """Test the initialization of the Diariser class. + + This test verifies that the Diariser class is correctly initialized with the specified model. + It checks whether the 'model' attribute of the instantiated Diariser object equals 'pyannote'. + Args: - diariser_instance (obj): instance of the Diariser object + diariser_instance (obj): instance of the Diariser class + + Returns: + None """ assert diariser_instance.model == 'pyannote' def test_diarisation_function(diariser_instance): - """tests if the Diariser works object with an example audio File + """Test the diarization function of the Diariser class. + + This test verifies that the diarization function of the Diariser class correctly processes + an audio file and returns the diarization result. It patches the apply method of the model + attribute of the Diariser instance using unittest.mock.patch.object, ensuring that it returns + a predetermined value ('diarization_result') when called with the audio file argument. + It then calls the diarization function with an example audio file and checks whether the returned + diarization output matches the expected result ('diarization_result'). Args: diariser_instance (obj): instance of the Diariser object + + Returns: + None """ with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): diarization_output = diariser_instance.diarization('example_audio_file.wav') From ae3dfb0fd5bc03066a336fde937f05271028fea1 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 25 Mar 2024 09:56:38 +0100 Subject: [PATCH 141/331] audio test files --- scraibe/test/audio_test_1.mp4 | Bin 0 -> 767698 bytes scraibe/test/audio_test_2.mp4 | Bin 0 -> 226740 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 scraibe/test/audio_test_1.mp4 create mode 100644 scraibe/test/audio_test_2.mp4 diff --git a/scraibe/test/audio_test_1.mp4 b/scraibe/test/audio_test_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d7b044067c30ea49a3253ec9996cdb82aa13a0ae GIT binary patch literal 767698 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

?#u4}YF6kfX7cy8o@4kH752Im0 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�s*+u~ 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 VBlpZ` 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 z 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 literal 0 HcmV?d00001 diff --git a/scraibe/test/audio_test_2.mp4 b/scraibe/test/audio_test_2.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c1307e569dc15d4bc82fce173a45a14ce149346e GIT binary patch literal 226740 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

    |3f5I}pwnKRZzG>_#D?CE3&Kobl4 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 Date: Mon, 25 Mar 2024 12:32:42 +0100 Subject: [PATCH 142/331] Tests_diarisation Docstring got added --- .../test_audio.cpython-39-pytest-7.3.1.pyc | Bin 0 -> 4389 bytes ...st_autotranscript.cpython-39-pytest-7.3.1.pyc | Bin 0 -> 4821 bytes .../test_transcriber.cpython-39-pytest-7.3.1.pyc | Bin 0 -> 1413 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 scraibe/test/__pycache__/test_audio.cpython-39-pytest-7.3.1.pyc create mode 100644 scraibe/test/__pycache__/test_autotranscript.cpython-39-pytest-7.3.1.pyc create mode 100644 scraibe/test/__pycache__/test_transcriber.cpython-39-pytest-7.3.1.pyc diff --git a/scraibe/test/__pycache__/test_audio.cpython-39-pytest-7.3.1.pyc b/scraibe/test/__pycache__/test_audio.cpython-39-pytest-7.3.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03969182f30504a17dfe7247cb4262c639964483 GIT binary patch literal 4389 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 literal 0 HcmV?d00001 diff --git a/scraibe/test/__pycache__/test_transcriber.cpython-39-pytest-7.3.1.pyc b/scraibe/test/__pycache__/test_transcriber.cpython-39-pytest-7.3.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f7cfdf7a404d432bef8e23d614adbacd64a9de2 GIT binary patch literal 1413 zcmah}OOGQp5VoCurPCSMT{MEr60L-^EiDQA;Id-Sjt~bfds)Px515|VNjlRn+3sZ% zbuK#`;U93A91$08@IyHH%89?QC(7M(x+OVvmvn=Ni zM*LOHr;0PGc8FSKRT-1y7 zk^Sg{XokaNApAiWFJ)7-7Ai@WyGKC2lcuKz3WVCFf-0wVwP< zIhQVTTW^M$XY4bwb(pWbOMi=ABKaNc2d%rsVhK{;NF^~$?Gdjnf&k)2ivS4_0qg%v zj?u(wW*9G#qujQA*)`n?~RSNizvX~abLAcC15t&dVExllIJ6Nm)HC(q+Z zM}n(Hl=NkqH(ZBT@u5FWv*_@eHyu8!S6vedeTz=hJck5oF|CR^6+yB?SfJ02c(N_WiTL*>(7khlxzH{VPaRlz5s zn$LL&S+H~h&2swH88@qWW*s<7(DKlk`9vhAX zF?;?l*{;?Oyr6SEfMP;P4*t0B literal 0 HcmV?d00001 From b01818212f76d34ab2bdf5bd22e0bac97501ee4e Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 10:23:24 +0100 Subject: [PATCH 143/331] changed folder position --- .../test_audio.cpython-39-pytest-7.3.1.pyc | Bin 4389 -> 0 bytes ...st_autotranscript.cpython-39-pytest-7.3.1.pyc | Bin 4821 -> 0 bytes .../test_transcriber.cpython-39-pytest-7.3.1.pyc | Bin 1413 -> 0 bytes {scraibe/test => test}/audio_test_1.mp4 | Bin {scraibe/test => test}/audio_test_2.mp4 | Bin {scraibe/test => test}/test_audio.py | 0 {scraibe/test => test}/test_autotranscript.py | 0 {scraibe/test => test}/test_diarisation.py | 0 {scraibe/test => test}/test_transcriber.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 scraibe/test/__pycache__/test_audio.cpython-39-pytest-7.3.1.pyc delete mode 100644 scraibe/test/__pycache__/test_autotranscript.cpython-39-pytest-7.3.1.pyc delete mode 100644 scraibe/test/__pycache__/test_transcriber.cpython-39-pytest-7.3.1.pyc rename {scraibe/test => test}/audio_test_1.mp4 (100%) rename {scraibe/test => test}/audio_test_2.mp4 (100%) rename {scraibe/test => test}/test_audio.py (100%) rename {scraibe/test => test}/test_autotranscript.py (100%) rename {scraibe/test => test}/test_diarisation.py (100%) rename {scraibe/test => test}/test_transcriber.py (100%) diff --git a/scraibe/test/__pycache__/test_audio.cpython-39-pytest-7.3.1.pyc b/scraibe/test/__pycache__/test_audio.cpython-39-pytest-7.3.1.pyc deleted file mode 100644 index 03969182f30504a17dfe7247cb4262c639964483..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4389 zcma)9O>7)V6|U;;>FN3PPn?jzvTL#|c*3sZ8OL!pB&)S{14P<&q9g(_m)2ye?Ti`E znCfyiHa${=bA|{Zl!FiUo`}SOBZ3=3d*HB#J+=@BxDaVi93u{V?{&{iPe>$Y)URGu zy{dYze!j1&RjCvOeuc|&<8xPte^MsTsd1aG zvXtG;Z+lHIEh{t&LE)&lDURh)Njb-|Sys7b1z%OU&8fiMnpSz`JrW19Iim`y_((Ko zRY{eRo=_E4MLMUZ)HKqQO5PT=nP>1stUr^GH5pgec2%c;Q}^3J81{A3Y457mvxn~q z(Gd@YXp?dFM*)3+FQmAQFw$5D7P^)Dm;G>ed%LCg{9wd7&*V}QPv!Q+}bRN|#p5HuwufH9f*Zs}S zpx15nRO>u?Tf2TYptRMw8@v;S_s-u=%Z6!Mcb>Xr;kPL8#htzQy_$}icshqWBa0u9MMM_2$bvLj*vc7MY;hkF7KZzP1`CI+p{v}Xb4m;>lShdcS;z7~{#+^- z-iWQjz>e&#;y{`bq!Y==`H3~iMY+c^T!(#G<)hq&R36oASjSGrvwxsI@$5?;EA@H;}?dWqR-RR#*~ibW2w)Q1?lj%z}DEps(6@#w=Qkd$n8kz`PB3L&~rx&Yj&y4;X9=*b&AMxnWF?y9phtK2a;Ok$=n&un6 ze$E!z2)!2~`!`vqk^P6P|0lAkLzf~;qj6t|#)HpdYl_yxOk)kOR}jHx=!w=qVhyIL zW^xT+!K=?+TFLqkcL;k-G0lu-zzUzRm(z&J(x0-I&@lruYWC2kU7}8?xya&qFQlFp zvYrmGb8wKR$9#XK6^4N}^KUKe>|LmZ#h<0h{8cVm zs)Y+wQm=(?b3vmP){29;*a zacO@l6LlRi#tvLw+V5qGUoyli{0x4&+v*kvJWne_$DpK+EyJ;KD9J3sS(?S1>Ff5r znmcy13HO#stmql?dlurU+&@P%_ln^|J)Re=pa$oOr}nr@j{SKx6B!m)f3w@a+v@tg)^-pV{IInV zP%+4h_Y-*Jn$~?CpYVw{TE-6^?0_0t#4xcpoDa||uPiTEJmX``E_dJVZ|}5ppy%LU zoZ}getx$7twIXl1xI`W&VJ#<_WT=&FO+A}g&1O=wq|LiA$Dd^C^=78;3rVA-YIRI| z+9Tgi^XyDAw%_S>%r{UQl8JDi$j{_orTz}|HWdtiQ&4DunX;}hWgST(s$**)l#N60 z02*=w&{8BKqA~#NmQ6$vS&@yr8@XG~ARpxq1jDd(S_}$N!Q|$|plIBvcpwL*$csvV zwsKSkaLt+gk*D0K3~0+!4MUq}aHC;r`~~Vu26UyLKr535pz=T!LyO;&1;L&EBUz-t zhkMS*;tRGQYWy2pB-B`B9c0644iH%ywgBvV+yv07v+z$>gSu@!Xq zS7sgH&M7F1QzDB>rgaykB?Bw8zE2jUqjhCsj9-yel#Fr6{iI_|`1=R8O8osZTMhlK zW=8xwSsBEN5&uP2q+`S?w*U#NslzD{Kt)ZfnW(}9P&F0QR-g!*-8DQ4A2SVP*d z5I~vWG(l!s2rNm;prl2k4#)kyf<_kGyCwzB)gi$EcL9xyVP_wBzW{ZJ`3(SRN)ynA zK4I8}g@mHe#r!)=Qg!NN{#-&>7`ujU80GXEG#h6j;vCpZo7fI|Dz-a4lR(QrWoCt?wz2AYkq>l zHfAJtD0ucqFOwr*B{5IpG>J1LzDD9T5??3rIs`f#g{SLO+92^JiEl$>LG9fp<6VND zz`Ja2c_KGq*_g0wG$t%pl3W6mjSCaGi!b7)$GC}T7vUMdT!dYg4*&H?#j2u&h zMJWPo^ZSK>M}m|*Ok;tkQ4^TxlO*W*6_;+WeSGtyYyPdZJJ<9nD*g%yy3MTD%9**O zQ>ed7;{V;>x#X((7PWbk#5YNtBf-ltUX%Hku1UtTbu>yB#gqC9amx;K*l2U;Awp0GZbB5;QcFsry1QHq zM~d_hEA7N*JNZ&l13269^ic;0N-tG@?0-m0v zxrvc?b8|0=2YD@wP!%I;z6o{K{sllvz%%c za+iiKf3(?zg{Rs^Po|ATCib+Eu0*CQ{YW1fnUOvTMW`!dY(CcRYw5DG#xT@?HU9F2nAv%q&BJ>^ir7(CUcWkgu;!)ipC`hVUvbm2mtLio8)X-SN!N;( zwo4B~#JQtu{KZW#_1sbJ#%XE#-0CM`)RQJGjLp@Vy!)-7gcBM# zjYAqVY1401kjgoD;g}NP}PJfo(lP6NL`L^YQvBEa26DS_6Y<8nx2V~ zp|s3U0?un@X8MT3OdlH@W^HATEv3PAHz3(0O~S0yc$YLXxSaG!gCq(wgiomf^%U7v*jf(G4@qH?OfWos2`w?xj5#{TU zs>ZufHQwr3xU1w(q2sxJqoPpQ=Nf(dGWPieeR!1$j|w_&i-OOsi>K_tvmP=mviPwvTp`~; z{kv(5g_Ae|UzO%CQYA}k97huPo=J3xHt}VQ(WVc-(in@Uj8VecipX)Qwpc!E3rZr6 z6N&$#LV=;Qv7iI}ua||Efsv5C38DQS`J0KqBSUC|nH4QAyMMsEr+F$^%s&yObXyFzVw5NyLLBQyB3ZZQ#Z7i>LGj z%_INGy^!#Q$ua_Gl6KTo6D_T*R`Md!4g-&g#~L9sM^tERQ>>$QiYcg{R>NuTTR4{s@1(oM zgt*~n%A64QCrsQRWA?2z@)<>E>}^POk|w8csqsE(FmZ2@MuoUbjYA%T{PQ7?QTQi$ zBd4^-r?88bM)eei_cuqjYVA#pkb-7*Rs%*SbC8B^ahhAs@Hx*wD8J+x&Y|QqcY>4C z+zL|0VZ-Ywh2>RqnoH|$P10OiIb^K7#@dJoemnTed1NDpsH*~fy%H-n;-m(=#@_UE zSD~lZ!a9=mlUbi&RNif1{0Gf&e!qe3U!+C*46Xk#j$*wA%V$^ejI?J+Ko}^&k{75T4v-g7P`G56T#fZF_3V&G4AH~VVoAlz7%Xi9M zkZ%IMZx^f0^;CN98IF+~w91o1?FvvD~n&txC-%qlyPs?hcg=v64?Z{jl{ zZ=ub*oIB-}g{d&V)F~LXU2H23@3xFSl(CBP`f*cUo3s@v?FylwCFQlr4WhWy(|w8W zMw}|MFZr$gL97U2yN~~vNV-5sI%V_Pc6^}rWyGXk>;_#qIAP^PNzw_;M99!9%jON> l%*%NT*Tel@bc_BY!2fRfk#NLoV#YLuOONH)4n4M0|349Ova$dG diff --git a/scraibe/test/__pycache__/test_transcriber.cpython-39-pytest-7.3.1.pyc b/scraibe/test/__pycache__/test_transcriber.cpython-39-pytest-7.3.1.pyc deleted file mode 100644 index 4f7cfdf7a404d432bef8e23d614adbacd64a9de2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1413 zcmah}OOGQp5VoCurPCSMT{MEr60L-^EiDQA;Id-Sjt~bfds)Px515|VNjlRn+3sZ% zbuK#`;U93A91$08@IyHH%89?QC(7M(x+OVvmvn=Ni zM*LOHr;0PGc8FSKRT-1y7 zk^Sg{XokaNApAiWFJ)7-7Ai@WyGKC2lcuKz3WVCFf-0wVwP< zIhQVTTW^M$XY4bwb(pWbOMi=ABKaNc2d%rsVhK{;NF^~$?Gdjnf&k)2ivS4_0qg%v zj?u(wW*9G#qujQA*)`n?~RSNizvX~abLAcC15t&dVExllIJ6Nm)HC(q+Z zM}n(Hl=NkqH(ZBT@u5FWv*_@eHyu8!S6vedeTz=hJck5oF|CR^6+yB?SfJ02c(N_WiTL*>(7khlxzH{VPaRlz5s zn$LL&S+H~h&2swH88@qWW*s<7(DKlk`9vhAX zF?;?l*{;?Oyr6SEfMP;P4*t0B diff --git a/scraibe/test/audio_test_1.mp4 b/test/audio_test_1.mp4 similarity index 100% rename from scraibe/test/audio_test_1.mp4 rename to test/audio_test_1.mp4 diff --git a/scraibe/test/audio_test_2.mp4 b/test/audio_test_2.mp4 similarity index 100% rename from scraibe/test/audio_test_2.mp4 rename to test/audio_test_2.mp4 diff --git a/scraibe/test/test_audio.py b/test/test_audio.py similarity index 100% rename from scraibe/test/test_audio.py rename to test/test_audio.py diff --git a/scraibe/test/test_autotranscript.py b/test/test_autotranscript.py similarity index 100% rename from scraibe/test/test_autotranscript.py rename to test/test_autotranscript.py diff --git a/scraibe/test/test_diarisation.py b/test/test_diarisation.py similarity index 100% rename from scraibe/test/test_diarisation.py rename to test/test_diarisation.py diff --git a/scraibe/test/test_transcriber.py b/test/test_transcriber.py similarity index 100% rename from scraibe/test/test_transcriber.py rename to test/test_transcriber.py From ab54afdab7559b976d2a1ef6c13b0056d1a2f4a7 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:18:31 +0100 Subject: [PATCH 144/331] pytest ci --- .github/workflows/pytest.yaml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/pytest.yaml diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100644 index 0000000..8848433 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,32 @@ +name: Run tests + +on: + + #pull_request: + #branches: ['main', 'develop'] + + workflow_dispatch: + +jobs: + pytest: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v3 + with: + python-version: 3.9 + + - name: Install Dependencies + run: python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Set up Hugging Face Token + env: + hf_token: ${{ secrets.HF_TOKEN }} + + - name: Run Pytest + run: pytest \ No newline at end of file From 72107a6146f7677ee2c6e5d103e38b780bc26dfa Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:23:40 +0100 Subject: [PATCH 145/331] ci datei --- .github/workflows/pytest.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8848433..abf3a4d 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -24,9 +24,9 @@ jobs: run: python -m pip install --upgrade pip pip install -r requirements.txt - - name: Set up Hugging Face Token - env: - hf_token: ${{ secrets.HF_TOKEN }} + - - name: Run Pytest + - name: Run pytest + env: + hf_token: ${{ secrets.HF_TOKEN }} run: pytest \ No newline at end of file From cce570f6483167561e2c3a9d44cd061e3d911e07 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:29:44 +0100 Subject: [PATCH 146/331] ci datei --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index abf3a4d..d00d260 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,6 +1,6 @@ name: Run tests -on: +on: push #pull_request: #branches: ['main', 'develop'] From c4d1ecbc11a4f06af722a65537658c1ff3ccb1aa Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:33:00 +0100 Subject: [PATCH 147/331] ci datei push --- .github/workflows/pytest.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index d00d260..70a1bc7 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,6 +1,7 @@ name: Run tests -on: push +on: + push: #pull_request: #branches: ['main', 'develop'] From b8ef01fdf1ded2c289489a7a13880311d2e71940 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:36:13 +0100 Subject: [PATCH 148/331] pip install pytest --- .github/workflows/pytest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 70a1bc7..c78a91e 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -24,6 +24,7 @@ jobs: - name: Install Dependencies run: python -m pip install --upgrade pip pip install -r requirements.txt + pip install pytest From 2e9d5b584abb759b6a847634329461be95476dfa Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:49:12 +0100 Subject: [PATCH 149/331] token in pyannote geschrieben --- .github/workflows/pytest.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index c78a91e..0c345db 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -29,6 +29,8 @@ jobs: - name: Run pytest - env: - hf_token: ${{ secrets.HF_TOKEN }} - run: pytest \ No newline at end of file + + run: | + echo "${{ secrets.HF_TOKEN }}" >> scraibe/.pyannotetoken + pytest + \ No newline at end of file From 6f9c0c8cc8034f69fb3ea5efe01bd9b8d3df5609 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 28 Mar 2024 11:55:05 +0100 Subject: [PATCH 150/331] ls --- .github/workflows/pytest.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 0c345db..7d35ee6 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -22,7 +22,8 @@ jobs: python-version: 3.9 - name: Install Dependencies - run: python -m pip install --upgrade pip + run: ls + python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest From d114841425efb3fbf520d51f720c86f23c89001e Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 10:10:52 +0200 Subject: [PATCH 151/331] relativ imports --- test/test_audio.py | 2 +- test/test_autotranscript.py | 12 +++++++----- test/test_diarisation.py | 2 +- test/test_transcriber.py | 10 +++++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/test/test_audio.py b/test/test_audio.py index 6b46c1b..373fde3 100644 --- a/test/test_audio.py +++ b/test/test_audio.py @@ -1,5 +1,5 @@ import pytest -from .audio import AudioProcessor +from ..scraibe.audio import AudioProcessor import torch diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 8f85353..c5e2ace 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -1,14 +1,16 @@ import pytest import torch -from scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor -from unittest.mock import MagicMOck, patch +from ..scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor +from unittest.mock import MagicMock, patch """ @pytest.fixture -def example_audio_file(tmp_path): - audio_path = tmp_path +def example_audio_file(audio_test_2.mp4): + audio_path = audio_test_2.mp4 """ + + @pytest.fixture def create_scraibe_instance(): return Scraibe() @@ -23,7 +25,7 @@ def test_scraibe_init(create_scraibe_instance): def test_scraibe_autotranscribe(create_scraibe_instance, example_audio_file): model = create_scraibe_instance - transcript = example_audio_file + transcript = model.autotranscribe(example_audio_file) assert isinstance(transcript, Transcript) def test_scraibe_diarization(create_scraibe_instance, example_audio_file): diff --git a/test/test_diarisation.py b/test/test_diarisation.py index 1976016..9c287c1 100644 --- a/test/test_diarisation.py +++ b/test/test_diarisation.py @@ -1,7 +1,7 @@ import pytest import os from unittest import mock -from scraibe import Diariser +from ..scraibe import Diariser diff --git a/test/test_transcriber.py b/test/test_transcriber.py index bb08efe..239f3f0 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -1,21 +1,21 @@ import pytest from unittest.mock import patch -from scraibe import Transcriber - +from ..scraibe import Transcriber +""" @pytest.mark.parametrize("audio_file, expected_transcription",[("path_to_test_audiofile", "test_transcription")] ) @patch("scraibe.Transcriber.load_model") def test_transcriber(mock_load_model, audio_file, expected_transcription): - """_summary_ + Args: mock_load_model (_type_): _description_ audio_file (_type_): _description_ expected_transcription (_type_): _description_ - """ + mock_model = mock_load_model.return_value mock_model.transcribe.return_value ={"text": expected_transcription} @@ -23,7 +23,7 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): transcription_result = transcriber.transcribe(audio=audio_file) - assert transcription_result == expected_transcription + assert transcription_result == expected_transcription """ From f499930873eb9dc9a1d1e8a5611d38e3421187e0 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 11:51:54 +0200 Subject: [PATCH 152/331] ls removed --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7d35ee6..8d6a4c2 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -22,7 +22,7 @@ jobs: python-version: 3.9 - name: Install Dependencies - run: ls + run: python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest From 5ee519d466b798ac51419117e56390de480d5255 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 12:11:42 +0200 Subject: [PATCH 153/331] imports --- test/test_audio.py | 2 +- test/test_diarisation.py | 2 +- test/test_transcriber.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_audio.py b/test/test_audio.py index 373fde3..9c39464 100644 --- a/test/test_audio.py +++ b/test/test_audio.py @@ -1,5 +1,5 @@ import pytest -from ..scraibe.audio import AudioProcessor +from scraibe.audio import AudioProcessor import torch diff --git a/test/test_diarisation.py b/test/test_diarisation.py index 9c287c1..1976016 100644 --- a/test/test_diarisation.py +++ b/test/test_diarisation.py @@ -1,7 +1,7 @@ import pytest import os from unittest import mock -from ..scraibe import Diariser +from scraibe import Diariser diff --git a/test/test_transcriber.py b/test/test_transcriber.py index 239f3f0..ef17951 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -1,6 +1,6 @@ import pytest from unittest.mock import patch -from ..scraibe import Transcriber +from scraibe import Transcriber From d458c28a0f6bd6a1a3895a51781f2a9a160ff712 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 13:56:13 +0200 Subject: [PATCH 154/331] setup install --- .github/workflows/pytest.yaml | 1 + test/test_diarisation.py | 4 +- tests/test_autotranscript.py | 120 ---------------------------------- 3 files changed, 3 insertions(+), 122 deletions(-) delete mode 100644 tests/test_autotranscript.py diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8d6a4c2..8988b0b 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,6 +25,7 @@ jobs: run: python -m pip install --upgrade pip pip install -r requirements.txt + python install . pip install pytest diff --git a/test/test_diarisation.py b/test/test_diarisation.py index 1976016..24989b1 100644 --- a/test/test_diarisation.py +++ b/test/test_diarisation.py @@ -1,7 +1,7 @@ import pytest import os from unittest import mock -from scraibe import Diariser +from scraibe import diarisation, Diariser @@ -15,7 +15,7 @@ def diariser_instance(): Returns: Diariser(Obj): An instance of the Diariser class with a mocked token. """ - with mock.patch.object(Diariser, '_get_token', return_value = 'personal Hugging-Face token') + with mock.patch.object(Diariser, '_get_token', return_value = 'HF_TOKEN' ): return Diariser('pyannote') diff --git a/tests/test_autotranscript.py b/tests/test_autotranscript.py deleted file mode 100644 index 475f4de..0000000 --- a/tests/test_autotranscript.py +++ /dev/null @@ -1,120 +0,0 @@ -import pytest -from scraibe import Transcriber -from unittest.mock import patch, mock_open -import os - -def test_load_pyannote_model(): - """ - Test load_pyannote_test - """ - from pyannote.audio.pipelines.speaker_diarization import SpeakerDiarization - from pyannote.audio import Pipeline - - pipeline = Pipeline.from_pretrained("models/pyannote/speaker_diarization/config.yaml") - assert isinstance(pipeline, SpeakerDiarization) - -# Test Transcribtion class - - -@pytest.fixture -def transcriber(): - """ - Prepare Transcriber for testing - Returns: Transcriber Object - """ - - return Transcriber.load_model("medium", local=True) - - -def test_Transcriber_init(transcriber): - """ - Test Transcriber initialization with a whisper model - """ - - assert isinstance(transcriber, Transcriber) - -def test_transcription(transcriber): - """ - Test transcription - """ - - transcript = transcriber.transcribe("tests/test.wav") - assert isinstance(transcript, str) - -def test_save_transcript_to_file(transcriber): - """ - Test save_transcript_to_file - """ - transcript = transcriber.transcribe("tests/test.wav") - - Transcriber.save_transcript(transcript, "tests/output.txt") - - assert os.path.exists("tests/output.txt") - - os.remove("tests/output.txt") - -# Test Diaraization class - -from scraibe import Diariser - -@pytest.fixture -def diarisation(): - """ - Prepare Diarisation for testing - Returns: Diarisation Object - """ - - return Diariser.load_model("models/pyannote/speaker_diarization/config.yaml", local=True) - -def test_Diarisation_init(diarisation): - """ - Test Diarisation initialization with a pyannote model - """ - - assert isinstance(diarisation, Diariser) - -def test_diarisation(diarisation): - """ - Test diarisation - """ - - diarisation = diarisation.diarization("tests/test.wav") - assert isinstance(diarisation, dict) - -# Test AudioProcessor - -from scraibe import AudioProcessor , TorchAudioProcessor - - -def test_AudioProcessor_init(): - """ - Test AudioProcessor initialization - """ - audio = AudioProcessor("tests/test.wav") - assert isinstance(audio, AudioProcessor) - -def test_AudioProcessor_convert(): - """ - Test AudioProcessor convert - """ - audio = AudioProcessor("tests/test.wav") - audio.convert_audio("tests/test.mp3", format="mp3") - assert os.path.exists("tests/test.mp3") - -def test_TorchAudioProcessor_from_file(): - """ - Test TorchAudioProcessor initialization - """ - audio = TorchAudioProcessor.from_file("tests/test.wav") - - assert isinstance(audio, TorchAudioProcessor) - - os.remove("tests/test.mp3") - - -def test_TorchAudioProcessor_from_ffmpeg(): - """ - Test TorchAudioProcessor initialization - """ - audio = TorchAudioProcessor.from_ffmpeg("tests/test.wav") - assert isinstance(audio, TorchAudioProcessor) From d8fa121864932f7ebad68923dfccac1a57f896f9 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 13:59:37 +0200 Subject: [PATCH 155/331] setup.py --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8988b0b..dcd7c65 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,7 +25,7 @@ jobs: run: python -m pip install --upgrade pip pip install -r requirements.txt - python install . + python setup.py install pip install pytest From b8d9195fd0cc288a3305c0c9f99673c34d860912 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:01:21 +0200 Subject: [PATCH 156/331] -r --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index dcd7c65..3f35203 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,7 +25,7 @@ jobs: run: python -m pip install --upgrade pip pip install -r requirements.txt - python setup.py install + python install -r setup.py pip install pytest From 77bfc5e4d0b954565555447b6f1644282fac90b6 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:10:37 +0200 Subject: [PATCH 157/331] pipeline operator --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 3f35203..cfdd25b 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -22,7 +22,7 @@ jobs: python-version: 3.9 - name: Install Dependencies - run: + run: | python -m pip install --upgrade pip pip install -r requirements.txt python install -r setup.py From 4da36230c61a28e35db2a61de7a3776c876ff98f Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:17:14 +0200 Subject: [PATCH 158/331] setup installation --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index cfdd25b..1b25aa9 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,7 +25,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - python install -r setup.py + python -r setup.py install pip install pytest From cb5be106a34eb73427423630b3bc2652d01789e3 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:21:54 +0200 Subject: [PATCH 159/331] pip install setup --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 1b25aa9..5d0b52d 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,7 +25,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - python -r setup.py install + pip install -r setup.py pip install pytest From 45519334e8e78f041827fff8ece94ad9d55bbaa1 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:25:55 +0200 Subject: [PATCH 160/331] pip install . --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 5d0b52d..f674f77 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,7 +25,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install -r setup.py + pip install . pip install pytest From a3a0491db8f25bbd5e595984327cc25d783516c8 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:35:09 +0200 Subject: [PATCH 161/331] libsndfile --- .github/workflows/pytest.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index f674f77..1b5da15 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -25,7 +25,8 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install . + pip install . + apt-get install libsndfile1-dev pip install pytest From 566cd5b470177567fd9ef77a4f066e57aa0f8753 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:39:56 +0200 Subject: [PATCH 162/331] sudo --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 1b5da15..d2ef0fd 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -26,7 +26,7 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . - apt-get install libsndfile1-dev + sudo apt-get install libsndfile1-dev pip install pytest From 2f9f86e9850d8f83da5770413955904355c49ac6 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 2 Apr 2024 14:46:59 +0200 Subject: [PATCH 163/331] removed relative import --- test/test_autotranscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index c5e2ace..b8223c1 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -1,6 +1,6 @@ import pytest import torch -from ..scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor +from scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor from unittest.mock import MagicMock, patch From f8e5c2b3206036e4a3de082b79d2d2aeb603ec4a Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 3 Apr 2024 11:25:00 +0200 Subject: [PATCH 164/331] hf token destination changed --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index d2ef0fd..bf8d16c 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -34,6 +34,6 @@ jobs: - name: Run pytest run: | - echo "${{ secrets.HF_TOKEN }}" >> scraibe/.pyannotetoken + echo "${{ secrets.HF_TOKEN }}" >> /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/scraibe/.pyannotetoken pytest \ No newline at end of file From 2668f6a582228b3e8a9c3c5c8a876ee10f7d966b Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 3 Apr 2024 13:51:04 +0200 Subject: [PATCH 165/331] hf token given as str --- .github/workflows/pytest.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index bf8d16c..5e4b4bb 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -32,8 +32,9 @@ jobs: - name: Run pytest - + env: + HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - echo "${{ secrets.HF_TOKEN }}" >> /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/scraibe/.pyannotetoken - pytest + python -c "import os; print('True' if 'HF_TOKEN' in os.environ else 'False')" + python -c "from scraibe import Scraibe;import os; print(Scraibe(use_auth_token=os.environ.get('HF_TOKEN'), verbose = True).autotranscribe('test/audio_test_1.mp4'))" \ No newline at end of file From 45ae587e9eb4c66d3a18ddbb127698bb174c56ab Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 3 Apr 2024 13:58:00 +0200 Subject: [PATCH 166/331] added ffmpeg --- .github/workflows/pytest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 5e4b4bb..5e97c85 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -27,6 +27,7 @@ jobs: pip install -r requirements.txt pip install . sudo apt-get install libsndfile1-dev + sudo apt-get install ffmpeg pip install pytest From ea8e97348c98757b4140ff40bc1e72239b715ffd Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 3 Apr 2024 14:23:46 +0200 Subject: [PATCH 167/331] upgrade --- .github/workflows/pytest.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 5e97c85..a3ec99f 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -23,6 +23,8 @@ jobs: - name: Install Dependencies run: | + + sudo apt update && sudo apt upgrade python -m pip install --upgrade pip pip install -r requirements.txt pip install . @@ -30,6 +32,7 @@ jobs: sudo apt-get install ffmpeg pip install pytest + - name: Run pytest From 2b4819c68fcc28ade7295674841f39fc8c515ad2 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 3 Apr 2024 16:00:35 +0200 Subject: [PATCH 168/331] run pytest --- .github/workflows/pytest.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index a3ec99f..4826cb1 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,6 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - python -c "import os; print('True' if 'HF_TOKEN' in os.environ else 'False')" - python -c "from scraibe import Scraibe;import os; print(Scraibe(use_auth_token=os.environ.get('HF_TOKEN'), verbose = True).autotranscribe('test/audio_test_1.mp4'))" + pytest \ No newline at end of file From 018a2bd1cf91f6f8356f4dde3f7a4c626ccf0ee2 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 3 Apr 2024 16:10:15 +0200 Subject: [PATCH 169/331] env hf token --- test/test_autotranscript.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index b8223c1..9958265 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -2,7 +2,7 @@ import pytest import torch from scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor from unittest.mock import MagicMock, patch - +import os """ @pytest.fixture @@ -13,7 +13,11 @@ def example_audio_file(audio_test_2.mp4): @pytest.fixture def create_scraibe_instance(): - return Scraibe() + if "HF_TOKEN" in os.environ: + return Scraibe(use_auth_token=os.environ["HF_TOKEN"] ) + else: + return Scraibe() + From c5e44ecb183ba56917773e130116a7a06f2d3c22 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 8 Apr 2024 12:48:25 +0200 Subject: [PATCH 170/331] audio.py --- test/test_audio.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/test_audio.py b/test/test_audio.py index 9c39464..311a472 100644 --- a/test/test_audio.py +++ b/test/test_audio.py @@ -5,7 +5,7 @@ import torch DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") -TEST_WAVEFORM = torch.tensor([]).to(DEVICE) +TEST_WAVEFORM = torch.sin(torch.randn(160000)).to(DEVICE) TEST_SR = 16000 SAMPLE_RATE = 16000 NORMALIZATION_FACTOR = 32768 @@ -54,7 +54,7 @@ def test_AudioProcessor_init(probe_audio_processor): -def test_cut(): +def test_cut(probe_audio_processor): """Test the cut function of the AudioProcessor class. This test verifies that the cut function correctly extracts a segment of audio data from @@ -69,7 +69,11 @@ def test_cut(): start = 4 end = 7 - assert AudioProcessor(TEST_WAVEFORM, TEST_SR).cut(start, end).size() == int((end - start) * TEST_SR) + trimmed_waveform = probe_audio_processor.cut(start, end) + expected_size = int((end - start) * TEST_SR) + real_size = trimmed_waveform.size(0) + assert real_size == expected_size + #assert AudioProcessor(TEST_WAVEFORM, TEST_SR).cut(start, end).size() == int((end - start) * TEST_SR) From ccdb12597e3ae0b9daba84486fb98c5f228fb015 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 8 Apr 2024 12:55:45 +0200 Subject: [PATCH 171/331] only audiotest --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 4826cb1..7ebf890 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest + pytest test/test_autotranscript.py \ No newline at end of file From eabd5734c246195615ed4123a7c48eb0b9e87d81 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 8 Apr 2024 13:05:26 +0200 Subject: [PATCH 172/331] audiotest 2nd --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7ebf890..feab65e 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_autotranscript.py + pytest test/test_audio.py \ No newline at end of file From c55c12db03c6489b7958e18a19b5bd5c2c8c7e36 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 8 Apr 2024 13:23:43 +0200 Subject: [PATCH 173/331] only test autotranscript.py --- .github/workflows/pytest.yaml | 2 +- test/test_autotranscript.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index feab65e..60b1d94 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_audio.py + pytest test/test_autotrnscript.py \ No newline at end of file diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 9958265..8fa53f5 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -11,6 +11,8 @@ def example_audio_file(audio_test_2.mp4): """ + + @pytest.fixture def create_scraibe_instance(): if "HF_TOKEN" in os.environ: @@ -27,7 +29,7 @@ def test_scraibe_init(create_scraibe_instance): assert isinstance(model.diariser, Diariser) -def test_scraibe_autotranscribe(create_scraibe_instance, example_audio_file): +""" def test_scraibe_autotranscribe(create_scraibe_instance, example_audio_file): model = create_scraibe_instance transcript = model.autotranscribe(example_audio_file) assert isinstance(transcript, Transcript) @@ -58,4 +60,4 @@ def test_get_audio_file(create_scraibe_instance, example_audio_file): audio_file = os.path.exist(example_audio_file) assert isinstance(audio_file, AudioProcessor) assert isinstance(audio_file.waveform, torch.Tensor) - assert isinstance(audio_file.sr, torch.Tensor) + assert isinstance(audio_file.sr, torch.Tensor) """ From 6663357b3739f16ee9735b35975dab12fa9a22a4 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 8 Apr 2024 13:29:39 +0200 Subject: [PATCH 174/331] spelling error autotranscript test --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 60b1d94..7ebf890 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_autotrnscript.py + pytest test/test_autotranscript.py \ No newline at end of file From a5fb4c96703e0654c334354740d3374e8ed77a26 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Mon, 8 Apr 2024 16:41:29 +0200 Subject: [PATCH 175/331] only test diarisation2.py --- .github/workflows/pytest.yaml | 2 +- test/test_diarisation2.py | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/test_diarisation2.py diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7ebf890..da3e1a4 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_autotranscript.py + pytest test/test_diarisation2.py \ No newline at end of file diff --git a/test/test_diarisation2.py b/test/test_diarisation2.py new file mode 100644 index 0000000..55518ef --- /dev/null +++ b/test/test_diarisation2.py @@ -0,0 +1,37 @@ +import pytest +import os +import unittest +from unittest import mock +from scraibe import Diariser +import torch + + + + + +@pytest.fixture +def diariser_instance(): + return Diariser('pyannote') + + + +def test_diariser_init(diariser_instance): + assert diariser_instance.model == 'pyannote' + + + +""" def test_format_diarization_output(): + dialogue = [("speaker1", "segment1"),("speaker2", "segment2"), ("speaker1","segment3")] + formatted_output = Diariser.format_diarization_output(dialogue) + assert formatted_output == {"speakers": ["speaker1", "speaker2", "speaker1"], "segments": ["segment1", "segment2", "segment3"]} """ + + +def test_get_diarisation_kwargs(): + kwargs = {"arg1": 1, "arg3": 3} + valid_kwargs = Diariser._get_diarisation_kwargs(**kwargs) + assert not valid_kwargs == {"arg1": 1, "arg3": 3} + + + + + From 328f7d4a0f1b36b3da93777eeafa243085fdcfb5 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 9 Apr 2024 10:00:11 +0200 Subject: [PATCH 176/331] test transcriber function --- .github/workflows/pytest.yaml | 2 +- test/test_transcriber.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index da3e1a4..7591ae5 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_diarisation2.py + pytest test/test_transcriber.py \ No newline at end of file diff --git a/test/test_transcriber.py b/test/test_transcriber.py index ef17951..68ff854 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -1,9 +1,13 @@ import pytest from unittest.mock import patch from scraibe import Transcriber +import torch +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") +TEST_WAVEFORM = "Hello World" + """ @pytest.mark.parametrize("audio_file, expected_transcription",[("path_to_test_audiofile", "test_transcription")] ) @patch("scraibe.Transcriber.load_model") @@ -25,5 +29,23 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): assert transcription_result == expected_transcription """ +@pytest.fixture +def transcriber_instance(): + return Transcriber('medium') + +def test_transcriber_initialization(transcriber_instance): + assert transcriber_instance.model == 'medium' + +""" def test_get_whisper_kwargs(): + kwargs = {"arg1": 1, "arg3": 3} + valid_kwargs = Transcriber._get_diarisation_kwargs(**kwargs) + assert not valid_kwargs == {"arg1": 1, "arg3": 3} """ + + +""" def test_transcribe(transcriber_instance, TEST_WAVEFORM): + mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) + transcript = transcriber_instance.transcribe("Hello, World") + assert isinstance(transcript, str) """ + From f03069b9d2186745d7f656493721e54cb3e2a625 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 9 Apr 2024 10:22:27 +0200 Subject: [PATCH 177/331] use all tests --- .github/workflows/pytest.yaml | 2 +- test/test_diarisation2.py | 37 ----------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 test/test_diarisation2.py diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7591ae5..4826cb1 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_transcriber.py + pytest \ No newline at end of file diff --git a/test/test_diarisation2.py b/test/test_diarisation2.py deleted file mode 100644 index 55518ef..0000000 --- a/test/test_diarisation2.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest -import os -import unittest -from unittest import mock -from scraibe import Diariser -import torch - - - - - -@pytest.fixture -def diariser_instance(): - return Diariser('pyannote') - - - -def test_diariser_init(diariser_instance): - assert diariser_instance.model == 'pyannote' - - - -""" def test_format_diarization_output(): - dialogue = [("speaker1", "segment1"),("speaker2", "segment2"), ("speaker1","segment3")] - formatted_output = Diariser.format_diarization_output(dialogue) - assert formatted_output == {"speakers": ["speaker1", "speaker2", "speaker1"], "segments": ["segment1", "segment2", "segment3"]} """ - - -def test_get_diarisation_kwargs(): - kwargs = {"arg1": 1, "arg3": 3} - valid_kwargs = Diariser._get_diarisation_kwargs(**kwargs) - assert not valid_kwargs == {"arg1": 1, "arg3": 3} - - - - - From d1f36ebdc6f286def490e9489c5f7653bbe062b3 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Tue, 9 Apr 2024 12:06:30 +0200 Subject: [PATCH 178/331] fixed diarisation test --- test/test_diarisation.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/test/test_diarisation.py b/test/test_diarisation.py index 24989b1..d1d26f3 100644 --- a/test/test_diarisation.py +++ b/test/test_diarisation.py @@ -15,8 +15,8 @@ def diariser_instance(): Returns: Diariser(Obj): An instance of the Diariser class with a mocked token. """ - with mock.patch.object(Diariser, '_get_token', return_value = 'HF_TOKEN' ): - return Diariser('pyannote') + #with mock.patch.object(Diariser, '_get_token', return_value = 'HF_TOKEN' ): + return Diariser('pyannote') @@ -37,25 +37,11 @@ def test_Diariser_init(diariser_instance): -def test_diarisation_function(diariser_instance): - """Test the diarization function of the Diariser class. - - This test verifies that the diarization function of the Diariser class correctly processes - an audio file and returns the diarization result. It patches the apply method of the model - attribute of the Diariser instance using unittest.mock.patch.object, ensuring that it returns - a predetermined value ('diarization_result') when called with the audio file argument. - It then calls the diarization function with an example audio file and checks whether the returned - diarization output matches the expected result ('diarization_result'). - - Args: - diariser_instance (obj): instance of the Diariser object - - Returns: - None - """ - with mock.patch.object(diariser_instance.model, 'apply', return_value='diarization_result'): - diarization_output = diariser_instance.diarization('example_audio_file.wav') - assert diarization_output == 'diarization_result' + + + + + From ac9d34136c68d05897c8a42bf4bafd657e0eb9f6 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Wed, 10 Apr 2024 16:13:29 +0200 Subject: [PATCH 179/331] added 3 tests --- test/test_autotranscript.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 8fa53f5..8864c5f 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -29,35 +29,36 @@ def test_scraibe_init(create_scraibe_instance): assert isinstance(model.diariser, Diariser) -""" def test_scraibe_autotranscribe(create_scraibe_instance, example_audio_file): +def test_scraibe_autotranscribe(create_scraibe_instance): model = create_scraibe_instance - transcript = model.autotranscribe(example_audio_file) + transcript = model.autotranscribe("audio_test_2.mp4") assert isinstance(transcript, Transcript) -def test_scraibe_diarization(create_scraibe_instance, example_audio_file): + +def test_scraibe_diarization(create_scraibe_instance): model = create_scraibe_instance - diarisation_result = model.diarisation(example_audio_file) + diarisation_result = model.diarization("audio_test_2.mp4") assert isinstance(diarisation_result, dict) -def test_scraibe_transcribe(create_scraibe_instance, example_audio_file): +def test_scraibe_transcribe(create_scraibe_instance): model = create_scraibe_instance - transcription_result = model.transcribe(example_audio_file) + transcription_result = model.transcribe("audio_test_2.mp4") assert isinstance(transcription_result, str) -def test_remove_audio_file(create_scraibe_instance, example_audio_file): +""" def test_remove_audio_file(create_scraibe_instance): model = create_scraibe_instance with pytest.raises(ValueError): model.remove_audio_file("non_existing_audio_file") - model.remove_audio_file(example_audio_file) - assert not os.path.exists(example_audio_file) + model.remove_audio_file("audio_test_2.mp4") + assert not os.path.exists("audio_test_2.mp4") """ -def test_get_audio_file(create_scraibe_instance, example_audio_file): +""" def test_get_audio_file(create_scraibe_instance): model = create_scraibe_instance - audio_file = os.path.exist(example_audio_file) + audio_file = os.path.exist("audio_test_2.mp4") assert isinstance(audio_file, AudioProcessor) assert isinstance(audio_file.waveform, torch.Tensor) - assert isinstance(audio_file.sr, torch.Tensor) """ + assert isinstance(audio_file.sr, torch.Tensor) """ From fb0c7a9fa6d8b7601f8160e4bcbb8a5cefb1c4e3 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 11 Apr 2024 09:58:16 +0200 Subject: [PATCH 180/331] test if file gets found --- .github/workflows/pytest.yaml | 2 +- test/test_autotranscript.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 4826cb1..7ebf890 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest + pytest test/test_autotranscript.py \ No newline at end of file diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 8864c5f..01a883c 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -3,6 +3,10 @@ import torch from scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor from unittest.mock import MagicMock, patch import os +import imp + + + """ @pytest.fixture @@ -31,13 +35,13 @@ def test_scraibe_init(create_scraibe_instance): def test_scraibe_autotranscribe(create_scraibe_instance): model = create_scraibe_instance - transcript = model.autotranscribe("audio_test_2.mp4") + transcript = model.autotranscribe(util) assert isinstance(transcript, Transcript) def test_scraibe_diarization(create_scraibe_instance): model = create_scraibe_instance - diarisation_result = model.diarization("audio_test_2.mp4") + diarisation_result = model.diarization('test/audio_test_2.mp4') assert isinstance(diarisation_result, dict) From 2844ecd628986fc8d27d5d25c8ddd376afade542 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Thu, 11 Apr 2024 10:06:05 +0200 Subject: [PATCH 181/331] changed file parth --- test/test_autotranscript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 01a883c..4ff74a2 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -35,7 +35,7 @@ def test_scraibe_init(create_scraibe_instance): def test_scraibe_autotranscribe(create_scraibe_instance): model = create_scraibe_instance - transcript = model.autotranscribe(util) + transcript = model.autotranscribe('test/audio_test_2.mp4') assert isinstance(transcript, Transcript) @@ -47,7 +47,7 @@ def test_scraibe_diarization(create_scraibe_instance): def test_scraibe_transcribe(create_scraibe_instance): model = create_scraibe_instance - transcription_result = model.transcribe("audio_test_2.mp4") + transcription_result = model.transcribe('test/audio_test_2.mp4') assert isinstance(transcription_result, str) From b4576abc0027288124d2cb2183b4eb3313fc6908 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 08:59:27 +0200 Subject: [PATCH 182/331] transcriber class tests --- test/test_transcriber.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/test_transcriber.py b/test/test_transcriber.py index 68ff854..406345b 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -31,21 +31,22 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): @pytest.fixture def transcriber_instance(): - return Transcriber('medium') + return Transcriber.load_model('medium') def test_transcriber_initialization(transcriber_instance): - assert transcriber_instance.model == 'medium' + assert isinstance(transcriber_instance, Transcriber) -""" def test_get_whisper_kwargs(): +def test_get_whisper_kwargs(): kwargs = {"arg1": 1, "arg3": 3} - valid_kwargs = Transcriber._get_diarisation_kwargs(**kwargs) - assert not valid_kwargs == {"arg1": 1, "arg3": 3} """ + valid_kwargs = Transcriber._get_whisper_kwargs(**kwargs) + assert not valid_kwargs == {"arg1": 1, "arg3": 3} -""" def test_transcribe(transcriber_instance, TEST_WAVEFORM): - mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) - transcript = transcriber_instance.transcribe("Hello, World") - assert isinstance(transcript, str) """ +def test_transcribe(transcriber_instance): + model = transcriber_instance + #mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) + transcript = model.transcribe('audio_test_2.mp4') + assert isinstance(transcript, str) From 6144e657f8699b244b954028b12f6e18d5b636fd Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 09:10:09 +0200 Subject: [PATCH 183/331] test transcriber --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7ebf890..7591ae5 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_autotranscript.py + pytest test/test_transcriber.py \ No newline at end of file From 9340b83d02a5897e263777a53a1f79e193e2f4bf Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 09:17:51 +0200 Subject: [PATCH 184/331] fixed file parth for test file --- test/test_transcriber.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_transcriber.py b/test/test_transcriber.py index 406345b..3a4a0dc 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -45,7 +45,7 @@ def test_get_whisper_kwargs(): def test_transcribe(transcriber_instance): model = transcriber_instance #mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) - transcript = model.transcribe('audio_test_2.mp4') + transcript = model.transcribe('test/audio_test_2.mp4') assert isinstance(transcript, str) From 54bedc53762581db3c33f7fbb5343539758cba85 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 09:37:01 +0200 Subject: [PATCH 185/331] removed unneccesary imports --- test/test_autotranscript.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 4ff74a2..05c1aa3 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -1,21 +1,11 @@ import pytest -import torch -from scraibe import Scraibe, Diariser, Transcriber, Transcript, AudioProcessor +from scraibe import Scraibe, Diariser, Transcriber, Transcript from unittest.mock import MagicMock, patch import os -import imp -""" -@pytest.fixture -def example_audio_file(audio_test_2.mp4): - audio_path = audio_test_2.mp4 -""" - - - @pytest.fixture def create_scraibe_instance(): @@ -35,7 +25,7 @@ def test_scraibe_init(create_scraibe_instance): def test_scraibe_autotranscribe(create_scraibe_instance): model = create_scraibe_instance - transcript = model.autotranscribe('test/audio_test_2.mp4') + transcript = model.autotranscribe('test/audio_test.mp4') assert isinstance(transcript, Transcript) From 1e86245c82abeaa183f40e77da2468f79695f6f1 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 09:49:14 +0200 Subject: [PATCH 186/331] use all tests --- .github/workflows/pytest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 7591ae5..b691fe1 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -39,5 +39,5 @@ jobs: env: HF_TOKEN : ${{ secrets.HF_TOKEN }} run: | - pytest test/test_transcriber.py + pytest \ No newline at end of file From 559c2aa715b803f6d8e127dac5d73217a4c8e39a Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 10:00:06 +0200 Subject: [PATCH 187/331] fixed audio file path --- test/test_autotranscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index 05c1aa3..edbe0f7 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -25,7 +25,7 @@ def test_scraibe_init(create_scraibe_instance): def test_scraibe_autotranscribe(create_scraibe_instance): model = create_scraibe_instance - transcript = model.autotranscribe('test/audio_test.mp4') + transcript = model.autotranscribe('test/audio_test_2.mp4') assert isinstance(transcript, Transcript) From 90a90f94dc9d0921de838efc765148c15d22b958 Mon Sep 17 00:00:00 2001 From: Tryndaron Date: Fri, 12 Apr 2024 10:50:47 +0200 Subject: [PATCH 188/331] changed trigger of ci to pull-requests only --- .github/workflows/pytest.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index b691fe1..8959789 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,10 +1,10 @@ name: Run tests on: - push: + #push: - #pull_request: - #branches: ['main', 'develop'] + pull_request: + branches: ['main', 'develop'] workflow_dispatch: From f7927fd524bd6a6d7527d18dd1ac5013c0412f01 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Fri, 19 Apr 2024 17:36:34 +0200 Subject: [PATCH 189/331] Add default path to pyannote model with fallback option. --- scraibe/diarisation.py | 100 +++++++++++++++++++++++------------------ scraibe/misc.py | 3 +- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index 0f0e14a..161dae5 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -19,6 +19,7 @@ Constants: - TOKEN_PATH (str): Path to the Pyannote token. - PYANNOTE_DEFAULT_PATH (str): Default path to Pyannote models. - PYANNOTE_DEFAULT_CONFIG (str): Default configuration for Pyannote models. +- PYANNOTE_FALLBACK_CONFIG (str): Fallback config for Pyannote models if default config does not work. Usage: from .diarisation import Diariser @@ -39,7 +40,7 @@ from torch import Tensor from torch import device as torch_device from torch.cuda import is_available, current_device -from .misc import PYANNOTE_DEFAULT_PATH, PYANNOTE_DEFAULT_CONFIG +from .misc import PYANNOTE_DEFAULT_PATH, PYANNOTE_DEFAULT_CONFIG, PYANNOTE_FALLBACK_CONFIG Annotation = TypeVar('Annotation') TOKEN_PATH = os.path.join(os.path.dirname( @@ -183,7 +184,7 @@ class Diariser: @classmethod def load_model(cls, - model: str = PYANNOTE_DEFAULT_CONFIG, + model: str = PYANNOTE_FALLBACK_CONFIG, use_auth_token: str = None, cache_token: bool = True, cache_dir: Union[Path, str] = PYANNOTE_DEFAULT_PATH, @@ -210,53 +211,64 @@ class Diariser: Returns: Pipeline: A pyannote.audio Pipeline object, encapsulating the loaded model. """ + try: + hf_model = PYANNOTE_DEFAULT_CONFIG + # if not use_auth_token: + # use_auth_token = cls._get_token() + _model = Pipeline.from_pretrained( + hf_model, use_auth_token=use_auth_token, + cache_dir=cache_dir, hparams_file=hparams_file + ) + except: + print(f'Trying fallback to config file.. ') + _model = None + if _model is None: - - if cache_token and use_auth_token is not None: - cls._save_token(use_auth_token) - - if not os.path.exists(model) and use_auth_token is None: - use_auth_token = cls._get_token() - - elif os.path.exists(model) and not use_auth_token: - # check if model can be found locally nearby the config file - with open(model, 'r') as file: - config = yaml.safe_load(file) - - path_to_model = config['pipeline']['params']['segmentation'] - - if not os.path.exists(path_to_model): - warnings.warn(f"Model not found at {path_to_model}. "\ - "Trying to find it nearby the config file.") + if cache_token and use_auth_token is not None: + cls._save_token(use_auth_token) - pwd = model.split("/")[:-1] - pwd = "/".join(pwd) + if not os.path.exists(model) and use_auth_token is None: + use_auth_token = cls._get_token() - path_to_model = os.path.join(pwd, "pytorch_model.bin") + elif os.path.exists(model) and not use_auth_token: + # check if model can be found locally nearby the config file + with open(model, 'r') as file: + config = yaml.safe_load(file) + + path_to_model = config['pipeline']['params']['segmentation'] if not os.path.exists(path_to_model): - warnings.warn(f"Model not found at {path_to_model}. \ - 'Trying to find it nearby .bin files instead.") - # list elementes with the ending .bin - bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] - if len(bin_files) == 1: - path_to_model = os.path.join(pwd, bin_files[0]) - else: - warnings.warn("Found more than one .bin file. "\ - "or none. Please specify the path to the model " \ - "or setup a huggingface token.") - - warnings.warn(f"Found model at {path_to_model} overwriting config file.") - - config['pipeline']['params']['segmentation'] = path_to_model - - with open(model, 'w') as file: - yaml.dump(config, file) - - _model = Pipeline.from_pretrained(model, - use_auth_token = use_auth_token, - cache_dir = cache_dir, - hparams_file = hparams_file,) + warnings.warn(f"Model not found at {path_to_model}. "\ + "Trying to find it nearby the config file.") + + pwd = model.split("/")[:-1] + pwd = "/".join(pwd) + + path_to_model = os.path.join(pwd, "pytorch_model.bin") + + if not os.path.exists(path_to_model): + warnings.warn(f"Model not found at {path_to_model}. \ + 'Trying to find it nearby .bin files instead.") + # list elementes with the ending .bin + bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] + if len(bin_files) == 1: + path_to_model = os.path.join(pwd, bin_files[0]) + else: + warnings.warn("Found more than one .bin file. "\ + "or none. Please specify the path to the model " \ + "or setup a huggingface token.") + + warnings.warn(f"Found model at {path_to_model} overwriting config file.") + + config['pipeline']['params']['segmentation'] = path_to_model + + with open(model, 'w') as file: + yaml.dump(config, file) + + _model = Pipeline.from_pretrained(model, + use_auth_token = use_auth_token, + cache_dir = cache_dir, + hparams_file = hparams_file,) # try to move the model to the device if device is None: diff --git a/scraibe/misc.py b/scraibe/misc.py index 992e40c..549ee67 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -13,7 +13,8 @@ if CACHE_DIR != PYANNOTE_CACHE_DIR: WHISPER_DEFAULT_PATH = os.path.join(CACHE_DIR, "whisper") PYANNOTE_DEFAULT_PATH = os.path.join(CACHE_DIR, "pyannote") -PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ +PYANNOTE_DEFAULT_CONFIG = 'jaikinator/scraibe' +PYANNOTE_FALLBACK_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ if os.path.exists(os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml")) \ else 'pyannote/speaker-diarization-3.1' From 7d8da3b81c31e5c53bbf7049862638ec6452af2e Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Tue, 23 Apr 2024 14:39:18 +0200 Subject: [PATCH 190/331] Reworking the hf wrapper, now without blank except block (wow)! --- scraibe/diarisation.py | 118 +++++++++++++++++++++-------------------- scraibe/misc.py | 5 +- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index 161dae5..8523940 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -19,7 +19,6 @@ Constants: - TOKEN_PATH (str): Path to the Pyannote token. - PYANNOTE_DEFAULT_PATH (str): Default path to Pyannote models. - PYANNOTE_DEFAULT_CONFIG (str): Default configuration for Pyannote models. -- PYANNOTE_FALLBACK_CONFIG (str): Fallback config for Pyannote models if default config does not work. Usage: from .diarisation import Diariser @@ -39,8 +38,10 @@ from pyannote.audio.pipelines.speaker_diarization import SpeakerDiarization from torch import Tensor from torch import device as torch_device from torch.cuda import is_available, current_device +from huggingface_hub import HfApi +from huggingface_hub.utils import RepositoryNotFoundError -from .misc import PYANNOTE_DEFAULT_PATH, PYANNOTE_DEFAULT_CONFIG, PYANNOTE_FALLBACK_CONFIG +from .misc import PYANNOTE_DEFAULT_PATH, PYANNOTE_DEFAULT_CONFIG Annotation = TypeVar('Annotation') TOKEN_PATH = os.path.join(os.path.dirname( @@ -184,7 +185,7 @@ class Diariser: @classmethod def load_model(cls, - model: str = PYANNOTE_FALLBACK_CONFIG, + model: str = PYANNOTE_DEFAULT_CONFIG, use_auth_token: str = None, cache_token: bool = True, cache_dir: Union[Path, str] = PYANNOTE_DEFAULT_PATH, @@ -211,64 +212,65 @@ class Diariser: Returns: Pipeline: A pyannote.audio Pipeline object, encapsulating the loaded model. """ - try: - hf_model = PYANNOTE_DEFAULT_CONFIG - # if not use_auth_token: - # use_auth_token = cls._get_token() - _model = Pipeline.from_pretrained( - hf_model, use_auth_token=use_auth_token, - cache_dir=cache_dir, hparams_file=hparams_file - ) - except: - print(f'Trying fallback to config file.. ') - _model = None - if _model is None: - - if cache_token and use_auth_token is not None: - cls._save_token(use_auth_token) - - if not os.path.exists(model) and use_auth_token is None: - use_auth_token = cls._get_token() - - elif os.path.exists(model) and not use_auth_token: - # check if model can be found locally nearby the config file - with open(model, 'r') as file: - config = yaml.safe_load(file) - - path_to_model = config['pipeline']['params']['segmentation'] + if isinstance(model, str) and os.path.exists(model): + # check if model can be found locally nearby the config file + with open(model, 'r') as file: + config = yaml.safe_load(file) + + path_to_model = config['pipeline']['params']['segmentation'] + + if not os.path.exists(path_to_model): + warnings.warn(f"Model not found at {path_to_model}. " + "Trying to find it nearby the config file.") + + pwd = model.split("/")[:-1] + pwd = "/".join(pwd) + + path_to_model = os.path.join(pwd, "pytorch_model.bin") if not os.path.exists(path_to_model): - warnings.warn(f"Model not found at {path_to_model}. "\ - "Trying to find it nearby the config file.") - - pwd = model.split("/")[:-1] - pwd = "/".join(pwd) - - path_to_model = os.path.join(pwd, "pytorch_model.bin") + warnings.warn(f"Model not found at {path_to_model}. \ + 'Trying to find it nearby .bin files instead.") + # list elementes with the ending .bin + bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] + if len(bin_files) == 1: + path_to_model = os.path.join(pwd, bin_files[0]) + else: + warnings.warn("Found more than one .bin file. "\ + "or none. Please specify the path to the model " \ + "or setup a huggingface token.") + raise FileNotFoundError - if not os.path.exists(path_to_model): - warnings.warn(f"Model not found at {path_to_model}. \ - 'Trying to find it nearby .bin files instead.") - # list elementes with the ending .bin - bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] - if len(bin_files) == 1: - path_to_model = os.path.join(pwd, bin_files[0]) - else: - warnings.warn("Found more than one .bin file. "\ - "or none. Please specify the path to the model " \ - "or setup a huggingface token.") - - warnings.warn(f"Found model at {path_to_model} overwriting config file.") - - config['pipeline']['params']['segmentation'] = path_to_model - - with open(model, 'w') as file: - yaml.dump(config, file) - - _model = Pipeline.from_pretrained(model, - use_auth_token = use_auth_token, - cache_dir = cache_dir, - hparams_file = hparams_file,) + warnings.warn(f"Found model at {path_to_model} overwriting config file.") + + config['pipeline']['params']['segmentation'] = path_to_model + + with open(model, 'w') as file: + yaml.dump(config, file) + elif isinstance(model, tuple): + try: + _model = model[0] + HfApi().model_info(_model) + model = _model + use_auth_token = None + except RepositoryNotFoundError: + print(f'{model[0]} not found on Huggingface, \ + trying {model[1]}') + _model = model[1] + HfApi().model_info(_model) + model = _model + if cache_token and use_auth_token is not None: + cls._save_token(use_auth_token) + + if not os.path.exists(model) and use_auth_token is None: + use_auth_token = cls._get_token() + else: + raise FileNotFoundError(f'No local model or directory found at {model}.') + + _model = Pipeline.from_pretrained(model, + use_auth_token=use_auth_token, + cache_dir=cache_dir, + hparams_file=hparams_file,) # try to move the model to the device if device is None: diff --git a/scraibe/misc.py b/scraibe/misc.py index 549ee67..c1d5484 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -13,10 +13,9 @@ if CACHE_DIR != PYANNOTE_CACHE_DIR: WHISPER_DEFAULT_PATH = os.path.join(CACHE_DIR, "whisper") PYANNOTE_DEFAULT_PATH = os.path.join(CACHE_DIR, "pyannote") -PYANNOTE_DEFAULT_CONFIG = 'jaikinator/scraibe' -PYANNOTE_FALLBACK_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ +PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ if os.path.exists(os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml")) \ - else 'pyannote/speaker-diarization-3.1' + else ('jaikinator/scraibe', 'pyannote/speaker-diarization-3.1') def config_diarization_yaml(file_path: str, path_to_segmentation: str = None) -> None: """Configure diarization pipeline from a YAML file. From 55a77b861cf40e5354e719e511ebe0e6977fe212 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Tue, 23 Apr 2024 16:29:48 +0200 Subject: [PATCH 191/331] Fixed cache default value, moved ValuError t othe right place, added to docstring. --- scraibe/diarisation.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index 8523940..1643db2 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -187,7 +187,7 @@ class Diariser: def load_model(cls, model: str = PYANNOTE_DEFAULT_CONFIG, use_auth_token: str = None, - cache_token: bool = True, + cache_token: bool = False, cache_dir: Union[Path, str] = PYANNOTE_DEFAULT_PATH, hparams_file: Union[str, Path] = None, device: str = None, @@ -196,11 +196,12 @@ class Diariser: """ Loads a pretrained model from pyannote.audio, - either from a local cache or online repository. + either from a local cache or some online repository. Args: model: Path or identifier for the pyannote model. - default: /models/pyannote/speaker_diarization/config.yaml + default: '/home/[user]/.cache/torch/models/pyannote/config.yaml' + or one of 'jaikinator/scraibe', 'pyannote/speaker-diarization-3.1' token: Optional HUGGINGFACE_TOKEN for authenticated access. cache_token: Whether to cache the token locally for future use. cache_dir: Directory for caching models. @@ -261,8 +262,8 @@ class Diariser: model = _model if cache_token and use_auth_token is not None: cls._save_token(use_auth_token) - - if not os.path.exists(model) and use_auth_token is None: + + if use_auth_token is None: use_auth_token = cls._get_token() else: raise FileNotFoundError(f'No local model or directory found at {model}.') @@ -271,18 +272,17 @@ class Diariser: use_auth_token=use_auth_token, cache_dir=cache_dir, hparams_file=hparams_file,) - - # try to move the model to the device - if device is None: - device = "cuda" if is_available() else "cpu" - - _model = _model.to(torch_device(device)) # torch_device is renamed from torch.device to avoid name conflict - if _model is None: raise ValueError('Unable to load model either from local cache' \ 'or from huggingface.co models. Please check your token' \ 'or your local model path') - + + # try to move the model to the device + if device is None: + device = "cuda" if is_available() else "cpu" + + _model = _model.to(torch_device(device)) # torch_device is renamed from torch.device to avoid name conflict + return cls(_model) @staticmethod From 7171b02d640fda3f2196dde0411bcf6a2b3dcf52 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 24 Apr 2024 13:20:38 +0000 Subject: [PATCH 192/331] update gitignore --- .gitignore | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/.gitignore b/.gitignore index 18c7986..65fa59f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,239 @@ scraibe/app/*__pycache__ scraibe/.pyannotetoken +# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,linux,windows +# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode,linux,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,linux,windows From 050555556dcef20acb30f37115d4ad99990eb461 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 24 Apr 2024 13:30:00 +0000 Subject: [PATCH 193/331] updated transcriber --- scraibe/transcriber.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index dbb290e..649330b 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -64,14 +64,17 @@ class Transcriber: The class supports various sizes and versions of Whisper models. Please refer to the load_model method for available options. """ - def __init__(self, model: whisper ) -> None: + def __init__(self, model: whisper , model_name: str ) -> None: """ Initialize the Transcriber class with a Whisper model. Args: model (whisper): The Whisper model to use for transcription. """ + self.model = model + + self.model_name = model_name def transcribe(self, audio : Union[str, Tensor, ndarray] , *args, **kwargs) -> str: @@ -156,7 +159,7 @@ class Transcriber: _model = load_model(model, download_root=download_root, device=device, in_memory=in_memory) - return cls(_model) + return cls(_model, model_name=model) @staticmethod def _get_whisper_kwargs(**kwargs) -> dict: @@ -179,4 +182,4 @@ class Transcriber: return whisper_kwargs def __repr__(self) -> str: - return f"Transcriber(model={self.model})" \ No newline at end of file + return f"Transcriber(model_name={self.model_name}, model={self.model})" \ No newline at end of file From dbb5f20b94ef98ca3a77bee5fd27ecfb796ab399 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 24 Apr 2024 13:30:38 +0000 Subject: [PATCH 194/331] added docsting --- scraibe/transcriber.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index 649330b..e8e5b2c 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -70,6 +70,7 @@ class Transcriber: Args: model (whisper): The Whisper model to use for transcription. + model_name (str): The name of the model. """ self.model = model From 8f58aeb04e6380c15de80a2305ac8cae4d79611d Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 24 Apr 2024 13:31:15 +0000 Subject: [PATCH 195/331] added new whisper large-v3 to docstring --- scraibe/transcriber.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index e8e5b2c..910ea59 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -141,6 +141,7 @@ class Transcriber: - 'medium' - 'large-v1' - 'large-v2' + - 'large-v3' - 'large' download_root (str, optional): Path to download the model. From 06665482c6d7bee51c7cc7002659568a0948a02b Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 24 Apr 2024 13:43:53 +0000 Subject: [PATCH 196/331] added update functions for transcriber and diariser + adding some type hints --- scraibe/autotranscript.py | 57 +++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/scraibe/autotranscript.py b/scraibe/autotranscript.py index 2664e3f..7d54ba8 100644 --- a/scraibe/autotranscript.py +++ b/scraibe/autotranscript.py @@ -95,7 +95,7 @@ class Scraibe: elif isinstance(dia_model, str): self.diariser = Diariser.load_model(dia_model, **kwargs) else: - self.diariser = dia_model + self.diariser : Diariser = dia_model if kwargs.get("verbose"): print("Scraibe initialized all models successfully loaded.") @@ -133,7 +133,7 @@ class Scraibe: if kwargs.get("verbose"): self.verbose = kwargs.get("verbose") # Get audio file as an AudioProcessor object - audio_file = self.get_audio_file(audio_file) + audio_file : AudioProcessor = self.get_audio_file(audio_file) # Prepare waveform and sample rate for diarization dia_audio = { @@ -203,7 +203,7 @@ class Scraibe: """ # Get audio file as an AudioProcessor object - audio_file = self.get_audio_file(audio_file) + audio_file : AudioProcessor = self.get_audio_file(audio_file) # Prepare waveform and sample rate for diarization dia_audio = { @@ -232,9 +232,56 @@ class Scraibe: str: The transcribed text from the audio source. """ - audio_file = self.get_audio_file(audio_file) + audio_file : AudioProcessor = self.get_audio_file(audio_file) return self.transcriber.transcribe(audio_file.waveform, **kwargs) + + def update_transcriber(self, whisper_model : Union[str, whisper], **kwargs) -> None: + """ + Update the transcriber model. + + Args: + whisper_model (Union[str, whisper]): + The new whisper model to use for transcription. + **kwargs: + Additional keyword arguments for the transcriber model. + + Returns: + None + """ + _old_model = self.transcriber.model_name + + if isinstance(whisper_model, str): + self.transcriber = Transcriber.load_model(whisper_model, **kwargs) + elif isinstance(whisper_model, Transcriber): + self.transcriber = whisper_model + else: + warn(f"Invalid model type. Please provide a valid model. Fallback to old {_old_model} Model.", RuntimeWarning) + + return None + + def update_diariser(self, dia_model : Union[str, DiarisationType], **kwargs) -> None: + """ + Update the diariser model. + + Args: + dia_model (Union[str, DiarisationType]): + The new diariser model to use for diarization. + **kwargs: + Additional keyword arguments for the diariser model. + + Returns: + None + """ + if isinstance(dia_model, str): + self.diariser = Diariser.load_model(dia_model, **kwargs) + elif isinstance(dia_model, Diariser): + self.diariser = dia_model + else: + warn(f"Invalid model type. Please provide a valid model. Fallback to old Model.", RuntimeWarning) + + return None + @staticmethod def remove_audio_file(audio_file : str, shred : bool = False) -> None: @@ -269,7 +316,6 @@ class Scraibe: print(f"Audiofile {audio_file} removed.") - @staticmethod def get_audio_file(audio_file : Union[str, torch.Tensor, ndarray], *args, **kwargs) -> AudioProcessor: @@ -298,6 +344,7 @@ class Scraibe: if not isinstance(audio_file, AudioProcessor): raise ValueError(f'Audiofile must be of type AudioProcessor,' \ f'not {type(audio_file)}') + return audio_file def __repr__(self): From 69b4e22b51a9f642566ff4a4a65819bb00f1c0f6 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Mon, 29 Apr 2024 13:38:55 +0200 Subject: [PATCH 197/331] Add deprecation warning. --- scraibe/diarisation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index 1643db2..ade9220 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -232,6 +232,10 @@ class Diariser: if not os.path.exists(path_to_model): warnings.warn(f"Model not found at {path_to_model}. \ 'Trying to find it nearby .bin files instead.") + warnings.warn( + 'Searching for nearby files in a folder path is ' + 'deprecated and will be removed in future versions.', + category=DeprecationWarning) # list elementes with the ending .bin bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] if len(bin_files) == 1: From ba2eac6c5cdf856270a08daddeb55a86d24e0074 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Mon, 29 Apr 2024 15:54:35 +0200 Subject: [PATCH 198/331] Delete scraibe/.pyannotetoken Deleted obsolete file --- scraibe/.pyannotetoken | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scraibe/.pyannotetoken diff --git a/scraibe/.pyannotetoken b/scraibe/.pyannotetoken deleted file mode 100644 index 8b13789..0000000 --- a/scraibe/.pyannotetoken +++ /dev/null @@ -1 +0,0 @@ - From 91623ac26523ec3188687596467ccd53b10a4c50 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Mon, 29 Apr 2024 16:02:47 +0200 Subject: [PATCH 199/331] Update documentation.yml only run when merged to main --- .github/workflows/documentation.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 9f5141f..53277f2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,6 +1,10 @@ name: documentation -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: + - main + workflow_dispatch: permissions: contents: write From 0240aa4fea9df002868964ff2b1af28a160b1cfc Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 3 May 2024 13:39:06 +0000 Subject: [PATCH 200/331] ruff ci --- .github/workflows/ruff.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..c8a0958 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,8 @@ +name: Ruff +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 \ No newline at end of file From 62f6d5305b88b6150f11a8cc36eb2f30e7610503 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 3 May 2024 13:53:00 +0000 Subject: [PATCH 201/331] added ruff.toml --- .ruff.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..d11fa04 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,3 @@ +[lint.extend-per-file-ignores] +"__init__.py" = ["E402"] +'__init__.py' = ["F403"] \ No newline at end of file From 174a000dc4fdd375908f19665dc9e6b975e77a40 Mon Sep 17 00:00:00 2001 From: Jacob Schmieder Date: Fri, 3 May 2024 16:09:46 +0200 Subject: [PATCH 202/331] fixed .ruff.toml --- .ruff.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index d11fa04..f6be0be 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,3 +1,2 @@ [lint.extend-per-file-ignores] -"__init__.py" = ["E402"] -'__init__.py' = ["F403"] \ No newline at end of file +"__init__.py" = ["E402","F403"] From f6f444093c1cb636882ef3b8a3d71b6f3363102f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 6 May 2024 12:34:32 +0000 Subject: [PATCH 203/331] changed workflow name --- .github/workflows/pytest.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8959789..973571f 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,11 +1,10 @@ -name: Run tests +name: Run Tests on: #push: pull_request: branches: ['main', 'develop'] - workflow_dispatch: jobs: From 0d9d8d57d181520252e9990f2ef7f3ed2354fcd3 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 6 May 2024 12:35:55 +0000 Subject: [PATCH 204/331] added pypi ci --- .github/workflows/pypi.yml | 102 +++++++++++++++++++++++++++ .github/workflows/python-publish.yml | 39 ---------- 2 files changed, 102 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/pypi.yml delete mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..0426bba --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,102 @@ +name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI + +on: + workflow_dispatch: + inputs: + branch_name: + description: 'Branch to build from (default is main)' + required: false + default: 'main' + workflow_run: + workflows: ["Run Tests"] # Ensure this matches the name of your testing workflow + types: + - completed + branches: [main, develop] # This ensures it only triggers for these branches + +env: + TestPyPI_URL: https://test.pypi.org/p/scraibe + PyPI_URL: https://pypi.org/p/scraibe + PyPI_DEV_URL: https://pypi.org/p/scraibe-nightly + +jobs: + + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + # Check if manually triggered or automatically after successful test + if: > + github.event_name == 'workflow_dispatch' || + (github.event.workflow_run.conclusion == 'success' && + (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'develop')) + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Build source distribution + run: python3 setup.py sdist + - name: Store the distribution packages + uses: actions/upload-artifact@v3 + with: + name: python-package-distributions + path: dist/ + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + needs: build + runs-on: ubuntu-latest + environment: + name: testpypi + url: $TestPyPI_URL + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + test-install: + name: Test Installation from TestPyPI + needs: publish-to-testpypi + runs-on: ubuntu-latest + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install package + run: | + python3 -m pip install --index-url $TestPyPI_URL + + publish-to-pypi: + name: Conditional Publish to PyPI or Dev Repository + needs: [build, test-install] + runs-on: ubuntu-latest + environment: + name: pypi + url: $PyPI_URL + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI or Dev Repository + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: ${{ github.ref == 'refs/heads/main' && 'env.PyPI_URL' || ' env.PyPI_DEV_URL' }} + user: ${{ secrets.PYPI_USERNAME }} + password: ${{ secrets.PYPI_PASSWORD }} + + diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index bdaab28..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Upload Python Package - -on: - release: - types: [published] - -permissions: - contents: read - -jobs: - deploy: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} From 3930a7e185840780fc826b3ca143b72ffd2aa47d Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 6 May 2024 12:39:08 +0000 Subject: [PATCH 205/331] use pypi api token --- .github/workflows/pypi.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 0426bba..7f5fc3b 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -96,7 +96,6 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: ${{ github.ref == 'refs/heads/main' && 'env.PyPI_URL' || ' env.PyPI_DEV_URL' }} - user: ${{ secrets.PYPI_USERNAME }} - password: ${{ secrets.PYPI_PASSWORD }} + password: ${{ secrets.PYPI_API_TOKEN}} From cc89f5e7cb98da8a6ad0564748aeaa1a1d7cf8bd Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 07:55:02 +0000 Subject: [PATCH 206/331] test push --- .github/workflows/pypi.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 7f5fc3b..21f0109 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,23 +1,34 @@ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI +# on: +# workflow_dispatch: +# inputs: +# branch_name: +# description: 'Branch to build from (default is main)' +# required: false +# default: 'main' +# workflow_run: +# workflows: ["Run Tests"] +# types: +# - completed +# branches: [main, develop] # This ensures it only triggers for these branches + on: + push: + branches: + - develop workflow_dispatch: inputs: branch_name: description: 'Branch to build from (default is main)' required: false default: 'main' - workflow_run: - workflows: ["Run Tests"] # Ensure this matches the name of your testing workflow - types: - - completed - branches: [main, develop] # This ensures it only triggers for these branches - + env: TestPyPI_URL: https://test.pypi.org/p/scraibe PyPI_URL: https://pypi.org/p/scraibe PyPI_DEV_URL: https://pypi.org/p/scraibe-nightly - + ISRELEASED: true jobs: build: From 141517d097fa94e60047bff0bc1416f0708a9cb3 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 08:03:48 +0000 Subject: [PATCH 207/331] removed checks --- .github/workflows/pypi.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 21f0109..acc5c54 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -34,11 +34,6 @@ jobs: build: name: Build distribution 📦 runs-on: ubuntu-latest - # Check if manually triggered or automatically after successful test - if: > - github.event_name == 'workflow_dispatch' || - (github.event.workflow_run.conclusion == 'success' && - (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'develop')) steps: - uses: actions/checkout@v4 with: From 1fe93497e13df33efb7de9df74513c94f1e35d33 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 08:06:17 +0000 Subject: [PATCH 208/331] added dependencies to pypi yaml --- .github/workflows/pypi.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index acc5c54..b77ebb2 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -42,6 +42,10 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.x" + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip install setuptools wheel - name: Build source distribution run: python3 setup.py sdist - name: Store the distribution packages From 5ef907105f05d4447e6500a86ce5024292e4ff91 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 11:15:10 +0000 Subject: [PATCH 209/331] test push to testpypi --- .github/workflows/pypi.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index b77ebb2..490202b 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -106,6 +106,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: ${{ github.ref == 'refs/heads/main' && 'env.PyPI_URL' || ' env.PyPI_DEV_URL' }} - password: ${{ secrets.PYPI_API_TOKEN}} - - + password: ${{ secrets.PYPI_API_TOKEN}} \ No newline at end of file From 0f3bd55d49366e9bd6950563e9fd57544f3fb6b0 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 11:30:25 +0000 Subject: [PATCH 210/331] changed long_description --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 3e2cac7..c554d62 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ version = attr: scraibe.__version__ author = Jacob Schmieder author_email = Jacob.Schmieder@dbfz.de description = My package description -long_description = file: README.md, LICENSE +long_description = file: README.md, file: LICENSE platforms = Linux keywords = transcription speech recognition whisper pyannote audio speech-to-text speech-to-text transcription speech-to-text recognition voice-to-speech license = GPL-3.0 From d740602a781dded09aabbf9fe9cee63d814bc3e0 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 11:37:03 +0000 Subject: [PATCH 211/331] added long description type --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index c554d62..7ecab8c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ author = Jacob Schmieder author_email = Jacob.Schmieder@dbfz.de description = My package description long_description = file: README.md, file: LICENSE +long_description_content_type = text/markdown; charset=UTF-8; variant=GFM platforms = Linux keywords = transcription speech recognition whisper pyannote audio speech-to-text speech-to-text transcription speech-to-text recognition voice-to-speech license = GPL-3.0 From 46f48d0e255ee29effa12bbe8163cb1b02a33f02 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 11:55:03 +0000 Subject: [PATCH 212/331] changed env name --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 490202b..fca875d 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest environment: name: testpypi - url: $TestPyPI_URL + url: env.TestPyPI_URL permissions: id-token: write steps: From 8b187871236b59e77c87697c53300814e8b106f8 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 11:59:43 +0000 Subject: [PATCH 213/331] try fix etras_require --- setup.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index c2b37d4..60c7c03 100644 --- a/setup.py +++ b/setup.py @@ -30,27 +30,26 @@ with open(os.path.join(os.path.dirname(__file__), "requirements.txt")) as f: if __name__ == "__main__": setup( - name=module_name, - version=version["get_version"](build_version), - packages=find_packages(), - python_requires=">=3.8", - readme="README.md", + name = module_name, + version = version["get_version"](build_version), + packages = find_packages(), + python_requires = ">=3.8", + readme = "README.md", install_requires = requirements, - extras_require= { - "app" : "scraibe-webui @ https://github.com/JSchmie/ScrAIbe-WebUI" - } - , + extras_require = { + "app" : ["scraibe-webui @ git+https://github.com/JSchmie/ScrAIbe-WebUI"], + }, # dependency_links=[ # 'https://download.pytorch.org/whl/cu113', # ], - url= github_url, + url = github_url, - license='GPL-3', - author='Jacob Schmieder', - author_email='Jacob.Schmieder@dbfz.de', - description='Transcription tool for audio files based on Whisper and Pyannote', - classifiers=[ + license = 'GPL-3', + author = 'Jacob Schmieder', + author_email = 'Jacob.Schmieder@dbfz.de', + description = 'Transcription tool for audio files based on Whisper and Pyannote', + classifiers = [ 'Development Status :: 3 - Alpha', 'Environment :: GPU :: NVIDIA CUDA :: 11.2', 'License :: OSI Approved :: Open Software License 3.0 (OSL-3.0)', @@ -61,8 +60,8 @@ if __name__ == "__main__": keywords = ['transcription', 'speech recognition', 'whisper', 'pyannote', 'audio', 'ScrAIbe', 'scraibe', 'speech-to-text', 'speech-to-text transcription', 'speech-to-text recognition', 'voice-to-speech'], - package_data={'scraibe.app' : ["*.html", "*.svg","*.yml"]}, - entry_points={'console_scripts': + package_data = {'scraibe.app' : ["*.html", "*.svg","*.yml"]}, + entry_points = {'console_scripts': ['scraibe = scraibe.cli:cli']} ) From 57bcddd238e6304a25e809ac2c73a60008f76b6f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 12:10:05 +0000 Subject: [PATCH 214/331] removed url from extra_requeire since pip does not support it until now --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 60c7c03..b4894f9 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ if __name__ == "__main__": readme = "README.md", install_requires = requirements, extras_require = { - "app" : ["scraibe-webui @ git+https://github.com/JSchmie/ScrAIbe-WebUI"], + "app" : ["scraibe-webui"], }, # dependency_links=[ # 'https://download.pytorch.org/whl/cu113', From f793f3235e1517672a030a44e52a1a1b6b3a3461 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 13:17:16 +0000 Subject: [PATCH 215/331] added package name for scraibe in test-install --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index fca875d..7282149 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -85,7 +85,7 @@ jobs: python-version: "3.x" - name: Install package run: | - python3 -m pip install --index-url $TestPyPI_URL + python3 -m pip install scraibe --index-url env.TestPyPI_URL publish-to-pypi: name: Conditional Publish to PyPI or Dev Repository From edbf5e7585196659dffc62c3afba9337bd1e37b1 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 14:43:34 +0000 Subject: [PATCH 216/331] test skip existing --- .github/workflows/pypi.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 7282149..b233a8e 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -73,6 +73,8 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ + skip-existing: true + test-install: name: Test Installation from TestPyPI @@ -93,7 +95,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: $PyPI_URL + url: env.PyPI_URL permissions: id-token: write steps: From fcf6e69f10fb401461ce3c35d9e5d225f91b67c8 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 14:45:56 +0000 Subject: [PATCH 217/331] test index url --- .github/workflows/pypi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index b233a8e..e380906 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -74,7 +74,7 @@ jobs: with: repository-url: https://test.pypi.org/legacy/ skip-existing: true - + test-install: name: Test Installation from TestPyPI @@ -87,7 +87,7 @@ jobs: python-version: "3.x" - name: Install package run: | - python3 -m pip install scraibe --index-url env.TestPyPI_URL + python3 -m pip install scraibe --index-url $env.TestPyPI_URL publish-to-pypi: name: Conditional Publish to PyPI or Dev Repository From f3c540b9a56011acb26080aa898402de314d686b Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 14:52:54 +0000 Subject: [PATCH 218/331] changed variable --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index e380906..814cc5a 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -87,7 +87,7 @@ jobs: python-version: "3.x" - name: Install package run: | - python3 -m pip install scraibe --index-url $env.TestPyPI_URL + python3 -m pip install scraibe --index-url $TestPyPI_URL publish-to-pypi: name: Conditional Publish to PyPI or Dev Repository From 417b30f9c393b02ba39684176619aec89e5cc70a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 14:57:32 +0000 Subject: [PATCH 219/331] fixed index-url --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 814cc5a..95e06c9 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -87,7 +87,7 @@ jobs: python-version: "3.x" - name: Install package run: | - python3 -m pip install scraibe --index-url $TestPyPI_URL + python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe publish-to-pypi: name: Conditional Publish to PyPI or Dev Repository From 3e04d20b5ae0cca24849cd66898242d0cc8ed52c Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 7 May 2024 15:02:43 +0000 Subject: [PATCH 220/331] install setuptools --- .github/workflows/pypi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 95e06c9..8693012 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -87,6 +87,7 @@ jobs: python-version: "3.x" - name: Install package run: | + python3 -m pip install setuptools python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe publish-to-pypi: From b92056196212eba221ce18fd709178e0df252689 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 07:21:15 +0000 Subject: [PATCH 221/331] include requirements --- MANIFEST.IN | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.IN b/MANIFEST.IN index 6607396..e1a3884 100644 --- a/MANIFEST.IN +++ b/MANIFEST.IN @@ -1,4 +1,5 @@ recursive-include scraibe *.py recursive-include scraibe *.yaml +include requirements.txt global-exclude *.pyc global-exclude __pycache__ \ No newline at end of file From 978dca56401231f788ddf570e385e86b7f90cfe7 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 07:21:53 +0000 Subject: [PATCH 222/331] removed unused package --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index b4894f9..a44fc0a 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ import os -import re from setuptools import setup, find_packages module_name = "scraibe" From c1067fed32c4f5cc3d6c1bf0ff9c209f1dcb21bb Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 07:38:04 +0000 Subject: [PATCH 223/331] added Nano Version --- scraibe/version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scraibe/version.py b/scraibe/version.py index bce39ee..01ff5da 100644 --- a/scraibe/version.py +++ b/scraibe/version.py @@ -4,8 +4,9 @@ import subprocess as sp MAJOR = 0 MINOR = 1 MICRO = 1 +NANO = 1 ISRELEASED = False -VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO) +VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO, NANO) # Return the git revision as a string # taken from numpy/numpy From 9893e4372b7510e2c9ac821fd62d65e2835b43a1 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 07:51:53 +0000 Subject: [PATCH 224/331] removed tests from opackage and try global include requirements --- MANIFEST.IN | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.IN b/MANIFEST.IN index e1a3884..e2cb9a8 100644 --- a/MANIFEST.IN +++ b/MANIFEST.IN @@ -1,5 +1,6 @@ recursive-include scraibe *.py recursive-include scraibe *.yaml -include requirements.txt +recursive-exclude test/* +global-include requirements.txt global-exclude *.pyc -global-exclude __pycache__ \ No newline at end of file +global-exclude __pycache__ From 7bf5d329cac1b07eb19a9e46b5a81fbbf246135c Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 08:00:38 +0000 Subject: [PATCH 225/331] check which branch is used --- .github/workflows/pypi.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 8693012..a7faaca 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -38,6 +38,10 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} + - name: Display Branch Name + run: | + echo "Building branch: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }}" + - name: Set up Python uses: actions/setup-python@v4 with: From e8a03f9777cd325b16d7c3863991a5820e7628fe Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:20:53 +0000 Subject: [PATCH 226/331] debug build --- .github/workflows/pypi.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index a7faaca..afa235c 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -38,10 +38,17 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} + - name: Debug Event Context + run: | + echo "Event Name: ${{ github.event_name }}" + echo "Inputs Branch Name: ${{ github.event.inputs.branch_name }}" + echo "Workflow Run Head Branch: ${{ github.event.workflow_run.head_branch }}" + echo "Ref: ${{ github.ref }}" + echo "GITHUB_REF: $GITHUB_REF" - name: Display Branch Name run: | - echo "Building branch: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }}" - + BRANCH=${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} + echo "Building branch: $BRANCH" - name: Set up Python uses: actions/setup-python@v4 with: From ee715a60f4fc18590b0eb14160c8ccb16a9f5165 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:38:46 +0000 Subject: [PATCH 227/331] get branch name --- .github/workflows/pypi.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index afa235c..cc4d7fd 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -38,17 +38,8 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} - - name: Debug Event Context - run: | - echo "Event Name: ${{ github.event_name }}" - echo "Inputs Branch Name: ${{ github.event.inputs.branch_name }}" - echo "Workflow Run Head Branch: ${{ github.event.workflow_run.head_branch }}" - echo "Ref: ${{ github.ref }}" - echo "GITHUB_REF: $GITHUB_REF" - - name: Display Branch Name - run: | - BRANCH=${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} - echo "Building branch: $BRANCH" + - name: Get Branch Name + run: echo running on branch ${GITHUB_REF##*/} - name: Set up Python uses: actions/setup-python@v4 with: From 5b627ab27ae2ab458ccd2346a793a3ab76e1a200 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:39:29 +0000 Subject: [PATCH 228/331] fixed syntax --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index cc4d7fd..cb0338a 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -39,7 +39,7 @@ jobs: with: ref: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} - name: Get Branch Name - run: echo running on branch ${GITHUB_REF##*/} + run: echo running on branch ${GITHUB_REF##*/} - name: Set up Python uses: actions/setup-python@v4 with: From c3b08f1c67f8565c485c2a21165c1f4bb4eadc2b Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:41:23 +0000 Subject: [PATCH 229/331] show manifest in --- .github/workflows/pypi.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index cb0338a..913dcba 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -36,10 +36,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.branch_name || github.event.workflow_run.head_branch }} - - name: Get Branch Name - run: echo running on branch ${GITHUB_REF##*/} - name: Set up Python uses: actions/setup-python@v4 with: @@ -48,8 +44,10 @@ jobs: run: | python3 -m pip install --upgrade pip pip install setuptools wheel - - name: Build source distribution - run: python3 setup.py sdist + - name: Build source distribution + run: | + cat MANIFEST.in + python3 setup.py sdist - name: Store the distribution packages uses: actions/upload-artifact@v3 with: From e52b30c9d58142272b377fd67ac6adf2e9cba60a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:43:32 +0000 Subject: [PATCH 230/331] change filename --- MANIFEST.IN => MANIFEST.in | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MANIFEST.IN => MANIFEST.in (100%) diff --git a/MANIFEST.IN b/MANIFEST.in similarity index 100% rename from MANIFEST.IN rename to MANIFEST.in From 31b415f98f71903301bc51628503d67f48f7d832 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:50:08 +0000 Subject: [PATCH 231/331] changed version to test new upload --- .github/workflows/pypi.yml | 1 - scraibe/version.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 913dcba..56d06bf 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -46,7 +46,6 @@ jobs: pip install setuptools wheel - name: Build source distribution run: | - cat MANIFEST.in python3 setup.py sdist - name: Store the distribution packages uses: actions/upload-artifact@v3 diff --git a/scraibe/version.py b/scraibe/version.py index 01ff5da..9c4faa5 100644 --- a/scraibe/version.py +++ b/scraibe/version.py @@ -4,7 +4,7 @@ import subprocess as sp MAJOR = 0 MINOR = 1 MICRO = 1 -NANO = 1 +NANO = 2 ISRELEASED = False VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO, NANO) From 2d2339000935daa599357c2df2d1c62ea3733f8e Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:58:42 +0000 Subject: [PATCH 232/331] check if variable set correctly --- .github/workflows/pypi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 56d06bf..3b47162 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -46,6 +46,7 @@ jobs: pip install setuptools wheel - name: Build source distribution run: | + python3 -c "import os; print("ISRELEASED" in os.environ)" python3 setup.py sdist - name: Store the distribution packages uses: actions/upload-artifact@v3 From 0aa28d87a4d3ce11d3a20f5b3fd639cc33ce414d Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 09:59:37 +0000 Subject: [PATCH 233/331] fixed wrong syntax --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 3b47162..0e73d9a 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -46,7 +46,7 @@ jobs: pip install setuptools wheel - name: Build source distribution run: | - python3 -c "import os; print("ISRELEASED" in os.environ)" + python3 -c "import os; print('ISRELEASED' in os.environ)" python3 setup.py sdist - name: Store the distribution packages uses: actions/upload-artifact@v3 From 9f590040298dab3e01de5d8f1e16017ef68ef793 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 8 May 2024 10:04:08 +0000 Subject: [PATCH 234/331] debug versioning --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a44fc0a..3dfc95e 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,9 @@ version["ISRELEASED"] = True if "ISRELEASED" in os.environ else False with open(os.path.join(os.path.dirname(__file__), "requirements.txt")) as f: requirements = [line.strip() for line in f if line.strip() and not line.startswith('#')] + +print(f"Launch Version: {version['get_version'](build_version)}") + if __name__ == "__main__": setup( @@ -59,8 +62,8 @@ if __name__ == "__main__": keywords = ['transcription', 'speech recognition', 'whisper', 'pyannote', 'audio', 'ScrAIbe', 'scraibe', 'speech-to-text', 'speech-to-text transcription', 'speech-to-text recognition', 'voice-to-speech'], + include_package_data=True, package_data = {'scraibe.app' : ["*.html", "*.svg","*.yml"]}, entry_points = {'console_scripts': - ['scraibe = scraibe.cli:cli']} - + ['scraibe = scraibe.cli:cli']} ) From 82e26771e0e4d32501feba442fec83bf3cb957a6 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Wed, 8 May 2024 15:49:05 +0200 Subject: [PATCH 235/331] Added WhisperX as possible whisper model. --- requirements.txt | 1 + scraibe/autotranscript.py | 5 +- scraibe/transcriber.py | 252 +++++++++++++++++++++++++++++++++----- 3 files changed, 224 insertions(+), 34 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5872774..d1bdccc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ tqdm>=4.65.0 numpy>=1.26.4 openai-whisper==20231117 +whisperx~=3.1.3 pyannote.audio~=3.1.1 pyannote.core~=5.0.0 diff --git a/scraibe/autotranscript.py b/scraibe/autotranscript.py index 7d54ba8..4081638 100644 --- a/scraibe/autotranscript.py +++ b/scraibe/autotranscript.py @@ -64,6 +64,7 @@ class Scraibe: """ def __init__(self, whisper_model: Union[bool, str, whisper] = None, + whisper_type: str = "whisper", dia_model : Union[bool, str, DiarisationType] = None, **kwargs) -> None: """Initializes the Scraibe class. @@ -84,9 +85,9 @@ class Scraibe: if whisper_model is None: - self.transcriber = Transcriber.load_model("medium", **kwargs) + self.transcriber = Transcriber.load_model("medium", whisper_type, **kwargs) elif isinstance(whisper_model, str): - self.transcriber = Transcriber.load_model(whisper_model, **kwargs) + self.transcriber = Transcriber.load_model(whisper_model, whisper_type, **kwargs) else: self.transcriber = whisper_model diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index 910ea59..365d321 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -24,16 +24,18 @@ Usage: >>> transcriber.save_transcript(transcript, "path/to/save.txt") """ -from whisper import Whisper, load_model +from whisper import Whisper +from whisper import load_model as whisper_load_model +from whisperx.asr import WhisperModel +from whisperx import load_model as whisperx_load_model from typing import TypeVar , Union , Optional from torch import Tensor, device from numpy import ndarray - +from inspect import getfullargspec +from abc import ABC, abstractmethod from .misc import WHISPER_DEFAULT_PATH -whisper = TypeVar('whisper') - - +whisper = TypeVar('whisper') class Transcriber: @@ -64,7 +66,7 @@ class Transcriber: The class supports various sizes and versions of Whisper models. Please refer to the load_model method for available options. """ - def __init__(self, model: whisper , model_name: str ) -> None: + def __init__(self, model: whisper, model_name: str) -> None: """ Initialize the Transcriber class with a Whisper model. @@ -77,7 +79,8 @@ class Transcriber: self.model_name = model_name - def transcribe(self, audio : Union[str, Tensor, ndarray] , + @abstractmethod + def transcribe(self, audio: Union[str, Tensor, ndarray] , *args, **kwargs) -> str: """ Transcribe an audio file. @@ -91,14 +94,7 @@ class Transcriber: Returns: str: The transcript as a string. """ - - kwargs = self._get_whisper_kwargs(**kwargs) - - if not kwargs.get("verbose"): - kwargs["verbose"] = None - - result = self.model.transcribe(audio, *args, **kwargs) - return result["text"] + pass @staticmethod def save_transcript(transcript : str , save_path : str) -> None: @@ -120,12 +116,106 @@ class Transcriber: @classmethod def load_model(cls, - model: str = "medium", - download_root: str = WHISPER_DEFAULT_PATH, - device: Optional[Union[str, device]] = None, - in_memory: bool = False, - *args, **kwargs - ) -> 'Transcriber': + model: str = "medium", + whisper_type: str = 'whisper', + download_root: str = WHISPER_DEFAULT_PATH, + device: Optional[Union[str, device]] = None, + in_memory: bool = False, + *args, **kwargs + ) -> 'Transcriber': + """ + Load whisper model. + + Args: + model (str): Whisper model. Available models include: + - 'tiny.en' + - 'tiny' + - 'base.en' + - 'base' + - 'small.en' + - 'small' + - 'medium.en' + - 'medium' + - 'large-v1' + - 'large-v2' + - 'large-v3' + - 'large' + + download_root (str, optional): Path to download the model. + Defaults to WHISPER_DEFAULT_PATH. + + device (Optional[Union[str, torch.device]], optional): + Device to load model on. Defaults to None. + in_memory (bool, optional): Whether to load model in memory. + Defaults to False. + args: Additional arguments only to avoid errors. + kwargs: Additional keyword arguments only to avoid errors. + + Returns: + Transcriber: A Transcriber object initialized with the specified model. + """ + if whisper_type.lower() == 'whisper': + _model = WhisperTranscriber.load_model( + model, download_root, device, in_memory, *args, **kwargs) + return _model + elif whisper_type.lower() == 'whisperx': + _model = WhisperXTranscriber.load_model( + model, download_root, device, *args, **kwargs) + return _model + else: + raise ValueError(f'Model type not recognized, exptected "whisper" ' + f'or "whisperx", got {whisper_type}.') + pass + + @staticmethod + def _get_whisper_kwargs(**kwargs) -> dict: + """ + Get kwargs for whisper model. Ensure that kwargs are valid. + + Returns: + dict: Keyword arguments for whisper model. + """ + pass + + def __repr__(self) -> str: + return f"Transcriber(model_name={self.model_name}, model={self.model})" + + +class WhisperTranscriber(Transcriber): + def __init__(self, model: whisper, model_name: str) -> None: + super().__init__(model, model_name) + + def transcribe(self, audio: Union[str, Tensor, ndarray], + *args, **kwargs) -> str: + """ + Transcribe an audio file. + + Args: + audio (Union[str, Tensor, nparray]): The audio file to transcribe. + *args: Additional arguments. + **kwargs: Additional keyword arguments, + such as the language of the audio file. + + Returns: + str: The transcript as a string. + """ + + kwargs = self._get_whisper_kwargs(**kwargs) + + if not kwargs.get("verbose"): + kwargs["verbose"] = None + + result = self.model.transcribe(audio, *args, **kwargs) + return result["text"] + + @classmethod + def load_model(cls, + model: str = "medium", + download_root: str = WHISPER_DEFAULT_PATH, + device: Optional[Union[str, device]] = None, + in_memory: bool = False, + *args, **kwargs + ) -> 'Transcriber': """ Load whisper model. @@ -158,8 +248,8 @@ class Transcriber: Transcriber: A Transcriber object initialized with the specified model. """ - _model = load_model(model, download_root=download_root, - device=device, in_memory=in_memory) + _model = whisper_load_model(model, download_root=download_root, + device=device, in_memory=in_memory) return cls(_model, model_name=model) @@ -171,17 +261,115 @@ class Transcriber: Returns: dict: Keyword arguments for whisper model. """ - _possible_kwargs = Whisper.transcribe.__code__.co_varnames - + # _possible_kwargs = WhisperModel.transcribe.__code__.co_varnames + _args = getfullargspec(Whisper.transcribe).args + _kwargs = getfullargspec(Whisper.transcribe).kwonlyargs + _possible_kwargs = _args + _kwargs + whisper_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} - + if (task := kwargs.get("task")): whisper_kwargs["task"] = task - + if (language := kwargs.get("language")): - whisper_kwargs["language"] = language - + whisper_kwargs["language"] = language + + return whisper_kwargs + + +class WhisperXTranscriber(Transcriber): + def __init__(self, model: whisper, model_name: str) -> None: + super().__init__(model, model_name) + + def transcribe(self, audio: Union[str, Tensor, ndarray], + *args, **kwargs) -> str: + """ + Transcribe an audio file. + + Args: + audio (Union[str, Tensor, nparray]): The audio file to transcribe. + *args: Additional arguments. + **kwargs: Additional keyword arguments, + such as the language of the audio file. + + Returns: + str: The transcript as a string. + """ + kwargs = self._get_whisper_kwargs(**kwargs) + + if isinstance(audio, Tensor): + audio = audio.cpu().numpy() + result = self.model.transcribe(audio, *args, **kwargs) + text = "" + for seg in result['segments']: + text += seg['text'] + return text + + + @classmethod + def load_model(cls, + model: str = "medium", + download_root: str = WHISPER_DEFAULT_PATH, + device: Optional[Union[str, device]] = None, + *args, **kwargs + ) -> 'Transcriber': + """ + Load whisper model. + + Args: + model (str): Whisper model. Available models include: + - 'tiny.en' + - 'tiny' + - 'base.en' + - 'base' + - 'small.en' + - 'small' + - 'medium.en' + - 'medium' + - 'large-v1' + - 'large-v2' + - 'large-v3' + - 'large' + + download_root (str, optional): Path to download the model. + Defaults to WHISPER_DEFAULT_PATH. + + device (Optional[Union[str, torch.device]], optional): + Device to load model on. Defaults to None. + in_memory (bool, optional): Whether to load model in memory. + Defaults to False. + args: Additional arguments only to avoid errors. + kwargs: Additional keyword arguments only to avoid errors. + + Returns: + Transcriber: A Transcriber object initialized with the specified model. + """ + if not isinstance(device, str): + device = str(device) + _model = whisperx_load_model(model, download_root=download_root, + device=device) + + return cls(_model, model_name=model) + + @staticmethod + def _get_whisper_kwargs(**kwargs) -> dict: + """ + Get kwargs for whisper model. Ensure that kwargs are valid. + + Returns: + dict: Keyword arguments for whisper model. + """ + # _possible_kwargs = WhisperModel.transcribe.__code__.co_varnames + _args = getfullargspec(WhisperModel.transcribe).args + _kwargs = getfullargspec(WhisperModel.transcribe).kwonlyargs + _possible_kwargs = _args + _kwargs + + whisper_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} + + if (task := kwargs.get("task")): + whisper_kwargs["task"] = task + + if (language := kwargs.get("language")): + whisper_kwargs["language"] = language + return whisper_kwargs - - def __repr__(self) -> str: - return f"Transcriber(model_name={self.model_name}, model={self.model})" \ No newline at end of file From 7dd2db0fad7254442e66231605bef64f8dd11e0b Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 08:37:56 +0000 Subject: [PATCH 236/331] added pyproject.toml based on poetry --- .github/workflows/pypi.yml | 1 - pyproject.toml | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 0e73d9a..56d06bf 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -46,7 +46,6 @@ jobs: pip install setuptools wheel - name: Build source distribution run: | - python3 -c "import os; print('ISRELEASED' in os.environ)" python3 setup.py sdist - name: Store the distribution packages uses: actions/upload-artifact@v3 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5222491 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,69 @@ +[tool.poetry] +name = "scraibe" +version = "0.0.0.0" +description = "Transcription tool for audio files based on Whisper and Pyannote" +authors = ["Schmieder, Jacob "] +license = "GPL-3.0-or-later" +readme = ["README.md", "LICENSE"] +repository = "https://github.com/JSchmie/ScAIbe" +documentation = "https://jschmie.github.io/ScrAIbe/" +keywords = ["transcription", "audio", "whisper", "pyannote", "speech-to-text", "speech-recognition"] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Environment :: GPU :: NVIDIA CUDA :: 12 :: 12.1', + 'Topic :: Scientific/Engineering :: Artificial Intelligence' + ] +packages = [{include = "scraibe"}] +exclude =[ + "__pycache__", + "*.pyc", + "test" + ] +[tool.poetry.dependencies] +python = "^3.8" +tqdm = "^4.66.4" +numpy = "^1.26.4" +openai-whisper = "^20231117" +"pyannote.audio" = "^3.2.0" +torch = "^2.3.0" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.1" + +[tool.poetry-dynamic-versioning] +enable = true +vcs = "git" +strict = true +format-jinja = """ +{%- if distance == 0 -%} + {{ serialize_pep440(base) }} +{%- else -%} + {{serialize_pep440(bump_version(base, index=1), dev=timestamp)}} +{%- endif -%} +""" + +[build-system] +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" + +[tool.poetry.group.docs.dependencies] +sphinx = "^7.3.7" +sphinx-rtd-theme = "^2.0.0" +markdown-it-py = {version = "~3.0.0", extras = ["plugins"]} +myst-parser = "^3.0.1" +mdit-py-plugins = "^0.4.1" + +[tool.poetry.scripts] +scraibe = "scraibe.cli:cli" + +[tool.poetry.extras] +app = ["scraibe-webui"] + +[tool.ruff.lint.extend-per-file-ignores] +"__init__.py" = ["E402","F403"] From 3930c4d09e381ca79ca2a75769a03b2369285019 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 08:50:15 +0000 Subject: [PATCH 237/331] removed ruff.toml and includ it to pyproject.toml --- .ruff.toml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml deleted file mode 100644 index f6be0be..0000000 --- a/.ruff.toml +++ /dev/null @@ -1,2 +0,0 @@ -[lint.extend-per-file-ignores] -"__init__.py" = ["E402","F403"] From fc0c32f1096ed5efb2ab74ae36d1584ac0589e1a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 08:51:44 +0000 Subject: [PATCH 238/331] removed files in favor of pyproject.toml --- MANIFEST.in | 6 ----- setup.cfg | 32 ------------------------- setup.py | 69 ----------------------------------------------------- 3 files changed, 107 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index e2cb9a8..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -recursive-include scraibe *.py -recursive-include scraibe *.yaml -recursive-exclude test/* -global-include requirements.txt -global-exclude *.pyc -global-exclude __pycache__ diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7ecab8c..0000000 --- a/setup.cfg +++ /dev/null @@ -1,32 +0,0 @@ -[metadata] -name = scraibe -version = attr: scraibe.__version__ -author = Jacob Schmieder -author_email = Jacob.Schmieder@dbfz.de -description = My package description -long_description = file: README.md, file: LICENSE -long_description_content_type = text/markdown; charset=UTF-8; variant=GFM -platforms = Linux -keywords = transcription speech recognition whisper pyannote audio speech-to-text speech-to-text transcription speech-to-text recognition voice-to-speech -license = GPL-3.0 -classifiers = - Development Status :: 3 - Alpha - Environment :: GPU :: NVIDIA CUDA :: 11.2 - License :: OSI Approved :: Open Software License 3.0 (OSL-3.0) - Topic :: Scientific/Engineering :: Artificial Intelligence - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - -[options] -zip_safe = False -include_package_data = True -packages = find: -python_requires = >=3.7 -install_requires = - requests - importlib-metadata; python_version<"3.8" - -[options.entry_points] -console_scripts = - executable-name = scraibe.cli:cli \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 3dfc95e..0000000 --- a/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -from setuptools import setup, find_packages - -module_name = "scraibe" -github_url = "https://github.com/JSchmie/ScAIbe" - -file_dir = os.path.dirname(os.path.realpath(__file__)) -absdir = lambda p: os.path.join(file_dir, p) - -############### versioning ############### -verfile = os.path.abspath(os.path.join(module_name, "version.py")) -version = {"__file__": verfile} - -with open(verfile, "r") as fp: - exec(fp.read(), version) - - -############### setup ############### - -build_version = "SCRAIBE_BUILD" in os.environ - -version["ISRELEASED"] = True if "ISRELEASED" in os.environ else False - -############### load requirements ############### - -with open(os.path.join(os.path.dirname(__file__), "requirements.txt")) as f: - requirements = [line.strip() for line in f if line.strip() and not line.startswith('#')] - - -print(f"Launch Version: {version['get_version'](build_version)}") - -if __name__ == "__main__": - - setup( - name = module_name, - version = version["get_version"](build_version), - packages = find_packages(), - python_requires = ">=3.8", - readme = "README.md", - install_requires = requirements, - extras_require = { - "app" : ["scraibe-webui"], - }, - # dependency_links=[ - # 'https://download.pytorch.org/whl/cu113', - # ], - - url = github_url, - - license = 'GPL-3', - author = 'Jacob Schmieder', - author_email = 'Jacob.Schmieder@dbfz.de', - description = 'Transcription tool for audio files based on Whisper and Pyannote', - classifiers = [ - 'Development Status :: 3 - Alpha', - 'Environment :: GPU :: NVIDIA CUDA :: 11.2', - 'License :: OSI Approved :: Open Software License 3.0 (OSL-3.0)', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10'], - keywords = ['transcription', 'speech recognition', 'whisper', 'pyannote', 'audio', 'ScrAIbe', 'scraibe', - 'speech-to-text', 'speech-to-text transcription', 'speech-to-text recognition', - 'voice-to-speech'], - include_package_data=True, - package_data = {'scraibe.app' : ["*.html", "*.svg","*.yml"]}, - entry_points = {'console_scripts': - ['scraibe = scraibe.cli:cli']} - ) From 4d2fd2dc6b584026af495de7cd6fe079999edc21 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 09:00:02 +0000 Subject: [PATCH 239/331] fullfill pep440 --- pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5222491..be79efb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,10 @@ +[build-system] +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" + [tool.poetry] name = "scraibe" -version = "0.0.0.0" +version = "0.0.0" description = "Transcription tool for audio files based on Whisper and Pyannote" authors = ["Schmieder, Jacob "] license = "GPL-3.0-or-later" @@ -48,10 +52,6 @@ format-jinja = """ {%- endif -%} """ -[build-system] -requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] -build-backend = "poetry_dynamic_versioning.backend" - [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" sphinx-rtd-theme = "^2.0.0" From fc046ae181f9b47a383e62b77efd05b83a13d65e Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 09:00:43 +0000 Subject: [PATCH 240/331] fullfill pep440 --- scraibe/version.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scraibe/version.py b/scraibe/version.py index 9c4faa5..19a84e0 100644 --- a/scraibe/version.py +++ b/scraibe/version.py @@ -3,10 +3,9 @@ import subprocess as sp MAJOR = 0 MINOR = 1 -MICRO = 1 -NANO = 2 +MICRO = 2 ISRELEASED = False -VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO, NANO) +VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO) # Return the git revision as a string # taken from numpy/numpy From 95436562bb32d97a7572bff652d8d6b1544d5a72 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 09:02:45 +0000 Subject: [PATCH 241/331] try removed jinja --- pyproject.toml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index be79efb..30964b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,13 +44,6 @@ pytest = "^8.1.1" enable = true vcs = "git" strict = true -format-jinja = """ -{%- if distance == 0 -%} - {{ serialize_pep440(base) }} -{%- else -%} - {{serialize_pep440(bump_version(base, index=1), dev=timestamp)}} -{%- endif -%} -""" [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From 69c4291ea78b3baa250149c29beb3126606a5d19 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 09:04:56 +0000 Subject: [PATCH 242/331] added other jinja --- pyproject.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 30964b6..7de3bd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,15 @@ pytest = "^8.1.1" enable = true vcs = "git" strict = true +format-jinja = """ + {%- if distance == 0 -%} + {{ serialize_pep440(base, stage, revision) }} + {%- elif revision is not none -%} + {{ serialize_pep440(base, stage, revision + 1, dev=distance, metadata=[commit]) }} + {%- else -%} + {{ serialize_pep440(bump_version(base), stage, revision, dev=distance, metadata=[commit]) }} + {%- endif -%} +""" [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From f4bf85b7d14a589ccc20d627d6e84954b1fe7c63 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Mon, 13 May 2024 11:46:01 +0200 Subject: [PATCH 243/331] Added docstring. --- scraibe/autotranscript.py | 2 ++ scraibe/transcriber.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scraibe/autotranscript.py b/scraibe/autotranscript.py index 4081638..cf77a62 100644 --- a/scraibe/autotranscript.py +++ b/scraibe/autotranscript.py @@ -72,6 +72,8 @@ class Scraibe: Args: whisper_model (Union[bool, str, whisper], optional): Path to whisper model or whisper model itself. + whisper_type (str): + Type of whisper model to load. "whisper" or "whisperx". diarisation_model (Union[bool, str, DiarisationType], optional): Path to pyannote diarization model or model itself. **kwargs: Additional keyword arguments for whisper diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index 365d321..977cd94 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -140,10 +140,10 @@ class Transcriber: - 'large-v2' - 'large-v3' - 'large' - + whisper_type (str): + Type of whisper model to load. "whisper" or "whisperx". download_root (str, optional): Path to download the model. Defaults to WHISPER_DEFAULT_PATH. - device (Optional[Union[str, torch.device]], optional): Device to load model on. Defaults to None. in_memory (bool, optional): Whether to load model in memory. From 047ee3a9bda51c96c505d333010db3419eb73c7b Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 10:19:45 +0000 Subject: [PATCH 244/331] test remove jinja --- pyproject.toml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7de3bd8..ea8eac3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,16 +43,7 @@ pytest = "^8.1.1" [tool.poetry-dynamic-versioning] enable = true vcs = "git" -strict = true -format-jinja = """ - {%- if distance == 0 -%} - {{ serialize_pep440(base, stage, revision) }} - {%- elif revision is not none -%} - {{ serialize_pep440(base, stage, revision + 1, dev=distance, metadata=[commit]) }} - {%- else -%} - {{ serialize_pep440(bump_version(base), stage, revision, dev=distance, metadata=[commit]) }} - {%- endif -%} -""" +style= "pep440" [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From 2a291c4c0cc530a0ed230956e5f7c607324f21ec Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 11:04:34 +0000 Subject: [PATCH 245/331] test jinja template --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea8eac3..59689a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,10 @@ pytest = "^8.1.1" [tool.poetry-dynamic-versioning] enable = true vcs = "git" -style= "pep440" +strict = true +format-jinja = """ + TEST_{{- branch -}}_TEST +""" [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From 267261691238f6126c5f810bc258c9c7d051e573 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 11:15:23 +0000 Subject: [PATCH 246/331] readded jijnja again --- pyproject.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 59689a6..7de3bd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,13 @@ enable = true vcs = "git" strict = true format-jinja = """ - TEST_{{- branch -}}_TEST + {%- if distance == 0 -%} + {{ serialize_pep440(base, stage, revision) }} + {%- elif revision is not none -%} + {{ serialize_pep440(base, stage, revision + 1, dev=distance, metadata=[commit]) }} + {%- else -%} + {{ serialize_pep440(bump_version(base), stage, revision, dev=distance, metadata=[commit]) }} + {%- endif -%} """ [tool.poetry.group.docs.dependencies] From 184ef401763b6b6ad54943b5f6aee13c5a1a39f5 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 12:00:13 +0000 Subject: [PATCH 247/331] removed version.py since it is not longer relavant --- scraibe/version.py | 68 ---------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 scraibe/version.py diff --git a/scraibe/version.py b/scraibe/version.py deleted file mode 100644 index 19a84e0..0000000 --- a/scraibe/version.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -import subprocess as sp - -MAJOR = 0 -MINOR = 1 -MICRO = 2 -ISRELEASED = False -VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO) - -# Return the git revision as a string -# taken from numpy/numpy -def git_version(): - def _minimal_ext_cmd(cmd): - # construct minimal environment - env = {} - for k in ['SYSTEMROOT', 'PATH', 'HOME']: - v = os.environ.get(k) - if v is not None: - env[k] = v - - # LANGUAGE is used on win32 - env['LANGUAGE'] = 'C' - env['LANG'] = 'C' - env['LC_ALL'] = 'C' - - out = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE, env=env).communicate()[0] - return out - - try: - out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD']) - GIT_REVISION = out.strip().decode('ascii') - except OSError: - GIT_REVISION = "Unknown" - - return GIT_REVISION - -def _get_git_version(): - cwd = os.getcwd() - - # go to the main directory - fdir = os.path.dirname(os.path.abspath(__file__)) - maindir = os.path.abspath(os.path.join(fdir, "..")) - # maindir = fdir # os.path.join(fdir, "..") - os.chdir(maindir) - - # get git version - res = git_version() - - # restore the cwd - os.chdir(cwd) - return res - -def get_version(build_version=False): - if ISRELEASED: - return VERSION - - # unreleased version - GIT_REVISION = _get_git_version() - - if build_version: - import datetime as dt - date = dt.date.strftime(dt.datetime.now(), "%Y%m%d%H%M%S") - return VERSION + ".dev" + date - else: - return VERSION + ".dev0+" + GIT_REVISION[:7] - - - From 6fd24ee39429b9f26f841eb867a08b86389f76f9 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 12:35:18 +0000 Subject: [PATCH 248/331] costume jinja --- pyproject.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7de3bd8..60ac606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,11 +46,9 @@ vcs = "git" strict = true format-jinja = """ {%- if distance == 0 -%} - {{ serialize_pep440(base, stage, revision) }} - {%- elif revision is not none -%} - {{ serialize_pep440(base, stage, revision + 1, dev=distance, metadata=[commit]) }} + {{ serialize_pep440(base) }} {%- else -%} - {{ serialize_pep440(bump_version(base), stage, revision, dev=distance, metadata=[commit]) }} + {{ serialize_pep440(bump_version(base), dev=distance, metadata=[commit]) }} {%- endif -%} """ From 3d0ca8d1e126608fbb8b5f0b25994361b5829646 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 12:51:19 +0000 Subject: [PATCH 249/331] removed version file from init --- scraibe/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scraibe/__init__.py b/scraibe/__init__.py index 233cd4f..42cd343 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -4,9 +4,7 @@ from .audio import * from .transcript_exporter import * from .diarisation import * -from .version import get_version as _get_version from .misc import * from .cli import * -__version__ = _get_version() From 267baa139ddeaca3af75c2989efaf71eec739604 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 13 May 2024 12:54:22 +0000 Subject: [PATCH 250/331] added __version__ --- scraibe/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scraibe/__init__.py b/scraibe/__init__.py index 42cd343..fbbd81e 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -8,3 +8,8 @@ from .misc import * from .cli import * + # set __version__ attribute + +import importlib.metadata + +__version__ = importlib.metadata.version(__package__ or __name__) From fa937c2ed1cfcd87488b24a5ac3c6bf75a197234 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 08:14:39 +0000 Subject: [PATCH 251/331] test what happens --- .github/workflows/pypi.yml | 84 ++++++++------------------------------ 1 file changed, 16 insertions(+), 68 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 56d06bf..974e41c 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,83 +1,31 @@ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI -# on: -# workflow_dispatch: -# inputs: -# branch_name: -# description: 'Branch to build from (default is main)' -# required: false -# default: 'main' -# workflow_run: -# workflows: ["Run Tests"] -# types: -# - completed -# branches: [main, develop] # This ensures it only triggers for these branches - on: push: branches: - - develop + - pyproject.toml # test branch for testing the workflow + tags: + - v* # Push tags to trigger the workflow workflow_dispatch: inputs: - branch_name: - description: 'Branch to build from (default is main)' - required: false - default: 'main' - -env: - TestPyPI_URL: https://test.pypi.org/p/scraibe - PyPI_URL: https://pypi.org/p/scraibe - PyPI_DEV_URL: https://pypi.org/p/scraibe-nightly - ISRELEASED: true -jobs: - - build: - name: Build distribution 📦 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip - pip install setuptools wheel - - name: Build source distribution - run: | - python3 setup.py sdist - - name: Store the distribution packages - uses: actions/upload-artifact@v3 - with: - name: python-package-distributions - path: dist/ + test: + description: "Push to TestPyPI not PyPI" + default: true + type: boolean - publish-to-testpypi: - name: Publish Python 🐍 distribution 📦 to TestPyPI - needs: build - runs-on: ubuntu-latest - environment: - name: testpypi - url: env.TestPyPI_URL - permissions: - id-token: write - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - skip-existing: true +jobs: + Build-and-publish-to-Test-PyPI: + if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} + uses: JRubics/poetry-publish@v1.16 + with: + plugins: "poetry-dynamic-versioning" + repository_name: "testpypi" + repository_url: "https://test.pypi.org/legacy/" test-install: name: Test Installation from TestPyPI - needs: publish-to-testpypi + needs: Build-and-publish-to-Test-PyPI runs-on: ubuntu-latest steps: - name: Set up Python From ab67d7d6954eefee444fde02107707562034c8e0 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 08:16:48 +0000 Subject: [PATCH 252/331] test latest version --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 974e41c..9d7832f 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -16,7 +16,7 @@ on: jobs: Build-and-publish-to-Test-PyPI: if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} - uses: JRubics/poetry-publish@v1.16 + uses: JRubics/poetry-publish@v2.0 with: plugins: "poetry-dynamic-versioning" repository_name: "testpypi" From 4d31eaa002a6409760a783c92008a164f5ce04b5 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 08:21:38 +0000 Subject: [PATCH 253/331] added checkout --- .github/workflows/pypi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 9d7832f..773c536 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -15,14 +15,14 @@ on: jobs: Build-and-publish-to-Test-PyPI: + ususes: actions/checkout@v4 if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} uses: JRubics/poetry-publish@v2.0 with: plugins: "poetry-dynamic-versioning" - repository_name: "testpypi" + repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" - test-install: name: Test Installation from TestPyPI needs: Build-and-publish-to-Test-PyPI From dd1739b2c28fd9aff28d586813b6f7fd00cc278d Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 08:25:45 +0000 Subject: [PATCH 254/331] moved uses for checkout --- .github/workflows/pypi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 773c536..9df81aa 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -14,8 +14,9 @@ on: type: boolean jobs: + uses: actions/checkout@v4 + Build-and-publish-to-Test-PyPI: - ususes: actions/checkout@v4 if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} uses: JRubics/poetry-publish@v2.0 with: From 54372aeb310842d47c437095faf131854a53264a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 08:31:32 +0000 Subject: [PATCH 255/331] added steps --- .github/workflows/pypi.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 9df81aa..79d5720 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -14,15 +14,19 @@ on: type: boolean jobs: - uses: actions/checkout@v4 - + Build-and-publish-to-Test-PyPI: - if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} - uses: JRubics/poetry-publish@v2.0 - with: - plugins: "poetry-dynamic-versioning" - repository_name: "scraibe" - repository_url: "https://test.pypi.org/legacy/" + name: Build and Publish to Test PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Poetry 📦 + if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} + uses: JRubics/poetry-publish@v2.0 + with: + plugins: "poetry-dynamic-versioning" + repository_name: "scraibe" + repository_url: "https://test.pypi.org/legacy/" test-install: name: Test Installation from TestPyPI From 56aa8a64563e2299fac2bdbc40016a677ac054be Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 08:35:28 +0000 Subject: [PATCH 256/331] less is more --- .github/workflows/pypi.yml | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 79d5720..d3bf886 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -42,23 +42,23 @@ jobs: python3 -m pip install setuptools python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe - publish-to-pypi: - name: Conditional Publish to PyPI or Dev Repository - needs: [build, test-install] - runs-on: ubuntu-latest - environment: - name: pypi - url: env.PyPI_URL - permissions: - id-token: write - steps: - - name: Download all the dists - uses: actions/download-artifact@v3 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution 📦 to PyPI or Dev Repository - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: ${{ github.ref == 'refs/heads/main' && 'env.PyPI_URL' || ' env.PyPI_DEV_URL' }} - password: ${{ secrets.PYPI_API_TOKEN}} \ No newline at end of file + # publish-to-pypi: + # name: Conditional Publish to PyPI or Dev Repository + # needs: [build, test-install] + # runs-on: ubuntu-latest + # environment: + # name: pypi + # url: env.PyPI_URL + # permissions: + # id-token: write + # steps: + # - name: Download all the dists + # uses: actions/download-artifact@v3 + # with: + # name: python-package-distributions + # path: dist/ + # - name: Publish distribution 📦 to PyPI or Dev Repository + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # repository-url: ${{ github.ref == 'refs/heads/main' && 'env.PyPI_URL' || ' env.PyPI_DEV_URL' }} + # password: ${{ secrets.PYPI_API_TOKEN}} \ No newline at end of file From 0222334a6a7ccbbf38f8db8ec494cb53aaaa5684 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 09:03:26 +0000 Subject: [PATCH 257/331] removed checkout --- .github/workflows/pypi.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index d3bf886..eef8797 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -19,7 +19,6 @@ jobs: name: Build and Publish to Test PyPI runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - name: Set up Poetry 📦 if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} uses: JRubics/poetry-publish@v2.0 From ea420094eb7459a5f0435b63f22d4d873f253905 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 09:46:46 +0000 Subject: [PATCH 258/331] test odler version of poetry publish --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index eef8797..326caf1 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Set up Poetry 📦 if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} - uses: JRubics/poetry-publish@v2.0 + uses: JRubics/poetry-publish@v1.16 with: plugins: "poetry-dynamic-versioning" repository_name: "scraibe" From 2c473eaf75f483f1c808b94b6c696394c3eaa0b4 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 09:48:08 +0000 Subject: [PATCH 259/331] added checkout --- .github/workflows/pypi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 326caf1..0da6399 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -19,6 +19,7 @@ jobs: name: Build and Publish to Test PyPI runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 - name: Set up Poetry 📦 if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} uses: JRubics/poetry-publish@v1.16 From 3b0785530bf9cae1ab2a2ba885cf4bc90d0a8c4a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 10:04:36 +0000 Subject: [PATCH 260/331] test if --- .github/workflows/pypi.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 0da6399..8e2c51c 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -16,10 +16,12 @@ on: jobs: Build-and-publish-to-Test-PyPI: - name: Build and Publish to Test PyPI runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - run: | + echo ${{ github.ref }} + echo ${{ inputs.test }} - name: Set up Poetry 📦 if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} uses: JRubics/poetry-publish@v1.16 From 9b4a0357ed42c47efc2e4079611c8b4733e7509f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 10:31:59 +0000 Subject: [PATCH 261/331] test upload ro pypi --- .github/workflows/pypi.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 8e2c51c..2ee2987 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -19,11 +19,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: | - echo ${{ github.ref }} - echo ${{ inputs.test }} - name: Set up Poetry 📦 - if: ${{ !(startsWith(github.ref, 'refs/tags')) || (inputs.test == true) }} + if: ${{ !(startsWith(github.ref, 'refs/tags'))}} uses: JRubics/poetry-publish@v1.16 with: plugins: "poetry-dynamic-versioning" From 8217ac82a7ed6b2c7ed18587359ee8da4c5fff2e Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 10:47:40 +0000 Subject: [PATCH 262/331] added fetch depth --- .github/workflows/pypi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 2ee2987..7d2f83d 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: '0' - name: Set up Poetry 📦 if: ${{ !(startsWith(github.ref, 'refs/tags'))}} uses: JRubics/poetry-publish@v1.16 From 254c7ca12d8be9bd110d689e4fc11108d00cddd5 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 11:24:51 +0000 Subject: [PATCH 263/331] downgraded pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 60ac606..639a9c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ format-jinja = """ """ [tool.poetry.group.docs.dependencies] -sphinx = "^7.3.7" +sphinx = "^7.1" sphinx-rtd-theme = "^2.0.0" markdown-it-py = {version = "~3.0.0", extras = ["plugins"]} myst-parser = "^3.0.1" From bc3c8c22fb538bf4e9543f8821c037b248f91226 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 11:27:26 +0000 Subject: [PATCH 264/331] updatet python version since nump requires python 3.9 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 639a9c6..b6f7257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ exclude =[ "test" ] [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" tqdm = "^4.66.4" numpy = "^1.26.4" openai-whisper = "^20231117" @@ -53,7 +53,7 @@ format-jinja = """ """ [tool.poetry.group.docs.dependencies] -sphinx = "^7.1" +sphinx = "^7.3.7" sphinx-rtd-theme = "^2.0.0" markdown-it-py = {version = "~3.0.0", extras = ["plugins"]} myst-parser = "^3.0.1" From 728ed42223295d222c244e94398c7fb13e63c3fe Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 11:34:38 +0000 Subject: [PATCH 265/331] added testpypi api token --- .github/workflows/pypi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 7d2f83d..06bbb3a 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -25,6 +25,7 @@ jobs: if: ${{ !(startsWith(github.ref, 'refs/tags'))}} uses: JRubics/poetry-publish@v1.16 with: + pypi_token: ${{ secrets.TEST_PYPI_API_TOKEN }} plugins: "poetry-dynamic-versioning" repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" From 4bcd28d0ea2878e0242b1b632ac359737a32de23 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Wed, 15 May 2024 15:18:17 +0200 Subject: [PATCH 266/331] Auto fixes from PEP8, fixes from flake8. --- scraibe/__init__.py | 2 +- scraibe/audio.py | 44 ++++---- scraibe/autotranscript.py | 191 +++++++++++++++++---------------- scraibe/cli.py | 124 +++++++++++---------- scraibe/diarisation.py | 108 ++++++++++--------- scraibe/hallucinations.py | 6 +- scraibe/misc.py | 17 +-- scraibe/transcriber.py | 42 ++++---- scraibe/transcript_exporter.py | 130 +++++++++++----------- scraibe/version.py | 10 +- source/conf.py | 20 ++-- test/test_audio.py | 61 +++-------- test/test_autotranscript.py | 10 +- test/test_diarisation.py | 23 +--- test/test_transcriber.py | 20 ++-- 15 files changed, 391 insertions(+), 417 deletions(-) diff --git a/scraibe/__init__.py b/scraibe/__init__.py index 233cd4f..eb0cc68 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -8,5 +8,5 @@ from .version import get_version as _get_version from .misc import * from .cli import * - + __version__ = _get_version() diff --git a/scraibe/audio.py b/scraibe/audio.py index 4d457b6..7fbc6fb 100644 --- a/scraibe/audio.py +++ b/scraibe/audio.py @@ -28,6 +28,7 @@ import torch SAMPLE_RATE = 16000 NORMALIZATION_FACTOR = 32768.0 + class AudioProcessor: """ Audio Processor class that leverages PyTorchaudio to provide functionalities @@ -39,10 +40,9 @@ class AudioProcessor: sr: int The sample rate of the audio. """ - - def __init__(self, waveform: torch.Tensor, sr : int = SAMPLE_RATE, + + def __init__(self, waveform: torch.Tensor, sr: int = SAMPLE_RATE, *args, **kwargs) -> None: - """ Initialize the AudioProcessor object. @@ -56,16 +56,17 @@ class AudioProcessor: Raises: ValueError: If the provided sample rate is not of type int. """ - - device = kwargs.get("device", "cuda" if torch.cuda.is_available() else "cpu") - + + device = kwargs.get( + "device", "cuda" if torch.cuda.is_available() else "cpu") + self.waveform = waveform.to(device) self.sr = sr - + if not isinstance(self.sr, int): - raise ValueError("Sample rate should be a single value of type int," \ + raise ValueError("Sample rate should be a single value of type int," f"not {len(self.sr)} and type {type(self.sr)}") - + @classmethod def from_file(cls, file: str, *args, **kwargs) -> 'AudioProcessor': """ @@ -77,14 +78,13 @@ class AudioProcessor: Returns: AudioProcessor: An instance of the AudioProcessor class containing the loaded audio. """ - - audio, sr = cls.load_audio(file , *args, **kwargs) + + audio, sr = cls.load_audio(file, *args, **kwargs) audio = torch.from_numpy(audio) - + return cls(audio, sr) - - + def cut(self, start: float, end: float) -> torch.Tensor: """ Cut a segment from the audio waveform between the specified start and end times. @@ -96,7 +96,7 @@ class AudioProcessor: Returns: torch.Tensor: The cut waveform segment. """ - + start = int(start * self.sr) if (isinstance(end, float) or isinstance(end, int)) and isinstance(self.sr, int): end = int(np.ceil(end * self.sr)) @@ -140,11 +140,13 @@ class AudioProcessor: try: out = run(cmd, capture_output=True, check=True).stdout except CalledProcessError as e: - raise RuntimeError(f"Failed to load audio: {e.stderr.decode()}") from e + raise RuntimeError( + f"Failed to load audio: {e.stderr.decode()}") from e + + out = np.frombuffer(out, np.int16).flatten().astype( + np.float32) / NORMALIZATION_FACTOR + + return out, sr - out = np.frombuffer(out, np.int16).flatten().astype(np.float32) / NORMALIZATION_FACTOR - - return out , sr - def __repr__(self) -> str: - return f'TorchAudioProcessor(waveform={len(self.waveform)}, sr={int(self.sr)})' \ No newline at end of file + return f'TorchAudioProcessor(waveform={len(self.waveform)}, sr={int(self.sr)})' diff --git a/scraibe/autotranscript.py b/scraibe/autotranscript.py index cf77a62..14d2451 100644 --- a/scraibe/autotranscript.py +++ b/scraibe/autotranscript.py @@ -55,18 +55,19 @@ class Scraibe: Attributes: transcriber (Transcriber): The transcriber object to handle transcription. diariser (Diariser): The diariser object to handle diarization. - + Methods: __init__: Initializes the Scraibe class with appropriate models. transcribe: Transcribes an audio file using the whisper model and pyannote diarization model. remove_audio_file: Removes the original audio file to avoid disk space issues or ensure data privacy. get_audio_file: Gets an audio file as an AudioProcessor object. """ + def __init__(self, - whisper_model: Union[bool, str, whisper] = None, - whisper_type: str = "whisper", - dia_model : Union[bool, str, DiarisationType] = None, - **kwargs) -> None: + whisper_model: Union[bool, str, whisper] = None, + whisper_type: str = "whisper", + dia_model: Union[bool, str, DiarisationType] = None, + **kwargs) -> None: """Initializes the Scraibe class. Args: @@ -84,12 +85,13 @@ class Scraibe: - save_kwargs: If True, the keyword arguments will be saved for autotranscribe. So you can unload the class and reload it again. """ - - + if whisper_model is None: - self.transcriber = Transcriber.load_model("medium", whisper_type, **kwargs) + self.transcriber = Transcriber.load_model( + "medium", whisper_type, **kwargs) elif isinstance(whisper_model, str): - self.transcriber = Transcriber.load_model(whisper_model, whisper_type, **kwargs) + self.transcriber = Transcriber.load_model( + whisper_model, whisper_type, **kwargs) else: self.transcriber = whisper_model @@ -98,26 +100,25 @@ class Scraibe: elif isinstance(dia_model, str): self.diariser = Diariser.load_model(dia_model, **kwargs) else: - self.diariser : Diariser = dia_model + self.diariser: Diariser = dia_model if kwargs.get("verbose"): print("Scraibe initialized all models successfully loaded.") self.verbose = True else: self.verbose = False - + # Save kwargs for autotranscribe if you want to unload the class and load it again. - if kwargs.get('save_setup'): - self.params = dict(whisper_model = whisper_model, - dia_model = dia_model, + if kwargs.get('save_setup'): + self.params = dict(whisper_model=whisper_model, + dia_model=dia_model, **kwargs) else: self.params = {} - - - def autotranscribe(self, audio_file : Union[str, torch.Tensor, ndarray], - remove_original : bool = False, - **kwargs) -> Transcript: + + def autotranscribe(self, audio_file: Union[str, torch.Tensor, ndarray], + remove_original: bool = False, + **kwargs) -> Transcript: """ Transcribes an audio file using the whisper model and pyannote diarization model. @@ -136,60 +137,62 @@ class Scraibe: if kwargs.get("verbose"): self.verbose = kwargs.get("verbose") # Get audio file as an AudioProcessor object - audio_file : AudioProcessor = self.get_audio_file(audio_file) - + audio_file: AudioProcessor = self.get_audio_file(audio_file) + # Prepare waveform and sample rate for diarization dia_audio = { - "waveform" : audio_file.waveform.reshape(1,len(audio_file.waveform)), + "waveform": audio_file.waveform.reshape(1, len(audio_file.waveform)), "sample_rate": audio_file.sr - } + } if self.verbose: print("Starting diarisation.") - + diarisation = self.diariser.diarization(dia_audio, **kwargs) - + if not diarisation["segments"]: print("No segments found. Try to run transcription without diarisation.") - - transcript = self.transcriber.transcribe(audio_file.waveform, **kwargs) - - final_transcript= {0 : {"speakers" : 'SPEAKER_01', - "segments" : [0, len(audio_file.waveform)], - "text" : transcript}} - + + transcript = self.transcriber.transcribe( + audio_file.waveform, **kwargs) + + final_transcript = {0: {"speakers": 'SPEAKER_01', + "segments": [0, len(audio_file.waveform)], + "text": transcript}} + return Transcript(final_transcript) - + if self.verbose: print("Diarisation finished. Starting transcription.") - - audio_file.sr = torch.Tensor([audio_file.sr]).to(audio_file.waveform.device) - + + audio_file.sr = torch.Tensor([audio_file.sr]).to( + audio_file.waveform.device) + # Transcribe each segment and store the results final_transcript = dict() - - for i in trange(len(diarisation["segments"]), desc= "Transcribing", disable = not self.verbose): - + + for i in trange(len(diarisation["segments"]), desc="Transcribing", disable=not self.verbose): + seg = diarisation["segments"][i] - + audio = audio_file.cut(seg[0], seg[1]) - + transcript = self.transcriber.transcribe(audio, **kwargs) - - final_transcript[i] = {"speakers" : diarisation["speakers"][i], - "segments" : seg, - "text" : transcript} - - # Remove original file if needed + + final_transcript[i] = {"speakers": diarisation["speakers"][i], + "segments": seg, + "text": transcript} + + # Remove original file if needed if remove_original: if kwargs.get("shred") is True: self.remove_audio_file(audio_file, shred=True) else: self.remove_audio_file(audio_file, shred=False) - + return Transcript(final_transcript) - def diarization(self, audio_file : Union[str, torch.Tensor, ndarray], + def diarization(self, audio_file: Union[str, torch.Tensor, ndarray], **kwargs) -> dict: """ Perform diarization on an audio file using the pyannote diarization model. @@ -204,24 +207,24 @@ class Scraibe: dict: A dictionary containing the results of the diarization process. """ - + # Get audio file as an AudioProcessor object - audio_file : AudioProcessor = self.get_audio_file(audio_file) - + audio_file: AudioProcessor = self.get_audio_file(audio_file) + # Prepare waveform and sample rate for diarization dia_audio = { - "waveform" : audio_file.waveform.reshape(1,len(audio_file.waveform)), + "waveform": audio_file.waveform.reshape(1, len(audio_file.waveform)), "sample_rate": audio_file.sr - } - + } + print("Starting diarisation.") - + diarisation = self.diariser.diarization(dia_audio, **kwargs) - + return diarisation - - def transcribe(self, audio_file : Union[str, torch.Tensor, ndarray], - **kwargs): + + def transcribe(self, audio_file: Union[str, torch.Tensor, ndarray], + **kwargs): """ Transcribe the provided audio file. @@ -235,11 +238,11 @@ class Scraibe: str: The transcribed text from the audio source. """ - audio_file : AudioProcessor = self.get_audio_file(audio_file) - - return self.transcriber.transcribe(audio_file.waveform, **kwargs) - - def update_transcriber(self, whisper_model : Union[str, whisper], **kwargs) -> None: + audio_file: AudioProcessor = self.get_audio_file(audio_file) + + return self.transcriber.transcribe(audio_file.waveform, **kwargs) + + def update_transcriber(self, whisper_model: Union[str, whisper], **kwargs) -> None: """ Update the transcriber model. @@ -248,22 +251,23 @@ class Scraibe: The new whisper model to use for transcription. **kwargs: Additional keyword arguments for the transcriber model. - + Returns: None """ _old_model = self.transcriber.model_name - + if isinstance(whisper_model, str): self.transcriber = Transcriber.load_model(whisper_model, **kwargs) elif isinstance(whisper_model, Transcriber): self.transcriber = whisper_model else: - warn(f"Invalid model type. Please provide a valid model. Fallback to old {_old_model} Model.", RuntimeWarning) + warn( + f"Invalid model type. Please provide a valid model. Fallback to old {_old_model} Model.", RuntimeWarning) return None - def update_diariser(self, dia_model : Union[str, DiarisationType], **kwargs) -> None: + def update_diariser(self, dia_model: Union[str, DiarisationType], **kwargs) -> None: """ Update the diariser model. @@ -272,7 +276,7 @@ class Scraibe: The new diariser model to use for diarization. **kwargs: Additional keyword arguments for the diariser model. - + Returns: None """ @@ -281,13 +285,13 @@ class Scraibe: elif isinstance(dia_model, Diariser): self.diariser = dia_model else: - warn(f"Invalid model type. Please provide a valid model. Fallback to old Model.", RuntimeWarning) - + warn("Invalid model type. Please provide a valid model. Fallback to old Model.", RuntimeWarning) + return None - + @staticmethod - def remove_audio_file(audio_file : str, - shred : bool = False) -> None: + def remove_audio_file(audio_file: str, + shred: bool = False) -> None: """ Removes the original audio file to avoid disk space issues or ensure data privacy. @@ -298,30 +302,29 @@ class Scraibe: """ if not os.path.exists(audio_file): raise ValueError(f"Audiofile {audio_file} does not exist.") - + if shred: - + warn("Shredding audiofile can take a long time.", RuntimeWarning) - + gen = iglob(f'{audio_file}', recursive=True) cmd = ['shred', '-zvu', '-n', '10', f'{audio_file}'] - + if os.path.isdir(audio_file): raise ValueError(f"Audiofile {audio_file} is a directory.") - + for file in gen: print(f'shredding {file} now\n') - - run(cmd , check=True) + + run(cmd, check=True) else: os.remove(audio_file) print(f"Audiofile {audio_file} removed.") - - + @staticmethod - def get_audio_file(audio_file : Union[str, torch.Tensor, ndarray], - *args, **kwargs) -> AudioProcessor: + def get_audio_file(audio_file: Union[str, torch.Tensor, ndarray], + *args, **kwargs) -> AudioProcessor: """Gets an audio file as TorchAudioProcessor. Args: @@ -334,20 +337,20 @@ class Scraibe: AudioProcessor: An object containing the waveform and sample rate in torch.Tensor format. """ - + if isinstance(audio_file, str): - audio_file = AudioProcessor.from_file(audio_file) - + audio_file = AudioProcessor.from_file(audio_file) + elif isinstance(audio_file, torch.Tensor): audio_file = AudioProcessor(audio_file[0], audio_file[1]) elif isinstance(audio_file, ndarray): audio_file = AudioProcessor(torch.Tensor(audio_file[0]), - audio_file[1]) - + audio_file[1]) + if not isinstance(audio_file, AudioProcessor): - raise ValueError(f'Audiofile must be of type AudioProcessor,' \ - f'not {type(audio_file)}') - + raise ValueError(f'Audiofile must be of type AudioProcessor,' + f'not {type(audio_file)}') + return audio_file def __repr__(self): diff --git a/scraibe/cli.py b/scraibe/cli.py index 7cc7b1d..b6f2c17 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -4,7 +4,7 @@ allowing for user interaction to transcribe and diarize audio files. The function includes arguments for specifying the audio files, model paths, output formats, and other options necessary for transcription. """ -import os +import os from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter import json @@ -12,7 +12,7 @@ from .autotranscript import Scraibe from .misc import ParseKwargs -from whisper.tokenizer import LANGUAGES , TO_LANGUAGE_CODE +from whisper.tokenizer import LANGUAGES, TO_LANGUAGE_CODE from torch.cuda import is_available from torch import set_num_threads @@ -26,42 +26,43 @@ def cli(): This function can be executed from the command line to perform transcription tasks, providing a user-friendly way to access the Scraibe class functionalities. """ - + def str2bool(string): str2val = {"True": True, "False": False} if string in str2val: return str2val[string] else: - raise ValueError(f"Expected one of {set(str2val.keys())}, got {string}") + raise ValueError( + f"Expected one of {set(str2val.keys())}, got {string}") - parser = ArgumentParser(formatter_class = ArgumentDefaultsHelpFormatter) + parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) group = parser.add_mutually_exclusive_group() - - parser.add_argument("-f","--audio-files", nargs="+", type=str, default=None, + + parser.add_argument("-f", "--audio-files", nargs="+", type=str, default=None, help="List of audio files to transcribe.") - + group.add_argument('--start-server', action='store_true', - help='Start the Gradio app.' \ - 'If set, all other arguments are ignored' \ - 'besides --server-config or --server-kwargs.') - - parser.add_argument("--server-config", type=str, default= None, + help='Start the Gradio app.' + 'If set, all other arguments are ignored' + 'besides --server-config or --server-kwargs.') + + parser.add_argument("--server-config", type=str, default=None, help="Path to the configy.yml file.") - + parser.add_argument('--server-kwargs', nargs='*', action=ParseKwargs, default={}, help='Keyword arguments for the Gradio app.') - + parser.add_argument("--whisper-model-name", default="medium", help="Name of the Whisper model to use.") - parser.add_argument("--whisper-model-directory", type=str, default= None, + parser.add_argument("--whisper-model-directory", type=str, default=None, help="Path to save Whisper model files; defaults to ./models/whisper.") - parser.add_argument("--diarization-directory", type=str, default= None, + parser.add_argument("--diarization-directory", type=str, default=None, help="Path to the diarization model directory.") - parser.add_argument("--hf-token", default= None, type=str, + parser.add_argument("--hf-token", default=None, type=str, help="HuggingFace token for private model download.") parser.add_argument("--inference-device", @@ -82,105 +83,112 @@ def cli(): parser.add_argument("--verbose-output", type=str2bool, default=True, help="Enable or disable progress and debug messages.") - parser.add_argument("--task", type=str, default= 'autotranscribe', # unifinished code + parser.add_argument("--task", type=str, default='autotranscribe', # unifinished code choices=["autotranscribe", "diarization", "autotranscribe+translate", "translate", 'transcribe'], help="Choose to perform transcription, diarization, or translation. \ If set to translate, the output will be translated to English.") parser.add_argument("--language", type=str, default=None, - choices=sorted(LANGUAGES.keys()) + sorted([k.title() for k in TO_LANGUAGE_CODE.keys()]), + choices=sorted( + LANGUAGES.keys()) + sorted([k.title() for k in TO_LANGUAGE_CODE.keys()]), help="Language spoken in the audio. Specify None to perform language detection.") args = parser.parse_args() - + arg_dict = vars(args) - + # configure output out_folder = arg_dict.pop("output_directory") os.makedirs(out_folder, exist_ok=True) out_format = arg_dict.pop("output_format") - - # seup server arg: + + # seup server arg: start_server = arg_dict.pop("start_server") - + task = arg_dict.pop("task") - + if args.num_threads > 0: set_num_threads(arg_dict.pop("num_threads")) - - class_kwargs = {'whisper_model' : arg_dict.pop("whisper_model_name"), + + class_kwargs = {'whisper_model': arg_dict.pop("whisper_model_name"), 'dia_model': arg_dict.pop("diarization_directory"), - 'use_auth_token' : arg_dict.pop("hf_token")} - + 'use_auth_token': arg_dict.pop("hf_token")} + if arg_dict["whisper_model_directory"]: class_kwargs["download_root"] = arg_dict.pop("whisper_model_directory") if not start_server: - + model = Scraibe(**class_kwargs) if arg_dict["audio_files"]: audio_files = arg_dict.pop("audio_files") - + if task == "autotranscribe" or task == "autotranscribe+translate": for audio in audio_files: if task == "autotranscribe+translate": task = "translate" else: task = "transcribe" - - out = model.autotranscribe(audio,task = task, language=arg_dict.pop("language"), verbose = arg_dict.pop("verbose_output")) + + out = model.autotranscribe(audio, task=task, language=arg_dict.pop( + "language"), verbose=arg_dict.pop("verbose_output")) basename = audio.split("/")[-1].split(".")[0] print(f'Saving {basename}.{out_format} to {out_folder}') - out.save(os.path.join(out_folder, f"{basename}.{out_format}")) - + out.save(os.path.join( + out_folder, f"{basename}.{out_format}")) + elif task == "diarization": for audio in audio_files: if arg_dict.pop("verbose_output"): - print(f"Verbose not implemented for diarization.") - + print("Verbose not implemented for diarization.") + out = model.diarization(audio) basename = audio.split("/")[-1].split(".")[0] path = os.path.join(out_folder, f"{basename}.{out_format}") - + print(f'Saving {basename}.{out_format} to {out_folder}') - + with open(path, "w") as f: - json.dump(json.dumps(out, indent= 1), f) + json.dump(json.dumps(out, indent=1), f) elif task == "transcribe" or task == "translate": - + for audio in audio_files: - - out = model.transcribe(audio, task = task, - language= arg_dict.pop("language"), - verbose = arg_dict.pop("verbose_output")) + + out = model.transcribe(audio, task=task, + language=arg_dict.pop("language"), + verbose=arg_dict.pop("verbose_output")) basename = audio.split("/")[-1].split(".")[0] path = os.path.join(out_folder, f"{basename}.{out_format}") with open(path, "w") as f: - f.write(out) - - - else: # unfinished code + f.write(out) + + else: # unfinished code raise NotImplementedError("Currently not Working") import subprocess import sys - - execute_path = os.path.join(os.path.dirname(__file__), "app/app_starter.py") - + + execute_path = os.path.join( + os.path.dirname(__file__), "app/app_starter.py") + config = arg_dict.pop("server_config") server_kwargs = arg_dict.pop("server_kwargs") - + if not config: - subprocess.run([sys.executable, execute_path, f"--server-kwargs={server_kwargs}"]) + subprocess.run([sys.executable, execute_path, + f"--server-kwargs={server_kwargs}"]) elif not server_kwargs: - subprocess.run([sys.executable, execute_path, f"--server-config={config}"]) + subprocess.run([sys.executable, execute_path, + f"--server-config={config}"]) elif not config and not server_kwargs: subprocess.run([sys.executable, execute_path]) else: - subprocess.run([sys.executable, execute_path, f"--server-config={config}", f"--server-kwargs={server_kwargs}"]) + subprocess.run([sys.executable, execute_path, + f"--server-config={config}", f"--server-kwargs={server_kwargs}"]) + if __name__ == "__main__": - cli() \ No newline at end of file + cli() diff --git a/scraibe/diarisation.py b/scraibe/diarisation.py index ade9220..d70df99 100644 --- a/scraibe/diarisation.py +++ b/scraibe/diarisation.py @@ -37,15 +37,16 @@ from pyannote.audio import Pipeline from pyannote.audio.pipelines.speaker_diarization import SpeakerDiarization from torch import Tensor from torch import device as torch_device -from torch.cuda import is_available, current_device +from torch.cuda import is_available from huggingface_hub import HfApi from huggingface_hub.utils import RepositoryNotFoundError from .misc import PYANNOTE_DEFAULT_PATH, PYANNOTE_DEFAULT_CONFIG -Annotation = TypeVar('Annotation') +Annotation = TypeVar('Annotation') TOKEN_PATH = os.path.join(os.path.dirname( - os.path.realpath(__file__)), '.pyannotetoken') + os.path.realpath(__file__)), '.pyannotetoken') + class Diariser: """ @@ -55,12 +56,12 @@ class Diariser: Args: model: The pretrained model to use for diarization. """ - + def __init__(self, model) -> None: self.model = model - def diarization(self, audiofile : Union[str, Tensor, dict] , + def diarization(self, audiofile: Union[str, Tensor, dict], *args, **kwargs) -> Annotation: """ Perform speaker diarization on the provided audio file, @@ -79,15 +80,15 @@ class Diariser: to the diarization process. """ kwargs = self._get_diarisation_kwargs(**kwargs) - - diarization = self.model(audiofile,*args, **kwargs) + + diarization = self.model(audiofile, *args, **kwargs) out = self.format_diarization_output(diarization) return out @staticmethod - def format_diarization_output(dia : Annotation) -> dict: + def format_diarization_output(dia: Annotation) -> dict: """ Formats the raw diarization output into a more usable structure for this project. @@ -99,14 +100,14 @@ class Diariser: as keys and a list of tuples representing segments as values. """ - dia_list = list(dia.itertracks(yield_label=True)) + dia_list = list(dia.itertracks(yield_label=True)) diarization_output = {"speakers": [], "segments": []} normalized_output = [] index_start_speaker = 0 index_end_speaker = 0 current_speaker = str() - + ### # Sometimes two consecutive speakers are the same # This loop removes these duplicates @@ -115,40 +116,39 @@ class Diariser: if len(dia_list) == 1: normalized_output.append([0, 0, dia_list[0][2]]) else: - + for i, (_, _, speaker) in enumerate(dia_list): - + if i == 0: current_speaker = speaker - + if speaker != current_speaker: index_end_speaker = i - 1 normalized_output.append([index_start_speaker, - index_end_speaker, - current_speaker]) + index_end_speaker, + current_speaker]) index_start_speaker = i current_speaker = speaker - if i == len(dia_list) - 1: index_end_speaker = i - - normalized_output.append([index_start_speaker, - index_end_speaker, - current_speaker]) - + + normalized_output.append([index_start_speaker, + index_end_speaker, + current_speaker]) + for outp in normalized_output: - start = dia_list[outp[0]][0].start - end = dia_list[outp[1]][0].end + start = dia_list[outp[0]][0].start + end = dia_list[outp[1]][0].end diarization_output["segments"].append([start, end]) diarization_output["speakers"].append(outp[2]) return diarization_output - + @staticmethod def _get_token(): """ @@ -161,14 +161,14 @@ class Diariser: Returns: str: The Huggingface token. """ - + if os.path.exists(TOKEN_PATH): with open(TOKEN_PATH, 'r', encoding="utf-8") as file: token = file.read() else: - raise ValueError('No token found.' \ - 'Please create a token at https://huggingface.co/settings/token' \ - f'and save it in a file called {TOKEN_PATH}') + raise ValueError('No token found.' + 'Please create a token at https://huggingface.co/settings/token' + f'and save it in a file called {TOKEN_PATH}') return token @staticmethod @@ -182,18 +182,17 @@ class Diariser: """ with open(TOKEN_PATH, 'w', encoding="utf-8") as file: file.write(token) - + @classmethod - def load_model(cls, - model: str = PYANNOTE_DEFAULT_CONFIG, - use_auth_token: str = None, - cache_token: bool = False, - cache_dir: Union[Path, str] = PYANNOTE_DEFAULT_PATH, - hparams_file: Union[str, Path] = None, - device: str = None, - *args, **kwargs - ) -> Pipeline: - + def load_model(cls, + model: str = PYANNOTE_DEFAULT_CONFIG, + use_auth_token: str = None, + cache_token: bool = False, + cache_dir: Union[Path, str] = PYANNOTE_DEFAULT_PATH, + hparams_file: Union[str, Path] = None, + device: str = None, + *args, **kwargs + ) -> Pipeline: """ Loads a pretrained model from pyannote.audio, either from a local cache or some online repository. @@ -237,16 +236,18 @@ class Diariser: 'deprecated and will be removed in future versions.', category=DeprecationWarning) # list elementes with the ending .bin - bin_files = [f for f in os.listdir(pwd) if f.endswith(".bin")] + bin_files = [f for f in os.listdir( + pwd) if f.endswith(".bin")] if len(bin_files) == 1: path_to_model = os.path.join(pwd, bin_files[0]) else: - warnings.warn("Found more than one .bin file. "\ - "or none. Please specify the path to the model " \ - "or setup a huggingface token.") + warnings.warn("Found more than one .bin file. " + "or none. Please specify the path to the model " + "or setup a huggingface token.") raise FileNotFoundError - warnings.warn(f"Found model at {path_to_model} overwriting config file.") + warnings.warn( + f"Found model at {path_to_model} overwriting config file.") config['pipeline']['params']['segmentation'] = path_to_model @@ -270,22 +271,24 @@ class Diariser: if use_auth_token is None: use_auth_token = cls._get_token() else: - raise FileNotFoundError(f'No local model or directory found at {model}.') + raise FileNotFoundError( + f'No local model or directory found at {model}.') _model = Pipeline.from_pretrained(model, use_auth_token=use_auth_token, cache_dir=cache_dir, hparams_file=hparams_file,) if _model is None: - raise ValueError('Unable to load model either from local cache' \ - 'or from huggingface.co models. Please check your token' \ - 'or your local model path') + raise ValueError('Unable to load model either from local cache' + 'or from huggingface.co models. Please check your token' + 'or your local model path') # try to move the model to the device if device is None: device = "cuda" if is_available() else "cpu" - _model = _model.to(torch_device(device)) # torch_device is renamed from torch.device to avoid name conflict + # torch_device is renamed from torch.device to avoid name conflict + _model = _model.to(torch_device(device)) return cls(_model) @@ -302,9 +305,10 @@ class Diariser: """ _possible_kwargs = SpeakerDiarization.apply.__code__.co_varnames - diarisation_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} - + diarisation_kwargs = {k: v for k, + v in kwargs.items() if k in _possible_kwargs} + return diarisation_kwargs - + def __repr__(self): return f"Diarisation(model={self.model})" diff --git a/scraibe/hallucinations.py b/scraibe/hallucinations.py index a337ec0..249cce5 100644 --- a/scraibe/hallucinations.py +++ b/scraibe/hallucinations.py @@ -1,6 +1,6 @@ # List of known hallucinations - adapted from: # https://github.com/openai/whisper/discussions/928 -KNOWN_HALLUCINATIONS=[ +KNOWN_HALLUCINATIONS = [ # en " www.mooji.org" # nl @@ -73,7 +73,7 @@ KNOWN_HALLUCINATIONS=[ " Sous-titres réalisés para la communauté d'Amara.org" # ln " Sous-titres réalisés para la communauté d'Amara.org" - # pl + # pl " Napisy stworzone przez społeczność Amara.org", " Napisy wykonane przez społeczność Amara.org", " Zdjęcia i napisy stworzone przez społeczność Amara.org", @@ -92,4 +92,4 @@ KNOWN_HALLUCINATIONS=[ # zh "字幕由Amara.org社区提供", "小編字幕由Amara.org社區提供" -] \ No newline at end of file +] diff --git a/scraibe/misc.py b/scraibe/misc.py index c1d5484..e8837d3 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -14,8 +14,9 @@ if CACHE_DIR != PYANNOTE_CACHE_DIR: WHISPER_DEFAULT_PATH = os.path.join(CACHE_DIR, "whisper") PYANNOTE_DEFAULT_PATH = os.path.join(CACHE_DIR, "pyannote") PYANNOTE_DEFAULT_CONFIG = os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml") \ - if os.path.exists(os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml")) \ - else ('jaikinator/scraibe', 'pyannote/speaker-diarization-3.1') + if os.path.exists(os.path.join(PYANNOTE_DEFAULT_PATH, "config.yaml")) \ + else ('jaikinator/scraibe', 'pyannote/speaker-diarization-3.1') + def config_diarization_yaml(file_path: str, path_to_segmentation: str = None) -> None: """Configure diarization pipeline from a YAML file. @@ -33,25 +34,29 @@ def config_diarization_yaml(file_path: str, path_to_segmentation: str = None) -> with open(file_path, "r") as stream: yml = yaml.safe_load(stream) - segmentation_path = path_to_segmentation or os.path.join(PYANNOTE_DEFAULT_PATH, "pytorch_model.bin") + segmentation_path = path_to_segmentation or os.path.join( + PYANNOTE_DEFAULT_PATH, "pytorch_model.bin") yml["pipeline"]["params"]["segmentation"] = segmentation_path if not os.path.exists(segmentation_path): - raise FileNotFoundError(f"Segmentation model not found at {segmentation_path}") + raise FileNotFoundError( + f"Segmentation model not found at {segmentation_path}") with open(file_path, "w") as stream: yaml.dump(yml, stream) + class ParseKwargs(Action): """ Custom argparse action to parse keyword arguments. """ + def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, dict()) for value in values: key, value = value.split('=') try: - value = eval(value) + value = ast.literal_eval(value) except: pass - getattr(namespace, self.dest)[key] = value \ No newline at end of file + getattr(namespace, self.dest)[key] = value diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index 977cd94..8802cf6 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -28,11 +28,11 @@ from whisper import Whisper from whisper import load_model as whisper_load_model from whisperx.asr import WhisperModel from whisperx import load_model as whisperx_load_model -from typing import TypeVar , Union , Optional +from typing import TypeVar, Union, Optional from torch import Tensor, device from numpy import ndarray from inspect import getfullargspec -from abc import ABC, abstractmethod +from abc import abstractmethod from .misc import WHISPER_DEFAULT_PATH whisper = TypeVar('whisper') @@ -66,6 +66,7 @@ class Transcriber: The class supports various sizes and versions of Whisper models. Please refer to the load_model method for available options. """ + def __init__(self, model: whisper, model_name: str) -> None: """ Initialize the Transcriber class with a Whisper model. @@ -74,13 +75,13 @@ class Transcriber: model (whisper): The Whisper model to use for transcription. model_name (str): The name of the model. """ - + self.model = model - + self.model_name = model_name @abstractmethod - def transcribe(self, audio: Union[str, Tensor, ndarray] , + def transcribe(self, audio: Union[str, Tensor, ndarray], *args, **kwargs) -> str: """ Transcribe an audio file. @@ -95,9 +96,9 @@ class Transcriber: str: The transcript as a string. """ pass - + @staticmethod - def save_transcript(transcript : str , save_path : str) -> None: + def save_transcript(transcript: str, save_path: str) -> None: """ Save a transcript to a file. @@ -111,7 +112,7 @@ class Transcriber: with open(save_path, 'w') as f: f.write(transcript) - + print(f'Transcript saved to {save_path}') @classmethod @@ -176,10 +177,10 @@ class Transcriber: dict: Keyword arguments for whisper model. """ pass - + def __repr__(self) -> str: return f"Transcriber(model_name={self.model_name}, model={self.model})" - + class WhisperTranscriber(Transcriber): def __init__(self, model: whisper, model_name: str) -> None: @@ -233,10 +234,10 @@ class WhisperTranscriber(Transcriber): - 'large-v2' - 'large-v3' - 'large' - + download_root (str, optional): Path to download the model. Defaults to WHISPER_DEFAULT_PATH. - + device (Optional[Union[str, torch.device]], optional): Device to load model on. Defaults to None. in_memory (bool, optional): Whether to load model in memory. @@ -266,7 +267,8 @@ class WhisperTranscriber(Transcriber): _kwargs = getfullargspec(Whisper.transcribe).kwonlyargs _possible_kwargs = _args + _kwargs - whisper_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} + whisper_kwargs = {k: v for k, + v in kwargs.items() if k in _possible_kwargs} if (task := kwargs.get("task")): whisper_kwargs["task"] = task @@ -280,7 +282,7 @@ class WhisperTranscriber(Transcriber): class WhisperXTranscriber(Transcriber): def __init__(self, model: whisper, model_name: str) -> None: super().__init__(model, model_name) - + def transcribe(self, audio: Union[str, Tensor, ndarray], *args, **kwargs) -> str: """ @@ -296,7 +298,7 @@ class WhisperXTranscriber(Transcriber): str: The transcript as a string. """ kwargs = self._get_whisper_kwargs(**kwargs) - + if isinstance(audio, Tensor): audio = audio.cpu().numpy() result = self.model.transcribe(audio, *args, **kwargs) @@ -304,8 +306,7 @@ class WhisperXTranscriber(Transcriber): for seg in result['segments']: text += seg['text'] return text - - + @classmethod def load_model(cls, model: str = "medium", @@ -330,10 +331,10 @@ class WhisperXTranscriber(Transcriber): - 'large-v2' - 'large-v3' - 'large' - + download_root (str, optional): Path to download the model. Defaults to WHISPER_DEFAULT_PATH. - + device (Optional[Union[str, torch.device]], optional): Device to load model on. Defaults to None. in_memory (bool, optional): Whether to load model in memory. @@ -364,7 +365,8 @@ class WhisperXTranscriber(Transcriber): _kwargs = getfullargspec(WhisperModel.transcribe).kwonlyargs _possible_kwargs = _args + _kwargs - whisper_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} + whisper_kwargs = {k: v for k, + v in kwargs.items() if k in _possible_kwargs} if (task := kwargs.get("task")): whisper_kwargs["task"] = task diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index 1ce43d4..5222d58 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -1,5 +1,6 @@ import json import time +from json.decoder import JSONDecodeError from typing import Union @@ -8,13 +9,12 @@ from .hallucinations import KNOWN_HALLUCINATIONS ALPHABET = [*"abcdefghijklmnopqrstuvwxyz"] - class Transcript: """ Class for storing transcript data, including speaker information and text segments, and exporting it to various file formats such as JSON, HTML, and LaTeX. """ - + def __init__(self, transcript: dict) -> None: """ Initializes the Transcript object with the given transcript data. @@ -30,7 +30,7 @@ class Transcript: self.speakers = self._extract_speakers() self.segments = self._extract_segments() self.annotation = {} - + def annotate(self, *args, **kwargs) -> dict: """ Annotates the transcript to associate specific names with speakers. @@ -46,36 +46,41 @@ class Transcript: ValueError: If the number of speaker names does not match the number of speakers, or if an unknown speaker is found. """ - + annotations = {} if args and len(args) != len(self.speakers): - raise ValueError("Number of speaker names does not match number of speakers") - + raise ValueError( + "Number of speaker names does not match number of speakers") + if args: for arg, speaker in zip(args, sorted(self.speakers)): - + annotations[speaker] = arg - + invalid_speakers = set(kwargs.keys()) - set(self.speakers) if invalid_speakers: - raise ValueError(f"These keys are not speakers: {', '.join(invalid_speakers)}") + raise ValueError( + f"These keys are not speakers: {', '.join(invalid_speakers)}") - annotations.update({key: kwargs[key] for key in self.speakers if key in kwargs}) + annotations.update({key: kwargs[key] + for key in self.speakers if key in kwargs}) self.annotation = annotations - + return self - + def _remove_hallucinations(self) -> None: """ Removes all occurances of known hallucinations from all segments of the transcript. Segments that are identical to empty strings afterwards are removed from the transcript. """ - segments_to_drop=[] + segments_to_drop = [] for id in self.transcript: for snippet in KNOWN_HALLUCINATIONS: - self.transcript[id]['text']=self.transcript[id]['text'].replace(snippet,'') - if self.transcript[id]['text'] == '': segments_to_drop.append(id) + self.transcript[id]['text'] = self.transcript[id]['text'].replace( + snippet, '') + if self.transcript[id]['text'] == '': + segments_to_drop.append(id) for id in segments_to_drop: del self.transcript[id] @@ -87,9 +92,9 @@ class Transcript: Returns: list: List of unique speaker names in the transcript. """ - + return list(set([self.transcript[id]["speakers"] for id in self.transcript])) - + def _extract_segments(self) -> list: """ Extracts all the text segments from the transcript. @@ -109,23 +114,23 @@ class Transcript: time stamps for each segment. """ fstring = "" - + for _id in self.transcript: seq = self.transcript[_id] - + if self.annotation: speaker = self.annotation[seq["speakers"]] else: speaker = seq["speakers"] - + segm = seq["segments"] - sseg = time.strftime("%H:%M:%S",time.gmtime(segm[0])) - eseg = time.strftime("%H:%M:%S",time.gmtime(segm[1])) - + sseg = time.strftime("%H:%M:%S", time.gmtime(segm[0])) + eseg = time.strftime("%H:%M:%S", time.gmtime(segm[1])) + fstring += f"{speaker} ({sseg} ; {eseg}):\t{seq['text']}\n" - + return fstring - + def __repr__(self) -> str: """Return a string representation of the Transcript object. @@ -133,8 +138,8 @@ class Transcript: str: A string that provides an informative description of the object. """ return f"Transcript(speakers = {self.speakers},"\ - f"segments = {self.segments}, annotation = {self.annotation})" - + f"segments = {self.segments}, annotation = {self.annotation})" + def get_dict(self) -> dict: """ Get transcript as dict @@ -142,10 +147,10 @@ class Transcript: :return: transcript as dict :rtype: dict """ - + return self.transcript - - def get_json(self, *args, use_annotation : bool = True, **kwargs) -> str: + + def get_json(self, *args, use_annotation: bool = True, **kwargs) -> str: """ Get transcript as json string :return: transcript as json string @@ -153,14 +158,14 @@ class Transcript: """ if "indent" not in kwargs: kwargs["indent"] = 3 - + if use_annotation and self.annotation: for _id in self.transcript: seq = self.transcript[_id] seq["speakers"] = self.annotation[seq["speakers"]] - + return json.dumps(self.transcript, *args, **kwargs) - + def get_html(self) -> str: """ Get transcript as html string @@ -171,9 +176,9 @@ class Transcript: html = "

    " + self.__str__().replace("\n", "
    ") + "

    " html = "" + html + "" html = html.replace("\t", "    ") - - return html - + + return html + def get_md(self) -> str: """Get transcript as Markdown string, using HTML formatting. @@ -181,7 +186,7 @@ class Transcript: str: Transcript as a Markdown string. """ return self.get_html() - + def get_tex(self) -> str: """Get transcript as LaTeX string. If no annotations are present, the speakers will be annotated with the first letters of the alphabet. @@ -192,43 +197,42 @@ class Transcript: if not self.annotation: self.annotate(*ALPHABET[:len(self.speakers)]) - - fstring ="\\begin{drama}" - + + fstring = "\\begin{drama}" + for speaker in self.speakers: - - fstring += "\n\t\\Character{"+ str(self.annotation[speaker]) + "}" \ - "{"+ str(self.annotation[speaker]) + "}" - + + fstring += "\n\t\\Character{" + str(self.annotation[speaker]) + "}" \ + "{" + str(self.annotation[speaker]) + "}" + for id in self.transcript: seq = self.transcript[id] speaker = self.annotation[seq["speakers"]] fstring += f"\n\\{speaker}speaks:\n{seq['text']}" - + fstring += "\n\\end{drama}" - + return fstring - - - def to_json(self,path, *args, **kwargs) -> None: + + def to_json(self, path, *args, **kwargs) -> None: """Save transcript as json file - + Args: path (str): path to save file """ with open(path, "w") as f: json.dump(self.transcript, f, *args, **kwargs) - + def to_txt(self, path: str) -> None: """Save transcript as a LaTeX file (placeholder function, implementation needed). Args: path (str): Path to save the LaTeX file. """ - + with open(path, "w") as f: f.write(self.__str__()) - + def to_md(self, path: str) -> None: """Get transcript as Markdown string, using HTML formatting. @@ -236,7 +240,7 @@ class Transcript: str: Transcript as a Markdown string. """ return self.to_html(path) - + def to_html(self, path: str) -> None: """ Save transcript as html file @@ -244,10 +248,10 @@ class Transcript: :param path: path to save file :type path: str """ - + with open(path, "w") as file: file.write(self.get_html()) - + def to_tex(self, path: str) -> None: """Save transcript as a LaTeX file (placeholder function, implementation needed). @@ -255,7 +259,7 @@ class Transcript: path (str): Path to save the LaTeX file. """ pass - + def to_pdf(self, path: str) -> None: """Save transcript as a PDF file (placeholder function, implementation needed). @@ -263,7 +267,7 @@ class Transcript: path (str): Path to save the PDF file. """ pass - + def save(self, path: str, *args, **kwargs) -> None: """Save transcript to file with the given path and file format. @@ -279,7 +283,7 @@ class Transcript: Raises: ValueError: If the file format specified in the path is unknown. """ - + if path.endswith(".json"): self.to_json(path, *args, **kwargs) elif path.endswith(".txt"): @@ -294,7 +298,7 @@ class Transcript: self.to_pdf(path, *args, **kwargs) else: raise ValueError("Unknown file format") - + @classmethod def from_json(cls, json: Union[dict, str]) -> "Transcript": """Load transcript from json file @@ -310,10 +314,8 @@ class Transcript: else: try: transcript = json.loads(json) - except: + except (TypeError, JSONDecodeError): with open(json, "r") as f: transcript = json.load(f) - - return cls(transcript) - \ No newline at end of file + return cls(transcript) diff --git a/scraibe/version.py b/scraibe/version.py index 9c4faa5..6bef146 100644 --- a/scraibe/version.py +++ b/scraibe/version.py @@ -10,6 +10,8 @@ VERSION = '%d.%d.%d.%d' % (MAJOR, MINOR, MICRO, NANO) # Return the git revision as a string # taken from numpy/numpy + + def git_version(): def _minimal_ext_cmd(cmd): # construct minimal environment @@ -24,7 +26,8 @@ def git_version(): env['LANG'] = 'C' env['LC_ALL'] = 'C' - out = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE, env=env).communicate()[0] + out = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE, + env=env).communicate()[0] return out try: @@ -35,6 +38,7 @@ def git_version(): return GIT_REVISION + def _get_git_version(): cwd = os.getcwd() @@ -51,6 +55,7 @@ def _get_git_version(): os.chdir(cwd) return res + def get_version(build_version=False): if ISRELEASED: return VERSION @@ -64,6 +69,3 @@ def get_version(build_version=False): return VERSION + ".dev" + date else: return VERSION + ".dev0+" + GIT_REVISION[:7] - - - diff --git a/source/conf.py b/source/conf.py index ba51ab3..43fe803 100644 --- a/source/conf.py +++ b/source/conf.py @@ -31,16 +31,16 @@ release = '0.1.1' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'sphinx.ext.napoleon', - 'myst_parser'] + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinx.ext.napoleon', + 'myst_parser'] # Napoleon settings napoleon_google_docstring = True diff --git a/test/test_audio.py b/test/test_audio.py index 311a472..aee6cb3 100644 --- a/test/test_audio.py +++ b/test/test_audio.py @@ -3,7 +3,6 @@ from scraibe.audio import AudioProcessor import torch - DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") TEST_WAVEFORM = torch.sin(torch.randn(160000)).to(DEVICE) TEST_SR = 16000 @@ -14,21 +13,17 @@ NORMALIZATION_FACTOR = 32768 @pytest.fixture def probe_audio_processor(): """Fixture for creating an instance of the AudioProcessor class with test waveform and sample rate. - + This fixture is used to create an instance of the AudioProcessor class with a predfined test waveform and sample rate (TEST_SR). It returns the instantiated AudioProcessor , which can bes used as a dependency in other test functions. Returns: AudioProcessor (obj): An instance of the AudioProcessor class with the test waveform and sample rate. - """ + """ return AudioProcessor(TEST_WAVEFORM, TEST_SR) - - - - def test_AudioProcessor_init(probe_audio_processor): """ Test the initialization of the AudioProcessor class. @@ -43,20 +38,19 @@ def test_AudioProcessor_init(probe_audio_processor): Returns: None - - - """ + + + """ assert isinstance(probe_audio_processor, AudioProcessor) assert probe_audio_processor.waveform.device == TEST_WAVEFORM.device assert torch.equal(probe_audio_processor.waveform, TEST_WAVEFORM) assert probe_audio_processor.sr == TEST_SR - def test_cut(probe_audio_processor): """Test the cut function of the AudioProcessor class. - + This test verifies that the cut function correctly extracts a segment of audio data from the waveform, given start and end indices. It checks whether the size of the extracted segment matches the expected size based on the provided start and end indices and the sample rate. @@ -65,63 +59,38 @@ def test_cut(probe_audio_processor): None - """ - + """ + start = 4 end = 7 trimmed_waveform = probe_audio_processor.cut(start, end) expected_size = int((end - start) * TEST_SR) real_size = trimmed_waveform.size(0) assert real_size == expected_size - #assert AudioProcessor(TEST_WAVEFORM, TEST_SR).cut(start, end).size() == int((end - start) * TEST_SR) - - - - - - - - + # assert AudioProcessor(TEST_WAVEFORM, TEST_SR).cut(start, end).size() == int((end - start) * TEST_SR) def test_audio_processor_invalid_sr(): """Test the behavior of AudioProcessor when an invalid smaple rate is provided. - + This test verifies that the AudioProcessor constructor raises a ValueError when an invalid sample rate is provided. It uses the pytest.raises context manager to check if the ValueError is raised when initializing an AudioProcessor object with an invalid sample rate. Returns: None - """ + """ with pytest.raises(ValueError): - AudioProcessor(TEST_WAVEFORM, [44100,48000]) + AudioProcessor(TEST_WAVEFORM, [44100, 48000]) def test_audio_processor_SAMPLE_RATE(): """Test the default sample rate of the AudioProcessor class. - + This test verifies that the default sample rate of the AudioProcessor class matches the expected value defined by the constant SAMPLE_RATE. It instantiates an AudioProcessor object with a test waveform and checks whether the sample rate attribute (sr) of the AudioProcessor object equals the predefined constant SAMPLE_RATE. Returns: None - """ + """ probe_audio_processor = AudioProcessor(TEST_WAVEFORM) - assert probe_audio_processor.sr == SAMPLE_RATE - - - - - - - - - - - - - - - - - + assert probe_audio_processor.sr == SAMPLE_RATE diff --git a/test/test_autotranscript.py b/test/test_autotranscript.py index edbe0f7..78442b3 100644 --- a/test/test_autotranscript.py +++ b/test/test_autotranscript.py @@ -1,20 +1,14 @@ import pytest from scraibe import Scraibe, Diariser, Transcriber, Transcript -from unittest.mock import MagicMock, patch import os - - - @pytest.fixture def create_scraibe_instance(): if "HF_TOKEN" in os.environ: - return Scraibe(use_auth_token=os.environ["HF_TOKEN"] ) + return Scraibe(use_auth_token=os.environ["HF_TOKEN"]) else: return Scraibe() - - def test_scraibe_init(create_scraibe_instance): @@ -47,7 +41,7 @@ def test_scraibe_transcribe(create_scraibe_instance): model.remove_audio_file("non_existing_audio_file") model.remove_audio_file("audio_test_2.mp4") - assert not os.path.exists("audio_test_2.mp4") """ + assert not os.path.exists("audio_test_2.mp4") """ """ def test_get_audio_file(create_scraibe_instance): diff --git a/test/test_diarisation.py b/test/test_diarisation.py index d1d26f3..01431be 100644 --- a/test/test_diarisation.py +++ b/test/test_diarisation.py @@ -1,8 +1,5 @@ import pytest -import os -from unittest import mock -from scraibe import diarisation, Diariser - +from scraibe import Diariser @pytest.fixture @@ -15,11 +12,10 @@ def diariser_instance(): Returns: Diariser(Obj): An instance of the Diariser class with a mocked token. """ - #with mock.patch.object(Diariser, '_get_token', return_value = 'HF_TOKEN' ): + # with mock.patch.object(Diariser, '_get_token', return_value = 'HF_TOKEN' ): return Diariser('pyannote') - def test_Diariser_init(diariser_instance): """Test the initialization of the Diariser class. @@ -30,18 +26,7 @@ def test_Diariser_init(diariser_instance): Args: diariser_instance (obj): instance of the Diariser class - Returns: + Returns: None - """ + """ assert diariser_instance.model == 'pyannote' - - - - - - - - - - - diff --git a/test/test_transcriber.py b/test/test_transcriber.py index 3a4a0dc..fee3aff 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -1,25 +1,23 @@ import pytest -from unittest.mock import patch from scraibe import Transcriber import torch - DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") TEST_WAVEFORM = "Hello World" -""" +""" @pytest.mark.parametrize("audio_file, expected_transcription",[("path_to_test_audiofile", "test_transcription")] ) @patch("scraibe.Transcriber.load_model") def test_transcriber(mock_load_model, audio_file, expected_transcription): - + Args: mock_load_model (_type_): _description_ audio_file (_type_): _description_ expected_transcription (_type_): _description_ - + mock_model = mock_load_model.return_value mock_model.transcribe.return_value ={"text": expected_transcription} @@ -29,24 +27,24 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): assert transcription_result == expected_transcription """ + @pytest.fixture def transcriber_instance(): return Transcriber.load_model('medium') + def test_transcriber_initialization(transcriber_instance): assert isinstance(transcriber_instance, Transcriber) + def test_get_whisper_kwargs(): - kwargs = {"arg1": 1, "arg3": 3} + kwargs = {"arg1": 1, "arg3": 3} valid_kwargs = Transcriber._get_whisper_kwargs(**kwargs) - assert not valid_kwargs == {"arg1": 1, "arg3": 3} + assert not valid_kwargs == {"arg1": 1, "arg3": 3} def test_transcribe(transcriber_instance): model = transcriber_instance - #mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) + # mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) transcript = model.transcribe('test/audio_test_2.mp4') assert isinstance(transcript, str) - - - From da3dbffba1a392179a500adb6df4de7c8726e731 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Wed, 15 May 2024 15:20:17 +0200 Subject: [PATCH 267/331] Fixes literal eval import --- scraibe/misc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scraibe/misc.py b/scraibe/misc.py index e8837d3..f12335f 100644 --- a/scraibe/misc.py +++ b/scraibe/misc.py @@ -2,6 +2,7 @@ import os import yaml from pyannote.audio.core.model import CACHE_DIR as PYANNOTE_CACHE_DIR from argparse import Action +from ast import literal_eval CACHE_DIR = os.getenv( "AUTOT_CACHE", @@ -56,7 +57,7 @@ class ParseKwargs(Action): for value in values: key, value = value.split('=') try: - value = ast.literal_eval(value) + value = literal_eval(value) except: pass getattr(namespace, self.dest)[key] = value From d1e5b023ff9338477d97e0ca1dd307dd89fe69c1 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 14:06:20 +0000 Subject: [PATCH 268/331] test dynamic versioning on specific branch --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b6f7257..a236a90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,8 @@ strict = true format-jinja = """ {%- if distance == 0 -%} {{ serialize_pep440(base) }} + {%- elif branch == pyproject.toml -%} + {{ serialize_pep440(bump_version(base), dev = distance) }} {%- else -%} {{ serialize_pep440(bump_version(base), dev=distance, metadata=[commit]) }} {%- endif -%} From 9f0a176b266d613cc701d7b882da5106e834a010 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 14:07:59 +0000 Subject: [PATCH 269/331] added string tag --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a236a90..730fb2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ strict = true format-jinja = """ {%- if distance == 0 -%} {{ serialize_pep440(base) }} - {%- elif branch == pyproject.toml -%} + {%- elif branch == 'pyproject.toml' -%} {{ serialize_pep440(bump_version(base), dev = distance) }} {%- else -%} {{ serialize_pep440(bump_version(base), dev=distance, metadata=[commit]) }} From 2577f347d1da51c56687ad74984bf53b11d14fd7 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Wed, 15 May 2024 16:23:47 +0000 Subject: [PATCH 270/331] added first test for a nighlty build --- .github/workflows/pypi.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 06bbb3a..9181e65 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -21,8 +21,13 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: '0' + - name: Setup package Name + if: ${{ github.ref_name == 'pyproject.toml'}} + run: | + sed -i 's/name = "scraibe"/name = "scraibe-nightly"/' pyproject.toml + echo "You are running on the develop branch deploying to nightly!" - name: Set up Poetry 📦 - if: ${{ !(startsWith(github.ref, 'refs/tags'))}} + if: ${{ !(startsWith(github.ref, 'refs/tags')) }} uses: JRubics/poetry-publish@v1.16 with: pypi_token: ${{ secrets.TEST_PYPI_API_TOKEN }} From 5918458d473e62445e1e9ef1103812fdfe5646ec Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 07:13:02 +0000 Subject: [PATCH 271/331] try get scraibe version for later test install --- .github/workflows/pypi.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 9181e65..dd49ce9 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -14,18 +14,12 @@ on: type: boolean jobs: - Build-and-publish-to-Test-PyPI: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: '0' - - name: Setup package Name - if: ${{ github.ref_name == 'pyproject.toml'}} - run: | - sed -i 's/name = "scraibe"/name = "scraibe-nightly"/' pyproject.toml - echo "You are running on the develop branch deploying to nightly!" - name: Set up Poetry 📦 if: ${{ !(startsWith(github.ref, 'refs/tags')) }} uses: JRubics/poetry-publish@v1.16 @@ -34,6 +28,17 @@ jobs: plugins: "poetry-dynamic-versioning" repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" + - name: Get the version + run: | + echo "SCRAIBE_VERSION=$(python -c 'import scraibe; print(scraibe.__version__)')" >> $GITHUB_ENV + echo "SCRAIBE_VERSION=$SCRAIBE_VERSION" + - name: 'Upload Artifact' + uses: actions/upload-artifact@v4 + with: + name: build + path: ./sdist + retention-days: 5 + test-install: name: Test Installation from TestPyPI From 21052df03a9449600079bb1875b7e816d6d1322e Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 07:29:43 +0000 Subject: [PATCH 272/331] added version from wheel --- .github/workflows/pypi.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index dd49ce9..73e23be 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -28,10 +28,13 @@ jobs: plugins: "poetry-dynamic-versioning" repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" - - name: Get the version + - name: Get the version from wheel run: | - echo "SCRAIBE_VERSION=$(python -c 'import scraibe; print(scraibe.__version__)')" >> $GITHUB_ENV - echo "SCRAIBE_VERSION=$SCRAIBE_VERSION" + WHEEL_FILE=$(find dist -name "*.whl" | head -n 1) + VERSION=$(python -c "from pkginfo import Wheel; w = Wheel('$WHEEL_FILE'); print(w.version)") + echo "SCRAIBE_VERSION=$VERSION" >> $GITHUB_ENV + echo "SCRAIBE_VERSION=$VERSION" + - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: From ab2cbbbd69176fbd9382d98db55562019e6133c7 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 07:37:03 +0000 Subject: [PATCH 273/331] try to activate venv --- .github/workflows/pypi.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 73e23be..4141ecf 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -28,12 +28,21 @@ jobs: plugins: "poetry-dynamic-versioning" repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" - - name: Get the version from wheel + + - name: List virtual environments + run: ls -la /github/home/.cache/pypoetry/virtualenvs + + - name: Get the virtual environment path + run: echo "VENV_PATH=$(ls -d /github/home/.cache/pypoetry/virtualenvs/*)" >> $GITHUB_ENV + + - name: Activate the virtual environment and get version run: | - WHEEL_FILE=$(find dist -name "*.whl" | head -n 1) - VERSION=$(python -c "from pkginfo import Wheel; w = Wheel('$WHEEL_FILE'); print(w.version)") + source $VENV_PATH/bin/activate + VERSION=$(python -c "import scraibe; print(scraibe.__version__)") echo "SCRAIBE_VERSION=$VERSION" >> $GITHUB_ENV echo "SCRAIBE_VERSION=$VERSION" + env: + VENV_PATH: ${{ env.VENV_PATH }} - name: 'Upload Artifact' uses: actions/upload-artifact@v4 From 35b5682d968b20b818c4ddd0a6d2b3a270c8c260 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 07:42:44 +0000 Subject: [PATCH 274/331] try to find venv --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 4141ecf..aaa8c26 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -33,7 +33,7 @@ jobs: run: ls -la /github/home/.cache/pypoetry/virtualenvs - name: Get the virtual environment path - run: echo "VENV_PATH=$(ls -d /github/home/.cache/pypoetry/virtualenvs/*)" >> $GITHUB_ENV + run: echo "VENV_PATH=$(ls -d $HOME/.cache/pypoetry/virtualenvs/* | head -n 1)" >> $GITHUB_ENV - name: Activate the virtual environment and get version run: | From 7e800294e9eed072da77f9e3c5380217b710596e Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 08:00:15 +0000 Subject: [PATCH 275/331] check what is there --- .github/workflows/pypi.yml | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index aaa8c26..a85e026 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -28,28 +28,8 @@ jobs: plugins: "poetry-dynamic-versioning" repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" - - - name: List virtual environments - run: ls -la /github/home/.cache/pypoetry/virtualenvs - - - name: Get the virtual environment path - run: echo "VENV_PATH=$(ls -d $HOME/.cache/pypoetry/virtualenvs/* | head -n 1)" >> $GITHUB_ENV - - - name: Activate the virtual environment and get version - run: | - source $VENV_PATH/bin/activate - VERSION=$(python -c "import scraibe; print(scraibe.__version__)") - echo "SCRAIBE_VERSION=$VERSION" >> $GITHUB_ENV - echo "SCRAIBE_VERSION=$VERSION" - env: - VENV_PATH: ${{ env.VENV_PATH }} - - - name: 'Upload Artifact' - uses: actions/upload-artifact@v4 - with: - name: build - path: ./sdist - retention-days: 5 + - name: Get list of available folders after checkout + run: ls -l test-install: From 542526b64359913db5942bcb31022059924bbe26 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 08:03:14 +0000 Subject: [PATCH 276/331] list source folder --- .github/workflows/pypi.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index a85e026..2e25907 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -29,7 +29,9 @@ jobs: repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" - name: Get list of available folders after checkout - run: ls -l + run: | + cd source + ls -l test-install: From 3fbcc52a5ce8bea97d282220de7500edbdc20c6e Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 09:24:23 +0000 Subject: [PATCH 277/331] added tests --- .github/workflows/pypi.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 2e25907..d4c9032 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -21,18 +21,12 @@ jobs: with: fetch-depth: '0' - name: Set up Poetry 📦 - if: ${{ !(startsWith(github.ref, 'refs/tags')) }} uses: JRubics/poetry-publish@v1.16 with: pypi_token: ${{ secrets.TEST_PYPI_API_TOKEN }} plugins: "poetry-dynamic-versioning" repository_name: "scraibe" repository_url: "https://test.pypi.org/legacy/" - - name: Get list of available folders after checkout - run: | - cd source - ls -l - test-install: name: Test Installation from TestPyPI From 63f56659c159c7195498401806e93a9225732f00 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 09:53:24 +0000 Subject: [PATCH 278/331] debug tagging --- .github/workflows/pypi.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index d4c9032..d33ddb9 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -14,6 +14,19 @@ on: type: boolean jobs: + check-tag: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: Check if tag is present + run: | + if [ -z "$GITHUB_REF" ]; then + echo "No tag found" + exit 1 + fi + echo "Tag found: $GITHUB_REF" Build-and-publish-to-Test-PyPI: runs-on: ubuntu-latest steps: From 760b525b177bbf5235e7cf465da175b052aae796 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 10:19:15 +0000 Subject: [PATCH 279/331] added setuptools to requires --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 730fb2b..593789b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["setuptools", "poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry] From ff6c0123b9e4ed65f0ff848ba0f583bf49096b6e Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Thu, 16 May 2024 13:01:55 +0200 Subject: [PATCH 280/331] Update transcriber test cases for whisper, whisperx. Add device catch to whisperx. --- scraibe/transcriber.py | 16 ++++++++++---- test/test_transcriber.py | 45 +++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index 8802cf6..f7765b6 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -30,6 +30,7 @@ from whisperx.asr import WhisperModel from whisperx import load_model as whisperx_load_model from typing import TypeVar, Union, Optional from torch import Tensor, device +from torch.cuda import is_available as cuda_is_available from numpy import ndarray from inspect import getfullargspec from abc import abstractmethod @@ -115,15 +116,14 @@ class Transcriber: print(f'Transcript saved to {save_path}') - @classmethod - def load_model(cls, - model: str = "medium", + @staticmethod + def load_model(model: str = "medium", whisper_type: str = 'whisper', download_root: str = WHISPER_DEFAULT_PATH, device: Optional[Union[str, device]] = None, in_memory: bool = False, *args, **kwargs - ) -> 'Transcriber': + ) -> 'Union[WhisperTranscriber, WhisperXTranscriber]': """ Load whisper model. @@ -278,6 +278,9 @@ class WhisperTranscriber(Transcriber): return whisper_kwargs + def __repr__(self) -> str: + return f"WhisperTranscriber(model_name={self.model_name}, model={self.model})" + class WhisperXTranscriber(Transcriber): def __init__(self, model: whisper, model_name: str) -> None: @@ -345,6 +348,8 @@ class WhisperXTranscriber(Transcriber): Returns: Transcriber: A Transcriber object initialized with the specified model. """ + if device is None: + device = "cuda" if cuda_is_available() else "cpu" if not isinstance(device, str): device = str(device) _model = whisperx_load_model(model, download_root=download_root, @@ -375,3 +380,6 @@ class WhisperXTranscriber(Transcriber): whisper_kwargs["language"] = language return whisper_kwargs + + def __repr__(self) -> str: + return f"WhisperXTranscriber(model_name={self.model_name}, model={self.model})" diff --git a/test/test_transcriber.py b/test/test_transcriber.py index fee3aff..7ecb1be 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -1,5 +1,5 @@ import pytest -from scraibe import Transcriber +from scraibe import Transcriber, WhisperTranscriber, WhisperXTranscriber import torch @@ -19,7 +19,7 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): expected_transcription (_type_): _description_ mock_model = mock_load_model.return_value - mock_model.transcribe.return_value ={"text": expected_transcription} + mock_model.transcribe.return_value ={"text": expected_transcription} transcriber = Transcriber.load_model(model="medium") @@ -29,12 +29,34 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): @pytest.fixture -def transcriber_instance(): - return Transcriber.load_model('medium') +def whisper_instance(): + return Transcriber.load_model('medium', whisper_type='whisper') -def test_transcriber_initialization(transcriber_instance): - assert isinstance(transcriber_instance, Transcriber) +@pytest.fixture +def whisperx_instance(): + return Transcriber.load_model('medium', whisper_type='whisperx') + + +def test_whisper_base_initialization(whisper_instance): + assert isinstance(whisper_instance, Transcriber) + + +def test_whisperx_base_initialization(whisperx_instance): + assert isinstance(whisperx_instance, Transcriber) + + +def test_whisper_transcriber_initialization(whisper_instance): + assert isinstance(whisper_instance, WhisperTranscriber) + + +def test_whisperx_transcriber_initialization(whisperx_instance): + assert isinstance(whisperx_instance, WhisperXTranscriber) + + +def test_wrong_transcriber_initialization(): + with pytest.raises(ValueError): + Transcriber.load_model('medium', whisper_type='wrong_whisper') def test_get_whisper_kwargs(): @@ -43,8 +65,15 @@ def test_get_whisper_kwargs(): assert not valid_kwargs == {"arg1": 1, "arg3": 3} -def test_transcribe(transcriber_instance): - model = transcriber_instance +def test_whisper_transcribe(whisper_instance): + model = whisper_instance + # mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) + transcript = model.transcribe('test/audio_test_2.mp4') + assert isinstance(transcript, str) + + +def test_whisperx_transcribe(whisperx_instance): + model = whisperx_instance # mocker.patch.object(transcriber_instance.model, 'transcribe', return_value={'Hello, World !'} ) transcript = model.transcribe('test/audio_test_2.mp4') assert isinstance(transcript, str) From 61d543440253122bacb7dc9240105870136b6ae3 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 11:19:21 +0000 Subject: [PATCH 281/331] try newer poetry version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 593789b..f11138e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=1.8.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry] From 74eaefa47cb7f429bd7522b4039f9aaab3ff6914 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 11:28:00 +0000 Subject: [PATCH 282/331] debut installation --- .github/workflows/pypi.yml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index d33ddb9..c8298da 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -14,19 +14,6 @@ on: type: boolean jobs: - check-tag: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: '0' - - name: Check if tag is present - run: | - if [ -z "$GITHUB_REF" ]; then - echo "No tag found" - exit 1 - fi - echo "Tag found: $GITHUB_REF" Build-and-publish-to-Test-PyPI: runs-on: ubuntu-latest steps: @@ -52,6 +39,8 @@ jobs: python-version: "3.x" - name: Install package run: | + which python3 + python3 -m pip install -U pip python3 -m pip install setuptools python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe From 7cf078ed60589d8892fda05e817128d42ad46435 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 16 May 2024 11:49:18 +0000 Subject: [PATCH 283/331] added matrix --- .github/workflows/pypi.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index c8298da..1c55f95 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -32,11 +32,14 @@ jobs: name: Test Installation from TestPyPI needs: Build-and-publish-to-Test-PyPI runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9, 3.10, 3.11, 3.12] steps: - - name: Set up Python + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: - python-version: "3.x" + python-version: ${{ matrix.python-version }} - name: Install package run: | which python3 @@ -44,6 +47,7 @@ jobs: python3 -m pip install setuptools python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe + # publish-to-pypi: # name: Conditional Publish to PyPI or Dev Repository # needs: [build, test-install] From 07b9939446ec85af64ef42e32eba00cada24f18a Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Thu, 16 May 2024 16:16:33 +0200 Subject: [PATCH 284/331] Changed loading of transcriber objects to function. Adjusted tests. --- scraibe/autotranscript.py | 8 ++-- scraibe/transcriber.py | 77 ++++++++++++++++++++++++++++++--------- test/test_transcriber.py | 9 +++-- 3 files changed, 69 insertions(+), 25 deletions(-) diff --git a/scraibe/autotranscript.py b/scraibe/autotranscript.py index 14d2451..7391f1a 100644 --- a/scraibe/autotranscript.py +++ b/scraibe/autotranscript.py @@ -38,7 +38,7 @@ from tqdm import trange # Application-Specific Imports from .audio import AudioProcessor from .diarisation import Diariser -from .transcriber import Transcriber, whisper +from .transcriber import Transcriber, load_transcriber, whisper from .transcript_exporter import Transcript @@ -87,10 +87,10 @@ class Scraibe: """ if whisper_model is None: - self.transcriber = Transcriber.load_model( + self.transcriber = load_transcriber( "medium", whisper_type, **kwargs) elif isinstance(whisper_model, str): - self.transcriber = Transcriber.load_model( + self.transcriber = load_transcriber( whisper_model, whisper_type, **kwargs) else: self.transcriber = whisper_model @@ -258,7 +258,7 @@ class Scraibe: _old_model = self.transcriber.model_name if isinstance(whisper_model, str): - self.transcriber = Transcriber.load_model(whisper_model, **kwargs) + self.transcriber = load_transcriber(whisper_model, **kwargs) elif isinstance(whisper_model, Transcriber): self.transcriber = whisper_model else: diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index f7765b6..c9b9b52 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -116,14 +116,16 @@ class Transcriber: print(f'Transcript saved to {save_path}') - @staticmethod - def load_model(model: str = "medium", + @classmethod + @abstractmethod + def load_model(cls, + model: str = "medium", whisper_type: str = 'whisper', download_root: str = WHISPER_DEFAULT_PATH, device: Optional[Union[str, device]] = None, in_memory: bool = False, *args, **kwargs - ) -> 'Union[WhisperTranscriber, WhisperXTranscriber]': + ) -> None: """ Load whisper model. @@ -153,19 +155,8 @@ class Transcriber: kwargs: Additional keyword arguments only to avoid errors. Returns: - Transcriber: A Transcriber object initialized with the specified model. + None: abscract method. """ - if whisper_type.lower() == 'whisper': - _model = WhisperTranscriber.load_model( - model, download_root, device, in_memory, *args, **kwargs) - return _model - elif whisper_type.lower() == 'whisperx': - _model = WhisperXTranscriber.load_model( - model, download_root, device, *args, **kwargs) - return _model - else: - raise ValueError(f'Model type not recognized, exptected "whisper" ' - f'or "whisperx", got {whisper_type}.') pass @staticmethod @@ -216,7 +207,7 @@ class WhisperTranscriber(Transcriber): device: Optional[Union[str, device]] = None, in_memory: bool = False, *args, **kwargs - ) -> 'Transcriber': + ) -> 'WhisperTranscriber': """ Load whisper model. @@ -316,7 +307,7 @@ class WhisperXTranscriber(Transcriber): download_root: str = WHISPER_DEFAULT_PATH, device: Optional[Union[str, device]] = None, *args, **kwargs - ) -> 'Transcriber': + ) -> 'WhisperXTranscriber': """ Load whisper model. @@ -383,3 +374,55 @@ class WhisperXTranscriber(Transcriber): def __repr__(self) -> str: return f"WhisperXTranscriber(model_name={self.model_name}, model={self.model})" + + +def load_transcriber(model: str = "medium", + whisper_type: str = 'whisper', + download_root: str = WHISPER_DEFAULT_PATH, + device: Optional[Union[str, device]] = None, + in_memory: bool = False, + *args, **kwargs + ) -> Union[WhisperTranscriber, WhisperXTranscriber]: + """ + Load whisper model. + + Args: + model (str): Whisper model. Available models include: + - 'tiny.en' + - 'tiny' + - 'base.en' + - 'base' + - 'small.en' + - 'small' + - 'medium.en' + - 'medium' + - 'large-v1' + - 'large-v2' + - 'large-v3' + - 'large' + whisper_type (str): + Type of whisper model to load. "whisper" or "whisperx". + download_root (str, optional): Path to download the model. + Defaults to WHISPER_DEFAULT_PATH. + device (Optional[Union[str, torch.device]], optional): + Device to load model on. Defaults to None. + in_memory (bool, optional): Whether to load model in memory. + Defaults to False. + args: Additional arguments only to avoid errors. + kwargs: Additional keyword arguments only to avoid errors. + + Returns: + Union[WhisperTranscriber, WhisperXTranscriber]: + One of the Whisper variants as Transcrbier object initialized with the specified model. + """ + if whisper_type.lower() == 'whisper': + _model = WhisperTranscriber.load_model( + model, download_root, device, in_memory, *args, **kwargs) + return _model + elif whisper_type.lower() == 'whisperx': + _model = WhisperXTranscriber.load_model( + model, download_root, device, *args, **kwargs) + return _model + else: + raise ValueError(f'Model type not recognized, exptected "whisper" ' + f'or "whisperx", got {whisper_type}.') diff --git a/test/test_transcriber.py b/test/test_transcriber.py index 7ecb1be..31765f6 100644 --- a/test/test_transcriber.py +++ b/test/test_transcriber.py @@ -1,5 +1,6 @@ import pytest -from scraibe import Transcriber, WhisperTranscriber, WhisperXTranscriber +from scraibe import (Transcriber, WhisperTranscriber, + WhisperXTranscriber, load_transcriber) import torch @@ -30,12 +31,12 @@ def test_transcriber(mock_load_model, audio_file, expected_transcription): @pytest.fixture def whisper_instance(): - return Transcriber.load_model('medium', whisper_type='whisper') + return load_transcriber('medium', whisper_type='whisper') @pytest.fixture def whisperx_instance(): - return Transcriber.load_model('medium', whisper_type='whisperx') + return load_transcriber('medium', whisper_type='whisperx') def test_whisper_base_initialization(whisper_instance): @@ -56,7 +57,7 @@ def test_whisperx_transcriber_initialization(whisperx_instance): def test_wrong_transcriber_initialization(): with pytest.raises(ValueError): - Transcriber.load_model('medium', whisper_type='wrong_whisper') + load_transcriber('medium', whisper_type='wrong_whisper') def test_get_whisper_kwargs(): From b6bed3ebd8b6bd00f984ef5af4baea9e07a246a8 Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Fri, 17 May 2024 11:23:49 +0200 Subject: [PATCH 285/331] Adjusted compute type for tests without gpu. --- scraibe/transcriber.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index c9b9b52..526ff8b 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -32,8 +32,9 @@ from typing import TypeVar, Union, Optional from torch import Tensor, device from torch.cuda import is_available as cuda_is_available from numpy import ndarray -from inspect import getfullargspec +from inspect import signature from abc import abstractmethod +import warnings from .misc import WHISPER_DEFAULT_PATH whisper = TypeVar('whisper') @@ -254,9 +255,7 @@ class WhisperTranscriber(Transcriber): dict: Keyword arguments for whisper model. """ # _possible_kwargs = WhisperModel.transcribe.__code__.co_varnames - _args = getfullargspec(Whisper.transcribe).args - _kwargs = getfullargspec(Whisper.transcribe).kwonlyargs - _possible_kwargs = _args + _kwargs + _possible_kwargs = signature(Whisper.transcribe).parameters.keys() whisper_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} @@ -343,6 +342,11 @@ class WhisperXTranscriber(Transcriber): device = "cuda" if cuda_is_available() else "cpu" if not isinstance(device, str): device = str(device) + compute_type = kwargs.get('compute_type', 'float16') + if device == 'cpu' and compute_type == 'float16': + warnings.warn(f'Compute type {compute_type} not compatible with ' + f'device {device}! Changing compute type to int8.') + compute_type = 'int8' _model = whisperx_load_model(model, download_root=download_root, device=device) @@ -357,9 +361,7 @@ class WhisperXTranscriber(Transcriber): dict: Keyword arguments for whisper model. """ # _possible_kwargs = WhisperModel.transcribe.__code__.co_varnames - _args = getfullargspec(WhisperModel.transcribe).args - _kwargs = getfullargspec(WhisperModel.transcribe).kwonlyargs - _possible_kwargs = _args + _kwargs + _possible_kwargs = signature(WhisperModel.transcribe).parameters.keys() whisper_kwargs = {k: v for k, v in kwargs.items() if k in _possible_kwargs} From 2291e77f53fe3c54008d07b10b439c02f67ea8ba Mon Sep 17 00:00:00 2001 From: Marko Henning Date: Fri, 17 May 2024 11:55:23 +0200 Subject: [PATCH 286/331] Actually use the new compute type variable... --- scraibe/transcriber.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/transcriber.py b/scraibe/transcriber.py index 526ff8b..0301955 100644 --- a/scraibe/transcriber.py +++ b/scraibe/transcriber.py @@ -348,7 +348,7 @@ class WhisperXTranscriber(Transcriber): f'device {device}! Changing compute type to int8.') compute_type = 'int8' _model = whisperx_load_model(model, download_root=download_root, - device=device) + device=device, compute_type=compute_type) return cls(_model, model_name=model) From d9750c17d350346e52cdb1e33b0222bce116b980 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 10:03:35 +0000 Subject: [PATCH 287/331] try to update wheel --- .github/workflows/pypi.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 1c55f95..9ee3bc8 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -42,9 +42,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install package run: | - which python3 - python3 -m pip install -U pip - python3 -m pip install setuptools + python3 -m pip install -U pip setuptools wheel python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe From 131d7fdb23b8c22c4e5dc4ab223f1a5ae6fa7221 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 11:25:50 +0000 Subject: [PATCH 288/331] update setup-python --- .github/workflows/pypi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 9ee3bc8..11aa476 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -34,10 +34,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, 3.10, 3.11, 3.12] + python-version: [3.9, 3.11, 3.12] steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install package From b92c6d8e391f8e44549d95ab7aeefc300a6b129f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 12:07:14 +0000 Subject: [PATCH 289/331] removed extra install --- .github/workflows/pypi.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 11aa476..0f1ae85 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -42,8 +42,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install package run: | - python3 -m pip install -U pip setuptools wheel - python3 -m pip install --index-url https://test.pypi.org/simple/ scraibe + python3 -m pip install -U --index-url https://test.pypi.org/simple/ scraibe # publish-to-pypi: From 85fa9deb96558700a66ebb3a81021f5e520e6cc4 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 12:13:25 +0000 Subject: [PATCH 290/331] force latest version --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 0f1ae85..c810ea4 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -42,7 +42,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install package run: | - python3 -m pip install -U --index-url https://test.pypi.org/simple/ scraibe + python3 -m pip install -U --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 # publish-to-pypi: From 3fdb9ecf81282b9d06ad4eaa62e1af3ad2977170 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 12:50:46 +0000 Subject: [PATCH 291/331] changed __version__ --- scraibe/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scraibe/__init__.py b/scraibe/__init__.py index fbbd81e..9f93efc 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -10,6 +10,4 @@ from .cli import * # set __version__ attribute -import importlib.metadata - -__version__ = importlib.metadata.version(__package__ or __name__) +__version__ = "0.0.0" From 3e9ca434d717bd3a4bb7a5592aa9bd3ce0491ec4 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 13:05:25 +0000 Subject: [PATCH 292/331] added _version.py file --- pyproject.toml | 1 + scraibe/_version.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 scraibe/_version.py diff --git a/pyproject.toml b/pyproject.toml index f11138e..8a62014 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,3 +69,4 @@ app = ["scraibe-webui"] [tool.ruff.lint.extend-per-file-ignores] "__init__.py" = ["E402","F403"] +"scraibe/misc.py" = ["E722"] diff --git a/scraibe/_version.py b/scraibe/_version.py new file mode 100644 index 0000000..3ab50aa --- /dev/null +++ b/scraibe/_version.py @@ -0,0 +1,3 @@ + # set __version__ attribute + +__version__ = "0.0.0" \ No newline at end of file From f5be9c87bba5c263d7e71832c0298744748992dc Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 13:31:17 +0000 Subject: [PATCH 293/331] added specific file for version --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8a62014..d53542d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,8 @@ format-jinja = """ {{ serialize_pep440(bump_version(base), dev=distance, metadata=[commit]) }} {%- endif -%} """ +[tool.poetry-dynamic-versioning.substitution] +files = ["scraibe/_version.py"] [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From 10f74c2e1da58a3595354fabd9476bee082ce11f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 13:49:15 +0000 Subject: [PATCH 294/331] removed line --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d53542d..8a62014 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,8 +53,6 @@ format-jinja = """ {{ serialize_pep440(bump_version(base), dev=distance, metadata=[commit]) }} {%- endif -%} """ -[tool.poetry-dynamic-versioning.substitution] -files = ["scraibe/_version.py"] [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From 88d826db8436d9db3337e20aa31e8fdd03b4af6d Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 13:57:10 +0000 Subject: [PATCH 295/331] install req from file --- .github/workflows/pypi.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index c810ea4..aa9edaf 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -42,7 +42,9 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install package run: | - python3 -m pip install -U --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 + pip install -U setuptools + pip install -r requirements.txt + python3 -m pip install --no-deps --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 # publish-to-pypi: From d43a7863c3fd0adbca72cfda7791967f5962c63a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 17 May 2024 14:00:45 +0000 Subject: [PATCH 296/331] added checkout for test section --- .github/workflows/pypi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index aa9edaf..25d1e2d 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -36,6 +36,7 @@ jobs: matrix: python-version: [3.9, 3.11, 3.12] steps: + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -44,7 +45,7 @@ jobs: run: | pip install -U setuptools pip install -r requirements.txt - python3 -m pip install --no-deps --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 + python3 -m pip install --no-deps --pre --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 # publish-to-pypi: From 54ef452051c6f2ebe6eb509127582fe35fcca09c Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 07:48:49 +0000 Subject: [PATCH 297/331] updated version --- .github/workflows/pypi.yml | 2 +- .github/workflows/pytest.yaml | 7 +------ scraibe/__init__.py | 7 +++---- scraibe/_version.py | 2 -- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 25d1e2d..f9a7ba1 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -46,7 +46,7 @@ jobs: pip install -U setuptools pip install -r requirements.txt python3 -m pip install --no-deps --pre --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 - + python3 -m -c "import scraibe; print(scraibe.__version__)" # publish-to-pypi: # name: Conditional Publish to PyPI or Dev Repository diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 973571f..a1b0234 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,8 +1,6 @@ name: Run Tests on: - #push: - pull_request: branches: ['main', 'develop'] workflow_dispatch: @@ -30,10 +28,7 @@ jobs: sudo apt-get install libsndfile1-dev sudo apt-get install ffmpeg pip install pytest - - - - + - name: Run pytest env: HF_TOKEN : ${{ secrets.HF_TOKEN }} diff --git a/scraibe/__init__.py b/scraibe/__init__.py index 9f93efc..4338879 100644 --- a/scraibe/__init__.py +++ b/scraibe/__init__.py @@ -7,7 +7,6 @@ from .diarisation import * from .misc import * from .cli import * - - # set __version__ attribute - -__version__ = "0.0.0" + +from ._version import __version__ + diff --git a/scraibe/_version.py b/scraibe/_version.py index 3ab50aa..3aa0d7b 100644 --- a/scraibe/_version.py +++ b/scraibe/_version.py @@ -1,3 +1 @@ - # set __version__ attribute - __version__ = "0.0.0" \ No newline at end of file From 16b517fa8ff784fd60a6c30902331bc110427d2f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 07:54:16 +0000 Subject: [PATCH 298/331] added semantic versioning --- .github/semver.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/semver.yml diff --git a/.github/semver.yml b/.github/semver.yml new file mode 100644 index 0000000..a351a3d --- /dev/null +++ b/.github/semver.yml @@ -0,0 +1,22 @@ +# .github/workflows/release.yml +name: Release + +on: + pull_request: + types: [closed] + +jobs: + build: + runs-on: ubuntu-latest + + if: github.event.pull_request.merged + + steps: + - name: Tag + uses: K-Phoen/semver-release-action@master + with: + release_branch: pyproject.toml + release_strategy: none + + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} From d4b328775e21fc6e34cd0b3a7886f71de16a3522 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 07:58:36 +0000 Subject: [PATCH 299/331] test semantic versioning --- .github/semver.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/semver.yml b/.github/semver.yml index a351a3d..5d7d05c 100644 --- a/.github/semver.yml +++ b/.github/semver.yml @@ -1,9 +1,8 @@ -# .github/workflows/release.yml -name: Release +name: Semantic Versioning -on: - pull_request: - types: [closed] +on: push + # pull_request: + # types: [closed] jobs: build: @@ -17,6 +16,5 @@ jobs: with: release_branch: pyproject.toml release_strategy: none - env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} From 952594929bb57e41f811534396d2786072f5a074 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 07:59:40 +0000 Subject: [PATCH 300/331] moved to workflow folder --- .github/{ => workflows}/semver.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/semver.yml (100%) diff --git a/.github/semver.yml b/.github/workflows/semver.yml similarity index 100% rename from .github/semver.yml rename to .github/workflows/semver.yml From f522d76605ec8f7d4a83fc4fb35b7e362343d3f5 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 08:00:20 +0000 Subject: [PATCH 301/331] test ci --- .github/workflows/semver.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index 5d7d05c..0c357a2 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -8,7 +8,7 @@ jobs: build: runs-on: ubuntu-latest - if: github.event.pull_request.merged + # if: github.event.pull_request.merged steps: - name: Tag From 759732f9538af88d3bda0d082e1f18f62791d1e9 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 08:02:24 +0000 Subject: [PATCH 302/331] removed checks --- .github/workflows/semver.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index 0c357a2..bddcdf0 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -6,15 +6,12 @@ on: push jobs: build: - runs-on: ubuntu-latest - - # if: github.event.pull_request.merged - + runs-on: ubuntu-latest steps: - name: Tag uses: K-Phoen/semver-release-action@master with: - release_branch: pyproject.toml + release_branch: test_semver release_strategy: none env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} From 932fc5dfb69d60d13040cf1422ee07a52911a0df Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 08:42:13 +0000 Subject: [PATCH 303/331] make my own version bumper --- .github/workflows/semver.yml | 53 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index bddcdf0..fbe4acb 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -1,17 +1,46 @@ -name: Semantic Versioning +name: Semantic Versioning for Tags -on: push - # pull_request: - # types: [closed] +on: + push: + branches: + - test_semserver jobs: - build: - runs-on: ubuntu-latest + bump-version: + runs-on: ubuntu-latest steps: - - name: Tag - uses: K-Phoen/semver-release-action@master + - name: Checkout Repository + uses: actions/checkout@v4 with: - release_branch: test_semver - release_strategy: none - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + fetch-depth: 0 + + - name: Bump Version and Tag + run: | + # Fetch the latest tags from the remote + git fetch --tags + + # Get the latest tag + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) + + # Split the latest tag into parts + IFS='.' read -r -a parts <<< "${latest_tag#v}" + + # Increment the patch version + major=${parts[0]} + minor=${parts[1]} + patch=${parts[2]} + new_patch=$((patch + 1)) + + # Create a new tag + new_tag="v$major.$minor.$new_patch" + + echo "Bumping version from $latest_tag to $new_tag" + + # Tag the new version + git tag $new_tag + + # Configure GitHub token authentication + git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.git + + # Push the new tag to the remote repository + git push origin $new_tag From e2d1deefdb970465a40d1fd3f68c5d4444d4b5dd Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 08:43:04 +0000 Subject: [PATCH 304/331] select branch --- .github/workflows/semver.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index fbe4acb..1c93b78 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -3,7 +3,7 @@ name: Semantic Versioning for Tags on: push: branches: - - test_semserver + - test_semver jobs: bump-version: From cd0b66c0b85029b1a8fa1d3b98ba860ea4385dd6 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 08:56:28 +0000 Subject: [PATCH 305/331] now just runs on merge to main --- .github/workflows/semver.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index 1c93b78..c6d5fb1 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -1,12 +1,14 @@ name: Semantic Versioning for Tags on: - push: + pull_request: + types: [closed] branches: - - test_semver + - main jobs: bump-version: + if: ${{ github.event.pull_request.merged == true $$ github.event.pull_request.base.ref == 'main' }} runs-on: ubuntu-latest steps: - name: Checkout Repository From ef1f176852b26739c2aaf215e0ad6f113c63f7f1 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 09:18:15 +0000 Subject: [PATCH 306/331] added realese to pypi --- .github/workflows/pypi.yml | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index f9a7ba1..4e5d905 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -3,7 +3,7 @@ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI on: push: branches: - - pyproject.toml # test branch for testing the workflow + - develop branch to create pre-release tags: - v* # Push tags to trigger the workflow workflow_dispatch: @@ -46,25 +46,19 @@ jobs: pip install -U setuptools pip install -r requirements.txt python3 -m pip install --no-deps --pre --index-url https://test.pypi.org/simple/ scraibe>=0.1.3 - python3 -m -c "import scraibe; print(scraibe.__version__)" + python3 -c "import scraibe; print(scraibe.__version__)" - # publish-to-pypi: - # name: Conditional Publish to PyPI or Dev Repository - # needs: [build, test-install] - # runs-on: ubuntu-latest - # environment: - # name: pypi - # url: env.PyPI_URL - # permissions: - # id-token: write - # steps: - # - name: Download all the dists - # uses: actions/download-artifact@v3 - # with: - # name: python-package-distributions - # path: dist/ - # - name: Publish distribution 📦 to PyPI or Dev Repository - # uses: pypa/gh-action-pypi-publish@release/v1 - # with: - # repository-url: ${{ github.ref == 'refs/heads/main' && 'env.PyPI_URL' || ' env.PyPI_DEV_URL' }} - # password: ${{ secrets.PYPI_API_TOKEN}} \ No newline at end of file + publish-to-pypi: + name: Publish to PyPI + needs: test-install + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: Set up Poetry 📦 + uses: JRubics/poetry-publish@v1.16 + with: + pypi_token: ${{ secrets.PYPI_API_TOKEN }} + plugins: "poetry-dynamic-versioning" + repository_name: "scraibe" \ No newline at end of file From de3eaba50a4b01e8fa3b29955187713484d471a5 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 09:37:43 +0000 Subject: [PATCH 307/331] added develop branch properly. Added checkout rules to enshure the right thing is published --- .github/workflows/pypi.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 4e5d905..451aba8 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -3,7 +3,7 @@ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI on: push: branches: - - develop branch to create pre-release + - develop tags: - v* # Push tags to trigger the workflow workflow_dispatch: @@ -53,9 +53,18 @@ jobs: needs: test-install runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout Repository Tags + uses: actions/checkout@v4 + if: github.ref == 'refs/tags/v*' with: fetch-depth: '0' + branch: 'main' + - name: Checkout Repository (Develop) + uses: actions/checkout@v4 + if: github.ref == "refs/heads/develop" + with: + fetch-depth: '0' + branch: 'develop' - name: Set up Poetry 📦 uses: JRubics/poetry-publish@v1.16 with: From 828cec39f497c7e0707c7ea7848704c017d2e8b0 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 11:07:25 +0000 Subject: [PATCH 308/331] make Ruff happy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8a62014..b850f63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,5 +68,5 @@ scraibe = "scraibe.cli:cli" app = ["scraibe-webui"] [tool.ruff.lint.extend-per-file-ignores] -"__init__.py" = ["E402","F403"] +"__init__.py" = ["E402","F403",'F401'] "scraibe/misc.py" = ["E722"] From 1789d0d23900cde533d825930af9dd6cca7bef9d Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 11:10:59 +0000 Subject: [PATCH 309/331] make ruff even more happy --- .github/workflows/ruff.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index c8a0958..271e0b4 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -3,6 +3,7 @@ on: [ push, pull_request ] jobs: ruff: runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' || (github.event_name == 'push') }} steps: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 \ No newline at end of file From ea216a26254069eb77724d2e193167e19cae9d81 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 11:21:54 +0000 Subject: [PATCH 310/331] ake dunamai happy --- .github/workflows/pytest.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index a1b0234..aac8afe 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -12,6 +12,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v3 From a49e72f488b248b7c80824cab63c7f4e241cd296 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Tue, 21 May 2024 11:38:16 +0000 Subject: [PATCH 311/331] fixed strin instead of value --- .github/workflows/pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 451aba8..f50ce5b 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -61,7 +61,7 @@ jobs: branch: 'main' - name: Checkout Repository (Develop) uses: actions/checkout@v4 - if: github.ref == "refs/heads/develop" + if: github.ref == 'refs/heads/develop' with: fetch-depth: '0' branch: 'develop' From 588dd7a95f8b459bab9365f300b06be6175905eb Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 27 May 2024 10:47:48 +0000 Subject: [PATCH 312/331] fixed variable name --- scraibe/transcript_exporter.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scraibe/transcript_exporter.py b/scraibe/transcript_exporter.py index 5222d58..dbd599b 100644 --- a/scraibe/transcript_exporter.py +++ b/scraibe/transcript_exporter.py @@ -300,7 +300,7 @@ class Transcript: raise ValueError("Unknown file format") @classmethod - def from_json(cls, json: Union[dict, str]) -> "Transcript": + def from_json(cls, _json: Union[dict, str]) -> "Transcript": """Load transcript from json file Args: @@ -309,13 +309,13 @@ class Transcript: Returns: Transcript: Transcript object """ - if isinstance(json, dict): - return cls(json) + if isinstance(_json, dict): + return cls(_json) else: try: - transcript = json.loads(json) + transcript = json.loads(_json) except (TypeError, JSONDecodeError): - with open(json, "r") as f: + with open(_json, "r") as f: transcript = json.load(f) return cls(transcript) From 9d67ffc1eca61d8155fb9e490ab6d5d48409ff19 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Mon, 27 May 2024 10:49:06 +0000 Subject: [PATCH 313/331] updated CI path --- .github/workflows/mirror_to_gitlab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mirror_to_gitlab.yml b/.github/workflows/mirror_to_gitlab.yml index b100359..bc52920 100644 --- a/.github/workflows/mirror_to_gitlab.yml +++ b/.github/workflows/mirror_to_gitlab.yml @@ -12,7 +12,7 @@ jobs: - name: Mirror + trigger CI uses: SvanBoxel/gitlab-mirror-and-ci-action@master with: - args: "https://git-dmz.thuenen.de/kida/i2-skills-beratungsstelle/scraibe" + args: "https://git-dmz.thuenen.de/kida/i2-skills-beratungsstelle/active-service-requests/scraibe/scraibe" env: FOLLOW_TAGS: "true" FORCE_PUSH: "true" From 21d63ad99fb2ae80205b0367c978c88352d19e2c Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 11:46:20 +0000 Subject: [PATCH 314/331] updated and added semver CI --- .github/workflows/check_semver.yaml | 75 +++++++++++++++++++++++++++++ .github/workflows/semver.yml | 75 ++++++++++++++++++++++++----- 2 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/check_semver.yaml diff --git a/.github/workflows/check_semver.yaml b/.github/workflows/check_semver.yaml new file mode 100644 index 0000000..ccfd2b8 --- /dev/null +++ b/.github/workflows/check_semver.yaml @@ -0,0 +1,75 @@ +name: Check and Add Version in Changelog + +on: + pull_request: + branches: + - main + - develop + +jobs: + check-and-add-version: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract and Determine Version + id: extract_version + run: | + # Fetch the latest tags from the remote + git fetch --tags + + # Get the latest tag, or initialize to v0.0.0 if no tags are found + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1` 2>/dev/null || echo "v0.0.0") + + # Extract version from PR title or body + pr_body="${{ github.event.pull_request.body }}" + pr_title="${{ github.event.pull_request.title }}" + version_regex="v([0-9]+)\.([0-9]+)\.([0-9]+)" + + if [[ $pr_body =~ $version_regex ]]; then + major=${BASH_REMATCH[1]} + minor=${BASH_REMATCH[2]} + patch=${BASH_REMATCH[3]} + new_tag="v$major.$minor.$patch" + elif [[ $pr_title =~ $version_regex ]]; then + major=${BASH_REMATCH[1]} + minor=${BASH_REMATCH[2]} + patch=${BASH_REMATCH[3]} + new_tag="v$major.$minor.$patch" + else + # Split the latest tag into parts + IFS='.' read -r -a parts <<< "${latest_tag#v}" + major=${parts[0]} + minor=${parts[1]} + patch=${parts[2]} + patch=$((patch + 1)) + new_tag="v$major.$minor.$patch" + fi + + clean_version="${new_tag#v}" + echo "version=$clean_version" >> $GITHUB_ENV + echo "Version determined: $clean_version" + + - name: Check if Version Already Exists in Tags + run: | + version="${{ env.version }}" + if git tag --list | grep -q "^$version$"; then + echo "Version $version already exists in tags." + exit 1 + else + echo "Version $version does not exist in tags." + fi + + - name: Check Version in CHANGELOG + id: check_version + run: | + version="${{ env.version }}" + if ! grep -q "^## \[$version\]" CHANGELOG.md; then + echo "Version $version not found in CHANGELOG.md." + exit 1 + else + echo "Version $version found in CHANGELOG.md." + fi diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index c6d5fb1..a4f97d3 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -8,7 +8,7 @@ on: jobs: bump-version: - if: ${{ github.event.pull_request.merged == true $$ github.event.pull_request.base.ref == 'main' }} + if: ${{ github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' }} runs-on: ubuntu-latest steps: - name: Checkout Repository @@ -17,27 +17,46 @@ jobs: fetch-depth: 0 - name: Bump Version and Tag + id: bump_version + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} run: | # Fetch the latest tags from the remote git fetch --tags - # Get the latest tag - latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) + # Get the latest tag, or initialize to v0.0.0 if no tags are found + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1` 2>/dev/null || echo "v0.0.0") - # Split the latest tag into parts - IFS='.' read -r -a parts <<< "${latest_tag#v}" + # Extract version from PR title or body + pr_body="${{ github.event.pull_request.body }}" + pr_title="${{ github.event.pull_request.title }}" + version_regex="v([0-9]+)\.([0-9]+)\.([0-9]+)" - # Increment the patch version - major=${parts[0]} - minor=${parts[1]} - patch=${parts[2]} - new_patch=$((patch + 1)) - - # Create a new tag - new_tag="v$major.$minor.$new_patch" + if [[ $pr_body =~ $version_regex ]]; then + major=${BASH_REMATCH[1]} + minor=${BASH_REMATCH[2]} + patch=${BASH_REMATCH[3]} + new_tag="v$major.$minor.$patch" + elif [[ $pr_title =~ $version_regex ]]; then + major=${BASH_REMATCH[1]} + minor=${BASH_REMATCH[2]} + patch=${BASH_REMATCH[3]} + new_tag="v$major.$minor.$patch" + else + # Split the latest tag into parts + IFS='.' read -r -a parts <<< "${latest_tag#v}" + major=${parts[0]} + minor=${parts[1]} + patch=${parts[2]} + patch=$((patch + 1)) + new_tag="v$major.$minor.$patch" + fi echo "Bumping version from $latest_tag to $new_tag" + # Set the new tag as an environment variable + echo "new_tag=$new_tag" >> $GITHUB_ENV + # Tag the new version git tag $new_tag @@ -46,3 +65,33 @@ jobs: # Push the new tag to the remote repository git push origin $new_tag + + - name: Extract Release Notes + id: extract_notes + run: | + version="${{ env.new_tag }}" + clean_version="${version#v}" + release_notes=$(awk -v version="$clean_version" ' + BEGIN { flag=0 } + # Start flagging when the version section is found + /^## \[.*\]/ { + if (flag) exit # Exit when the next section starts + } + /^## \['"$clean_version"'\]/ { flag=1; next } # Start printing after the header + flag { print } # Print lines while flag is 1 + ' CHANGELOG.md) + echo "RELEASE_NOTES<> $GITHUB_ENV + echo "$release_notes" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Create Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + with: + tag_name: ${{ env.new_tag }} + release_name: Release ${{ env.new_tag }} + body: ${{ env.RELEASE_NOTES }} + draft: false + prerelease: false + \ No newline at end of file From 7a388a129a4eea34c05acd6d0590b1f5eb159188 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 12:02:11 +0000 Subject: [PATCH 315/331] added whisperx to pyroject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b850f63..c97f4ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ python = "^3.9" tqdm = "^4.66.4" numpy = "^1.26.4" openai-whisper = "^20231117" +whisperx = "^3.1.3" "pyannote.audio" = "^3.2.0" torch = "^2.3.0" From ae5edbf7fcf21bbe96b7c0d5dcc41c7c12b88245 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:02:09 +0000 Subject: [PATCH 316/331] Updated Readme.md --- Pictures/BMEL.jpg | Bin 7402 -> 0 bytes Pictures/BMEL_dark.png | Bin 16981 -> 0 bytes Pictures/DBFZ.png | Bin 14843 -> 0 bytes Pictures/DBFZ_dark.png | Bin 15228 -> 0 bytes Pictures/MRI.png | Bin 8958 -> 0 bytes Pictures/gradio_app.png | Bin 134500 -> 0 bytes Pictures/kida.png | Bin 16332 -> 0 bytes Pictures/kida_dark.png | Bin 17646 -> 0 bytes README.md | 241 ++++++++++++++++------------------------ 9 files changed, 98 insertions(+), 143 deletions(-) delete mode 100644 Pictures/BMEL.jpg delete mode 100644 Pictures/BMEL_dark.png delete mode 100644 Pictures/DBFZ.png delete mode 100644 Pictures/DBFZ_dark.png delete mode 100644 Pictures/MRI.png delete mode 100644 Pictures/gradio_app.png delete mode 100644 Pictures/kida.png delete mode 100644 Pictures/kida_dark.png diff --git a/Pictures/BMEL.jpg b/Pictures/BMEL.jpg deleted file mode 100644 index 16a2baf8eb93f5e436958bfd80c405b4b1a48ec2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7402 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 QcAbL-^6vjl&6rdF0kd^(Qvd(} diff --git a/Pictures/BMEL_dark.png b/Pictures/BMEL_dark.png deleted file mode 100644 index 2bd7a58987903ef7cdedcd2cb740f4378d6c9924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16981 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 diff --git a/Pictures/DBFZ_dark.png b/Pictures/DBFZ_dark.png deleted file mode 100644 index 4020b0c12a764cb8f5a320820582a5af8dc31063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15228 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 YX>V=-F@#v`?*IS*07*qoM6N<$f*(=5eE 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

    mvmKe1lgJQz1- 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 diff --git a/Pictures/kida.png b/Pictures/kida.png deleted file mode 100644 index dfeab5cfd6aa86c779a20196333e6b38d7ada3b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16332 zcmeIYbyQT}_cuO(q%=q)1CqkfLxXfEBPmMiz>vcXF{E@VNDSSbN=t{dgn%?C452hg zBOMREKlxq1XRY7*JnMP>`^>Cc`|SNXd%w;;=bn4d4by(AMnc3u1ONa?G}M)KZ;vOp z8-f7ucJJeK;&yxJd#(S%MHlK0baZyGvbDDax_CNT0xdmktpEUz8H9ERNf(g#(kna) z@Qse{4ZHFt9&;PM#Iv`rDXo{PNiFQMPBbc_7DLzzOI0SZbn}D5*_*?pzDd)|IC73( z8ga>L5^xK#4Z{t#+BUDP1(j5298^8Up~fL>t640n$K)qr*p!UH;q*?)f#N|@Z7Vg) zE;pT1m~a^~*O}Qpw|O(Kc@C(TnsSViO@wf=;VJ+_M)jQCvNC%#FUL7p-|z*3l5cq- zlYfMES;|OF(_zpF0Kf{dRZ`N{P*VCwOt+Dw`@~4AcgZvLv=t_t7fXCo=w?;ghT*fk zwRmq%!53erSVGE~)S9iK6#MfhcLNQRH=hBsNxG}6KW)n&VVeqC z^7VFK=$&)-A1M}`JW|1WRvn=)!)b*o$-TMfvj+vl5j+{3pxXFM+!R&e<#GC&_J`rz z@RcN9LQ6j&TAj(V2}V; zw<8~7%-K{fs`#&Udb8M*G|PqQBx$rmi2C<~ZaQ|6L!C$QY1D7AWCiN3gh)@_1FYui z+M*H7p4c8)0la0~6=MF4T4P%Xkjoun1P<*q0y2-o@9rPNWh%_Bb}&Xwgu8eZ(aM8Q zynuZLM>>@Gq{{*5H|@^wBqZ*1s(dlqJ=mH5I)5!YzPXD(V&FVDt5mUen|yAzw@LN_ zqA6wJfZ&J19Lz2GJrIt!*#`i~Jo0daTEHz`faaFgwn$mF-R3qnpe;<6?Kv1C2ys-h zw6Rrx?QE&{`l-IfYq*6ZjO~#ek&K7bEdavO1q$>)*dtL=9*!+ZfCT=*gql0J zy2!Gz-IfFY;UB^g0{JIA67_c$Zut=KfI12Y@e2wd5CZ>hfpSrCy9N2XL;qU~l>TjB z70|UrIk-AoSgN>LB3&N*I|R()pZ1Qf&h~$W1G5mYw6{dus-kXd75X1ls%b#9|7r1u z0&81@<6l;{Wd9FM7h9`;k@Y`(`&05)IREa*t@%H3|A+QJV*g9|Rto}=Qg*O#{o|g7 zvMk%5`lVnF7Pc^{zfQ%)gvHFEqCy~ZOHoUZsGx-?2r4cj1Of{SS&5lTib{yVB>oLb z1Br5hA}uWcK;44#+uq_xh)4mq7)U}=LIflvC<%s%hzbcwm<#qW9Ff+475pJwNbXGw@F^rFDIy^(AtEL&DIpFP`!{<7OJ~$=F8)Ck666;V{mcAM!${qRb4xAs zPo~}i{3XAQMoP)q66)gMtnc7pFU$7FDc~Q=e-=Y-I}{A+0#$~(Sl)sP3X4byN=gZe z>I;cTfrX_+#drmUr3C*?-vMT8<@x`o{ipi?W&YOa>b9uc`aS;={jE>wSvvjg>Tj3! zwtsadAn>nlAqBPgTL>tqnNY|D;tI(9 z_om|cpWe9JSpI2;TgpI!lAwQ4Ce9}C_h1G7j2QojRz~3e!-vdY3ja35+}iz3b~|+4 zj)VgL918!=*R8MrFMoe;hyRyV0D=FlHwKIf>AeIr{5{sQviuLrwr{emvTEY0&hdva^I##y$1#L3_6%%xvUk9sRM4X z_elIJ05Zg{)UK!Qwxptj?)LesVV#Qm-^Dt@6C`XW1zZzlQ?~a2u5n(G0WNS}^mw%q zEP#i!)D^M$wz$qRrT0&j~`bV2!DF^gsWd0JW(@^m8^Pr?s|R2Q0vZ- zf|Btk!)gJ%asc#VuPd`UGWc8b5H!U~#Lh>T^|@9OW= zsCBLecl;yL_tNWGQcigRRN<#{Lf4tV2M;$E)M5kF4sQ5i6R5$)Hb5nHdnDkxW8o!s zrHG$EwDvsXqj^4lIKygGa zF!HkTVb;2b_St>*8YC;E{U7Nn`Qa@R0n!xE?gf!R^^3;neiarz ziw<9;rU+82F%xnf6rANw#lQg=z^fo^Ul*lX^FgLKVjTR?Mk=(#2~%Df)z|8ufa|0g zM!>bkmbvhqbw=nsFGb1*MVXebD`g{K8VJ||3IU42Jr;H zjjjXsfIbd%3$lWN92%%=X1lWx@>fVa|IgG6Cv;f%Zj)v! zsjSvI;Wpg4sGlN5aiw0_qZQFnhYIkh4OGhsaYm%lsYDn-ZYV zVSDoYFjhnOS{BKoF(A!L6P}HAQu^X~auHoNJF41ZV*NHtkB zcG!e0^*ty%WRE_Y;*!6A;f8e4(i||1?ZFruiPtMy-)=P4V)3fxV5jD>S&`tt*svc% zED!!#)0AUEpp06T6Std>kFUV8U4paE`xE3&EBvclb$cIN|DZ#gZS$aMtac^X`fVy=f5U69U~QkR@3B*_u^{HK>97W? zR*B+Q;|@*ixUE?8qD5KS9S%Z=JOwyZCjyIv;-XISAJuT z^{x&r^eM0iJOOrT2~F1(u0GYKOX$j)e69}X*{E&RH}PU;mAuG6r&W_vL!&-Yk}fd3 z^`Lr4elkPUs>KyFS0k+|@T?2w2}z9@OKUd-7~0v=8rLj`f2ai%Dk;AD#7s!m$FTDm z@S0)Z!B+Ad7CYwxNxKm2XDib>yF$$zpk-IHQWO-(wGsDx3>Lj#pB-|pm{pHe7WUw5 z-WJe?zeg(~RQ)v>&x&{e$kX&jAs5He#q#Uor(d$MOgDMow00E@eqE*c#5vQkJ%I{Q zv=fqNS0~q%7A|J0xv%R*sYHt4*#x%o<2cf@r`YQG)<^7mkYv5TItrS=Fbw$iYaV4M zTn*CUbKKRC&>6}@Y+@}^sn6Ih+R!ES9LkLsH%8~ZO4cA*New+ekuY=5kGZa*afFx% zIsjnb;ZscFUM3GEcC!PoY1U@=Qoy$5)7A(q!zlRhr_WCBFvkeO#V5len_i{0`EGik2i7c|yV=`v+^#1{%Z{+KwXea07luSfZLS&pt41T@+)s&7c( z+{z2SP6V9m;A9Xf^eA-f$qXC5n*aW^u->NWhAwvQIC7&}^X~5e->HLL>fVMaROWSA z92j?z?>_yZ8E20TWfycIu`%KS>Z`{NeZI?&xw7lX<+5Zgp>&`6vSCt{|C<|Cy& zx_68YSHoNK4d&oftfmgP+6u6rgXqm7RLVsVferNrgm+x%zJ=1X)g`%Ke2tL?{U7-um zQk|Y!D=~DShNoqv-Ki=le5-ceeoPi1c}BVQsf7ZX-9I2IzZUTrmFwap7{JF)+IlB8 z#*GQQn+HH*JA#JVws@El_28Bn*4@Amw&N9;@n^W24x{&U4`&F%9gk9{ffm~NG!(UT za)*%WzGi_w)r;Zn@Jhi@;U&}VJEb2tzV3&Qn$M(J=-Jl5NkaCOn3~0zauUka(rU;^ zolDW}o(qwzKjvwToxX}QQrSX2Ql05u)M2qGTo8PE!~`TY1XR ziD{;fGsEqFPjGlewW=4L-?dzn3@?*Er*)oYyz@Kx5f+H{;(p17+1!t#C5FK2GF+f| z<1n)b=l7ct{HB?f7jS>u01GiluGTS0zDXRo*%k2IDYjcSd*B4X{{uFuSsjgaN&hjn zl)<&jy1ZEzuU})AkT8c?!^-eoot~r4?8}}yQ|!gH)p`m4Ms(%$LyIW!H+x@Gulwj8 z8AZqMSHLOQ&L^q!t)+J3aRozn-ow)<7fd|O7{p^by@+O^!^*7It6By+ILMIt zOs?N#u$1f|xh9fA6U%>lNZGSLFC~Ff;bBHk#>vOkjcRH$clj^!h9w2bM+U8K%m9B$u#yI zmH42{lUxhmg@Hpn#&MI`^F)!%?M%1jCs_kG1F3zYgbYp@W*6v`zUK^aCc_&f?{!+y zYY^oIqoGzM$$ZMqB6NP3WoXCU6wl*}4r=0}kuUvt z=yu&;+HQt%YU7XnGaU7FpUf`r5^{c%hu!|?vo=3@XRZ^_mw4MH_!+Gx3P~XOL;i3( zicNXF=hKQ@Pwr6L=W38)KP^bq`?3ZKzB zlC<=xBLqycC~&Y|H+OPz=6yT>2o)2d(|b8Imr$-cmE3NqPDw7aNIye>YSt`2$+8y= ziWm<)z&;sSCw2U`tK5jhu>5ox*Z(~tkkUTuApey>ODu`0W z+x2AuZQ0uo@T%H*2_O{xoq&q(XdYkj=f2tqOEY$CNL}M$dIGFdV`9-MZw@X(YP$l8N@yoBh+@q0*jRIgih3{*CB+|xlthJamD&2nSP*O z)etp1Mr@{WSA-z07F*plbJ#PAv+`FE25mB8mU5w}&UA`9&QpMGW5-Y8zq}D8pS({^ zP2b&alVf*&l`7*T!L=-9x0;MI=8~i8%Lq+bcHf}2X2LUtKrZCi+1)5$MX~J!5j9v) zZ=shNCUDmM>1I|!V4TgD-+VfPVz_$*B0^dP#<61ABW;A)pMOMK)~gUj6#BlK3c}^% zeNW)sxaH>4P1lD*t3Knz6@1rZb?m&_ha&HDBVJ>e$)or9_s3ULW`9ct;Snn*8lh(rmBht}7Q;mkPBYk9zuoE@iAN zI3egt%K7e#OEPeTIX);Pv55IcoDgsp*2^NG;#kj_jfek+P$G+u`#5C3fGV>Jn_c7c zu?|D*k5$!xqs!Th6B8W6&2THGajR*}O-|xAlGE?~b=^6K8p_c@CAqB>C!1c^c9|3W zt7G`hp#RsRN58xh$$q8cYdCs_&y1@CS4TBkQ;CZ`K$#6=T{Fk5ER6bnioo7tRv)~= zwhQ*OQtXXsMi+3@GTL(AE>Z~1eJZL!g~i`?}fIaUM4$v zO(gH~1Qsqa0mN+_24zF!26Os`P7hdTLM~V73S2`uA=W|3ML^z<$2k@8>~v!3x}N+) z#*BWhwJuXQ-8{eZ8?V65B0EI2j6DL?HU1~x-@RJWpr%`+OmCdT!d^nFfT~HZM|3sw zcRlc76mPdeO;mm)9GvDMGD<_YH(CT1Uph00BraTkmCyu7FiEtV0>bhK-A=9Cf6ArQ z*`!WeKl_F{LiLU>8?9*uCNEW-8E9@$4z?)>`(DexvJVlOzRNl;6!i&{gavxhi;`{A8UWekBF4*V3gIKO&&_GYh|8;Zc;FpL|Js6R=KLJv>nh z+|m6kigQ>@1jm24Ek5xxR*x|KuMRQcH_aeoN#2~m-wqD}gvS#08Lqyp#xE-Jl>eppV+tF)?4lPDs= zYsNT4Pz-TTE0yMlP?VToI8s0SO8=c8N~^6TVEJMGpoF#U{PWi}zRe?;SKNzskFRVA zXop^^Enj$?F6G-{U!`-N08`(V^z|huR|>FoUe*aca0uvTs$cdwVb6Bu&8qd!R^=j! zXn!CJ+BAHsy=4((R&zod$Ak`Gews zfo<66Ok;%N=XDuVRRx zJC2Do{>ZXsq0V#Dcd`U!h+e`PyoVcI!-{mO?<99WOm8H%S$mzco86h4bSti{Vh$PL zPW2DJOcEC{+9}N1Jm_bPNsBJ?`L@vbd?a+#8Jt7LCNg{C$pEd(Zhp1FH{4v2gKO}s z*qM!drj1HgLcSTu+~?D8Bb`c1Z&UNTHWTi?=zz-5D2`iO`?XB?)!(q)o-4KIJl*if zixO=oX!x{pDCW_9KeqhX$MP>56Md6>q9CIaDlFEiZlB))91>l0uW&z74vp#G-sa_Z z3{-~tF*}_xU(uyzNM{lXX`@4V1lVA#8+J63-FS*{duRP<2v%gE(>L zwWX^W)_7(a52ZdN{RRkrxAAD@mB9lOT~85dZ-#tKrNJ;|{9Ad(*EoQL0$)zp7B;(KC2cGVMwYTR@1 zU7qOXL}(l-dSpDHuZ(Ucb}`P%wJa2N338pca0qsth_%VwOMcfM|GKO&+5pAB1Mi_= zTJxi;o@?zgAmCkkp_|oI*QXPNF^>~k5=LSGTX~REj3L|50G*q9oT2N|C-LHya|Ie3 zZoAU43u*wPF8QEny8(8)Krz-%VYCz@bf>~?VhKiUiNPV%Efb-9{R*qP5BvVz_q9(S z9P+y=-oGp9r0U3K13Elpl@2q*)|jn9xM=vCy#f9xO!`JUtMKzsi>;o>=7~^4T^Vug zq3=B*K=a;;==nnQ$D#L}l!^g3$_WfoA72%j%N6DB*focs(}UQZc^5lh5PbOszkTD@ zuO(1mw7CQDT|J#v(|%RgU-^P{yZn0K0h+I(75D3IuLT@iH%p>$Q2$)RbO_w!wdynB zSY9!ag%cyRI9b6%aMXJ929vs+*s-hH5HCR5&uJXwczY{q=z2e~v!L+t@S}3dFt37C z3H@sT{cF6kEGnkBsseAMgwKcV=J6Mgzj)lJBtw(-S{y6}4_FD3Hl`-J_}sIMk^ZGC zeP#2ptS{)ZcT$gp#|?aTlt_MUDWo&HrEJIq<4Vjjl0*4P zum}9aA&X=A+_4D=^YjaKOo%G~IToB4%>-Nqi)ehTxnJ4_mp;AUg-Jzu3qTs=qADs7 zEMGL-rDng+rsPjaIbDsnThO~&v0w%d2huz!C+g*Mn<+Z*R8ilrukb-M?gEFQ!u25P z{etmUJv8IXx;MvFxGZIG3#@YMbnZ|HmmEeg*Ge%nq%zd3vjyI&G~n`#ZBVHKJj@c3 z_&#ZRSuU3Jt18-Yg3a!$I8SZno9?nh)^;V^gHM~;k$#p%CY_`=DGEagqz9V049ix= zKALz338*HV#&>&h6PhK!eRxJ?72D2Z%4>Ff7K>R7EJNObwKUss^vfWk;(J)R?hJIV zWGJ`}yHqIEi?>O!e6`LiXeBSeg(H!zo=-Ov<#=}jRGPEx(SbF;s&qj6_lYw~x7^egnZ-Fn9h)kN_i=2KCbg-_xVPidwB!wKs%3J%9;G||#-p~6X zdtEYF)kKF8QNNPA&WS&^Wunk9rK$~#kgZW&ZV)z3xO09at(%Q_;oq^X7(Z(ZWtSbc z(^#$3EVn>~s(F;FHki~^6-3^(-KmzL>$lQp_c}PakO(z=QuPVg4C+x#Espja%L)oV68i(For`d^Fol#rW_gfv)zv?o0L4LDR+~$^7iD z3j(;2DesKuX`aHi#AYluH?<+ZK?MHtH1pO*G>Y1zj$94rjEBMyzqic4SBt^ z)PE932mRJJYE{;ODc=jDNOPpOmc$h%rs7_TTG=JBMEdjyN5$+&2`BNVBF~?1HIPi1 z8w2_e-9wrXT`E?Y8`uS?^fT?3a+SO^yGMilVF(br!3Vs0N({IFyN~Fj8CrDQ3|;dH zto(^ffr#ErQ~TGwXtDCbjYsLeS)tY519YIcml2h6iKGWM=c@wQNC;e(bN~KL{&(># zqb+_Ta)$FK455Dbu?pg`K^J$sDuu`ZDB;@UHT0>U-tJP_F4*5gxu=0&oSIz1U<9F@ zk2xw|hbY!T2wQ5i;vrQ0l-V+asyGdCD$;^lgA~JW5da=Kjg+>JU z)D!lVsU)p>*98`u#@OvR5pT3~Xb(F@^_NT}ENo*D9t&`xYu^@*;`f2%i#(TRB~LbN zHBa<21EynZn)G_8iNfh?0P_kBydfoXCq;#Qx8ZO=h9abNcDm#(0#;(-Wq0Hk}fTISBe#L z@hvw?uTRtNr;Jt^f@39`f|K&JF5a_~*q$YI$Y>J9PI1=1nn@Fb3;A$DP9xFs#>iXX z+}A5s$U*f*p+-lF)2iGYh1JucAZklY#NeJ}t#?&fFyn_t@$5#4B)K2n!FM?Vv2!@3 z>7&`?CEsjkSORA7exQe33 z(F(T44{Sa3^d1zlFJ4(Tc=;*#O7zW1R>v80joLT{iRZ{A{j}QMtcb9QWN1_LCBtuJ zid{wIr@H2JZDq3aujR)ELst(wKQ+1KO3n`u|6CCrrM!PBFP@(Uok8Jd=|MX5NWvVx z<_n&a&|BZdxABgoywrWbEx(9h1>259{1BY{;1k1G$Y(J2Mffon(TZH(5Zp25nCLJ) zlVnf)LbUTWv9?5(v{mpnpl-BGe)ikUnGxg5L+RJ`_qtnHW1omP&Rf{Py|7sq2j&Je zE`_ctmM98wIE(k?)RHsqDK*T$ev(HotUUK9e}X@6_38oSGIOtj^?8`7g6fSVWJ_V< zvP8SyCeTXAhqzzq=3ebJVSLXcm4=$y&>5>pYqsA7{nN~`Ee9W5z^ViPWrJX{JOcEpjWb?#?w0@4B&y1n$-en;uOnY$ZXvn!rb1dQ^>o7WWd z`<_Nz0?cHDho|9pzW(Qt|7o-tV;1o{D^R$qOnI z23Iq@>y)Ep(b#0dXZ-9JaK3<&C{buuNd6kalJi^@eJ25oh6TSPaE_F3NJrC21Id65dRzOA)(=W8%l*G2Mc43On@}Y6JN!g#--i+|SJ2an0Q3_R1EZ@z!oC39 z@%vA`FxK6<7N2B{5~^yk$j~WLO6GkPl5agyg35Ozo18YlcQRW;gEP%Td2RxmyyVM5 z>>2i?PnDnote7O@+Wm@t%$$yRq;52;=mMND7gw~}!qbG#oqUyX`oRw@I3Jte{aL9d ziUu`h3NT@|mCw67^os^|-Ln1Z7Z2Ri(!o8L$y7a~$8}RST~VrZ82PL#Y~d|s^`Bo} zb#*hNjT=8Bmi!3zUc9W20DcoMZjRSKOx^u?R>A|c=ZybN2-3!>vt}B)ddcR_`(W0= zXctb+PPJp8KKN+WRx379(@<1)`iapuhE!wl9_~c2*UG5*PB;_!pwXM3XFm+>#6BTK z>G}1d;YsobKBn8A95EK+_|M)YzwPC%o|4R?|<`Z*9(5yE`b26j<;-c?8(B4xbD87ZnA*^ zU=-cY``UB@Rv+FjQNBxFv9r2AtkDwBxpDHl!B3LCnmf&!&DilFNtjx6-lLazVQ>fh zFv+6B>;br^+2H7be8Wl7K&6}M*$SHcTQdP_MDM;rGAE$nLCDA)dXq6}q#!v66JB`! zRZVG%DobTr!8D|OyqtlE2c*W7voWOh(1TW2yZ$?~blST{sHkRaTmu<Z)ngWui>I zyuap0X3nMl}7R)fPwKR;aV$y%Z&h*nZG zuaN)vg;Vk4*zs?{(>87Q1%azJvQChl&mi*y)NIGsq)+)bCvVtiTJ9JQ&jld{*Tjc% z3#x_K)83DKf^!qFg%^JYfWaB{h4fxvSk*P>#0+x=Tr6|~jKac!Xr|zW1)T)&F&VB% zo@n)f4y#Ev`O(9==mrgr%?CHk(BE+$_j^boR{#K+)t_$x{Q0o_2nN6dI7Yl$buUhAowmO8`M3c-^Lz}a&X*SuXn zzDszK;QGmjj>+=$7QztBHU%;kKe$ zni*M1^rYqfZqi-@f5?XtYRbTKceT{jCQ12j3NoIJ>jpL@b|G3V+X!zEcYA*QQ`)f(os1Cqbm_wVgZ#^(@78f=r=qbEPQI+ zt_hSeBzsprm-b7FURB;abkmq6xT!hXL^*W!UEEb~p+%?;9R7a!-FsPUwo;=FY5qgO z3cIQb5)|#Yz3I2>XjYv79s=56_Crkr<0D_<&TTus>q=gy;MoX4zRw?sHhG9g)KwKl z=88D>vsY_Oh!7EU4r*6Zz(cvkJ5xrw;;ATm(80^tuvT`#0^bcP^59D>^>d6ov zc(Z4^?9K_m$|e8X6+GmN24myzH)<4B1Y#ro9c;I3 z%t0BlD0YMBx(-iY$+fs=K+ zqqFJ4Q&cpj0yeJk^o{j^blj8f%A@xz$%AG5`2x~EM`eEPpCXC<{#%r2ZAor*6oUoc zCsN;6oi&i%B_w$G2sf~JI7ge_X6B6P>gGMV!Iu^7L_lxPoF86{lu8!Deqa_{HL1*` zTDk}(q92(_i4y!}Q<6BP_^{9ZSs>@Mn}e3Zgd!McxhRD={Jep*O*kVy=)DbL@zk7_VXF`Z8rE1n`+tvRLM;U3CW_w z6%n*{!z7XSgoBY?P6Y1OSaV?sXiTsi!8>xi8pWsL4CBZ?L98FD-ZQ&-K&Pum@*nZM zBm!Nj)bW1msM`DvT_*c=N8{NJEVEh&_fkwBuS?dW=T5NklSnlra|4AwlQdOwng8-> zbWmxT)`4jaD9yRmW@D*imH9Q>;IjrYN-O3;&uf`l&sZXed;E87Xlx?#lPfl^3w_on z@y=8{#8bF~0_O`tLvUfvhXgdA2#a<;0Mnd464&oYHg5k0C!3o@`9;%ng<9LF?9mH{ zVX+9OWT*uFF45Y&MiXI4@qSy7{Ub~q)e7ucWbV*Pi`0NXuWiHGzp}1prTLRlLE|d-=pP5*xG%4-dTus+d+A zJdKhrpQlS}(O)iW5GB{Y^<;YbLuY7hp}%oYFa`O%?uX^WVuXZ1?al?O3l-LP?53I)xhW`9TDLPDS1Bai&_0<859|5Rr|2&KE1pJa4}UT3u^S5Au30ct>!*ks z8y`Pb9#4}qrC*@qjc~R{Eo5u>>aC_AL9API{K9T;|Kuoz)djhPKI)r6X3A5lkK&zk zI=^Rz8{qDW0V6~^$n5i)D@WY> zFfRbTq{8LuGqYy0&eyL6Q5JN#nRkED&#U7{%(bJ`zqiEdY3 zS^1nwW-lA2*I{O=8-AkT4Fe{B+L*K=Lh&=}ynNh&vxSE!5KHT`-Vh{~P9>b83iX|5(oa8ZilNYu04OBDa+XUTMN z5@TGWx3Xi zeK_+Yq(2us_iXYCH~z8#2?qDq-r4&+VV}y<##_6>)d#ueF`y6~vfhOyE7Pu~_pBAl zTsgpphPZ;m?-Y0plZI~$@Gm#JXSbyk z)R_cfaAlWl4b!y{#kexRtOB=A+m5fuTNn75E^6qbh1KtO^2!v&PL0ux%grIz39c)! z7p7=O%Ho@#vUm>W?ECi{(Vzl%wwSK%)Fj#N&|`FuXksW~&42e!yXbm|MIuF*5EcoYic(v!VB2M?-L9Y1h)358 zqOZW^nbMUm3&>|P1|qfWR@5Y?cCnYXyi!CU(^m)X(8!^*ju2#PyG_ zyH4%g(>urL5p*Lptc(IGRp$#QHq#Dd?Iau)R?n=f9n6YK@ ziF@Nn;WODeQrxvdT`U;_92z;AlNqDxShTb)IcFV% zC3hc^8i`M|c~{N5-%V|+%Y{5AlFis7TuY6Vd|%rZI#!>qZl&`>g60}zCGJvQ|IC`x zG0^xjhpeNudI31EP%vJA8}r__-x z%QjVJeet})x^p78D8r-xU$hgRyk0ex)?Ht}cE)#Xy$l}S96S?@G5DgOniILW;PaiO z=->wQ-Qm%X!#64JyNL5#0iT^i)->xQ8^oSHimLkAhkViLJ8_H>d-!W%F&~a)BC~rR zOr%DqRz~8^+xt|J>9|X?8b53Z^qr|-&MpbH(()T_o>L2xNeI%%yGaDgftd2(LTT8A zfq^Ts!72}bma+SfzFq4*z;JOdT7g}e>RFu@f=4$?SP6C9$5X%O;hKL@cyL}nsXo+W z`u;~DP+aYtdg%sK*eFGMt;zgp`V;y~9!qRh=FcbYdopbTo!h>1fniM{$Pd;jhi5bLDK z-ucoKd}wyu+(3nl^qQYNZrjG6zV~n!wUoKfu-* zn(im|L$N!GV>3;oZM`GPdgE5FjYkK~6kgVbR~<%rRD6c@g+ysBk1GcBp0KDq0;!;O zIh)wrl_(Kn%8XjPmjQYseGAi1bn&)T7hD<+FPtvrl>BW)o$LD{bUpKTf6RZK7Mb#3 z1|PlX!Ar_@oO!PjX3fgiN6^D8QbYVO!_BQg)Gv!h-SB?gZ0fS?hSE@efgpY?{S-&4 zG9tF>9pT1XnT$NEeiybmwNZT*r7REbwydWUO&C-~%$-HEqO(%Y!?yM2CXSRTMMcPs zYO>Y3-5%~8;fe01L>t>qoKOxT!5jhW7AgfPQ@7h$J?s!fK#;33$w?>k@~q_Tf&W9V zkqEQCutyoIJFdzEZSoD$>gn98>UM*T1WJZHZ*;~9mKqsWrAB3L=uva-t8xoeP7@s$ z%wQB5e$_lftk@|XyW4C4cHi%qpFJ}YnZpttPK=wz1su6=XA+e4Hz92v5-G1J=&v4} zuf%O+`D*G-S)1K1t=2U_tUL%BOAwr670*NEGG`uH$O|E6Esq*`No+<*er#6U4k1II zZ8*VVCgnykQltFQT9I*^iA9CHC2M<;9JyHRSZ?#kPd z8fib}+$aK@^;D};2hYSn>f_?Rk*P*hr_&PC`ms9C^N@&AUfZwk>^Q@r{(kq+>T*_Ab0}0wg#D3os0j0E4>+9|-R5lfdBa5CXw%a0w(xfZ*)mP`%z5ku5+OvCiul01VXRYqqEjvs_N%k=oITio_kD+o>Y7h7Ghszcd z{o&u&@!aj<*5jq2Z&%O@zbn9axGqA$rb ze4^(T6tCWiwiW!?;r-BoM8*knX*OmQ5#+~$GNr+XzS3wi@hNb>s{YE+v;4NIo@C#( zuL;|RUZ^c0s5zm70)NfO8tKFX)tev%WjNTXn7@$B78fV}HMWQPAi59$?q5rMLfKL1miMw5^ zx1yU;ddhtqd=&tI6l^6asREUh{D(~smSp(GiO78yr|dLV&(maj;)dtgqvBc-Va%Vz zMDkU_in)5*#Az#JB$pQI=tyLM{lt$&i^?Fw)zyI*^(!u(HYuYt>5D^u=qKq&jP6?x z_kG9nCwSzXz+=M^%kj4$fG{G4s;5xN{(P9rDy5dqB?`I45U(aXd zD8ER-ISA|E_)I}^D0n#VInQ~K@kv!>E?yR4428?f71`2$);3300hWV-@;Jp|{E)Ct zHH?0$fxh?h!qSwg8c|2V8U0urkRj{5F1_L<8;rQ{ovsO$dpq+K3c<$`H z!j?}NxaV@ntGS&-XTOGewUkIl2i(~%uQZnQdbj_2!hVsf@P19sFHNEXvpH*&@7LS6 z`XAjVcS~ww#xZCV3?T~uMo9acu*zxSMXH^h-6gJ)J3nB)$!XN!ZZYg3_}r`>f=oy8 zm7ux3Evu=8y_qGehpody^Z|gdn1_R@Io#5P#?11ym7NG^zo`vGV`U)%(&kZQS9FlH zd}Af&ikeE$G=r63sqG4 zr^X)&UR&8Z{H66k_Ptbc3UpOU}K`PYU#sQ(l9-=zOx`(MHjQi_U#QugMq zf0_rC5&`|GU(mwd+{!}muOz1>CodPj1t$c?ZDs-C=7O<91h}|)Ae`nr+yZ=j>>M1X zX8!^OwR3hcwKKQ;1N8vTYW0A_&uz|Y%FD|M;pO1xhj7FAxgn+k9Oe)nm^nKKCqEC& zjNSZSAe5b~9-`9J=3iU&2g>3BO2EvLpVLf00Ag-x$`0W+=i-2v!8j}+mI7R6FiRe8 zGcI%Pzo0D41!e4=Y)v2BX=Q8r+LFz|?)6^sSE5u;h_&$ z*;&}TJO59ShLx?Qx{K)_J~?;=xH)(^IXHPa`M3nw|5nvsh+39T&JVHp2bF`JmFsUM ze;gzDV9o=zrhg*!0pKs#2Wtc+oh(gV?4301?QKLre;{c7X#TTU@u5Rmn7WusnYvg$ zfUy;3=K0@U_2S><-QQUL@xudU5Ox8`|4_yYV*9(XY=1_Ke^@Kb_W$8S_%DHf z8Dbvv{+9PJbUloOZ2uez|IXK+LFfPB@9%#2e^|o<^nWY)kNEvBUH_%)KVslN68>*= z{g*05kwp>ZOLq?5?4EN@C}!-*Ag} zvZ1&tYQ?8)vyJ?)mby@KX+oZgTnYlGpn78UsbAX*{WWZBP88M7q4)2KCpTN#4Q$B#o26;Ly+pVDZU1R zh7hGx(v^$>6~J9-)bcY#a_oGng+0Do@4zN+<{y5m@SCD66er%rAJ;Nm)=TZ86m2aK zEJZYG_ud+fZNxyM=^IM#jK$3^D)#0jp?>m5K8W+*7nq3OTOKh6q{I4)voaIGYaadx zcsSlZ{fyj^8-lsf*(;U>N?nToa@tE`yhTB9QF4P)=|~Zw(De8h5`qwV7rj;p+TqJ7 zRdk|2l=Uy4-CEF9XdLw{IV`r;1nq`>_ccyfO9f@eK$RE-KCGF8N_#xvoV+MSG8UuT zC2c}-i^KwDrMn78$}1!Hbe`nBFTua(MM&cFe6f0kpidOCvTSq&;x=~YWPdE?(P!Tg z_7;Md=sj6`Z=p{J-Afchs|P8^K2R7M6~(f|-=yA^x!&q1ceEHf#bnRvoQ(=(h4s`8 zt6@7iir6n{WwY{|&547~T@V{_2KC|3L}U#(8b?mt&A=uL`BPgPvT;5_PVn3H2FJKH zOYr{qJ+6FXefZi8c-n3COK;~%vv2yzwwl>8`Ku!ZL=MyrK`XDHR4;_#8=B#%i=dA4 zdABB?ne+Sl6in?vxiAkSy7njS-RStWlx%88)QekVbt7V@BJg%&SO1eXW41LlLX2ic z$_d;{PcZ6P8YcJ-PSM*U9WWhROv=a_ODT6GM@5mhoMM(8-wh<4)(9j(Mdh7b^JDHc zvafu>+OG4h45Z`7XNnv1O>s9Ay`*wOv7t>4NtnX86K{>c_w>kjd45IH8+o&D?wCH@ z+dIWfUKDVbo=S^l)7Ff8bHvD<*6n?W5>j zpG2pFU%c7s(HHWa>#$OuzU+>@$yhrod_#3T8{T8gT;b~*_WM3=boUkFU1jM*2(7yp zrApda0hk{#m%V~iMj?YcWNe%$0jY_ruE(GZ^*9R-0$Z$r;RSm@1-+cC_bVoC;XWuR z54oKs?hQ#J>xKR;O`SXAnyzCXf$0q)WVWf-v)P;|ZTxTU*eDl_-t`%%0 zEg>_8`|Go_7fLWsCH=jn=<|h7@WK+`bkgCy@C>2wst`FbLt2jFNtgG^KSXlade1u= z>Z0bgvWjAT^jc5D!0tA|aP7(ayzV8MisrX#Q1E@s+6&HR-@V-TX?AC#B~?4<&d&Ez zW;+h%uo`dQRZR#Zy6pK_6L`31tVqb5lVHV9J*Y0rkGWy>o$F3$qnBT(nmd z_$)ozO4wOnAkTucOazEc)%OEKNDk^gQL46bQnp-G;g}~HfRMsqA=&{;FP$r!e+)rD zKVIeOHXy8g#Lwle!=ztjENa^K1Eu{ZaE3M#w4EA;{t>A?9V|-+CTzhlY3>KHxFu}o zS7hOsPy_wj8^dx&=*zEwD96m!!VDo9=O~&>v&QscMmtR!&&vAandsvVKZwK;B=|$x z20PjL{L6DP)}fZhW=3-YxH{MMw26&p0?KxAGu32Hk-> zAbhoe5GlhNrby0=LJV&vm77H}nREsWeA3})l4s*_s& zbw=Le;&&sgVt#Hd?&Izq>jRaUmgu@@G=Mvc)-e>xWCz&-t?bJ%@i z>E`vB?^qxe*0O=@56i*1;lUk_joXhD&N=DsZ1QysX0q?c7zNJW@=lNCHivBz?$xjhpCAxJd0kRk`LO)^L&Lah8X{!xG}I0@~@{ zJ)mf>PI{1$RXWard1Mo&fGWU-#_pi=CMAcLWwZXW7lcJ93OX-d5C-*Tdrz#pl(a@lp`p6fbc;ELJQq+(wi94}zQKZ`F*+g(Ny|B;$ z1?zxxS)P~}0=TY0eoJ}t;3x4mVp}vz=}Us4<7A|GwdByd@huhU{P@R=o-uc2$}HC* z{j)8lF26)jbNQ9dn}gT-wmf{b(j4aONH7AvX>P5|M;v>wX5R_Kt`T~<#fuL2^Ro@9 zvTaRNBrc?QHyC*J_k5e19mKz_^2vOe?QFj@A2xW^>QB+7%{aZP*?YVqA{y=i=uVw7 zok!IMHN{^?gDw&2@mRh7HRpGRdMwj+hNvTQPA`Bqv;%D}?~w@WyTg6A0-~XdR5%IMNb{6dC ztu`EGS9kejF5x8SqoV;};ykFS3tzzpFw$mRp!~luMJC_YB9-+`Hu~IUm{mn@F!8I5~m&i>?tsVTlo}XSo>2*Ay}67gFGf`+N=+OR;6sme5h%?~uxB#AHHrIuN5Y2Ybam~Fh_ZG-7xKDiQBUyt z`TFqsajkcC$*pGMdTE1{X|X*MhsB@;O8Lx* zCA}>UM*PjYOm~sqyI$lvC5FA@cxNrdk|^t}93=*0$J}g+P+~pTx`15*^K;W4!lj9n zdW|SxL4-Hnm%jEzxP7Rzt1o9VA+&TG#8VCfO19HfSJbvg+sX?$Sx<>*shVd3 z*vAGI9nh0J_V?*7R^5539^T*4O)!9dMz3QcGK3P?Gi>iTd|W*a5Y{2UQy+b=#_Z!l zb7B7)M5I(N(Uo#(Y741vt6HlpsyDx9ui}!b-VtA&5w4)9$EiaOxXv)7i?&k4_ov6U zQQzQ=-@!Ji>Ogl;=C0mqZo;bse&R&y_tG0|_2I*9buGGSFWyp*(4@`5#m5`e@xS;O z$T-<@!JUF8w-q9XIjb1B;Otd0x0=9C;Bw3@L3;^wO-0(SkRIPGM`6|~OE#3SjNiCE zb%q-dlg0d~L)y-UBwXkmUWrI1?!T$YMvH5oJSPK(0QHpZ=A0KLSRb>7wu(DV>_q!3 zIjKvfP&kV@Wa^D*t@!f*e5+4n?XE-4 zD%@3$fDHJgk}2TS;(*z0GO8TX(GrY48!N?FQ=_>iK@N#-pb3b`3#lddOL6F41P|l& z^@|W-Ow07pVMxUGd~eL!nB{nG?!Cb)AR%;1#1~y37AaF;Qkj}+NOO*DSwbb{H22gm zrRHlPrD~2zik+k!jR0nQ5hm@&UrNyq;lrs)PR>Cl8G-2$q)3!77}v_BVJe&vRS)0& z4uFgXIYLe`%B{%-wsODnw?4){Ix-LJ7XlW04r=FV&vi~BPmm7i*izj(A6JZYEiBD9 z@M5q9g5iTZnt(qcU&%Sh9&R8T@BJcx4nKfa6xjh1fPv5XF<^P_?v-!uswm+p0WbOi zWkB53{SvTvHe;hCe=#=DDp$Ozy>&GQH407Itd(fi@*k|^6^q2Oo)koPcXk!*DES__$6iq&Rxdu9E)EPX^L=L zi4`yX^fmFI=MAh|Byem;UZ#Nd7re?sqQQ9Vhjb782`A$d=b!w;n3VMJ6@w> zwxkfA+4#7<1du`b(Klc;){-2zcdHA|eHa@w@d(Va>mLDi=`$$g@P?*OksUQT!+i0F zS}Q4>xw#ic(@7YiU`?E zD7cZD8Wn^UIj-19zs3GluoMpPjRrP<)kO6rrA200{ZJQrtdvh3g}3aKFZC;%bY{h) znnME-S@sgRGl6ZS&2)l};-Pm&b~W+%$geX|4yb@Opu8|O%8fQ(I_gsuDX@TUs6?`H z2X>D}qNYN7H_@9x!TL7ui^dk`Nt#6p)pn$X_;Z>|J11j)&21QCD<8_{BNK|(MVn69 zp8BEJdRy#ba_)sQv}GPtvXt2`2`hMe7xmt7EI81j1r{?Y)dxhX`J)^}cg&HW7pLhi zowA^&v5xt1HY$#!Fh5f-CN2f?lP`H?&Swm|x^8PsZ;0B-VQ!z|?XoeODFgT@88=x# z;n*i-m;sMf)xs%vUBdDQ9Ua}chNz~s+bo*5+`({nPgG%%GSC8pcep^}NJh$C=y*5X zf(=Vy!5FdhaV_4t@0hc@ih*0-TP)qC){RWeJ!Uyd|3~fAmV+5DDm>`6#&JO%$9EL8 zi`PaippEnT)>S0pWBVR4LN${13QmlG7z+FRz*MqM zU1_Q6qfL#B-9%VxnsZ@lU?#x z!S8T|q^b_V{O_6rHt*fqZ;^^|Oz?pa=G;@eup0dc!c1_B2jf=QAm$7I4Kpo`QRo{a z+JfHM2CBHJ(2x0>_ei=-@0EimPKh0F_Cwk2B;?o|RHgn4Z-EaLCwm$lSU!ykHNjtr%7F02hwCoPlqOeoahUSvB&j3l>RlXLO z3XoEU+;|kgChx;u*-CbISVXkc!3!}P*su(TnuEx6uY+ow<*^)BlZw~S(*{N_B%PW4 zF*5d6V%;(^ad8D*!&{p(w(t0E;4v{!ePwj~q9_`z&SwttTNht*&b?4nE~56O8q88D zIfjOmwPyf>JEy$D*MY_4sAYz_@_s;x_Odv2fkFf@)Rl7Y8m~mST!GVPETC?S0Oiu8 zTIIDy#>-mWymS#8cP6CHjLaUEaW-N@t)hHSjoPTD#W`5QXgY9}#C2=(5wDB2= z&etHQU&K%4s~=2bv_Ax4NEFa^tN5<*beWw>fBvVt@6(bgT`#U)Ut)$CSz=iu*TY}I z7R43*6>Iw(WX@_f(qbX|Axvp%H9g*#IdJk`LYHP)4Cl(3;H;^hOh-S07{#9*k6-Og zS{u}g3W?lIoWC?D{~Sarmg(Q{<}0`9)@EddGNVA@WB+)=`qHeW?=4YrUqQ5&evg8=3Xz zbeDzE`+3QgMn{y!5N-xv%3oKS8Bsq6@1K!QDuRs_q8C0B$gV3851Owi5z2ZMS!2aV zehHap&@ZU;bVYUK8?v%#Sb9qJuCRVMe#Kj^YoY(8iMteY)BM{qj)=XF&axG-N*r^A z-_nyQGv2^ZJhAX+jXP;VQA#A5x4^S#HafjX`fmnd%cQMRqsF2cY%;Y`h?r3eas9{b z9D;o06Waie(@QKXeUx@El|M347QDM4ek(X3!*H3u5Fi!Ed0I2-lZd4!Z4=daQwhK@ zd1^A(hA=Dk%=Hex4t*E7D!MpFNZ1aG2Sgv0tLh^n`K|Lj*Pd}PO4JEiJ^u#pXK&z^ z$Y|#KtcdL3KTxF%FlosEXRq|*r_{!6?#MZqe^`7ijEtz$zS<-ZtYdItks!fw_*; zQZ=~^iFVmCXx&T^_5OzH$=WBblKu+&8e|b+s`gH${74;#;k21Sf}iO^a_6-jc$2KC zqa8oU-V)$CCLNVEKZl)VfJ4gg99(1GA)+h=11RuU#^+{9`_Rq^2goC=;Ut+2bCM zs!|5NxbMxPk`#m+iuc3AXxAL;Kzcl1Wf+FYig^*y(=cB0|A6U+w?lqNL7v=@LJ7+F0NC|v3KLNsi7WL%Z;Inq;d^U}g8|WU6z@5_juI&S9Am7zE$vngTbioqu*o ztq8EE7KIlPPnYn!lQ8Nft-S}=(Ol9ceo9q3@(imH+CHZum>wOIcz*c<66!8@r@v(H zB;`@Se>1>D(tlAYO5Xr|KeAjioDKBMFwoKTskSZ^(m!M5LN6{9T8D0vmEU_ zQGhcfCwmZl`EvddMoh$MPvoF8dG9V4Z*cVbye#9!nQHj1S)j0>9@AV=Sl+DjiY>v= z{)S&FPTY+WRGDY@XSE$w@jA=CCC1@|(1$Ws<@vo%EfgFB!xR`>oR3C?O{LT}HNyt$ zi_mnZN`6t0Cs{8k)#u4aVE=fQ(1iMYYc7uA;L~E#go< zd6db?&w2WqYISYw<5%&`5(c+gkb|Z00;N%g*jn_kJ4p>!Lhy9YNQD|m?CV>qG?eyh z12bJ(Nk*=R#aQZuALpg@Q+WuQa8>|JaO2D*RbFbe_o*<3pQD`;TzVUKb5Et$Jr@PH znxSJ-auq1+d%w!p*ctR}`&8}AvZQTxQM%8R`3H{D6=awm-QM%0ZR#HEm3V zdpMGYl027ftDk7s8{3*|DscMT+eajZscSiYIa_w9MlPC={gFVbPs_2hE|sV~+Bsh$ zW8N@kffGLiuZouaAK;>OUAgtOCgnGRHMHGUw zOo?cs?Qot>nL$PP%Z5(Z;w;etEV-nUfQf35&c)KB8+*f`wHQL^vjE*Vqk(}v#!HM# zr5NHTvtN#k7ejQhAKSf;-Dp9V;AlJtnm@NE$sp~22v;#UFbs+;hOZ_2lZfBvn#qTt zeW4LoijoMZV(M?Zi=$xSi_WZWyB@D zqgYvs#vu_{*)JH(_johrNKxvgrj8iGgq7t`Z^gnUEfMy0-}!tBORYv&J*-y3b)?X1 z6TD@IZc~aTYoa?2@9ap$G(mcb)?ynXryJZN`7aHcU)~LBgNbis{3eMBBv)5w zzbZS)70@&k9E{(uO@>5IHBo6Bjy1g~G7WtFL(8iduO=u4-cw#Z-MJCFGi~G>zeeCp zE23B9VK?2~ig<(m8ME3s;=_LVp}9|YK&csrJ>Nz&K0oXXIpDjT9|n>t5hlL1wso=( z6)m?bHR2quT}mq;-Q+$aP15P}n|KuYt3E!d@5}(Jmd2!1SW0^6JI(|fpy&(BQk?BOq2Jix5I=Q#bimsMPCO*<%bor z;vd#(N0HMGD_nle;7bnd6Sg{p?uElk2Z{5bT+ zKp4=I=B0sW^qx(jHekJ2x^#L@uaSUM%;0%z{PH0))!J~WT3SrgYC>D5?bi?RKnwzA?A;tia<11l@OKi6Uw&`mP1c({1tz|zzl|KIUrfmR2m3`9^gqV z9GS`^MM^2kN|C;Co}C}~+z~vbimogxez#Bwi74qiHExoHZ`9_w8FbZTkaold6=#e1 zW<)e(vYj3aa(|SjwDPsIxm9V~r$L$Vb9x<>d?ygmWWzhG(;Hkog!s16L2JCx*Q6mD zyT3r8;a6h*8ezcOZ@tsejD$7Np!-Go16S*1n}kgHKuKDJs~Twp^A{7RejgTmyH*Or zqq9L<9_M=Ot`BEr1mE+qQI}&d6UEO_4+H`fgEZ8bRZrP49Td$wXk|lx-wqFSG0$y4 z5N)!6>*lA`?|4|)l=d*(N5{>tL6x0S0mFv$u{zOi1QMMI=ckddHeKl<=#f;=t5T7H z9ZPN9%8X#n)z8gt>nkke_6`~@ir;(zM4KSv%%#?;EfZX;`Lb4H zM%HP4(Y5XcSCXeE06-M_=PbZwONPSpE7ZDy0|n*epH_he%4no5uIe(63&_CdL`QFu zm~Db0HPE`FlvU@wmLl2vF6zEycc)oMab4D7>vk33h@CY#J<=oS+TMUP2Ks$&wIj(3 z^nq^JwmR$u4!PI?B3Fs2h(E9^7Wr7Ov#Xnd>~}Qtc;%M$b$VelM6?opB=eo z4AJXy9=3L)iZ~pTJBDR|vQKEy{B@s)B|m9!AGGprgBtZUX6E(!sX|^r*^Iso7~!S$ zy}v5KMUI}R)Ju3cC1I>Fnr+Wa_!}9y!;U(W<`fO@%}t6ErS64!M)eChS`FVM25&hxlhn2ml3Hw z#H+WV8Xbf>8!7&rt{O9-Fsx&Ft_awMvPK6;p4Sv`=VNh$mWwtX+>%sZV?wvlsw}u3 z)6rS>u>Q%RJGDg?Emtb_T(8{Nsb4My7vM*yrF2mQ?xiqEqof4nGOirM#vhiykU!DP z$*0s*kIWh2D1}yX^w%yA<#1JNV%tWB5l&mZYfxR?!(CZxXL1$wmcFcvNxQ>|dB2ABjbatX6LUEd*A|D6sooy? zW#op+kd9Ad;cOloC9-_kRv#*NB2_oRav$~s$Ebt)uu2Bwu6=1evCjtjT!$Vu`?5Jqno#MIi@7veZ3K%K@1 zZpH`h=s0{*ux}%ur=tZq)fx9hX{pHn{2ItOi$xpPp+BqbxHQfSe7zEmxR@mW_z7Fl zFk}tusx5YkreB_KqH9Et=_j~)qPo(v=CD?FtncBpOJ%#Yp@b|^VwtvRIW9~9%2UXT zC%01cMCJv$BPAPq^WxrJe&fLyDW{2hbji1d?xrKo7gF|LUp}9NTI+P1hDF7zmK(Z$ zL=Z?HUbj)onHy3}zB7X()X+_n5$xLVjjm-v^Kj=zDA<>6a2FPTQXxzHbZf;Rx<&E! z6-}3XRs3dSOV6IvaSfUbp6hSZXG{7U_iCth4J6mN3qd8U9D)+`Su`Kch(q*JCzX`K zrY-Fh9XbpXs~%C``hErPGmE2RAsP~DbO=YTA!}!8op&E!)ue*i!qZJD)_J%}TV`P+ zNyi&O<&Eze`5Ae$qtjxlljixtN8&9aBPf$5F84CB6&M7Jq)s07u4Wi{HGOd~VniLl zNJw~z;V_-?a(+4&olACe6`GlJL-LjYBd%E*2sm^u<{Ft)XroSy%!!?)k-E^fHYeX6 zT9V^8&k62OD}Jwmn0GczR1EJXnL^j=TRwV?&1@%DH)CNMaTzt7u~t*#u=akdAz z@11R}mYP0ad`X^r7x{~UQhPt!2GEm@*BYSHAHOB_W<09y!W-?y$OK8PZJW0)PHw%K z2I9_Y^e~U6*V`0GIUJ}Am3open9iDY1*VxlYqGj5EF$AHD|GYXGcKvC$m#d@HAhIz zTJI*|4B4)95JLA7Pk1ujn~IO75zYD)OZ#tFDzeTW>22{M(zUoanVM`8SXPIz+_uea zUr}gqZRdfqb5o{KXEe%I)8Ter46BB($ZXrjgvxoHGJfpuzsd1c-Cv&9QZXokioCcP zs|e3AE{1ds%uvP8YLFb^QaYybw$#y|PS(l3n)10ddanoXZfwg~JHGN0?1eO?OSG9Vk|=I;TLmPJ#G*RT=Lfhn7^3GQz0je zYYQyt;f2sip|xTyLx2`&>3h7Z>-VWqY+8_huPa~$vsNzr*cCIsZwH_>B zkGBffP;6QvRN3QUW5K!M?suWl73a2nd^+8@_xvN;lnrATuX?>X7yb%q5^z9+JU~c% zQT1u@D{6HR+IUIJ2mF=CBxAGnveRdXikfG%i>53AVVJ7#i+V0DX#(_tSK~bWAXH7o z37=9-lR%o*D2SpAg*59cq)cW>IW89BiG7SJtay zz@feg6>ibdxQQN1;~%KuzM2;KPnkBCa8IsX1|^MKa+EDo3>I2q1}%hlcCk3l5;{)e z_^&p+jL{IU-}uSM-wMj#ifXcdi;?#t`^jI|+b$@hHll;j{{7?A_H=jSoziVNX;{@9^^Op{7wA`6RlzZ1o z;HAlPilA2Kg51w9nDlE9SCo?>6p5h6gTJ^7)6?b6d=!pvvN2|NusWzZzwuC2h-`le zj$O0=viTC_M2#=e^_GSB@jMqTC3Qz-6-hB}_~poT%m*}|P?CPFFn&>$*0ZaLkbAR%u% z41VFbHK(il7h$m|X094_qr@3s%Tz&^%wvh?BW4EEg^bEr3$B6RVrWG!Acg3wXsnM~ zhP63IWZ-MF!iK~8DpDlU^Se?#Je5nrpFJ1lQMo&8J5+ti@)iBP3%y!$K`k_N8%*Th zALI>r1j{2Wi8rgOLs{**6oe*s*-BGP4B*;L$f!9)B4qFtMLcVpmlV?Az2OC!iX7hi zcNT0HW7_Yxu*?JqBe#(y_^rek1r-&2*OhQ?Urh?QV;nq^a6AzVTQlyo>Q1o}ftO45 zz^45fK4mdDmgKhjA|(@a=f6RI4$7SgsUs4auCP} zN*c&EBDl<#e)DA}V7+svDSINO6TFBk-Y#H_UG=&0$}t(>-n{+-JZy!%z4JHP&k2zl z-w3D6jlz=^LqW=FMqO47@_C2mn#JgP?@n~Q%(X@$ z(OzM!WOhQJ##ZeFS35|Y*M)2&)bNZ zxS8BuXxp^TMU{~ogS*V=&CQeqTsuEX7?;jdJP-UQQtSU z!f2q}uSl%gzfnG;uqNCt%*{$|grPxgZ+SjnWa#k?p##^cGgz=Q5w0d1ziXn`h1r`o z<52{VMRmh6<{<4%t4A*9-g=EQQo6!z`<l8WseZW>ZML47hHZ?wEO!cL z?3_GT>Nxt4oj`|wf`WKB&2v{c|5`!Lc2-!Dh5E~3LN*vQx$>eAsqS}+-A|XpnA7W5 zVTTprTR5K2J~I<}eoSvuq}N+2c3&vId-kGyP)FdDC#+vcPL_%l|NdCVO?$(Rxdu+p zr!5w%o{a#~`x7jZogcCMG|p zPs{So9ba0Fcx7E$JAUq0Dbh?&y#5lgv@4fp>wXfaoESO(e(i{WdAr8jj-~&X1Yq~d zG2_E8AI-3+9B_6E&T=;bm7I`!MDJ(Lt)AAUO!ac4B67K?gl-mcz`eZ7ylm1+4sb)( zUMWFYqR79DOz_fs^O_ds=8dW;cWQU)ixK88i5Jdv(=%WfRf-QZg%v5rY1Rn~nWybF z1=g?8?Kyl{KXDcL?FVH%OzT7{W)wbqo@Nh@J0j!qR$BS_8IY0JJlHIWK>jgeNUfP@ ziO0F@h2*g~@J!n{+2L7)`vA)n|HE0iAukN-ZK~!qZ2mXLMyl37UMtz^hznd|!y|JB zb`-X{iWCOyfdj-kQCfZ;eIwsTv}Ai6lxcF+%jP zIjS^uqD~;Gol`!60gK$VcD$Oi_*6Q`q&psdSVHrzkAgE`ve)`nqQhqYmCccs&q$|0 zy|PxCO$86NySIesM&8nNNuq7%X<7Ws!*}&E>y|{iLN~t+^E=82DfkjRoJwNi+_J5u z4_8ThKp%(;3HM#|ww+s8zBQ_MsAV1A&>J=^5wTZ5S;L;999kcJX*fw30=sm7rNd(EFx% zkA>hhn2@^EcP6<4_xkIyS4WClTC377-Tj8npLw&;gWLV`2rV5yMc)5TxZqTi5{_QW zB8GW2sHl?ej+q889ZXHF#WY)nFtdVlI%C%^jILU;pB@foAhBL$!VRBW7Vr9$`b52s zki^wY98rEv=%(lRmZfa_ZnWSGo2o6rA}cow+nUc`l|asx8B|Bwe;s!dha;rOiFwOQ zK6oTOQG@C2mhQ61dBVh)BuuYxZbvX#7pv5q=I1Z3_4^swxt=z@nX;TuH0anruZeTC z==Jl^6;s_;Rs0uEb@_5aU6YFQL~kpXPb;US9N5BzKHPF7yLm~0^oaGKnB_jIgZ{|F zpc9`YJ&xiAshB4ESy2YFJC%8N?*{o&_h}ZEY*Cd@iBZu3%uuV3R?+2R3=LV{cOrX@ zZ1cV_gm8Py#8Lzo8$v3-B)j~pegd&p8T+(`HjAK@IkkxSl9cTmLV1P7qp1=%sq;Bt zt5Z@6l{$39INr!F@nn3Oz7JLd1!*j`YTWE4?dgUnl*OR7#MJwDl69IlK`D73^9pVRyb z>1i~c!09822{o~5sWcuMi)$kMZZs5(m;KNTpkP(yX?jcmgM3 z{YvCdmbrm8&MHFb&|7?;H19&(>E{gIoPow?>I?T=K|SRRL5+*dNc@5;+5O$Z*a=RH zM`wx?R?V2dE?%5sgKIsC(*(`y39jPuUj}6r*%(Gh_hB~F#?S#w zO0uEYnVC{$r!RX_KO{}ml_DMJ2l+f4J>eL!r|Eb$P1^xq`gH8QIgCf3lZ3ayisGR- ssvfLVmX+~;^o`HIej3CZvU`ujT&tMK--T!J=Zhw&w31Y*gh}B40w;?+YybcN diff --git a/README.md b/README.md index 58a279b..476e9bb 100644 --- a/README.md +++ b/README.md @@ -1,218 +1,173 @@ +# `ScrAIbe: Streamlined Conversation Recording with Automated Intelligence Based Environment` 🎙️🧠 -# `ScrAIbe: Streamlined Conversation Recording with Automated Intelligence Based Environment` +Welcome to `ScrAIbe`, a state-of-the-art, [PyTorch](https://pytorch.org/) based multilingual speech-to-text framework designed to generate fully automated transcriptions. -`ScrAIbe` is a state-of-the-art, [PyTorch](https://pytorch.org/) based multilingual speech-to-text framework to generate fully automated transcriptions. +Beyond transcription, ScrAIbe supports advanced functions such as speaker diarization and speaker recognition. 🚀 -Beyond transcription, ScrAIbe supports advanced functions, such as speaker diarization and speaker recognition. +Designed as a comprehensive AI toolkit, it uses multiple powerful AI models: -Designed as a comprehensive AI toolkit, it uses multiple AI models: - -- [whisper](https://github.com/openai/whisper): A general-purpose speech recognition model. -- [payannote-audio](https://github.com/pyannote/pyannote-audio): An open-source toolkit for speaker diarization. +- **[Whisper](https://github.com/openai/whisper)**: A general-purpose speech recognition model. +- **[WhisperX](https://github.com/m-bain/whisperX)**: A faster, quantized version of Whisper for enhanced performance on CPU. ⚡ +- **[Pyannote-Audio](https://github.com/pyannote/pyannote-audio)**: An open-source toolkit for speaker diarization. 🗣️ The framework utilizes a PyanNet-inspired pipeline, with the `Pyannote` library for speaker diarization and `VoxCeleb` for speaker embedding. -During post-diarization, each audio segment is processed by the OpenAI `Whisper` model, in a transformer encoder-decoder structure. Initially, a CNN mitigates noise and enhances speech. Before transcription, `VoxLingua` identifies the language segment, facilitating Whisper's role in both transcription and text translation. +During post-diarization, each audio segment is processed by the OpenAI `Whisper` model in a transformer encoder-decoder structure. Initially, a CNN mitigates noise and enhances speech. Before transcription, `VoxLingua` identifies the language segment, facilitating Whisper's role in both transcription and text translation. 🌍✨ The following graphic illustrates the whole pipeline: -![Pipeline](./Pictures/pipeline.png#gh-dark-mode-only) -![Pipeline](./Pictures/pipeline_light.png#gh-light-mode-only) +

    + + +
    -## Install `ScrAIbe` : +## Getting Started 🚀 -The following command will pull and install the latest commit from this repository, along with its Python dependencies. +### Prerequisites - pip install scraibe +Before installing ScrAIbe, ensure you have the following prerequisites: -- **Python version**: Python 3.8 -- **PyTorch version**: Python 1.11.0 -- **CUDA version**: Cuda-toolkit 11.3.1 -- **OS**: Linux +- **Python**: Version 3.9 or later. +- **PyTorch**: Version 2.0 or later. +- **CUDA**: A compatible version with your PyTorch Version if you want to use GPU acceleration. -In order to run `scraibe` properly, it is recommended to install `pytoch` using: +**Note:** PyTorch should be automatically installed with the pip installer. However, if you encounter any issues, you should consider installing it manually by following the instructions on the [PyTorch website](https://pytorch.org/get-started/locally/). - pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 torchaudio==0.11.0+cu113 --extra-index-url https://download.pytorch.org/whl/cu113 +### Install ScrAIbe -This ensures that the right torchaudio version is installed. +Install ScrAIbe on your local machine with ease using PyPI. -We recommend using the CPU Version of Pytorch for a smooth ScrAIbe installation across both Windows and MacOS platforms. Should you face any issues, please contact us. +```bash +pip install scraibe +``` - pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cpu +If you want to install the development version, you can do so by installing it from GitHub: -Important: For the `Pyannote` model, you need to be granted access to Hugging Face. -Check the [Pyannote model page](https://huggingface.co/pyannote/speaker-diarization) to get access to the model. +```bash +pip install git+https://github.com/JSchmie/ScrAIbe.git@develop +``` -Additionally, you need to generate a [Hugging Face token](https://huggingface.co/docs/hub/security-tokens). +or from PyPI using our latest pre-release: +```bash +pip install --pre scraibe +``` -## Usage +Get started with ScrAIbe today and experience seamless, automated transcription and diarization. + +## Usage We've developed ScrAIbe with several access points to cater to diverse user needs. -### Python usage +### Python Usage -It enables full control over the functionalities as well as process customization. +Gain full control over the functionalities as well as process customization. ```python from scraibe import Scraibe -model = Scraibe(use_auth_token = "hf_yourhftoken") +model = Scraibe() text = model.autotranscribe("audio.wav") print(f"Transcription: \n{text}") ``` -The `Scraibe` Class is taking care of the models being properly loaded. Therefore, you can choose the other [whisper](https://github.com/openai/whisper/blob/main/model-card.md) models using the `whisper_model` keyword. -You can also change the `pyannote` diarization model using the `dia_model` keyword. +The `Scraibe` class ensures the models are properly loaded. You can customize the models with various keywords: -As input, `autoranscribe` accepts every format which is compatible with [ffmgeg](https://ffmpeg.org/ffmpeg-formats.html). Examples therefore are `.mp4 .mp3 .wav .ogg .flac` and many more. +- **Whisper Models**: Use the `whisper_model` keyword to specify models like `tiny`, `base`, `small`, `medium`, or `large` (`large-v2`, `large-v3`) depending on your accuracy and speed needs. +- **Pyannote Diarization Model**: Use the `dia_model` keyword to change the diarization model. +- **WhisperX**: Set the `whisper_type` to `"whisperX"` for enhanced performance on CPU and use their enhanced models. (Model names are the same) +- **Keyword Arguments**: A variety of different `kwargs` are available: + - `use_auth_token`: Pass a Hugging Face token to the Pyannote backend if you want to use one of the models hosted on their Hugging Face. + - `verbose`: Enable this to add an additional level of verbosity. + + In general, you should be able to input any `kwargs` that you can input in the original Whisper (WhisperX) and Pyannote Python APIs. -To further control the pipeline of `ScrAIbe` you can parse almost any keyword you also cloud parsed towards `whisper` or `pyannote` if you need more option, try to check out the documentations tows two Frameworks, you might have a good chance that these keywords will work here as well. -Here's are some examples regarding the `diarization` (which relies on the `pyannote` pipeline): +As input, `autotranscribe` accepts every format compatible with [FFmpeg](https://ffmpeg.org/ffmpeg-formats.html). Examples include `.mp4`, `.mp3`, `.wav`, `.ogg`, `.flac`, and many more. -- `num_speakers` Number of speakers in the audio file -- `min_speakers` Minimal Number of speakers in the audio file -- `max_speakers` maximal Number of speakers in the audio file +To further control the pipeline of `ScrAIbe`, you can pass almost any keyword argument that is accepted by `Whisper` or `Pyannote`. For more options, refer to the documentation of these frameworks, as their keywords are likely to work here as well. -Then there are arguments about the transcription process, which uses the "whisper" model. +Here are some examples regarding `diarization` (which relies on the `pyannote` pipeline): -- `language` Specify the language ([list to supported languages](https://github.com/openai/whisper/blob/main/language-breakdown.svg)) -- `task` can be just `transcribe` or `translate`. If `translate` is selected, the transcribed audio will be translated to English. +- `num_speakers`: Number of speakers in the audio file +- `min_speakers`: Minimum number of speakers in the audio file +- `max_speakers`: Maximum number of speakers in the audio file + +Then there are arguments for the transcription process, which uses the "Whisper" model: + +- `language`: Specify the language ([list of supported languages](https://github.com/openai/whisper/blob/main/language-breakdown.svg)) +- `task`: Can be either `transcribe` or `translate`. If `translate` is selected, the transcribed audio will be translated to English. For example: -``` +```python text = model.autotranscribe("audio.wav", language="german", num_speakers = 2) ``` -`Scraibe` also contains the option to just do a transcription +`Scraibe` also contains the option to just do a transcription: + ```python transcription = model.transcribe("audio.wav") ``` -or just do a diarization: + +or just do a diarization: ```python -diarization = model.diarize("audio.wav") +diarization = model.diarization("audio.wav") ``` +Start exploring the powerful features of ScrAIbe and customize it to fit your specific transcription and diarization needs! + ### Command-line usage Next to the Pyhton interface, you can also run ScrAIbe using the command-line interface: - scraibe -f "audio.wav" --hf-token "hf_yourhftoken" --language "german" --num_speakers 2 +```bash +scraibe -f "audio.wav" --language "german" --num_speakers 2 +``` For the full list of options, run: - scraibe -h - -The HuggingFace token will be saved after its initial run and can be found at `path/to/scraibe/.pyannotetoken`. It does not need to be called each time you execute `scraibe`. - -### Gradio App - -The Gradio App is a user-friendly interface for ScrAIbe. It enables you to run the model without any coding knowledge. Therefore, you can run the app in your browser and upload your audio file, or you can make the Framework avail on your network and run it on your local machine. - -#### Running the Gradio App on your local machine - -To run the Gradio App on your local machine, just use the following command: - -``` -scraibe --start-server --port 7860 --hf-token hf_yourhftoken +```bash +scraibe -h ``` -- `--start-server`: Command to start the Gradio App. -- `--port`: Flag for connecting the container internal port to the port on your local machine. -- `--hf-token`: Flag for entering your personal HuggingFace token in the container. +This will display a comprehensive list of all command-line options, allowing you to tailor ScrAIbe’s functionality to your specific needs. -When the app is running, it will show you at which address you can access it. -The default address is: http://127.0.0.1:7860 or http://0.0.0.0:7860 +## Gradio App 🌐 -After the app is running, you can upload your audio file and select the desired options. -An example is shown below: +The Gradio App is now part of ScrAIbe-WebUI! This user-friendly interface enables you to run the model without any coding knowledge. You can easily run the app in your browser and upload your audio files, or make the framework available on your network and run it on your local machine. 🚀 -![Gradio App](./Pictures//gradio_app.png) +All functionalities previously available in the Gradio App are now part of the ScrAIbe-WebUI. For more information and detailed instructions, visit the [ScrAIbe-WebUI GitHub repository](https://github.com/JSchmie/ScrAIbe-WebUI). + +## Docker Container 🐳 + +ScrAIbe's Docker containers have also moved to ScrAIbe-WebUI! This option is especially useful if you want to run the model on a server or if you would like to use the GPU without dealing with CUDA. + +All Docker container functionalities are now part of ScrAIbe-WebUI. For more information and detailed instructions on how to use the Docker containers, please visit the [ScrAIbe-WebUI GitHub repository](https://github.com/JSchmie/ScrAIbe-WebUI). + +--- + +With these changes, ScrAIbe focuses on its core functionalities while the enhanced Gradio App and related Docker containers are now part of ScrAIbe-WebUI. Enjoy a more streamlined and powerful transcription experience! 🎉 + +## Documentation 📚 + +For comprehensive guides, detailed instructions, and advanced usage tips, visit our [documentation page](https://jschmie.github.io/ScrAIbe/). Here, you will find everything you need to make the most out of ScrAIbe. + +### Contributions 🤝 + +We warmly welcome contributions from the community! Whether you’re fixing bugs, adding new features, or improving documentation, your help is invaluable. Please see our [Contributing Guidelines](./CONTRIBUTING.md) for more information on how to get involved and make your mark on ScrAIbe-WebUI. -### Running a Docker container +### License 📜 -Another option to run ScrAIbe is to use a Docker container. This option is especially useful if you want to run the model on a server or if you would like to use the GPU without dealing with CUDA. -To get our Container, you can pull it from Docker Hub: - -``` -docker pull hadr0n/scraibe:tagname -``` -We provide different tags for different versions of ScrAIbe, including different whisper models. -The current tags are: - -| Tagname | Description | -| --- | --- | -|`0.1.1.dev-large`|Uses ScrAibe Verison 0.1.1 and the whisper model `large-v2`| -|`0.1.1.dev-medium`|Uses ScrAibe Verison 0.1.1 and the whisper model `medium`| -|`0.1.1.dev-small`|Uses ScrAibe Verison 0.1.1 and the whisper model `small`| -|`0.1.1.dev-base`|Uses ScrAibe Verison 0.1.1 and the whisper model `base`| -|`0.1.1.dev-tiny`|Uses ScrAibe Verison 0.1.1 and the whisper model `tiny`| - -By running the container, you get access to the CLI and the Gradio App. -Here an example command for running the container with the Gradio App: - -``` -docker run -p 7860:7860 --name [container name] hadr0n/scraibe:tagname --start-server --server-name 0.0.0.0 -``` - -- `-p`: Flag for connecting the container internal port to the port on your local machine. -- `--server-name 0.0.0.0` is used to make the Gradio App available on your network. - -#### Enabling GPU usage - -To use the GPU, ensure your Docker installation supports GPU usage. -For further information, check: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker -To enable GPU usage, you need to add the following flag to the `docker run` command: - -``` -docker run -it -p 7860:7860 --gpus all --name [container name]hadr0n/scraibe:tagname --start-server --server-name 0.0.0.0 -``` - -For further guidance, check: https://blog.roboflow.com/use-the-gpu-in-docker/ - -## Documentation - -For further insights, check the [documentation page](https://jschmie.github.io/ScrAIbe/). - -## Contributions - -We are happy to have any interest in contributing and about feedback: In order to do that, create an issue with your feedback or feel free to contact us. - -## Roadmap - -The following milestones are planned for further releases of ScrAIbe: - -- Model quantization -Quantization to empower memory and computational efficiency. - -- Model fine-tuning -In order to be able to cover a variety of linguistic phenomena. - -For example, currently ScrAIbe is able to transcribe word by word, but ignores filler words or speech pauses. -These phenomena can be addressed by fine-tuning with the corresponding data. - -- Implementation of LLMs -One example is the implementation of a summarization or extraction model, which enables ScrAIbe to automatically summarize or retrieve the key information out of a generated transcription, which could be the minutes of a meeting. - -- Executable for Windows - -## Contact - -For queries contact [Jacob Schmieder](Jacob.Schmieder@dbfz.de) - -## License - -ScrAIbe is licensed under [GNU General Public License](LICENSE). +ScrAIbe-WebUI is proudly open source and licensed under the GPL-3.0 license. This promotes a collaborative and transparent development process. For more details, see the [LICENSE](./LICENSE) file in this repository. ## Acknowledgments -Special thanks go to the KIDA project and the BMEL (Bundesministerium für Ernährung und Landwirtschaft), especially to the AI Consultancy Team. +Special thanks go to the [KIDA](https://www.kida-bmel.de/) project and the [BMEL (Bundesministerium für Ernährung und Landwirtschaft)](https://www.bmel.de/EN/Home/home_node.html), especially to the AI Consultancy Team. -![KIDA](./Pictures/kida_dark.png#gh-dark-mode-only)   ![BMEL](./Pictures/BMEL_dark.png#gh-dark-mode-only)      ![DBFZ](./Pictures/DBFZ_dark.png#gh-dark-mode-only)       ![MRI](./Pictures/MRI.png#gh-dark-mode-only) +--- -![KIDA](./Pictures/kida.png#gh-light-mode-only)   ![BMEL](./Pictures/BMEL.jpg#gh-light-mode-only)      ![DBFZ](./Pictures/DBFZ.png#gh-light-mode-only)       ![MRI](./Pictures/MRI.png#gh-light-mode-only) +Join us in making ScrAIbe even better! 🚀 \ No newline at end of file From a2e8aa227dbd10876a4d403cd93d4e4ccb60e738 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:02:35 +0000 Subject: [PATCH 317/331] Added Contributing.md --- CONTRIBUTING.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..504e847 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to ScrAIbe + +Thank you for your interest in contributing to ScrAIbe! We appreciate your efforts to improve the project. Before making any changes, please discuss them with the project maintainers via an issue, email, or any other method. + +Please note that we have a code of conduct, and we ask you to adhere to it in all your interactions with the project. + +## Pull Request Process + +1. **Dependency Management**: Ensure any install or build dependencies are removed before the end of the layer when doing a build. +2. **Documentation Updates**: Update the `README.md` with details of changes to the interface, including new environment variables, exposed ports, useful file locations, and container parameters. +3. **Versioning**: Increase the version numbers in any example files and the `README.md` to the new version that this Pull Request would represent. We use the [SemVer](http://semver.org/) versioning scheme. +4. **Review and Merge**: You may merge the Pull Request once you have the sign-off of two other developers. If you do not have permission to merge, request a second reviewer to merge it for you. + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]. + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ From d61b6650474c9abd8339ab4163bc896aedbce694 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:15:43 +0000 Subject: [PATCH 318/331] added Changelog.md --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9cf7545 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.2.0] - 2024-05-28 + +### Added + +- **Python Usage Section**: Detailed instructions on how to use ScrAIbe with Python, including examples for Whisper models, WhisperX, and keyword arguments. +- **Command-line Usage Section**: Enhanced instructions for using ScrAIbe via the command-line interface, including examples and key options. +- **Documentation Section**: Expanded the documentation section with highlights on installation guides, usage examples, API reference, troubleshooting tips, and advanced configuration. +- **Getting Started Section**: Added detailed prerequisites and installation instructions for both stable and development versions of ScrAIbe. +- **WhisperX Support**: Added support for the WhisperX backend. + +### Changed + +- **Model Customization**: Clarified the use of various keywords to customize Whisper models, Pyannote diarization models, and WhisperX. +- **Example Enhancements**: Improved examples to illustrate the usage of different features and options in ScrAIbe. +- **Formatting and Clarity**: Improved formatting and clarity across all sections to enhance readability and user experience. +- **Backend Robustness**: Enhanced the backend to be more robust, removing the need for a HuggingFace token for basic usage. +- **CLI**: to Work without Gradio + +### Removed + +- **Docker Build**: Removed Docker build support. +- **Gradio App**: Removed the Gradio App integration. + +Both the Docker Build and the Gradio App are now Available under [ScrAIbe-WebUI](https://github.com/JSchmie/ScrAIbe-WebUI) + +### Documentation + +- **Documentation Page Link**: Updated the documentation section with a direct link to the [ScrAIbe documentation page](https://jschmie.github.io/ScrAIbe/). + +**Note**: This changelog might be incomplete, but we promise to improve it in the future. Thank you for your understanding and support. From c8b9a7aa9339fd41b22d640c3fe8e89ecb4d79f1 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:18:33 +0000 Subject: [PATCH 319/331] removed gradio related stuff --- scraibe/cli.py | 110 ++++++++++++++++--------------------------------- 1 file changed, 35 insertions(+), 75 deletions(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index b6f2c17..9dfe395 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -37,22 +37,9 @@ def cli(): parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - group = parser.add_mutually_exclusive_group() - parser.add_argument("-f", "--audio-files", nargs="+", type=str, default=None, help="List of audio files to transcribe.") - group.add_argument('--start-server', action='store_true', - help='Start the Gradio app.' - 'If set, all other arguments are ignored' - 'besides --server-config or --server-kwargs.') - - parser.add_argument("--server-config", type=str, default=None, - help="Path to the configy.yml file.") - - parser.add_argument('--server-kwargs', nargs='*', action=ParseKwargs, default={}, - help='Keyword arguments for the Gradio app.') - parser.add_argument("--whisper-model-name", default="medium", help="Name of the Whisper model to use.") @@ -104,9 +91,6 @@ def cli(): out_format = arg_dict.pop("output_format") - # seup server arg: - start_server = arg_dict.pop("start_server") - task = arg_dict.pop("task") if args.num_threads > 0: @@ -118,76 +102,52 @@ def cli(): if arg_dict["whisper_model_directory"]: class_kwargs["download_root"] = arg_dict.pop("whisper_model_directory") + - if not start_server: + model = Scraibe(**class_kwargs) - model = Scraibe(**class_kwargs) + if arg_dict["audio_files"]: + audio_files = arg_dict.pop("audio_files") - if arg_dict["audio_files"]: - audio_files = arg_dict.pop("audio_files") + if task == "autotranscribe" or task == "autotranscribe+translate": + for audio in audio_files: + if task == "autotranscribe+translate": + task = "translate" + else: + task = "transcribe" - if task == "autotranscribe" or task == "autotranscribe+translate": - for audio in audio_files: - if task == "autotranscribe+translate": - task = "translate" - else: - task = "transcribe" + out = model.autotranscribe(audio, task=task, language=arg_dict.pop( + "language"), verbose=arg_dict.pop("verbose_output")) + basename = audio.split("/")[-1].split(".")[0] + print(f'Saving {basename}.{out_format} to {out_folder}') + out.save(os.path.join( + out_folder, f"{basename}.{out_format}")) - out = model.autotranscribe(audio, task=task, language=arg_dict.pop( - "language"), verbose=arg_dict.pop("verbose_output")) - basename = audio.split("/")[-1].split(".")[0] - print(f'Saving {basename}.{out_format} to {out_folder}') - out.save(os.path.join( - out_folder, f"{basename}.{out_format}")) + elif task == "diarization": + for audio in audio_files: + if arg_dict.pop("verbose_output"): + print("Verbose not implemented for diarization.") - elif task == "diarization": - for audio in audio_files: - if arg_dict.pop("verbose_output"): - print("Verbose not implemented for diarization.") + out = model.diarization(audio) + basename = audio.split("/")[-1].split(".")[0] + path = os.path.join(out_folder, f"{basename}.{out_format}") - out = model.diarization(audio) - basename = audio.split("/")[-1].split(".")[0] - path = os.path.join(out_folder, f"{basename}.{out_format}") + print(f'Saving {basename}.{out_format} to {out_folder}') - print(f'Saving {basename}.{out_format} to {out_folder}') + with open(path, "w") as f: + json.dump(json.dumps(out, indent=1), f) - with open(path, "w") as f: - json.dump(json.dumps(out, indent=1), f) + elif task == "transcribe" or task == "translate": - elif task == "transcribe" or task == "translate": + for audio in audio_files: - for audio in audio_files: - - out = model.transcribe(audio, task=task, - language=arg_dict.pop("language"), - verbose=arg_dict.pop("verbose_output")) - basename = audio.split("/")[-1].split(".")[0] - path = os.path.join(out_folder, f"{basename}.{out_format}") - with open(path, "w") as f: - f.write(out) - - else: # unfinished code - raise NotImplementedError("Currently not Working") - import subprocess - import sys - - execute_path = os.path.join( - os.path.dirname(__file__), "app/app_starter.py") - - config = arg_dict.pop("server_config") - server_kwargs = arg_dict.pop("server_kwargs") - - if not config: - subprocess.run([sys.executable, execute_path, - f"--server-kwargs={server_kwargs}"]) - elif not server_kwargs: - subprocess.run([sys.executable, execute_path, - f"--server-config={config}"]) - elif not config and not server_kwargs: - subprocess.run([sys.executable, execute_path]) - else: - subprocess.run([sys.executable, execute_path, - f"--server-config={config}", f"--server-kwargs={server_kwargs}"]) + out = model.transcribe(audio, task=task, + language=arg_dict.pop("language"), + verbose=arg_dict.pop("verbose_output")) + basename = audio.split("/")[-1].split(".")[0] + path = os.path.join(out_folder, f"{basename}.{out_format}") + with open(path, "w") as f: + f.write(out) if __name__ == "__main__": From 754d0e9b8474655538ff365d8355d70949ea6f2f Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:23:05 +0000 Subject: [PATCH 320/331] removed unused packages --- scraibe/cli.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index 9dfe395..c07f90f 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -5,17 +5,12 @@ The function includes arguments for specifying the audio files, model paths, output formats, and other options necessary for transcription. """ import os -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter import json - -from .autotranscript import Scraibe -from .misc import ParseKwargs - - +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter from whisper.tokenizer import LANGUAGES, TO_LANGUAGE_CODE from torch.cuda import is_available from torch import set_num_threads - +from .autotranscript import Scraibe def cli(): """ @@ -149,6 +144,5 @@ def cli(): with open(path, "w") as f: f.write(out) - if __name__ == "__main__": cli() From dee6907be54a8e97e76bda2aa880d9ceb139e8cf Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:23:40 +0000 Subject: [PATCH 321/331] removed comment --- scraibe/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index c07f90f..eece1bb 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -65,7 +65,7 @@ def cli(): parser.add_argument("--verbose-output", type=str2bool, default=True, help="Enable or disable progress and debug messages.") - parser.add_argument("--task", type=str, default='autotranscribe', # unifinished code + parser.add_argument("--task", type=str, default='autotranscribe', choices=["autotranscribe", "diarization", "autotranscribe+translate", "translate", 'transcribe'], help="Choose to perform transcription, diarization, or translation. \ From 5ec66effc2eba6939f0dd90e9cd2ab4d245358db Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Thu, 30 May 2024 14:50:06 +0000 Subject: [PATCH 322/331] added whisper type to cli --- scraibe/cli.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scraibe/cli.py b/scraibe/cli.py index eece1bb..ee40c8b 100644 --- a/scraibe/cli.py +++ b/scraibe/cli.py @@ -35,6 +35,10 @@ def cli(): parser.add_argument("-f", "--audio-files", nargs="+", type=str, default=None, help="List of audio files to transcribe.") + parser.add_argument("--whisper-type", type=str, default="whisper", + choices=["whisper", "whisperx"], + help="Type of Whisper model to use ('whisper' or 'whisperx').") + parser.add_argument("--whisper-model-name", default="medium", help="Name of the Whisper model to use.") @@ -92,8 +96,10 @@ def cli(): set_num_threads(arg_dict.pop("num_threads")) class_kwargs = {'whisper_model': arg_dict.pop("whisper_model_name"), + 'whisper_type':arg_dict.pop("whisper_type"), 'dia_model': arg_dict.pop("diarization_directory"), - 'use_auth_token': arg_dict.pop("hf_token")} + 'use_auth_token': arg_dict.pop("hf_token"), + } if arg_dict["whisper_model_directory"]: class_kwargs["download_root"] = arg_dict.pop("whisper_model_directory") From 0ae39c1167a7665d3faae8cf10074e69f5d62695 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 08:29:25 +0000 Subject: [PATCH 323/331] ruff runs just on push --- .github/workflows/ruff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 271e0b4..1e5c277 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -1,5 +1,5 @@ name: Ruff -on: [ push, pull_request ] +on: push jobs: ruff: runs-on: ubuntu-latest From 7451cc5346bbb31851134fa2cb404dcc4d432c8c Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 10:35:30 +0000 Subject: [PATCH 324/331] changed branch to develop --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b850f63..cadc678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ strict = true format-jinja = """ {%- if distance == 0 -%} {{ serialize_pep440(base) }} - {%- elif branch == 'pyproject.toml' -%} + {%- elif branch == 'develop' -%} {{ serialize_pep440(bump_version(base), dev = distance) }} {%- else -%} {{ serialize_pep440(bump_version(base), dev=distance, metadata=[commit]) }} From 1ef2db3442f80f21e7c4b52cd34cbbfba683fc20 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 10:38:23 +0000 Subject: [PATCH 325/331] added whisperx --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index cadc678..9dbf2c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ python = "^3.9" tqdm = "^4.66.4" numpy = "^1.26.4" openai-whisper = "^20231117" +whisperx = "^3.1.3" "pyannote.audio" = "^3.2.0" torch = "^2.3.0" From 740629b83ab136103d455c00eda2390e1314a949 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 10:40:11 +0000 Subject: [PATCH 326/331] make whisperx happy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9dbf2c0..6c9fa08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ tqdm = "^4.66.4" numpy = "^1.26.4" openai-whisper = "^20231117" whisperx = "^3.1.3" -"pyannote.audio" = "^3.2.0" +pyannote.audio = "^3.1.1" torch = "^2.3.0" [tool.poetry.group.dev.dependencies] From a9cf43cd2db1a1b9978667e6e8c0d8f916ece546 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 10:40:54 +0000 Subject: [PATCH 327/331] make pypi happy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6c9fa08..8c46bdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ tqdm = "^4.66.4" numpy = "^1.26.4" openai-whisper = "^20231117" whisperx = "^3.1.3" -pyannote.audio = "^3.1.1" +"pyannote.audio" = "^3.1.1" torch = "^2.3.0" [tool.poetry.group.dev.dependencies] From 72f57d5a7f733a70f85f203224893d796da979a7 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 12:30:10 +0000 Subject: [PATCH 328/331] added check for docs --- .github/workflows/check_semver.yaml | 17 ++++++++++++++++- .github/workflows/semver.yml | 16 +++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check_semver.yaml b/.github/workflows/check_semver.yaml index ccfd2b8..71de22c 100644 --- a/.github/workflows/check_semver.yaml +++ b/.github/workflows/check_semver.yaml @@ -15,7 +15,20 @@ jobs: with: fetch-depth: 0 + - name: Check if Source Branch is docs + id: check_docs_branch + run: | + pr_head_ref="${{ github.event.pull_request.head.ref }}" + if [[ "$pr_head_ref" == "docs" ]]; then + echo "This is a docs branch merge. Exiting without creating a tag." + echo "is_docs_branch=true" >> $GITHUB_ENV + exit 0 + else + echo "is_docs_branch=false" >> $GITHUB_ENV + fi + - name: Extract and Determine Version + if: env.is_docs_branch != 'true' id: extract_version run: | # Fetch the latest tags from the remote @@ -54,6 +67,7 @@ jobs: echo "Version determined: $clean_version" - name: Check if Version Already Exists in Tags + if: env.is_docs_branch != 'true' run: | version="${{ env.version }}" if git tag --list | grep -q "^$version$"; then @@ -64,6 +78,7 @@ jobs: fi - name: Check Version in CHANGELOG + if: env.is_docs_branch != 'true' id: check_version run: | version="${{ env.version }}" @@ -72,4 +87,4 @@ jobs: exit 1 else echo "Version $version found in CHANGELOG.md." - fi + fi \ No newline at end of file diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index a4f97d3..88565c3 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -16,7 +16,20 @@ jobs: with: fetch-depth: 0 + - name: Check if Source Branch is docs + id: check_docs_branch + run: | + pr_head_ref="${{ github.event.pull_request.head.ref }}" + if [[ "$pr_head_ref" == "docs" ]]; then + echo "is_docs_branch=true" >> $GITHUB_ENV + echo "This is a docs branch merge. Exiting without creating a tag." + exit 0 + else + echo "is_docs_branch=false" >> $GITHUB_ENV + fi + - name: Bump Version and Tag + if: env.is_docs_branch != 'true' id: bump_version env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} @@ -67,6 +80,7 @@ jobs: git push origin $new_tag - name: Extract Release Notes + if: env.is_docs_branch != 'true' id: extract_notes run: | version="${{ env.new_tag }}" @@ -85,6 +99,7 @@ jobs: echo "EOF" >> $GITHUB_ENV - name: Create Release + if: env.is_docs_branch != 'true' uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} @@ -94,4 +109,3 @@ jobs: body: ${{ env.RELEASE_NOTES }} draft: false prerelease: false - \ No newline at end of file From 571176589d93975913816d22f99e83ebfa6a19ef Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 12:48:56 +0000 Subject: [PATCH 329/331] changed when the ci should run --- .github/workflows/pypi.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index f50ce5b..967e9f9 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -2,10 +2,11 @@ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI on: push: - branches: - - develop tags: - v* # Push tags to trigger the workflow + pull_request: + branches: + - develop workflow_dispatch: inputs: test: From 9598073565a87d6a50532fb753f0b63592c4574a Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 13:15:35 +0000 Subject: [PATCH 330/331] updates types --- .github/workflows/pypi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 967e9f9..b4b2a06 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -5,6 +5,7 @@ on: tags: - v* # Push tags to trigger the workflow pull_request: + types: [closed] branches: - develop workflow_dispatch: From 44a14a7ce742ce2033b8be2c5cf2c35643158419 Mon Sep 17 00:00:00 2001 From: "Schmieder, Jacob" Date: Fri, 31 May 2024 13:42:05 +0000 Subject: [PATCH 331/331] fixed requirements --- requirements.txt | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/requirements.txt b/requirements.txt index f10eb8c..f08e2e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,22 +8,7 @@ pyannote.audio~=3.1.1 pyannote.core~=5.0.0 pyannote.database~=5.0.1 pyannote.metrics~=3.2.1 -pyannote.pipeline~=2.3 - -setuptools~=69.0.3 -setuptools-rust~=1.8.1 - -tqdm>=4.65.0 - -gradio~=3.36.1 -gradio-client~=0.2.7 - -# add pytorch to override the one installed by pyannote.audio - -torch~=1.11.0 -torchvision~=0.12.0 -torchaudio~=0.11.0 -#optional: -#sphinx~=5.0.2 +pyannote.pipeline~=3.0.1 +torch>=2.0.0