# app.py import re import gradio as gr from datetime import datetime import os from docx import Document # krever python-docx i requirements # Aksepterer både punktum og komma i desimaldelen TIMESTAMP_RE = re.compile(r'(\[\d{2}:\d{2}:\d{2}(?:[.,]\d{1,3})?\])') TMP_DIR = "/tmp/hf_transcript" os.makedirs(TMP_DIR, exist_ok=True) def format_transcript(text: str) -> str: """Bevarer tidsstempler og slår sammen linjer per tidsstempel.""" if not text: return "" text = text.replace('\r\n', '\n').replace('\r', '\n') parts = TIMESTAMP_RE.split(text) out_lines = [] i = 0 if parts and not TIMESTAMP_RE.match(parts[0]): if len(parts) > 1: parts[1] = parts[0] + parts[1] i = 1 else: return re.sub(r'\n+', ' ', text).strip() while i < len(parts) - 1: ts = parts[i].strip() body = parts[i+1] body = re.sub(r'\n+', ' ', body).strip() body = re.sub(r' {2,}', ' ', body) line = f"{ts} {body}".strip() out_lines.append(line) i += 2 if i < len(parts): tail = parts[i].strip() if tail: tail = re.sub(r'\n+', ' ', tail).strip() out_lines.append(tail) return "\n\n".join(out_lines) def format_without_timestamps(text: str, sentences_per_paragraph: int = 5) -> str: """ Tar tekst uten tidskoder og formaterer den til avsnitt etter hver N setninger. - splitter på setningsendelser (., !, ?), forsøker å beholde forkortelser rimelig. - samler setninger i grupper på sentences_per_paragraph og separerer gruppene med et tomt linjeskift. """ if not text: return "" # Normaliser whitespace og newlines txt = text.replace('\r\n', '\n').replace('\r', '\n') txt = re.sub(r'\n+', ' ', txt) # fjern nye linjer internt txt = re.sub(r'\s+', ' ', txt).strip() # collapse whitespace # Split på setningsavslutning: behold skilletegn # Dette splitter på (?<=[.!?]) etterfulgt av ett eller flere mellomrom raw_sentences = re.split(r'(?<=[.!?])\s+', txt) # Hvis veldig korte "setninger" eller tomme, filter dem ut sentences = [s.strip() for s in raw_sentences if s.strip()] if not sentences: return txt # fallback # Gruppér setninger og lag avsnitt paragraphs = [] for i in range(0, len(sentences), sentences_per_paragraph): group = sentences[i:i+sentences_per_paragraph] paragraph = ' '.join(group).strip() paragraphs.append(paragraph) return "\n\n".join(paragraphs) def download_filename(prefix="formatted_transcript"): now = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") return f"{prefix}_{now}" def make_txt_file(text: str) -> str: fname = download_filename() + ".txt" path = os.path.join(TMP_DIR, fname) with open(path, "wb") as f: f.write(text.encode("utf-8")) return path def make_docx_file(text: str) -> str: fname = download_filename() + ".docx" path = os.path.join(TMP_DIR, fname) doc = Document() segments = text.split("\n\n") for seg in segments: doc.add_paragraph(seg) doc.save(path) return path def format_and_prepare_files(preferred_text: str) -> tuple: """ Lager txt og docx fra gitt tekst (forventet allerede formatert). Returnerer: (tekst, txt_path, docx_path) """ formatted = preferred_text if preferred_text is not None else "" txt_path = make_txt_file(formatted) docx_path = make_docx_file(formatted) return formatted, txt_path, docx_path def generate_from_preferred(inp_text: str, out_text: str): """ Velger utdatafeltets tekst hvis det finnes (prioritet), ellers bruker original input. Deretter lager filer fra valgt tekst. """ preferred = out_text.strip() if out_text and out_text.strip() else inp_text if not preferred: return "", None, None return format_and_prepare_files(preferred) def clear_input_and_outputs(): return gr.update(value=""), gr.update(value=""), None, None with gr.Blocks(title="Transkript-formatter (TXT + DOCX)") as demo: gr.Markdown( "Transkript-formatering
" "Lim inn transkriptet ditt (med tidskoder eller uten).
" "- Bruk Formatér for å slå sammen tekst under tidskoder.
" "- Bruk Uten tidskoder for å formatere tekst uten tidskoder: avsnitt etter hver 5. setning.
" "- Når utdata er i tekstfeltet, kan du trykke Generer .txt og .docx for å laste ned filene basert på utdata." ) with gr.Row(): inp = gr.Textbox(lines=18, label="Råtekst (lim inn her)", placeholder="Lim inn hele transkriptet her...") out = gr.Textbox(lines=18, label="Formatert tekst (kopier herfra eller generer filer)") with gr.Row(): fmt_btn = gr.Button("Formatér (vis i felt)") no_ts_btn = gr.Button("Uten tidskoder") # <-- ny knapp gen_btn = gr.Button("Generer .txt og .docx (nedlasting)") txt_file = gr.File(label="Last ned .txt") docx_file = gr.File(label="Last ned .docx") with gr.Row(): copy_btn = gr.Button("Oppdater utdata (klar for kopiering)") clear_btn = gr.Button("Clear input") # Normalt format (bevarer tidskoder) fmt_btn.click(lambda t: format_transcript(t), inputs=inp, outputs=out) # Uten tidskoder: formaterer tekst ved å gruppere hver 5 setning til ett avsnitt # (Returnerer oppdatert out-tekst) def without_timestamps_and_show(text): return format_without_timestamps(text, sentences_per_paragraph=5) no_ts_btn.click(without_timestamps_and_show, inputs=inp, outputs=out) # Generer bruker preferert tekst (out hvis ikke tom, ellers inp) gen_btn.click(generate_from_preferred, inputs=[inp, out], outputs=[out, txt_file, docx_file]) copy_btn.click(lambda t: t, inputs=out, outputs=out) clear_btn.click(clear_input_and_outputs, inputs=None, outputs=[inp, out, txt_file, docx_file]) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)