mirror of
https://github.com/ihmily/DouyinLiveRecorder.git
synced 2026-03-22 07:28:24 +08:00
Add blued live record and fix yy live record
This commit is contained in:
13
README.md
13
README.md
@@ -5,7 +5,7 @@
|
||||

|
||||

|
||||
|
||||
一款可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制以及直播状态推送
|
||||
一款可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制以及直播状态推送。
|
||||
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
- [x] B站
|
||||
- [x] 小红书
|
||||
- [x] bigo
|
||||
- [x] blued
|
||||
- [ ] 更多平台正在更新中
|
||||
|
||||
</div>
|
||||
@@ -92,8 +93,11 @@ https://live.bilibili.com/320
|
||||
小红书:
|
||||
https://www.xiaohongshu.com/hina/livestream/568980065082002402?appuid=5f3f478a00000000010005b3&apptime=
|
||||
|
||||
bigo:
|
||||
bigo直播:
|
||||
https://www.bigo.tv/cn/716418802
|
||||
|
||||
buled直播:
|
||||
https://app.blued.cn/live?id=Mp6G2R
|
||||
```
|
||||
|
||||
Tiktok目前只支持PC网页端地址(我没下载app),其他平台 app端直播间分享地址和网页端长地址都能正常进行录制(抖音尽量用长链接,避免因短链接转换失效导致不能正常录制)。
|
||||
@@ -102,7 +106,7 @@ Tiktok目前只支持PC网页端地址(我没下载app),其他平台 app
|
||||
|
||||
解析接口:
|
||||
|
||||
该解析接口 ~~仅供演示~~(演示接口暂时停止),并且只包含抖音、快手、虎牙直播的解析,其他平台如有需要请自行添加,源码在这里 [DouyinLiveRecorder/api](https://github.com/ihmily/DouyinLiveRecorder/tree/main/api)
|
||||
该解析接口 ~~仅供演示~~(演示接口暂时停止,后续再开放),并且只包含抖音、快手、虎牙直播的解析,其他平台如有需要请自行添加,源码在这里 [DouyinLiveRecorder/api](https://github.com/ihmily/DouyinLiveRecorder/tree/main/api)
|
||||
|
||||
```HTTP
|
||||
GET https://hmily.vip/api/jx/live/?url=
|
||||
@@ -134,6 +138,9 @@ GET https://hmily.vip/api/jx/live/convert.php?url=https://v.douyin.com/iQLgKSj/
|
||||
|
||||
## ⏳提交日志
|
||||
|
||||
- 20231207
|
||||
- 新增blued直播录制,修复YY直播录制,新增直播结束消息推送
|
||||
|
||||
- 20231206
|
||||
- 新增bigo直播录制
|
||||
|
||||
|
||||
@@ -31,3 +31,4 @@ YY_cookie =
|
||||
B站cookie =
|
||||
小红书cookie =
|
||||
bigo_cookie =
|
||||
blued_cookie =
|
||||
|
||||
62
main.py
62
main.py
@@ -4,7 +4,7 @@
|
||||
Author: Hmily
|
||||
GitHub: https://github.com/ihmily
|
||||
Date: 2023-07-17 23:52:05
|
||||
Update: 2023-12-06 00:31:48
|
||||
Update: 2023-12-07 01:33:19
|
||||
Copyright (c) 2023 by Hmily, All Rights Reserved.
|
||||
Function: Record live stream video.
|
||||
"""
|
||||
@@ -13,6 +13,7 @@ import random
|
||||
import os
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import configparser
|
||||
import subprocess
|
||||
import threading
|
||||
@@ -32,7 +33,8 @@ from spider import (
|
||||
get_yy_stream_data,
|
||||
get_bilibili_stream_data,
|
||||
get_xhs_stream_url,
|
||||
get_bigo_stream_url
|
||||
get_bigo_stream_url,
|
||||
get_blued_stream_url
|
||||
)
|
||||
|
||||
from web_rid import (
|
||||
@@ -46,8 +48,8 @@ from utils import (
|
||||
from msg_push import dingtalk, xizhi
|
||||
|
||||
# 版本号
|
||||
version = "v2.0.4"
|
||||
platforms = "抖音|Tiktok|快手|虎牙|斗鱼|YY|B站|小红书|bigo"
|
||||
version = "v2.0.5"
|
||||
platforms = "抖音|Tiktok|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播"
|
||||
# --------------------------全局变量-------------------------------------
|
||||
recording = set()
|
||||
unrecording = set()
|
||||
@@ -301,7 +303,7 @@ def get_tiktok_stream_url(json_data):
|
||||
stream_data = live_room.get('liveRoom', {}).get('streamData', {}).get('pull_data', {}).get('stream_data', '{}')
|
||||
stream_data = json.loads(stream_data).get('data', {})
|
||||
|
||||
quality_list = list(stream_data.keys()) # ["origin","uhd","sd","ld"]
|
||||
quality_list: list = list(stream_data.keys()) # ["origin","uhd","sd","ld"]
|
||||
while len(quality_list) < 4:
|
||||
quality_list.append(quality_list[-1])
|
||||
video_qualities = {"原画": 0, "蓝光": 0, "超清": 1, "高清": 2, "标清": 3}
|
||||
@@ -592,11 +594,15 @@ def start_record(url_tuple, count_variable=-1):
|
||||
with semaphore:
|
||||
port_info = get_xhs_stream_url(record_url, xhs_cookie)
|
||||
|
||||
elif record_url.find("https://www.bigo.tv/cn/") > -1:
|
||||
elif record_url.find("https://www.bigo.tv/") > -1:
|
||||
with semaphore:
|
||||
port_info = get_bigo_stream_url(record_url, bigo_cookie)
|
||||
|
||||
anchor_name:str= port_info.get("anchor_name", '')
|
||||
elif record_url.find("https://app.blued.cn/") > -1:
|
||||
with semaphore:
|
||||
port_info = get_blued_stream_url(record_url, blued_cookie)
|
||||
|
||||
anchor_name: str = port_info.get("anchor_name", '')
|
||||
|
||||
if not anchor_name:
|
||||
print(f'序号{count_variable} 网址内容获取失败,进行重试中...获取失败的地址是:{url_tuple}')
|
||||
@@ -622,8 +628,9 @@ def start_record(url_tuple, count_variable=-1):
|
||||
else:
|
||||
content = f"{record_name} 正在直播中..."
|
||||
print(content)
|
||||
|
||||
# 推送通知
|
||||
if live_status_push != '':
|
||||
if live_status_push:
|
||||
if '微信' in live_status_push:
|
||||
xizhi(xizhi_api_url, content)
|
||||
if '钉钉' in live_status_push:
|
||||
@@ -633,7 +640,7 @@ def start_record(url_tuple, count_variable=-1):
|
||||
full_path = f'{default_path}/{anchor_name}'
|
||||
if real_url != "":
|
||||
live_list.append(anchor_name)
|
||||
now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))
|
||||
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime(time.time()))
|
||||
try:
|
||||
if len(video_save_path) > 0:
|
||||
if video_save_path[-1] != "/":
|
||||
@@ -669,19 +676,12 @@ def start_record(url_tuple, count_variable=-1):
|
||||
"-fflags", "+discardcorrupt",
|
||||
"-i", real_url,
|
||||
"-bufsize", "5000k",
|
||||
# "-map", "0", # 不同点
|
||||
"-sn", "-dn",
|
||||
# "-bsf:v","h264_mp4toannexb",
|
||||
# "-c","copy", # 直接用copy的话体积特别大.
|
||||
# "-c:v","libx264", # 后期可以用crf来控制大小
|
||||
"-reconnect_delay_max", "30",
|
||||
"-reconnect_streamed", "-reconnect_at_eof",
|
||||
# "-c:v", "copy", # 不同点
|
||||
"-c:a", "copy",
|
||||
"-max_muxing_queue_size", "64",
|
||||
"-correct_ts_overflow", "1",
|
||||
# "-f", "matroska", # 不同点
|
||||
# "{path}".format(path=file), # 不同点
|
||||
]
|
||||
|
||||
# 添加代理参数
|
||||
@@ -723,7 +723,6 @@ def start_record(url_tuple, count_variable=-1):
|
||||
print(f"\r{time.strftime('%Y-%m-%d %H:%M:%S')} {anchor_name} 未开播")
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
|
||||
elif video_save_type == "MKV":
|
||||
filename = anchor_name + '_' + now + ".mkv"
|
||||
print(f'{rec_info}/{filename}')
|
||||
@@ -754,7 +753,6 @@ def start_record(url_tuple, count_variable=-1):
|
||||
print(f"{e.output} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
|
||||
elif video_save_type == "MP4":
|
||||
|
||||
filename = anchor_name + '_' + now + ".mp4"
|
||||
@@ -791,7 +789,6 @@ def start_record(url_tuple, count_variable=-1):
|
||||
print(f"{e.output} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
|
||||
elif video_save_type == "MKV音频":
|
||||
filename = anchor_name + '_' + now + ".mkv"
|
||||
print(f'{rec_info}/{filename}')
|
||||
@@ -846,7 +843,7 @@ def start_record(url_tuple, count_variable=-1):
|
||||
|
||||
if Splitvideobysize: # 这里默认是启用/不启用视频分割功能
|
||||
while True:
|
||||
now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))
|
||||
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime(time.time()))
|
||||
filename = anchor_name + '_' + now + ".ts"
|
||||
print(f'{rec_info}/{filename}')
|
||||
file = full_path + '/' + filename
|
||||
@@ -874,7 +871,7 @@ def start_record(url_tuple, count_variable=-1):
|
||||
|
||||
record_finished = True # 这里表示正常录制成功一次
|
||||
record_finished_2 = True
|
||||
count_time = time.time() # 这个记录当前时间, 用于后面 1分钟内快速2秒循环 这个值不能放到后面
|
||||
count_time = time.time()
|
||||
|
||||
if tsconvert_to_mp4:
|
||||
threading.Thread(target=converts_mp4, args=(file,)).start()
|
||||
@@ -887,7 +884,6 @@ def start_record(url_tuple, count_variable=-1):
|
||||
f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
break
|
||||
|
||||
|
||||
else:
|
||||
filename = anchor_name + '_' + now + ".ts"
|
||||
|
||||
@@ -910,8 +906,7 @@ def start_record(url_tuple, count_variable=-1):
|
||||
]
|
||||
|
||||
ffmpeg_command.extend(command)
|
||||
_output = subprocess.check_output(ffmpeg_command,
|
||||
stderr=subprocess.STDOUT)
|
||||
_output = subprocess.check_output(ffmpeg_command,stderr=subprocess.STDOUT)
|
||||
record_finished = True
|
||||
record_finished_2 = True
|
||||
count_time = time.time()
|
||||
@@ -921,13 +916,12 @@ def start_record(url_tuple, count_variable=-1):
|
||||
if tsconvert_to_m4a:
|
||||
threading.Thread(target=converts_m4a, args=(file,)).start()
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
# logging.warning(str(e.output))
|
||||
print(f"{e.output} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
if record_finished_2 == True:
|
||||
if record_finished_2:
|
||||
if record_name in recording:
|
||||
recording.remove(record_name)
|
||||
if anchor_name in unrecording:
|
||||
@@ -935,6 +929,14 @@ def start_record(url_tuple, count_variable=-1):
|
||||
print(f"\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制完成\n")
|
||||
record_finished_2 = False
|
||||
|
||||
# 推送通知
|
||||
content = f"{record_name} 直播已结束"
|
||||
if live_status_push:
|
||||
if '微信' in live_status_push:
|
||||
xizhi(xizhi_api_url, content)
|
||||
if '钉钉' in live_status_push:
|
||||
dingtalk(dingtalk_api_url, content, dingtalk_phone_num)
|
||||
|
||||
except Exception as e:
|
||||
print(f"错误信息:{e}\r\n读取的地址为: {record_url} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.warning("错误信息: " + str(e) + " 发生错误的行数: " + str(e.__traceback__.tb_lineno))
|
||||
@@ -952,7 +954,7 @@ def start_record(url_tuple, count_variable=-1):
|
||||
|
||||
# 这里是.如果录制结束后,循环时间会暂时变成30s后检测一遍. 这样一定程度上防止主播卡顿造成少录
|
||||
# 当30秒过后检测一遍后. 会回归正常设置的循环秒数
|
||||
if record_finished == True:
|
||||
if record_finished:
|
||||
count_time_end = time.time() - count_time
|
||||
if count_time_end < 60:
|
||||
x = 30
|
||||
@@ -985,7 +987,7 @@ def backup_file(file_path, backup_dir):
|
||||
if not os.path.exists(backup_dir):
|
||||
os.makedirs(backup_dir)
|
||||
# 拼接备份文件名,年-月-日-时-分-秒
|
||||
timestamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
|
||||
timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
||||
backup_file_name = os.path.basename(file_path) + '_' + timestamp
|
||||
# 拷贝文件到备份目录
|
||||
backup_file_path = os.path.join(backup_dir, backup_file_name)
|
||||
@@ -1125,6 +1127,7 @@ while True:
|
||||
bili_cookie = read_config_value(config, 'Cookie', 'B站cookie', '')
|
||||
xhs_cookie = read_config_value(config, 'Cookie', '小红书cookie', '')
|
||||
bigo_cookie = read_config_value(config, 'Cookie', 'bigo_cookie', '')
|
||||
blued_cookie = read_config_value(config, 'Cookie', 'blued_cookie', '')
|
||||
|
||||
if len(video_save_type) > 0:
|
||||
if video_save_type.upper().lower() == "FLV".lower():
|
||||
@@ -1198,7 +1201,8 @@ while True:
|
||||
'www.yy.com',
|
||||
'live.bilibili.com',
|
||||
'www.xiaohongshu.com',
|
||||
'www.bigo.tv'
|
||||
'www.bigo.tv',
|
||||
'app.blued.cn'
|
||||
]
|
||||
if url_host in host_list:
|
||||
new_line = (url, split_line[1])
|
||||
|
||||
49
spider.py
49
spider.py
@@ -4,7 +4,7 @@
|
||||
Author: Hmily
|
||||
GitHub:https://github.com/ihmily
|
||||
Date: 2023-07-15 23:15:00
|
||||
Update: 2023-12-06 01:09:56
|
||||
Update: 2023-12-07 23:35:47
|
||||
Copyright (c) 2023 by Hmily, All Rights Reserved.
|
||||
Function: Get live stream data.
|
||||
"""
|
||||
@@ -288,7 +288,6 @@ def get_douyu_stream_data(rid: str, rate: str = '-1', cookies: Union[str, None]
|
||||
|
||||
@trace_error_decorator
|
||||
def get_yy_stream_data(url: str, cookies: Union[str, None] = None) -> Dict[str, Any]:
|
||||
cid = re.search('yy.com/(.*?)/', url).group(1)
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
||||
@@ -302,12 +301,12 @@ def get_yy_stream_data(url: str, cookies: Union[str, None] = None) -> Dict[str,
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
response = opener.open(req, timeout=15)
|
||||
html_str = response.read().decode('utf-8')
|
||||
live_info = re.search('<div class="w-liveplayer-head__content">(.*)<i class="follow-i">', html_str, re.S).group(1)
|
||||
anchor_name = re.search('<h2>(.*?)</h2>', live_info).group(1)
|
||||
anchor_name = re.search('nick: "(.*?)",\n\s+logo', html_str).group(1)
|
||||
cid = re.search('sid : "(.*?)",\n\s+ssid', html_str, re.S).group(1)
|
||||
|
||||
data = '{"head":{"seq":1691766627723,"appidstr":"0","bidstr":"121","cidstr":"' + cid + '","sidstr":"' + cid + '","uid64":0,"client_type":108,"client_ver":"5.14.13","stream_sys_ver":1,"app":"yylive_web","playersdk_ver":"5.14.13","thundersdk_ver":"0","streamsdk_ver":"5.14.13"},"client_attribute":{"client":"web","model":"","cpu":"","graphics_card":"","os":"chrome","osversion":"0","vsdk_version":"","app_identify":"","app_version":"","business":"","width":"1536","height":"864","scale":"","client_type":8,"h265":0},"avp_parameter":{"version":1,"client_type":8,"service_type":0,"imsi":0,"send_time":1691766627,"line_seq":-1,"gear":4,"ssl":1,"stream_format":0}}'
|
||||
data = '{"head":{"seq":1701869217590,"appidstr":"0","bidstr":"121","cidstr":"' + cid + '","sidstr":"' + cid + '","uid64":0,"client_type":108,"client_ver":"5.17.0","stream_sys_ver":1,"app":"yylive_web","playersdk_ver":"5.17.0","thundersdk_ver":"0","streamsdk_ver":"5.17.0"},"client_attribute":{"client":"web","model":"web0","cpu":"","graphics_card":"","os":"chrome","osversion":"0","vsdk_version":"","app_identify":"","app_version":"","business":"","width":"1920","height":"1080","scale":"","client_type":8,"h265":0},"avp_parameter":{"version":1,"client_type":8,"service_type":0,"imsi":0,"send_time":1701869217,"line_seq":-1,"gear":4,"ssl":1,"stream_format":0}}'
|
||||
data_bytes = data.encode('utf-8')
|
||||
url2 = f'https://stream-manager.yy.com/v3/channel/streams?uid=0&cid={cid}&sid={cid}&appid=0&sequence=1691766112069&encode=json'
|
||||
url2 = f'https://stream-manager.yy.com/v3/channel/streams?uid=0&cid={cid}&sid={cid}&appid=0&sequence=1701869217590&encode=json'
|
||||
req = urllib.request.Request(url2, data=data_bytes, headers=headers)
|
||||
response = opener.open(req, timeout=15)
|
||||
json_str = response.read().decode('utf-8')
|
||||
@@ -383,7 +382,7 @@ def get_bigo_stream_url(url: str, cookies: Union[str, None] = None) -> Dict[str,
|
||||
data = {'siteId': room_id} # roomId
|
||||
url = 'https://ta.bigo.tv/official_website/studio/getInternalStudioInfo'
|
||||
data = urllib.parse.urlencode(data).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=data,headers=headers)
|
||||
req = urllib.request.Request(url, data=data, headers=headers)
|
||||
response = opener.open(req, timeout=15)
|
||||
json_str = response.read().decode('utf-8')
|
||||
json_data = json.loads(json_str)
|
||||
@@ -402,8 +401,40 @@ def get_bigo_stream_url(url: str, cookies: Union[str, None] = None) -> Dict[str,
|
||||
return result
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_blued_stream_url(url: str, cookies: Union[str, None] = None) -> Dict[str, Any]:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
|
||||
}
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
response = opener.open(req, timeout=15)
|
||||
html_str = response.read().decode('utf-8')
|
||||
json_str = re.search('decodeURIComponent\(\"(.*?)\"\)\)\,window\.Promise', html_str, re.S).group(1)
|
||||
json_str = urllib.parse.unquote(json_str)
|
||||
json_data = json.loads(json_str)
|
||||
anchor_name = json_data['userInfo']['name']
|
||||
live_status = json_data['userInfo']['onLive']
|
||||
result = {
|
||||
"anchor_name": anchor_name,
|
||||
"is_live": False,
|
||||
}
|
||||
|
||||
if live_status:
|
||||
m3u8_url = "http:" + json_data['liveInfo']['liveUrl']
|
||||
result['m3u8_url'] = m3u8_url
|
||||
result['is_live'] = True
|
||||
result['record_url'] = m3u8_url
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 尽量用自己的cookie,以避免默认的不可用导致无法获取数据
|
||||
# 以下示例链接不保证时效性,请自行查看链接是否能正常访问
|
||||
url = "https://live.douyin.com/745964462470" # 抖音直播
|
||||
# url = "https://www.tiktok.com/@pearlgaga88/live" # Tiktok直播
|
||||
# url = "https://live.kuaishou.com/u/yall1102" # 快手直播
|
||||
@@ -415,9 +446,10 @@ if __name__ == '__main__':
|
||||
# 小红书直播
|
||||
# url = 'https://www.xiaohongshu.com/hina/livestream/568980065082002402?appuid=5f3f478a00000000010005b3&apptime='
|
||||
# url = 'https://www.bigo.tv/cn/716418802' # bigo直播
|
||||
# url = 'https://app.blued.cn/live?id=Mp6G2R' # blued直播
|
||||
|
||||
print(get_douyin_stream_data(url))
|
||||
# print(get_tiktok_stream_data(url,'http://127.0.0.1:7890'))
|
||||
# print(get_tiktok_stream_data(url,proxy_addr=''))
|
||||
# print(get_kuaishou_stream_data(url))
|
||||
# print(get_huya_stream_data(url))
|
||||
# print(get_douyu_info_data(url))
|
||||
@@ -426,3 +458,4 @@ if __name__ == '__main__':
|
||||
# print(get_bilibili_stream_data(url))
|
||||
# print(get_xhs_stream_url(url))
|
||||
# print(get_bigo_stream_url(url))
|
||||
# print(get_blued_stream_url(url))
|
||||
|
||||
Reference in New Issue
Block a user