Spaces:
Sleeping
Sleeping
""" | |
bilibili_api.audio_uploader | |
音频上传 | |
""" | |
import asyncio | |
import os | |
import json | |
import time | |
from enum import Enum | |
from typing import List, Union, Optional | |
from dataclasses import dataclass, field | |
from . import user | |
from .utils.upos import UposFile, UposFileUploader | |
from .utils.utils import get_api, raise_for_statement | |
from .utils.picture import Picture | |
from .utils.AsyncEvent import AsyncEvent | |
from .utils.credential import Credential | |
from .exceptions.ApiException import ApiException | |
from .utils.network import Api, get_session, HEADERS | |
from .exceptions.NetworkException import NetworkException | |
from enum import Enum | |
_API = get_api("audio_uploader") | |
class SongCategories: | |
class ContentType(Enum): # cr_type | |
""" | |
内容类型 | |
+ MUSIC: 音乐 | |
+ AUDIO_PROGRAM: 有声节目 | |
""" | |
MUSIC = 1 | |
AUDIO_PROGRAM = 2 | |
class CreationType(Enum): | |
""" | |
创作类型 | |
+ ORIGINAL: 原创 | |
+ COVER: 翻唱/翻奏 | |
+ REMIX: 改编/remix | |
""" | |
ORIGINAL = 1 | |
COVER = 2 | |
REMIX = 48 | |
class SongType(Enum): | |
""" | |
声音类型 | |
+ HUMAN_SINGING: 人声演唱 | |
+ VOCALOID: VOCALOID歌手 | |
+ HUMAN_GHOST: 人力鬼畜 | |
+ PURE_MUSIC: 纯音乐/演奏 | |
""" | |
HUMAN_SINGING = 3 | |
VOCALOID = 4 | |
HUMAN_GHOST = 5 | |
PURE_MUSIC = 6 | |
class Style(Enum): | |
""" | |
音乐风格 | |
+ POP: 流行 | |
+ ANCIENT: 古风 | |
+ ROCK: 摇滚 | |
+ FOLK: 民谣 | |
+ ELECTRONIC: 电子 | |
+ DANCE: 舞曲 | |
+ RAP: 说唱 | |
+ LIGHT_MUSIC: 轻音乐 | |
+ ACAPELLA: 阿卡贝拉 | |
+ JAZZ: 爵士 | |
+ COUNTRY: 乡村 | |
+ RNB_SOUL: R&B/Soul | |
+ CLASSICAL: 古典 | |
+ ETHNIC: 民族 | |
+ BRITISH: 英伦 | |
+ METAL: 金属 | |
+ PUNK: 朋克 | |
+ BLUES: 蓝调 | |
+ REGGAE: 雷鬼 | |
+ WORLD_MUSIC: 世界音乐 | |
+ LATIN: 拉丁 | |
+ ALTERNATIVE_INDEPENDENT: 另类/独立 | |
+ NEW_AGE: New Age | |
+ POST_ROCK: 后摇 | |
+ BOSSA_NOVA: Bossa Nova | |
""" | |
POP = 7 | |
ANCIENT = 8 | |
ROCK = 9 | |
FOLK = 10 | |
ELECTRONIC = 11 | |
DANCE = 12 | |
RAP = 13 | |
LIGHT_MUSIC = 14 | |
ACAPELLA = 15 | |
JAZZ = 16 | |
COUNTRY = 17 | |
RNB_SOUL = 18 | |
CLASSICAL = 19 | |
ETHNIC = 20 | |
BRITISH = 21 | |
METAL = 22 | |
PUNK = 23 | |
BLUES = 24 | |
REGGAE = 25 | |
WORLD_MUSIC = 26 | |
LATIN = 27 | |
ALTERNATIVE_INDEPENDENT = 28 | |
NEW_AGE = 29 | |
POST_ROCK = 30 | |
BOSSA_NOVA = 31 | |
class Language(Enum): | |
""" | |
语言 | |
+ CHINESE: 华语 | |
+ JAPANESE: 日语 | |
+ ENGLISH: 英语 | |
+ KOREAN: 韩语 | |
+ CANTONESE: 粤语 | |
+ OTHER_LANGUAGES: 其他语种 | |
""" | |
CHINESE = 32 | |
JAPANESE = 33 | |
ENGLISH = 34 | |
KOREAN = 35 | |
CANTONESE = 36 | |
OTHER_LANGUAGES = 37 | |
class Theme(Enum): | |
""" | |
主题 | |
+ ANIMATION: 动画 | |
+ GAME: 游戏 | |
+ FILM_AND_TELEVISION: 影视 | |
+ INTERNET_SONG: 网络歌曲 | |
+ SECOND_CREATION: 同人 | |
+ IDOL: 偶像 | |
""" | |
ANIMATION = 38 | |
GAME = 39 | |
FILM_AND_TELEVISION = 40 | |
INTERNET_SONG = 41 | |
SECOND_CREATION = 42 | |
IDOL = 43 | |
class AudioType(Enum): | |
""" | |
有声节目类型 | |
+ RADIO_DRAMA: 广播剧 | |
+ AUDIO_STORY: 有声故事 | |
+ OTHER: 其他 | |
""" | |
RADIO_DRAMA = 44 | |
AUDIO_STORY = 45 | |
OTHER = 47 | |
class CompilationCategories: | |
class SongType(Enum): | |
""" | |
声音类型 | |
+ HUMAN_SINGING: 人声演唱 | |
+ VOCALOID_SINGER: VOCALOID歌手 | |
+ HUMAN_KICHUKU: 人力鬼畜 | |
+ PURE_MUSIC: 纯音乐 | |
""" | |
HUMAN_SINGING = 102 | |
VOCALOID_SINGER = 103 | |
HUMAN_KICHUKU = 104 | |
PURE_MUSIC = 105 | |
class CreationType(Enum): | |
""" | |
创作类型 | |
+ ORIGINAL: 原创 | |
+ COVER: 翻唱/翻奏 | |
+ REMIX: 改编/remix | |
""" | |
ORIGINAL = 106 | |
COVER = 107 | |
REMIX = 108 | |
class Language(Enum): | |
""" | |
语种 | |
+ CHINESE: 中文 | |
+ JAPANESE: 日语 | |
+ ENGLISH: 英语 | |
+ KOREAN: 韩语 | |
+ CANTONESE: 粤语 | |
+ OTHER_LANGUAGES: 其他语种 | |
""" | |
CHINESE = 109 | |
JAPANESE = 110 | |
ENGLISH = 111 | |
KOREAN = 112 | |
CANTONESE = 113 | |
OTHER_LANGUAGES = 114 | |
class Theme(Enum): | |
""" | |
主题来源 | |
+ GAME: 游戏 | |
+ ANIMATION: 动画 | |
+ FILM_AND_TELEVISION: 影视 | |
+ NETWORK_SONG: 网络歌曲 | |
+ DERIVATIVE_WORK: 同人 | |
+ IDOL: 偶像 | |
""" | |
ANIMATION = 115 | |
GAME = 116 | |
FILM_AND_TELEVISION = 117 | |
NETWORK_SONG = 118 | |
DERIVATIVE_WORK = 119 | |
IDOL = 120 | |
class Style(Enum): | |
""" | |
风格 | |
+ POP: 流行 | |
+ ANCIENT_STYLE: 古风 | |
+ ROCK: 摇滚 | |
+ FOLK_SONG: 民谣 | |
+ ELECTRONIC: 电子 | |
+ DANCE_MUSIC: 舞曲 | |
+ RAP: 说唱 | |
+ LIGHT_MUSIC: 轻音乐 | |
+ A_CAPPELLA: 阿卡贝拉 | |
+ JAZZ: 爵士 | |
+ COUNTRY_MUSIC: 乡村 | |
+ R_AND_B: R&B/Soul | |
+ CLASSICAL: 古典 | |
+ CLASSICAL: 古典 | |
+ ETHNIC: 民族 | |
+ BRITISH: 英伦 | |
+ METAL: 金属 | |
+ PUNK: 朋克 | |
+ BLUES: 蓝调 | |
+ REGGAE: 雷鬼 | |
+ WORLD_MUSIC: 世界音乐 | |
+ LATIN: 拉丁 | |
+ ALTERNATIVE: 另类/独立 | |
+ NEW_AGE: New Age | |
+ POST_ROCK: 后摇 | |
+ BOSSA_NOVA: Bossa Nova | |
""" | |
POP = 121 | |
ANCIENT_STYLE = 122 | |
ROCK = 123 | |
FOLK_SONG = 124 | |
ELECTRONIC = 125 | |
DANCE_MUSIC = 126 | |
RAP = 127 | |
LIGHT_MUSIC = 128 | |
A_CAPPELLA = 129 | |
JAZZ = 130 | |
COUNTRY_MUSIC = 131 | |
R_AND_B = 132 | |
CLASSICAL = 133 | |
ETHNIC = 134 | |
BRITISH = 135 | |
METAL = 136 | |
PUNK = 137 | |
BLUES = 138 | |
REGGAE = 139 | |
WORLD_MUSIC = 140 | |
LATIN = 141 | |
ALTERNATIVE = 142 | |
NEW_AGE = 143 | |
POST_ROCK = 144 | |
BOSSA_NOVA = 145 | |
class ContentType(Enum): | |
""" | |
内容类型 | |
+ MUSIC: 音乐 | |
+ AUDIO_PROGRAM: 有声节目 | |
""" | |
MUSIC = 146 | |
AUDIO_PROGRAM = 147 | |
class AudioType(Enum): | |
""" | |
声音类型 | |
+ RADIO_DRAMA: 广播剧 | |
+ AUDIO_STORY: 有声故事 | |
+ ASMR: ASMR | |
+ OTHER: 其他 | |
""" | |
RADIO_DRAMA = 148 | |
AUDIO_STORY = 149 | |
ASMR = 150 | |
OTHER = 151 | |
class AudioUploaderEvents(Enum): | |
""" | |
上传事件枚举 | |
Events: | |
+ PREUPLOAD 获取上传信息 | |
+ PREUPLOAD_FAILED 获取上传信息失败 | |
+ PRE_CHUNK 上传分块前 | |
+ AFTER_CHUNK 上传分块后 | |
+ CHUNK_FAILED 区块上传失败 | |
+ PRE_COVER 上传封面前 | |
+ AFTER_COVER 上传封面后 | |
+ COVER_FAILED 上传封面失败 | |
+ PRE_SUBMIT 提交音频前 | |
+ SUBMIT_FAILED 提交音频失败 | |
+ AFTER_SUBMIT 提交音频后 | |
+ COMPLETED 完成上传 | |
+ ABORTED 用户中止 | |
+ FAILED 上传失败 | |
""" | |
PREUPLOAD = "PREUPLOAD" | |
PREUPLOAD_FAILED = "PREUPLOAD_FAILED" | |
PRE_CHUNK = "PRE_CHUNK" | |
AFTER_CHUNK = "AFTER_CHUNK" | |
CHUNK_FAILED = "CHUNK_FAILED" | |
PRE_COVER = "PRE_COVER" | |
AFTER_COVER = "AFTER_COVER" | |
COVER_FAILED = "COVER_FAILED" | |
PRE_SUBMIT = "PRE_SUBMIT" | |
SUBMIT_FAILED = "SUBMIT_FAILED" | |
AFTER_SUBMIT = "AFTER_SUBMIT" | |
COMPLETED = "COMPLETE" | |
ABORTED = "ABORTED" | |
FAILED = "FAILED" | |
class AuthorInfo: | |
name: str | |
uid: int = 0 | |
class SongMeta: | |
""" | |
content_type (SongCategories.ContentType): 内容类型 | |
song_type (Union[SongCategories.SongType, SongCategories.AudioType]): 歌曲类型 | |
creation_type (SongCategories.CreationType): 创作类型 | |
language (Optional[SongCategories.Language]): 语言类型 | |
theme (Optional[SongCategories.Theme]): 主题来源 | |
style (Optional[SongCategories.Style]): 风格类型 | |
singer (List[AuthorInfo]): 歌手 | |
player (Optional[List[AuthorInfo]]): 演奏 | |
sound_source (Optional[List[AuthorInfo]]): 音源 | |
tuning (Optional[List[AuthorInfo]]): 调音 | |
lyricist (Optional[List[AuthorInfo]]): 作词 | |
arranger (List[AuthorInfo]): 编曲 | |
composer (Optional[List[AuthorInfo]]): 作曲 | |
mixer (Optional[str]): 混音 | |
cover_maker (Optional[List[AuthorInfo]]): 封面制作者 | |
instrument (Optional[List[str]]): 乐器 | |
origin_url (Optional[str]): 原曲链接 | |
origin_title (Optional[str]): 原曲标题 | |
title (str): 标题 | |
cover (Optional[Picture]): 封面 | |
description (Optional[str]): 描述 | |
tags (Union[List[str], str]): 标签 | |
aid (Optional[int]): 视频 aid | |
cid (Optional[int]): 视频 cid | |
tid (Optional[int]): 视频 tid | |
compilation_id (Optional[int]): 合辑 ID | |
lrc (Optional[str]): 歌词 | |
""" | |
title: str | |
desc: str | |
tags: Union[List[str], str] | |
content_type: SongCategories.ContentType | |
song_type: Union[SongCategories.SongType, SongCategories.AudioType] | |
creation_type: SongCategories.CreationType | |
language: Optional[SongCategories.Language] = None | |
theme: Optional[SongCategories.Theme] = None | |
style: Optional[SongCategories.Style] = None | |
singer: Optional[List[AuthorInfo]] = field(default_factory=list) | |
player: Optional[List[AuthorInfo]] = field(default_factory=list) | |
sound_source: Optional[List[AuthorInfo]] = field(default_factory=list) | |
tuning: Optional[List[AuthorInfo]] = field(default_factory=list) | |
lyricist: Optional[List[AuthorInfo]] = field(default_factory=list) | |
arranger: Optional[List[AuthorInfo]] = field(default_factory=list) | |
composer: Optional[List[AuthorInfo]] = field(default_factory=list) | |
mixer: Optional[List[AuthorInfo]] = field(default_factory=list) | |
cover_maker: Optional[List[AuthorInfo]] = field(default_factory=list) | |
instrument: Optional[List[str]] = field(default_factory=list) | |
origin_url: Optional[str] = None | |
origin_title: Optional[str] = None | |
cover: Optional[Picture] = None | |
aid: Optional[int] = None | |
cid: Optional[int] = None | |
tid: Optional[int] = None | |
lrc: Optional[str] = None | |
compilation_id: Optional[int] = None | |
is_bgm: bool = True | |
class AudioUploader(AsyncEvent): | |
""" | |
音频上传 | |
""" | |
__song_id: int | |
__upos_file: UposFile | |
__task: asyncio.Task | |
def _check_meta(self): | |
raise_for_statement(self.meta.content_type is not None) | |
raise_for_statement(self.meta.song_type is not None) | |
raise_for_statement(self.meta.cover is not None and isinstance(self.meta.cover, str)) | |
if self.meta.content_type == SongCategories.ContentType.MUSIC: | |
raise_for_statement(self.meta.creation_type is not None) | |
raise_for_statement(self.meta.song_type is not None) | |
raise_for_statement(self.meta.language is not None) | |
if self.meta.song_type == SongCategories.SongType.HUMAN_SINGING: | |
raise_for_statement(self.meta.singer is not None) | |
raise_for_statement(self.meta.language is not None) | |
elif self.meta.song_type in [ | |
SongCategories.SongType.VOCALOID, | |
SongCategories.SongType.HUMAN_GHOST, | |
]: | |
raise_for_statement(self.meta.sound_source is not None) | |
raise_for_statement(self.meta.language is not None) | |
raise_for_statement(self.meta.tuning is not None) | |
elif self.meta.song_type == SongCategories.SongType.PURE_MUSIC: | |
raise_for_statement(self.meta.player is not None) | |
if isinstance(self.meta.tags, str): | |
self.meta.tags = self.meta.tags.split(",") | |
raise_for_statement(len(self.meta.tags != 0)) | |
raise_for_statement(self.meta.title is not None) | |
raise_for_statement(self.meta.cover is not None) | |
raise_for_statement(self.meta.desc is not None) | |
def __init__(self, path: str, meta: SongMeta, credential: Credential): | |
""" | |
初始化 | |
Args: | |
path (str): 文件路径 | |
meta (AudioMeta): 元数据 | |
credential (Credential): 账号信息 | |
""" | |
super().__init__() | |
self.path = path | |
self.meta = meta | |
self.credential = credential | |
self.__upos_file = UposFile(path) | |
async def _preupload(self) -> dict: | |
""" | |
分 P 上传初始化 | |
Returns: | |
dict: 初始化信息 | |
""" | |
self.dispatch(AudioUploaderEvents.PREUPLOAD.value, {"song": self.meta}) | |
api = _API["preupload"] | |
# 首先获取音频文件预检信息 | |
session = get_session() | |
resp = await session.get( | |
api["url"], | |
params={ | |
"profile": "uga/bup", | |
"name": os.path.basename(self.path), | |
"size": self.__upos_file.size, | |
"r": "upos", | |
"ssl": "0", | |
"version": "2.6.0", | |
"build": 2060400, | |
}, | |
cookies=self.credential.get_cookies(), | |
headers=HEADERS.copy(), | |
) | |
if resp.status_code >= 400: | |
self.dispatch( | |
AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} | |
) | |
raise NetworkException(resp.status_code, resp.reason_phrase) | |
preupload = resp.json() | |
if preupload["OK"] != 1: | |
self.dispatch( | |
AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} | |
) | |
raise ApiException(json.dumps(preupload)) | |
url = f'https:{preupload["endpoint"]}/{preupload["upos_uri"].removeprefix("upos://")}' | |
headers = HEADERS.copy() | |
headers["x-upos-auth"] = preupload["auth"] | |
# 获取 upload_id | |
resp = await session.post( | |
url, | |
headers=headers, | |
params={ | |
"uploads": "", | |
"output": "json", | |
"profile": "uga/bup", | |
"filesize": self.__upos_file.size, | |
"partsize": preupload["chunk_size"], | |
"biz_id": preupload["biz_id"], | |
}, | |
) | |
if resp.status_code >= 400: | |
self.dispatch( | |
AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} | |
) | |
raise ApiException("获取 upload_id 错误") | |
data = json.loads(resp.text) | |
if data["OK"] != 1: | |
self.dispatch( | |
AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} | |
) | |
raise ApiException("获取 upload_id 错误:" + json.dumps(data)) | |
preupload["upload_id"] = data["upload_id"] | |
self.__song_id = preupload["biz_id"] | |
return preupload | |
async def _upload_cover(self, cover: str) -> str: | |
return await upload_cover(cover, self.credential) | |
async def _main(self): | |
preupload = await self._preupload() | |
await UposFileUploader(file=self.__upos_file, preupload=preupload).upload() | |
if self.meta.lrc: | |
lrc_url = await upload_lrc( | |
song_id=self.__song_id, lrc=self.meta.lrc, credential=self.credential | |
) | |
else: | |
lrc_url = "" | |
self.dispatch(AudioUploaderEvents.PRE_COVER) | |
if self.meta.cover: | |
try: | |
cover_url = await self._upload_cover(self.meta.cover) | |
except Exception as e: | |
self.dispatch(AudioUploaderEvents.COVER_FAILED, {"err": e}) | |
raise e | |
self.dispatch(AudioUploaderEvents.AFTER_COVER.value, cover_url) | |
self.dispatch(AudioUploaderEvents.PRE_SUBMIT.value) | |
try: | |
result = await self._submit(lrc_url=lrc_url, cover_url=cover_url) | |
except Exception as e: | |
self.dispatch(AudioUploaderEvents.SUBMIT_FAILED.value, {"err": e}) | |
raise e | |
self.dispatch(AudioUploaderEvents.AFTER_SUBMIT.value, result) | |
return result | |
async def _submit(self, cover_url: str, lrc_url: str = "") -> int: | |
uploader = await user.get_self_info(self.credential) | |
data = { | |
"lyric_url": lrc_url, | |
"cover_url": cover_url, | |
"song_id": self.__song_id, | |
"mid": uploader["mid"], | |
"cr_type": self.meta.content_type.value, | |
"creation_type_id": self.meta.creation_type.value, | |
"music_type_id": self.meta.song_type.value, | |
"style_type_id": self.meta.style.value if self.meta.style else 0, | |
"theme_type_id": self.meta.theme.value if self.meta.theme else 0, | |
"language_type_id": self.meta.language.value if self.meta.language else 0, | |
"origin_title": self.meta.origin_title if self.meta.origin_title else "", | |
"origin_url": self.meta.origin_url if self.meta.origin_url else "", | |
"avid": self.meta.aid if self.meta.aid else "", | |
"tid": self.meta.tid if self.meta.tid else "", | |
"cid": self.meta.cid if self.meta.cid else "", | |
"compilation_id": self.meta.compilation_id | |
if self.meta.compilation_id | |
else "", | |
"title": self.meta.title, | |
"intro": self.meta.desc, | |
"member_with_type": [ | |
{ | |
"m_type": 1, # 歌手 | |
"members": [ | |
{"name": singer.name, "mid": singer.uid} | |
for singer in self.meta.singer | |
], | |
}, | |
{ | |
"m_type": 2, # 作词 | |
"members": [ | |
{"name": lyricist.name, "mid": lyricist.uid} | |
for lyricist in self.meta.lyricist | |
], | |
}, | |
{ | |
"m_type": 3, | |
"members": [ | |
{"name": composer.name, "mid": composer.uid} | |
for composer in self.meta.composer | |
], | |
}, # 作曲 | |
{ | |
"m_type": 4, | |
"members": [ | |
{"name": arranger.name, "mid": arranger.uid} | |
for arranger in self.meta.arranger | |
], | |
}, # 编曲 | |
{ | |
"m_type": 5, | |
"members": [ | |
{"name": mixer.name, "mid": mixer.uid} | |
for mixer in self.meta.mixer | |
], | |
}, # 混音只能填一个人,你问我为什么我不知道 | |
{ | |
"m_type": 6, | |
"members": [ | |
{"name": cover_maker.name, "mid": cover_maker.uid} | |
for cover_maker in self.meta.cover_maker | |
], | |
}, # 本家作者 | |
{ | |
"m_type": 7, | |
"members": [ | |
{"name": cover_maker.name, "mid": cover_maker.uid} | |
for cover_maker in self.meta.cover_maker | |
], | |
}, # 封面 | |
{ | |
"m_type": 8, | |
"members": [ | |
{"name": sound_source.name, "mid": sound_source.uid} | |
for sound_source in self.meta.sound_source | |
], | |
}, # 音源 | |
{ | |
"m_type": 9, | |
"members": [ | |
{"name": tuning.name, "mid": tuning.uid} | |
for tuning in self.meta.tuning | |
], | |
}, # 调音 | |
{ | |
"m_type": 10, | |
"members": [ | |
{"name": player.name, "mid": player.uid} | |
for player in self.meta.player | |
], | |
}, # 演奏 | |
{ | |
"m_type": 11, | |
"members": [ | |
{"name": instrument} for instrument in self.meta.instrument | |
], | |
}, # 乐器 | |
{ | |
"m_type": 127, | |
"members": [{"name": uploader["name"], "mid": uploader["mid"]}], | |
}, # 上传者 | |
], | |
"song_tags": [{"tagName": tag_name} for tag_name in self.meta.tags], | |
"create_time": "%.3f" % time.time(), | |
"activity_id": 0, | |
"is_bgm": 1 if self.meta.is_bgm else 0, | |
"source": 0, | |
"album_id": 0, | |
} | |
api = _API["submit_single_song"] | |
return ( | |
await Api(**api, credential=self.credential, json_body=True, no_csrf=True) | |
.update_data(**data) | |
.result | |
) | |
async def start(self) -> dict: | |
""" | |
开始上传 | |
""" | |
task = asyncio.create_task(self._main()) | |
self.__task = task | |
try: | |
result = await task | |
self.__task = None | |
return result | |
except asyncio.CancelledError: | |
# 忽略 task 取消异常 | |
pass | |
except Exception as e: | |
self.dispatch(AudioUploaderEvents.FAILED.value, {"err": e}) | |
raise e | |
async def abort(self): | |
""" | |
中断更改 | |
""" | |
if self.__task: | |
self.__task.cancel("用户手动取消") | |
self.dispatch(AudioUploaderEvents.ABORTED.value, None) | |
async def upload_lrc(lrc: str, song_id: int, credential: Credential) -> str: | |
""" | |
上传 LRC 歌词 | |
Args: | |
lrc (str): 歌词 | |
credential (Credential): 凭据 | |
""" | |
api = _API["lrc"] | |
data = {"song_id": song_id, "lrc": lrc} | |
return await Api(**api, credential=credential).update_data(**data).result | |
async def get_upinfo(param: Union[int, str], credential: Credential) -> List[dict]: | |
""" | |
获取 UP 信息 | |
Args: | |
param (Union[int, str]): UP 主 ID 或者用户名 | |
credential (Credential): 凭据 | |
""" | |
api = _API["upinfo"] | |
data = {"param": param} | |
return await Api(**api, credential=credential).update_data(**data).result | |
async def upload_cover(cover: Picture, credential: Credential) -> str: | |
api = _API["image"] | |
# 小于 3MB | |
raise_for_statement(os.path.getsize(cover) < 1024 * 1024 * 3, "3MB size limit") | |
# 宽高比 1:1 | |
raise_for_statement(cover.width == cover.height, "width == height, 600 * 600 recommanded") | |
files = {"file": cover.content} | |
return await Api(**api, credential=credential).update_files(**files).result | |