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
93 lines
3.2 KiB
Python
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()
|