import gradio as gr from pydub import AudioSegment from google import genai import tempfile import os import time import re import concurrent.futures API_TIMEOUT_SEC = 300 PROMPT = """ Transcribe the following audio exactly as spoken. Format the transcript with clear paragraphs. When the speaker moves to a new topic or subject, insert a subheading in markdown (### Topic) that summarizes the upcoming content. For verses, stanzas, or ślokas, break them into separate lines as they would appear in a written poem (one line per quarter or line as appropriate). Maintain the original language rules: - Sanskrit proper names: IAST without italics. - Other Sanskrit words and phrases: IAST with *italics* and English translation in parentheses, e.g., *śāntiḥ* (*peace*). - English: exactly as spoken. - Serbian/Croatian: word order as given (e.g., 'Svāmī je klimnuo glavom' not 'klimnuo je'). Do not add timestamps. Output only the formatted transcript. """ def call_gemini(seg_path, api_key): """Poziva Gemini API (izvršava se u pozadinskoj niti).""" client = genai.Client(api_key=api_key) myfile = client.files.upload(file=seg_path) response = client.models.generate_content( model="gemini-2.5-flash", contents=[myfile, PROMPT] ) text = response.text client.files.delete(name=myfile.name) return text def transcribe_segment(audio_path, api_key, start_min, end_min, progress=gr.Progress()): # Provera ulaza if audio_path is None: yield "❌ Niste otpremili MP3 fajl!" return if not api_key.strip(): yield "❌ Unesite Gemini API ključ." return if start_min is None or end_min is None: yield "❌ Unesite početak i kraj u minutima." return if start_min >= end_min: yield "❌ Početak mora biti manji od kraja." return progress(0.0, desc="Učitavanje audio fajla...") yield "⏳ Učitavanje audio fajla..." try: sound = AudioSegment.from_file(audio_path) except Exception as e: yield f"❌ Greška pri učitavanju fajla: {e}" return total_sec = sound.duration_seconds start_ms = int(start_min * 60 * 1000) end_ms = int(end_min * 60 * 1000) if end_ms > len(sound): yield f"❌ Kraj ({end_min} min) premašuje trajanje snimka ({total_sec/60:.1f} min)." return if start_ms < 0: start_ms = 0 segment = sound[start_ms:end_ms] segment_dur = len(segment) / 1000 progress(0.1, desc=f"Isečeno {start_min}-{end_min} min ({segment_dur/60:.1f} min)") yield f"✂️ Isečen segment {start_min}–{end_min} min ({segment_dur/60:.1f} min)" progress(0.2, desc="Priprema audio segmenta...") yield "📦 Priprema audio segmenta..." tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) segment.export(tmp.name, format="mp3") seg_path = tmp.name api_key_clean = api_key.strip() last_err = "" for attempt in range(1, 10): progress(0.25 + attempt * 0.07, desc=f"Transkripcija (pokušaj {attempt})...") yield f"🎤 Transkripcija u toku (pokušaj {attempt})..." print(f"API poziv za {start_min}-{end_min} min, pokušaj {attempt}") try: with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(call_gemini, seg_path, api_key_clean) text = future.result(timeout=API_TIMEOUT_SEC) progress(1.0, desc="Gotovo!") os.unlink(seg_path) yield f"### {start_min}–{end_min} min\n{text}" return except concurrent.futures.TimeoutError: last_err = "API poziv je prekoračio vremensko ograničenje (5 min)." print(" Timeout!") break except Exception as e: last_err = str(e) print(f" Greška: {last_err}") if "503" in last_err or "UNAVAILABLE" in last_err: wait = 10 * attempt print(f" 503, čekam {wait}s...") yield f"⏳ Server nedostupan (503), čekam {wait}s..." time.sleep(wait) elif "429" in last_err and "RESOURCE_EXHAUSTED" in last_err: match = re.search(r"retryDelay':\s*'?(\d+)s?'?", last_err) wait = int(match.group(1)) + 2 if match else 60 print(f" 429, čekam {wait}s...") yield f"⏳ Previše zahteva (429), čekam {wait}s..." time.sleep(wait) else: break # Sve probe neuspešne try: os.unlink(seg_path) except: pass yield f"### {start_min}–{end_min} min\n*Greška: {last_err}*" with gr.Blocks() as demo: gr.Markdown("# 🎙️ Transkripcija po opsezima (progres uživo + formatiranje)") with gr.Row(): audio_in = gr.Audio(type="filepath", label="Otpremite MP3 fajl") api_key = gr.Textbox(label="Gemini API ključ", type="password") with gr.Row(): start_min = gr.Number(label="Početak (minuti)", value=0, precision=1) end_min = gr.Number(label="Kraj (minuti)", value=20, precision=1) gr.Markdown("*Otpremite ceo MP3, pa unosite opsege jedan po jedan (npr. 0–20, 20–40…).*") btn = gr.Button("Pokreni transkripciju ovog opsega") output = gr.Markdown(label="Transkript") btn.click( fn=transcribe_segment, inputs=[audio_in, api_key, start_min, end_min], outputs=output ) if __name__ == "__main__": demo.launch()