feat: add twitch tv record

This commit is contained in:
ihmily 2024-04-25 19:09:27 +08:00
parent 937361c4b2
commit 564c9b481c
4 changed files with 165 additions and 24 deletions

View File

@ -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
- 新增百度直播录制、微博直播录制

View File

@ -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
View File

@ -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
View File

@ -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=''))