修复斗鱼、虎牙的弹幕录制BUG,增加报错提示,增加日志,修改了XML文件的删除部分,修改了部分代码的协程的调用,优化重复请求,增加Twitch弹幕录播 (#456)
* 修复斗鱼、虎牙的弹幕录制BUG,增加报错提示,增加日志 修改了XML文件的删除部分(源代码一直删不掉XML文件导致BILIUP无限提示上传那两个文件,一晚上输出了几万行日志) 修改了部分代码的协程的调用方式 * 增加Twitch弹幕录播,开关为config文件里面的 twitch_danmaku: true,默认关闭,仍旧和以前一样,只有ffmpeg下载器才能用 --------- Co-authored-by: KNaiFen <knaifen@foxmail.com>
This commit is contained in:
1 parent
25caf52c1c
commit
8a7e95cdf0
12 files changed
+254
-121
No files matched your search
@@ -121,4 +121,5 @@ class Daemon(object):
|
||||
|
||||
def _run(self):
|
||||
""" run your fun"""
|
||||
asyncio.run(self.fn())
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(self.fn())
|
||||
+20
-13
@@ -64,7 +64,9 @@ class DownloadBase:
|
||||
filename = f'{self.fname}%Y-%m-%dT%H_%M_%S'
|
||||
filename = get_valid_filename(filename)
|
||||
fmtname = time.strftime(filename.encode("unicode-escape").decode()).encode().decode("unicode-escape")
|
||||
threading.Thread(target=asyncio.run, args=(self.danmaku_download_start(fmtname),)).start()
|
||||
# threading.Thread(target=asyncio.run, args=(self.danmaku_download_start(fmtname),)).start()
|
||||
loop = asyncio.new_event_loop()
|
||||
threading.Thread(target=loop.run_until_complete, args=(self.danmaku_download_start(fmtname),)).start()
|
||||
if self.downloader == 'stream-gears':
|
||||
stream_gears_download(self.raw_stream_url, self.fake_headers, filename, config.get('segment_time'),
|
||||
config.get('file_size'))
|
||||
@@ -168,8 +170,17 @@ class DownloadBase:
|
||||
i = 0
|
||||
logger.info('开始下载%s:%s' % (self.__class__.__name__, self.fname))
|
||||
date = time.localtime()
|
||||
lasttime_download = 0
|
||||
thistime_download = 0
|
||||
delay = config.get('delay') if (config.get('delay') > 5) else 5
|
||||
while i < 30:
|
||||
try:
|
||||
# 限制每次拉流的时间间隔必须大于config.get('delay'),防止主播还没推流的时候疯狂重复请求
|
||||
thistime_download = time.time()
|
||||
interval = thistime_download - lasttime_download
|
||||
if interval < delay:
|
||||
logger.info(f"频繁请求:等待{interval}秒后再次请求直播流")
|
||||
time.sleep(config.get('delay') - interval)
|
||||
ret = self.run()
|
||||
except:
|
||||
logger.exception("Uncaught exception:")
|
||||
@@ -177,21 +188,17 @@ class DownloadBase:
|
||||
finally:
|
||||
self.close()
|
||||
if ret is False:
|
||||
if i < 4:
|
||||
logger.info(f"获取直播流失败{i},等待5秒尝试重新获取")
|
||||
time.sleep(5)
|
||||
i += 1
|
||||
continue
|
||||
else:
|
||||
if config.get('delay'):
|
||||
time.sleep(config.get('delay'))
|
||||
logger.info(f"delay: {config.get('delay')}")
|
||||
if self.check_stream():
|
||||
time.sleep(5)
|
||||
continue
|
||||
if config.get('delay'):
|
||||
time.sleep(config.get('delay'))
|
||||
logger.info(f"delay: {config.get('delay')}")
|
||||
if self.check_stream():
|
||||
time.sleep(5)
|
||||
continue
|
||||
break
|
||||
elif ret == 1 or self.downloader == 'stream-gears':
|
||||
time.sleep(45)
|
||||
|
||||
lasttime_download = thistime_download
|
||||
i += 1
|
||||
logger.info(f'退出下载{i}: {self.fname}')
|
||||
return {
|
||||
|
||||
+11
-5
@@ -34,7 +34,7 @@ class UploadBase:
|
||||
|
||||
|
||||
def filter_file(self, index):
|
||||
media_extensions = ['.mp4', '.flv', '.ts']
|
||||
media_extensions = ['.mp4', '.flv', '.ts', '.flv.part']
|
||||
file_list = UploadBase.file_list(index)
|
||||
if len(file_list) == 0:
|
||||
return False
|
||||
@@ -53,10 +53,16 @@ class UploadBase:
|
||||
logger.info(f'过滤删除-{r}')
|
||||
if ext == '.xml': #过滤不存在对应视频的xml弹幕文件
|
||||
xml_file_name = name
|
||||
media_regex = re.compile(r'^{}(\.(mp4|flv|ts))?$'.format(
|
||||
re.escape(xml_file_name)
|
||||
))
|
||||
if not any(media_regex.match(f'{xml_file_name}{ext2}') for ext2 in media_extensions for x in file_list):
|
||||
# media_regex = re.compile(r'^{}(\.(mp4|flv|ts))?$'.format(
|
||||
# re.escape(xml_file_name)
|
||||
# ))
|
||||
# if not any(media_regex.match(f'{xml_file_name}{ext2}') for ext2 in media_extensions for x in file_list):
|
||||
# self.remove_file(r)
|
||||
# logger.info(f'无视频,已过滤删除-{r}')
|
||||
have_video = False
|
||||
for video_ext in media_extensions:
|
||||
if f"{xml_file_name}{video_ext}" in file_list: have_video = True
|
||||
if not have_video:
|
||||
self.remove_file(r)
|
||||
logger.info(f'无视频,已过滤删除-{r}')
|
||||
file_list = UploadBase.file_list(index)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# 部分弹幕功能代码来自项目:https://github.com/IsoaSFlus/danmaku,感谢大佬
|
||||
# 快手弹幕代码来源及思路:https://github.com/py-wuhao/ks_barrage,感谢大佬
|
||||
# 部分斗鱼录播修复代码与思路来源于:https://github.com/SmallPeaches/DanmakuRender,感谢大佬
|
||||
# 仅抓取用户弹幕,不包括入场提醒、礼物赠送等。
|
||||
|
||||
import asyncio
|
||||
@@ -7,6 +8,7 @@ import os
|
||||
import re
|
||||
import ssl
|
||||
import time
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
import lxml.etree as etree
|
||||
@@ -16,7 +18,9 @@ import aiohttp
|
||||
from biliup.plugins.Danmaku.douyu import Douyu
|
||||
from biliup.plugins.Danmaku.huya import Huya
|
||||
from biliup.plugins.Danmaku.bilibili import Bilibili
|
||||
from biliup.plugins.Danmaku.twitch import Twitch
|
||||
|
||||
logger = logging.getLogger('biliup')
|
||||
__all__ = ['DanmakuClient']
|
||||
|
||||
|
||||
@@ -37,7 +41,7 @@ class DanmakuClient:
|
||||
self.__url = url
|
||||
else:
|
||||
self.__url = 'http://' + url
|
||||
for u, s in {'douyu.com': Douyu, 'huya.com': Huya, 'live.bilibili.com': Bilibili}.items():
|
||||
for u, s in {'douyu.com': Douyu, 'huya.com': Huya, 'live.bilibili.com': Bilibili, 'twitch.tv': Twitch}.items():
|
||||
if re.match(r'^(?:http[s]?://)?.*?%s/(.+?)$' % u, url):
|
||||
self.__site = s
|
||||
self.__u = u
|
||||
@@ -50,39 +54,56 @@ class DanmakuClient:
|
||||
ws_url, reg_datas = await self.__site.get_ws_info(self.__url)
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.set_ciphers('DEFAULT')
|
||||
self.__ws = await self.__hs.ws_connect(ws_url, ssl_context=ctx)
|
||||
if reg_datas:
|
||||
try:
|
||||
self.__ws = await self.__hs.ws_connect(ws_url, ssl_context=ctx)
|
||||
for reg_data in reg_datas:
|
||||
await self.__ws.send_bytes(reg_data)
|
||||
if type(reg_data) == str:
|
||||
await self.__ws.send_str(reg_data)
|
||||
else:
|
||||
await self.__ws.send_bytes(reg_data)
|
||||
except Exception as Error:
|
||||
logger.debug(f"init_ws:捕获到异常:{Error}")
|
||||
if not os.path.exists(self.__filename):
|
||||
async with aiofiles.open(self.__filename, mode='w') as f:
|
||||
await f.write("<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||
"<i xmlns:ns0='http://www.w3.org/1999/XSL/Transform'>\n"
|
||||
"</i>"
|
||||
)
|
||||
"<i xmlns:ns0='http://www.w3.org/1999/XSL/Transform'>\n"
|
||||
"</i>"
|
||||
)
|
||||
|
||||
async def heartbeats(self):
|
||||
while not self.__stop and self.__site.heartbeat:
|
||||
await asyncio.sleep(self.__site.heartbeatInterval)
|
||||
try:
|
||||
await self.__ws.send_bytes(self.__site.heartbeat)
|
||||
if type(self.__site.heartbeat) == str:
|
||||
await self.__ws.send_str(self.__site.heartbeat)
|
||||
else:
|
||||
await self.__ws.send_bytes(self.__site.heartbeat)
|
||||
except Exception as Error:
|
||||
print(f"捕获到异常:{Error}")
|
||||
logger.debug(f"heartbeats:捕获到异常:{Error}")
|
||||
if not self.__stop:
|
||||
logger.info(f'触发弹幕重连')
|
||||
await self.init_ws()
|
||||
await asyncio.sleep(3)
|
||||
|
||||
await self.__hs.close()
|
||||
|
||||
async def fetch_danmaku(self):
|
||||
# while not self.__stop:
|
||||
await asyncio.sleep(1)
|
||||
await self.init_ws()
|
||||
await asyncio.sleep(1)
|
||||
async for msg in self.__ws:
|
||||
await asyncio.sleep(1)
|
||||
await self.init_ws()
|
||||
await asyncio.sleep(1)
|
||||
async for msg in self.__ws:
|
||||
try:
|
||||
if self.__stop:
|
||||
await self.__dm_queue.put(None)
|
||||
return
|
||||
self.__link_status = True
|
||||
# self.__link_status = True
|
||||
ms = self.__site.decode_msg(msg.data)
|
||||
for m in ms:
|
||||
await self.__dm_queue.put(m)
|
||||
except Exception as Error:
|
||||
logger.debug(f"fetch_danmaku:弹幕处理异常:{Error}")
|
||||
await asyncio.sleep(10)
|
||||
|
||||
async def print_danmaku(self):
|
||||
parser = etree.XMLParser(recover=True)
|
||||
@@ -102,7 +123,6 @@ class DanmakuClient:
|
||||
m = await self.__dm_queue.get()
|
||||
if m is None:
|
||||
return
|
||||
# print(m)
|
||||
if m['msg_type'] == 'danmaku':
|
||||
d = etree.SubElement(root, 'd')
|
||||
if 'col' in m:
|
||||
@@ -114,16 +134,20 @@ class DanmakuClient:
|
||||
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"]
|
||||
|
||||
if msg_i >= 5:
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, partial(write_file, self.__filename))
|
||||
msg_i = 0
|
||||
else:
|
||||
msg_i = msg_i + 1
|
||||
|
||||
except Exception as Error:
|
||||
print(f"捕获到异常:{Error}")
|
||||
logger.debug(f"print_danmaku:弹幕处理异常:{Error}")
|
||||
|
||||
try:
|
||||
if m['msg_type'] == 'danmaku' and msg_i >= 5:
|
||||
# loop = asyncio.get_running_loop()
|
||||
# await loop.run_in_executor(None, partial(write_file, self.__filename))
|
||||
write_file(self.__filename)
|
||||
msg_i = 0
|
||||
else:
|
||||
msg_i = msg_i + 1
|
||||
except Exception as Error:
|
||||
logger.debug(f"print_danmaku:弹幕写入异常:{Error}")
|
||||
|
||||
|
||||
async def start(self):
|
||||
self.__dm_queue = asyncio.Queue()
|
||||
@@ -134,10 +158,10 @@ class DanmakuClient:
|
||||
self.fetch_danmaku(),
|
||||
self.print_danmaku(),
|
||||
)
|
||||
print("弹幕录制结束")
|
||||
|
||||
def stop(self):
|
||||
self.__stop = True
|
||||
self.__hs.close()
|
||||
if not (os.path.exists(f"{self.__filename_video_suffix}.part") or
|
||||
os.path.exists(f"{self.__filename_video_suffix}")):
|
||||
os.remove(self.__filename)
|
||||
|
||||
@@ -6,10 +6,9 @@ import aiohttp
|
||||
|
||||
|
||||
class Douyu:
|
||||
wss_url = 'wss://danmuproxy.douyu.com:8503/'
|
||||
heartbeat = b'\x14\x00\x00\x00\x14\x00\x00\x00\xb1\x02\x00\x00\x74\x79\x70\x65\x40\x3d\x6d\x72\x6b\x6c' \
|
||||
b'\x2f\x00 '
|
||||
heartbeatInterval = 60
|
||||
wss_url = 'wss://danmuproxy.douyu.com:8506/'
|
||||
heartbeat = b"\x14\x00\x00\x00\x14\x00\x00\x00\xb1\x02\x00\x00\x74\x79\x70\x65\x40\x3d\x6d\x72\x6b\x6c\x2f\x00"
|
||||
heartbeatInterval = 30
|
||||
|
||||
@staticmethod
|
||||
async def get_ws_info(url):
|
||||
@@ -47,5 +46,6 @@ class Douyu:
|
||||
msga['col'] = msg.get('col', '0')
|
||||
msgs.append(msga)
|
||||
except Exception as Error:
|
||||
print(f"捕获到异常:{Error}")
|
||||
# print(f"decode_msg:捕获到异常:{Error}")
|
||||
pass
|
||||
return msgs
|
||||
@@ -16,37 +16,41 @@ class Huya:
|
||||
|
||||
@staticmethod
|
||||
async def get_ws_info(url):
|
||||
reg_datas = []
|
||||
url = 'https://m.huya.com/' + url.split('/')[-1]
|
||||
headers = {
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, '
|
||||
'like Gecko) Chrome/79.0.3945.88 Mobile Safari/537.36'}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers) as resp:
|
||||
room_page = await resp.text()
|
||||
m = re.search(r"lYyid\":([0-9]+)", room_page, re.MULTILINE)
|
||||
ayyuid = m.group(1)
|
||||
m = re.search(r"lChannelId\":([0-9]+)", room_page, re.MULTILINE)
|
||||
tid = m.group(1)
|
||||
m = re.search(r"lSubChannelId\":([0-9]+)", room_page, re.MULTILINE)
|
||||
sid = m.group(1)
|
||||
try:
|
||||
reg_datas = []
|
||||
url = 'https://m.huya.com/' + url.split('/')[-1]
|
||||
headers = {
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, '
|
||||
'like Gecko) Chrome/79.0.3945.88 Mobile Safari/537.36'}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers) as resp:
|
||||
room_page = await resp.text()
|
||||
m = re.search(r"lYyid\":([0-9]+)", room_page, re.MULTILINE)
|
||||
ayyuid = m.group(1)
|
||||
m = re.search(r"lChannelId\":([0-9]+)", room_page, re.MULTILINE)
|
||||
tid = m.group(1)
|
||||
m = re.search(r"lSubChannelId\":([0-9]+)", room_page, re.MULTILINE)
|
||||
sid = m.group(1)
|
||||
|
||||
oos = tarscore.TarsOutputStream()
|
||||
oos.write(tarscore.int64, 0, int(ayyuid))
|
||||
oos.write(tarscore.boolean, 1, True) # Anonymous
|
||||
oos.write(tarscore.string, 2, "") # sGuid
|
||||
oos.write(tarscore.string, 3, "")
|
||||
oos.write(tarscore.int64, 4, int(tid))
|
||||
oos.write(tarscore.int64, 5, int(sid))
|
||||
oos.write(tarscore.int64, 6, 0)
|
||||
oos.write(tarscore.int64, 7, 0)
|
||||
oos = tarscore.TarsOutputStream()
|
||||
oos.write(tarscore.int64, 0, int(ayyuid))
|
||||
oos.write(tarscore.boolean, 1, True) # Anonymous
|
||||
oos.write(tarscore.string, 2, "") # sGuid
|
||||
oos.write(tarscore.string, 3, "")
|
||||
oos.write(tarscore.int64, 4, int(tid))
|
||||
oos.write(tarscore.int64, 5, int(sid))
|
||||
oos.write(tarscore.int64, 6, 0)
|
||||
oos.write(tarscore.int64, 7, 0)
|
||||
|
||||
wscmd = tarscore.TarsOutputStream()
|
||||
wscmd.write(tarscore.int32, 0, 1)
|
||||
wscmd.write(tarscore.bytes, 1, oos.getBuffer())
|
||||
wscmd = tarscore.TarsOutputStream()
|
||||
wscmd.write(tarscore.int32, 0, 1)
|
||||
wscmd.write(tarscore.bytes, 1, oos.getBuffer())
|
||||
|
||||
reg_datas.append(wscmd.getBuffer())
|
||||
return Huya.wss_url, reg_datas
|
||||
reg_datas.append(wscmd.getBuffer())
|
||||
except:
|
||||
print("huya:get_ws_info()执行错误")
|
||||
finally:
|
||||
return Huya.wss_url, reg_datas
|
||||
|
||||
@staticmethod
|
||||
def decode_msg(data):
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import random
|
||||
import re
|
||||
|
||||
|
||||
class Twitch:
|
||||
heartbeat = "PING"
|
||||
heartbeatInterval = 40
|
||||
|
||||
@staticmethod
|
||||
async def get_ws_info(url):
|
||||
reg_datas = []
|
||||
room_id = re.search(r"/([^/?]+)[^/]*$", url).group(1)
|
||||
|
||||
reg_datas.append("CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership")
|
||||
reg_datas.append("PASS SCHMOOPIIE")
|
||||
nick = f"justinfan{int(8e4 * random.random() + 1e3)}"
|
||||
reg_datas.append(f"NICK {nick}")
|
||||
reg_datas.append(f"USER {nick} 8 * :{nick}")
|
||||
reg_datas.append(f"JOIN #{room_id}")
|
||||
|
||||
return "wss://irc-ws.chat.twitch.tv", reg_datas
|
||||
|
||||
@staticmethod
|
||||
def decode_msg(data):
|
||||
msgs = []
|
||||
if data is not None:
|
||||
for d in data.splitlines():
|
||||
msgt = {}
|
||||
try:
|
||||
msgt["content"] = re.search(r"PRIVMSG [^:]+:(.+)", d).group(1)
|
||||
msgt["name"] = re.search(r"display-name=([^;]+);", d).group(1)
|
||||
# if msgt["content"][0] == '@': continue # 丢掉表情符号
|
||||
c = re.search(r"color=#([a-zA-Z0-9]{6});", d).group(1)
|
||||
msgt["color"] = int(c, 16)
|
||||
msgt["msg_type"] = "danmaku"
|
||||
# print(msgt)
|
||||
msgs.append(msgt)
|
||||
except Exception as Error:
|
||||
# print(f"twitch:decode_msg:捕获到异常:{Error}")
|
||||
# traceback.print_exc()
|
||||
pass
|
||||
return msgs
|
||||
+43
-22
@@ -7,6 +7,10 @@ from ..engine.decorators import Plugin
|
||||
from ..engine.download import DownloadBase
|
||||
from ..plugins import logger
|
||||
|
||||
# import http.client
|
||||
# http.client.HTTPConnection._http_vsn = 10
|
||||
# http.client.HTTPConnection._http_vsn_str = 'HTTP/1.0'
|
||||
|
||||
|
||||
@Plugin.download(regexp=r'(?:https?://)?(?:(?:www|m)\.)?douyu\.com')
|
||||
class Douyu(DownloadBase):
|
||||
@@ -16,29 +20,40 @@ class Douyu(DownloadBase):
|
||||
|
||||
def check_stream(self):
|
||||
if len(self.url.split("douyu.com/")) < 2:
|
||||
logger.debug("直播间地址:" + self.url + " 错误")
|
||||
logger.error("斗鱼:" + self.url + ":地址错误")
|
||||
return False
|
||||
html = requests.get(self.url).text
|
||||
vid = match1(html, r'\$ROOM\.room_id\s*=\s*(\d+)',
|
||||
r'room_id\s*=\s*(\d+)',
|
||||
r'"room_id.?":(\d+)',
|
||||
r'data-onlineid=(\d+)')
|
||||
if not vid:
|
||||
logger.debug("直播间" + vid + ":被关闭或不存在")
|
||||
|
||||
try:
|
||||
html = requests.get(self.url).text
|
||||
vid = match1(html, r'\$ROOM\.room_id\s*=\s*(\d+)',
|
||||
r'room_id\s*=\s*(\d+)',
|
||||
r'"room_id.?":(\d+)',
|
||||
r'data-onlineid=(\d+)')
|
||||
if not vid:
|
||||
logger.debug("斗鱼:" + self.url + ":被关闭或不存在")
|
||||
return False
|
||||
except:
|
||||
logger.warning("斗鱼:" + self.url + ":获取vid错误")
|
||||
return False
|
||||
roominfo = requests.get(f"https://www.douyu.com/betard/{vid}").json()['room']
|
||||
if roominfo['show_status'] != 1 or roominfo['videoLoop'] != 0:
|
||||
logger.debug("直播间" + vid + ":未开播或正在放录播")
|
||||
|
||||
try:
|
||||
roominfo = requests.get(f"https://www.douyu.com/betard/{vid}").json()['room']
|
||||
if roominfo['show_status'] != 1 or roominfo['videoLoop'] != 0:
|
||||
logger.debug("斗鱼:" + vid + ":未开播或正在放录播")
|
||||
return False
|
||||
self.room_title = roominfo['room_name']
|
||||
html_h5enc = requests.get(f'https://www.douyu.com/swf_api/homeH5Enc?rids={vid}').json()
|
||||
js_enc = html_h5enc['data']['room' + vid]
|
||||
params = {
|
||||
'cdn': config.get('douyucdn', 'tct-h5'),
|
||||
'iar': 0,
|
||||
'ive': 0,
|
||||
'rate': 0,
|
||||
}
|
||||
except:
|
||||
logger.warning("斗鱼:" + vid + ":获取roominfo错误")
|
||||
return False
|
||||
self.room_title = roominfo['room_name']
|
||||
html_h5enc = requests.get(f'https://www.douyu.com/swf_api/homeH5Enc?rids={vid}').json()
|
||||
js_enc = html_h5enc['data']['room' + vid]
|
||||
params = {
|
||||
'cdn': config.get('douyucdn', 'tct-h5'),
|
||||
'iar': 0,
|
||||
'ive': 0,
|
||||
'rate': 0,
|
||||
}
|
||||
|
||||
try:
|
||||
ub98484234(js_enc, vid, params)
|
||||
except:
|
||||
@@ -62,9 +77,15 @@ class Douyu(DownloadBase):
|
||||
logger.info("结束弹幕录制")
|
||||
|
||||
def get_play_info(vid, headers, params):
|
||||
html_content = requests.post(f'https://www.douyu.com/lapi/live/getH5Play/{vid}', headers=headers, params=params).json()
|
||||
live_data = html_content["data"]
|
||||
try:
|
||||
html_content = requests.post(f'https://www.douyu.com/lapi/live/getH5Play/{vid}', headers=headers, params=params).json()
|
||||
live_data = html_content["data"]
|
||||
except:
|
||||
logger.warning(f"douyu:get_play_info:请求出错:https://www.douyu.com/lapi/live/getH5Play/{vid}")
|
||||
return None
|
||||
# 禁用斗鱼主线路
|
||||
if not type(live_data) == type({}):
|
||||
return live_data
|
||||
if not live_data['rtmp_cdn'].endswith('h5') or 'akm' in live_data['rtmp_cdn']:
|
||||
params['cdn'] = 'tct-h5'
|
||||
return get_play_info(vid, headers, params)
|
||||
|
||||
+25
-20
@@ -22,6 +22,7 @@ class Huya(DownloadBase):
|
||||
try:
|
||||
res = requests.get(self.url, timeout=5, headers=self.fake_headers)
|
||||
except:
|
||||
logger.warning("虎牙:" + self.url + ":获取res错误")
|
||||
return False
|
||||
finally:
|
||||
res.close()
|
||||
@@ -35,26 +36,30 @@ class Huya(DownloadBase):
|
||||
else:
|
||||
huya = None
|
||||
if huya:
|
||||
huyacdn = config.get('huyacdn', 'AL')
|
||||
huyajson1 = json.loads(huya)['data'][0]['gameStreamInfoList']
|
||||
huyajson2 = json.loads(huya)['vMultiStreamInfo']
|
||||
ratio = huyajson2[0]['iBitRate']
|
||||
ibitrate_list = []
|
||||
sdisplayname_list = []
|
||||
for key in huyajson2:
|
||||
ibitrate_list.append(key['iBitRate'])
|
||||
sdisplayname_list.append(key['sDisplayName'])
|
||||
if len(sdisplayname_list) > len(set(sdisplayname_list)):
|
||||
ratio = max(ibitrate_list)
|
||||
huyajson = huyajson1[0]
|
||||
for cdn in huyajson1:
|
||||
if cdn['sCdnType'] == huyacdn:
|
||||
huyajson = cdn
|
||||
absurl = f'{huyajson["sFlvUrl"]}/{huyajson["sStreamName"]}.{huyajson["sFlvUrlSuffix"]}?' \
|
||||
f'{huyajson["sFlvAntiCode"]}'
|
||||
self.raw_stream_url = html.unescape(absurl) + "&ratio=" + str(ratio)
|
||||
self.room_title = json.loads(huya)['data'][0]['gameLiveInfo']['introduction']
|
||||
return True
|
||||
try:
|
||||
huyacdn = config.get('huyacdn', 'AL')
|
||||
huyajson1 = json.loads(huya)['data'][0]['gameStreamInfoList']
|
||||
huyajson2 = json.loads(huya)['vMultiStreamInfo']
|
||||
ratio = huyajson2[0]['iBitRate']
|
||||
ibitrate_list = []
|
||||
sdisplayname_list = []
|
||||
for key in huyajson2:
|
||||
ibitrate_list.append(key['iBitRate'])
|
||||
sdisplayname_list.append(key['sDisplayName'])
|
||||
if len(sdisplayname_list) > len(set(sdisplayname_list)):
|
||||
ratio = max(ibitrate_list)
|
||||
huyajson = huyajson1[0]
|
||||
for cdn in huyajson1:
|
||||
if cdn['sCdnType'] == huyacdn:
|
||||
huyajson = cdn
|
||||
absurl = f'{huyajson["sFlvUrl"]}/{huyajson["sStreamName"]}.{huyajson["sFlvUrlSuffix"]}?' \
|
||||
f'{huyajson["sFlvAntiCode"]}'
|
||||
self.raw_stream_url = html.unescape(absurl) + "&ratio=" + str(ratio)
|
||||
self.room_title = json.loads(huya)['data'][0]['gameLiveInfo']['introduction']
|
||||
return True
|
||||
except:
|
||||
logger.warning("虎牙:" + self.url + ":json解析错误")
|
||||
return False
|
||||
|
||||
async def danmaku_download_start(self, filename):
|
||||
if self.huya_danmaku:
|
||||
|
||||
@@ -10,6 +10,7 @@ from ..engine.decorators import Plugin
|
||||
from ..engine.download import DownloadBase
|
||||
from ..plugins import BatchCheckBase
|
||||
from biliup.config import config
|
||||
from biliup.plugins.Danmaku import DanmakuClient
|
||||
|
||||
VALID_URL_BASE = r'(?:https?://)?(?:(?:www|go|m)\.)?twitch\.tv/(?P<id>[0-9_a-zA-Z]+)'
|
||||
_OPERATION_HASHES = {
|
||||
@@ -86,6 +87,7 @@ class TwitchVideos(DownloadBase):
|
||||
class Twitch(DownloadBase):
|
||||
def __init__(self, fname, url, suffix='flv'):
|
||||
DownloadBase.__init__(self, fname, url, suffix=suffix)
|
||||
self.twitch_danmaku = config.get('twitch_danmaku', False)
|
||||
self.proc = None
|
||||
|
||||
def check_stream(self):
|
||||
@@ -123,7 +125,16 @@ class Twitch(DownloadBase):
|
||||
self.raw_stream_url = info['formats'][-1]['url']
|
||||
return True
|
||||
|
||||
async def danmaku_download_start(self, filename):
|
||||
if self.twitch_danmaku:
|
||||
logger.info("开始弹幕录制")
|
||||
self.danmaku = DanmakuClient(self.url, filename + "." + self.suffix)
|
||||
await self.danmaku.start()
|
||||
|
||||
def close(self):
|
||||
if self.twitch_danmaku:
|
||||
self.danmaku.stop()
|
||||
logger.info("结束弹幕录制")
|
||||
try:
|
||||
if self.proc is not None:
|
||||
self.proc.terminate()
|
||||
|
||||
+7
-1
@@ -44,7 +44,7 @@ check_sourcecode = 15
|
||||
|
||||
|
||||
|
||||
#########各平台分设置#########
|
||||
#########各平台录播设置#########
|
||||
### 使用直播间封面作为投稿封面。此封面优先级低于单个主播指定的自定义封面。(目前仅支持bilibili。直播封面将会保存于cover文件夹下,请自行定期删除)
|
||||
#use_live_cover = true
|
||||
|
||||
@@ -119,6 +119,12 @@ check_sourcecode = 15
|
||||
### 仅下载该日期之前的视频(可与上面的youtube_after_date配合使用,构成指定下载范围区间)
|
||||
#youtube_before_date = '20230501'
|
||||
|
||||
#------Twitch------#
|
||||
# 录制Twitch弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
#twitch_danmaku = false
|
||||
|
||||
|
||||
|
||||
#########录制主播设置#########
|
||||
|
||||
# 最小配置示例
|
||||
|
||||
+7
-1
@@ -42,7 +42,7 @@ pool2_size: 5
|
||||
### 检测源码文件变化间隔,单位:秒,检测源码到变化后,程序会在空闲时自动重启
|
||||
check_sourcecode: 15
|
||||
|
||||
#########各平台分设置#########
|
||||
#########各平台录播设置#########
|
||||
### 使用直播间封面作为投稿封面。此封面优先级低于单个主播指定的自定义封面。(直播封面将会保存于cover文件夹下,请自行定期删除)
|
||||
#use_live_cover: true
|
||||
|
||||
@@ -117,6 +117,12 @@ check_sourcecode: 15
|
||||
### 仅下载该日期之前的视频(可与上面的youtube_after_date配合使用,构成指定下载范围区间)
|
||||
#youtube_before_date: '20230501'
|
||||
|
||||
#------Twitch------#
|
||||
# 录制Twitch弹幕,只有使用FFMPEG作为下载器才支持,默认关闭
|
||||
#twitch_danmaku: false
|
||||
|
||||
|
||||
|
||||
#########录制主播设置#########
|
||||
streamers:
|
||||
### 最小配置示例
|
||||
|
||||
Reference in new issue
Block a user