新增BILIBILI弹幕支持,修正config注释,修改changelog,修正虎牙弹幕颜色 (#348)
* 虎牙、斗鱼录制弹幕功能 * 修了一下会PRINT弹幕的毛病 * 1. 修正config.toml格式 2. 补全config.toml设置 3. config新增斗鱼和虎牙的弹幕录制开关 * 增加bilibili弹幕支持 修正虎牙弹幕颜色 * 增加bilibili弹幕支持 修正虎牙弹幕颜色 * 修改CHANGELOG、补全config文件的备注 * 无 --------- Co-authored-by: KNaiFen <knaifen@foxmail.com>
This commit is contained in:
1 parent
871cfe41f8
commit
0e4e6b529f
8 files changed
+177
-19
No files matched your search
@@ -5,6 +5,13 @@
|
||||
- 🔧已修复的问题
|
||||
- ⚠️需要手动操作的更新信息
|
||||
|
||||
## 0.4.5 (待发布)
|
||||
- 更新时间:未发行
|
||||
- 💡新增斗鱼,虎牙,B站的弹幕录制功能,默认关闭,需要在config文件中开启,只支持FFMPEG(目前)[@KNaiFen](https://github.com/KNaiFen) (感谢:[THMonster/danmaku](https://github.com/THMonster/danmaku) )
|
||||
- 🔧修复了BILIBILI录制中OV05节点的BUG[@haha114514](https://github.com/haha114514)
|
||||
- ⚠️由于为Config新增了弹幕录制的设置,如需使用相关功能请参考最新的config示例添加缺失的部分。
|
||||
- 🔧优化代码[@ForgQi](https://github.com/ForgQi)
|
||||
|
||||
## 0.4.4 ⚠️⚠️本次修改了config内部分参数的名称,请需要使用的用户参考最新的config示例修改
|
||||
- 更新时间:2023.02.20
|
||||
- 💡统一了Config中关键词替换的关键词。 [@haha114514](https://github.com/haha114514)
|
||||
|
||||
@@ -12,6 +12,7 @@ import aiohttp
|
||||
|
||||
from .douyu import Douyu
|
||||
from .huya import Huya
|
||||
from .bilibili import Bilibili
|
||||
|
||||
__all__ = ['DanmakuClient']
|
||||
|
||||
@@ -33,7 +34,7 @@ class DanmakuClient:
|
||||
self.__url = url
|
||||
else:
|
||||
self.__url = 'http://' + url
|
||||
for u, s in {'douyu.com': Douyu, 'huya.com': Huya}.items():
|
||||
for u, s in {'douyu.com': Douyu, 'huya.com': Huya, 'live.bilibili.com': Bilibili}.items():
|
||||
if re.match(r'^(?:http[s]?://)?.*?%s/(.+?)$' % u, url):
|
||||
self.__site = s
|
||||
self.__u = u
|
||||
@@ -82,17 +83,20 @@ class DanmakuClient:
|
||||
tree = etree.parse(self.__filename, parser=parser)
|
||||
root = tree.getroot()
|
||||
msg_i = 0
|
||||
msg_col = {'1': '16717077', '2': '2000880', '3': '8046667', '4': '16744192', '5': '10172916', '6': '16738740'}
|
||||
msg_col = {'0': '16777215', '1': '16717077', '2': '2000880', '3': '8046667', '4': '16744192', '5': '10172916',
|
||||
'6': '16738740'}
|
||||
while not self.__stop:
|
||||
try:
|
||||
m = await self.__dm_queue.get()
|
||||
# print(m)
|
||||
if m['msg_type'] == 'danmaku':
|
||||
# print(f'{m["name"]}:{m["content"]}')
|
||||
d = etree.SubElement(root, 'd')
|
||||
if 'col' in m:
|
||||
color = msg_col[m["col"]]
|
||||
else:
|
||||
color = '16777215'
|
||||
elif 'color' in m:
|
||||
color = m["color"]
|
||||
# else:
|
||||
# color = '16777215'
|
||||
msg_time = format(time.time() - self.__starttime, '.3f')
|
||||
d.set('p', f"{msg_time},1,25,{color},0,0,0,0")
|
||||
d.text = m["content"]
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import json
|
||||
import random
|
||||
from struct import pack, unpack
|
||||
import aiohttp
|
||||
import zlib
|
||||
|
||||
|
||||
class Bilibili:
|
||||
wss_url = 'wss://broadcastlv.chat.bilibili.com/sub'
|
||||
heartbeat = b'\x00\x00\x00\x1f\x00\x10\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x5b\x6f\x62\x6a\x65\x63\x74\x20' \
|
||||
b'\x4f\x62\x6a\x65\x63\x74\x5d '
|
||||
heartbeatInterval = 60
|
||||
|
||||
@staticmethod
|
||||
async def get_ws_info(url):
|
||||
url = 'https://api.live.bilibili.com/room/v1/Room/room_init?id=' + url.split('/')[-1]
|
||||
reg_datas = []
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
room_json = json.loads(await resp.text())
|
||||
room_id = room_json['data']['room_id']
|
||||
data = json.dumps({
|
||||
'roomid': room_id,
|
||||
'uid': int(1e14 + 2e14 * random.random()),
|
||||
'protover': 1
|
||||
}, separators=(',', ':')).encode('ascii')
|
||||
data = (pack('>i', len(data) + 16) + b'\x00\x10\x00\x01' +
|
||||
pack('>i', 7) + pack('>i', 1) + data)
|
||||
reg_datas.append(data)
|
||||
|
||||
return Bilibili.wss_url, reg_datas
|
||||
|
||||
@staticmethod
|
||||
def decode_msg(data):
|
||||
dm_list_compressed = []
|
||||
dm_list = []
|
||||
ops = []
|
||||
msgs = []
|
||||
while True:
|
||||
try:
|
||||
packetLen, headerLen, ver, op, seq = unpack('!IHHII', data[0:16])
|
||||
except Exception as e:
|
||||
break
|
||||
if len(data) < packetLen:
|
||||
break
|
||||
if ver == 1 or ver == 0:
|
||||
ops.append(op)
|
||||
dm_list.append(data[16:packetLen])
|
||||
elif ver == 2:
|
||||
dm_list_compressed.append(data[16:packetLen])
|
||||
if len(data) == packetLen:
|
||||
data = b''
|
||||
break
|
||||
else:
|
||||
data = data[packetLen:]
|
||||
|
||||
for dm in dm_list_compressed:
|
||||
d = zlib.decompress(dm)
|
||||
while True:
|
||||
try:
|
||||
packetLen, headerLen, ver, op, seq = unpack('!IHHII', d[0:16])
|
||||
except Exception as e:
|
||||
break
|
||||
if len(d) < packetLen:
|
||||
break
|
||||
ops.append(op)
|
||||
dm_list.append(d[16:packetLen])
|
||||
if len(d) == packetLen:
|
||||
d = b''
|
||||
break
|
||||
else:
|
||||
d = d[packetLen:]
|
||||
|
||||
for i, d in enumerate(dm_list):
|
||||
try:
|
||||
msg = {}
|
||||
if ops[i] == 5:
|
||||
j = json.loads(d)
|
||||
msg['msg_type'] = {
|
||||
'SEND_GIFT': 'gift',
|
||||
'DANMU_MSG': 'danmaku',
|
||||
'WELCOME': 'enter',
|
||||
'NOTICE_MSG': 'broadcast',
|
||||
'LIVE_INTERACTIVE_GAME': 'interactive_danmaku' # 新增互动弹幕,经测试与弹幕内容一致
|
||||
}.get(j.get('cmd'), 'other')
|
||||
|
||||
# 2021-06-03 bilibili 字段更新, 形如 DANMU_MSG:4:0:2:2:2:0
|
||||
if msg.get('msg_type', 'UNKNOWN').startswith('DANMU_MSG'):
|
||||
msg['msg_type'] = 'danmaku'
|
||||
|
||||
if msg['msg_type'] == 'danmaku':
|
||||
msg['name'] = (j.get('info', ['', '', ['', '']])[2][1] or
|
||||
j.get('data', {}).get('uname', ''))
|
||||
msg['content'] = j.get('info', ['', ''])[1]
|
||||
msg["color"] = f"{j.get('info', '16777215')[0][3]}"
|
||||
|
||||
|
||||
elif msg['msg_type'] == 'interactive_danmaku':
|
||||
msg['name'] = j.get('data', {}).get('uname', '')
|
||||
msg['content'] = j.get('data', {}).get('msg', '')
|
||||
msg["color"] = '16777215'
|
||||
|
||||
elif msg['msg_type'] == 'broadcast':
|
||||
msg['type'] = j.get('msg_type', 0)
|
||||
msg['roomid'] = j.get('real_roomid', 0)
|
||||
msg['content'] = j.get('msg_common', '')
|
||||
msg['raw'] = j
|
||||
else:
|
||||
msg['content'] = j
|
||||
else:
|
||||
msg = {'name': '', 'content': d, 'msg_type': 'other'}
|
||||
msgs.append(msg)
|
||||
except Exception as Error:
|
||||
print(Error)
|
||||
|
||||
return msgs
|
||||
@@ -35,15 +35,17 @@ class Douyu:
|
||||
def decode_msg(data):
|
||||
msgs = []
|
||||
for msg in re.findall(b'(type@=.*?)\x00', data):
|
||||
msga = {}
|
||||
try:
|
||||
msg = msg.replace(b'@=', b'":"').replace(b'/', b'","')
|
||||
msg = msg.replace(b'@A', b'@').replace(b'@S', b'/')
|
||||
msg = json.loads((b'{"' + msg[:-2] + b'}').decode('utf8', 'ignore'))
|
||||
msg['name'] = msg.get('nn', '')
|
||||
msg['content'] = msg.get('txt', '')
|
||||
msg['msg_type'] = {'dgb': 'gift', 'chatmsg': 'danmaku',
|
||||
msga['name'] = msg.get('nn', '')
|
||||
msga['content'] = msg.get('txt', '')
|
||||
msga['msg_type'] = {'dgb': 'gift', 'chatmsg': 'danmaku',
|
||||
'uenter': 'enter'}.get(msg['type'], 'other')
|
||||
msgs.append(msg)
|
||||
msga['col'] = msg.get('col', '0')
|
||||
msgs.append(msga)
|
||||
except Exception as Error:
|
||||
print(f"捕获到异常:{Error}")
|
||||
return msgs
|
||||
@@ -52,10 +52,14 @@ class Huya:
|
||||
def decode_msg(data):
|
||||
class user(tarscore.struct):
|
||||
def readFrom(ios):
|
||||
return ios.read(tarscore.string, 2, False).decode('utf8')
|
||||
return ios.read(tarscore.string, 2, False).decode("utf8")
|
||||
|
||||
name = ''
|
||||
content = ''
|
||||
class dcolor(tarscore.struct):
|
||||
def readFrom(ios):
|
||||
return ios.read(tarscore.int32, 0, False)
|
||||
|
||||
name = ""
|
||||
content = ""
|
||||
msgs = []
|
||||
ios = tarscore.TarsInputStream(data)
|
||||
|
||||
@@ -64,11 +68,14 @@ class Huya:
|
||||
if ios.read(tarscore.int64, 1, False) == 1400:
|
||||
ios = tarscore.TarsInputStream(ios.read(tarscore.bytes, 2, False))
|
||||
name = ios.read(user, 0, False) # username
|
||||
content = ios.read(tarscore.string, 3, False).decode('utf8') # content
|
||||
content = ios.read(tarscore.string, 3, False).decode("utf8") # content
|
||||
color = ios.read(dcolor, 6, False) # danmaku color
|
||||
if color == -1:
|
||||
color = 16777215
|
||||
|
||||
if name != '':
|
||||
msg = {'name': name, 'content': content, 'msg_type': 'danmaku'}
|
||||
else:
|
||||
msg = {'name': '', 'content': '', 'msg_type': 'other'}
|
||||
msgs.append(msg)
|
||||
if name != "":
|
||||
msg = {"name": name, "color": f"{color}", "content": content, "msg_type": "danmaku"}
|
||||
# else:
|
||||
# msg = {"name": "", "content": "", "msg_type": "other"}
|
||||
msgs.append(msg)
|
||||
return msgs
|
||||
@@ -1,8 +1,11 @@
|
||||
import asyncio
|
||||
|
||||
import requests
|
||||
import re
|
||||
import random
|
||||
from . import match1, logger
|
||||
from biliup.config import config
|
||||
from .Danmaku.danmaku_main import Danmaku
|
||||
from ..engine.decorators import Plugin
|
||||
from ..engine.download import DownloadBase
|
||||
|
||||
@@ -16,6 +19,7 @@ class Bilibili(DownloadBase):
|
||||
self.fake_headers['cookie'] = config.get('user', {}).get('bili_cookie')
|
||||
self.customAPI_use_cookie = config.get('user', {}).get('customAPI_use_cookie')
|
||||
self.bili_cdn_fallback = config.get('bili_cdn_fallback', True)
|
||||
self.bilibili_danmaku = config.get('bilibili_danmaku', False)
|
||||
|
||||
def check_stream(self):
|
||||
# 预读配置
|
||||
@@ -119,6 +123,16 @@ class Bilibili(DownloadBase):
|
||||
break
|
||||
return True
|
||||
|
||||
def danmaku_download_start(self, filename):
|
||||
if self.bilibili_danmaku:
|
||||
self.danmaku = None
|
||||
self.danmaku = Danmaku(filename, self.url)
|
||||
self.danmaku.start()
|
||||
|
||||
def danmaku_download_stop(self):
|
||||
if self.bilibili_danmaku:
|
||||
asyncio.run(self.danmaku.stop())
|
||||
|
||||
|
||||
def get_play_info(s, custom_api, official_api_host, params):
|
||||
if custom_api:
|
||||
|
||||
@@ -45,14 +45,18 @@ check_sourcecode = 15
|
||||
#------斗鱼------#
|
||||
# 如遇到斗鱼录制卡顿可以尝试切换线路,tct-h5(备用线路5),ali-h5(备用线路6),akm-h5(主线路1)
|
||||
#douyucdn = "tct-h5"
|
||||
# 录制斗鱼弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
#douyu_danmaku = false
|
||||
|
||||
#------虎牙------#
|
||||
# 如遇到虎牙录制卡顿可以尝试切换线路,AL, HW, TX, WS
|
||||
#huyacdn = "AL"
|
||||
# 录制虎牙弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
#huya_danmaku = false
|
||||
|
||||
#------哔哩哔哩------#
|
||||
# 录制BILIBILI弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
# bilibili_danmaku = false
|
||||
### 哔哩哔哩直播流协议.可选 stream(Flv)、hls,默认为stream
|
||||
#bili_protocol = stream
|
||||
### 哔哩哔哩直播优选CDN,默认无
|
||||
|
||||
+5
-1
@@ -43,14 +43,18 @@ check_sourcecode: 15
|
||||
#------斗鱼------#
|
||||
### 如遇到斗鱼录制卡顿可以尝试切换线路,tct-h5(备用线路5),ali-h5(备用线路6),akm-h5(主线路1)
|
||||
#douyucdn: tct-h5
|
||||
# 录制斗鱼弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
#douyu_danmaku: false
|
||||
|
||||
#------虎牙------#
|
||||
### 如遇到虎牙录制卡顿可以尝试切换线路,AL, HW, TX, WS
|
||||
#huyacdn: AL
|
||||
# 录制虎牙弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
#huya_danmaku: false
|
||||
|
||||
#------哔哩哔哩------#
|
||||
#------哔哩哔哩------#\
|
||||
# 录制BILIBILI弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
# bilibili_danmaku: false
|
||||
### 哔哩哔哩直播流协议.可选 stream(Flv)、hls,默认为stream
|
||||
#bili_protocol: stream
|
||||
### 哔哩哔哩直播优选CDN,默认无
|
||||
|
||||
Reference in new issue
Block a user