Spaces:
Runtime error
Runtime error
Commit ·
6934a38
1
Parent(s): 2a1a32a
feat: v0.0.1 functional
Browse files- .env.dist +8 -1
- .gitignore +4 -0
- Dockerfile +22 -26
- README.md +113 -240
- README.template.md +0 -62
- app/app.py +374 -0
- app/app.sh +5 -1
- app/config_templates/template.yml +0 -3
- app/gradio_app.py +135 -0
- odtp.yml +117 -79
- requirements.txt +9 -1
.env.dist
CHANGED
|
@@ -1,11 +1,18 @@
|
|
| 1 |
#ODTP COMPONENT VARIABLES
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
# ODTP ENV VARIABLES TO CONNECT
|
| 4 |
ODTP_MONGO_SERVER=
|
| 5 |
ODTP_S3_SERVER=
|
| 6 |
ODTP_BUCKET_NAME=
|
| 7 |
ODTP_ACCESS_KEY=
|
| 8 |
ODTP_SECRET_KEY=
|
|
|
|
| 9 |
# ODTP ENV VARIABLES DB REFERENCES
|
| 10 |
ODTP_USER_ID=
|
| 11 |
ODTP_DIGITAL_TWIN=
|
|
|
|
| 1 |
#ODTP COMPONENT VARIABLES
|
| 2 |
+
HF_TOKEN=
|
| 3 |
+
MODEL=
|
| 4 |
+
TASK=
|
| 5 |
+
LANGUAGE=
|
| 6 |
+
INPUT_FILE=
|
| 7 |
+
OUTPUT_FILE=
|
| 8 |
+
|
| 9 |
# ODTP ENV VARIABLES TO CONNECT
|
| 10 |
ODTP_MONGO_SERVER=
|
| 11 |
ODTP_S3_SERVER=
|
| 12 |
ODTP_BUCKET_NAME=
|
| 13 |
ODTP_ACCESS_KEY=
|
| 14 |
ODTP_SECRET_KEY=
|
| 15 |
+
|
| 16 |
# ODTP ENV VARIABLES DB REFERENCES
|
| 17 |
ODTP_USER_ID=
|
| 18 |
ODTP_DIGITAL_TWIN=
|
.gitignore
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Mac crap
|
| 2 |
.DS_Store
|
| 3 |
|
|
|
|
| 1 |
+
# ODTP dev
|
| 2 |
+
odtp-input
|
| 3 |
+
odtp-output
|
| 4 |
+
|
| 5 |
# Mac crap
|
| 6 |
.DS_Store
|
| 7 |
|
Dockerfile
CHANGED
|
@@ -1,31 +1,8 @@
|
|
| 1 |
-
FROM
|
| 2 |
|
| 3 |
-
RUN apt update
|
| 4 |
-
RUN apt install python3.10 python3-pip -y
|
| 5 |
|
| 6 |
-
|
| 7 |
-
# Ubuntu setup
|
| 8 |
-
##################################################
|
| 9 |
-
|
| 10 |
-
RUN apt-get update \
|
| 11 |
-
&& apt-get install -y wget \
|
| 12 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
-
|
| 14 |
-
RUN apt-get update && apt-get -y upgrade \
|
| 15 |
-
&& apt-get install -y --no-install-recommends \
|
| 16 |
-
unzip \
|
| 17 |
-
nano \
|
| 18 |
-
git \
|
| 19 |
-
g++ \
|
| 20 |
-
gcc \
|
| 21 |
-
htop \
|
| 22 |
-
zip \
|
| 23 |
-
ca-certificates \
|
| 24 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 25 |
-
|
| 26 |
-
##################################################
|
| 27 |
-
# ODTP setup
|
| 28 |
-
##################################################
|
| 29 |
|
| 30 |
COPY odtp-component-client/requirements.txt /tmp/odtp.requirements.txt
|
| 31 |
RUN pip install -r /tmp/odtp.requirements.txt
|
|
@@ -39,6 +16,17 @@ RUN pip install -r /tmp/odtp.requirements.txt
|
|
| 39 |
COPY requirements.txt /tmp/requirements.txt
|
| 40 |
RUN pip install -r /tmp/requirements.txt
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
######################################################################
|
| 44 |
# ODTP COMPONENT CONFIGURATION.
|
|
@@ -70,4 +58,12 @@ COPY ./odtp-component-client /odtp/odtp-component-client
|
|
| 70 |
COPY ./app /odtp/odtp-app
|
| 71 |
WORKDIR /odtp
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
ENTRYPOINT ["bash", "/odtp/odtp-component-client/startup.sh"]
|
|
|
|
| 1 |
+
FROM nvidia/cuda:12.1.0-devel-ubuntu22.04
|
| 2 |
|
| 3 |
+
RUN apt-get update && apt-get install -y apt-utils
|
|
|
|
| 4 |
|
| 5 |
+
RUN apt-get install -y python3.11 python3.11-venv python3-pip
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
COPY odtp-component-client/requirements.txt /tmp/odtp.requirements.txt
|
| 8 |
RUN pip install -r /tmp/odtp.requirements.txt
|
|
|
|
| 16 |
COPY requirements.txt /tmp/requirements.txt
|
| 17 |
RUN pip install -r /tmp/requirements.txt
|
| 18 |
|
| 19 |
+
# Dependencies
|
| 20 |
+
|
| 21 |
+
RUN apt-get update && \
|
| 22 |
+
apt-get install -y zip git && \
|
| 23 |
+
apt-get clean && \
|
| 24 |
+
rm -rf /var/lib/apt/lists/*
|
| 25 |
+
|
| 26 |
+
# ffmpeg
|
| 27 |
+
COPY --link --from=mwader/static-ffmpeg:6.1.1 /ffmpeg /usr/local/bin/
|
| 28 |
+
COPY --link --from=mwader/static-ffmpeg:6.1.1 /ffprobe /usr/local/bin/
|
| 29 |
+
|
| 30 |
|
| 31 |
######################################################################
|
| 32 |
# ODTP COMPONENT CONFIGURATION.
|
|
|
|
| 58 |
COPY ./app /odtp/odtp-app
|
| 59 |
WORKDIR /odtp
|
| 60 |
|
| 61 |
+
##################################################
|
| 62 |
+
# Fix for end of the line issue on Windows
|
| 63 |
+
##################################################
|
| 64 |
+
|
| 65 |
+
RUN sed -i 's/\r$//' /odtp/odtp-component-client/odtp-app.sh
|
| 66 |
+
RUN sed -i 's/\r$//' /odtp/odtp-component-client/startup.sh
|
| 67 |
+
RUN sed -i 's/\r$//' /odtp/odtp-app/app.sh
|
| 68 |
+
|
| 69 |
ENTRYPOINT ["bash", "/odtp/odtp-component-client/startup.sh"]
|
README.md
CHANGED
|
@@ -1,276 +1,149 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
## How to clone this repository?
|
| 8 |
|
| 9 |
> [!NOTE]
|
| 10 |
> This repository makes use of submodules. Therefore, when cloning it you need to include them.
|
| 11 |
>
|
| 12 |
-
> `git clone --recurse-submodules https://github.com/
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
##
|
| 30 |
|
| 31 |
-
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
|
| 37 |
-
|
| 38 |
-
-
|
| 39 |
-
|
| 40 |
-
- `/odtp/odtp-workdir`: This is the working directory where the tool repository should be placed and all the middle files such as cache folders.
|
| 41 |
-
- `/odtp/odtp-input`: Input folder that is be mounted as volume for the docker container.
|
| 42 |
-
- `/odtp/odtp-output`: Output folder that is mounted as volume for the docker container.
|
| 43 |
-
- `/odtp/odtp-logs`: Folder reserved for internal loggings.
|
| 44 |
-
- `/odtp/odtp-config`: Folder reserved for odtp configuration.
|
| 45 |
|
| 46 |
-
##
|
| 47 |
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
2. Testing it as a single component using `odtp`
|
| 52 |
-
3. Testing it in a `odtp` digital twin execution
|
| 53 |
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
-
##
|
| 57 |
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
| 61 |
|
| 62 |
-
```
|
| 63 |
-
-
|
| 64 |
-
- data-input
|
| 65 |
-
- data-output
|
| 66 |
```
|
| 67 |
|
| 68 |
-
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
```
|
| 77 |
|
| 78 |
-
|
| 79 |
|
| 80 |
-
|
| 81 |
-
docker build -t odtp-component .
|
| 82 |
-
```
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
```
|
| 87 |
-
docker run -it --rm \
|
| 88 |
-v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
|
| 89 |
-
-v {
|
| 90 |
-
--
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
This command will run the component. If you want debug some errors and execute the docker in an interactive manner, you can use the flag `--entrypoint bash` when running docker.
|
| 95 |
-
|
| 96 |
-
Also if your tool is interactive such as an Streamlit app, don't forget to map the ports by using `-p XXXX:XXXX`.
|
| 97 |
-
|
| 98 |
-
### Testing the component as part of odtp
|
| 99 |
-
|
| 100 |
-
To execute the command as part of `odtp` please refer to our `odtp` documentation:
|
| 101 |
-
|
| 102 |
-
https://odtp-org.github.io/odtp-manuals/
|
| 103 |
-
|
| 104 |
-
## `odtp.yml`
|
| 105 |
-
|
| 106 |
-
ODTP requires a set of metadata to work. These fields should be filled by the developers.
|
| 107 |
-
|
| 108 |
-
```yml
|
| 109 |
-
# This file should contain basic component information for your component.
|
| 110 |
-
component-name: Component Name
|
| 111 |
-
component-author: Component Author
|
| 112 |
-
component-version: Component Version
|
| 113 |
-
component-repository: Component Repository
|
| 114 |
-
component-license: Component License
|
| 115 |
-
component-type: ephemeral or interactive
|
| 116 |
-
component-description: Description
|
| 117 |
-
tags:
|
| 118 |
-
- tag1
|
| 119 |
-
- tag2
|
| 120 |
-
|
| 121 |
-
# Information about the tools
|
| 122 |
-
tools:
|
| 123 |
-
- tool-name: tool's name
|
| 124 |
-
tool-author: Tool's author
|
| 125 |
-
tool-version: Tool version
|
| 126 |
-
tool-repository: Tool's repository
|
| 127 |
-
tool-license: Tool's license
|
| 128 |
-
|
| 129 |
-
# If your tool require some secrets token to be passed as ENV to the component
|
| 130 |
-
# This won't be traced
|
| 131 |
-
secrets:
|
| 132 |
-
- name: Key of the argument
|
| 133 |
-
- description: Description of the secret
|
| 134 |
-
|
| 135 |
-
# If the tool requires some building arguments such as Matlab license
|
| 136 |
-
build-args:
|
| 137 |
-
- name: Key of the argument
|
| 138 |
-
- description: Descriptio of the building argument
|
| 139 |
-
- secret: Bool
|
| 140 |
-
|
| 141 |
-
# If applicable, ports exposed by the component
|
| 142 |
-
# Include Name, Description, and Port Value for each port
|
| 143 |
-
ports:
|
| 144 |
-
- name: PORT A
|
| 145 |
-
description: Description of Port A
|
| 146 |
-
port-value: XXXX
|
| 147 |
-
- name: PORT B
|
| 148 |
-
description: Description of Port B
|
| 149 |
-
port-value: YYYY
|
| 150 |
-
|
| 151 |
-
# If applicable, parameters exposed by the component
|
| 152 |
-
# Datatype can be str, int, float, or bool.
|
| 153 |
-
parameters:
|
| 154 |
-
- name: PARAMETER A
|
| 155 |
-
default-value: DEFAULT_VALUE_A
|
| 156 |
-
datatype: DATATYPE_A
|
| 157 |
-
description: Description of Parameter A
|
| 158 |
-
parameter-bounds: # Boundaries for int and float datatype
|
| 159 |
-
- 0 # Lower bound
|
| 160 |
-
- inf # Upper bound
|
| 161 |
-
options: null
|
| 162 |
-
allow-custom-value: false # If true the user can add a custom value out of parameter-bounds, or options
|
| 163 |
-
|
| 164 |
-
- name: PARAMETER B
|
| 165 |
-
default-value: DEFAULT_VALUE_B
|
| 166 |
-
datatype: DATATYPE_B
|
| 167 |
-
description: Description of Parameter B
|
| 168 |
-
parameter-bounds: null
|
| 169 |
-
options: # If your string parameter is limited to a few option, please list them here.
|
| 170 |
-
- OptionA
|
| 171 |
-
- OptionB
|
| 172 |
-
- OptionC
|
| 173 |
-
allow-custom-value: false # If true the user can add a custom value out of parameter-bounds, or options
|
| 174 |
-
|
| 175 |
-
# If applicable, data-input list required by the component
|
| 176 |
-
data-inputs:
|
| 177 |
-
- name: INPUT A
|
| 178 |
-
type: TYPE_A # Folder or filetype
|
| 179 |
-
path: VALUE_A
|
| 180 |
-
description: Description of Input A
|
| 181 |
-
- name: INPUT B
|
| 182 |
-
type: TYPE_B # Folder or filetype
|
| 183 |
-
path: VALUE_B
|
| 184 |
-
description: Description of Input B
|
| 185 |
-
|
| 186 |
-
# If applicable, data-output list produced by the component
|
| 187 |
-
data-output:
|
| 188 |
-
- name: OUTPUT A
|
| 189 |
-
type: TYPE_A # Folder or filetype
|
| 190 |
-
path: VALUE_A
|
| 191 |
-
description: Description of Output A
|
| 192 |
-
- name: OUTPUT B
|
| 193 |
-
type: TYPE_B # Folder or filetype
|
| 194 |
-
path: VALUE_B
|
| 195 |
-
description: Description of Output B
|
| 196 |
-
|
| 197 |
-
# If applicable, path to schemas to perform semantic validation.
|
| 198 |
-
# Still under development. Ignore.
|
| 199 |
-
schema-input: PATH_TO_INPUT_SCHEMA
|
| 200 |
-
schema-output: PATH_TO_OUTPUT_SCHEMA
|
| 201 |
-
|
| 202 |
-
# If applicable, define devices needed such as GPU.
|
| 203 |
-
devices:
|
| 204 |
-
gpu: Bool
|
| 205 |
```
|
| 206 |
|
| 207 |
-
##
|
| 208 |
-
|
| 209 |
-
- v0.4.0
|
| 210 |
-
- Update default Base and Python in Dockerfile to `ubuntu:22.04` and `python3.10`
|
| 211 |
|
| 212 |
-
|
| 213 |
-
- Inclusion of `secrets` and `build-args` in `odtp.yml`
|
| 214 |
-
- Tools as list
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
- Updating schema fields in `odtp.yml` to kebab-case.
|
| 225 |
-
|
| 226 |
-
- v0.3.0
|
| 227 |
-
- Turning `odtp-client` into a separate repository and adding it as a submodule in `odtp-component-client`
|
| 228 |
-
- Updating `app.sh` and tutorial.
|
| 229 |
-
- Updating `odtp.yml` file.
|
| 230 |
-
- Adding `.DS_Store` to `.gitignore`
|
| 231 |
-
|
| 232 |
-
- v0.2.0
|
| 233 |
-
- Compatible with ODTP v.0.2.0 only with platform / components
|
| 234 |
-
- Compatible with configuration text files
|
| 235 |
-
- Improved loging system
|
| 236 |
-
- Accepting Digital Twins, Executions, and steps, metadata.
|
| 237 |
-
- Including component versioning in `odtp.yml`
|
| 238 |
-
|
| 239 |
-
- v0.1.0
|
| 240 |
-
- Compatible with ODTP v.0.1.0 only with platform / components
|
| 241 |
-
- Compatible with configuration text files
|
| 242 |
-
|
| 243 |
-
## Acknowledgments, Copyright, and Licensing
|
| 244 |
-
|
| 245 |
-
### Acknowledgments and Funding
|
| 246 |
-
|
| 247 |
-
This work is part of the broader project **O**pen **D**igital **T**win **P**latform of the **S**wiss **M**obility **S**ystem (ODTP-SMS) funded by Swissuniversities CHORD grant Track B - Establish Projects. ODTP-SMS project is a joint endeavour by the Center for Sustainable Future Mobility - CSFM (ETH Zürich) and the Swiss Data Science Center - SDSC (EPFL and ETH Zürich).
|
| 248 |
-
The Swiss Data Science Center (SDSC) develops domain-agnostic standards and containerized components to manage digital twins. This includes the creation of the Core Platform (both back-end and front-end), Service Component Integration Templates, Component Ontology, and the Component Zoo template.
|
| 249 |
-
The Center for Sustainable Future Mobility (CSFM) develops mobility services and utilizes the components produced by SDSC to deploy a mobility digital twin platform. CSFM focuses on integrating mobility services and collecting available components in the mobility zoo, thereby applying the digital twin concept in the realm of mobility.
|
| 250 |
-
|
| 251 |
-
### Copyright
|
| 252 |
-
|
| 253 |
-
Copyright © 2023-2024 Swiss Data Science Center (SDSC), www.datascience.ch. All rights reserved.
|
| 254 |
-
The SDSC is jointly established and legally represented by the École Polytechnique Fédérale de Lausanne (EPFL) and the Eidgenössische Technische Hochschule Zürich (ETH Zürich). This copyright encompasses all materials, software, documentation, and other content created and developed by the SDSC.
|
| 255 |
-
|
| 256 |
-
### Intellectual Property (IP) Rights
|
| 257 |
-
|
| 258 |
-
The Open Digital Twin Platform (ODTP) is the result of a collaborative effort between ETH Zurich (ETHZ) and the École Polytechnique Fédérale de Lausanne (EPFL). Both institutions hold equal intellectual property rights for the ODTP project, reflecting the equitable and shared contributions of EPFL and ETH Zürich in the development and advancement of this initiative.
|
| 259 |
-
|
| 260 |
-
### Licensing
|
| 261 |
-
|
| 262 |
-
The Service Component Integration Templates within this repository are licensed under the BSD 3-Clause "New" or "Revised" License. This license allows for broad compatibility and standardization, encouraging open use and contribution. For the full license text, please see the LICENSE file accompanying these templates.
|
| 263 |
|
| 264 |
-
###
|
| 265 |
|
| 266 |
-
|
| 267 |
-
- **Ontology**: Creative Commons Attribution-ShareAlike (CC BY-SA).
|
| 268 |
-
- **Component Zoo Template**: BSD-3 license.
|
| 269 |
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
-
|
| 273 |
|
| 274 |
-
|
| 275 |
|
| 276 |
-
|
|
|
|
| 1 |
+
# Name of the component
|
| 2 |
|
| 3 |
+
Add here your badges:
|
| 4 |
+
[](http://localhost:8501/launch-component)
|
| 5 |
+
[]("")
|
|
|
|
|
|
|
| 6 |
|
| 7 |
> [!NOTE]
|
| 8 |
> This repository makes use of submodules. Therefore, when cloning it you need to include them.
|
| 9 |
>
|
| 10 |
+
> `git clone --recurse-submodules https://github.com/sdsc-ordes/odtp-pyannote-whisper`
|
| 11 |
+
|
| 12 |
+
This pipeline processes a `.wav` audio file by detecting the number of speakers present in the recording using `pyannote.audio`. For each detected speaker segment, it employs `OpenAI's Whisper model` to transcribe or translate the speech individually. This approach ensures accurate and speaker-specific transcriptions or translations, providing a clear understanding of who said what throughout the audio.
|
| 13 |
+
|
| 14 |
+
Note: This application utilizes `pyannote.audio` and OpenAI's Whisper model. You must accept the terms of use on Hugging Face for the `pyannote/segmentation` and `pyannote/speaker-diarization` models before using this application.
|
| 15 |
+
|
| 16 |
+
## Table of Contents
|
| 17 |
+
|
| 18 |
+
- [Tools Information](#tools-information)
|
| 19 |
+
- [How to add this component to your ODTP instance](#how-to-add-this-component-to-your-odtp-instance)
|
| 20 |
+
- [Data sheet](#data-sheet)
|
| 21 |
+
- [Parameters](#parameters)
|
| 22 |
+
- [Secrets](#secrets)
|
| 23 |
+
- [Input Files](#input-files)
|
| 24 |
+
- [Output Files](#output-files)
|
| 25 |
+
- [Tutorial](#tutorial)
|
| 26 |
+
- [How to run this component as docker](#how-to-run-this-component-as-docker)
|
| 27 |
+
- [Development Mode](#development-mode)
|
| 28 |
+
- [Running with GPU](#running-with-gpu)
|
| 29 |
+
- [Running in API Mode](#running-in-api-mode)
|
| 30 |
+
- [Credits and References](#credits-and-references)
|
| 31 |
+
|
| 32 |
+
## Tools Information
|
| 33 |
+
|
| 34 |
+
| Tool | Semantic Versioning | Commit | Documentation |
|
| 35 |
+
| --- | --- | --- | --- |
|
| 36 |
+
| Tool | Version | Commit Hash | Documentation |
|
| 37 |
+
|-------------------------------------------------|------------|-------------|--------------------------------------------------------------------|
|
| 38 |
+
| [OpenAI Whisper](https://github.com/openai/whisper) | Latest | [Commit History](https://github.com/openai/whisper/commits/main) | [Whisper Documentation](https://github.com/openai/whisper#readme) |
|
| 39 |
+
| [pyannote.audio](https://github.com/pyannote/pyannote-audio) | Latest | [Commit History](https://github.com/pyannote/pyannote-audio/commits/master) | [pyannote.audio Documentation](https://pyannote.github.io/pyannote-audio/) |
|
| 40 |
+
|
| 41 |
+
## How to add this component to your ODTP instance
|
| 42 |
+
|
| 43 |
+
In order to add this component to your ODTP CLI, you can use. If you want to use the component directly, please refer to the docker section.
|
| 44 |
+
|
| 45 |
+
``` bash
|
| 46 |
+
odtp new odtp-component-entry \
|
| 47 |
+
--name odtp-pyannote-whisper \
|
| 48 |
+
--component-version v0.0.1 \
|
| 49 |
+
--repository https://github.com/sdsc-ordes/odtp-pyannote-whisper
|
| 50 |
+
```
|
| 51 |
|
| 52 |
+
## Data sheet
|
| 53 |
|
| 54 |
+
### Parameters
|
| 55 |
|
| 56 |
+
| Parameter | Description | Type | Required | Default Value | Possible Values | Constraints |
|
| 57 |
+
|--------------|--------------------------------------------------------|--------|----------|---------------|-------------------------------------------------------------------|------------------------------------|
|
| 58 |
+
| `MODEL` | Whisper model to use for transcription or translation | String | Yes | `large-v3` | `tiny`, `base`, `small`, `medium`, `large`, `large-v2`, `large-v3` | Must be a valid Whisper model name |
|
| 59 |
+
| `TASK` | Task to perform on the audio input | String | Yes | `transcribe` | `transcribe`, `translate` | Must be `transcribe` or `translate` |
|
| 60 |
+
| `LANGUAGE` | Source language code for the audio input | String | No | `auto` | `auto`, `en`, `es`, `fr`, `de`, `it`, `pt`, `nl`, `ja`, `zh`, `ru` | Must be a supported language code |
|
| 61 |
+
| `INPUT_FILE` | Path to the input `.wav` audio file | String | Yes | N/A | Any valid file path to a `.wav` file | File must exist and be accessible |
|
| 62 |
+
| `OUTPUT_FILE`| Base name for the output files (without extension) | String | Yes | `output` | Any valid file name | Should not contain invalid characters |
|
| 63 |
|
| 64 |
+
### Secrets
|
| 65 |
|
| 66 |
+
| Secret Name | Description | Type | Required | Default Value | Constraints | Notes |
|
| 67 |
+
|-------------|-----------------------------------------|--------|----------|---------------|----------------|---------------------------------------------------------------|
|
| 68 |
+
| HF_TOKEN | Hugging Face API token for model access | String | Yes | None | Valid API Token | Obtain from your Hugging Face account settings |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
### Input Files
|
| 71 |
|
| 72 |
+
| File/Folder | Description | File Type | Required | Format | Notes |
|
| 73 |
+
|-----------------|-----------------------------------|-----------|----------|-------------|--------------------------------------------------|
|
| 74 |
+
| `INPUT_FILE` | Input audio file for processing | `.wav` | Yes | WAV format | Path specified by `INPUT_FILE` parameter |
|
| 75 |
|
| 76 |
+
### Output Files
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
| File/Folder | Description | File Type | Contents | Usage |
|
| 79 |
+
|----------------------|--------------------------------------|-----------|------------------------------|--------------------------------------------------|
|
| 80 |
+
| `OUTPUT_FILE.srt` | Transcribed subtitles in SRT format | `.srt` | Transcribed text with timings | Use with video players to display subtitles |
|
| 81 |
+
| `OUTPUT_FILE.json` | Transcription data in JSON format | `.json` | Detailed transcription data | For programmatic access and data analysis |
|
| 82 |
|
| 83 |
+
## Tutorial
|
| 84 |
|
| 85 |
+
### How to run this component as docker
|
| 86 |
|
| 87 |
+
Build the dockerfile.
|
| 88 |
|
| 89 |
+
``` bash
|
| 90 |
+
docker build -t odtp-pyannote-whisper .
|
|
|
|
|
|
|
| 91 |
```
|
| 92 |
|
| 93 |
+
Run the following command. Mount the correct volumes for input/output/logs folders.
|
| 94 |
|
| 95 |
+
``` bash
|
| 96 |
+
docker run -it --rm \
|
| 97 |
+
-v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
|
| 98 |
+
-v {PATH_TO_YOUR_OUTPUT_VOLUME}:/odtp/odtp-output \
|
| 99 |
+
-v {PATH_TO_YOUR_LOGS_VOLUME}:/odtp/odtp-logs \
|
| 100 |
+
--env-file .env odtp-pyannote-whisper
|
| 101 |
```
|
| 102 |
|
| 103 |
+
### Development Mode
|
| 104 |
|
| 105 |
+
To run the component in development mode, mount the app folder inside the container:
|
|
|
|
|
|
|
| 106 |
|
| 107 |
+
``` bash
|
| 108 |
+
docker run -it --rm \
|
|
|
|
|
|
|
| 109 |
-v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
|
| 110 |
+
-v {PATH_TO_YOUR_OUTPUT_VOLUME}:/odtp/odtp-output \
|
| 111 |
+
-v {PATH_TO_YOUR_LOGS_VOLUME}:/odtp/odtp-logs \
|
| 112 |
+
-v {PATH_TO_YOUR_APP_FOLDER}:/odtp/app \
|
| 113 |
+
--env-file .env odtp-pyannote-whisper
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
```
|
| 115 |
|
| 116 |
+
### Running with GPU
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
+
To run the component with GPU support, use the following command:
|
|
|
|
|
|
|
| 119 |
|
| 120 |
+
``` bash
|
| 121 |
+
docker run -it --rm \
|
| 122 |
+
--gpus all \
|
| 123 |
+
-v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
|
| 124 |
+
-v {PATH_TO_YOUR_OUTPUT_VOLUME}:/odtp/odtp-output \
|
| 125 |
+
-v {PATH_TO_YOUR_LOGS_VOLUME}:/odtp/odtp-logs \
|
| 126 |
+
--env-file .env odtp-pyannote-whisper
|
| 127 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
### Running in API Mode
|
| 130 |
|
| 131 |
+
To run the component in API mode and expose a port, use the following command:
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
``` bash
|
| 134 |
+
docker run -it --rm \
|
| 135 |
+
-v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
|
| 136 |
+
-v {PATH_TO_YOUR_OUTPUT_VOLUME}:/odtp/odtp-output \
|
| 137 |
+
-v {PATH_TO_YOUR_LOGS_VOLUME}:/odtp/odtp-logs \
|
| 138 |
+
-p {HOST_PORT}:7860 \
|
| 139 |
+
--env-file .env \
|
| 140 |
+
--entrypoing python3 \
|
| 141 |
+
odtp-pyannote-whisper \
|
| 142 |
+
/odtp/odtp-app/gradio_app.py
|
| 143 |
+
```
|
| 144 |
|
| 145 |
+
## Credits and references
|
| 146 |
|
| 147 |
+
SDSC
|
| 148 |
|
| 149 |
+
This component has been created using the `odtp-component-template` `v0.5.0`.
|
README.template.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
| 1 |
-
# Name of the component
|
| 2 |
-
|
| 3 |
-
Description of the component
|
| 4 |
-
|
| 5 |
-
| Tool Info | Links |
|
| 6 |
-
| --- | --- |
|
| 7 |
-
| Original Tool | []() |
|
| 8 |
-
| Current Tool Version | [commit-hash](link-to-commit-hash) |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
## ODTP command
|
| 12 |
-
|
| 13 |
-
```
|
| 14 |
-
odtp new odtp-component-entry \
|
| 15 |
-
--name odtp-component \
|
| 16 |
-
--component-version x.y.z \
|
| 17 |
-
--repository Link to repository
|
| 18 |
-
```
|
| 19 |
-
|
| 20 |
-
## Data sheet
|
| 21 |
-
|
| 22 |
-
### Parameters
|
| 23 |
-
|
| 24 |
-
| Parameter | Description | Default Value |
|
| 25 |
-
| --- | --- | --- |
|
| 26 |
-
| A | B | C |
|
| 27 |
-
|
| 28 |
-
### Input Files
|
| 29 |
-
|
| 30 |
-
| File/Folder | Description |
|
| 31 |
-
| --- | --- |
|
| 32 |
-
| A | B |
|
| 33 |
-
|
| 34 |
-
### Output Files
|
| 35 |
-
|
| 36 |
-
| File/Folder | Description |
|
| 37 |
-
| --- | --- |
|
| 38 |
-
| A | B |
|
| 39 |
-
|
| 40 |
-
## Tutorial
|
| 41 |
-
|
| 42 |
-
### How to run this component as docker
|
| 43 |
-
|
| 44 |
-
Build the dockerfile
|
| 45 |
-
|
| 46 |
-
```
|
| 47 |
-
docker build -t odtp-component .
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
Run the following command. Mount the correct volumes for input/output folders.
|
| 51 |
-
|
| 52 |
-
```
|
| 53 |
-
docker run -it --rm \
|
| 54 |
-
-v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
|
| 55 |
-
-v {PATH_TO_YOUR_OUTPUT_VOLUME}:/odtp/odtp-output \
|
| 56 |
-
--env-file .env odtp-component
|
| 57 |
-
```
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
## Developed by
|
| 61 |
-
|
| 62 |
-
XXXXXXXXXX
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/app.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import argparse
|
| 3 |
+
from typing import Any, Optional, TextIO, List
|
| 4 |
+
from pyannote.audio import Pipeline, Audio
|
| 5 |
+
import whisper
|
| 6 |
+
from whisper.utils import WriteSRT, WriteVTT
|
| 7 |
+
from whisper import Whisper
|
| 8 |
+
import torch
|
| 9 |
+
from math import ceil, floor
|
| 10 |
+
import soundfile as sf
|
| 11 |
+
import librosa
|
| 12 |
+
import json
|
| 13 |
+
from dataclasses import dataclass, asdict
|
| 14 |
+
from typing import List
|
| 15 |
+
from jsonschema import validate, ValidationError
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class Segment:
|
| 20 |
+
start: float
|
| 21 |
+
end: float
|
| 22 |
+
text: str
|
| 23 |
+
speaker: str
|
| 24 |
+
language: str
|
| 25 |
+
|
| 26 |
+
def generate_segments(transcription_data, speaker, language) -> List[Segment]:
|
| 27 |
+
segments = []
|
| 28 |
+
for item in transcription_data:
|
| 29 |
+
segment = Segment(
|
| 30 |
+
start=item['start'],
|
| 31 |
+
end=item['end'],
|
| 32 |
+
text=item['text'],
|
| 33 |
+
speaker=speaker,
|
| 34 |
+
language=language
|
| 35 |
+
)
|
| 36 |
+
segments.append(segment)
|
| 37 |
+
return segments
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
schema = {
|
| 41 |
+
"type": "object",
|
| 42 |
+
"properties": {
|
| 43 |
+
"segments": {
|
| 44 |
+
"type": "array",
|
| 45 |
+
"items": {
|
| 46 |
+
"type": "object",
|
| 47 |
+
"properties": {
|
| 48 |
+
"start": {"type": "number"},
|
| 49 |
+
"end": {"type": "number"},
|
| 50 |
+
"text": {"type": "string"},
|
| 51 |
+
"speaker": {"type": "string"},
|
| 52 |
+
"language": {"type": "string"}
|
| 53 |
+
},
|
| 54 |
+
"required": ["start", "end", "text", "speaker", "language"]
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"required": ["segments"]
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
def validate_json(json_data: str) -> bool:
|
| 62 |
+
try:
|
| 63 |
+
data = json.loads(json_data)
|
| 64 |
+
validate(instance=data, schema=schema)
|
| 65 |
+
return True
|
| 66 |
+
except ValidationError as e:
|
| 67 |
+
print(f"Validation error: {e}")
|
| 68 |
+
return False
|
| 69 |
+
|
| 70 |
+
def diarize_audio(HF_AUTH_TOKEN, AUDIO_FILE):
|
| 71 |
+
pipeline = Pipeline.from_pretrained(
|
| 72 |
+
"pyannote/speaker-diarization-3.1",
|
| 73 |
+
use_auth_token=HF_AUTH_TOKEN)
|
| 74 |
+
# Send pyannote pipeline to GPU (when available)
|
| 75 |
+
device: str = ""
|
| 76 |
+
if torch.cuda.is_available():
|
| 77 |
+
device = "cuda"
|
| 78 |
+
else:
|
| 79 |
+
device = "cpu"
|
| 80 |
+
pipeline.to(torch.device(device))
|
| 81 |
+
print(f"Diarize audio on {device}")
|
| 82 |
+
io = Audio(mono='downmix', sample_rate=16000)
|
| 83 |
+
waveform, sample_rate = io(AUDIO_FILE)
|
| 84 |
+
diarization = pipeline({"waveform": waveform, "sample_rate": sample_rate})
|
| 85 |
+
return diarization, waveform, sample_rate
|
| 86 |
+
|
| 87 |
+
class AppendResultsMixin:
|
| 88 |
+
"""Class to return srt or vtt file path and open mode of write or append
|
| 89 |
+
to allow incremental writing.
|
| 90 |
+
"""
|
| 91 |
+
first_call: bool = True
|
| 92 |
+
output_path: str = ''
|
| 93 |
+
|
| 94 |
+
def get_path_and_open_mode(self, *, audio_path: str, dir: str, ext: str) -> tuple[str, str]:
|
| 95 |
+
mode: str
|
| 96 |
+
if self.first_call:
|
| 97 |
+
audio_basename = os.path.basename(audio_path)
|
| 98 |
+
audio_basename = os.path.splitext(audio_basename)[0]
|
| 99 |
+
self.output_path: str = os.path.join(dir, audio_basename + "." + ext)
|
| 100 |
+
self.first_call = False
|
| 101 |
+
mode = 'w' # open for write initially
|
| 102 |
+
else:
|
| 103 |
+
mode = 'a' # open for append after
|
| 104 |
+
return self.output_path, mode
|
| 105 |
+
|
| 106 |
+
class WriteSRTIncremental(AppendResultsMixin, WriteSRT):
|
| 107 |
+
"""Incrementally create an SRT file with multiple calls appending new entries
|
| 108 |
+
to the file.
|
| 109 |
+
"""
|
| 110 |
+
srt_index: int = 1 # Index for SRT blocks retained across multiple calls
|
| 111 |
+
|
| 112 |
+
def __init__(self, output_dir: Optional[str] = None):
|
| 113 |
+
super().__init__(output_dir=output_dir)
|
| 114 |
+
self.extension = '.srt'
|
| 115 |
+
self.srt_index = 1 # Move to instance variable
|
| 116 |
+
|
| 117 |
+
def __call__(
|
| 118 |
+
self,
|
| 119 |
+
result: dict,
|
| 120 |
+
audio_path: str,
|
| 121 |
+
speaker: str,
|
| 122 |
+
start_base: float,
|
| 123 |
+
options: Optional[dict] = None,
|
| 124 |
+
output_path: Optional[str] = None,
|
| 125 |
+
**kwargs,
|
| 126 |
+
):
|
| 127 |
+
if output_path:
|
| 128 |
+
path = output_path
|
| 129 |
+
mode = 'a' if os.path.exists(path) else 'w'
|
| 130 |
+
else:
|
| 131 |
+
audio_dir = os.path.dirname(audio_path)
|
| 132 |
+
output_dir = self.output_dir if self.output_dir else audio_dir
|
| 133 |
+
audio_basename = os.path.splitext(os.path.basename(audio_path))[0]
|
| 134 |
+
path = os.path.join(output_dir, f"{audio_basename}{self.extension}")
|
| 135 |
+
mode = 'a' if os.path.exists(path) else 'w'
|
| 136 |
+
|
| 137 |
+
with open(path, mode, encoding="utf-8") as f:
|
| 138 |
+
self.write_result(result, f, speaker, start_base, options=options, **kwargs)
|
| 139 |
+
|
| 140 |
+
def write_result(
|
| 141 |
+
self,
|
| 142 |
+
result: dict,
|
| 143 |
+
file: TextIO,
|
| 144 |
+
speaker: str,
|
| 145 |
+
start_base: float,
|
| 146 |
+
options: Optional[dict] = None,
|
| 147 |
+
**kwargs,
|
| 148 |
+
):
|
| 149 |
+
for segment in result['segments']:
|
| 150 |
+
start = self.format_timestamp(segment['start'])
|
| 151 |
+
end = self.format_timestamp(segment['end'])
|
| 152 |
+
text = f"[{speaker}]: {segment['text']}"
|
| 153 |
+
print(f"{self.srt_index}\n{start} --> {end}\n{text}\n", file=file, flush=True)
|
| 154 |
+
self.srt_index += 1
|
| 155 |
+
|
| 156 |
+
class WriteVTTIncremental(AppendResultsMixin, WriteVTT):
|
| 157 |
+
"""Incrementally create a VTT file with multiple calls appending new entries
|
| 158 |
+
to the file.
|
| 159 |
+
"""
|
| 160 |
+
def __call__(
|
| 161 |
+
self,
|
| 162 |
+
result: dict,
|
| 163 |
+
audio_path: str,
|
| 164 |
+
speaker: str,
|
| 165 |
+
start: float,
|
| 166 |
+
options: Optional[dict] = None,
|
| 167 |
+
output_path: Optional[str] = None,
|
| 168 |
+
**kwargs,
|
| 169 |
+
):
|
| 170 |
+
if output_path:
|
| 171 |
+
path = output_path
|
| 172 |
+
mode = 'a' if os.path.exists(path) else 'w'
|
| 173 |
+
else:
|
| 174 |
+
path, mode = self.get_path_and_open_mode(
|
| 175 |
+
audio_path=audio_path,
|
| 176 |
+
dir=".",
|
| 177 |
+
ext="vtt"
|
| 178 |
+
)
|
| 179 |
+
with open(path, mode, encoding="utf-8") as f:
|
| 180 |
+
self.write_result(result, file=f, options=options, **kwargs)
|
| 181 |
+
|
| 182 |
+
def write_result(
|
| 183 |
+
self,
|
| 184 |
+
result: dict,
|
| 185 |
+
file: TextIO,
|
| 186 |
+
options: Optional[dict] = None,
|
| 187 |
+
**kwargs,
|
| 188 |
+
):
|
| 189 |
+
if file.tell() == 0:
|
| 190 |
+
print("WEBVTT\n", file=file)
|
| 191 |
+
|
| 192 |
+
for segment in result['segments']:
|
| 193 |
+
start = self.format_timestamp(segment['start'])
|
| 194 |
+
end = self.format_timestamp(segment['end'])
|
| 195 |
+
text = f"[{segment.get('speaker', 'unknown')}]: {segment['text']}"
|
| 196 |
+
print(f"{start} --> {end}\n{text}\n", file=file, flush=True)
|
| 197 |
+
|
| 198 |
+
class SegmentsJSONWriter(AppendResultsMixin):
|
| 199 |
+
"""Incrementally write segments to a JSON file."""
|
| 200 |
+
def __init__(self, output_dir: Optional[str] = None):
|
| 201 |
+
self.output_dir = output_dir # Now optional
|
| 202 |
+
self.first_call = True
|
| 203 |
+
self.output_path = '' # Will store the output file path
|
| 204 |
+
|
| 205 |
+
def __call__(
|
| 206 |
+
self,
|
| 207 |
+
segments: List[Segment],
|
| 208 |
+
audio_path: str,
|
| 209 |
+
output_path: Optional[str] = None,
|
| 210 |
+
):
|
| 211 |
+
if output_path:
|
| 212 |
+
# Use the provided output path directly
|
| 213 |
+
path = output_path
|
| 214 |
+
mode = 'a' if os.path.exists(path) else 'w'
|
| 215 |
+
self.output_path = path
|
| 216 |
+
else:
|
| 217 |
+
if not self.output_path:
|
| 218 |
+
audio_basename = os.path.splitext(os.path.basename(audio_path))[0]
|
| 219 |
+
# Use output_dir if provided, else use the directory of audio_path
|
| 220 |
+
dir = self.output_dir if self.output_dir else os.path.dirname(audio_path)
|
| 221 |
+
self.output_path = os.path.join(dir, audio_basename + ".json")
|
| 222 |
+
path = self.output_path
|
| 223 |
+
mode = 'a' if not self.first_call else 'w'
|
| 224 |
+
|
| 225 |
+
with open(path, mode, encoding='utf-8') as f:
|
| 226 |
+
if self.first_call:
|
| 227 |
+
# Start the JSON structure
|
| 228 |
+
f.write('{"segments": [\n')
|
| 229 |
+
else:
|
| 230 |
+
f.write(',\n')
|
| 231 |
+
for idx, segment in enumerate(segments):
|
| 232 |
+
if idx > 0:
|
| 233 |
+
f.write(',\n')
|
| 234 |
+
json.dump(asdict(segment), f, ensure_ascii=False, indent=2)
|
| 235 |
+
self.first_call = False
|
| 236 |
+
|
| 237 |
+
def finalize(self):
|
| 238 |
+
"""Call this method after all segments have been written to close the JSON array."""
|
| 239 |
+
if self.output_path:
|
| 240 |
+
with open(self.output_path, 'a', encoding='utf-8') as f:
|
| 241 |
+
f.write('\n ]\n}\n')
|
| 242 |
+
|
| 243 |
+
def close(self):
|
| 244 |
+
with open(self.output_path, 'a', encoding='utf-8') as f:
|
| 245 |
+
f.write('\n]}\n')
|
| 246 |
+
|
| 247 |
+
class WhisperFacade:
|
| 248 |
+
wmodel: Whisper
|
| 249 |
+
|
| 250 |
+
def __init__(self, model:str, *, quantize=False) -> None:
|
| 251 |
+
"""Load the Whisper model and optionally quantize."""
|
| 252 |
+
print("Initialize whisper")
|
| 253 |
+
whisper_model = whisper.load_model(model)
|
| 254 |
+
if quantize:
|
| 255 |
+
print("Quantize")
|
| 256 |
+
DTYPE = torch.qint8
|
| 257 |
+
qmodel: Whisper = torch.quantization.quantize_dynamic(
|
| 258 |
+
whisper_model, {torch.nn.Linear}, dtype=DTYPE)
|
| 259 |
+
del whisper_model
|
| 260 |
+
self.wmodel = qmodel
|
| 261 |
+
else:
|
| 262 |
+
self.wmodel = whisper_model
|
| 263 |
+
|
| 264 |
+
def _set_timing_for(self, segment: dict[str, float], # simplified typing
|
| 265 |
+
offset: float) -> None:
|
| 266 |
+
"""For speech fragments in different parts of an audio file, patch the
|
| 267 |
+
whisper segment and word timing using the offset (typically the diarization offset)
|
| 268 |
+
in seconds. This makes the timing accurate for subtitles when multiple
|
| 269 |
+
calls to whisper are used for various parts of the audio.
|
| 270 |
+
"""
|
| 271 |
+
s = segment
|
| 272 |
+
s['start'] += offset
|
| 273 |
+
s['end'] += offset
|
| 274 |
+
# Update word start/stop times, if present
|
| 275 |
+
if 'words' in s:
|
| 276 |
+
w: dict[str, float] # simplified typing
|
| 277 |
+
for w in s['words']: # type: ignore
|
| 278 |
+
w['start'] += offset
|
| 279 |
+
w['end'] += offset
|
| 280 |
+
|
| 281 |
+
def load_audio(self, file_path: str):
|
| 282 |
+
self.audio = whisper.load_audio(file_path)
|
| 283 |
+
|
| 284 |
+
def transcribe(self, *, start: float, end: float, options: dict[str, Any] ) -> dict[str, Any]:
|
| 285 |
+
"""Transcribe from start time to end time (both in seconds)."""
|
| 286 |
+
SAMPLE_RATE = 16_000 # 16kHz audio
|
| 287 |
+
start_index = floor(start * SAMPLE_RATE)
|
| 288 |
+
end_index = ceil(end * SAMPLE_RATE)
|
| 289 |
+
audio_segment = self.audio[start_index:end_index]
|
| 290 |
+
result = whisper.transcribe(self.wmodel, audio_segment, **options)
|
| 291 |
+
#
|
| 292 |
+
segments = result['segments']
|
| 293 |
+
s: dict[str, float] # simplified typing
|
| 294 |
+
for s in segments: # type: ignore
|
| 295 |
+
self._set_timing_for(segment=s, offset=start)
|
| 296 |
+
return result
|
| 297 |
+
|
| 298 |
+
def clip_audio(audio_file_path, sample_rate, start, end, output_path):
|
| 299 |
+
# Ensure the output directory exists
|
| 300 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 301 |
+
|
| 302 |
+
# Load the audio file
|
| 303 |
+
waveform, sr = librosa.load(audio_file_path, sr=sample_rate, mono=True)
|
| 304 |
+
|
| 305 |
+
# Calculate start and end samples
|
| 306 |
+
start_sample = int(start * sr)
|
| 307 |
+
end_sample = int(end * sr)
|
| 308 |
+
|
| 309 |
+
# Write the audio segment to the output path
|
| 310 |
+
sf.write(output_path, waveform[start_sample:end_sample], sr, format='WAV')
|
| 311 |
+
|
| 312 |
+
def main(args):
|
| 313 |
+
diarization, _, sample_rate = diarize_audio(args.hf_token, args.input_file)
|
| 314 |
+
model = WhisperFacade(model=args.model, quantize=args.quantize)
|
| 315 |
+
model.load_audio(args.input_file)
|
| 316 |
+
#
|
| 317 |
+
writer = WriteSRTIncremental()
|
| 318 |
+
writer_json = SegmentsJSONWriter()
|
| 319 |
+
whisper_options = {"verbose": None, "word_timestamps": False,
|
| 320 |
+
"task": args.task, "suppress_tokens": ""}
|
| 321 |
+
if args.language:
|
| 322 |
+
whisper_options["language"] = args.language
|
| 323 |
+
writer_options = {"max_line_width":55, "max_line_count":2, "word_timestamps": False}
|
| 324 |
+
print("Process diarized blocks")
|
| 325 |
+
|
| 326 |
+
# Group consecutive segments of the same speaker
|
| 327 |
+
grouped_segments = []
|
| 328 |
+
current_speaker = None
|
| 329 |
+
current_start = None
|
| 330 |
+
current_end = None
|
| 331 |
+
|
| 332 |
+
for turn, _, speaker in diarization.itertracks(yield_label=True):
|
| 333 |
+
print(speaker)
|
| 334 |
+
if turn.end - turn.start < 0.5: # Suppress short utterances (pyannote artifact)
|
| 335 |
+
print(f"start={turn.start:.1f}s stop={turn.end:.1f}s IGNORED")
|
| 336 |
+
continue
|
| 337 |
+
|
| 338 |
+
if speaker == current_speaker:
|
| 339 |
+
current_end = turn.end
|
| 340 |
+
else:
|
| 341 |
+
if current_speaker is not None:
|
| 342 |
+
grouped_segments.append((current_start, current_end, current_speaker))
|
| 343 |
+
current_speaker = speaker
|
| 344 |
+
current_start = turn.start
|
| 345 |
+
current_end = turn.end
|
| 346 |
+
|
| 347 |
+
# Append the last segment
|
| 348 |
+
if current_speaker is not None:
|
| 349 |
+
grouped_segments.append((current_start, current_end, current_speaker))
|
| 350 |
+
|
| 351 |
+
# Process each grouped segment
|
| 352 |
+
for start, end, speaker in grouped_segments:
|
| 353 |
+
clip_path = f"/tmp/speaker_{speaker}_start_{start:.1f}_end_{end:.1f}.wav"
|
| 354 |
+
clip_audio(args.input_file, sample_rate, start, end, clip_path)
|
| 355 |
+
result = model.transcribe(start=start, end=end, options=whisper_options)
|
| 356 |
+
language = result['language']
|
| 357 |
+
print(f"start={start:.1f}s stop={end:.1f}s lang={language} {speaker}")
|
| 358 |
+
writer(result, args.output_file, speaker, start, writer_options)
|
| 359 |
+
writer_json(generate_segments(result['segments'], speaker, language), args.output_json_file)
|
| 360 |
+
writer_json.finalize()
|
| 361 |
+
|
| 362 |
+
if __name__ == '__main__':
|
| 363 |
+
parser = argparse.ArgumentParser(description="Diarization and Whisper Transcription CLI")
|
| 364 |
+
parser.add_argument('--model', type=str, required=True, help="Whisper model to use")
|
| 365 |
+
parser.add_argument('--quantize', action='store_true', help="Whether to quantize the model")
|
| 366 |
+
parser.add_argument('--hf-token', type=str, required=True, help="Hugging Face authentication token")
|
| 367 |
+
parser.add_argument('--task', type=str, choices=['transcribe', 'translate'], required=True, help="Task to perform")
|
| 368 |
+
parser.add_argument('--language', type=str, required=False, help="Language to use for transcription or translation")
|
| 369 |
+
parser.add_argument('--input-file', type=str, required=True, help="Input audio file")
|
| 370 |
+
parser.add_argument('--output-file', type=str, required=True, help="Output file for the results (SRT or VTT)")
|
| 371 |
+
parser.add_argument('--output-json-file', type=str, required=True, help="Output file for the results (SRT or VTT)")
|
| 372 |
+
|
| 373 |
+
args = parser.parse_args()
|
| 374 |
+
main(args)
|
app/app.sh
CHANGED
|
@@ -32,7 +32,11 @@
|
|
| 32 |
# While the output is managed by ODTP and placed in /odtp/odtp-output/
|
| 33 |
#########################################################
|
| 34 |
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
#########################################################
|
| 38 |
# 5. OUTPUT FOLDER MANAGEMENT
|
|
|
|
| 32 |
# While the output is managed by ODTP and placed in /odtp/odtp-output/
|
| 33 |
#########################################################
|
| 34 |
|
| 35 |
+
if [ -n "$LANGUAGE" ]; then
|
| 36 |
+
python3 /odtp/odtp-app/app.py --model $MODEL --quantize --hf-token $HF_TOKEN --task $TASK --language $LANGUAGE --input-file /odtp/odtp-input/$INPUT_FILE --output-file /odtp/odtp-output/$OUTPUT_FILE.translate.srt --output-json-file /odtp/odtp-output/$OUTPUT_FILE.translate.json
|
| 37 |
+
else
|
| 38 |
+
python3 /odtp/odtp-app/app.py --model $MODEL --quantize --hf-token $HF_TOKEN --task $TASK --input-file /odtp/odtp-input/$INPUT_FILE --output-file /odtp/odtp-output/$OUTPUT_FILE.srt --output-json-file /odtp/odtp-output/$OUTPUT_FILE.json
|
| 39 |
+
fi
|
| 40 |
|
| 41 |
#########################################################
|
| 42 |
# 5. OUTPUT FOLDER MANAGEMENT
|
app/config_templates/template.yml
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
# Use the variable name between squared brackets to replace values in the template.
|
| 2 |
-
constant_string: /odtp/odtp-workdir/cache
|
| 3 |
-
value_to_replace: [variable_key]
|
|
|
|
|
|
|
|
|
|
|
|
app/gradio_app.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import tempfile
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import subprocess
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import io
|
| 8 |
+
|
| 9 |
+
def create_temp_structure():
|
| 10 |
+
"""Create temporary ODTP folder structure"""
|
| 11 |
+
temp_dir = tempfile.mkdtemp(prefix="odtp_")
|
| 12 |
+
os.makedirs(os.path.join(temp_dir, "odtp-input"))
|
| 13 |
+
os.makedirs(os.path.join(temp_dir, "odtp-output"))
|
| 14 |
+
return temp_dir
|
| 15 |
+
|
| 16 |
+
def cleanup_temp(temp_dir):
|
| 17 |
+
"""Remove temporary folder structure"""
|
| 18 |
+
shutil.rmtree(temp_dir)
|
| 19 |
+
|
| 20 |
+
def process_audio(audio_file, model, task, language, hf_token):
|
| 21 |
+
"""Process audio file with Whisper and Pyannote"""
|
| 22 |
+
# Create temp structure
|
| 23 |
+
temp_dir = create_temp_structure()
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
# Copy input file
|
| 27 |
+
input_path = os.path.join(temp_dir, "odtp-input", "input.wav")
|
| 28 |
+
shutil.copy2(audio_file, input_path)
|
| 29 |
+
|
| 30 |
+
# Prepare output paths
|
| 31 |
+
output_base = "output"
|
| 32 |
+
output_srt = os.path.join(temp_dir, "odtp-output",
|
| 33 |
+
f"{output_base}.{'translate.' if task == 'translate' else ''}srt")
|
| 34 |
+
output_json = os.path.join(temp_dir, "odtp-output",
|
| 35 |
+
f"{output_base}.{'translate.' if task == 'translate' else ''}json")
|
| 36 |
+
|
| 37 |
+
# Build command
|
| 38 |
+
cmd = [
|
| 39 |
+
"python3", "/odtp/odtp-app/app.py",
|
| 40 |
+
"--model", model,
|
| 41 |
+
"--quantize",
|
| 42 |
+
"--hf-token", hf_token,
|
| 43 |
+
"--task", task,
|
| 44 |
+
"--input-file", input_path,
|
| 45 |
+
"--output-file", output_srt,
|
| 46 |
+
"--output-json-file", output_json
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
if language != "auto":
|
| 50 |
+
cmd.extend(["--language", language])
|
| 51 |
+
|
| 52 |
+
# Run transcription
|
| 53 |
+
subprocess.run(cmd, check=True)
|
| 54 |
+
|
| 55 |
+
# Read results
|
| 56 |
+
with open(output_srt, 'r', encoding='utf-8') as f:
|
| 57 |
+
srt_content = f.read()
|
| 58 |
+
with open(output_json, 'r', encoding='utf-8') as f:
|
| 59 |
+
json_content = f.read()
|
| 60 |
+
|
| 61 |
+
# Create BytesIO objects for downloads
|
| 62 |
+
srt_bytes = io.BytesIO(srt_content.encode('utf-8'))
|
| 63 |
+
srt_bytes.name = "output.srt"
|
| 64 |
+
json_bytes = io.BytesIO(json_content.encode('utf-8'))
|
| 65 |
+
json_bytes.name = "output.json"
|
| 66 |
+
|
| 67 |
+
# Return contents and BytesIO objects
|
| 68 |
+
return srt_content, json_content, srt_bytes, json_bytes
|
| 69 |
+
|
| 70 |
+
finally:
|
| 71 |
+
# Cleanup
|
| 72 |
+
cleanup_temp(temp_dir)
|
| 73 |
+
|
| 74 |
+
# Define Gradio interface
|
| 75 |
+
with gr.Blocks() as demo:
|
| 76 |
+
gr.Markdown("# Audio Transcription/Translation with Speaker Diarization")
|
| 77 |
+
|
| 78 |
+
with gr.Row():
|
| 79 |
+
with gr.Column():
|
| 80 |
+
audio_input = gr.Audio(
|
| 81 |
+
type="filepath",
|
| 82 |
+
label="Upload Audio File (WAV format)"
|
| 83 |
+
)
|
| 84 |
+
model = gr.Dropdown(
|
| 85 |
+
choices=["tiny", "base", "small", "medium", "large", "large-v2"],
|
| 86 |
+
value="base",
|
| 87 |
+
label="Whisper Model"
|
| 88 |
+
)
|
| 89 |
+
task = gr.Dropdown(
|
| 90 |
+
choices=["transcribe", "translate"],
|
| 91 |
+
value="transcribe",
|
| 92 |
+
label="Task"
|
| 93 |
+
)
|
| 94 |
+
language = gr.Dropdown(
|
| 95 |
+
choices=["auto", "en", "es", "fr", "de", "it", "pt", "nl", "ja", "zh", "ru"],
|
| 96 |
+
value="auto",
|
| 97 |
+
label="Source Language"
|
| 98 |
+
)
|
| 99 |
+
hf_token = gr.Textbox(
|
| 100 |
+
label="Hugging Face Token",
|
| 101 |
+
type="password"
|
| 102 |
+
)
|
| 103 |
+
submit_btn = gr.Button("Process Audio")
|
| 104 |
+
|
| 105 |
+
with gr.Column():
|
| 106 |
+
srt_output = gr.Textbox(
|
| 107 |
+
label="SRT Output",
|
| 108 |
+
lines=10
|
| 109 |
+
)
|
| 110 |
+
json_output = gr.Textbox(
|
| 111 |
+
label="JSON Output",
|
| 112 |
+
lines=10
|
| 113 |
+
)
|
| 114 |
+
# Add download buttons
|
| 115 |
+
srt_download = gr.File(
|
| 116 |
+
label="Download SRT File"
|
| 117 |
+
)
|
| 118 |
+
json_download = gr.File(
|
| 119 |
+
label="Download JSON File"
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
submit_btn.click(
|
| 123 |
+
fn=process_audio,
|
| 124 |
+
inputs=[audio_input, model, task, language, hf_token],
|
| 125 |
+
outputs=[srt_output, json_output, srt_download, json_download]
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
demo.launch(
|
| 130 |
+
server_name="0.0.0.0", # More secure default for development
|
| 131 |
+
server_port=7860, # Default Gradio port
|
| 132 |
+
share=False, # Disable temporary public URL
|
| 133 |
+
show_error=True, # Show detailed error messages
|
| 134 |
+
debug=True # Enable debug mode for development
|
| 135 |
+
)
|
odtp.yml
CHANGED
|
@@ -1,95 +1,133 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
component-
|
| 6 |
-
component-
|
| 7 |
-
component-
|
| 8 |
-
component-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
tags:
|
| 10 |
-
-
|
| 11 |
-
-
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
#
|
| 14 |
tools:
|
| 15 |
-
- tool-name:
|
| 16 |
-
tool-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
tool-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
# This won't be traced in MongoDB
|
| 23 |
secrets:
|
| 24 |
-
- name:
|
| 25 |
-
|
|
|
|
| 26 |
|
| 27 |
-
#
|
| 28 |
-
build-args:
|
| 29 |
-
- name: Key of the argument
|
| 30 |
-
- description: Descriptio of the building argument
|
| 31 |
-
- secret: Bool
|
| 32 |
|
| 33 |
-
#
|
| 34 |
-
|
| 35 |
-
ports:
|
| 36 |
-
- name: PORT A
|
| 37 |
-
description: Description of Port A
|
| 38 |
-
port-value: XXXX
|
| 39 |
-
- name: PORT B
|
| 40 |
-
description: Description of Port B
|
| 41 |
-
port-value: YYYY
|
| 42 |
|
| 43 |
-
#
|
| 44 |
-
# Datatype can be str, int, float, or bool.
|
| 45 |
parameters:
|
| 46 |
-
- name:
|
| 47 |
-
default-value:
|
| 48 |
-
datatype:
|
| 49 |
-
description:
|
| 50 |
-
parameter-bounds:
|
| 51 |
-
|
| 52 |
-
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
parameter-bounds: null
|
| 60 |
-
options:
|
| 61 |
-
-
|
| 62 |
-
-
|
| 63 |
-
|
| 64 |
-
allow-custom-value: false # If true the user can add a custom value out of parameter-bounds, or options
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
data-inputs:
|
| 68 |
-
- name:
|
| 69 |
-
type:
|
| 70 |
-
path:
|
| 71 |
-
description:
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
description: Description of Output A
|
| 83 |
-
- name: OUTPUT B
|
| 84 |
-
type: TYPE_B # Folder or filetype
|
| 85 |
-
path: VALUE_B
|
| 86 |
-
description: Description of Output B
|
| 87 |
|
| 88 |
-
#
|
| 89 |
-
|
| 90 |
-
schema-
|
| 91 |
-
schema-output: PATH_TO_OUTPUT_SCHEMA
|
| 92 |
|
| 93 |
-
#
|
| 94 |
devices:
|
| 95 |
-
|
|
|
|
|
|
| 1 |
+
# Schema version for tracking updates to the schema format
|
| 2 |
+
schema-version: "v0.5.0"
|
| 3 |
+
|
| 4 |
+
# Component Information
|
| 5 |
+
component-name: odtp-pyannote-whisper
|
| 6 |
+
component-version: "v0.0.1"
|
| 7 |
+
component-license: AGPL 3.0
|
| 8 |
+
component-type: ephemeral
|
| 9 |
+
component-description: Transcribe or translate audio files using Whisper and Pyannote for speaker diarization
|
| 10 |
+
component-authors:
|
| 11 |
+
- name: Carlos Vivar Rios
|
| 12 |
+
orcid: null
|
| 13 |
+
component-repository:
|
| 14 |
+
url: "https://github.com/odtp-org/odtp-pyannote-whisper"
|
| 15 |
+
doi: null
|
| 16 |
+
component-docker-image: null
|
| 17 |
tags:
|
| 18 |
+
- audio
|
| 19 |
+
- transcription
|
| 20 |
+
- translation
|
| 21 |
+
- whisper
|
| 22 |
+
- pyannote
|
| 23 |
|
| 24 |
+
# Tool Information
|
| 25 |
tools:
|
| 26 |
+
- tool-name: whisper
|
| 27 |
+
tool-authors:
|
| 28 |
+
- name: OpenAI
|
| 29 |
+
orcid: null
|
| 30 |
+
tool-version: latest
|
| 31 |
+
tool-repository:
|
| 32 |
+
url: "https://github.com/openai/whisper"
|
| 33 |
+
doi: null
|
| 34 |
+
tool-license: MIT
|
| 35 |
+
|
| 36 |
+
- tool-name: pyannote
|
| 37 |
+
tool-authors:
|
| 38 |
+
- name: Hervé Bredin
|
| 39 |
+
orcid: null
|
| 40 |
+
tool-version: latest
|
| 41 |
+
tool-repository:
|
| 42 |
+
url: "https://github.com/pyannote/pyannote-audio"
|
| 43 |
+
doi: null
|
| 44 |
+
tool-license: MIT
|
| 45 |
|
| 46 |
+
# Secrets (ENV variables)
|
|
|
|
| 47 |
secrets:
|
| 48 |
+
- name: HF_TOKEN
|
| 49 |
+
description: Hugging Face API token for accessing pyannote models
|
| 50 |
+
type: str
|
| 51 |
|
| 52 |
+
# Build Arguments (if any)
|
| 53 |
+
build-args: null
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
# Exposed Ports
|
| 56 |
+
ports: null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
# Parameters for the Component
|
|
|
|
| 59 |
parameters:
|
| 60 |
+
- name: MODEL
|
| 61 |
+
default-value: large-v3
|
| 62 |
+
datatype: str
|
| 63 |
+
description: Whisper model to use for transcription/translation
|
| 64 |
+
parameter-bounds: null
|
| 65 |
+
options:
|
| 66 |
+
- tiny
|
| 67 |
+
- base
|
| 68 |
+
- small
|
| 69 |
+
- medium
|
| 70 |
+
- large
|
| 71 |
+
- large-v2
|
| 72 |
+
- large-v3
|
| 73 |
+
allow-custom-value: false
|
| 74 |
+
|
| 75 |
+
- name: TASK
|
| 76 |
+
default-value: transcribe
|
| 77 |
+
datatype: str
|
| 78 |
+
description: Task to perform (transcribe or translate)
|
| 79 |
parameter-bounds: null
|
| 80 |
+
options:
|
| 81 |
+
- transcribe
|
| 82 |
+
- translate
|
| 83 |
+
allow-custom-value: false
|
|
|
|
| 84 |
|
| 85 |
+
- name: LANGUAGE
|
| 86 |
+
default-value: auto
|
| 87 |
+
datatype: str
|
| 88 |
+
description: Source language code (use 'auto' for auto-detection)
|
| 89 |
+
parameter-bounds: null
|
| 90 |
+
options:
|
| 91 |
+
- auto
|
| 92 |
+
- en
|
| 93 |
+
- es
|
| 94 |
+
- fr
|
| 95 |
+
- de
|
| 96 |
+
- it
|
| 97 |
+
- pt
|
| 98 |
+
- nl
|
| 99 |
+
- ja
|
| 100 |
+
- zh
|
| 101 |
+
- ru
|
| 102 |
+
allow-custom-value: true
|
| 103 |
+
|
| 104 |
+
# Data Inputs
|
| 105 |
data-inputs:
|
| 106 |
+
- name: INPUT_FILE
|
| 107 |
+
type: .wav
|
| 108 |
+
path: /odtp/odtp-input
|
| 109 |
+
description: Input audio file in WAV format
|
| 110 |
+
naming-convention: null
|
| 111 |
+
|
| 112 |
+
# Data Outputs
|
| 113 |
+
data-outputs:
|
| 114 |
+
- name: OUTPUT_FILE
|
| 115 |
+
type: .srt
|
| 116 |
+
path: /odtp/odtp-output
|
| 117 |
+
description: Transcription/translation output in SRT format with speaker diarization
|
| 118 |
+
naming-convention: null
|
| 119 |
|
| 120 |
+
- name: OUTPUT_JSON_FILE
|
| 121 |
+
type: .json
|
| 122 |
+
path: /odtp/odtp-output
|
| 123 |
+
description: Transcription/translation output in JSON format with speaker diarization
|
| 124 |
+
naming-convention: null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
# Validation Schemas (Future Development)
|
| 127 |
+
schema-input: null
|
| 128 |
+
schema-output: null
|
|
|
|
| 129 |
|
| 130 |
+
# Device Requirements
|
| 131 |
devices:
|
| 132 |
+
- type: gpu
|
| 133 |
+
required: true
|
requirements.txt
CHANGED
|
@@ -1 +1,9 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--extra-index-url https://download.pytorch.org/whl/cu121
|
| 2 |
+
torch
|
| 3 |
+
torchaudio
|
| 4 |
+
openai-whisper
|
| 5 |
+
pyannote.audio
|
| 6 |
+
soundfile
|
| 7 |
+
librosa
|
| 8 |
+
jsonschema
|
| 9 |
+
gradio
|