Files
ordinaceprojekt/Video/stahni_video.py
T
2026-06-17 11:53:54 +02:00

202 lines
7.0 KiB
Python

#!/usr/bin/env python3
"""
stahni_video.py — stahování videí (Vimeo, YouTube, …) přes yt-dlp.
Co umí:
* Automaticky nastaví cestu k ffmpeg (přes balík static-ffmpeg) — netřeba ho
mít v systému.
* Stáhne nejlepší dostupnou kvalitu a sloučí video+audio do .mp4.
* Pokud je video SOUKROMÉ / nedostupné / odstraněné, sám to pozná, vypíše
srozumitelnou hlášku a přeskočí ho (nespadne, jede dál na další URL).
Použití:
python stahni_video.py URL [URL2 ...]
python stahni_video.py # vezme URL z urls.txt (1 na řádek)
python stahni_video.py --cookies-from-browser firefox URL # video za přihlášením
Instalace závislostí (jednorázově):
python -m pip install -U yt-dlp static-ffmpeg
"""
import argparse
import os
import shutil
import sys
from pathlib import Path
# Pojistka: ať výpis nikdy nespadne na znaku, který kódování konzole nezná
# (zachová kódování konzole, jen neznámý znak escapne místo pádu programu).
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(errors="backslashreplace")
except Exception:
pass
SKRIPT_DIR = Path(__file__).resolve().parent
# --- důvody, proč video NEJDE stáhnout (→ přeskočit, ne padat) ----------------
# klíč hledáme (case-insensitive) v textu chyby od yt-dlp
DUVODY_PRESKOCIT = [
("private video", "video je soukromé"),
("video is private", "video je soukromé"),
("this is a private video", "video je soukromé"),
("video unavailable", "video není dostupné"),
("this video is unavailable", "video není dostupné"),
("video has been removed", "video bylo odstraněno"),
("removed by the uploader", "video odstranil autor"),
("no longer available", "video už není dostupné"),
("members-only", "jen pro členy kanálu"),
("available to members", "jen pro členy kanálu"),
("account associated with this video has been terminated",
"účet autora byl zrušen"),
("has been terminated", "účet autora byl zrušen"),
("blocked it on copyright", "blokováno kvůli autorským právům"),
("not available in your country", "nedostupné ve tvé zemi"),
("not available on this app", "nedostupné pro tohoto klienta"),
("sign in to confirm your age", "věkově omezené (nutné přihlášení)"),
("requires payment", "placené video"),
("this live event will begin", "živý přenos zatím nezačal"),
("premieres in", "video bude teprve uvedeno (premiéra)"),
]
def klasifikuj_chybu(msg: str):
"""Vrátí český popis důvodu k přeskočení, nebo None pokud jde o jinou chybu."""
m = msg.lower()
for klic, popis in DUVODY_PRESKOCIT:
if klic in m:
return popis
return None
class _TichyLogger:
"""Potlačí ukecaný výpis yt-dlp; chyby si hlídáme sami přes výjimky."""
def debug(self, msg):
pass
def info(self, msg):
pass
def warning(self, msg):
pass
def error(self, msg):
pass
def _progress_hook(d):
if d.get("status") == "downloading":
pct = (d.get("_percent_str") or "").strip()
spd = (d.get("_speed_str") or "").strip()
print(f"\r stahuji {pct} {spd} ", end="", flush=True)
elif d.get("status") == "finished":
print(f"\r staženo, zpracovávám… ")
def priprav_ffmpeg():
"""Zajistí ffmpeg/ffprobe a vrátí adresář s binárkami (nebo None)."""
try:
import static_ffmpeg
static_ffmpeg.add_paths() # přidá ffmpeg/ffprobe do PATH (1. běh = stáhne)
except ImportError:
pass
ff = shutil.which("ffmpeg")
if ff:
return os.path.dirname(ff)
print("UPOZORNĚNÍ: ffmpeg nenalezen — sloučení video+audio nemusí fungovat.")
print(" Nainstaluj: python -m pip install -U static-ffmpeg")
return None
def nacti_urls(args_urls):
if args_urls:
return args_urls
soubor = SKRIPT_DIR / "urls.txt"
if soubor.exists():
radky = [r.strip() for r in soubor.read_text(encoding="utf-8").splitlines()]
return [r for r in radky if r and not r.startswith("#")]
return []
def stahni(urls, out_dir: Path, cookies_browser=None):
try:
import yt_dlp
from yt_dlp.utils import DownloadError
except ImportError:
sys.exit("Chybí yt-dlp. Nainstaluj: python -m pip install -U yt-dlp")
ff_dir = priprav_ffmpeg()
out_dir.mkdir(parents=True, exist_ok=True)
ydl_opts = {
"outtmpl": str(out_dir / "%(title)s [%(id)s].%(ext)s"),
"format": "bestvideo*+bestaudio/best",
"merge_output_format": "mp4",
"logger": _TichyLogger(),
"progress_hooks": [_progress_hook],
"noprogress": True, # vlastní progress řešíme hookem
"noplaylist": True,
}
if ff_dir:
ydl_opts["ffmpeg_location"] = ff_dir
if cookies_browser:
ydl_opts["cookiesfrombrowser"] = (cookies_browser,)
stazeno, preskoceno, chyby = 0, [], []
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
for i, url in enumerate(urls, 1):
print(f"\n[{i}/{len(urls)}] {url}")
try:
info = ydl.extract_info(url, download=True)
nazev = info.get("title", url) if info else url
print(f" [HOTOVO] {nazev}")
stazeno += 1
except DownloadError as e:
duvod = klasifikuj_chybu(str(e))
if duvod:
print(f" [PRESKOCENO] {duvod}")
preskoceno.append((url, duvod))
else:
strucne = str(e).split("\n")[0]
print(f" [CHYBA] {strucne}")
chyby.append((url, strucne))
except Exception as e: # nečekané — taky nezhasnout celý běh
print(f" [CHYBA] {e}")
chyby.append((url, str(e)))
print("\n=== SOUHRN ===")
print(f" staženo: {stazeno}")
print(f" přeskočeno: {len(preskoceno)}")
for url, duvod in preskoceno:
print(f" - {url} ({duvod})")
if chyby:
print(f" chyby: {len(chyby)}")
for url, msg in chyby:
print(f" - {url} ({msg})")
return stazeno, preskoceno, chyby
def main():
p = argparse.ArgumentParser(
description="Stáhne videa přes yt-dlp; soukromá/nedostupná sám přeskočí.")
p.add_argument("urls", nargs="*", help="URL videí (nebo nech prázdné a použij urls.txt)")
p.add_argument("-o", "--out-dir", default=str(SKRIPT_DIR),
help="výstupní adresář (výchozí: tento adresář)")
p.add_argument("--cookies-from-browser", dest="cookies",
help="prohlížeč pro cookies u videí za přihlášením (firefox/chrome/edge…)")
a = p.parse_args()
urls = nacti_urls(a.urls)
if not urls:
sys.exit("Nezadal jsi žádné URL. Předej je jako argumenty nebo do urls.txt.")
stahni(urls, Path(a.out_dir), cookies_browser=a.cookies)
if __name__ == "__main__":
main()