mirror of
https://github.com/ihmily/DouyinLiveRecorder.git
synced 2025-12-26 05:48:32 +08:00
feat: add twitch tv record
This commit is contained in:
parent
937361c4b2
commit
564c9b481c
@ -37,6 +37,7 @@
|
||||
- [x] 百度直播
|
||||
- [x] 微博直播
|
||||
- [x] 酷狗直播
|
||||
- [x] TwitchTV
|
||||
- [ ] 更多平台正在更新中
|
||||
|
||||
</div>
|
||||
@ -161,6 +162,9 @@ https://weibo.com/l/wblive/p/show/1022:2321325026370190442592
|
||||
|
||||
酷狗直播:
|
||||
https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom=
|
||||
|
||||
TwitchTV:
|
||||
https://www.twitch.tv/gamerbee
|
||||
```
|
||||
|
||||
直播间分享地址和网页端长地址都能正常进行录制(抖音尽量用长链接,避免因短链接转换失效导致不能正常录制,而且需要有nodejs环境,否则无法转换)。
|
||||
@ -293,9 +297,12 @@ docker-compose stop
|
||||
|
||||
## ⏳提交日志
|
||||
|
||||
- 20240425
|
||||
- 新增TwitchTV直播录制
|
||||
|
||||
- 20240424
|
||||
- 新增酷狗直播录制、优化PopkonTV直播录制
|
||||
|
||||
|
||||
- 20240423
|
||||
- 新增百度直播录制、微博直播录制
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ ts录制完成后自动增加生成m4a格式 = 否
|
||||
音频录制完成后自动转为mp3格式 = 否
|
||||
追加格式后删除原文件 = 否
|
||||
生成时间文件 = 否
|
||||
使用代理录制的平台(逗号分隔) = tiktok, afreecatv, pandalive, winktv, flextv, popkontv
|
||||
使用代理录制的平台(逗号分隔) = tiktok, afreecatv, pandalive, winktv, flextv, popkontv, twitch
|
||||
额外使用代理录制的平台(逗号分隔) =
|
||||
|
||||
[推送配置]
|
||||
@ -55,6 +55,7 @@ twitcasting_cookie =
|
||||
baidu_cookie =
|
||||
weibo_cookie =
|
||||
kugou_cookie =
|
||||
twitch_cookie =
|
||||
|
||||
[Authorization]
|
||||
popkontv_token =
|
||||
|
||||
46
main.py
46
main.py
@ -4,7 +4,7 @@
|
||||
Author: Hmily
|
||||
GitHub: https://github.com/ihmily
|
||||
Date: 2023-07-17 23:52:05
|
||||
Update: 2024-04-23 22:02:49
|
||||
Update: 2024-04-25 19:07:49
|
||||
Copyright (c) 2023-2024 by Hmily, All Rights Reserved.
|
||||
Function: Record live stream video.
|
||||
"""
|
||||
@ -52,7 +52,8 @@ from spider import (
|
||||
get_twitcasting_stream_url,
|
||||
get_baidu_stream_data,
|
||||
get_weibo_stream_url,
|
||||
get_kugou_stream_url
|
||||
get_kugou_stream_url,
|
||||
get_twitchtv_stream_data,
|
||||
)
|
||||
|
||||
from web_rid import (
|
||||
@ -67,7 +68,7 @@ from msg_push import dingtalk, xizhi, tg_bot
|
||||
|
||||
version = "v3.0.3"
|
||||
platforms = "\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo|blued|网易CC|千度热播|猫耳FM|Look|TwitCasting|百度|微博|酷狗" \
|
||||
"\n海外站点:TikTok|AfreecaTV|PandaTV|WinkTV|FlexTV|PopkonTV"
|
||||
"\n海外站点:TikTok|AfreecaTV|PandaTV|WinkTV|FlexTV|PopkonTV|TwitchTV"
|
||||
|
||||
# --------------------------全局变量-------------------------------------
|
||||
recording = set()
|
||||
@ -727,6 +728,26 @@ def get_baidu_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def get_twitchtv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
|
||||
play_url_list = json_data['play_url_list']
|
||||
quality_list = {'原画': 0, '蓝光': 0, '超清': 1, '高清': 2, '标清': 3}
|
||||
while len(play_url_list) < 4:
|
||||
play_url_list.append(play_url_list[-1])
|
||||
|
||||
selected_quality = quality_list[video_quality]
|
||||
m3u8_url = play_url_list[selected_quality]
|
||||
|
||||
return {
|
||||
"anchor_name": json_data['anchor_name'],
|
||||
"is_live": True,
|
||||
"m3u8_url": json_data['m3u8_url'],
|
||||
"record_url": m3u8_url
|
||||
}
|
||||
|
||||
|
||||
def push_message(content: str):
|
||||
push_pts = []
|
||||
if '微信' in live_status_push:
|
||||
@ -1007,6 +1028,19 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
port_info = get_kugou_stream_url(
|
||||
url=record_url, proxy_addr=proxy_address, cookies=kugou_cookie)
|
||||
|
||||
elif record_url.find("www.twitch.tv/") > -1:
|
||||
platform = 'TwitchTV'
|
||||
with semaphore:
|
||||
if global_proxy or proxy_address:
|
||||
json_data = get_twitchtv_stream_data(
|
||||
url=record_url,
|
||||
proxy_addr=proxy_address,
|
||||
cookies=twitch_cookie
|
||||
)
|
||||
port_info = get_twitchtv_stream_url(json_data, record_quality)
|
||||
else:
|
||||
logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问TwitchTV直播平台")
|
||||
|
||||
else:
|
||||
logger.error(f'{record_url} 未知直播地址')
|
||||
return
|
||||
@ -1704,6 +1738,7 @@ while True:
|
||||
baidu_cookie = read_config_value(config, 'Cookie', 'baidu_cookie', '')
|
||||
weibo_cookie = read_config_value(config, 'Cookie', 'weibo_cookie', '')
|
||||
kugou_cookie = read_config_value(config, 'Cookie', 'kugou_cookie', '')
|
||||
twitch_cookie = read_config_value(config, 'Cookie', 'twitch_cookie', '')
|
||||
|
||||
if len(video_save_type) > 0:
|
||||
if video_save_type.upper().lower() == "FLV".lower():
|
||||
@ -1800,7 +1835,8 @@ while True:
|
||||
'www.pandalive.co.kr',
|
||||
'www.winktv.co.kr',
|
||||
'www.flextv.co.kr',
|
||||
'www.popkontv.com'
|
||||
'www.popkontv.com',
|
||||
'www.twitch.tv',
|
||||
]
|
||||
|
||||
platform_host.extend(overseas_platform_host)
|
||||
@ -1860,4 +1896,4 @@ while True:
|
||||
|
||||
first_run = False
|
||||
|
||||
time.sleep(3)
|
||||
time.sleep(3)
|
||||
|
||||
131
spider.py
131
spider.py
@ -4,20 +4,20 @@
|
||||
Author: Hmily
|
||||
GitHub:https://github.com/ihmily
|
||||
Date: 2023-07-15 23:15:00
|
||||
Update: 2024-04-23 23:42:27
|
||||
Update: 2024-04-25 19:05:11
|
||||
Copyright (c) 2023 by Hmily, All Rights Reserved.
|
||||
Function: Get live stream data.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import random
|
||||
import ssl
|
||||
import time
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
from urllib.request import Request
|
||||
from typing import Union, Dict, Any, Tuple
|
||||
import requests
|
||||
import ssl
|
||||
import re
|
||||
import json
|
||||
import execjs
|
||||
@ -42,7 +42,7 @@ def get_req(
|
||||
proxy_addr: Union[str, None] = None,
|
||||
headers: Union[dict, None] = None,
|
||||
data: Union[dict, bytes, None] = None,
|
||||
json_data: dict = None,
|
||||
json_data: Union[dict, list, None] = None,
|
||||
timeout: int = 20,
|
||||
abroad: bool = False
|
||||
) -> Union[str, Any]:
|
||||
@ -63,7 +63,7 @@ def get_req(
|
||||
else:
|
||||
if data and not isinstance(data, bytes):
|
||||
data = urllib.parse.urlencode(data).encode('utf-8')
|
||||
if json_data and isinstance(json_data, dict):
|
||||
if json_data and isinstance(json_data, (dict, list)):
|
||||
data = json.dumps(json_data).encode('utf-8')
|
||||
|
||||
req = urllib.request.Request(url, data=data, headers=headers)
|
||||
@ -116,8 +116,8 @@ def jsonp_to_json(jsonp_str):
|
||||
return None
|
||||
|
||||
|
||||
def get_play_url_list(m3u8: str, proxy: Union[str, None] = None, header: Union[dict, None] = None) -> list:
|
||||
resp = get_req(url=m3u8, proxy_addr=proxy, headers=header, abroad=True)
|
||||
def get_play_url_list(m3u8: str, proxy: Union[str, None] = None, header: Union[dict, None] = None, abroad: bool = False) -> list:
|
||||
resp = get_req(url=m3u8, proxy_addr=proxy, headers=header, abroad=abroad)
|
||||
play_url_list = []
|
||||
for i in resp.split('\n'):
|
||||
if i.startswith('https://'):
|
||||
@ -874,7 +874,7 @@ def get_pandatv_stream_data(url: str, proxy_addr: Union[str, None] = None, cooki
|
||||
play_url = json_data['PlayList']['hls'][0]['url']
|
||||
result['m3u8_url'] = play_url
|
||||
result['is_live'] = True
|
||||
result['play_url_list'] = get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers)
|
||||
result['play_url_list'] = get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers, abroad=True)
|
||||
return result
|
||||
|
||||
|
||||
@ -981,7 +981,7 @@ def get_winktv_stream_data(url: str, proxy_addr: Union[str, None] = None, cookie
|
||||
raise RuntimeError(json_data['errorData']['code'], json_data['message'])
|
||||
m3u8_url = json_data['PlayList']['hls'][0]['url']
|
||||
result['m3u8_url'] = m3u8_url
|
||||
result['play_url_list'] = get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers)
|
||||
result['play_url_list'] = get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers, abroad=True)
|
||||
return result
|
||||
|
||||
|
||||
@ -1104,7 +1104,7 @@ def get_flextv_stream_data(
|
||||
url=url, proxy_addr=proxy_addr, cookies=cookies, username=username, password=password)
|
||||
if play_url:
|
||||
result['m3u8_url'] = play_url
|
||||
result['play_url_list'] = get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers)
|
||||
result['play_url_list'] = get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers, abroad=True)
|
||||
except Exception as e:
|
||||
print('FlexTV直播间数据获取失败', e)
|
||||
return result
|
||||
@ -1377,16 +1377,12 @@ def get_popkontv_stream_url(
|
||||
update_config('./config/config.ini', 'Authorization', 'popkontv_token', new_access_token)
|
||||
else:
|
||||
raise RuntimeError('popkontv登录失败,请检查账号和密码是否正确')
|
||||
|
||||
json_data = json.loads(json_str)
|
||||
status_msg = json_data["statusMsg"]
|
||||
if json_data['statusCd'] == "L000A":
|
||||
print('获取直播源失败,', status_msg)
|
||||
raise RuntimeError('你是未认证会员。登录popkontv官方网站后,在“我的页面”>“修改我的信息”底部进行手机认证后可用')
|
||||
|
||||
elif json_data['statusCd'] == "L00A1":
|
||||
raise RuntimeError('获取直播源失败,该直播间需要赠送礼物才可观看')
|
||||
|
||||
raise RuntimeError(
|
||||
'你是未认证会员。登录popkontv官方网站后,在“我的页面”>“修改我的信息”底部进行手机认证后可用')
|
||||
elif json_data['statusCd'] == "L0001":
|
||||
cast_start_date_code = int(cast_start_date_code) - 1
|
||||
json_str = fetch_data(headers, partner_code)
|
||||
@ -1394,7 +1390,6 @@ def get_popkontv_stream_url(
|
||||
m3u8_url = json_data['data']['castHlsUrl']
|
||||
result["m3u8_url"] = m3u8_url
|
||||
result["record_url"] = m3u8_url
|
||||
|
||||
elif json_data['statusCd'] == "L0000":
|
||||
m3u8_url = json_data['data']['castHlsUrl']
|
||||
result["m3u8_url"] = m3u8_url
|
||||
@ -1657,6 +1652,106 @@ def get_kugou_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies:
|
||||
return result
|
||||
|
||||
|
||||
def get_twitchtv_room_info(url: str, token: str, proxy_addr: Union[str, None] = None,
|
||||
cookies: Union[str, None] = None):
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
|
||||
'Accept-Language': 'zh-CN',
|
||||
'Referer': 'https://www.twitch.tv/',
|
||||
'Client-Id': 'kimne78kx3ncx6brgo4mv6wki5h1ko',
|
||||
'Client-Integrity': token,
|
||||
'Content-Type': 'text/plain;charset=UTF-8',
|
||||
}
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
uid = url.split('?')[0].rsplit('/', maxsplit=1)[-1]
|
||||
|
||||
data = [
|
||||
{
|
||||
"operationName": "ChannelShell",
|
||||
"variables": {
|
||||
"login": uid
|
||||
},
|
||||
"extensions": {
|
||||
"persistedQuery": {
|
||||
"version": 1,
|
||||
"sha256Hash": "580ab410bcd0c1ad194224957ae2241e5d252b2c5173d8e0cce9d32d5bb14efe"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
json_str = get_req('https://gql.twitch.tv/gql', proxy_addr=proxy_addr, headers=headers, json_data=data, abroad=True)
|
||||
json_data = json.loads(json_str)
|
||||
nickname = json_data[0]['data']['userOrError']['displayName']
|
||||
status = True if json_data[0]['data']['userOrError']['stream'] else False
|
||||
return nickname, status
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_twitchtv_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \
|
||||
Dict[str, Any]:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Accept-Language': 'en-US',
|
||||
'Referer': 'https://www.twitch.tv/',
|
||||
'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko',
|
||||
}
|
||||
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
uid = url.split('?')[0].rsplit('/', maxsplit=1)[-1]
|
||||
|
||||
data = {
|
||||
"operationName": "PlaybackAccessToken_Template",
|
||||
"query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) { value signature authorization { isForbidden forbiddenReasonCode } __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}",
|
||||
"variables": {
|
||||
"isLive": True,
|
||||
"login": uid,
|
||||
"isVod": False,
|
||||
"vodID": "",
|
||||
"playerType": "site"
|
||||
}
|
||||
}
|
||||
|
||||
json_str = get_req('https://gql.twitch.tv/gql', proxy_addr=proxy_addr, headers=headers, json_data=data, abroad=True)
|
||||
json_data = json.loads(json_str)
|
||||
token = json_data['data']['streamPlaybackAccessToken']['value']
|
||||
sign = json_data['data']['streamPlaybackAccessToken']['signature']
|
||||
|
||||
anchor_name, live_status = get_twitchtv_room_info(url=url, token=token, proxy_addr=proxy_addr, cookies=cookies)
|
||||
result = {"anchor_name": anchor_name, "is_live": live_status}
|
||||
if live_status:
|
||||
play_session_id = random.choice(["bdd22331a986c7f1073628f2fc5b19da", "064bc3ff1722b6f53b0b5b8c01e46ca5"])
|
||||
params = {
|
||||
"acmb": "e30=",
|
||||
"allow_sourc": "true",
|
||||
"browser_family": "firefox",
|
||||
"browser_version": "124.0",
|
||||
"cdm": "wv",
|
||||
"fast_bread": "true",
|
||||
"os_name": "Windows",
|
||||
"os_version": "NT%2010.0",
|
||||
"p": "3553732",
|
||||
"platform": "web",
|
||||
"play_session_id": play_session_id,
|
||||
"player_backend": "mediaplayer",
|
||||
"player_version": "1.28.0-rc.1",
|
||||
"playlist_include_framerate": "true",
|
||||
"reassignments_supported": "true",
|
||||
"sig": sign,
|
||||
"token": token,
|
||||
"transcode_mode": "cbr_v1"
|
||||
}
|
||||
access_key = urllib.parse.urlencode(params)
|
||||
m3u8_url = f'https://usher.ttvnw.net/api/channel/hls/{uid}.m3u8?{access_key}'
|
||||
play_url_list = get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers, abroad=True)
|
||||
result['m3u8_url'] = m3u8_url
|
||||
result['play_url_list'] = play_url_list
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 尽量用自己的cookie,以避免默认的不可用导致无法获取数据
|
||||
# 以下示例链接不保证时效性,请自行查看链接是否能正常访问
|
||||
@ -1691,6 +1786,7 @@ if __name__ == '__main__':
|
||||
# room_url = 'https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category' # 百度直播
|
||||
# room_url = 'https://weibo.com/l/wblive/p/show/1022:2321325026370190442592' # 微博直播
|
||||
# room_url = 'https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom=' # 酷狗直播
|
||||
# room_url = 'https://www.twitch.tv/gamerbee' # TwitchTV
|
||||
|
||||
print(get_douyin_stream_data(room_url, proxy_addr=''))
|
||||
# print(get_tiktok_stream_data(room_url, proxy_addr=''))
|
||||
@ -1715,4 +1811,5 @@ if __name__ == '__main__':
|
||||
# print(get_twitcasting_stream_url(room_url, proxy_addr='', username='', password=''))
|
||||
# print(get_baidu_stream_data(room_url, proxy_addr=''))
|
||||
# print(get_weibo_stream_url(room_url, proxy_addr=''))
|
||||
# print(get_kugou_stream_url(room_url, proxy_addr=''))
|
||||
# print(get_kugou_stream_url(room_url, proxy_addr=''))
|
||||
# print(get_twitchtv_stream_data(room_url, proxy_addr=''))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user