Skip to content
This repository was archived by the owner on Jan 30, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Custom ignore
local/
data/
ssl/server.crt
ssl/server.key

### Python template
# Byte-compiled / optimized / DLL files
Expand Down
10 changes: 7 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ COPY app /app
COPY ./requirements.txt /requirements.txt

# Pip install the dependencies
RUN pip install --upgrade pip
RUN pip --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org install --upgrade pip
# For CPU only, you can use pip install git+https://github.com/MiscellaneousStuff/whisper.git
# in place of openai-whisper.
# Also, --extra-index-url https://download.pytorch.org/whl/cpu might be needed if you are using a CPU only machine
RUN pip install --no-cache-dir -r /requirements.txt
RUN pip install --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org --no-cache-dir -r /requirements.txt

# Set the working directory to /app
WORKDIR /app
Expand All @@ -29,4 +29,8 @@ EXPOSE 8501
VOLUME /data

# Run the app
CMD streamlit run /app/01_🏠_Home.py
#CMD streamlit run /app/01_🏠_Home.py

# Run the app on https
COPY ssl /app/ssl
CMD streamlit run /app/01_🏠_Home.py --server.sslCertFile '/app/ssl/server.crt' --server.sslKeyFile '/app/ssl/server.key'
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Streamlit UI for OpenAI's Whisper
# Streamlit UI for OpenAI's Whisper (mic support)

This is a simple [Streamlit UI](https://streamlit.io/) for [OpenAI's Whisper speech-to-text model](https://openai.com/blog/whisper/).
It let's you download and transcribe media from YouTube videos, playlists, or local files.
Expand All @@ -25,12 +25,23 @@ conda env create -f environment.yml

Note: If you're using a CPU-only machine, your runtime can be sped-up by using quantization implemented by [@MicellaneousStuff](https://github.com/MiscellaneousStuff) by swapping out `pip install openai-whisper` from `requirements.txt` and replacing it with their fork `pip install git+https://github.com/MiscellaneousStuff/whisper.git` (See related discussion here - https://github.com/hayabhay/whisper-ui/issues/20)

## Setup SSL termination configuration

This `mic_support` branch supports "mic input". Due to browser media access issue, you should use SSL termination configuration in a reverse proxy or load balancer, or you can use streamlit [HTTPS support](https://docs.streamlit.io/library/advanced-features/https-support). streamlit 1.20.0 or later supports HTTPS. "mic input" data will be treated as "uploaded".

As the easiest way, you can create a self-signed-cert as follows:

```
cd ssl
create-self-signed-cert.sh
```

## Usage

Once you're set up, you can run the app with:

```
streamlit run app/01_🏠_Home.py
streamlit run app/01_🏠_Home.py --server.sslCertFile 'ssl/server.crt' --server.sslKeyFile 'ssl/server.key'
```

This will open a new tab in your browser with the app. You can then select a YouTube URL or local file & click "Run Whisper" to run the model on the selected media.
Expand All @@ -43,7 +54,7 @@ Alternatively, you can run the app containerized with Docker via the included do
docker compose up
```

Then open up a new tab and navigate to [http://localhost:8501/](http://localhost:8501/)
Then open up a new tab and navigate to [https://localhost:8501/](https://localhost:8501/)

NOTE: For existing users, this will break the database since absolute paths of files are saved. A future fix will be added to fix this.

Expand Down
62 changes: 58 additions & 4 deletions app/01_🏠_Home.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
from config import get_page_config, get_whisper_settings, save_whisper_settings
from core import MediaManager

# for self-signed-cert, not verified but un-safe.
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

from streamlit.runtime.uploaded_file_manager import UploadedFile, UploadedFileRec
from audiorecorder import audiorecorder

st.set_page_config(**get_page_config())


Expand Down Expand Up @@ -40,14 +47,37 @@ def get_formatted_date(date_str: str) -> str:
# ---------
with st.sidebar.expander("➕   Add Media", expanded=False):
# # Render media type selection on the sidebar & the form
source_type = st.radio("Media Source", ["YouTube", "Upload"], label_visibility="collapsed")
source_type = st.radio("Media Source", ["YouTube", "Upload", "Mic"], label_visibility="collapsed")
with st.form("input_form"):
if source_type == "YouTube":
youtube_url = st.text_input("Youtube video or playlist URL")
elif source_type == "Upload":
input_files = st.file_uploader(
"Add one or more files", type=["mp4", "avi", "mov", "mkv", "mp3", "wav"], accept_multiple_files=True
)
elif source_type == "Mic":
audio = audiorecorder("Click to record", "Recording...")
if len(audio) > 0:
# To play audio in frontend:
#st.audio(audio.tobytes())

# To save audio to a file:
#wav_file = open("audio.mp3", "wb")
#wav_file.write(audio.tobytes())
#wav_file.close()

# emulate "uploaded file"
now = datetime.now()
d = f'{now:%Y%m%d%H%M%S}'

filerec = UploadedFileRec(0,
f"mic_input_{d}.mp3",
"mp3",
audio.tobytes())
input_files = UploadedFile(filerec)
else:
input_files = None

task_options = ["transcribe", "translate"]
task = st.selectbox(
"Task", options=task_options, index=task_options.index(st.session_state.whisper_params["task"])
Expand All @@ -66,6 +96,11 @@ def get_formatted_date(date_str: str) -> str:
source = input_files
else:
st.error("Please upload files")
elif source_type == "Mic":
if input_files:
source = input_files
else:
st.error("Please input your voice via mic")

# Lowercase the source type
source_type = source_type.lower()
Expand Down Expand Up @@ -156,7 +191,7 @@ def get_formatted_date(date_str: str) -> str:
# Add a meta caption
st.write(f"#### {media['source_name']}")

source_type = "YouTube" if media["source_type"] == "youtube" else "upload"
source_type = "YouTube" if media["source_type"] == "youtube" else media["source_type"]
st.markdown(
f"""
<i>Source</i>: {source_type}<br/>
Expand All @@ -179,7 +214,7 @@ def get_formatted_date(date_str: str) -> str:
# Render the media
if media["source_type"] == "youtube":
st.video(media["source_link"])
elif media["source_type"] == "upload":
elif media["source_type"] in ["upload", "mic"]:
st.audio(media["filepath"])

st.write("---")
Expand Down Expand Up @@ -207,6 +242,12 @@ def get_formatted_date(date_str: str) -> str:

# Add a meta caption
source_type = "YouTube" if media["source_type"] == "youtube" else "uploaded"
if media["source_type"] == "youtube":
source_type = "YouTube"
elif media["source_type"] == "upload":
source_type = "uploaded"
else:
source_tpye = media["source_type"]
st.markdown(
f"""
<i>Source</i>: <b>{media['source_name']}</b> ({source_type})<br/>
Expand Down Expand Up @@ -253,14 +294,27 @@ def get_formatted_date(date_str: str) -> str:
if media["source_type"] == "youtube":
st.sidebar.audio(media["filepath"], start_time=st.session_state.selected_media_offset)
st.sidebar.video(media["source_link"])
elif media["source_type"] == "upload":
elif media["source_type"] in ["upload", "mic"]:
st.sidebar.audio(media["filepath"], start_time=st.session_state.selected_media_offset)

st.write(f'## {media["source_name"]}')

with st.expander("🔽 &nbsp; Downloads"):
fpath = str(Path(media["filepath"]).parent)
fname = str(Path(media["filepath"]).parent.name)
for ext in (".srt",".vtt",".json",".txt",".tsv"):
with open(fpath + '/transcript' + ext) as f:
st.download_button('Download ' + ext, f, file_name=fname+ext);

with st.expander("📝 &nbsp; Metadata"):
# Add a meta caption
source_type = "YouTube" if media["source_type"] == "youtube" else "uploaded"
if media["source_type"] == "youtube":
source_type = "YouTube"
elif media["source_type"] == "upload":
source_type = "uploaded"
else:
source_tpye = media["source_type"]
st.markdown(
f"""
<i>Source</i>: <b>{media['source_name']}</b> ({source_type})<br/>
Expand Down
4 changes: 2 additions & 2 deletions app/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def _create(self, source_list: List[Union[str, Any]], source_type: str, whisper_
save_dir = self.media_dir / f"""{source_dirname}-{datetime.now().strftime("%Y-%m-%d %H-%M-%S")}"""
# Download the audio file
yc.streams.get_by_itag(140).download(save_dir, filename=save_filename)
elif source_type == "upload":
elif source_type in ["upload", "mic"]:
# Parse the file name from the source
tokens = source.name.split(".")
source_name = ".".join(tokens[:-1])
Expand Down Expand Up @@ -162,7 +162,7 @@ def add(self, source: Union[str, Any], source_type: str, **whisper_args):
source_list = list(Playlist(source))
else:
source_list = [source]
elif source_type == "upload":
elif source_type in ["upload", "mic"]:
source_list = source if type(source) == list else [source]

# Download & save the audio files and add them to the database
Expand Down
2 changes: 1 addition & 1 deletion app/pages/02_⚙️_Settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# --------------
st.write("### ⚙️ Whisper Settings")
with st.form("whisper_settings_form"):
model_options = ["tiny", "base", "small", "medium", "large"]
model_options = ["tiny", "base", "small", "medium", "large", "large-v2"]
selected_model = model_options.index(st.session_state.whisper_params["whisper_model"])
whisper_model = st.selectbox("Model", options=model_options, index=selected_model)
temperature = st.number_input(
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ services:
# TODO: Fix this to relative paths from some user-defined directory
- ./data:/data
restart: unless-stopped
init: true # https://qiita.com/sun33/items/d62744f5746f6815f3c8
ports:
- 8501:8501
3 changes: 2 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ dependencies:
- six==1.16.0
- smmap==5.0.0
- sqlalchemy==2.0.2
- streamlit==1.17.0
- streamlit==1.20.0
- streamlit-audiorecorder==0.0.2
- tokenizers==0.13.2
- toml==0.10.2
- toolz==0.12.0
Expand Down
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ SQLAlchemy==2.0.3 # https://github.com/sqlalchemy/sqlalchemy

# Frontend
# --------------------
streamlit==1.18.1 # https://github.com/streamlit/streamlit
streamlit==1.20.0 # https://github.com/streamlit/streamlit

# Audio Recorder on Frontend
# --------------------
streamlit-audiorecorder==0.0.2 # https://github.com/theevann/streamlit-audiorecorder
12 changes: 12 additions & 0 deletions ssl/create-self-signed-cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

#openssl genrsa 2048 > server.key
#openssl req -new -key server.key > server.csr
#openssl x509 -days 3650 -req -sha256 -signkey server.key < server.csr > server.crt
#openssl x509 -text < server.crt

# https://qiita.com/miyuki_samitani/items/b19aa5ac3b3c6e312bd5
# https://qiita.com/sanyamarseille/items/46fc6ff5a0aca12e1946

# https://www.karakaram.com/creating-self-signed-certificate/
openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 -subj /CN=localhost -keyout server.key -out server.crt