This commit is contained in:
2026-06-17 11:53:54 +02:00
parent 9edfddae95
commit dc07e19179
20 changed files with 2294 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# Video — stahování videí
## stahni_video.py
Stahuje videa z Vimea, YouTube a dalších webů přes **yt-dlp**. Nejlepší dostupná
kvalita, sloučení video+audio do `.mp4`. Soukromá / nedostupná videa sám pozná
a přeskočí (nespadne).
### Závislosti (jednorázově)
```bat
python -m pip install -U yt-dlp static-ffmpeg
```
- **yt-dlp** — vlastní downloader.
- **static-ffmpeg** — dodá `ffmpeg.exe` + `ffprobe.exe` (v systému ffmpeg není).
Skript si přes `static_ffmpeg.add_paths()` cestu nastaví sám; binárky se
stáhnou při prvním běhu do `site-packages\static_ffmpeg\bin\`.
### Použití
```bat
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
python stahni_video.py -o D:\nekam URL # jiný výstupní adresář
```
Výchozí výstupní adresář je tento (`Video/`). Soubory: `%(title)s [%(id)s].mp4`.
### Jak pozná soukromé/nedostupné video
yt-dlp vyhodí `DownloadError` s textem chyby. Funkce `klasifikuj_chybu()` hledá
v textu známé fráze (`private video`, `video unavailable`, `removed`,
`members-only`, …) a vrátí český popis → video se přeskočí. Jiné chyby (síť,
chybí ffmpeg) se vypíšou jako `[CHYBA]`, ale běh pokračuje na další URL.
Na konci se vypíše souhrn (staženo / přeskočeno / chyby).
### Poznámky / úskalí
- **Soukromé YouTube video opravdu nejde stáhnout**, pokud k němu přihlášený
účet nemá udělený přístup — to je záměr, skript ho jen přeskočí.
- **Diakritika v názvech**: cesty se zkomolí, když se předávají Windows binárce
přes Bug Bash pipe; v běžné konzoli (cp1250) je vše v pořádku.
- **Vimeo** dává oddělené video/audio HLS streamy → ffmpeg je nutný pro sloučení.
- Při prvním běhu může yt-dlp varovat na chybějící JavaScript runtime (deno);
pro běžná veřejná videa to nevadí.
+201
View File
@@ -0,0 +1,201 @@
#!/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()
Binary file not shown.