Files
atm-curs-dl/extract_text_lessons.py
Claude Agent ff25f3d9c3 Lectii text: extract_text_lessons.py + Modul 3 #9 sumarizata
extract_audio.py sarea peste lectiile fara video. Gasita una in curriculum-ul
accesibil (Modul 3 #9 - expirari de optiuni, witching, 0DTE), cu 4847 de
caractere de continut real. Era numarata gresit ca acoperita.

Plus:
- render_html.py: index grupat pe module cu titlul H1 al fiecarei pagini
- render_html.py: garda pe liniile goale din <svg> (rup blocul HTML tacut)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
2026-09-12 17:57:02 +00:00

93 lines
3.2 KiB
Python

"""
Extrage lecțiile FĂRĂ video (text/quiz) — pe care extract_audio.py le sare,
pentru că el caută doar un MP4.
API-ul de lecție întoarce conținutul în câmpul `html`, împreună cu tot
CSS-ul paginii systeme.io. Curățăm style/script, apoi tagurile, și scriem
text în același loc cu transcrierile, ca să intre în același flux de
sumarizare.
"""
import argparse
import html as html_mod
import logging
import os
import re
import sys
from pathlib import Path
import requests
from dotenv import load_dotenv
from download import BASE, get_course_id, get_lecture_video_url, get_menu, login, slugify
from extract_audio import CORE_MODULES
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger(__name__)
def lecture_text(session: requests.Session, lecture_id: int) -> str:
raw = session.get(f"{BASE}/api/membership/lecture/{lecture_id}").json().get("html") or ""
raw = re.sub(r"(?is)<(style|script)\b.*?</\1>", " ", raw)
text = html_mod.unescape(re.sub(r"<[^>]+>", "\n", raw))
lines = [ln.strip() for ln in text.split("\n") if ln.strip()]
return "\n".join(lines)
def main():
p = argparse.ArgumentParser(description="Extrage lecțiile text din cursul ATM")
p.add_argument("--out", default="transcripts")
p.add_argument("--only", default=None, help="Un singur modul (ocolește CORE_MODULES)")
args = p.parse_args()
load_dotenv()
email, password = os.getenv("ATM_EMAIL", ""), os.getenv("ATM_PASSWORD", "")
if not email or not password:
log.error("Setează ATM_EMAIL și ATM_PASSWORD în .env")
sys.exit(1)
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"})
login(session, email, password)
modules = get_menu(session, get_course_id(session))
done = skipped = 0
for mod_idx, mod in enumerate(modules, 1):
if args.only:
if mod["name"] != args.only:
continue
elif mod["name"] not in CORE_MODULES:
continue
for lec_idx, lec in enumerate(mod["lectures"], 1):
try:
if get_lecture_video_url(session, lec["id"]):
continue # are video -> îl ia extract_audio.py
except requests.exceptions.HTTPError as e:
if e.response is not None and e.response.status_code == 403:
continue # fără acces
raise
dest = Path(args.out) / f"{mod_idx:02d}_{slugify(mod['name'])}" / f"{lec_idx:02d}_{slugify(lec['name'])}.txt"
if dest.exists() and dest.stat().st_size > 200:
log.info(f" Există deja: {dest.name}")
skipped += 1
continue
text = lecture_text(session, lec["id"])
if len(text) < 200:
log.info(f" Prea puțin text, probabil quiz gol: {lec['name']}")
continue
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(text, encoding="utf-8")
log.info(f" Extras ({len(text)} caractere): {dest}")
done += 1
log.info("=" * 60)
log.info(f"Lecții text extrase: {done}, sărite: {skipped}.")
if __name__ == "__main__":
main()