From 0e4e6b529fa7edec758c75a6c8d959870b1cc976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=B6=E7=B2=89?= <53338804+KNaiFen@users.noreply.github.com> Date: Tue, 28 Feb 2023 04:56:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EBILIBILI=E5=BC=B9=E5=B9=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BF=AE=E6=AD=A3config=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=EF=BC=8C=E4=BF=AE=E6=94=B9changelog=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E8=99=8E=E7=89=99=E5=BC=B9=E5=B9=95=E9=A2=9C=E8=89=B2?= =?UTF-8?q?=20(#348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 虎牙、斗鱼录制弹幕功能 * 修了一下会PRINT弹幕的毛病 * 1. 修正config.toml格式 2. 补全config.toml设置 3. config新增斗鱼和虎牙的弹幕录制开关 * 增加bilibili弹幕支持 修正虎牙弹幕颜色 * 增加bilibili弹幕支持 修正虎牙弹幕颜色 * 修改CHANGELOG、补全config文件的备注 * 无 --------- Co-authored-by: KNaiFen --- CHANGELOG.md | 7 ++ biliup/plugins/Danmaku/src/__init__.py | 14 +-- biliup/plugins/Danmaku/src/bilibili.py | 116 +++++++++++++++++++++++++ biliup/plugins/Danmaku/src/douyu.py | 10 ++- biliup/plugins/Danmaku/src/huya.py | 25 ++++-- biliup/plugins/bilibili.py | 14 +++ public/config.toml | 4 + public/config.yaml | 6 +- 8 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 biliup/plugins/Danmaku/src/bilibili.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f1bb826..2632f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/biliup/plugins/Danmaku/src/__init__.py b/biliup/plugins/Danmaku/src/__init__.py index 9d1ec88..db6a7f2 100644 --- a/biliup/plugins/Danmaku/src/__init__.py +++ b/biliup/plugins/Danmaku/src/__init__.py @@ -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"] diff --git a/biliup/plugins/Danmaku/src/bilibili.py b/biliup/plugins/Danmaku/src/bilibili.py new file mode 100644 index 0000000..67fd60a --- /dev/null +++ b/biliup/plugins/Danmaku/src/bilibili.py @@ -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 diff --git a/biliup/plugins/Danmaku/src/douyu.py b/biliup/plugins/Danmaku/src/douyu.py index 0f71e47..82dc511 100644 --- a/biliup/plugins/Danmaku/src/douyu.py +++ b/biliup/plugins/Danmaku/src/douyu.py @@ -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 diff --git a/biliup/plugins/Danmaku/src/huya.py b/biliup/plugins/Danmaku/src/huya.py index 67ee5b5..7ab9625 100644 --- a/biliup/plugins/Danmaku/src/huya.py +++ b/biliup/plugins/Danmaku/src/huya.py @@ -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 diff --git a/biliup/plugins/bilibili.py b/biliup/plugins/bilibili.py index adca181..9dff9f4 100644 --- a/biliup/plugins/bilibili.py +++ b/biliup/plugins/bilibili.py @@ -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: diff --git a/public/config.toml b/public/config.toml index e9f5757..f8c88d6 100644 --- a/public/config.toml +++ b/public/config.toml @@ -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,默认无 diff --git a/public/config.yaml b/public/config.yaml index a6de1e1..5bee532 100644 --- a/public/config.yaml +++ b/public/config.yaml @@ -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,默认无