Naposledy aktivní 1 day ago

YouTube & Facebook → MP3 Downloader

urocibg revidoval tento gist 1 day ago. Přejít na revizi

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 revidoval tento gist 1 day ago. Přejít na revizi

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 revidoval tento gist 1 day ago. Přejít na revizi

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 revidoval tento gist 1 day ago. Přejít na revizi

1 file changed, 481 insertions

FedyaMP3.py(vytvořil soubor)

@@ -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 + # ═══════════════════════════════════════════════════════════════════════════════
Novější Starší