|
"""
|
|
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):
|
|
"""
|
|
内容类型
|
|
|
|
+ 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"
|
|
|
|
|
|
@dataclass
|
|
class AuthorInfo:
|
|
name: str
|
|
uid: int = 0
|
|
|
|
|
|
@dataclass
|
|
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"]
|
|
|
|
|
|
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:
|
|
|
|
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"]
|
|
|
|
raise_for_statement(os.path.getsize(cover) < 1024 * 1024 * 3, "3MB size limit")
|
|
|
|
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
|
|
|