initial commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
CONFIG_FILE = os.environ.get('CONFIG_FILE', "config.yml")
|
||||||
|
|
||||||
|
if CONFIG_FILE is None:
|
||||||
|
raise ValueError('CONFIG_FILE environment variable is not set')
|
||||||
|
elif not os.path.exists(CONFIG_FILE):
|
||||||
|
raise ValueError(f'CONFIG_FILE {CONFIG_FILE} does not exist')
|
||||||
|
|
||||||
|
with open(CONFIG_FILE, 'r') as config_file:
|
||||||
|
config = yaml.safe_load(config_file)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,72 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
from pydub import AudioSegment
|
||||||
|
from pydub.silence import split_on_silence
|
||||||
|
|
||||||
|
|
||||||
|
def split_audio(audio_file_path: str,
|
||||||
|
chunk_folder: str,
|
||||||
|
max_num_chunks: int,
|
||||||
|
min_silence_level: int,
|
||||||
|
min_silence_length: int,
|
||||||
|
keep_silence: int) -> list:
|
||||||
|
"""
|
||||||
|
Split the audio file into chunks.
|
||||||
|
:param audio_file_path: path to the audio file
|
||||||
|
:param chunk_folder: path to the chunk folder
|
||||||
|
:param max_num_chunks: number of chunks to split the audio file into
|
||||||
|
:param min_silence_level: minimum silence level
|
||||||
|
:param min_silence_length: minimum silence length
|
||||||
|
:param keep_silence: whether to keep silence
|
||||||
|
:return: list of chunked audio file paths
|
||||||
|
"""
|
||||||
|
|
||||||
|
# create the chunk folder
|
||||||
|
if not os.path.exists(chunk_folder):
|
||||||
|
os.makedirs(chunk_folder)
|
||||||
|
|
||||||
|
# load audio file
|
||||||
|
audio = AudioSegment.from_file(audio_file_path)
|
||||||
|
audio_name = os.path.basename(audio_file_path).split('.')[0]
|
||||||
|
|
||||||
|
# split audio file into chunks
|
||||||
|
chunks = split_on_silence(audio,
|
||||||
|
silence_thresh=min_silence_level,
|
||||||
|
min_silence_len=min_silence_length,
|
||||||
|
keep_silence=keep_silence)
|
||||||
|
|
||||||
|
if len(chunks) > max_num_chunks:
|
||||||
|
# randomly combine chunks
|
||||||
|
while len(chunks) > max_num_chunks:
|
||||||
|
random_chunk_index = random.randint(0, len(chunks) - 2)
|
||||||
|
chunks[random_chunk_index] += chunks[random_chunk_index + 1]
|
||||||
|
chunks.pop(random_chunk_index + 1)
|
||||||
|
|
||||||
|
# save chunks
|
||||||
|
chunked_audio_files = []
|
||||||
|
for i, chunk in enumerate(chunks):
|
||||||
|
chunk_file_path = os.path.join(chunk_folder, f'{audio_name}_{i}.wav')
|
||||||
|
chunk.export(chunk_file_path, format='wav')
|
||||||
|
chunked_audio_files.append(chunk_file_path)
|
||||||
|
|
||||||
|
return chunked_audio_files
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_wav(audio_file_path: str,
|
||||||
|
output_file_path: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert an audio file to wav format.
|
||||||
|
:param audio_file_path: path to the audio file
|
||||||
|
:param output_folder_path: path to the output folder
|
||||||
|
:return: path to the converted audio file
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_audio = AudioSegment.from_file(audio_file_path)
|
||||||
|
if ".wav" in output_file_path:
|
||||||
|
new_audio_file_path = output_file_path
|
||||||
|
else:
|
||||||
|
new_audio_file_path = output_file_path + ".wav"
|
||||||
|
input_audio.export(new_audio_file_path, format='wav')
|
||||||
|
|
||||||
|
return new_audio_file_path
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from multiprocessing import Lock
|
||||||
|
|
||||||
|
# multiprocessing lock for thread safety
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
chunk_input_subdir = 'input_chunks'
|
||||||
|
chunk_output_subdir = 'output_chunks'
|
||||||
|
|
||||||
|
|
||||||
|
def get_job_data(job_name: str, config: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Get job data from the database.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
:return: job data
|
||||||
|
"""
|
||||||
|
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
|
||||||
|
if not os.path.exists(job_dir):
|
||||||
|
raise ValueError(f'Job {job_name} does not exist')
|
||||||
|
|
||||||
|
job_data = dict()
|
||||||
|
job_data['name'] = job_name
|
||||||
|
|
||||||
|
output_file = os.path.join(job_dir, 'output.txt')
|
||||||
|
if os.path.exists(output_file):
|
||||||
|
with open(output_file, 'r') as f:
|
||||||
|
job_data['output'] = f.read()
|
||||||
|
job_data["completed"] = True
|
||||||
|
else:
|
||||||
|
job_data['output'] = None
|
||||||
|
job_data["completed"] = False
|
||||||
|
|
||||||
|
processing_file = os.path.join(job_dir, '_PROCESSING')
|
||||||
|
if os.path.exists(processing_file):
|
||||||
|
job_data["processing"] = True
|
||||||
|
else:
|
||||||
|
job_data["processing"] = False
|
||||||
|
|
||||||
|
# get progress of task
|
||||||
|
job_data["progress"] = get_progress(job_name, config)
|
||||||
|
|
||||||
|
return job_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_existing_jobs(config: dict) -> list:
|
||||||
|
"""
|
||||||
|
Get existing jobs from the database.
|
||||||
|
:param config: app configuration
|
||||||
|
:return: list of jobs
|
||||||
|
"""
|
||||||
|
|
||||||
|
job_root_dir = config['jobs']['root_directory']
|
||||||
|
|
||||||
|
if not os.path.exists(job_root_dir):
|
||||||
|
return []
|
||||||
|
|
||||||
|
jobs = list()
|
||||||
|
for folder in os.listdir(job_root_dir):
|
||||||
|
job_data = get_job_data(folder, config)
|
||||||
|
jobs.append(job_data)
|
||||||
|
return jobs
|
||||||
|
|
||||||
|
|
||||||
|
def is_job(job_name: str, config: dict) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a job exists.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
:return: True if the job exists, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
get_job_data(job_name, config)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_job(job_name: str, config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Create a job in the file system.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
"""
|
||||||
|
# check, if job root directory exists
|
||||||
|
if not os.path.exists(config['jobs']['root_directory']):
|
||||||
|
os.makedirs(config['jobs']['root_directory'])
|
||||||
|
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
|
||||||
|
if os.path.exists(job_dir):
|
||||||
|
raise ValueError(f'Job {job_name} already exists')
|
||||||
|
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
os.mkdir(job_dir)
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def rename_job(job_name: str, new_job_name: str, config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Rename a job in the file system.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param new_job_name: new name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
"""
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
new_job_dir = os.path.join(config['jobs']['root_directory'], new_job_name)
|
||||||
|
|
||||||
|
if not os.path.exists(job_dir):
|
||||||
|
raise ValueError(f'Job {job_name} does not exist')
|
||||||
|
|
||||||
|
if os.path.exists(new_job_dir):
|
||||||
|
raise ValueError(f'Job {new_job_name} already exists')
|
||||||
|
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
os.rename(job_dir, new_job_dir)
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_job(job_name: str, config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Remove a job from the file system.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
"""
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
|
||||||
|
if not os.path.exists(job_dir):
|
||||||
|
raise ValueError(f'Job {job_name} does not exist')
|
||||||
|
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
# remove the directory
|
||||||
|
shutil.rmtree(job_dir)
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def get_processing(job_name: str, config: dict) -> bool:
|
||||||
|
"""
|
||||||
|
Get the processing status of a job.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
:return: True if the job is processing, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
processing_file = os.path.join(job_dir, '_PROCESSING')
|
||||||
|
|
||||||
|
return os.path.exists(processing_file)
|
||||||
|
|
||||||
|
|
||||||
|
def set_processing(job_name: str, config: dict, processing: bool) -> None:
|
||||||
|
"""
|
||||||
|
Set the processing status of a job.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
:param processing: processing status
|
||||||
|
"""
|
||||||
|
|
||||||
|
lock.acquire()
|
||||||
|
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
processing_file = os.path.join(job_dir, '_PROCESSING')
|
||||||
|
|
||||||
|
if processing:
|
||||||
|
open(processing_file, 'w').close()
|
||||||
|
else:
|
||||||
|
os.remove(processing_file)
|
||||||
|
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def get_progress(job_name: str, config: dict) -> int:
|
||||||
|
"""
|
||||||
|
Get the progress of a job.
|
||||||
|
:param job_name: name of the job
|
||||||
|
:param config: app configuration
|
||||||
|
:return: progress of the job
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_dir = os.path.join(config['jobs']['root_directory'], job_name, chunk_input_subdir)
|
||||||
|
output_dir = os.path.join(config['jobs']['root_directory'], job_name, chunk_output_subdir)
|
||||||
|
|
||||||
|
num_input_files = len(os.listdir(input_dir))
|
||||||
|
num_output_files = len(os.listdir(output_dir))
|
||||||
|
|
||||||
|
if num_input_files == 0 or num_output_files == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if num_output_files >= num_input_files:
|
||||||
|
return 100
|
||||||
|
|
||||||
|
return int(num_output_files / num_input_files * 100)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import whisper
|
||||||
|
|
||||||
|
|
||||||
|
# clojure for model
|
||||||
|
def model_loader() -> callable:
|
||||||
|
"""
|
||||||
|
Clojure for loading the model
|
||||||
|
:return: function that loads the model
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = None
|
||||||
|
|
||||||
|
def load_model(config: dict):
|
||||||
|
"""
|
||||||
|
Load the model
|
||||||
|
:return: model
|
||||||
|
"""
|
||||||
|
nonlocal model
|
||||||
|
|
||||||
|
if model is not None:
|
||||||
|
return model
|
||||||
|
else:
|
||||||
|
model_type = config['model']['type']
|
||||||
|
model = whisper.load_model(model_type)
|
||||||
|
return model
|
||||||
|
|
||||||
|
return load_model
|
||||||
|
|
||||||
|
|
||||||
|
load_model = model_loader()
|
||||||
|
|
||||||
|
|
||||||
|
def transcribe_audio(audio_file_path: str,
|
||||||
|
output_file_path: str,
|
||||||
|
config: dict) -> str:
|
||||||
|
"""
|
||||||
|
Transcribe an audio file with openai whisper
|
||||||
|
:param audio_file_path: filepath of the audio file
|
||||||
|
:param output_file_path: filepath of the output file
|
||||||
|
:param config: app config
|
||||||
|
:return: transcribed text
|
||||||
|
"""
|
||||||
|
|
||||||
|
# load the model
|
||||||
|
model = load_model(config)
|
||||||
|
|
||||||
|
# move model to desired device
|
||||||
|
desired_device = config['model']['device']
|
||||||
|
model.to(desired_device)
|
||||||
|
|
||||||
|
result = model.transcribe(audio_file_path)
|
||||||
|
result_text = result['text']
|
||||||
|
|
||||||
|
# write the result to a text file
|
||||||
|
with open(output_file_path, 'w') as result_file:
|
||||||
|
result_file.write(result["text"])
|
||||||
|
return result_text
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import concurrent.futures
|
||||||
|
import os
|
||||||
|
from multiprocessing import Lock
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
|
||||||
|
import job_utils
|
||||||
|
from utils import get_logger
|
||||||
|
from audio_processing import split_audio, convert_to_wav
|
||||||
|
from job_utils import is_job, remove_job, create_job, get_processing, set_processing
|
||||||
|
from transcription import transcribe_audio
|
||||||
|
|
||||||
|
job_update_lock = Lock()
|
||||||
|
default_input_audio_file_name = 'input_audio'
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# define a global executor for non-blocking transcription jobs
|
||||||
|
executor = ProcessPoolExecutor()
|
||||||
|
|
||||||
|
|
||||||
|
def transcribe(audio_file_path: str,
|
||||||
|
config: dict,
|
||||||
|
blocking: bool = True,
|
||||||
|
overwrite: bool = False,
|
||||||
|
executor: concurrent.futures.Executor = executor) -> str:
|
||||||
|
"""
|
||||||
|
Transcribe an audio file.
|
||||||
|
:param audio_file_path: path to the audio file
|
||||||
|
:param config: app configuration
|
||||||
|
:param blocking: block until the job is finished
|
||||||
|
:param overwrite: overwrite existing job
|
||||||
|
:param executor: executor for non-blocking transcription
|
||||||
|
:return: name of the created job
|
||||||
|
"""
|
||||||
|
|
||||||
|
job_name = os.path.basename(audio_file_path).split('.')[0]
|
||||||
|
job_dir = os.path.join(config['jobs']['root_directory'], job_name)
|
||||||
|
|
||||||
|
logger.info(f"Transcribing {audio_file_path} to {job_dir}")
|
||||||
|
|
||||||
|
# check, if a job with the same audio file already exists
|
||||||
|
job_already_exists = is_job(job_name, config)
|
||||||
|
|
||||||
|
if job_already_exists and not overwrite:
|
||||||
|
logger.error(f'Job for {audio_file_path} already exists and overwrite is not set')
|
||||||
|
raise ValueError(f'Job for {audio_file_path} already exists')
|
||||||
|
|
||||||
|
# remove, if overwrite is set
|
||||||
|
if job_already_exists and overwrite:
|
||||||
|
logger.info(f'Removing existing job {job_name} since overwrite is set')
|
||||||
|
remove_job(job_name, config)
|
||||||
|
|
||||||
|
# create a new base job
|
||||||
|
logger.info(f'Creating job {job_name}')
|
||||||
|
create_job(job_name, config)
|
||||||
|
|
||||||
|
# copy the audio file to the job directory
|
||||||
|
logger.info(f'Copying audio file {audio_file_path} to {job_dir}')
|
||||||
|
input_audio_file_name = os.path.basename(audio_file_path).split('.')[0]
|
||||||
|
input_audio_file_type = os.path.splitext(audio_file_path)[1]
|
||||||
|
audio_file_destination = os.path.join(job_dir, f"{input_audio_file_name}{input_audio_file_type}")
|
||||||
|
os.system(f'cp {audio_file_path} {audio_file_destination}')
|
||||||
|
|
||||||
|
# convert the audio file to wav, if it is not already
|
||||||
|
if not audio_file_destination.endswith('.wav'):
|
||||||
|
logger.info(f'Converting audio file {audio_file_destination} to wav')
|
||||||
|
convert_to_wav(audio_file_path,
|
||||||
|
os.path.join(job_dir, f"{default_input_audio_file_name}.wav"))
|
||||||
|
|
||||||
|
# run the transcription job
|
||||||
|
logger.info(f'Running transcription job {job_name}')
|
||||||
|
|
||||||
|
if blocking:
|
||||||
|
logger.info(f'Starting blocking transcription job {job_name}')
|
||||||
|
run_transcription_job(job_name, config)
|
||||||
|
else:
|
||||||
|
logger.info(f'Starting async transcription job {job_name}')
|
||||||
|
executor.submit(run_transcription_job, job_name, config, 100)
|
||||||
|
logger.info(f'Started async transcription job {job_name}')
|
||||||
|
|
||||||
|
return job_name
|
||||||
|
|
||||||
|
|
||||||
|
def run_transcription_job(job_name: str,
|
||||||
|
config: dict,
|
||||||
|
num_chunks=100) -> None:
|
||||||
|
"""
|
||||||
|
Start a transcription job.
|
||||||
|
:param job_name: id of the job
|
||||||
|
:param config: app configuration
|
||||||
|
:param num_chunks: number of chunks to split the audio file into
|
||||||
|
"""
|
||||||
|
|
||||||
|
# check if the job is already running
|
||||||
|
if get_processing(job_name, config):
|
||||||
|
raise ValueError(f'Job {job_name} is already running')
|
||||||
|
|
||||||
|
# set the job to processing
|
||||||
|
set_processing(job_name, config, processing=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# split the audio file into chunks
|
||||||
|
logger.info(f'Splitting audio file into chunks for job {job_name}')
|
||||||
|
audio_file_path = os.path.join(config['jobs']['root_directory'], job_name,
|
||||||
|
f"{default_input_audio_file_name}.wav")
|
||||||
|
audio_chunk_folder = os.path.join(config['jobs']['root_directory'], job_name, job_utils.chunk_input_subdir)
|
||||||
|
audio_chunks = split_audio(audio_file_path,
|
||||||
|
audio_chunk_folder,
|
||||||
|
num_chunks,
|
||||||
|
config["audio"]["chunking"]["min_silence_level"],
|
||||||
|
config["audio"]["chunking"]["min_silence_length"],
|
||||||
|
config["audio"]["chunking"]["ms_silence_to_keep"])
|
||||||
|
|
||||||
|
# transcribe the audio chunks
|
||||||
|
logger.info(f'Transcribing {len(audio_chunks)} audio chunks for job {job_name}')
|
||||||
|
# create chunk output folder, if not done yet
|
||||||
|
chunk_output_folder = os.path.join(config['jobs']['root_directory'], job_name, job_utils.chunk_output_subdir)
|
||||||
|
if not os.path.exists(chunk_output_folder):
|
||||||
|
os.makedirs(chunk_output_folder)
|
||||||
|
for i, audio_chunk in enumerate(audio_chunks):
|
||||||
|
# transcribe the audio
|
||||||
|
logger.info(f'Transcribing audio chunk {i + 1} of {len(audio_chunks)} for job {job_name}')
|
||||||
|
output_file_path = os.path.join(chunk_output_folder, f'output_{i}.txt')
|
||||||
|
transcribe_audio(audio_chunk,
|
||||||
|
output_file_path,
|
||||||
|
config)
|
||||||
|
|
||||||
|
# combine the output text chunks
|
||||||
|
logger.info(f'Combining output text chunks for job {job_name}')
|
||||||
|
output_chunk_folder = os.path.join(config['jobs']['root_directory'], job_name, job_utils.chunk_output_subdir)
|
||||||
|
output_text = ''
|
||||||
|
for text_chunk_file in sorted(os.listdir(output_chunk_folder)):
|
||||||
|
with open(os.path.join(output_chunk_folder, text_chunk_file), 'r') as text_chunk:
|
||||||
|
output_text += text_chunk.read() + ' '
|
||||||
|
|
||||||
|
# remove trailing space
|
||||||
|
output_text = output_text.strip()
|
||||||
|
|
||||||
|
# write the output to a file
|
||||||
|
logger.info(f'Writing output text to file for job {job_name}')
|
||||||
|
output_file = os.path.join(config['jobs']['root_directory'], job_name, 'output.txt')
|
||||||
|
with open(output_file, 'w') as f:
|
||||||
|
f.write(output_text)
|
||||||
|
|
||||||
|
# set the job to not processing
|
||||||
|
set_processing(job_name, config, processing=False)
|
||||||
|
|
||||||
|
logger.info(f'Job {job_name} completed')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# set the job to not processing
|
||||||
|
set_processing(job_name, config, processing=False)
|
||||||
|
raise e
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from logging import Logger
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(name: str) -> Logger:
|
||||||
|
"""
|
||||||
|
Get a logger instance.
|
||||||
|
:param name: name of the logger
|
||||||
|
:return: logger instance
|
||||||
|
"""
|
||||||
|
logger = logging.getLogger(name)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# create a file handler
|
||||||
|
file_handler = logging.FileHandler(f'{name}.log')
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# create stdout handler
|
||||||
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stdout_handler.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# create a logging format
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
stdout_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# add the handlers to the logger
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(stdout_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
|||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
from webapp.task_list import render_task_list
|
||||||
|
|
||||||
|
|
||||||
|
def render_home(container,
|
||||||
|
app_config: dict,
|
||||||
|
authenticator):
|
||||||
|
"""
|
||||||
|
Render the home page
|
||||||
|
:param container: container object to place the content in
|
||||||
|
:param app_config: app configuration
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
with container.container():
|
||||||
|
st.title('Annescribe Home')
|
||||||
|
st.write("Welcome to AnneScribe!")
|
||||||
|
authenticator.logout()
|
||||||
|
|
||||||
|
# render task list
|
||||||
|
render_task_list(container, app_config)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import streamlit_authenticator as st_auth
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
from functools import partial
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from streamlit_autorefresh import st_autorefresh
|
||||||
|
|
||||||
|
|
||||||
|
from transcription.job_utils import get_existing_jobs
|
||||||
|
|
||||||
|
def show_results(job_data: dict):
|
||||||
|
"""
|
||||||
|
Show the results of a job
|
||||||
|
:param job_data: job data
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if job_data is None:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
st.session_state[f"results_button_{job_data['name']}"] = True
|
||||||
|
st.write(f"Transcription Results for job {job_data['name']}")
|
||||||
|
st.write(f"Output: {job_data['output']}")
|
||||||
|
|
||||||
|
def hide_results(job_data: dict):
|
||||||
|
"""
|
||||||
|
Hide the results of a job
|
||||||
|
:param job_data: job data
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
st.session_state[f"results_button_{job_data['name']}"] = False
|
||||||
|
|
||||||
|
def results_button(job_data: dict, app_config: dict):
|
||||||
|
"""
|
||||||
|
Show the results of a job
|
||||||
|
:param job_data: job data
|
||||||
|
:param app_config: app configuration
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
is_toggled = st.session_state.get(f"results_button_{job_data['name']}")
|
||||||
|
other_toggled = any([st.session_state.get(f"results_button_{job['name']}") for job in get_existing_jobs(app_config)])
|
||||||
|
|
||||||
|
if other_toggled:
|
||||||
|
for job in get_existing_jobs(app_config):
|
||||||
|
st.session_state[f"results_button_{job['name']}"] = False
|
||||||
|
if is_toggled:
|
||||||
|
return st.button("Show Results", on_click=partial(show_results, job_data))
|
||||||
|
else:
|
||||||
|
return st.button("Hide Results", on_click=partial(hide_results, job_data))
|
||||||
|
|
||||||
|
|
||||||
|
def render_task_list(container, app_config: dict):
|
||||||
|
"""
|
||||||
|
Render the task list page
|
||||||
|
:param container: container object to place the content in
|
||||||
|
:param app_config: app configuration
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
# disable all results
|
||||||
|
for job in get_existing_jobs(app_config):
|
||||||
|
st.session_state[f"results_button_{job['name']}"] = False
|
||||||
|
|
||||||
|
with container.container():
|
||||||
|
st.subheader('Task List')
|
||||||
|
|
||||||
|
existing_jobs = get_existing_jobs(app_config)
|
||||||
|
|
||||||
|
column_config = [
|
||||||
|
{
|
||||||
|
"name": "Name",
|
||||||
|
"render_func": lambda x: st.markdown(f"**{x['name']}**")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Progress",
|
||||||
|
"render_func": lambda x: st.progress(x["progress"]) if x["progress"] < 100 else st.markdown(" -- ")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Status",
|
||||||
|
"render_func": lambda x: st.markdown("Completed") if x["completed"] else st.markdown("Processing" if x["processing"] else "Pending")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"render_func": lambda x: results_button(x, app_config)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(existing_jobs) == 0:
|
||||||
|
st.write("No tasks available.")
|
||||||
|
else:
|
||||||
|
for i, job in enumerate(existing_jobs):
|
||||||
|
cols = st.columns(len(column_config))
|
||||||
|
for j, col in enumerate(column_config):
|
||||||
|
cols[j].write(col["name"])
|
||||||
|
with cols[j]:
|
||||||
|
col["render_func"](job)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import streamlit_authenticator as st_auth
|
||||||
|
from streamlit_autorefresh import st_autorefresh
|
||||||
|
|
||||||
|
from webapp.home import render_home
|
||||||
|
|
||||||
|
CONFIG_FILE = os.environ.get('CONFIG_FILE', "config.yml")
|
||||||
|
|
||||||
|
if CONFIG_FILE is None:
|
||||||
|
raise ValueError('CONFIG_FILE environment variable is not set')
|
||||||
|
elif not os.path.exists(CONFIG_FILE):
|
||||||
|
raise ValueError(f'CONFIG_FILE {CONFIG_FILE} does not exist')
|
||||||
|
|
||||||
|
with open(CONFIG_FILE, 'r') as config_file:
|
||||||
|
config = yaml.safe_load(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)
|
||||||
|
|
||||||
|
authenticator = st_auth.Authenticate(
|
||||||
|
auth_config["credentials"],
|
||||||
|
auth_config["cookie"]["name"],
|
||||||
|
auth_config["cookie"]["key"],
|
||||||
|
auth_config["cookie"]["expiry_days"],
|
||||||
|
)
|
||||||
|
|
||||||
|
content_wrapper = st.empty()
|
||||||
|
|
||||||
|
|
||||||
|
def render_login():
|
||||||
|
with content_wrapper.container():
|
||||||
|
st.title('Annescribe Login')
|
||||||
|
st.write("Please login with your credentials.")
|
||||||
|
authenticator.login(callback=st.session_state.clear())
|
||||||
|
|
||||||
|
|
||||||
|
if "authentication_status" not in st.session_state.keys():
|
||||||
|
authentication_status = None
|
||||||
|
st.session_state["authentication_status"] = authentication_status
|
||||||
|
else:
|
||||||
|
authentication_status = st.session_state.get("authentication_status")
|
||||||
|
username = st.session_state.get("username")
|
||||||
|
print(authentication_status)
|
||||||
|
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()
|
||||||
|
else:
|
||||||
|
# make sure to remove cookie
|
||||||
|
render_login()
|
||||||
|
|
||||||
|
|
||||||
|
# st_autorefresh(interval=2000, )
|
||||||
Reference in New Issue
Block a user