downloaders/deezer_dl.py
2026-07-25 10:37:57 +05:00

285 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import subprocess
import sys
import os
import re
import zipfile
import shutil
import urllib.request
import urllib.parse
import json
import webbrowser
import time
import msvcrt
os.system('')
def pip_install(pkg):
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "--upgrade", pkg],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
import yt_dlp
except ImportError:
print("Устанавливаю yt-dlp...")
pip_install("yt-dlp")
import yt_dlp
try:
import requests
except ImportError:
pip_install("requests")
import requests
FFMPEG_DIR = os.path.join(os.path.expanduser("~"), ".local_ffmpeg")
FFMPEG_EXE = os.path.join(FFMPEG_DIR, "ffmpeg.exe")
def ensure_ffmpeg():
if shutil.which("ffmpeg"):
return None
if os.path.exists(FFMPEG_EXE):
return FFMPEG_DIR
os.makedirs(FFMPEG_DIR, exist_ok=True)
zip_path = os.path.join(FFMPEG_DIR, "ffmpeg.zip")
url = "https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"
print("Скачиваю ffmpeg (один раз)...")
urllib.request.urlretrieve(url, zip_path)
with zipfile.ZipFile(zip_path, "r") as zf:
for name in zf.namelist():
bname = os.path.basename(name)
if bname in ("ffmpeg.exe", "ffprobe.exe"):
src = zf.extract(name, FFMPEG_DIR)
dst = os.path.join(FFMPEG_DIR, bname)
if src != dst:
shutil.move(src, dst)
os.remove(zip_path)
for item in os.listdir(FFMPEG_DIR):
p = os.path.join(FFMPEG_DIR, item)
if os.path.isdir(p):
shutil.rmtree(p)
print("ffmpeg готов.")
return FFMPEG_DIR
try:
import ctypes
ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), 7)
except Exception:
pass
R = "\033[0m"
B = "\033[1m"
CYN = "\033[96m"
GRN = "\033[92m"
YLW = "\033[93m"
GRY = "\033[90m"
WHT = "\033[97m"
MGT = "\033[95m"
PAGE_SIZE = 8
ARL_FILE = os.path.join(os.path.expanduser("~"), ".deezer_arl.txt")
OUT_DIR = os.path.join(os.path.expanduser("~"), "Downloads", "Deezer")
BROWSERS = ["chrome", "edge", "firefox", "chromium", "opera", "brave", "vivaldi"]
os.makedirs(OUT_DIR, exist_ok=True)
ffmpeg_location = ensure_ffmpeg()
def fmt_dur(s):
if not s: return "?:??"
m, s = divmod(int(s), 60)
return f"{m}:{s:02d}"
def trunc(s, n):
return s if len(s) <= n else s[:n-1] + "…"
def read_key():
ch = msvcrt.getch()
if ch in (b'\x00', b'\xe0'):
return ('spec', msvcrt.getch())
return ('char', ch)
def draw(items, sel, query, page, total_pages):
os.system('cls')
W = 74
header = f" {CYN}{B}Deezer{R} {GRY}▸{R} {WHT}«{query}»{R}"
nav = f" {GRY}↑↓ выбор ←→ страница {page+1}/{total_pages} Enter скачать Esc новый поиск{R}"
print(f"\n{CYN}{'▄'*W}{R}")
print(header)
print(nav)
print(f"{CYN}{'▀'*W}{R}\n")
for i, it in enumerate(items):
artist = trunc(it.get('artist',''), 22)
title = trunc(it.get('title',''), 30)
album = trunc(it.get('album',''), 18)
dur = it.get('dur','')
line = f"{artist:<22} {title:<30} {GRY}{album:<18} {dur}{R}"
if i == sel:
print(f" {GRN}{B}▶ {line}{R}")
else:
print(f" {GRY}{line}{R}")
print(f"\n{CYN}{'▄'*W}{R}\n")
def tui_pick(pages, query):
page = 0
sel = 0
while True:
cur = pages[page]
draw(cur, sel, query, page, len(pages))
kind, key = read_key()
if kind == 'spec':
if key == b'H':
sel = max(0, sel - 1)
elif key == b'P':
sel = min(len(cur)-1, sel + 1)
elif key == b'M' and page < len(pages)-1:
page += 1; sel = 0
elif key == b'K' and page > 0:
page -= 1; sel = 0
elif kind == 'char':
if key == b'\r':
return cur[sel]
elif key == b'\x1b':
return None
def search_deezer(query, limit=20):
q = urllib.parse.quote(query)
r = requests.get(f"https://api.deezer.com/search?q={q}&limit={limit}", timeout=10)
raw = r.json().get("data", [])
items = []
for t in raw:
items.append({
'id': t['id'],
'artist': t['artist']['name'],
'title': t['title'],
'album': t.get('album',{}).get('title',''),
'dur': fmt_dur(t.get('duration',0)),
})
return [items[i:i+PAGE_SIZE] for i in range(0, len(items), PAGE_SIZE)] if items else []
def get_arl_from_browser():
try:
import browser_cookie3
except ImportError:
pip_install("browser-cookie3")
import browser_cookie3
for br in BROWSERS:
try:
loader = getattr(browser_cookie3, br, None)
if not loader: continue
for c in loader(domain_name=".deezer.com"):
if c.name == "arl" and len(c.value) > 50:
return c.value
except Exception:
continue
return None
def load_arl():
if os.path.exists(ARL_FILE):
v = open(ARL_FILE).read().strip()
if len(v) > 50: return v
return None
def save_arl(v):
open(ARL_FILE, "w").write(v)
def login_flow():
os.system('cls')
print(f"\n{CYN}{B} Deezer — Авторизация{R}\n")
print(" Открываю браузер на странице входа...")
webbrowser.open("https://www.deezer.com/login")
time.sleep(2)
input(f"\n {YLW}Войди в Deezer в браузере, затем нажми Enter...{R} ")
print(" Считываю куки автоматически...")
arl = get_arl_from_browser()
if arl:
save_arl(arl)
print(f" {GRN}Авторизация успешна!{R}")
time.sleep(1)
return arl
print(f"\n {YLW}Не удалось считать автоматически.{R}")
print(" F12 → Application → Cookies → deezer.com → скопируй 'arl'")
arl = input(" Вставь ARL вручную: ").strip()
if len(arl) > 50:
save_arl(arl)
return arl
return None
def download(arl, url, is_track=True):
opts = {
"outtmpl": os.path.join(OUT_DIR, "%(uploader)s - %(title)s.%(ext)s"),
"format": "flac/bestaudio/best",
"postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "flac"}],
"quiet": False,
"noplaylist": is_track,
"http_headers": {"Cookie": f"arl={arl}"},
}
if ffmpeg_location:
opts["ffmpeg_location"] = ffmpeg_location
with yt_dlp.YoutubeDL(opts) as ydl:
ydl.download([url])
def is_deezer_url(s):
return bool(re.match(r"https?://(www\.)?deezer\.com/", s))
def main():
os.system('cls')
print(f"\n{CYN}{B} ══════════════════════════════════════{R}")
print(f"{CYN}{B} Deezer Downloader → FLAC Lossless {R}")
print(f"{CYN}{B} ══════════════════════════════════════{R}")
print(f" {GRY}Папка: {OUT_DIR}{R}\n")
arl = load_arl()
if arl:
print(f" {GRN}✓ Авторизация найдена{R}\n")
else:
arl = login_flow()
if not arl:
print("Выход."); return
while True:
print(f"\n {GRY}Введи URL или поисковый запрос │ 'login' │ 'q' выход{R}")
query = input(f" {CYN}▸{R} ").strip()
if not query: continue
if query.lower() == 'q': break
if query.lower() == 'login':
arl = login_flow()
continue
if is_deezer_url(query):
os.system('cls')
print(f"\n {GRN}Скачиваю: {query}{R}\n")
try:
download(arl, query, "/track/" in query)
print(f"\n {GRN}Готово! → {OUT_DIR}{R}")
except Exception as e:
print(f"\n Ошибка: {e}")
input(f"\n {GRY}Enter чтобы продолжить...{R}")
continue
print(f" {GRY}Ищу...{R}", end="\r")
try:
pages = search_deezer(query)
except Exception as e:
print(f" Ошибка поиска: {e}\n"); continue
if not pages:
print(f" {YLW}Ничего не найдено.{R}\n"); continue
track = tui_pick(pages, query)
if track is None:
continue
url = f"https://www.deezer.com/track/{track['id']}"
os.system('cls')
print(f"\n {GRN}Скачиваю: {track['artist']} — {track['title']}{R}\n")
try:
download(arl, url, True)
print(f"\n {GRN}Готово! → {OUT_DIR}{R}")
except Exception as e:
print(f"\n Ошибка: {e}")
if "401" in str(e) or "403" in str(e):
print(f" {YLW}Попробуй обновить авторизацию → введи 'login'{R}")
input(f"\n {GRY}Enter чтобы продолжить...{R}")
if __name__ == "__main__":
main()
input("\nНажми Enter для выхода...")