Files
ViGent2/backend/app/services/uploader/bilibili_uploader.py
Kevin Wong cfe21d8337 更新
2026-01-23 09:42:10 +08:00

173 lines
6.7 KiB
Python

"""
Bilibili uploader using biliup library
"""
import json
import asyncio
from pathlib import Path
from typing import Optional, List, Dict, Any
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
try:
from biliup.plugins.bili_webup import BiliBili, Data
BILIUP_AVAILABLE = True
except ImportError:
BILIUP_AVAILABLE = False
from loguru import logger
from .base_uploader import BaseUploader
# Thread pool for running sync biliup code
_executor = ThreadPoolExecutor(max_workers=2)
class BilibiliUploader(BaseUploader):
"""Bilibili video uploader using biliup library"""
def __init__(
self,
title: str,
file_path: str,
tags: List[str],
publish_date: Optional[datetime] = None,
account_file: Optional[str] = None,
description: str = "",
tid: int = 122, # 分区ID: 122=国内原创
copyright: int = 1 # 1=原创, 2=转载
):
"""
Initialize Bilibili uploader
Args:
tid: Bilibili category ID (default: 122 for 国内原创)
copyright: 1 for original, 2 for repost
"""
super().__init__(title, file_path, tags, publish_date, account_file, description)
self.tid = tid
self.copyright = copyright
if not BILIUP_AVAILABLE:
raise ImportError(
"biliup library not installed. Please run: pip install biliup"
)
async def main(self) -> Dict[str, Any]:
"""
Upload video to Bilibili
Returns:
dict: Upload result
"""
# Run sync upload in thread pool to avoid asyncio.run() conflict
loop = asyncio.get_event_loop()
return await loop.run_in_executor(_executor, self._upload_sync)
def _upload_sync(self) -> Dict[str, Any]:
"""Synchronous upload logic (runs in thread pool)"""
try:
# 1. Load cookie data
if not self.account_file or not Path(self.account_file).exists():
logger.error(f"[B站] Cookie 文件不存在: {self.account_file}")
return {
"success": False,
"message": "Cookie 文件不存在,请先登录",
"url": None
}
with open(self.account_file, 'r', encoding='utf-8') as f:
cookie_data = json.load(f)
# Convert simple cookie format to biliup format if needed
if 'cookie_info' not in cookie_data and 'SESSDATA' in cookie_data:
# Transform to biliup expected format
cookie_data = {
'cookie_info': {
'cookies': [
{'name': k, 'value': v} for k, v in cookie_data.items()
]
},
'token_info': {
'access_token': cookie_data.get('access_token', ''),
'refresh_token': cookie_data.get('refresh_token', '')
}
}
logger.info("[B站] Cookie格式已转换")
# 2. Prepare video data
data = Data()
data.copyright = self.copyright
data.title = self.title
data.desc = self.description or f"标签: {', '.join(self.tags)}"
data.tid = self.tid
data.set_tag(self.tags)
data.dtime = self._get_timestamp(self.publish_date)
logger.info(f"[B站] 开始上传: {self.file_path.name}")
logger.info(f"[B站] 标题: {self.title}")
logger.info(f"[B站] 定时发布: {'' if data.dtime > 0 else ''}")
# 3. Upload video
with BiliBili(data) as bili:
# Login with cookies
bili.login_by_cookies(cookie_data)
bili.access_token = cookie_data.get('access_token', '')
# Upload file (3 threads, auto line selection)
video_part = bili.upload_file(
str(self.file_path),
lines='AUTO',
tasks=3
)
video_part['title'] = self.title
data.append(video_part)
# Submit
ret = bili.submit()
# Debug: log full response
logger.debug(f"[B站] API响应: {ret}")
if ret.get('code') == 0:
# Try multiple keys for bvid (API may vary)
bvid = ret.get('data', {}).get('bvid') or ret.get('bvid', '')
aid = ret.get('data', {}).get('aid') or ret.get('aid', '')
if bvid:
logger.success(f"[B站] 上传成功: {bvid}")
return {
"success": True,
"message": "上传成功" if data.dtime == 0 else "已设置定时发布",
"url": f"https://www.bilibili.com/video/{bvid}"
}
elif aid:
logger.success(f"[B站] 上传成功: av{aid}")
return {
"success": True,
"message": "上传成功" if data.dtime == 0 else "已设置定时发布",
"url": f"https://www.bilibili.com/video/av{aid}"
}
else:
# No bvid/aid but code=0, still consider success
logger.warning(f"[B站] 上传返回code=0但无bvid/aid: {ret}")
return {
"success": True,
"message": "上传成功(无法获取视频链接)",
"url": None
}
else:
error_msg = ret.get('message', '未知错误')
logger.error(f"[B站] 上传失败: {error_msg} (完整响应: {ret})")
return {
"success": False,
"message": f"上传失败: {error_msg}",
"url": None
}
except Exception as e:
logger.exception(f"[B站] 上传异常: {e}")
return {
"success": False,
"message": f"上传异常: {str(e)}",
"url": None
}