HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //opt/whisper-gui/main.py
import gradio as gr
import whisperx
import subprocess, os, gc, argparse, shutil, inspect
import soundfile as sf
import torch, re, json, time
from datetime import datetime
from scripts.whisper_model import load_custom_model, LANG_CODES
from typing import Optional, Tuple, Callable
from scripts.config_io import read_config_value

ALIGN_LANGS = ["en", "fr", "de", "es", "it", "ja", "zh", "nl", "uk", "pt", "ar", "cs", "ru", "pl", "hu", "fi", "fa", "el", "tr", "da", "he", "vi", "ko", "ur", "te", "hi", "ca", "ml", "no", "nn"]

# global variables
g_model = None
g_model_a = None
g_model_a_metadata = None
g_params = {}

def list_models():
	models_dir = os.path.join("models", "custom")
	if not os.path.exists(models_dir):
		# create the directory if it doesn"t exist
		os.makedirs(models_dir)
	subdirs = os.listdir(models_dir)
	models = [s for s in subdirs if s.startswith("models--")]
	models = [s.replace("models--", "").replace("--", "/") for s in models]
	return models

def create_save_folder(save_root):
	# Get the current date and create the date folder
	current_date = datetime.now().strftime("%Y-%m-%d")
	date_dir = os.path.join(save_root, current_date)
	
	# Create the directory if it doesn"t exist
	if not os.path.exists(date_dir):
		os.makedirs(date_dir)

	# Determine the highest counter already used in folder names
	highest_counter = -1
	for existing_folder in os.listdir(date_dir):
		match = re.match("(\d+)", existing_folder)
		if match:
			highest_counter = max(highest_counter, int(match.group(1)))
	
	# Determine the next available folder with counter
	next_counter = highest_counter + 1
	save_dir = os.path.join(date_dir, f"{next_counter:04d}")  # Format as four digits

	# Create the counter folder
	os.makedirs(save_dir)
	return save_dir

def save_audio_to_mp3(audio_tuple, save_dir):
	rate, y = audio_tuple
	if len(y.shape) == 2:
		y = y.T[0]  # If stereo, take one channel
	audio_path = os.path.join(save_dir, "audio.mp3")
	wav_path = os.path.join("temp", "temp_audio.wav")
	sf.write(wav_path, y, rate)
	subprocess.run(["ffmpeg", "-i", wav_path, audio_path])  # Convert WAV to MP3
	os.remove(wav_path)  # Remove temporary WAV file
	return audio_path

def save_transcription_to_txt(text_str, save_dir, name="transcription.txt"):
	text_path = os.path.join(save_dir, name)
	print(f"Saving transcription to {text_path}...")
	with open(text_path, "w", encoding="utf-8") as f:
		f.write(text_str)
	return text_path

def save_alignments_to_json(alignment_dict, save_dir, name="alignments.json"):
	json_path = os.path.join(save_dir, name)
	print(f"Saving alignments to {json_path}...")
	with open(json_path, "w", encoding="utf-8") as f:
		json.dump(alignment_dict, f, indent=4)
	return json_path

def save_alignments_to_srt(subtitles_dict, save_dir, name="subtitles.srt"):
	srt_path = os.path.join(save_dir, name)
	print(f"Saving subtitles to {srt_path}...")
	with open(srt_path, "w", encoding="utf-8") as f:
		for sub in subtitles_dict:
			f.write(f"{sub['number']}\n{sub['start']} --> {sub['end']}\n{sub['text']}\n\n")
	return srt_path

def load_and_save_audio(audio_path, micro_audio, save_audio, save_dir):
	if micro_audio:
		print("Saving micro audio...")
		audio_path = save_audio_to_mp3(micro_audio, save_dir if save_audio else "temp")
	elif save_audio:
		print("Making a copy of the audio...")
		original_name = os.path.basename(audio_path)
		shutil.copy(audio_path, os.path.join(save_dir, original_name if g_params["preserve_name"] else "audio.mp3"))

	print("Loading audio...")
	audio = whisperx.load_audio(audio_path)
	
	if micro_audio and not save_audio:
		os.remove(audio_path)
	
	return audio

def float_to_time_str(time_float):
	seconds_total = int(time_float)
	hours = seconds_total // 3600
	minutes = (seconds_total % 3600) // 60
	seconds = seconds_total % 60
	if hours > 0:
		time_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
	else:
		time_str = f"{minutes:02d}:{seconds:02d}"
	return time_str

def format_alignments(alignments):
	formatted_transcription = []
	for segment in alignments["segments"]:
		start_time = float_to_time_str(segment["start"])
		end_time = float_to_time_str(segment["end"])
		text = segment["text"].strip()
		# Format the line as "start_time - end_time: text"
		formatted_line = f"{start_time} - {end_time}: {text}"
		formatted_transcription.append(formatted_line)
	return "\n\n".join(formatted_transcription)

def alignments2subtitles(subtitles, max_line_length=40):
	def sec2timesrt(sec):
		# Convert seconds to HH:MM:SS,mmm format
		hours, remainder = divmod(sec, 3600)
		minutes, seconds = divmod(remainder, 60)
		milliseconds = int((seconds - int(seconds)) * 1000)
		return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02},{milliseconds:03}"

	def split_text(text, max_line_length):
		# Split text into multiple lines based on max line length
		lines = []
		while text:
			if len(text) <= max_line_length:
				lines.append(text)
				break
			else:
				# Find the nearest space before max_line_length
				split_at = text.rfind(" ", 0, max_line_length + 1)
				if split_at == -1:  # No spaces found, force split
					split_at = max_line_length
				lines.append(text[:split_at])
				text = text[split_at+1:]  # Skip the space
		return "\n".join(lines)

	converted_subtitles = []
	for index, sub in enumerate(subtitles, start=1):
		converted_sub = {
			"number": index,
			"start": sec2timesrt(sub["start"]),
			"end": sec2timesrt(sub["end"]),
			"text": split_text(sub["text"], max_line_length)
		}
		converted_subtitles.append(converted_sub)

	return converted_subtitles

def release_whisper():
	global g_model, g_params
	del g_model
	if g_params.get("device", None) == "cuda": torch.cuda.empty_cache()
	else: gc.collect()
	g_model = None
	print("Whisper model released from memory")

def release_align():
	global g_model_a, g_params
	del g_model_a
	if g_params.get("device", None) == "cuda": torch.cuda.empty_cache()
	else: gc.collect()
	g_model_a = None
	print("Alignment model released from memory")

def release_memory_models():
	global g_model, g_model_a, g_params
	del g_model, g_model_a
	if g_params.get("device", None) == "cuda": torch.cuda.empty_cache()
	else: gc.collect()
	g_model = None
	g_model_a = None
	print("Models released from memory")

def get_args_str(func: Callable) -> list:
	return list(inspect.signature(func).parameters)

def get_params(func: Callable, values: list) -> dict:
	keys = get_args_str(func)
	return {k: values[k] for k in keys}

def same_params(params1: dict, params2: dict, *args):
	if args:
		return all(params1.get(arg, None) == params2.get(arg, None) for arg in args)
	else:
		return params1 == params2

def transcribe_whisperx(
		model_name: str,
		audio_path: str,
		micro_audio: tuple,
		device: str,
		batch_size: int,
		compute_type: str,
		language: str,
		chunk_size: int,
		beam_size: int,
		release_memory: bool,
		save_root: Optional[str],
		save_audio: bool,
		save_transcription: bool,
		save_alignments: bool,
		save_in_subfolder: bool,
		preserve_name: bool,
		alignments_format: str
	) -> Tuple[str, str, str, str]:

	print("Inputs received. Starting...")
	params = get_params(transcribe_whisperx, locals())
	global g_model, g_params

	if not same_params(params, g_params, "language"):
		print("Language changed. Releasing previous alignment model from memory...")
		release_align()

	if not same_params(params, g_params, "model_name", "device", "compute_type", "beam_size") or g_model is None:
		if g_model is not None:
			print("Parameters changed. Releasing previous whisper model from memory...")
			release_whisper()
		print("Loading model...")
		g_model = whisperx.load_model(model_name, device, compute_type=compute_type, asr_options={"beam_size": beam_size}, download_root="models/whisperx")
	g_params = params

	return _transcribe()

def transcribe_custom(
		model_name: str,
		audio_path: str,
		micro_audio: tuple,
		device: str,
		batch_size: int,
		compute_type: str,
		language: str,
		chunk_size: int,
		beam_size: int,
		release_memory: bool,
		save_root: Optional[str],
		save_audio: bool,
		save_transcription: bool,
		save_alignments: bool,
		save_in_subfolder: bool,
		preserve_name: bool,
		alignments_format: str
	) -> Tuple[str, str, str, str]:

	print("Inputs received. Starting...")
	params = get_params(transcribe_custom, locals())
	global g_model, g_params

	if not same_params(params, g_params, "language", "device"):
		print("Language changed. Releasing previous alignment model from memory...")
		release_align()

	if not same_params(params, g_params, "model_name", "device", "compute_type", "beam_size") or g_model is None:
		if g_model is not None:
			print("Parameters changed. Releasing previous models from memory...")
			release_memory_models()
		print("Loading model...")
		g_model = load_custom_model(model_name, device, compute_type=compute_type, beam_size=beam_size, download_root="models/custom")
	g_params = params

	return _transcribe()

def _transcribe() -> Tuple[str, str]:
	global g_model, g_model_a, g_model_a_metadata, g_params
	# Create save folder
	save_dir = None
	if not os.path.exists("temp"):
		os.makedirs("temp")
	if g_params["save_audio"] or g_params["save_transcription"] or g_params["save_alignments"]:
		if g_params["save_root"] is not None and g_params["save_root"] != "":
			save_root = g_params["save_root"]
		else:
			save_root = "outputs"
		if g_params["save_in_subfolder"]:
			save_dir = create_save_folder(save_root)
		else:
			save_dir = save_root

	# Load (and save) audio
	audio = load_and_save_audio(g_params["audio_path"], g_params["micro_audio"], g_params["save_audio"], save_dir)

	# Transcription
	if g_params["language"] == "auto": 
		language = None
	else:
		language = g_params["language"]
	time_transcribe = time.time()
	print("Starting transcription...")
	result = g_model.transcribe(audio, batch_size=g_params["batch_size"], language=language, chunk_size=g_params["chunk_size"], print_progress=True)
	if "time" in result.keys():
		time_transcribe = result["time"]
	else:
		time_transcribe = time.time() - time_transcribe
	joined_text = " ".join([segment["text"].strip() for segment in result["segments"]])
	if g_params["save_transcription"]:
		if g_params["preserve_name"]:
			audio_name = os.path.basename(g_params["audio_path"]).split(".")[0]
			save_name = f"{audio_name}_transcription.txt"
		else:
			save_name = "transcription.txt"
		save_transcription_to_txt(joined_text, save_dir, save_name)

	if g_params["release_memory"]:
		release_whisper()

	# Word-level alignment
	lang_used = result["language"]
	if lang_used not in ALIGN_LANGS:
		print(f"WARNING! Language {lang_used} not supported for alignment. Using English instead. Results may be inaccurate.")
		lang_used = "en"
	if g_model_a is None:
		print("Loading alignment model...")
		g_model_a, g_model_a_metadata = whisperx.load_align_model(language_code=lang_used, device=g_params["device"], model_dir="models/alignment")
	print("Aligning...")
	time_align = time.time()
	aligned_result = whisperx.align(result["segments"], g_model_a, g_model_a_metadata, audio, g_params["device"], return_char_alignments=False)
	time_align = time.time() - time_align
	if g_params["save_alignments"]:
		align_format = g_params["alignments_format"].lower()
		if g_params["preserve_name"]:
			audio_name = os.path.basename(g_params["audio_path"]).split(".")[0]
			save_name = f"{audio_name}_timestamps." + align_format
		else:
			save_name = "timestamps." + align_format
		if align_format == "json":
			save_alignments_to_json(aligned_result, save_dir, save_name)
		elif align_format == "srt":
			subtitles = alignments2subtitles(aligned_result["segments"], max_line_length=50)
			save_alignments_to_srt(subtitles, save_dir, save_name)
	if g_params["release_memory"]:
		release_align()
	print("Done!")
	if not os.listdir("temp") and os.path.exists("temp"):
		# Remove temp folder if empty
		os.rmdir("temp")
	# Return the transcription and sentence-level alignments
	return joined_text, format_alignments(aligned_result), f"{round(time_transcribe, 3)}s", f"{round(time_align, 3)}s"


# Prepare interface data
whisperx_models = ["large-v3", "large-v2", "large-v1", "medium", "small", "base", "tiny", "medium.en", "small.en", "base.en", "tiny.en"]
custom_models = list_models()
whisperx_langs = ["auto", "en", "es", "fr", "de", "it", "ja", "zh", "nl", "uk", "pt"]
custom_langs = ["auto"] + list(LANG_CODES.keys())
release_whisper_message = "When changed, requires the Whisper model to reload."
release_align_message = "When changed, requires the alignment model to reload."
release_both_message = "When changed, requires both models to reload."

# Read config
gpu_support, error = read_config_value("gpu_support")
if gpu_support:
	device = "cuda"
	device_interactive = True
	device_message = ""
else:
	device = "cpu"
	device_interactive = False
	if gpu_support is None:
		device_message = "If you don\"t have a GPU, select \"cpu\".\n"
	else:
		device_message = "GPU support is disabled in the config file.\n"

with gr.Blocks(title="Whisper GUI") as demo:
	gr.Markdown("""# Whisper GUI
A simple interface to transcribe audio files using the Whisper model""")
	with gr.Tab("Faster Whisper"):
		with gr.Row():
			with gr.Column():
				model_select = gr.Dropdown(whisperx_models, value="base", label="Load WhisperX Model", info=release_whisper_message)
				with gr.Group():
					audio_upload = gr.Audio(sources=["upload"], type="filepath", label="Upload Audio File")
					audio_record = gr.Audio(sources=["microphone"], type="numpy", label="or Record Audio (If both are provided, only microphone audio will be used)")
					save_audio = gr.Checkbox(value=False, label="Save Audio", info="Save copy of audio to \"Save Path\" folder.")
				gr.Examples(examples=["examples/coffe_break_example.mp3"], inputs=audio_upload)
				with gr.Accordion(label="Advanced Options", open=False):
					language_select = gr.Dropdown(whisperx_langs, value = "auto", label="Language", info="Select the language of the audio file. Select \"auto\" to automatically detect it. "+release_align_message)
					device_select = gr.Radio(["cuda", "cpu"], value = device, label="Device", info=device_message+release_both_message, interactive=device_interactive)
					with gr.Group():
						with gr.Row():
							save_transcription = gr.Checkbox(value=True, label="Save Transcription")
							save_alignments = gr.Checkbox(value=True, label="Save Timestamps")
						save_root = gr.Textbox(label="Save Path", placeholder="outputs", lines=1)
						save_in_subfolder = gr.Checkbox(value=True, label="Save in Subfolder", info="Save files in a subfolder \"YYYY-MM-DD/NNNN/\" in the \"Save Path\" folder. CAUTION: if unchecked, files may be overwritten.")
						preserve_name = gr.Checkbox(value=False, label="Preserve Name", info="Preserve the original name of the audio file when saving. E.g. \"<audio_name>_transcription.txt\". Only works for uploaded audio.")
						alignments_format = gr.Radio(["JSON", "SRT"], value="JSON", label="Alignments Format", interactive=True)
					gr.Markdown("""### Optimizations""")
					compute_type_select = gr.Radio(["int8", "float16", "float32"], value = "int8", label="Compute Type", info="int8 is fastest and requires less memory. float32 is more accurate (Your device may not support some data types). "+release_whisper_message)
					batch_size_slider = gr.Slider(1, 128, value = 1, step=1, label="Batch Size", info="Larger batch sizes may be faster but require more memory.")
					chunk_size_slider = gr.Slider(1, 80, value = 20, step=1, label="Chunk Size", info="Larger chunk sizes may be faster but require more memory.")
					beam_size_slider = gr.Slider(1, 100, value = 5, step=1, label="Beam Size", info="Larger beam sizes may be more accurate but require more memory and may decrease speed. "+release_whisper_message)
					release_memory_checkbox = gr.Checkbox(label="Release Memory", value=True, info="Release Whisper model from memory before loading alignment model. Then release alignment model. Prevents having both models in memory at the same time.")
				submit_button = gr.Button(value="Start Transcription")
			with gr.Column():
				transcription_output = gr.Textbox(label="Transcription", lines=15)
				alignments_output = gr.Textbox(label="Timestamps", lines=15)
				with gr.Row():
					time_transcribe = gr.Textbox(label="Transcription Time", info="Including language detection (if Language = \"auto\")", lines=1)
					time_align = gr.Textbox(label="Alignment Time", lines=1)
				release_memory_button = gr.Button(value="Release Models from Memory")

	with gr.Tab("Custom model"):
		with gr.Row():
			with gr.Column():
				with gr.Group():
					model_select2 = gr.Dropdown(custom_models, value=None, label="Upload Local Model  or  Download a Model from HuggingFace", allow_custom_value=True, info=release_whisper_message)
				with gr.Group():
					audio_upload2 = gr.Audio(sources=["upload"], type="filepath", label="Upload Audio File")
					audio_record2 = gr.Audio(sources=["microphone"], type="numpy", label="or Record Audio (If both are provided, only microphone audio will be used)")
					save_audio2 = gr.Checkbox(value=False, label="Save Audio", info="Save copy of audio to \"Save Path\" folder.")
				gr.Examples(examples=["examples/coffe_break_example.mp3"], inputs=audio_upload2)
				with gr.Accordion(label="Advanced Options", open=False):
					language_select2 = gr.Dropdown(custom_langs, value = "auto", label="Language", info="Select the language of the audio file. Select \"auto\" to automatically detect it. "+release_align_message)
					device_select2 = gr.Radio(["cuda", "cpu"], value = device, label="Device", info=device_message+release_both_message, interactive=device_interactive)
					with gr.Group():
						with gr.Row():
							save_transcription2 = gr.Checkbox(value=True, label="Save Transcription")
							save_alignments2 = gr.Checkbox(value=True, label="Save Timestamps")
						save_root2 = gr.Textbox(label="Save Path", placeholder="outputs", lines=1)
						save_in_subfolder2 = gr.Checkbox(value=True, label="Save in Subfolder", info="Save files in a subfolder \"YYYY-MM-DD/NNNN/\" in the \"Save Path\" folder. CAUTION: if unchecked, files may be overwritten.")
						preserve_name2 = gr.Checkbox(value=False, label="Preserve Name", info="Preserve the original name of the audio file when saving. E.g. \"<audio_name>_transcription.txt\". Only works for uploaded audio.")
						alignments_format2 = gr.Radio(["JSON", "SRT"], value="JSON", label="Alignments Format", interactive=True)
					gr.Markdown("""### Optimizations""")
					compute_type_select2 = gr.Radio(["float16", "float32"], value = "float16", label="Compute Type", info="float16 is faster and requires less memory. float32 is more accurate (Your device may not support some data types). "+release_whisper_message)
					batch_size_slider2 = gr.Slider(1, 128, value = 1, step=1, label="Batch Size", info="Larger batch sizes may be faster but require more memory.")
					chunk_size_slider2 = gr.Slider(1, 80, value = 20, step=1, label="Chunk Size", info="Larger chunk sizes may be faster but require more memory.")
					beam_size_slider2 = gr.Slider(1, 100, value = 5, step=1, label="Beam Size", info="Larger beam sizes may be more accurate but require more memory and may decrease speed. "+release_whisper_message)
					release_memory_checkbox2 = gr.Checkbox(label="Release Memory", value=True, info="Release Whisper model from memory before loading alignment model. Then release alignment model. Prevents having both models in memory at the same time.")
				submit_button2 = gr.Button(value="Start Transcription")
			with gr.Column():
				transcription_output2 = gr.Textbox(label="Transcription", lines=15)
				alignments_output2 = gr.Textbox(label="Timestamps", lines=15)
				with gr.Row():
					time_transcribe2 = gr.Textbox(label="Transcription Time", lines=1)
					time_align2 = gr.Textbox(label="Alignment Time", lines=1)
				release_memory_button2 = gr.Button(value="Release Models from Memory")

	
	submit_button.click(transcribe_whisperx,
						inputs=[model_select, audio_upload, audio_record, device_select, batch_size_slider, compute_type_select, language_select, chunk_size_slider, beam_size_slider, release_memory_checkbox, save_root, save_audio, save_transcription, save_alignments, save_in_subfolder, preserve_name, alignments_format],
						outputs=[transcription_output, alignments_output, time_transcribe, time_align])
	
	submit_button2.click(transcribe_custom,
						inputs=[model_select2, audio_upload2, audio_record2, device_select2, batch_size_slider2, compute_type_select2, language_select2, chunk_size_slider2, beam_size_slider2, release_memory_checkbox2, save_root2, save_audio2, save_transcription2, save_alignments2, save_in_subfolder2, preserve_name2, alignments_format2],
						outputs=[transcription_output2, alignments_output2, time_transcribe2, time_align2])
	
	release_memory_button.click(release_memory_models)
	release_memory_button2.click(release_memory_models)
	

if __name__ == "__main__":
	# Parse arguments
	parser = argparse.ArgumentParser(description="Transcribe audio files using WhisperX")
	parser.add_argument("--autolaunch", action="store_true", default=False, help="Launch the interface automatically in the default browser")
	parser.add_argument("--share", action="store_true", default=False, help="Create a share link to access the interface from another device")
	args = parser.parse_args()

	# Launch the interface
	print("Creating interface...")
	demo.launch(server_name='0.0.0.0', server_port=7979, inbrowser=args.autolaunch, share=args.share)