diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f80545a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.10.16 + +# set env variables for python +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# set work directory +WORKDIR /app + +COPY pyproject.toml /app/ +COPY config.yml /app/ +COPY src/ /app/src + +RUN pip install --upgrade pip +RUN pip install . + +CMD ["streamlit", "run", "src/annescribe/webapp_main.py"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e69de29..f35b454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "annescribe" +version = "0.1.0" +authors = [ + { name = "Alex Blank", email = "alexblank@fastmail.com" } +] +description = "A tool for transcribing audio files with whisper in a webapp" +readme = "README.md" +requires-python = "==3.10.16" +dependencies = [ + "torch>=2.3", + "openai-whisper>=20240927", + "streamlit==1.41.1", + "streamlit-authenticator==0.4.1", + "streamlit-autorefresh==1.0.1", + "pydub==0.25.1" +] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 182168a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -streamlit -streamlit-authenticator -streamlit-autorefresh -openai-whisper -pydub \ No newline at end of file diff --git a/transcription/__init__.py b/src/annescribe/__init__.py similarity index 100% rename from transcription/__init__.py rename to src/annescribe/__init__.py diff --git a/main.py b/src/annescribe/main.py similarity index 100% rename from main.py rename to src/annescribe/main.py diff --git a/webapp/__init__.py b/src/annescribe/transcription/__init__.py similarity index 100% rename from webapp/__init__.py rename to src/annescribe/transcription/__init__.py diff --git a/transcription/audio_processing.py b/src/annescribe/transcription/audio_processing.py similarity index 100% rename from transcription/audio_processing.py rename to src/annescribe/transcription/audio_processing.py diff --git a/transcription/job_utils.py b/src/annescribe/transcription/job_utils.py similarity index 100% rename from transcription/job_utils.py rename to src/annescribe/transcription/job_utils.py diff --git a/transcription/transcription.py b/src/annescribe/transcription/transcription.py similarity index 100% rename from transcription/transcription.py rename to src/annescribe/transcription/transcription.py diff --git a/transcription/transcription_helper.py b/src/annescribe/transcription/transcription_helper.py similarity index 94% rename from transcription/transcription_helper.py rename to src/annescribe/transcription/transcription_helper.py index 61c1e74..008df80 100644 --- a/transcription/transcription_helper.py +++ b/src/annescribe/transcription/transcription_helper.py @@ -3,11 +3,11 @@ import os from multiprocessing import Lock from concurrent.futures import ProcessPoolExecutor -from utils import get_logger -from transcription import job_utils -from transcription.job_utils import is_job, remove_job, create_job, get_processing, set_processing -from transcription.audio_processing import split_audio, convert_to_wav -from transcription.transcription import transcribe_audio +from annescribe.utils import get_logger +from annescribe.transcription import job_utils +from annescribe.transcription.job_utils import is_job, remove_job, create_job, get_processing, set_processing +from annescribe.transcription.audio_processing import split_audio, convert_to_wav +from annescribe.transcription.transcription import transcribe_audio job_update_lock = Lock() default_input_audio_file_name = 'input_audio' diff --git a/utils.py b/src/annescribe/utils.py similarity index 100% rename from utils.py rename to src/annescribe/utils.py diff --git a/transcription/job_execution.py b/src/annescribe/webapp/__init__.py similarity index 100% rename from transcription/job_execution.py rename to src/annescribe/webapp/__init__.py diff --git a/webapp/home.py b/src/annescribe/webapp/home.py similarity index 86% rename from webapp/home.py rename to src/annescribe/webapp/home.py index 393a567..09d50d8 100644 --- a/webapp/home.py +++ b/src/annescribe/webapp/home.py @@ -2,8 +2,8 @@ from functools import partial import streamlit as st -from webapp.new_task import render_new_task -from webapp.task_list import render_task_list +from annescribe.webapp.new_task import render_new_task +from annescribe.webapp.task_list import render_task_list def render_home(container, diff --git a/src/annescribe/webapp/new_task.py b/src/annescribe/webapp/new_task.py new file mode 100644 index 0000000..a173cdd --- /dev/null +++ b/src/annescribe/webapp/new_task.py @@ -0,0 +1,71 @@ +import os + +import streamlit as st + +from annescribe.utils import get_logger +from annescribe.transcription.job_utils import create_job +from annescribe.transcription.transcription_helper import transcribe + +logger = get_logger(__name__) + + +def create_new_task(file_upload, app_config, overwrite: bool): + """ + Create a new task + :param file_upload: file upload from form + :param app_config: app configuration + :param overwrite: overwrite existing task + :return: None + """ + upload_directory = app_config["data"]["upload_directory"] + + st.write(f"Uploading...") + # create the upload directory if it does not exist + if not os.path.exists(upload_directory): + logger.info(f"Creating upload directory {upload_directory}") + os.makedirs(upload_directory) + + logger.info(f"Saving file {file_upload.name} to {upload_directory}") + file_path = f"{upload_directory}/{file_upload.name}" + with open(file_path, "wb") as file: + file.write(file_upload.getbuffer()) + + st.success(f"File uploaded to {file_path}") + + logger.info(f"Creating new task for file {file_upload}") + + if file_path is None: + st.error("No file uploaded") + return + + transcribe(file_path, app_config, blocking=False, overwrite=overwrite) + + +def render_new_task(container, app_config: dict): + """ + Render the new task page + :param container: container object to place the content in + :param app_config: app configuration + :return: None + """ + + def upload_file(file_path: str): + """ + Upload a file + :param file_path: path to the file + :return: None + """ + + @st.dialog("Upload new audio file") + def show_create_task_dialog(): + st.write("Create a transcription job") + # add checkbox for overwrite + overwrite = st.checkbox("Overwrite existing task", value=False, key="overwrite") + uploaded_file = st.file_uploader("Audio File", type=["wav", "mp3", "flac", "ogg"]) + if st.button("Create Task", key="btn_create_task"): + create_new_task(uploaded_file, app_config, overwrite=overwrite) + st.write("Task created") + st.rerun() + + st.button("Create Task", on_click=show_create_task_dialog, key="btn_open_create_task_dialog") + diff --git a/webapp/task_list.py b/src/annescribe/webapp/task_list.py similarity index 98% rename from webapp/task_list.py rename to src/annescribe/webapp/task_list.py index 89b3e98..df73e18 100644 --- a/webapp/task_list.py +++ b/src/annescribe/webapp/task_list.py @@ -4,7 +4,7 @@ import streamlit as st from streamlit_autorefresh import st_autorefresh -from transcription.job_utils import get_existing_jobs, remove_job +from annescribe.transcription.job_utils import get_existing_jobs, remove_job def show_results(job_data: dict): diff --git a/webapp_main.py b/src/annescribe/webapp_main.py similarity index 56% rename from webapp_main.py rename to src/annescribe/webapp_main.py index 23daec5..122d948 100644 --- a/webapp_main.py +++ b/src/annescribe/webapp_main.py @@ -8,7 +8,10 @@ import streamlit as st import streamlit_authenticator as st_auth from streamlit_autorefresh import st_autorefresh -from webapp.home import render_home +from annescribe.utils import get_logger +from annescribe.webapp.home import render_home + +logger = get_logger(__name__) CONFIG_FILE = os.environ.get('CONFIG_FILE', "config.yml") @@ -23,18 +26,23 @@ with open(CONFIG_FILE, 'r') as config_file: AUTH_FILE = os.environ.get('AUTH_FILE', "auth.yml") if AUTH_FILE is None: raise ValueError('AUTH_YML_FILE environment variable is not set') -elif not os.path.exists(AUTH_FILE): - raise ValueError(f'AUTH_YML_FILE {AUTH_FILE} does not exist') -with open(AUTH_FILE, 'r') as auth_file: - auth_config = yaml.safe_load(auth_file) +if os.path.exists(AUTH_FILE): + with open(AUTH_FILE, 'r') as auth_file: + auth_config = yaml.safe_load(auth_file) + print(logger.info(f'User authentication enabled')) + authentication_enabled = True +else: + logger.info(f"AUTH_FILE {AUTH_FILE} does not exist, user authentication disabled") + authentication_enabled = False -authenticator = st_auth.Authenticate( - auth_config["credentials"], - auth_config["cookie"]["name"], - auth_config["cookie"]["key"], - auth_config["cookie"]["expiry_days"], -) +if authentication_enabled: + authenticator = st_auth.Authenticate( + auth_config["credentials"], + auth_config["cookie"]["name"], + auth_config["cookie"]["key"], + auth_config["cookie"]["expiry_days"], + ) content_wrapper = st.empty() @@ -43,7 +51,8 @@ def render_login(): with content_wrapper.container(): st.title('Annescribe Login') st.write("Please login with your credentials.") - authenticator.login() + if authentication_enabled: + authenticator.login() if "authentication_status" not in st.session_state: @@ -54,10 +63,10 @@ if "authentication_status" not in st.session_state: authentication_status = st.session_state.get("authentication_status") username = st.session_state.get("username") if authentication_status is True: - render_home(content_wrapper, config, authenticator) -elif authentication_status is None: - st.warning('Please enter your username and password') - render_login() + render_home(content_wrapper, config, authenticator if authentication_enabled else None) +# elif authentication_status is None: +# st.warning('Please enter your username and password') +# render_login() else: # make sure to remove cookie render_login() diff --git a/transcription/__pycache__/__init__.cpython-311.pyc b/transcription/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index b0cde6c..0000000 Binary files a/transcription/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/transcription/__pycache__/job_utils.cpython-311.pyc b/transcription/__pycache__/job_utils.cpython-311.pyc deleted file mode 100644 index c13862e..0000000 Binary files a/transcription/__pycache__/job_utils.cpython-311.pyc and /dev/null differ diff --git a/webapp/__pycache__/__init__.cpython-311.pyc b/webapp/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index f0da1a1..0000000 Binary files a/webapp/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/webapp/__pycache__/home.cpython-311.pyc b/webapp/__pycache__/home.cpython-311.pyc deleted file mode 100644 index 669bd27..0000000 Binary files a/webapp/__pycache__/home.cpython-311.pyc and /dev/null differ diff --git a/webapp/__pycache__/task_list.cpython-311.pyc b/webapp/__pycache__/task_list.cpython-311.pyc deleted file mode 100644 index f58c0df..0000000 Binary files a/webapp/__pycache__/task_list.cpython-311.pyc and /dev/null differ diff --git a/webapp/login.py b/webapp/login.py deleted file mode 100644 index 88e2e50..0000000 --- a/webapp/login.py +++ /dev/null @@ -1,9 +0,0 @@ -import os -import yaml - -import streamlit as st -import streamlit_authenticator as st_auth - - - - diff --git a/webapp/new_task.py b/webapp/new_task.py deleted file mode 100644 index 6b99715..0000000 --- a/webapp/new_task.py +++ /dev/null @@ -1,61 +0,0 @@ -import os - -import streamlit as st - -from utils import get_logger -from transcription.job_utils import create_job -from transcription.transcription_helper import transcribe - -logger = get_logger(__name__) - - -def create_new_task(filepath: str, app_config, overwrite: bool): - """ - Create a new task - :param filepath: path to the audio file - :param app_config: app configuration - :param overwrite: overwrite existing task - :return: None - """ - logger.info(f"Creating new task for file {filepath}") - - if filepath is None: - st.error("No file uploaded") - return - - transcribe(filepath, app_config, blocking=False, overwrite=overwrite) - - -def render_new_task(container, app_config: dict): - """ - Render the new task page - :param container: container object to place the content in - :param app_config: app configuration - :return: None - """ - - with container.container(): - st.write("Create a new task") - # add checkbox for overwrite - with st.form(key="new_task_form", clear_on_submit=True): - overwrite = st.checkbox("Overwrite existing task", value=False, key="overwrite") - uploaded_file = st.file_uploader("Audio File", type=["wav", "mp3", "flac", "ogg"], key="audio_file") - file_path = None - - if uploaded_file is not None: - # save files to upload directory - upload_directory = app_config["data"]["upload_directory"] - - # create the upload directory if it does not exist - if not os.path.exists(upload_directory): - logger.info(f"Creating upload directory {upload_directory}") - os.makedirs(upload_directory) - - logger.info(f"Saving file {uploaded_file.name} to {upload_directory}") - file_path = f"{upload_directory}/{uploaded_file.name}" - with open(file_path, "wb") as file: - file.write(uploaded_file.getbuffer()) - - st.success(f"File saved to {file_path}") - - st.form_submit_button("Create Task", on_click=create_new_task, args=(file_path, app_config, overwrite))