diff --git a/biliup/common/Daemon.py b/biliup/common/Daemon.py
index 3d79f56..506ad2a 100644
--- a/biliup/common/Daemon.py
+++ b/biliup/common/Daemon.py
@@ -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())
diff --git a/biliup/engine/download.py b/biliup/engine/download.py
index 27a95e1..f0134fd 100644
--- a/biliup/engine/download.py
+++ b/biliup/engine/download.py
@@ -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 {
diff --git a/biliup/engine/upload.py b/biliup/engine/upload.py
index 7c13282..7a6db69 100644
--- a/biliup/engine/upload.py
+++ b/biliup/engine/upload.py
@@ -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)
diff --git a/biliup/plugins/Danmaku/__init__.py b/biliup/plugins/Danmaku/__init__.py
index 632f90d..616dadf 100644
--- a/biliup/plugins/Danmaku/__init__.py
+++ b/biliup/plugins/Danmaku/__init__.py
@@ -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("\n"
- "\n"
- ""
- )
+ "\n"
+ ""
+ )
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)
diff --git a/biliup/plugins/Danmaku/douyu.py b/biliup/plugins/Danmaku/douyu.py
index 82dc511..5c5667b 100644
--- a/biliup/plugins/Danmaku/douyu.py
+++ b/biliup/plugins/Danmaku/douyu.py
@@ -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
diff --git a/biliup/plugins/Danmaku/huya.py b/biliup/plugins/Danmaku/huya.py
index 7ab9625..29c4c3d 100644
--- a/biliup/plugins/Danmaku/huya.py
+++ b/biliup/plugins/Danmaku/huya.py
@@ -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):
diff --git a/biliup/plugins/Danmaku/twitch.py b/biliup/plugins/Danmaku/twitch.py
new file mode 100644
index 0000000..a240c1d
--- /dev/null
+++ b/biliup/plugins/Danmaku/twitch.py
@@ -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
diff --git a/biliup/plugins/douyu.py b/biliup/plugins/douyu.py
index 0be15a8..1647b3f 100644
--- a/biliup/plugins/douyu.py
+++ b/biliup/plugins/douyu.py
@@ -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)
diff --git a/biliup/plugins/huya.py b/biliup/plugins/huya.py
index 1852dd8..90f2331 100644
--- a/biliup/plugins/huya.py
+++ b/biliup/plugins/huya.py
@@ -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:
diff --git a/biliup/plugins/twitch.py b/biliup/plugins/twitch.py
index 431bcd0..009d4c8 100644
--- a/biliup/plugins/twitch.py
+++ b/biliup/plugins/twitch.py
@@ -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[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()
diff --git a/public/config.toml b/public/config.toml
index 03b4285..290694c 100644
--- a/public/config.toml
+++ b/public/config.toml
@@ -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
+
+
+
#########录制主播设置#########
# 最小配置示例
diff --git a/public/config.yaml b/public/config.yaml
index 4faea9b..1872065 100644
--- a/public/config.yaml
+++ b/public/config.yaml
@@ -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:
### 最小配置示例