202 lines
7.0 KiB
Python
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()
|