z230
This commit is contained in:
@@ -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í.
|
||||
@@ -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.
Reference in New Issue
Block a user