"
)
return "\n".join(out) or "No results yet."
@app.get("/open")
def open_local():
file = request.args.get("file","")
t = int(request.args.get("t","0"))
return f"
{file}\nStart at: {t} sec
"
@app.get('/media')
def media():
rel = request.args.get('file', '')
try:
full = _safe_under(LIBRARY_ROOT, rel)
except Exception:
return abort(404)
if not full.exists():
return abort(404)
# Let Flask guess mimetype
return send_file(str(full), conditional=True)
@app.get('/play')
def play():
rel = request.args.get('file', '')
t = int(request.args.get('t', '0') or 0)
src = f"/media?file={requests.utils.quote(rel)}"
track = f"/subtitle?file={requests.utils.quote(rel)}&format=vtt"
return (
""
"Play"
""
f"
{rel}
"
f""
""
)
@app.get("/subtitle")
def subtitle():
file = request.args.get("file", "")
fmt = request.args.get("format", "").lower() # when 'vtt', serve as text/vtt for player
base = os.path.splitext(os.path.basename(file))[0]
kind, content, used_path = _load_transcript_variants(base)
# Build a VTT if requested/needed and we can
if fmt == "vtt":
vtt = ""
if kind == "vtt":
vtt = content if content.lstrip().upper().startswith("WEBVTT") else _vtt_header() + content
elif kind == "srt":
vtt = _srt_to_vtt_text(content)
elif kind == "json":
vtt = _json_to_vtt_text(content)
else:
# No structured timing available
return abort(404)
resp = make_response(vtt)
resp.headers["Content-Type"] = "text/vtt; charset=utf-8"
return resp
# Otherwise, render a simple HTML preview
if kind in ("vtt", "srt", "json"):
# Normalize to VTT first
if kind == "vtt":
vtt_text = content if content.lstrip().upper().startswith("WEBVTT") else _vtt_header() + content
elif kind == "srt":
vtt_text = _srt_to_vtt_text(content)
else:
vtt_text = _json_to_vtt_text(content)
# If ?raw=1 is present, show raw VTT for debugging
if request.args.get("raw") == "1":
return (
""
"Transcript"
""
f"
Transcript (raw VTT): {base}
"
f"
{vtt_text}
"
)
# Otherwise render a readable transcript with clickable timestamps
cues = _parse_vtt_to_cues(vtt_text)
# Build HTML list
items = []
for c in cues:
mm = int(c["start"] // 60)
ss = int(c["start"] % 60)
hh = int(c["start"] // 3600)
ts_label = f"{hh:02d}:{mm%60:02d}:{ss:02d}" if hh else f"{mm:02d}:{ss:02d}"
items.append(
"
"
f""
f"{c['text']}"
"
"
)
html = (
""
"Transcript"
""
f"
Transcript: {base}
"
"
Click a timestamp to open the player at that point.
"
f"
{''.join(items) or 'No cues found.'}
"
"
"
)
return html
elif kind == "txt":
safe = content.strip()
# Simple paragraphization: collapse >2 newlines, wrap in
paras = [p.strip() for p in re.split(r"\n{2,}", safe) if p.strip()]
clean_paras = [re.sub(r'[\n\r]+', ' ', p) for p in paras[:2000]]
items = "".join(f"
{p}
" for p in clean_paras)
# Build fallback without using backslashes inside an f-string expression
fallback = f"
{safe}
"
body = items if items else fallback
return (
""
"Transcript"
""
f"