Добавил новые ЭП для новой версии загрузки видео с YouTube

This commit is contained in:
Viner Abubakirov
2026-02-25 12:11:06 +05:00
parent 8ffa8cdf71
commit dc5f07fd78
8 changed files with 99 additions and 16 deletions

View File

@@ -0,0 +1,22 @@
from fastapi import APIRouter
from app.schemas import TaskCreateResponse
from app.schemas import DownloadRequest
from app.tasks import download_youtube
from app.services import YouTubeService
router = APIRouter()
@router.post("/download", response_model=TaskCreateResponse)
async def download_video(data: DownloadRequest):
task = download_youtube.delay(url=str(data.url), quality=data.quality)
return TaskCreateResponse(task_id=task.id, status=task.status)
@router.get("/resolutions")
async def video_resolutions(url: str):
return {"resolutions": YouTubeService.resolutions(url)}
@router.get("/size")
async def video_size(data: DownloadRequest):
return {"size": YouTubeService.filesize(data)}

8
app/api/v2/router.py Normal file
View File

@@ -0,0 +1,8 @@
from fastapi import APIRouter
from app.api.v2.endpoints import youtube
router = APIRouter()
router.include_router(youtube.router, prefix="/youtube", tags=["YouTube"])

View File

@@ -1,9 +1,12 @@
from fastapi import FastAPI from fastapi import FastAPI
from app.api.v1.router import router as v1_router from app.api.v1.router import router as v1_router
from app.api.v2.router import router as v2_router
app = FastAPI() app = FastAPI()
app.include_router(v1_router, prefix="/api/v1") app.include_router(v1_router, prefix="/api/v1")
app.include_router(v2_router, prefix="/api/v2")
@app.get("/ping") @app.get("/ping")

View File

@@ -11,6 +11,10 @@ class DownloadResponse(BaseModel):
audio: str audio: str
class DownloadResponseV2(BaseModel):
video: str
class TaskCreateResponse(BaseModel): class TaskCreateResponse(BaseModel):
task_id: str task_id: str
status: str status: str

View File

@@ -2,19 +2,52 @@ import os
from app.core.config import settings from app.core.config import settings
from app.core.uploader import uploader_backend from app.core.uploader import uploader_backend
from app.utils.youtube import YtDlpManager from app.utils.uploader import S3UploadBackend
from app.schemas import DownloadRequest, DownloadResponse from app.schemas import DownloadRequest, DownloadResponse, DownloadResponseV2
class YouTubeService: class YouTubeService:
@staticmethod @staticmethod
def download(data: DownloadRequest): def download(data: DownloadRequest):
from app.utils.youtube import YtDlpManager
manager = YtDlpManager(str(data.url), uploader_backend) manager = YtDlpManager(str(data.url), uploader_backend)
uploader_backend.key_prefix = f"{manager.id}@{data.quality}@" uploader_backend.key_prefix = f"{manager.id}@{data.quality}@"
video_url = manager.download_video(data.quality) video_url = manager.download_video(data.quality)
audio_url = manager.download_audio() audio_url = manager.download_audio()
return DownloadResponse(video=video_url, audio=audio_url) return DownloadResponse(video=video_url, audio=audio_url)
@staticmethod
def download_v2(data: DownloadRequest):
filepath = None
try:
from app.utils.youtubeV2 import YtDlpManager
manager = YtDlpManager(str(data.url))
best_audio = manager.best_audio()
best_video = manager.best_video(data.quality)
filepath = manager.download(best_video, best_audio)
upload_backend = S3UploadBackend(f"{manager.id}@{data.quality}@")
video_url = upload_backend.upload(os.path.basename(filepath), filepath)
return DownloadResponseV2(video=video_url)
finally:
if filepath:
os.remove(filepath)
@staticmethod
def resolutions(url: str):
from app.utils.youtubeV2 import YtDlpManager
manager = YtDlpManager(url)
return manager.resolutions
@staticmethod
def filesize(data: DownloadRequest):
from app.utils.youtubeV2 import YtDlpManager
manager = YtDlpManager((data.url))
video_size = manager.best_video(data.quality).filesize
audio_size = manager.best_audio().filesize
return {"filesize": video_size + audio_size}
class Files: class Files:
@staticmethod @staticmethod

View File

@@ -11,8 +11,19 @@ from app.schemas import DownloadRequest
retry_backoff=True, retry_backoff=True,
) )
def download_youtube(self, url: str, quality: int) -> dict: def download_youtube(self, url: str, quality: int) -> dict:
print("Get Task. Try to make them")
request = DownloadRequest(url=url, quality=quality) request = DownloadRequest(url=url, quality=quality)
response = YouTubeService.download(request) response = YouTubeService.download(request)
print("Task make successfully. Return response") return response.model_dump()
@celery_app.task(
bind=True,
name="download_youtube_v2",
autoretry_for=(Exception,),
retry_kwargs={"max_retries": 0},
retry_backoff=True,
)
def download_youtube_v2(self, url: str, quality: int) -> dict:
request = DownloadRequest(url=url, quality=quality)
response = YouTubeService.download_v2(request)
return response.model_dump() return response.model_dump()

View File

@@ -1,5 +1,6 @@
import os import os
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from urllib.parse import quote
import boto3 import boto3
from botocore.client import Config from botocore.client import Config
@@ -180,9 +181,10 @@ class S3UploadBackend(UploadBackend):
self.key_prefix = key_prefix self.key_prefix = key_prefix
def upload(self, name: str, file: bytes | str): def upload(self, name: str, file: bytes | str):
response = self.s3.upload_file( key = f"{self.key_prefix}{name}"
Filename=file, if isinstance(file, str):
Bucket=self.bucket, self.s3.upload_file(Filename=file, Bucket=self.bucket, Key=key)
Key=f"{self.key_prefix}{name}", else:
) self.s3.put_object(Bucket=self.bucket, Key=key, Body=file)
return response["Location"] encoded_key = quote(key, "")
return f"{settings.S3_ENDPOINT_URL}/{self.bucket}/{encoded_key}"

View File

@@ -66,20 +66,20 @@ class YtDlpManager:
return MediaInfo(f, f.get("format_id")) return MediaInfo(f, f.get("format_id"))
return None return None
def download(self, video_id: str | None = None, audio_id: str | None = None): def download(self, video: MediaInfo | None = None, audio: MediaInfo | None = None):
if video_id is None and audio_id is None: if video is None and audio is None:
format_id = self.info.get( format_id = self.info.get(
"format_id", "format_id",
) )
else: else:
format_id = "" + str(video_id) if video_id is not None else "" format_id = "" + str(video.id) if video is not None else ""
if audio_id is not None: if audio is not None:
if len(format_id) > 0: if len(format_id) > 0:
format_id += "+" format_id += "+"
format_id += str(audio_id) format_id += str(audio.id)
ydl_opts = { ydl_opts = {
"format": f"{video_id}+{audio_id}", "format": f"{format_id}",
"merge_output_format": "mp4", "merge_output_format": "mp4",
"outtmpl": f"{settings.MEDIA_DIR}/%(title)s.%(ext)s", "outtmpl": f"{settings.MEDIA_DIR}/%(title)s.%(ext)s",
} }