137 lines
4.4 KiB
Python
137 lines
4.4 KiB
Python
"""Search the transcript index.
|
|
|
|
Usage:
|
|
python search.py "isom trademark"
|
|
python search.py "pull up" --tag eric_july
|
|
python search.py "judge ramos" --ep 103 --limit 5
|
|
python search.py "rage" --from 2017-01-01 --to 2019-12-31 --report out.txt
|
|
|
|
Output lines include EPISODE + TIMESTAMP so they can be deep-linked to audio
|
|
later: [Ep 373 | 2023-08-29] 01:12:45-01:13:02 "...transcript snippet..."
|
|
"""
|
|
import argparse
|
|
import datetime as dt
|
|
import json
|
|
import re
|
|
import sqlite3
|
|
import sys
|
|
|
|
from config import DB_PATH
|
|
from index import open_db
|
|
|
|
|
|
def ts(seconds: float) -> str:
|
|
s = int(seconds)
|
|
return f"{s // 3600:02d}:{(s % 3600) // 60:02d}:{s % 60:02d}"
|
|
|
|
|
|
def parse_date(s: str):
|
|
return dt.date.fromisoformat(s)
|
|
|
|
|
|
def pubdate_year(pub: str):
|
|
m = re.search(r"(\d{4})", pub or "")
|
|
return int(m.group(1)) if m else None
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("query", help="FTS5 query (supports \"quoted phrases\", AND/OR)")
|
|
ap.add_argument("--tag", action="append", default=[],
|
|
help="filter by hashtag (repeatable)")
|
|
ap.add_argument("--ep", type=int, action="append", default=[],
|
|
help="restrict to episode number(s)")
|
|
ap.add_argument("--from", dest="date_from", default=None)
|
|
ap.add_argument("--to", dest="date_to", default=None)
|
|
ap.add_argument("--limit", type=int, default=25)
|
|
ap.add_argument("--context", type=int, default=0,
|
|
help="include N neighbouring segments")
|
|
ap.add_argument("--report", default=None,
|
|
help="also write matches to this file")
|
|
args = ap.parse_args()
|
|
|
|
con = open_db(DB_PATH)
|
|
q = args.query.strip()
|
|
|
|
sql = """
|
|
SELECT s.rowid, e.ep_num, e.guid, e.title, e.pub_date, e.audio_url,
|
|
e.page_link, s.seg_idx, s.start_sec, s.end_sec, s.text, s.tags
|
|
FROM segments s JOIN episodes e ON e.id = s.episode_id
|
|
WHERE segments MATCH ?
|
|
"""
|
|
params = [q]
|
|
|
|
if args.tag:
|
|
for t in args.tag:
|
|
sql += " AND s.tags LIKE ?"
|
|
params.append(f"%#{t}%")
|
|
if args.ep:
|
|
sql += " AND e.ep_num IN (%s)" % ",".join("?" * len(args.ep))
|
|
params += args.ep
|
|
if args.date_from:
|
|
y = parse_date(args.date_from).year
|
|
# crude pub-date year filter; feed dates are RFC2822
|
|
sql += " AND CAST(substr(e.pub_date, length(e.pub_date)-3, 4) AS INTEGER) >= ?"
|
|
params.append(y)
|
|
if args.date_to:
|
|
y = parse_date(args.date_to).year
|
|
sql += " AND CAST(substr(e.pub_date, length(e.pub_date)-3, 4) AS INTEGER) <= ?"
|
|
params.append(y)
|
|
|
|
sql += " ORDER BY e.pub_date LIMIT ?"
|
|
params.append(args.limit)
|
|
|
|
rows = con.execute(sql, params).fetchall()
|
|
if not rows:
|
|
print("no matches")
|
|
return
|
|
|
|
lines = []
|
|
|
|
def parse_tags(raw: str) -> list[str]:
|
|
raw = raw or ""
|
|
if not raw:
|
|
return []
|
|
if raw.startswith("["):
|
|
try:
|
|
return json.loads(raw)
|
|
except ValueError:
|
|
return []
|
|
return [t for t in raw.split() if t]
|
|
|
|
for r in rows:
|
|
(_rid, ep_num, guid, title, pub, audio_url, page_link, seg_idx,
|
|
start, end, text, tags) = r
|
|
label = f"Ep {ep_num}" if ep_num else title[:30]
|
|
taglist = parse_tags(tags)
|
|
head = (f"[{label} | {pub}] {ts(start)}-{ts(end)} "
|
|
f"{' '.join(taglist)}")
|
|
body = f" \"{text}\""
|
|
extra = ""
|
|
if args.context:
|
|
ctx = con.execute(
|
|
"""SELECT start_sec, end_sec, text FROM segments
|
|
WHERE episode_id=(SELECT id FROM episodes WHERE guid=?)
|
|
AND seg_idx BETWEEN ? AND ? ORDER BY seg_idx""",
|
|
(guid, max(0, seg_idx - args.context),
|
|
seg_idx + args.context)).fetchall()
|
|
body = "\n".join(
|
|
f" [{ts(a)}-{ts(b)}] {t}" for a, b, t in ctx)
|
|
link = page_link or audio_url
|
|
extra = f" -> {link}\n mp3: {audio_url}"
|
|
lines.append(head + "\n" + body + "\n" + extra)
|
|
|
|
out = "\n\n".join(lines)
|
|
print(out[:20000])
|
|
print(f"\n({len(rows)} result(s), limit {args.limit})")
|
|
|
|
if args.report:
|
|
with open(args.report, "a", encoding="utf-8") as f:
|
|
f.write(f"\n===== QUERY: {q} tags={args.tag} eps={args.ep} =====\n")
|
|
f.write(out + "\n")
|
|
print(f"appended to {args.report}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|