Добавил новый загрузчик c YouTube

This commit is contained in:
Viner Abubakirov
2026-02-25 10:56:28 +05:00
parent f81da09c36
commit 8ffa8cdf71
4 changed files with 164 additions and 1 deletions

View File

@@ -25,6 +25,12 @@ class ChunkUploadBackend(ABC):
"""Прерывания загрузки"""
class UploadBackend(ABC):
@abstractmethod
def upload(self, name: str, file: bytes | str) -> str:
"""Загрузка файла"""
class DiskChunkUploadBackend(ChunkUploadBackend):
def __init__(self, key_prefix: str = ""):
self.base_path = str(settings.MEDIA_DIR)
@@ -157,3 +163,26 @@ class HybridDiskS3UploadBackend(ChunkUploadBackend):
)
os.remove(filepath)
return response["Location"]
class S3UploadBackend(UploadBackend):
def __init__(self, key_prefix=""):
self.s3 = boto3.client(
service_name="s3",
aws_access_key_id=settings.S3_ACCESS_KEY,
aws_secret_access_key=settings.S3_SECRET_KEY,
endpoint_url=settings.S3_ENDPOINT_URL,
region_name=settings.S3_REGION_NAME,
use_ssl=True,
config=Config(signature_version=settings.S3_SIGNATURE_VERSION),
)
self.bucket = settings.S3_BUCKET_NAME
self.key_prefix = key_prefix
def upload(self, name: str, file: bytes | str):
response = self.s3.upload_file(
Filename=file,
Bucket=self.bucket,
Key=f"{self.key_prefix}{name}",
)
return response["Location"]

View File

@@ -1,5 +1,4 @@
import subprocess
from typing import Literal
from dataclasses import dataclass
from app.utils.uploader import ChunkUploadBackend

129
app/utils/youtubeV2.py Normal file
View File

@@ -0,0 +1,129 @@
import yt_dlp
from app.core.config import settings
class MediaInfo:
def __init__(self, format: dict, id: int):
self.__format = format
self.id = id
@property
def filesize(self):
return self.__format.get("filesize")
@property
def codec(self):
if vcodec := self.__format.get("vcodec"):
return vcodec
return self.__format.get("acodec")
class YtDlpManager:
def __init__(self, url: str):
self.url = url
self._extract_info()
self._set_video_codecs()
@property
def resolutions(self):
return sorted(self._resolutions.keys())
def best_audio(self) -> MediaInfo | None:
"""
Получает аудио дорожку с наилучшим качеством звучания
"""
if self.info.get("acodec") in ("none", None):
return None
ids = str(self.info.get("format_id", "")).split("+")
if len(ids) == 2:
audio_id = ids[1]
else:
audio_id = ids[0]
for f in self.info["formats"]:
if f.get("format_id") == audio_id:
return MediaInfo(f, audio_id)
return None
def best_video(self, height: int | None = None, codec: str | None = None):
"""
Возвращает видео дорожку с наилучшим качеством с указанными параметрами
"""
if height is None:
if codec is None:
height = self.info.get("height")
else:
height = max(self._codecs.get(codec, [0]))
if not self._video_exist(codec, height):
return None
iterator = reversed(self.info["formats"])
for f in iterator:
if f.get("height", 0) == height:
if codec is None:
return MediaInfo(f, f.get("format_id"))
if f.get("codec") == codec:
return MediaInfo(f, f.get("format_id"))
return None
def download(self, video_id: str | None = None, audio_id: str | None = None):
if video_id is None and audio_id is None:
format_id = self.info.get(
"format_id",
)
else:
format_id = "" + str(video_id) if video_id is not None else ""
if audio_id is not None:
if len(format_id) > 0:
format_id += "+"
format_id += str(audio_id)
ydl_opts = {
"format": f"{video_id}+{audio_id}",
"merge_output_format": "mp4",
"outtmpl": f"{settings.MEDIA_DIR}/%(title)s.%(ext)s",
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(self.url, download=True)
return ydl.prepare_filename(info)
def _extract_info(self):
ydl_opts = {
"quiet": True,
"no_warnings": True,
"extract_flat": False,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
self.info = ydl.extract_info(self.url, download=False)
if self.info is None:
raise Exception("Не удалось получить информацию о видео")
def _set_video_codecs(self):
self._codecs: dict[str, list[int]] = {}
self._resolutions: dict[int, list[str]] = {}
for f in self.info["formats"]:
codec = f.get("vcodec")
if codec in ("none", None):
continue
height = f.get("height")
if height not in self._resolutions:
self._resolutions[height] = []
if codec not in self._codecs:
self._codecs[codec] = []
self._codecs[codec].append(height)
self._resolutions[height].append(codec)
def _video_exist(self, codec: str = "", resolution: int = 0):
if codec:
if resolution:
if resolutions := self._codecs.get(codec, []):
return resolution in resolutions
return True
if resolution:
if codec:
if codecs := self._resolutions.get(resolution, []):
return codec in codecs
return True
return False

6
plans.md Normal file
View File

@@ -0,0 +1,6 @@
- Реализовать ЭП, который возвращает список разрешений и размер файла
- Переделать загрузку (видео и аудио склеиваются) сперва скачать на диск, затем готовый файл отправить в S3 в случае если будет ошибка, поставить таймаут на 15 минут
- Реализовать статус доступности S3
- Перед загрузкой проверить нет ли случайно данное видео в S3 если есть дать на неё ссылку