urocibg revisou este gist 1 day ago. Ir para a revisão
1 file changed, 2 insertions, 2 deletions
FedyaMP3.py
| @@ -1,9 +1,9 @@ | |||
| 1 | 1 | #!/usr/bin/env python3 | |
| 2 | 2 | """ | |
| 3 | 3 | ╔══════════════════════════════════════════════════════════╗ | |
| 4 | - | ║ Fedya-MP3 Downloader ║ | |
| 4 | + | ║ Fedya-MP3 Downloader | |
| 5 | 5 | ║ YouTube & Facebook → MP3 Downloader ║ | |
| 6 | - | ║ https://itpraktika.com/ ║ | |
| 6 | + | ║ https://itpraktika.com/ | |
| 7 | 7 | ╚══════════════════════════════════════════════════════════╝ | |
| 8 | 8 | ||
| 9 | 9 | Изисквания: | |
urocibg revisou este gist 1 day ago. Ir para a revisão
1 file changed, 1 insertion, 1 deletion
FedyaMP3.py
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ╔══════════════════════════════════════════════════════════╗ | |
| 4 | 4 | ║ Fedya-MP3 Downloader ║ | |
| 5 | 5 | ║ YouTube & Facebook → MP3 Downloader ║ | |
| 6 | - | ║ github.com/you ║ | |
| 6 | + | ║ https://itpraktika.com/ ║ | |
| 7 | 7 | ╚══════════════════════════════════════════════════════════╝ | |
| 8 | 8 | ||
| 9 | 9 | Изисквания: | |
urocibg revisou este gist 1 day ago. Ir para a revisão
1 file changed, 1 insertion, 1 deletion
FedyaMP3.py
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | #!/usr/bin/env python3 | |
| 2 | 2 | """ | |
| 3 | 3 | ╔══════════════════════════════════════════════════════════╗ | |
| 4 | - | ║ M E D I A S N A T C H ║ | |
| 4 | + | ║ Fedya-MP3 Downloader ║ | |
| 5 | 5 | ║ YouTube & Facebook → MP3 Downloader ║ | |
| 6 | 6 | ║ github.com/you ║ | |
| 7 | 7 | ╚══════════════════════════════════════════════════════════╝ | |
urocibg revisou este gist 1 day ago. Ir para a revisão
1 file changed, 481 insertions
FedyaMP3.py(arquivo criado)
| @@ -0,0 +1,481 @@ | |||
| 1 | + | #!/usr/bin/env python3 | |
| 2 | + | """ | |
| 3 | + | ╔══════════════════════════════════════════════════════════╗ | |
| 4 | + | ║ M E D I A S N A T C H ║ | |
| 5 | + | ║ YouTube & Facebook → MP3 Downloader ║ | |
| 6 | + | ║ github.com/you ║ | |
| 7 | + | ╚══════════════════════════════════════════════════════════╝ | |
| 8 | + | ||
| 9 | + | Изисквания: | |
| 10 | + | pip install yt-dlp colorama | |
| 11 | + | + FFmpeg инсталиран (или ffmpeg.exe в същата папка) | |
| 12 | + | """ | |
| 13 | + | ||
| 14 | + | import os | |
| 15 | + | import sys | |
| 16 | + | import re | |
| 17 | + | import subprocess | |
| 18 | + | import time | |
| 19 | + | from pathlib import Path | |
| 20 | + | from datetime import datetime | |
| 21 | + | ||
| 22 | + | # ── Colorama (Windows ANSI support) ────────────────────────────────────────── | |
| 23 | + | try: | |
| 24 | + | from colorama import just_fix_windows_console | |
| 25 | + | just_fix_windows_console() | |
| 26 | + | except ImportError: | |
| 27 | + | pass # не е критично | |
| 28 | + | ||
| 29 | + | # ── Цветова палитра ─────────────────────────────────────────────────────────── | |
| 30 | + | class C: | |
| 31 | + | RESET = "\033[0m" | |
| 32 | + | BOLD = "\033[1m" | |
| 33 | + | DIM = "\033[2m" | |
| 34 | + | # основни | |
| 35 | + | WHITE = "\033[97m" | |
| 36 | + | CYAN = "\033[96m" | |
| 37 | + | BLUE = "\033[94m" | |
| 38 | + | GREEN = "\033[92m" | |
| 39 | + | YELLOW = "\033[93m" | |
| 40 | + | RED = "\033[91m" | |
| 41 | + | MAGENTA = "\033[95m" | |
| 42 | + | GRAY = "\033[90m" | |
| 43 | + | # акцентни | |
| 44 | + | BG_DARK = "\033[40m" | |
| 45 | + | ||
| 46 | + | def c(*codes): | |
| 47 | + | return "".join(codes) | |
| 48 | + | ||
| 49 | + | def paint(text, *codes): | |
| 50 | + | return c(*codes) + str(text) + C.RESET | |
| 51 | + | ||
| 52 | + | ||
| 53 | + | # ── Банер ───────────────────────────────────────────────────────────────────── | |
| 54 | + | BANNER = f""" | |
| 55 | + | {c(C.CYAN, C.BOLD)} | |
| 56 | + | ______ ______ _______ __ __ __ _____ ____ | |
| 57 | + | | ____| ____| __ \ \ / //\ | \/ | __ \___ \ | |
| 58 | + | | |__ | |__ | | | \ \_/ // \ | \ / | |__) |__) | | |
| 59 | + | | __| | __| | | | |\ // /\ \ | |\/| | ___/|__ < | |
| 60 | + | | | | |____| |__| | | |/ ____ \ | | | | | ___) | | |
| 61 | + | |_| |______|_____/ |_/_/ \_\ |_| |_|_| |____/ | |
| 62 | + | ||
| 63 | + | ||
| 64 | + | ||
| 65 | + | {C.RESET}{c(C.BLUE, C.BOLD)} | |
| 66 | + | ────────────────────────────────────────────────────────── | |
| 67 | + | YouTube & Facebook → MP3 Downloader | |
| 68 | + | ────────────────────────────────────────────────────────── | |
| 69 | + | {C.RESET}""" | |
| 70 | + | ||
| 71 | + | def banner(): | |
| 72 | + | os.system("cls" if sys.platform == "win32" else "clear") | |
| 73 | + | print(BANNER) | |
| 74 | + | ||
| 75 | + | ||
| 76 | + | # ── Проверка / инсталация на зависимости ───────────────────────────────────── | |
| 77 | + | def ensure_dependencies(): | |
| 78 | + | missing = [] | |
| 79 | + | try: | |
| 80 | + | import yt_dlp # noqa | |
| 81 | + | except ImportError: | |
| 82 | + | missing.append("yt-dlp") | |
| 83 | + | try: | |
| 84 | + | import colorama # noqa | |
| 85 | + | except ImportError: | |
| 86 | + | missing.append("colorama") | |
| 87 | + | ||
| 88 | + | if missing: | |
| 89 | + | print(paint(f" ⚙ Инсталирам: {', '.join(missing)} …", C.YELLOW)) | |
| 90 | + | subprocess.run( | |
| 91 | + | [sys.executable, "-m", "pip", "install", "--quiet"] + missing, | |
| 92 | + | check=True | |
| 93 | + | ) | |
| 94 | + | print(paint(" ✓ Готово.\n", C.GREEN)) | |
| 95 | + | ||
| 96 | + | ||
| 97 | + | # ── Проверка за нова версия на yt-dlp ──────────────────────────────────────── | |
| 98 | + | def check_ytdlp_update(): | |
| 99 | + | """ | |
| 100 | + | Сравнява инсталираната версия на yt-dlp с последната в PyPI. | |
| 101 | + | Ако има по-нова – пита потребителя дали да обнови. | |
| 102 | + | Работи в отделна нишка, за да не бави стартирането. | |
| 103 | + | """ | |
| 104 | + | import threading | |
| 105 | + | ||
| 106 | + | def _check(): | |
| 107 | + | try: | |
| 108 | + | import urllib.request | |
| 109 | + | import json | |
| 110 | + | import yt_dlp | |
| 111 | + | ||
| 112 | + | installed = yt_dlp.version.__version__ | |
| 113 | + | ||
| 114 | + | url = "https://pypi.org/pypi/yt-dlp/json" | |
| 115 | + | req = urllib.request.Request(url, headers={"User-Agent": "MediaSnatch/1.0"}) | |
| 116 | + | with urllib.request.urlopen(req, timeout=5) as resp: | |
| 117 | + | data = json.loads(resp.read()) | |
| 118 | + | latest = data["info"]["version"] | |
| 119 | + | ||
| 120 | + | def parse_ver(v): | |
| 121 | + | """2026.03.17 → (2026, 3, 17) за коректно сравнение""" | |
| 122 | + | try: | |
| 123 | + | return tuple(int(x) for x in v.split(".")) | |
| 124 | + | except ValueError: | |
| 125 | + | return (0,) | |
| 126 | + | ||
| 127 | + | if parse_ver(installed) >= parse_ver(latest): | |
| 128 | + | print( | |
| 129 | + | f" {paint('✓', C.GREEN)} yt-dlp {paint(installed, C.CYAN)} " | |
| 130 | + | f"{paint('– актуална версия', C.GRAY)}" | |
| 131 | + | ) | |
| 132 | + | else: | |
| 133 | + | print( | |
| 134 | + | f"\n {paint('⚠ Налична е нова версия на yt-dlp!', C.YELLOW, C.BOLD)}\n" | |
| 135 | + | f" Инсталирана : {paint(installed, C.RED)}\n" | |
| 136 | + | f" Нова : {paint(latest, C.GREEN, C.BOLD)}\n" | |
| 137 | + | ) | |
| 138 | + | ans = input( | |
| 139 | + | paint(" Обновявам сега? [Y/n]: ", C.BOLD) | |
| 140 | + | ).strip().lower() | |
| 141 | + | ||
| 142 | + | if ans in {"", "y", "yes", "да"}: | |
| 143 | + | print(paint(" ⚙ Обновявам yt-dlp…", C.YELLOW)) | |
| 144 | + | result = subprocess.run( | |
| 145 | + | [sys.executable, "-m", "pip", "install", "--upgrade", "--quiet", "yt-dlp"], | |
| 146 | + | capture_output=True, text=True | |
| 147 | + | ) | |
| 148 | + | if result.returncode == 0: | |
| 149 | + | print(paint(f" ✓ yt-dlp обновен до {latest}\n", C.GREEN, C.BOLD)) | |
| 150 | + | else: | |
| 151 | + | print(paint(" ✗ Обновяването неуспешно. Опитай ръчно: pip install -U yt-dlp\n", C.RED)) | |
| 152 | + | else: | |
| 153 | + | print(paint( | |
| 154 | + | f" ℹ Пропускам. Ако сваляниeто гърми, пусни:\n" | |
| 155 | + | f" pip install -U yt-dlp\n", | |
| 156 | + | C.GRAY | |
| 157 | + | )) | |
| 158 | + | ||
| 159 | + | except Exception: | |
| 160 | + | # Мълчим при грешка – няма интернет или PyPI timeout; не е критично | |
| 161 | + | pass | |
| 162 | + | ||
| 163 | + | t = threading.Thread(target=_check, daemon=True) | |
| 164 | + | t.start() | |
| 165 | + | t.join(timeout=7) # изчакваме макс. 7 секунди; после продължаваме | |
| 166 | + | ||
| 167 | + | ||
| 168 | + | # ── FFmpeg локация ──────────────────────────────────────────────────────────── | |
| 169 | + | def find_ffmpeg() -> str | None: | |
| 170 | + | """Търси ffmpeg: в PATH, до скрипта/exe-то.""" | |
| 171 | + | import shutil | |
| 172 | + | ||
| 173 | + | # 1. В PATH | |
| 174 | + | if shutil.which("ffmpeg"): | |
| 175 | + | return None # yt-dlp ще го намери сам | |
| 176 | + | ||
| 177 | + | # 2. До .exe (PyInstaller _MEIPASS) или до .py | |
| 178 | + | base = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) | |
| 179 | + | candidate = os.path.join(base, "ffmpeg.exe" if sys.platform == "win32" else "ffmpeg") | |
| 180 | + | if os.path.isfile(candidate): | |
| 181 | + | return candidate | |
| 182 | + | ||
| 183 | + | return "NOT_FOUND" | |
| 184 | + | ||
| 185 | + | ||
| 186 | + | # ── Десктоп ─────────────────────────────────────────────────────────────────── | |
| 187 | + | def get_desktop() -> Path: | |
| 188 | + | if sys.platform == "win32": | |
| 189 | + | desktop = Path(os.environ.get("USERPROFILE", Path.home())) / "Desktop" | |
| 190 | + | else: | |
| 191 | + | desktop = Path.home() / "Desktop" | |
| 192 | + | ||
| 193 | + | if not desktop.exists(): | |
| 194 | + | desktop = Path.home() / "Downloads" | |
| 195 | + | desktop.mkdir(exist_ok=True) | |
| 196 | + | ||
| 197 | + | # Подпапка Fedya-MP3 | |
| 198 | + | folder = desktop / "Fedya-MP3" | |
| 199 | + | folder.mkdir(exist_ok=True) | |
| 200 | + | return folder | |
| 201 | + | ||
| 202 | + | ||
| 203 | + | # ── URL разпознаване ────────────────────────────────────────────────────────── | |
| 204 | + | YT_RE = re.compile( | |
| 205 | + | r"https?://(www\.)?(youtube\.com|youtu\.be|music\.youtube\.com)/.+" | |
| 206 | + | ) | |
| 207 | + | FB_RE = re.compile( | |
| 208 | + | r"https?://(www\.|m\.|web\.)?facebook\.com/.+|https?://fb\.watch/.+" | |
| 209 | + | ) | |
| 210 | + | ||
| 211 | + | def detect_source(url: str) -> str: | |
| 212 | + | url = url.strip() | |
| 213 | + | if YT_RE.match(url): return "YouTube" | |
| 214 | + | if FB_RE.match(url): return "Facebook" | |
| 215 | + | return "Unknown" | |
| 216 | + | ||
| 217 | + | ||
| 218 | + | # ── Progress hook ───────────────────────────────────────────────────────────── | |
| 219 | + | _last_pct = -1 | |
| 220 | + | ||
| 221 | + | def progress_hook(d): | |
| 222 | + | global _last_pct | |
| 223 | + | status = d.get("status") | |
| 224 | + | ||
| 225 | + | if status == "downloading": | |
| 226 | + | pct_raw = d.get("_percent_str", "").strip().replace("%", "") | |
| 227 | + | try: | |
| 228 | + | pct = int(float(pct_raw)) | |
| 229 | + | except (ValueError, TypeError): | |
| 230 | + | pct = 0 | |
| 231 | + | ||
| 232 | + | if pct == _last_pct: | |
| 233 | + | return | |
| 234 | + | _last_pct = pct | |
| 235 | + | ||
| 236 | + | filled = int(pct / 5) # 20-char bar | |
| 237 | + | bar = paint("█" * filled, C.CYAN) + paint("░" * (20 - filled), C.GRAY) | |
| 238 | + | ||
| 239 | + | speed = d.get("_speed_str", "?").strip() | |
| 240 | + | eta = d.get("_eta_str", "?").strip() | |
| 241 | + | ||
| 242 | + | line = ( | |
| 243 | + | f"\r {bar} " | |
| 244 | + | f"{paint(f'{pct:3d}%', C.BOLD, C.WHITE)} " | |
| 245 | + | f"{paint(speed, C.YELLOW)} " | |
| 246 | + | f"ETA {paint(eta, C.BLUE)}" | |
| 247 | + | ) | |
| 248 | + | print(line, end="", flush=True) | |
| 249 | + | ||
| 250 | + | elif status == "finished": | |
| 251 | + | print(f"\r {paint('█'*20, C.GREEN)} {paint('100% ✓ конвертирам в MP3…', C.GREEN, C.BOLD)}") | |
| 252 | + | _last_pct = -1 | |
| 253 | + | ||
| 254 | + | ||
| 255 | + | # ── Изтегляне ───────────────────────────────────────────────────────────────── | |
| 256 | + | def download(url: str, output_dir: Path, source: str, quality: str) -> bool: | |
| 257 | + | import yt_dlp | |
| 258 | + | ||
| 259 | + | global _last_pct | |
| 260 | + | _last_pct = -1 | |
| 261 | + | ||
| 262 | + | output_template = str(output_dir / "%(title).80s.%(ext)s") | |
| 263 | + | ||
| 264 | + | ffmpeg_loc = find_ffmpeg() | |
| 265 | + | if ffmpeg_loc == "NOT_FOUND": | |
| 266 | + | print(paint( | |
| 267 | + | "\n ✗ FFmpeg не е намерен!\n" | |
| 268 | + | " Windows: winget install ffmpeg\n" | |
| 269 | + | " macOS : brew install ffmpeg\n" | |
| 270 | + | " Linux : sudo apt install ffmpeg\n" | |
| 271 | + | " ИЛИ сложи ffmpeg.exe до програмата.\n", | |
| 272 | + | C.RED | |
| 273 | + | )) | |
| 274 | + | return False | |
| 275 | + | ||
| 276 | + | ydl_opts = { | |
| 277 | + | "format" : "bestaudio/best", | |
| 278 | + | "postprocessors": [{ | |
| 279 | + | "key" : "FFmpegExtractAudio", | |
| 280 | + | "preferredcodec" : "mp3", | |
| 281 | + | "preferredquality": quality, | |
| 282 | + | }], | |
| 283 | + | "outtmpl" : output_template, | |
| 284 | + | "quiet" : True, | |
| 285 | + | "no_warnings" : True, | |
| 286 | + | "noplaylist" : True, | |
| 287 | + | "socket_timeout" : 30, | |
| 288 | + | "progress_hooks" : [progress_hook], | |
| 289 | + | "postprocessor_hooks": [], | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | if ffmpeg_loc: # намерен до exe-то | |
| 293 | + | ydl_opts["ffmpeg_location"] = os.path.dirname(ffmpeg_loc) | |
| 294 | + | ||
| 295 | + | # Facebook лични видеа – cookies от Chrome | |
| 296 | + | if source == "Facebook": | |
| 297 | + | try: | |
| 298 | + | ydl_opts["cookiesfrombrowser"] = ("chrome",) | |
| 299 | + | except Exception: | |
| 300 | + | pass | |
| 301 | + | ||
| 302 | + | icon_yt = paint("▶", C.RED, C.BOLD) | |
| 303 | + | icon_fb = paint("f", C.BLUE, C.BOLD) | |
| 304 | + | icon = icon_yt if source == "YouTube" else icon_fb | |
| 305 | + | ||
| 306 | + | print(f"\n {icon} {paint(source, C.BOLD)} {paint(url[:72] + ('…' if len(url)>72 else ''), C.GRAY)}") | |
| 307 | + | print(f" {paint('Изтеглям…', C.DIM)}") | |
| 308 | + | ||
| 309 | + | t_start = time.time() | |
| 310 | + | ||
| 311 | + | try: | |
| 312 | + | with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
| 313 | + | info = ydl.extract_info(url, download=True) | |
| 314 | + | title = info.get("title", "Unknown") | |
| 315 | + | ||
| 316 | + | elapsed = time.time() - t_start | |
| 317 | + | print( | |
| 318 | + | f"\n {paint('✓', C.GREEN, C.BOLD)} " | |
| 319 | + | f"{paint(title[:60], C.WHITE, C.BOLD)}\n" | |
| 320 | + | f" {paint(f'Качество: {quality} kbps | Времетраене: {elapsed:.1f}s', C.GRAY)}" | |
| 321 | + | ) | |
| 322 | + | return True | |
| 323 | + | ||
| 324 | + | except yt_dlp.utils.DownloadError as e: | |
| 325 | + | msg = str(e).lower() | |
| 326 | + | print(f"\n {paint('✗ Грешка:', C.RED, C.BOLD)}", str(e)[:120]) | |
| 327 | + | if "login" in msg or "private" in msg or "sign in" in msg: | |
| 328 | + | print(paint( | |
| 329 | + | " 💡 Видеото изисква вход. За Facebook добавете cookies от Chrome.\n" | |
| 330 | + | " За YouTube: youtube.com/premium или публично видео.", | |
| 331 | + | C.YELLOW | |
| 332 | + | )) | |
| 333 | + | return False | |
| 334 | + | ||
| 335 | + | except Exception as e: | |
| 336 | + | print(f"\n {paint('✗ Неочаквана грешка:', C.RED)} {e}") | |
| 337 | + | return False | |
| 338 | + | ||
| 339 | + | ||
| 340 | + | # ── Меню за качество ────────────────────────────────────────────────────────── | |
| 341 | + | QUALITY_MAP = { | |
| 342 | + | "1": ("320", "320 kbps – Максимално (по-голям файл)"), | |
| 343 | + | "2": ("192", "192 kbps – Отлично (препоръчително)"), | |
| 344 | + | "3": ("128", "128 kbps – Добро (по-малък файл)"), | |
| 345 | + | "4": ("64", " 64 kbps – Ниско (podcasts/говор)"), | |
| 346 | + | } | |
| 347 | + | ||
| 348 | + | def choose_quality() -> str: | |
| 349 | + | print(f"\n {paint('Качество на MP3:', C.CYAN, C.BOLD)}") | |
| 350 | + | for k, (q, label) in QUALITY_MAP.items(): | |
| 351 | + | print(f" {paint(k, C.YELLOW, C.BOLD)}. {label}") | |
| 352 | + | while True: | |
| 353 | + | choice = input(paint("\n Избор [1-4, Enter=2]: ", C.BOLD)).strip() or "2" | |
| 354 | + | if choice in QUALITY_MAP: | |
| 355 | + | return QUALITY_MAP[choice][0] | |
| 356 | + | ||
| 357 | + | ||
| 358 | + | # ── История ─────────────────────────────────────────────────────────────────── | |
| 359 | + | def log_history(output_dir: Path, url: str, title: str, source: str, quality: str): | |
| 360 | + | log_path = output_dir / "download_history.txt" | |
| 361 | + | now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| 362 | + | with open(log_path, "a", encoding="utf-8") as f: | |
| 363 | + | f.write(f"[{now}] [{source}] [{quality}kbps] {title}\n {url}\n\n") | |
| 364 | + | ||
| 365 | + | ||
| 366 | + | # ── Разделители ─────────────────────────────────────────────────────────────── | |
| 367 | + | def divider(char="─", color=C.GRAY): | |
| 368 | + | try: | |
| 369 | + | w = os.get_terminal_size().columns | |
| 370 | + | except Exception: | |
| 371 | + | w = 72 | |
| 372 | + | print(paint(char * min(w, 72), color)) | |
| 373 | + | ||
| 374 | + | ||
| 375 | + | def section(title: str): | |
| 376 | + | divider("═", C.BLUE) | |
| 377 | + | print(f" {paint(title, C.CYAN, C.BOLD)}") | |
| 378 | + | divider("─") | |
| 379 | + | ||
| 380 | + | ||
| 381 | + | # ── Main ────────────────────────────────────────────────────────────────────── | |
| 382 | + | def main(): | |
| 383 | + | ensure_dependencies() | |
| 384 | + | banner() | |
| 385 | + | ||
| 386 | + | # Проверка за нова версия на yt-dlp (при всяко стартиране) | |
| 387 | + | print(paint(" Проверявам версията на yt-dlp…", C.GRAY)) | |
| 388 | + | check_ytdlp_update() | |
| 389 | + | ||
| 390 | + | output_dir = get_desktop() | |
| 391 | + | print( | |
| 392 | + | f" {paint('📁', '')} Файловете се запазват в: " | |
| 393 | + | f"{paint(str(output_dir), C.CYAN, C.BOLD)}\n" | |
| 394 | + | ) | |
| 395 | + | ||
| 396 | + | quality = choose_quality() | |
| 397 | + | success = 0 | |
| 398 | + | failed = 0 | |
| 399 | + | ||
| 400 | + | section("ВМЪКНИ URL (q = изход, c = смени качество)") | |
| 401 | + | ||
| 402 | + | while True: | |
| 403 | + | print() | |
| 404 | + | try: | |
| 405 | + | raw = input(paint(" URL ▸ ", C.BOLD, C.WHITE)).strip() | |
| 406 | + | except (KeyboardInterrupt, EOFError): | |
| 407 | + | raw = "q" | |
| 408 | + | ||
| 409 | + | if not raw: | |
| 410 | + | continue | |
| 411 | + | ||
| 412 | + | if raw.lower() in {"q", "quit", "exit", "изход"}: | |
| 413 | + | break | |
| 414 | + | ||
| 415 | + | if raw.lower() in {"c", "quality", "качество"}: | |
| 416 | + | quality = choose_quality() | |
| 417 | + | section("ВМЪКНИ URL (q = изход, c = смени качество)") | |
| 418 | + | continue | |
| 419 | + | ||
| 420 | + | source = detect_source(raw) | |
| 421 | + | if source == "Unknown": | |
| 422 | + | print(paint(" ⚠ Не е разпознат YouTube или Facebook URL.", C.YELLOW)) | |
| 423 | + | continue | |
| 424 | + | ||
| 425 | + | ok = download(raw, output_dir, source, quality) | |
| 426 | + | if ok: | |
| 427 | + | success += 1 | |
| 428 | + | else: | |
| 429 | + | failed += 1 | |
| 430 | + | ||
| 431 | + | divider("─", C.GRAY) | |
| 432 | + | status = ( | |
| 433 | + | paint(f"✓ {success}", C.GREEN, C.BOLD) + | |
| 434 | + | paint(" ✗ ", C.GRAY) + | |
| 435 | + | paint(str(failed), C.RED if failed else C.GRAY) | |
| 436 | + | ) | |
| 437 | + | print(f" Сесия: {status} │ " | |
| 438 | + | f"{paint('c', C.YELLOW)} = качество " | |
| 439 | + | f"{paint('q', C.YELLOW)} = изход") | |
| 440 | + | ||
| 441 | + | # Край | |
| 442 | + | divider("═", C.BLUE) | |
| 443 | + | print( | |
| 444 | + | f"\n {paint('Сесията приключи.', C.CYAN, C.BOLD)}\n" | |
| 445 | + | f" Свалени: {paint(success, C.GREEN, C.BOLD)} " | |
| 446 | + | f"Неуспешни: {paint(failed, C.RED if failed else C.GRAY, C.BOLD)}\n" | |
| 447 | + | f" Папка: {paint(str(output_dir), C.CYAN)}\n" | |
| 448 | + | ) | |
| 449 | + | ||
| 450 | + | if sys.platform == "win32": | |
| 451 | + | input(paint(" Натисни Enter за изход…", C.GRAY)) | |
| 452 | + | ||
| 453 | + | ||
| 454 | + | if __name__ == "__main__": | |
| 455 | + | main() | |
| 456 | + | ||
| 457 | + | ||
| 458 | + | # ═══════════════════════════════════════════════════════════════════════════════ | |
| 459 | + | # КОМПИЛИРАНЕ В .EXE (с PyInstaller) | |
| 460 | + | # ═══════════════════════════════════════════════════════════════════════════════ | |
| 461 | + | # | |
| 462 | + | # 1. Инсталирай зависимостите: | |
| 463 | + | # pip install yt-dlp colorama pyinstaller | |
| 464 | + | # | |
| 465 | + | # 2. Свали ffmpeg.exe и го сложи в СЪЩАТА папка като mediasnatch.py | |
| 466 | + | # (от https://ffmpeg.org/download.html → Windows Builds) | |
| 467 | + | # | |
| 468 | + | # 3. Компилирай: | |
| 469 | + | # pyinstaller --onefile --console --icon=downloader.ico ^ | |
| 470 | + | # --add-binary "ffmpeg.exe;." ^ | |
| 471 | + | # --name MediaSnatch ^ | |
| 472 | + | # mediasnatch.py | |
| 473 | + | # | |
| 474 | + | # 4. Готовият .exe е в папка dist\MediaSnatch.exe | |
| 475 | + | # | |
| 476 | + | # ЗАБЕЛЕЖКА: --add-binary синтаксис на Linux/macOS е с ":" вместо ";" | |
| 477 | + | # pyinstaller --onefile --console --icon=downloader.ico \ | |
| 478 | + | # --add-binary "ffmpeg:." \ | |
| 479 | + | # --name MediaSnatch \ | |
| 480 | + | # mediasnatch.py | |
| 481 | + | # ═══════════════════════════════════════════════════════════════════════════════ | |