You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
4.0 KiB
130 lines
4.0 KiB
"""
|
|
Base Uploader - Lớp cơ sở cho tất cả uploaders.
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Optional
|
|
|
|
from utils.console import print_step, print_substep
|
|
|
|
|
|
@dataclass
|
|
class VideoMetadata:
|
|
"""Metadata cho video cần upload."""
|
|
|
|
file_path: str
|
|
title: str
|
|
description: str = ""
|
|
tags: List[str] = field(default_factory=list)
|
|
hashtags: List[str] = field(default_factory=list)
|
|
thumbnail_path: Optional[str] = None
|
|
schedule_time: Optional[str] = None # ISO 8601 format
|
|
privacy: str = "public" # public, private, unlisted
|
|
category: str = "Entertainment"
|
|
language: str = "vi" # Vietnamese
|
|
|
|
|
|
class BaseUploader(ABC):
|
|
"""Lớp cơ sở cho tất cả platform uploaders."""
|
|
|
|
platform_name: str = "Unknown"
|
|
|
|
def __init__(self):
|
|
self._authenticated = False
|
|
|
|
@abstractmethod
|
|
def authenticate(self) -> bool:
|
|
"""Xác thực với platform API.
|
|
|
|
Returns:
|
|
True nếu xác thực thành công.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def upload(self, metadata: VideoMetadata) -> Optional[str]:
|
|
"""Upload video lên platform.
|
|
|
|
Args:
|
|
metadata: VideoMetadata chứa thông tin video.
|
|
|
|
Returns:
|
|
URL của video đã upload, hoặc None nếu thất bại.
|
|
"""
|
|
pass
|
|
|
|
def validate_video(self, metadata: VideoMetadata) -> bool:
|
|
"""Kiểm tra video có hợp lệ trước khi upload.
|
|
|
|
Args:
|
|
metadata: VideoMetadata cần kiểm tra.
|
|
|
|
Returns:
|
|
True nếu hợp lệ.
|
|
"""
|
|
if not os.path.exists(metadata.file_path):
|
|
print_substep(
|
|
f"[{self.platform_name}] File không tồn tại: {metadata.file_path}", style="bold red"
|
|
)
|
|
return False
|
|
|
|
file_size = os.path.getsize(metadata.file_path)
|
|
if file_size == 0:
|
|
print_substep(
|
|
f"[{self.platform_name}] File rỗng: {metadata.file_path}", style="bold red"
|
|
)
|
|
return False
|
|
|
|
if not metadata.title:
|
|
print_substep(f"[{self.platform_name}] Thiếu tiêu đề video", style="bold red")
|
|
return False
|
|
|
|
return True
|
|
|
|
def safe_upload(self, metadata: VideoMetadata, max_retries: int = 3) -> Optional[str]:
|
|
"""Upload video với retry logic.
|
|
|
|
Args:
|
|
metadata: VideoMetadata chứa thông tin video.
|
|
max_retries: Số lần thử lại tối đa.
|
|
|
|
Returns:
|
|
URL của video đã upload, hoặc None nếu thất bại.
|
|
"""
|
|
if not self.validate_video(metadata):
|
|
return None
|
|
|
|
if not self._authenticated:
|
|
print_step(f"Đang xác thực với {self.platform_name}...")
|
|
if not self.authenticate():
|
|
print_substep(f"Xác thực {self.platform_name} thất bại!", style="bold red")
|
|
return None
|
|
|
|
for attempt in range(1, max_retries + 1):
|
|
try:
|
|
print_step(f"Đang upload lên {self.platform_name} (lần {attempt}/{max_retries})...")
|
|
url = self.upload(metadata)
|
|
if url:
|
|
print_substep(
|
|
f"Upload {self.platform_name} thành công! URL: {url}",
|
|
style="bold green",
|
|
)
|
|
return url
|
|
except Exception as e:
|
|
print_substep(
|
|
f"[{self.platform_name}] Lỗi upload (lần {attempt}): {e}",
|
|
style="bold red",
|
|
)
|
|
if attempt < max_retries:
|
|
backoff = min(2**attempt, 60) # Exponential backoff, max 60s
|
|
print_substep(f"Chờ {backoff}s trước khi thử lại...", style="bold yellow")
|
|
time.sleep(backoff)
|
|
|
|
print_substep(
|
|
f"Upload {self.platform_name} thất bại sau {max_retries} lần thử!", style="bold red"
|
|
)
|
|
return None
|