GetSubtitlesApp / streamlit_app.py
KaanGoker's picture
GetSubtitlesApp V1.0 - Demo
08c468e
import os
import sys
import time
from pathlib import Path
import threading
import streamlit as st
from PIL import Image
from uuid import uuid4
import transcribe_utils
def resource_path(rel_path: str) -> str:
base = getattr(sys, "_MEIPASS", os.path.dirname(__file__))
return os.path.join(base, rel_path)
st.set_page_config(
page_title="Get Subtitles",
page_icon=Image.open(resource_path("icon_assets/getsubtitles.png")),
layout="centered",
)
HIDE_UI = """
<style>
/* Hide Streamlit toolbar + hamburger + footer */
div[data-testid="stToolbar"] { display: none !important; }
#MainMenu { visibility: hidden; }
footer { visibility: hidden; }
header { visibility: hidden; }
/* 'Model' radio grubundaki 2. ve 3. seçenekleri hedefle */
div[data-testid="stRadio"][aria-label="Model"] > div > div:nth-child(2) label,
div[data-testid="stRadio"][aria-label="Model"] > div > div:nth-child(3) label {
color: #AAA; /* Metni gri yap */
pointer-events: none; /* Tıklanamaz yap */
cursor: not-allowed; /* 'İzin verilmiyor' imleci göster */
}
/* Radio butonunun kendisini de (noktayı) etkisiz hale getir */
div[data-testid="stRadio"][aria-label="Model"] > div > div:nth-child(2) input,
div[data-testid="stRadio"][aria-label="Model"] > div > div:nth-child(3) input {
pointer-events: none;
cursor: not-allowed;
}
</style>
"""
st.markdown(HIDE_UI, unsafe_allow_html=True)
st.title("Get Subtitles in SRT Format")
st.markdown("""
* The system will automatically detect the language of the uploaded video/audio.
* Once the file is loaded, please wait for the **"Create subtitles"** button to appear.
* If you have a large video, uploading only the audio is much faster.
* It works better with files without background music.
""")
st.markdown(
'<p style="color:red;font-size:0.9rem;">In this demo version, only the <b>Fast</b> model is available, and the upload limit is 500MB due to hardware limitations.</p>',
unsafe_allow_html=True
)
st.markdown(
"""
<p align="center">
<strong>
<a href="https://github.com/KaanGoker/GetSubtitlesApp/releases/tag/v1.0" target="_blank">
💻 Download for Windows
</a>
&nbsp;&nbsp; | &nbsp;&nbsp;
<a href="https://github.com/KaanGoker/GetSubtitlesApp" target="_blank">
🐙 View Project on GitHub
</a>
</strong>
</p>
""",
unsafe_allow_html=True
)
MODEL_LABELS = [
"**Fast** — Shorter time, lower accuracy.",
"**Balanced** — Longer time, better accuracy.",
"**Best** — Highest accuracy; slowest and needs a strong GPU.",
]
MODEL_MAP = {
MODEL_LABELS[0]: "fast",
MODEL_LABELS[1]: "balanced",
MODEL_LABELS[2]: "best",
}
MODEL_DISPLAY = {"fast": "Fast", "balanced": "Balanced", "best": "Best"}
LANGUAGE_NAMES = {
"af":"Afrikaans","am":"Amharic","ar":"Arabic","as":"Assamese","az":"Azerbaijani","ba":"Bashkir",
"be":"Belarusian","bg":"Bulgarian","bn":"Bengali","bo":"Tibetan","br":"Breton","bs":"Bosnian",
"ca":"Catalan","cs":"Czech","cy":"Welsh","da":"Danish","de":"German","el":"Greek","en":"English",
"es":"Spanish","et":"Estonian","eu":"Basque","fa":"Persian","fi":"Finnish","fo":"Faroese",
"fr":"French","gl":"Galician","gu":"Gujarati","ha":"Hausa","haw":"Hawaiian","he":"Hebrew",
"hi":"Hindi","hr":"Croatian","ht":"Haitian","hu":"Hungarian","hy":"Armenian","id":"Indonesian",
"is":"Icelandic","it":"Italian","ja":"Japanese","jw":"Javanese","ka":"Georgian","kk":"Kazakh",
"km":"Khmer","kn":"Kannada","ko":"Korean","la":"Latin","lb":"Luxembourgish","ln":"Lingala",
"lo":"Lao","lt":"Lithuanian","lv":"Latvian","mg":"Malagasy","mi":"Maori","mk":"Macedonian",
"ml":"Malayalam","mn":"Mongolian","mr":"Marathi","ms":"Malay","mt":"Maltese","my":"Burmese",
"ne":"Nepali","nl":"Dutch","no":"Norwegian","oc":"Occitan","pa":"Punjabi","pl":"Polish","ps":"Pashto",
"pt":"Portuguese","ro":"Romanian","ru":"Russian","sa":"Sanskrit","sd":"Sindhi","si":"Sinhala",
"sk":"Slovak","sl":"Slovenian","sn":"Shona","so":"Somali","sq":"Albanian","sr":"Serbian",
"su":"Sundanese","sv":"Swedish","sw":"Swahili","ta":"Tamil","te":"Telugu","tg":"Tajik","th":"Thai",
"tk":"Turkmen","tl":"Tagalog","tr":"Turkish","tt":"Tatar","uk":"Ukrainian","ur":"Urdu","uz":"Uzbek",
"vi":"Vietnamese","yi":"Yiddish","yo":"Yoruba","zh":"Chinese"
}
def pretty_lang(code: str) -> str:
return LANGUAGE_NAMES.get((code or "").lower(), (code or "Unknown").upper())
def fmt_mmss(seconds):
if seconds is None:
return "?:??"
seconds = max(0, int(round(float(seconds))))
return f"{seconds // 60}:{seconds % 60:02d}"
def force_fast_model():
if st.session_state.model_choice_key != MODEL_LABELS[0]:
st.warning("Only the 'Fast' model is available in this demo. Re-selecting 'Fast'.")
st.session_state.model_choice_key = MODEL_LABELS[0]
selected_label = st.radio(
"Model",
MODEL_LABELS,
index=0,
key="model_choice_key",
on_change=force_fast_model
)
model_choice = MODEL_MAP[selected_label]
SUB_LABELS = ["Same as audio/video", "English (translate)"]
language = "auto"
task_value = "transcribe" if st.radio("Subtitle language", SUB_LABELS, index=0) == SUB_LABELS[0] else "translate"
STYLE_LABELS = ["Default Video (16:9 / Longer lines)", "Vertical Video (9:16 / Shorter Subtitles for Reels/Shorts)"]
STYLE_MAP = {
STYLE_LABELS[0]: "default",
STYLE_LABELS[1]: "vertical",
}
style_choice = STYLE_MAP[st.radio("Subtitle style", STYLE_LABELS, index=0,
help="Use Vertical for reels/shorts: shorter, faster-changing single-line captions.")]
file = st.file_uploader("Upload audio/video", type=None)
# Run job
if file and st.button("Create subtitles"):
try:
job_id = str(uuid4())[:8]
src_path = transcribe_utils.UPLOAD_DIR / f"{job_id}_{file.name}"
src_path.write_bytes(file.getvalue())
wav_path = transcribe_utils.UPLOAD_DIR / f"{job_id}.wav"
# Dosya dönüştürme hatasını yakalamak için st.spinner kullan
with st.spinner("Preparing audio file... (This may take a moment for large videos)"):
transcribe_utils.to_wav16k_mono(src_path, wav_path)
job = transcribe_utils.Job(job_id, file.name, transcribe_utils.DEFAULT_OUTPUT_DIR)
with transcribe_utils.JOBS_LOCK:
transcribe_utils.JOBS[job_id] = job
lang = None if language == "auto" else language
thread = threading.Thread(
target=transcribe_utils.run_transcription,
args=(
job_id, wav_path, lang, task_value, model_choice,
transcribe_utils.DEFAULT_OUTPUT_DIR, style_choice
),
daemon=True
)
thread.start()
prog = st.progress(0, text="Starting…")
status_text = st.empty()
t0 = time.time()
t_running_start = None
while True:
with transcribe_utils.JOBS_LOCK:
job_status = transcribe_utils.JOBS.get(job_id)
if job_status:
current_status = job_status.status
current_progress = job_status.progress
current_lang = job_status.language
current_model_choice = job_status.model_choice
current_model_name = job_status.model_name
current_duration = job_status.duration
current_srt_path = str(job_status.srt_path) if job_status.srt_path else None
current_error = job_status.error_msg
else:
current_status = None
if not current_status:
st.error("Job not found.")
break
info = {
"status": current_status, "progress": current_progress, "language": current_lang,
"model_choice": current_model_choice, "model_name": current_model_name,
"duration_sec": current_duration, "srt_path": current_srt_path, "error": current_error,
}
eta = None
if info["status"] == "running" and info.get("progress", 0) > 0:
elapsed = time.time() - t0
progress = info["progress"]
if progress > 0.001:
eta = max(elapsed * (1.0 - progress) / progress, 0.0)
if info["status"] == "loading_model":
prog.progress(0, text="Please wait..")
status_text.info(
"The selected AI model is being downloaded/loaded. "
"This may take a while on the first run."
)
elif info["status"] == "running":
if t_running_start is None:
t_running_start = time.time()
real_pct = int(round((info.get("progress") or 0.0) * 100))
elapsed_running = time.time() - t_running_start
SIMULATED_DURATION_SECONDS = 15.0
SIMULATED_PROGRESS_CAP = 15
simulated_pct = (elapsed_running / SIMULATED_DURATION_SECONDS) * SIMULATED_PROGRESS_CAP
simulated_pct = min(int(simulated_pct), SIMULATED_PROGRESS_CAP)
display_pct = max(real_pct, simulated_pct)
prog.progress(min(max(display_pct, 0), 100), text=f"{display_pct}%")
model_pretty = MODEL_DISPLAY.get(info.get("model_choice",""), info.get("model_name",""))
lang_pretty = pretty_lang(info.get("language"))
status_text.info(f"Processing… {display_pct}% • ETA: {fmt_mmss(eta)} • Model: {model_pretty}")
elif info["status"] == "done":
elapsed = time.time() - t0
model_pretty = MODEL_DISPLAY.get(info.get("model_choice",""), info.get("model_name",""))
lang_pretty = pretty_lang(info.get("language"))
st.success(f"Done in {fmt_mmss(elapsed)} — Detected: {lang_pretty} • Model: {model_pretty}")
suggested = Path(file.name).with_suffix(".srt").name
p = info.get("srt_path")
if p and Path(p).exists():
st.download_button(
"Save .srt",
Path(p).read_bytes(),
file_name=suggested,
mime="text/plain",
use_container_width=True,
)
else:
st.error(f"SRT file not found at path: {p}")
break
elif info["status"] == "error":
st.error(f"Failed: {info.get('error','unknown error')}")
break
time.sleep(0.5)
except Exception as e:
st.error(f"Failed to start job: {e}")
st.divider()
if st.button("Clear cache", type="primary", use_container_width=True):
transcribe_utils._model_cache.clear()
transcribe_utils._model_meta.clear()
st.cache_data.clear()
st.rerun()
st.markdown(
"<p style='text-align: center; color: #666; font-size: 0.9rem;'>OpenAI's Whisper is used in this app</p>",
unsafe_allow_html=True
)