VideoHelp Forum




+ Reply to Thread
Results 1 to 26 of 26
  1. This TV channel from Ecuador previously used the VuStream platform but recently it has switched platform, and the stream links are no longer .m3u8, now it's .mpd links, and I don't know how to download them.

    The links I get with The Stream Detector.

    Code:
    https://us-b4-p-e-qg12.cdn.mdstrm.com/live-stream-gdai/6a233703aa86429263ce17ec/linear/dash/pa/event/Z8uMUTUBSC6R09A92EKh1g/stream/1c4c6d47-4e7a-4f28-b114-efce56079b92:TUL/manifest.mpd?pid=5QQZEwhHoRN47YeyhdZ0Vxvc3QJe75iq&sid=G8PIyKncDwVav4Fy37dyb4EMqB5eFjTB&uid=LFoAadgdq5ui0OfK6zhfUBF8H1T06Okm&aid=693c80acf445d8c957fb250d&access_token=c7bdQ2YQWTEHsV96bfj0VPy4TBTjndvVfJ4decuci9KmGflz4GrHEDSm4lK4NkHrsXP5WFaApgh&ot=ekT5x80JzJe7SVUkwvMf0g&ote=1791623816659&proto=https&es=us-b4-p-e-qg12.cdn.mdstrm.com
    https://mdstrm.com/live-stream-playlist/6a233703aa86429263ce17ec.mpd?_=1791537454242&dnt=true&dual_render=1&player=6945b9bca18f515cff51c75b&access_token=c7bdQ2YQWTEHsV96bfj0VPy4TBTjndvVfJ4decuci9KmGflz4GrHEDSm4lK4NkHrsXP5WFaApgh&uid=LFoAadgdq5ui0OfK6zhfUBF8H1T06Okm&sid=G8PIyKncDwVav4Fy37dyb4EMqB5eFjTB&pid=5QQZEwhHoRN47YeyhdZ0Vxvc3QJe75iq&an=lightning-player&at=web-app&av=v1.0.109&sc=0&ref=mdstrm.com&res=1920x1080&ext_pb=0
    https://mdstrm.com/live-stream-playlist/6a0dedcb6c7d568efdc8e0d9.mpd?_=1791537454278&dnt=true&dual_render=1&player=6945b9bca18f515cff51c75b&access_token=zgyI3CtDAAMJ9cfcCZnukNbhDee6FmEp3i9srcVB1TNpymIzbPcFb8IwYh1u5UT4OeeXX6JEFUb&uid=LFoAadgdq5ui0OfK6zhfUBF8H1T06Okm&sid=G8PIyKncDwVav4Fy37dyb4EMqB5eFjTB&pid=yVDbOrf1fnFMKCXfnFnc3dSyo1noW1re&an=lightning-player&at=web-app&av=v1.0.109&sc=0&ref=mdstrm.com&res=1920x1080&ext_pb=0
    I used FFmpeg on Windows to download video from .m3u8 links.
    Last edited by naoto89; 9th Oct 2026 at 09:19.
    Quote Quote  
  2. can’t you download them with n_m3u8dl-re?
    Quote Quote  
  3. Originally Posted by CopyPasteDev View Post
    can’t you download them with n_m3u8dl-re?
    No. I don't even know exactly which of those is the right link.
    Quote Quote  
  4. "https://www.teleamazonas.com/actualidad/24horas/estelar/noticias-jueves-8-octubre-2026-24-horas-ecuador-estelar-130059/"


    yt-dlp "https://mdstrm.com/video/6ac8475b334a12f2722aef14.m3u8?uid=OT5bQUsWlL1nvA1d MJkhLr6W71YvUGAd&sid=FbzASGZ7GJ4wZOBra4kDz4ILwQOWl aFW&pid=yEif7bhpffx9IJBBD6h9aVr11qfSX0LF&pid_dvr=j QMfk74KTFRAlQGO6lu1ienYTR4MpIYH&an=screen&at=web-app&av=v7.0.124&ref=https%3A%2F%2Fwww.teleamazonas .com%2F&res=751x423&without_cookies=false&listener id=&dnt=true&CMCD=cid%3D%226ac8475b334a12f2722aef1 4%22%2Cmtp%3D500%2Cot%3Dm%2Csf%3Dh%2Csid%3D%22yEif 7bhpffx9IJBBD6h9aVr11qfSX0LF%22%2Csu" -o video.mp4
    [generic] Extracting URL: https://mdstrm.com/video/6ac8475b334a12f2722aef14.m3u8?uid=OT5bQUsWlL1nvA1dMJkhLr6W71Y...fSX0LF%22%2Csu
    [generic] 6ac8475b334a12f2722aef14.m3u8?uid=OT5bQUsWlL1nvA1d MJkhLr6W71YvUGAd&sid=FbzASGZ7GJ4wZOBra4kDz4ILwQOWl aFW&pid=yEif7bhpffx9IJBBD6h9aVr11qfSX0LF&pid_dvr=j QMfk74KTFRAlQGO6lu1ienYTR4MpIYH&an=screen&at=web-app&av=v7.0.124&ref=https://www.teleamazonas: Downloading webpage
    [generic] 6ac8475b334a12f2722aef14.m3u8?uid=OT5bQUsWlL1nvA1d MJkhLr6W71YvUGAd&sid=FbzASGZ7GJ4wZOBra4kDz4ILwQOWl aFW&pid=yEif7bhpffx9IJBBD6h9aVr11qfSX0LF&pid_dvr=j QMfk74KTFRAlQGO6lu1ienYTR4MpIYH&an=screen&at=web-app&av=v7.0.124&ref=https://www.teleamazonas: Downloading m3u8 information
    [generic] 6ac8475b334a12f2722aef14.m3u8?uid=OT5bQUsWlL1nvA1d MJkhLr6W71YvUGAd&sid=FbzASGZ7GJ4wZOBra4kDz4ILwQOWl aFW&pid=yEif7bhpffx9IJBBD6h9aVr11qfSX0LF&pid_dvr=j QMfk74KTFRAlQGO6lu1ienYTR4MpIYH&an=screen&at=web-app&av=v7.0.124&ref=https://www.teleamazonas: Checking m3u8 live status
    [info] 6ac8475b334a12f2722aef14.m3u8?uid=OT5bQUsWlL1nvA1d MJkhLr6W71YvUGAd&sid=FbzASGZ7GJ4wZOBra4kDz4ILwQOWl aFW&pid=yEif7bhpffx9IJBBD6h9aVr11qfSX0LF&pid_dvr=j QMfk74KTFRAlQGO6lu1ienYTR4MpIYH&an=screen&at=web-app&av=v7.0.124&ref=https://www.teleamazonas: Downloading 1 format(s): 5000
    [hlsnative] Downloading m3u8 manifest
    WARNING: Live HLS streams are not supported by the native downloader. If this is a livestream, please add "--downloader ffmpeg --hls-use-mpegts" to your command
    [hlsnative] Total fragments: 337
    [download] Destination: video.mp4
    [download] 2.4% of ~ 895.02MiB at 1.39MiB/s ETA 11:09 (frag 8/337)
    ...
    Quote Quote  
  5. Originally Posted by LZAA View Post
    "https://www.teleamazonas.com/actualidad/24horas/estelar/noticias-jueves-8-octubre-2026-24-horas-ecuador-estelar-130059/"
    What does that link have to do with what I asked? The channel's live stream isn't there. The streams section was the first thing I linked (behind the words "TV channel") when I started the post.
    Quote Quote  
  6. Originally Posted by naoto89 View Post
    use this command (you can change the duration in red)
    Code:
    n_m3u8dl-re "https://dai.google.com/linear/dash/pa/event/Z8uMUTUBSC6R09A92EKh1g/stream/bfe53670-3a82-4b10-a8e0-a4e1cf4b61b3:DLS/manifest.mpd" --key aeb0e7ae0feb4dae87c9859bc0e0d239:3c6df9213b350a19371cc1b3fea59a70 --use-shaka-packager -M mkv --live-real-time-merge --live-pipe-mux --live-wait-time 5 --live-record-limit 00:30:00 --save-name "filename"
    Quote Quote  
  7. Originally Posted by CopyPasteDev View Post
    Originally Posted by naoto89 View Post
    use this command (you can change the duration in red)
    Code:
    n_m3u8dl-re "https://dai.google.com/linear/dash/pa/event/Z8uMUTUBSC6R09A92EKh1g/stream/bfe53670-3a82-4b10-a8e0-a4e1cf4b61b3:DLS/manifest.mpd" --key aeb0e7ae0feb4dae87c9859bc0e0d239:3c6df9213b350a19371cc1b3fea59a70 --use-shaka-packager -M mkv --live-real-time-merge --live-pipe-mux --live-wait-time 5 --live-record-limit 00:30:00 --save-name "filename"
    Where did you copy that code from? It’s clearly not designed for my specific case, which is why I’m getting errors. There’s even a decryption key command involved, and I don’t know anything about that. If I knew what to do, I wouldn't have come to ask for help.
    Quote Quote  
  8. Which country's IP is used?
    Quote Quote  
  9. Is it possible to download 'mpd' Manifest without a VPN?
    Quote Quote  
  10. Originally Posted by LZAA View Post
    Is it possible to download 'mpd' Manifest without a VPN?
    yes its possible (but it expires fast)
    https://dai.google.com/linear/dash/pa/event/Z8uMUTUBSC6R09A92EKh1g/stream/dfa7da58-b7f...L/manifest.mpd
    I've uploaded the manifest just in case
    https://files.videohelp.com/u/315200/manifest.mpd
    the HAR object too
    https://files.videohelp.com/u/315200/www.teleamazonas.com_HAR.har
    Quote Quote  
  11. I still don't know everything I should do in this case, including the decryption key, which was the last thing I said. Can only N_m3u8DL-RE be used, and not another tool like Streamlink?
    Quote Quote  
  12. use this script

    Code:
    #!/usr/bin/env python3
    """
    recorder.py — Teleamazonas en vivo recorder
    Fetches a fresh Google DAI (SSAI) DASH session and records with N_m3u8DL-RE.
    
    Usage:
      python recorder.py
    
    Requires:
      - Python 3.10+
      - pip install requests
      - N_m3u8DL-RE on PATH (https://github.com/nilaoda/N_m3u8DL-RE/releases)
      - ffmpeg on PATH (for finalize / remux after stop)
      - Optional: shaka-packager on PATH (recommended; --use-shaka-packager)
    
    Controls:
      Ctrl+C  — stop recording early, then finalize + cleanup (script keeps going)
    
    Widevine:
      This stream is DRM-protected. The default KEY/KID pair below worked for the
      same event id. If decryption fails, refresh the key (browser CDM / your own
      extract) and pass --key KID:KEY or set TELEAMAZONAS_KEY.
    """
    
    from __future__ import annotations
    
    import argparse
    import os
    import random
    import re
    import shutil
    import signal
    import subprocess
    import sys
    import time
    import uuid
    from datetime import datetime
    from pathlib import Path
    
    import requests
    
    # Stable Google DAI event for Teleamazonas linear stream (from HAR / live page)
    EVENT_ID = "Z8uMUTUBSC6R09A92EKh1g"
    SSAI_URL = f"https://pubads.g.doubleclick.net/ssai/event/{EVENT_ID}/streams"
    
    # Default content key observed working for this event (KID:KEY, no dashes)
    # Override with env TELEAMAZONAS_KEY or --key
    DEFAULT_KEY = "aeb0e7ae0feb4dae87c9859bc0e0d239:3c6df9213b350a19371cc1b3fea59a70"
    
    UA = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/155.0.0.0 Safari/537.36"
    )
    
    PAGE_REF = "https://www.teleamazonas.com/teleamazonas-en-vivo/"
    
    MEDIA_EXTS = {".mkv", ".mp4", ".ts", ".m4s", ".cmfv", ".cmfa", ".mp4a", ".h264"}
    MANIFEST_EXTS = {".mpd", ".m3u8"}
    
    
    def eprint(*args, **kwargs):
        print(*args, file=sys.stderr, **kwargs)
    
    
    def find_n_m3u8dl() -> str:
        for name in ("N_m3u8DL-RE", "n_m3u8dl-re", "N_m3u8DL-RE.exe", "n_m3u8dl-re.exe"):
            path = shutil.which(name)
            if path:
                return path
        raise SystemExit(
            "N_m3u8DL-RE not found on PATH.\n"
            "Download a release for your OS from:\n"
            "  https://github.com/nilaoda/N_m3u8DL-RE/releases\n"
            "Put the binary on PATH, then re-run."
        )
    
    
    def find_ffmpeg() -> str | None:
        return shutil.which("ffmpeg")
    
    
    def prompt(text: str, default: str | None = None) -> str:
        if default is not None:
            suffix = f" [{default}]"
        else:
            suffix = ""
        while True:
            raw = input(f"{text}{suffix}: ").strip()
            if raw:
                return raw
            if default is not None:
                return default
            print("  (required)")
    
    
    def prompt_duration() -> str:
        """
        Returns HH:MM:SS string for --live-record-limit.
        Accepts: 30m, 1h, 90, 01:30:00, etc.
        """
        print("Duration examples: 30m  |  1h  |  90 (minutes)  |  00:30:00")
        raw = prompt("Duration", "30m")
        raw = raw.strip().lower()
        if re.fullmatch(r"\d{1,2}:\d{2}:\d{2}", raw):
            return raw
        if raw.endswith("h") and raw[:-1].isdigit():
            h = int(raw[:-1])
            return f"{h:02d}:00:00"
        if raw.endswith("m") and raw[:-1].isdigit():
            m = int(raw[:-1])
            h, m = divmod(m, 60)
            return f"{h:02d}:{m:02d}:00"
        if raw.isdigit():
            m = int(raw)
            h, m = divmod(m, 60)
            return f"{h:02d}:{m:02d}:00"
        return raw
    
    
    def prompt_format() -> str:
        print("Format: mkv  |  mp4  |  ts")
        fmt = prompt("Container format", "mkv").lower()
        if fmt not in {"mkv", "mp4", "ts"}:
            print("  unknown format, using mkv")
            return "mkv"
        return fmt
    
    
    def safe_filename(name: str) -> str:
        name = re.sub(r'[<>:"/\\|?*\x00-\x1f]+', "_", name).strip(" .")
        return name or "teleamazonas"
    
    
    def fetch_stream_session(timeout: float = 30.0) -> dict:
        """POST Google DAI SSAI to get a fresh stream_manifest (MPD)."""
        correlator = str(random.randint(10**15, 10**16 - 1))
        sid = str(uuid.uuid4()).upper()
        data = {
            "vpmute": "0",
            "api-key": "null",
            "auth-token": "null",
            "ctv": "0",
            "correlator": correlator,
            "ptt": "20",
            "osd": "2",
            "sdr": "1",
            "sdki": "445",
            "sdkv": "h.3.792.0",
            "ua": UA,
            "frm": "2",
            "sid": sid,
            "ssss": "gima",
            "ref": PAGE_REF,
            "url": "https://www.teleamazonas.com/",
            "gdpr": "0",
            "cookie_enabled": "1",
            "cdm": "mdstrm.com",
            "eoidce": "1",
        }
        headers = {
            "accept": "*/*",
            "content-type": "application/x-www-form-urlencoded;charset=utf-8",
            "origin": "https://mdstrm.com",
            "referer": "https://mdstrm.com/",
            "user-agent": UA,
        }
        r = requests.post(SSAI_URL, headers=headers, data=data, timeout=timeout)
        r.raise_for_status()
        js = r.json()
        if not js.get("stream_manifest"):
            raise RuntimeError(f"SSAI response missing stream_manifest: {js}")
        return js
    
    
    def normalize_key(key: str) -> str:
        """Accept KID:KEY with or without dashes; return lowercase no-dash form."""
        key = key.strip().replace(" ", "")
        if ":" not in key:
            raise ValueError("key must be KID:KEY")
        kid, k = key.split(":", 1)
        kid = kid.replace("-", "").lower()
        k = k.replace("-", "").lower()
        if len(kid) != 32 or len(k) != 32:
            raise ValueError("KID and KEY must each be 32 hex chars (16 bytes)")
        return f"{kid}:{k}"
    
    
    def build_cmd(
        nre: str,
        mpd: str,
        key: str,
        save_name: str,
        duration: str,
        fmt: str,
        use_shaka: bool,
        save_dir: Path,
        tmp_dir: Path,
    ) -> list[str]:
        cmd = [
            nre,
            mpd,
            "--key",
            key,
            # skip interactive stream picker (space/enter UI)
            "--auto-select",
            "-sv",
            "best",
            "-sa",
            "best",
            "-M",
            fmt,
            "--live-real-time-merge",
            "--live-pipe-mux",
            "--live-wait-time",
            "5",
            "--live-record-limit",
            duration,
            "--save-name",
            save_name,
            "--tmp-dir",
            str(tmp_dir),
            "--save-dir",
            str(save_dir),
            "--log-level",
            "INFO",
        ]
        has_shaka = bool(shutil.which("shaka-packager") or shutil.which("packager"))
        if use_shaka and has_shaka:
            cmd.append("--use-shaka-packager")
        elif use_shaka and not has_shaka:
            eprint("[!] shaka-packager not on PATH — continuing without --use-shaka-packager")
        cmd.extend(
            [
                "--header",
                f"User-Agent: {UA}",
                "--header",
                f"Referer: {PAGE_REF}",
                "--header",
                "Origin: https://www.teleamazonas.com",
            ]
        )
        return cmd
    
    
    def _kill_process_tree(proc: subprocess.Popen) -> None:
        """Hard-stop N_m3u8DL-RE and any children (Windows + Unix)."""
        if proc.poll() is not None:
            return
        pid = proc.pid
        try:
            if os.name == "nt":
                # /T = tree, /F = force — most reliable on Windows consoles
                subprocess.run(
                    ["taskkill", "/PID", str(pid), "/T", "/F"],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    check=False,
                )
            else:
                try:
                    os.killpg(os.getpgid(pid), signal.SIGTERM)
                except Exception:
                    proc.terminate()
                t0 = time.time()
                while proc.poll() is None and time.time() - t0 < 8:
                    time.sleep(0.2)
                if proc.poll() is None:
                    try:
                        os.killpg(os.getpgid(pid), signal.SIGKILL)
                    except Exception:
                        proc.kill()
        except Exception:
            try:
                proc.kill()
            except Exception:
                pass
    
    
    def _poll_stop_key() -> bool:
        """
        Non-blocking key check. True if user hit Q / S / Esc.
        Windows: msvcrt. Unix: select on stdin.
        """
        try:
            if os.name == "nt":
                import msvcrt
    
                while msvcrt.kbhit():
                    ch = msvcrt.getch()
                    # prefix for special keys — swallow second byte
                    if ch in (b"\x00", b"\xe0"):
                        if msvcrt.kbhit():
                            msvcrt.getch()
                        continue
                    if ch in (b"q", b"Q", b"s", b"S", b"\x1b", b"\x03"):
                        return True
                return False
            else:
                import select
    
                if not sys.stdin.isatty():
                    return False
                r, _, _ = select.select([sys.stdin], [], [], 0)
                if r:
                    ch = sys.stdin.read(1)
                    if ch.lower() in {"q", "s", "\x1b", "\x03"}:
                        return True
                return False
        except Exception:
            return False
    
    
    def run_recording(cmd: list[str]) -> tuple[bool, int]:
        """
        Run N_m3u8DL-RE. Stop with:
          • Q or S key (no Enter)
          • Ctrl+C (or Ctrl+Break on Windows)
        Child stdin is DEVNULL so the keyboard stays with this script.
        Returns (user_stopped, returncode).
        """
        stop_flag = {"set": False}
        prev_sigint = signal.getsignal(signal.SIGINT)
        prev_break = None
    
        def _request_stop():
            stop_flag["set"] = True
    
        def _on_sigint(signum, frame):
            _request_stop()
    
        signal.signal(signal.SIGINT, _on_sigint)
        if hasattr(signal, "SIGBREAK"):
            try:
                prev_break = signal.getsignal(signal.SIGBREAK)  # type: ignore[attr-defined]
                signal.signal(signal.SIGBREAK, _on_sigint)  # type: ignore[attr-defined]
            except Exception:
                prev_break = None
    
        kwargs: dict = {
            # keep keyboard on the parent — child must not own stdin
            "stdin": subprocess.DEVNULL,
        }
        if os.name == "nt":
            kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP  # type: ignore[attr-defined]
        else:
            kwargs["start_new_session"] = True
    
        print("[*] recording…")
        print("[*] stop: press  Q  (or S)   |   Ctrl+C")
        proc = subprocess.Popen(cmd, **kwargs)
        user_stopped = False
        returncode = 0
    
        try:
            while True:
                if stop_flag["set"] or _poll_stop_key():
                    user_stopped = True
                    print("\n[*] stop requested — killing recorder, then finalizing…")
                    _kill_process_tree(proc)
                    try:
                        returncode = proc.wait(timeout=15)
                    except subprocess.TimeoutExpired:
                        returncode = -9
                    time.sleep(1.0)
                    break
                rc = proc.poll()
                if rc is not None:
                    returncode = rc
                    break
                time.sleep(0.12)
        finally:
            try:
                signal.signal(signal.SIGINT, prev_sigint)
            except Exception:
                pass
            if prev_break is not None and hasattr(signal, "SIGBREAK"):
                try:
                    signal.signal(signal.SIGBREAK, prev_break)  # type: ignore[attr-defined]
                except Exception:
                    pass
    
        return user_stopped, returncode if returncode is not None else 0
    
    
    def collect_media_files(root: Path, save_name: str) -> list[Path]:
        """Find media-ish files produced for this save_name under root (recursive)."""
        if not root.exists():
            return []
        found: list[Path] = []
        for p in root.rglob("*"):
            if not p.is_file():
                continue
            if save_name not in p.name and save_name not in str(p.relative_to(root)):
                # still allow exact prefix matches in name
                if not p.name.startswith(save_name):
                    continue
            if p.suffix.lower() in MEDIA_EXTS or p.suffix.lower() in {".mkv", ".mp4", ".ts"}:
                found.append(p)
        # also direct non-recursive prefix in save dir
        for p in root.glob(f"{save_name}*"):
            if p.is_file() and p not in found:
                found.append(p)
        return found
    
    
    def pick_best_source(files: list[Path]) -> Path | None:
        """Prefer real containers; largest file wins. Ignore tiny/manifest junk."""
        if not files:
            return None
        usable = [
            p
            for p in files
            if p.exists()
            and p.suffix.lower() not in MANIFEST_EXTS
            and p.stat().st_size >= 64 * 1024
        ]
        if not usable:
            return None
        preferred = [p for p in usable if p.suffix.lower() in {".mkv", ".mp4", ".ts"}]
        pool = preferred or usable
        return max(pool, key=lambda p: p.stat().st_size)
    
    
    def finalize_with_ffmpeg(
        source: Path,
        save_dir: Path,
        save_name: str,
        fmt: str,
    ) -> Path | None:
        """
        Silent remux/finalize to a clean file.
        Returns final path or None.
        """
        ffmpeg = find_ffmpeg()
        final = save_dir / f"{save_name}.{fmt}"
    
        # if source is already the desired container and looks complete, just move/rename
        if source.resolve() == final.resolve() and source.stat().st_size > 0:
            return final
    
        if not ffmpeg:
            # no ffmpeg — best-effort rename/copy
            if source.suffix.lower() == f".{fmt}":
                if source.resolve() != final.resolve():
                    shutil.move(str(source), str(final))
                return final if final.exists() else source
            eprint("[!] ffmpeg not on PATH — leaving raw file as-is")
            return source
    
        # write to temp then replace
        tmp_out = save_dir / f".{save_name}.finalizing.{fmt}"
        cmd = [
            ffmpeg,
            "-y",
            "-v",
            "error",
            "-i",
            str(source),
            "-c",
            "copy",
            "-movflags",
            "+faststart" if fmt == "mp4" else "none",
            str(tmp_out),
        ]
        # movflags none is invalid — fix for non-mp4
        if fmt != "mp4":
            cmd = [
                ffmpeg,
                "-y",
                "-v",
                "error",
                "-i",
                str(source),
                "-c",
                "copy",
                str(tmp_out),
            ]
    
        try:
            r = subprocess.run(
                cmd,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                check=False,
            )
        except FileNotFoundError:
            eprint("[!] ffmpeg missing at run time")
            return source
    
        if r.returncode != 0 or not tmp_out.exists() or tmp_out.stat().st_size == 0:
            if tmp_out.exists():
                tmp_out.unlink(missing_ok=True)
            # keep source
            if source.suffix.lower() == f".{fmt}" and source.resolve() != final.resolve():
                try:
                    shutil.move(str(source), str(final))
                    return final
                except Exception:
                    return source
            return source
    
        if final.exists() and final.resolve() != tmp_out.resolve():
            final.unlink(missing_ok=True)
        tmp_out.replace(final)
        return final
    
    
    def cleanup_junk(save_dir: Path, tmp_dir: Path, save_name: str, keep: Path | None) -> None:
        """Remove temp dirs and leftover fragments; never delete the keep file."""
        keep_resolved = keep.resolve() if keep and keep.exists() else None
    
        # tmp dir for this recording
        if tmp_dir.exists():
            try:
                shutil.rmtree(tmp_dir, ignore_errors=True)
            except Exception:
                pass
    
        # leftovers in save_dir matching name but not final
        if save_dir.exists():
            for p in save_dir.glob(f"{save_name}*"):
                try:
                    if keep_resolved and p.resolve() == keep_resolved:
                        continue
                    if p.is_file():
                        # drop sidecar / part / duplicate
                        if p.suffix.lower() in {".part", ".tmp", ".bak"} or ".finalizing." in p.name:
                            p.unlink(missing_ok=True)
                        elif keep_resolved and p.resolve() != keep_resolved:
                            # other partial containers for same name
                            if p.suffix.lower() in MEDIA_EXTS:
                                # only remove if clearly smaller fragment or different suffix junk
                                if p.stat().st_size < 1024 * 64:
                                    p.unlink(missing_ok=True)
                    elif p.is_dir():
                        shutil.rmtree(p, ignore_errors=True)
                except Exception:
                    pass
    
        # empty top tmp_record
        top_tmp = Path("tmp_record")
        if top_tmp.exists():
            try:
                if not any(top_tmp.iterdir()):
                    top_tmp.rmdir()
            except Exception:
                pass
    
    
    def interactive() -> argparse.Namespace:
        print("=" * 60)
        print("  Teleamazonas en vivo — recorder")
        print("  Ctrl+C stops recording early, then finalizes")
        print("=" * 60)
        print()
        name = prompt("Recording name", f"teleamazonas_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
        duration = prompt_duration()
        fmt = prompt_format()
        key_default = os.environ.get("TELEAMAZONAS_KEY", DEFAULT_KEY)
        print(f"DRM key default: {key_default[:8]}… (env TELEAMAZONAS_KEY or Enter to keep)")
        key_raw = prompt("KID:KEY", key_default)
        return argparse.Namespace(
            name=name,
            duration=duration,
            format=fmt,
            key=key_raw,
            yes=False,
            no_shaka=False,
        )
    
    
    def parse_args() -> argparse.Namespace:
        p = argparse.ArgumentParser(description="Record Teleamazonas live (DAI DASH + Widevine key)")
        p.add_argument("-n", "--name", help="Output name (no extension)")
        p.add_argument("-d", "--duration", help="Limit HH:MM:SS or 30m / 1h")
        p.add_argument("-f", "--format", choices=["mkv", "mp4", "ts"], help="Container")
        p.add_argument("--key", help="Widevine KID:KEY (32hex:32hex)")
        p.add_argument("-y", "--yes", action="store_true", help="No confirm prompt")
        p.add_argument("--no-shaka", action="store_true", help="Do not pass --use-shaka-packager")
        args, _ = p.parse_known_args()
        if args.name and args.duration and args.format:
            if not args.key:
                args.key = os.environ.get("TELEAMAZONAS_KEY", DEFAULT_KEY)
            return args
        if len(sys.argv) == 1 or (not args.name and not args.duration):
            return interactive()
        if not args.name:
            args.name = prompt("Recording name", f"teleamazonas_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
        if not args.duration:
            args.duration = prompt_duration()
        else:
            d = args.duration.lower()
            if d.endswith("m") and d[:-1].isdigit():
                m = int(d[:-1])
                h, m = divmod(m, 60)
                args.duration = f"{h:02d}:{m:02d}:00"
            elif d.endswith("h") and d[:-1].isdigit():
                args.duration = f"{int(d[:-1]):02d}:00:00"
            elif d.isdigit():
                m = int(d)
                h, m = divmod(m, 60)
                args.duration = f"{h:02d}:{m:02d}:00"
        if not args.format:
            args.format = prompt_format()
        if not args.key:
            args.key = os.environ.get("TELEAMAZONAS_KEY", DEFAULT_KEY)
        return args
    
    
    def main() -> int:
        args = parse_args()
        nre = find_n_m3u8dl()
        try:
            key = normalize_key(args.key)
        except ValueError as exc:
            eprint(f"bad key: {exc}")
            return 2
    
        save_name = safe_filename(args.name)
        duration = args.duration
        fmt = args.format
        save_dir = Path("recordings")
        tmp_dir = Path("tmp_record") / save_name
    
        print()
        print("[*] requesting fresh DAI stream session…")
        try:
            session = fetch_stream_session()
        except Exception as exc:
            eprint(f"SSAI failed: {exc}")
            return 1
    
        mpd = session["stream_manifest"]
        stream_id = session.get("stream_id", "?")
        print(f"[+] stream_id : {stream_id}")
        print(f"[+] manifest  : {mpd}")
        print(f"[+] key       : {key[:8]}…:{key.split(':')[1][:8]}…")
        print(f"[+] duration  : {duration}")
        print(f"[+] format    : {fmt}")
        print(f"[+] save name : {save_name}")
        print(f"[+] stop early: Ctrl+C  (then auto-finalize)")
        print()
    
        if not args.yes:
            ok = input("Start recording? [Y/n] ").strip().lower()
            if ok in {"n", "no"}:
                print("aborted")
                return 0
    
        save_dir.mkdir(parents=True, exist_ok=True)
        tmp_dir.mkdir(parents=True, exist_ok=True)
    
        cmd = build_cmd(
            nre,
            mpd,
            key,
            save_name,
            duration,
            fmt,
            use_shaka=not getattr(args, "no_shaka", False),
            save_dir=save_dir,
            tmp_dir=tmp_dir,
        )
        print(" ", subprocess.list2cmdline(cmd) if os.name == "nt" else " ".join(cmd))
        print()
    
        try:
            user_stopped, returncode = run_recording(cmd)
        except FileNotFoundError:
            eprint("failed to execute N_m3u8DL-RE")
            return 1
    
        if user_stopped:
            print("[*] recorder stopped by user")
        elif returncode not in (0, None):
            eprint()
            eprint(f"N_m3u8DL-RE exit code {returncode}")
            eprint("Will still try to finalize any partial output…")
            eprint("If empty: KEY rotated, MPD died, or muxer missing.")
    
        # gather outputs from save + tmp
        candidates = collect_media_files(save_dir, save_name) + collect_media_files(tmp_dir, save_name)
        source = pick_best_source(candidates)
    
        final_path: Path | None = None
        if source and source.exists() and source.stat().st_size > 0:
            print("[*] finalizing (silent ffmpeg remux)…")
            final_path = finalize_with_ffmpeg(source, save_dir, save_name, fmt)
            if final_path and final_path.exists():
                size_mb = final_path.stat().st_size / (1024 * 1024)
                print(f"[+] saved: {final_path}  ({size_mb:.1f} MB)")
            else:
                eprint("[!] finalize produced nothing useful")
        else:
            eprint("[!] no media file found to finalize")
    
        print("[*] cleaning temp files…")
        cleanup_junk(save_dir, tmp_dir, save_name, final_path)
    
        if final_path and final_path.exists():
            print(f"[*] done — {final_path}")
            return 0
        return 1 if returncode not in (0, None) and not user_stopped else 0
    
    
    if __name__ == "__main__":
        raise SystemExit(main())
    Quote Quote  
  13. I assume it's a Python script, but that just complicates things further.

    On which operating system does the script run? As I mentioned earlier, I use Windows.

    What is the download and tools paths? I handle everything on drives other than C:\, I use .bat scripts with variables to automate processes and custom paths.

    This isn't going to help me, especially since I always have issues with Python and package installation.
    Quote Quote  
  14. I still don't know what to do, and nobody has given a correct answer so far.
    Quote Quote  
  15. Use a script.
    Quote Quote  
  16. Originally Posted by LZAA View Post
    Use a script.
    What script?
    Quote Quote  
  17. Originally Posted by naoto89 View Post
    I assume it's a Python script, but that just complicates things further.

    On which operating system does the script run? As I mentioned earlier, I use Windows.

    What is the download and tools paths? I handle everything on drives other than C:\, I use .bat scripts with variables to automate processes and custom paths.

    This isn't going to help me, especially since I always have issues with Python and package installation.
    You don't have to install anything (only python should be installed in your pc)
    https://files.videohelp.com/u/315187/teleamazons_recorder.exe
    Help others the way you’d want to be helped. Don’t just take—learn to give back too.
    Quote Quote  
  18. "https://mrs.shift72.com/77444323-e95b-4351-8331-2cfb4a79f5ce.mpd?pt=eyJhbGciOiJIUzI1NiIsInR5cCI6Ik pXVCJ9.eyJhZ2UiOjMxLCJiaWQiOm51bGwsImNoa2R2YyI6dHJ 1ZSwiY2lkIjozMzEsImNvcmlkIjoiIiwiZGVscG9sIjoidXJuO nNoaWZ0NzIiLCJleHAiOjE3OTE5ODYzNDcsImZ3IjpudWxsLCJ naWQiOjIsImdyIjoiZGsiLCJpYXQiOjE3OTE4OTk5NDcsImlzc yI6Imh0dHA6Ly9zaGlmdDcyLmNvbS8iLCJsaWQiOjIyNDg3OTc xLCJtaWQiOjIyMTQyNCwib3duIjoicmVudCIsInBpZCI6MCwic GxheWlkIjoiYzFhOTAwZGUtMGRmOS00MmQ0LTgwZGQtYzZhYzY 3NGE1YzJjIiwicWlkIjoiaGQiLCJzNzJ1c3IiOnRydWUsInNrY XZhaWwiOmZhbHNlLCJzbHVnIjoiL2ZpbG0vMTc2ODM1Iiwic3V iIjoiNDIwNjE4NCIsInRsbXRyeSI6dHJ1ZSwidXNyIjoiNDIwN jE4NCIsIndhdGVybWFya2luZ19tZXRob2QiOjB9.MLYfrDTwet _2UuCnyoMGDeNTt-LzAE5lI-OwldeDJN8"

    This video is DRM protected
    URL?
    Quote Quote  
  19. 'naoto89'

    use this script
    Quote Quote  
  20. Originally Posted by xangetsue View Post
    Originally Posted by naoto89 View Post
    I assume it's a Python script, but that just complicates things further.

    On which operating system does the script run? As I mentioned earlier, I use Windows.

    What is the download and tools paths? I handle everything on drives other than C:\, I use .bat scripts with variables to automate processes and custom paths.

    This isn't going to help me, especially since I always have issues with Python and package installation.
    You don't have to install anything (only python should be installed in your pc)
    https://files.videohelp.com/u/315187/teleamazons_recorder.exe
    I have Python installed on my Windows PC, but it doesn't work reliably for me; I can't wait until it runs correctly with every error that Python shows

    Also, since I don't understand how that script works, I won't be able to customize it with things like the stream URLs (if you noticed, there are 2 streams on the same page, one called "Quito" and the other "Guayaquil"), get the keys (which I also don't know how to do), the save paths, and adding the run of a player that displays the stream at the same time as it downloads it. Previously, I used this batch script to download the video using FFmpeg and FFplay when the earlier stream had an m3u8 link.

    Code:
    "%ffmpeg%" ^
      -thread_queue_size 4096 ^
      -user_agent "%UA%" ^
      -referer "https://www.teleamazonas.com/" ^
      -i "%URL%" ^
      -map 0:p:3 ^
      -c copy ^
      -reconnect true ^
      -reconnect_at_eof true ^
      -reconnect_on_network_error true ^
      -reconnect_on_http_error "4xx, 5xx" ^
      -reconnect_streamed true ^
      -reconnect_delay_max 4294 ^
      -multiple_requests true "%OUTPUT%" ^
      -f mpegts ^
      - | "%ffplay%" - ^
      -x 854 ^
      -y 480 ^
      -af "volume=3" ^
      -window_title "%CSTFFP%"
    Quote Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!