Spaces:
Sleeping
Sleeping
| # 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( | |
| "<b>Transkript-formatering</b><br>" | |
| "Lim inn transkriptet ditt (med tidskoder eller uten).<br>" | |
| "- Bruk <i>Formatér</i> for å slå sammen tekst under tidskoder. <br>" | |
| "- Bruk <i>Uten tidskoder</i> for å formatere tekst uten tidskoder: avsnitt etter hver 5. setning. <br>" | |
| "- Når utdata er i tekstfeltet, kan du trykke <i>Generer .txt og .docx</i> 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) | |