Spaces:
Running
Running
| 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> | |
| | | |
| <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 | |
| ) |