katospiegel commited on
Commit
6934a38
·
1 Parent(s): 2a1a32a

feat: v0.0.1 functional

Browse files
Files changed (11) hide show
  1. .env.dist +8 -1
  2. .gitignore +4 -0
  3. Dockerfile +22 -26
  4. README.md +113 -240
  5. README.template.md +0 -62
  6. app/app.py +374 -0
  7. app/app.sh +5 -1
  8. app/config_templates/template.yml +0 -3
  9. app/gradio_app.py +135 -0
  10. odtp.yml +117 -79
  11. requirements.txt +9 -1
.env.dist CHANGED
@@ -1,11 +1,18 @@
1
  #ODTP COMPONENT VARIABLES
2
- VARIABLES=
 
 
 
 
 
 
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 ubuntu:22.04
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
- # ODTP Component Template
2
 
3
- This is a template that facilitates the development of new `odtp-components`. An `odtp` compatible component is a docker container able to perform a functional unit of computing in the digital twin. You can think of it as a blackbox that takes inputs files and/or parameters and perfom a task. Usually this lead to some files as a result (Ephemeral component), or to a visualization (Interactive component).
4
-
5
- Internally a component will run a bash script `./app/app.sh` that must include the commands for running your tool, and managing the input/output logic. While input files are located in the folder `/odtp/odtp-input`, parameters values are represented by environment variables within the component. In this way you can access to them by using `$` before the name of your variable. Finally, the output files generated are requested to be placed in `/odtp/odtp-output/`.
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/odtp-org/odtp-component-template`
13
-
14
-
15
- ## How to create an odtp compatible component using this template?
16
-
17
- 1. Identify which parameters would you like to expose.
18
- 2. Configure the Dockerfile to install all the OS requirements needed for your tool to run.
19
- 1. (Optional) If your tool requires python, and the dependencies offered in the repo are not compatible with the docker image you can configure custom dependencies in requirements.txt
20
- 3. Configure the `app/app.sh` file to:
21
- 1. Clone the repository of your tool and checkout to one specific commit.
22
- 2. (Optional) If your app uses a config file (i.e. `config.yml` or `config.json`), you need to provide a templace including placeholders for the variables you would like to expose. Placeholders can be defined by using double curly braces wrapping the name of the variable, such as `{{VARIABLE}}`. Then you can run `python3 /odtp/odtp-component-client/parameters.py PATH_TO_TEMPLATE PATH_TO_OUTPUT_CONFIG_FILE` and every placeholder will be replaced by the value in the environment variable.
23
- 3. Copy (`cp -r`) or create symbolic links (`ln -s`) to locate the input files in `/odpt/odtp-input/` in the folder.
24
- 4. Run the tool. You can access to the parameters as environemnt variables (i.e. `$PARAMETER_A`)
25
- 5. Manage the output exporting. At the end of the component execution all generated output should be located in `/odtp/odtp-output`. Copy all output files into this folder.
26
- 4. Describe all the metadata in `odtp.yml`. Please check below for instructions.
27
- 5. Publish your tool in the ODTP Zoo. (Temporaly unavailable)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- ### Semantic Validation
30
 
31
- ODTP will be able to validate the input/output files. In order to do this we use SHACL validation. However, the developer should provide a schema of the input/output schema. This section is still under development and it will be available soon.
32
 
33
- ## Internal data structure of a component
 
 
 
 
 
 
34
 
35
- It's important to remark that when the container is built an specific folder structure is generated:
36
 
37
- - `/odtp`: The main folder.
38
- - `/odtp/odtp-component-client`: This is the odtp client that will manage the execution, logging, and input/output functions of the component. It is include as a submodule, and the user doesn't need to modify it.
39
- - `/odtp/odtp-app`: This folder have the content of `/app` folder in this template. It contains the tool execution bash script and the tool configuration files.
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
- ## Testing the component
47
 
48
- There are 3 main ways in which you can test a component and the different odtp features.
 
 
49
 
50
- 1. Testing it as a docker container
51
- 2. Testing it as a single component using `odtp`
52
- 3. Testing it in a `odtp` digital twin execution
53
 
54
- When developing we recomend to start by testing the component via docker and then follow with the others.
 
 
 
55
 
56
- ### Testing the component as a docker container
57
 
58
- The user will need to manually create the input/output folders and build the docker image.
59
 
60
- 1. Prepare the following folder structure:
61
 
62
- ```
63
- - testing-folder
64
- - data-input
65
- - data-output
66
  ```
67
 
68
- Place all required input files in `testing-folder/data-input`.
69
 
70
- 2. Create your `.env` file with the following parameters.
71
-
72
- ```
73
- # ODTP COMPONENT VARIABLES
74
- PARAMETER-A=.....
75
- PARAMETER-B=.....
76
  ```
77
 
78
- 3. Build the dockerfile.
79
 
80
- ```
81
- docker build -t odtp-component .
82
- ```
83
 
84
- 4. Run the following command.
85
-
86
- ```
87
- docker run -it --rm \
88
  -v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-input \
89
- -v {PATH_TO_YOUR_INPUT_VOLUME}:/odtp/odtp-output \
90
- --env-file .env \
91
- odtp-component
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
- ## Changelog
208
-
209
- - v0.4.0
210
- - Update default Base and Python in Dockerfile to `ubuntu:22.04` and `python3.10`
211
 
212
- - v0.3.4
213
- - Inclusion of `secrets` and `build-args` in `odtp.yml`
214
- - Tools as list
215
 
216
- - v0.3.3
217
- - Inclusion of boundaries conditions and options in `odtp.yml` parameters.
218
-
219
- - v0.3.2
220
- - Extended `odtp.yml` parameters and input/output definition.
221
- - `odtp.requirements.txt` transfered to submodule `odtp-component-client`.
222
-
223
- - v0.3.1
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
- #### Distinct Licensing for Other Components
265
 
266
- - **Core Platform**: Open-source under AGPLv3.
267
- - **Ontology**: Creative Commons Attribution-ShareAlike (CC BY-SA).
268
- - **Component Zoo Template**: BSD-3 license.
269
 
270
- ### Alternative Commercial Licensing
 
 
 
 
 
 
 
 
 
 
271
 
272
- Alternative commercial licensing options for the core platform and other components are available and can be negotiated through the EPFL Technology Transfer Office (https://tto.epfl.ch) or ETH Zürich Technology Transfer Office (https://ethz.ch/en/industry/transfer.html).
273
 
274
- ## Ethical Use and Legal Compliance Disclaimer
275
 
276
- Please note that this software should not be used to deliberately harm any individual or entity. Users and developers must adhere to ethical guidelines and use the software responsibly and legally. This disclaimer serves to remind all parties involved in the use or development of this software to engage in practices that are ethical, lawful, and in accordance with the intended purpose of the software.
 
1
+ # Name of the component
2
 
3
+ Add here your badges:
4
+ [![Launch in your ODTP](https://img.shields.io/badge/Launch%20in%20your-ODTP-blue?logo=launch)](http://localhost:8501/launch-component)
5
+ [![Compatible with ODTP v0.5.x](https://img.shields.io/badge/Compatible%20with-ODTP%20v0.5.0-green)]("")
 
 
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
- # COMMAND $PARAMETER_A #PARAMETER_B /odtp/odtp-input/data
 
 
 
 
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
- # This file should contain basic component information for your component.
2
- component-name: Component Name
3
- component-author: Component Author
4
- component-version: Component Version
5
- component-repository: Component Repository
6
- component-license: Component License
7
- component-type: ephemeral or interactive
8
- component-description: Description
 
 
 
 
 
 
 
 
9
  tags:
10
- - tag1
11
- - tag2
 
 
 
12
 
13
- # Information about the tools
14
  tools:
15
- - tool-name: tool's name
16
- tool-author: Tool's author
17
- tool-version: Tool version
18
- tool-repository: Tool's repository
19
- tool-license: Tool's license
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # If your tool require some secrets token to be passed as ENV to the component
22
- # This won't be traced in MongoDB
23
  secrets:
24
- - name: Key of the argument
25
- - description: Description of the secret
 
26
 
27
- # If the tool requires some building arguments such as Matlab license
28
- build-args:
29
- - name: Key of the argument
30
- - description: Descriptio of the building argument
31
- - secret: Bool
32
 
33
- # If applicable, ports exposed by the component
34
- # Include Name, Description, and Port Value for each port
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
- # If applicable, parameters exposed by the component
44
- # Datatype can be str, int, float, or bool.
45
  parameters:
46
- - name: PARAMETER A
47
- default-value: DEFAULT_VALUE_A
48
- datatype: DATATYPE_A
49
- description: Description of Parameter A
50
- parameter-bounds: # Boundaries for int and float datatype
51
- - 0 # Lower bound
52
- - inf # Upper bound
53
- options: null
54
- allow-custom-value: false # If true the user can add a custom value out of parameter-bounds, or options
55
- - name: PARAMETER B
56
- default-value: DEFAULT_VALUE_B
57
- datatype: DATATYPE_B
58
- description: Description of Parameter B
 
 
 
 
 
 
59
  parameter-bounds: null
60
- options: # If your string parameter is limited to a few option, please list them here.
61
- - OptionA
62
- - OptionB
63
- - OptionC
64
- allow-custom-value: false # If true the user can add a custom value out of parameter-bounds, or options
65
 
66
- # If applicable, data-input list required by the component
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  data-inputs:
68
- - name: INPUT A
69
- type: TYPE_A # Folder or filetype
70
- path: VALUE_A
71
- description: Description of Input A
72
- - name: INPUT B
73
- type: TYPE_B # Folder or filetype
74
- path: VALUE_B
75
- description: Description of Input B
 
 
 
 
 
76
 
77
- # If applicable, data-output list produced by the component
78
- data-output:
79
- - name: OUTPUT A
80
- type: TYPE_A # Folder or filetype
81
- path: VALUE_A
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
- # If applicable, path to schemas to perform semantic validation.
89
- # Still under development. Ignore.
90
- schema-input: PATH_TO_INPUT_SCHEMA
91
- schema-output: PATH_TO_OUTPUT_SCHEMA
92
 
93
- # If applicable, define devices needed such as GPU.
94
  devices:
95
- gpu: Bool
 
 
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
- # Please list here all python dependencies of your tool
 
 
 
 
 
 
 
 
 
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