Files
atm-curs-dl/extract_audio.py
Claude Agent 20c5960320 transa 2 de Analiza Zilnica (lectiile 26-50), audio sters
extract_audio.py primeste --skip N, ca transele urmatoare sa nu reia
lectiile deja luate: slice + enumerate de la skip+1, numerotarea
fisierelor ramane cea globala.

Audio-ul (862 MB) sters dupa verificare fisier cu fisier - fiecare mp3
avea transcriere completa, iar singurul fara era duplicatul confirmat
_duplicate/02_08092026 (fingerprint 1.000 fata de 01_10092026).

PROGRESS.md: sectiune noua 0bis cu ce ruleaza acum.

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

118 lines
4.1 KiB
Python

"""
Extract audio-only tracks for the core-curriculum lectures of the ATM course.
No video ever touches disk: ffmpeg reads the CloudFront MP4 URL directly over
HTTP and writes only the audio track (mono, 16kHz -> what whisper wants
anyway) to disk. Manifest-free; resumable by checking file existence.
"""
import argparse
import logging
import os
import subprocess
import sys
from pathlib import Path
import requests
from dotenv import load_dotenv
from download import login, get_course_id, get_menu, get_lecture_video_url, slugify
# First-phase scope: core curriculum only, not the 147 "ANALIZĂ ZILNICĂ"
# recaps / Q&A / live-room recordings (confirmed with user 2026-09-11).
CORE_MODULES = {
"MODUL 1", "MODUL 2", "MODUL 3", "MODUL 4", "MODUL 5", "MODUL 6",
"CATALIZATORI", "ANALIZĂ FUNDAMENTALĂ",
"MASTER ATM 1", "MASTER ATM 2", "MASTER ATM 3", "MASTER ATM 4",
}
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger(__name__)
def extract_audio(url: str, dest: Path) -> bool:
dest.parent.mkdir(parents=True, exist_ok=True)
tmp = dest.with_suffix(dest.suffix + ".tmp")
cmd = [
"ffmpeg", "-y", "-i", url,
"-vn", "-acodec", "libmp3lame", "-ar", "16000", "-ac", "1", "-b:a", "64k",
"-f", "mp3", str(tmp),
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
if result.returncode != 0 or not tmp.exists():
log.error(f" ffmpeg failed: {result.stderr[-1500:]}")
return False
tmp.rename(dest)
return True
def main():
p = argparse.ArgumentParser(description="Extract audio for ATM core-curriculum lectures")
p.add_argument("--out", default="audio", help="Output directory")
p.add_argument("--only", default=None, help="Limit to one module name (bypasses CORE_MODULES)")
p.add_argument("--lectures", type=int, default=None, help="With --only: stop after lecture N of that module")
p.add_argument("--skip", type=int, default=0, help="Skip the first N lectures (transa urmatoare)")
args = p.parse_args()
load_dotenv()
email = os.getenv("ATM_EMAIL", "")
password = os.getenv("ATM_PASSWORD", "")
if not email or not password:
log.error("Set ATM_EMAIL and ATM_PASSWORD in .env")
sys.exit(1)
out_dir = Path(args.out)
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"})
log.info("Logging in...")
login(session, email, password)
course_id = get_course_id(session)
modules = get_menu(session, course_id)
done = skipped = failed = 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
mod_dir = out_dir / f"{mod_idx:02d}_{slugify(mod['name'])}"
log.info(f"Module: {mod['name']} ({len(mod['lectures'])} lectures)")
lectures = mod["lectures"][args.skip : args.lectures]
for lec_idx, lec in enumerate(lectures, args.skip + 1):
stem = f"{lec_idx:02d}_{slugify(lec['name'])}"
dest = mod_dir / f"{stem}.mp3"
if dest.exists() and dest.stat().st_size > 10_000:
log.info(f" Skipping (exists): {dest.name}")
skipped += 1
continue
try:
video_url = get_lecture_video_url(session, lec["id"])
except requests.exceptions.HTTPError as e:
if e.response is not None and e.response.status_code == 403:
log.info(f" Skipping (not included in access): {lec['name']}")
continue
raise
if not video_url:
log.info(f" No video (text/quiz): {lec['name']}")
continue
log.info(f" Extracting: {lec['name']}")
if extract_audio(video_url, dest):
done += 1
else:
failed += 1
log.info("=" * 60)
log.info(f"Extracted {done}, skipped {skipped}, failed {failed}.")
if failed:
sys.exit(1)
if __name__ == "__main__":
main()