117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
# downloader.py
|
|
import yt_dlp
|
|
import threading
|
|
import os
|
|
import logging
|
|
import subprocess
|
|
import shutil
|
|
import asyncio
|
|
from typing import List, Optional, Callable
|
|
|
|
from config import (
|
|
DEFAULT_TIKTOK_VIDEO_QUALITY,
|
|
DEFAULT_TIKTOK_AUDIO_QUALITY,
|
|
DEFAULT_INSTAGRAM_VIDEO_QUALITY,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class DownloadCancelled(Exception):
|
|
pass
|
|
|
|
def _progress_hook(cancel_event, progress_cb):
|
|
def hook(d):
|
|
if cancel_event.is_set():
|
|
raise DownloadCancelled("Cancelled by user")
|
|
if d["status"] in ["downloading", "finished"] and progress_cb:
|
|
progress_cb(d)
|
|
return hook
|
|
|
|
|
|
def optimize_for_telegram(input_path: str, output_path: str) -> None:
|
|
"""
|
|
Асинхронная функция: конвертирует видео в Telegram-safe mp4
|
|
"""
|
|
file_size_mb = os.path.getsize(input_path) / (1024 ** 2)
|
|
crf = 28 if file_size_mb > 50 else 23
|
|
|
|
cmd = [
|
|
'ffmpeg',
|
|
'-i', input_path,
|
|
'-c:v', 'libx264',
|
|
'-preset', 'fast',
|
|
'-crf', str(crf),
|
|
'-c:a', 'aac',
|
|
'-b:a', '128k',
|
|
'-movflags', '+faststart',
|
|
'-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
|
|
'-y',
|
|
output_path
|
|
]
|
|
|
|
process = await asyncio.create_subprocess_exec(
|
|
*cmd,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
|
|
stdout, stderr = await process.communicate()
|
|
|
|
if process.returncode != 0:
|
|
logger.error(f"FFmpeg error: {stderr.decode()}")
|
|
shutil.copy2(input_path, output_path)
|
|
|
|
async def process_download(message, url: str, output_path: str, cookies_file: Optional[str] = None, cancel_event: threading.Event = None):
|
|
ydl_options = {
|
|
"format": f"bestvideo[height<={DEFAULT_INSTAGRAM_VIDEO_QUALITY}] + bestaudio/best",
|
|
"outtmpl": output_path,
|
|
"cookiefile": cookies_file if cookies_file and os.path.exists(cookies_file) else None,
|
|
"progress_hooks": [lambda d: asyncio.create_task(progress_callback(message, d))],
|
|
"quiet": True,
|
|
"no_warnings": True,
|
|
"concurrent_fragment_downloads": 4,
|
|
"http_chunk_size": 1024 ** 2,
|
|
}
|
|
|
|
async def progress_callback(msg, d):
|
|
if d["status"] == "downloading":
|
|
downloaded_bytes = d.get("downloaded_bytes", 0)
|
|
total_bytes = d.get("total_bytes_est", None)
|
|
|
|
if total_bytes:
|
|
percent_downloaded = (downloaded_bytes / total_bytes) * 100
|
|
download_speed_bps = d.get("speed", 1e-6)
|
|
estimated_remaining_time_seconds = max(0, (total_bytes - downloaded_bytes) / download_speed_bps)
|
|
|
|
progress_text = "<b>Downloading...</b>\n"
|
|
progress_text += f"Progress: {percent_downloaded:.2f}%\n"
|
|
progress_text += f"Downloaded: {downloaded_bytes / (1024 ** 2):.2f} MB\n"
|
|
progress_text += f"Total Size: {total_bytes / (1024 ** 2):.2f} MB\n"
|
|
progress_text += f"[{d.get('filename')}] @ {int(download_speed_bps) / (1024 ** 2)} MB/s\n"
|
|
progress_text += f"ETA: {estimated_remaining_time_seconds:.1f} сек."
|
|
else:
|
|
progress_text = "<b>Downloading...</b>\n"
|
|
progress_text += f"Downloaded Bytes: {downloaded_bytes}\n"
|
|
|
|
try:
|
|
await msg.edit_text(progress_text, parse_mode="HTML")
|
|
except Exception as e:
|
|
logger.error(f"Error updating progress message: {e}")
|
|
|
|
try:
|
|
with yt_dlp.YoutubeDL(ydl_options) as ydl:
|
|
ydl.download([url])
|
|
|
|
await optimize_for_telegram(input_path=output_path, output_path=output_path + "_optimized")
|
|
except Exception as e:
|
|
logger.error(f"Download or optimization error: {e}")
|
|
raise
|
|
|
|
async def download_ytdlp(url: str, ydl_options: dict):
|
|
try:
|
|
with yt_dlp.YoutubeDL(ydl_options) as ydl:
|
|
info = ydl.extract_info(url, download=True)
|
|
return info
|
|
except Exception as e:
|
|
logger.error(f"Error downloading from {url}: {e}")
|
|
raise |