refactor: convert asynchronously func and add readme_pypi

This commit is contained in:
ihmily 2025-02-05 22:44:21 +08:00
parent d055602e81
commit bf7381bf6c
5 changed files with 175 additions and 41 deletions

140
README_PYPI.md Normal file
View File

@ -0,0 +1,140 @@
# StreamGet
`StreamGet` is a lightweight Python package designed to extract live stream URLs from live room links.
------
## Features
- **Extract Live Stream URLs**: Get direct video stream URLs by crawling the live room page and extracting the stream source interface.
- **Platform Support**: Works with popular live streaming platforms (e.g. Twitch, YouTube, Douyin, Xiaohongshu, Huya, Douyu, etc.).
- **No Dependencies**: Pure Python implementation with no external dependencies, ensuring lightweight and fast performance.
------
## Installation
Install `StreamGet` via pip:
```bash
pip install streamget
```
------
## Quick Start
```python
import asyncio
from streamget import spider, stream
async def main():
# Initialize with a live room URL
url = "https://live.douyin.com/745964462470"
# Get the live stream URL asynchronously
room_data = await spider.get_douyin_app_stream_data(url)
print('room_data:', room_data)
stream_data = await stream.get_douyin_stream_url(room_data, '0')
print('stream_data :', stream_data)
stream_url = stream_data.get('record_url')
print("Live Stream URL:", stream_url)
# Run the async function
asyncio.run(main())
```
------
## Supported Platforms
| Platform | Support status | HLS support | FLV support |
| :---------- | :------------- | :---------- | :---------- |
| 抖音 | ✅ | ✅ | ✅ |
| TikTok | ✅ | ✅ | ✅ |
| 快手 | ✅ | ❌ | ✅ |
| 虎牙 | ✅ | ✅ | ✅ |
| 斗鱼 | ✅ | ❌ | ✅ |
| YY | ✅ | ❌ | ✅ |
| B站 | ✅ | ❌ | ✅ |
| 小红书 | ✅ | ✅ | ✅ |
| Bigo | ✅ | ✅ | ❌ |
| Blued | ✅ | ✅ | ❌ |
| SOOP | ✅ | ✅ | ❌ |
| 网易CC | ✅ | ❌ | ✅ |
| 千度热播 | ✅ | ❌ | ✅ |
| PandaTV | ✅ | ✅ | ❌ |
| 猫耳FM | ✅ | ✅ | ✅ |
| Look直播 | ✅ | ✅ | ✅ |
| WinkTV | ✅ | ✅ | ❌ |
| FlexTV | ✅ | ✅ | ❌ |
| PopkonTV | ✅ | ✅ | ❌ |
| TwitCasting | ✅ | ✅ | ❌ |
| 百度直播 | ✅ | ✅ | ✅ |
| 微博直播 | ✅ | ✅ | ✅ |
| 酷狗直播 | ✅ | ❌ | ✅ |
| TwitchTV | ✅ | ✅ | ❌ |
| LiveMe | ✅ | ✅ | ✅ |
| 花椒直播 | ✅ | ❌ | ✅ |
| 流星直播 | ✅ | ❌ | ✅ |
| ShowRoom | ✅ | ✅ | ❌ |
| Acfun | ✅ | ✅ | ✅ |
| 映客直播 | ✅ | ✅ | ✅ |
| 音播直播 | ✅ | ✅ | ✅ |
| 知乎直播 | ✅ | ✅ | ✅ |
| CHZZK | ✅ | ✅ | ❌ |
| 嗨秀直播 | ✅ | ❌ | ✅ |
| vv星球直播 | ✅ | ✅ | ❌ |
| 17Live | ✅ | ❌ | ✅ |
| 浪Live | ✅ | ✅ | ✅ |
| 畅聊直播 | ✅ | ✅ | ✅ |
| 飘飘直播 | ✅ | ✅ | ✅ |
| 六间房直播 | ✅ | ❌ | ✅ |
| 乐嗨直播 | ✅ | ✅ | ✅ |
| 花猫直播 | ✅ | ✅ | ❌ |
| Shopee | ✅ | ❌ | ✅ |
| YouTube | ✅ | ✅ | ❌ |
| 淘宝 | ✅ | ✅ | ✅ |
| 京东 | ✅ | ✅ | ✅ |
| Faceit | ✅ | ✅ | ❌ |
| More ... | | | |
### Notes:
1. **Support Status**: ✅ indicates supported, ❌ indicates unsupported.
------
## Supported Quality
| Chinese clarity | abbreviation | Full Name | Note |
| :-------------- | :----------- | :-------------------- | :------------------------------------------------- |
| 原画 | `OD` | Original Definition | Highest clarity, original picture quality |
| 蓝光 | `BD` | Blue-ray Definition | High definition close to blue light quality |
| 超清 | `UHD` | Ultra High Definition | Ultra high definition |
| 高清 | `HD` | High Definition | High definition, usually referring to 1080p |
| 标清 | `SD` | Standard Definition | Standard clarity, usually referring to 480p |
| 流畅 | `LD` | Low Definition | Low definition, usually referring to 360p or lower |
## Contributing
Contributions are welcome! If you'd like to add support for a new platform or improve the package, please check out the [GitHub repository](https://github.com/ihmily/DouyinLiveRecorder) and submit a pull request.
------
## License
`StreamGet` is released under the MIT License. See the [LICENSE](https://github.com/ihmily/DouyinLiveRecorder/blob/main/LICENSE) file for details.
------
## Documentation
For full documentation and advanced usage, visit the [official documentation](https://streamget.readthedocs.io/).
------

45
main.py
View File

@ -522,7 +522,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
url=record_url,
proxy_addr=proxy_address,
cookies=dy_cookie))
port_info = stream.get_douyin_stream_url(json_data, record_quality)
port_info = asyncio.run(stream.get_douyin_stream_url(json_data, record_quality))
elif record_url.find("https://www.tiktok.com/") > -1:
platform = 'TikTok直播'
@ -532,7 +532,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
url=record_url,
proxy_addr=proxy_address,
cookies=tiktok_cookie))
port_info = stream.get_tiktok_stream_url(json_data, record_quality)
port_info = asyncio.run(stream.get_tiktok_stream_url(json_data, record_quality))
else:
logger.error("错误信息: 网络异常请检查网络是否能正常访问TikTok平台")
@ -543,7 +543,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
url=record_url,
proxy_addr=proxy_address,
cookies=ks_cookie))
port_info = stream.get_kuaishou_stream_url(json_data, record_quality)
port_info = asyncio.run(stream.get_kuaishou_stream_url(json_data, record_quality))
elif record_url.find("https://www.huya.com/") > -1:
platform = '虎牙直播'
@ -553,7 +553,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
url=record_url,
proxy_addr=proxy_address,
cookies=hy_cookie))
port_info = stream.get_huya_stream_url(json_data, record_quality)
port_info = asyncio.run(stream.get_huya_stream_url(json_data, record_quality))
else:
port_info = asyncio.run(spider.get_huya_app_stream_url(
url=record_url,
@ -575,7 +575,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
with semaphore:
json_data = asyncio.run(spider.get_yy_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=yy_cookie))
port_info = stream.get_yy_stream_url(json_data)
port_info = asyncio.run(stream.get_yy_stream_url(json_data))
elif record_url.find("https://live.bilibili.com/") > -1:
platform = 'B站直播'
@ -620,7 +620,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
utils.update_config(
config_file, 'Cookie', 'sooplive_cookie', json_data['new_cookies']
)
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问SOOP平台")
@ -629,7 +629,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
with semaphore:
json_data = asyncio.run(spider.get_netease_stream_data(
url=record_url, cookies=netease_cookie))
port_info = stream.get_netease_stream_url(json_data, record_quality)
port_info = asyncio.run(stream.get_netease_stream_url(json_data, record_quality))
elif record_url.find("qiandurebo.com/") > -1:
platform = '千度热播'
@ -646,7 +646,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
proxy_addr=proxy_address,
cookies=pandatv_cookie
))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问PandaTV直播平台")
@ -664,7 +664,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
url=record_url,
proxy_addr=proxy_address,
cookies=winktv_cookie))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问WinkTV直播平台")
@ -683,7 +683,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
utils.update_config(
config_file, 'Cookie', 'flextv_cookie', json_data['new_cookies']
)
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问FlexTV直播平台")
@ -739,14 +739,15 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
url=record_url,
proxy_addr=proxy_address,
cookies=baidu_cookie))
port_info = stream.get_stream_url(json_data, record_quality)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality))
elif record_url.find("weibo.com/") > -1:
platform = '微博直播'
with semaphore:
json_data = asyncio.run(spider.get_weibo_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=weibo_cookie))
port_info = stream.get_stream_url(json_data, record_quality, hls_extra_key='m3u8_url')
port_info = asyncio.run(stream.get_stream_url(
json_data, record_quality, hls_extra_key='m3u8_url'))
elif record_url.find("kugou.com/") > -1:
platform = '酷狗直播'
@ -763,7 +764,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
proxy_addr=proxy_address,
cookies=twitch_cookie
))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问TwitchTV直播平台")
@ -793,15 +794,15 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
with semaphore:
json_data = asyncio.run(spider.get_showroom_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=showroom_cookie))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
elif record_url.find("live.acfun.cn/") > -1 or record_url.find("m.acfun.cn/") > -1:
platform = 'Acfun'
with semaphore:
json_data = asyncio.run(spider.get_acfun_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=acfun_cookie))
port_info = stream.get_stream_url(
json_data, record_quality, url_type='flv', flv_extra_key='url')
port_info = asyncio.run(stream.get_stream_url(
json_data, record_quality, url_type='flv', flv_extra_key='url'))
elif record_url.find("live.tlclw.com/") > -1:
platform = '畅聊直播'
@ -832,7 +833,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
with semaphore:
json_data = asyncio.run(spider.get_chzzk_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=chzzk_cookie))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
elif record_url.find("www.haixiutv.com/") > -1:
platform = '嗨秀直播'
@ -895,17 +896,17 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
with semaphore:
json_data = asyncio.run(spider.get_youtube_stream_url(
url=record_url, proxy_addr=proxy_address, cookies=youtube_cookie))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
elif record_url.find("tb.cn") > -1:
platform = '淘宝直播'
with semaphore:
json_data = asyncio.run(spider.get_taobao_stream_url(
url=record_url, proxy_addr=proxy_address, cookies=taobao_cookie))
port_info = stream.get_stream_url(
port_info = asyncio.run(stream.get_stream_url(
json_data, record_quality,
url_type='all', hls_extra_key='hlsUrl', flv_extra_key='flvUrl'
)
))
elif record_url.find("3.cn") > -1 or record_url.find("m.jd.com") > -1:
platform = '京东直播'
@ -920,7 +921,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
with semaphore:
json_data = asyncio.run(spider.get_faceit_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=faceit_cookie))
port_info = stream.get_stream_url(json_data, record_quality, spec=True)
port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问faceit直播平台")
@ -1978,4 +1979,4 @@ while True:
t2.start()
first_run = False
time.sleep(3)
time.sleep(3)

View File

@ -4,9 +4,9 @@ version = "4.0.2"
description = "A simple and efficient tool to fetch live stream URLs from various platforms. Supports multiple platforms and easy integration."
authors = [{ name = "Hmily" }]
license = {text = "MIT"}
readme = "README.md"
readme = "README_PYPI.md"
urls = {Repository = "https://github.com/ihmily/DouyinLiveRecorder"}
keywords = ["douyin", "live", "recorder"]
keywords = ["douyin", "youtube", "tiktok", "twitch", "live", "recorder"]
requires-python = ">=3.10,<4.0"
dependencies = [

View File

@ -7,7 +7,7 @@ setup(
author='Hmily',
description='A simple and efficient tool to fetch live stream URLs from various platforms. Supports multiple '
'platforms and easy integration.',
long_description=open('README.md', encoding='utf-8').read(),
long_description=open('README_PYPI.md', encoding='utf-8').read(),
long_description_content_type='text/markdown',
url='https://github.com/ihmily/DouyinLiveRecorder',
packages=find_packages(),

View File

@ -37,7 +37,7 @@ def get_quality_index(quality) -> tuple:
@trace_error_decorator
def get_douyin_stream_url(json_data: dict, video_quality: str) -> dict:
async def get_douyin_stream_url(json_data: dict, video_quality: str) -> dict:
anchor_name = json_data.get('anchor_name')
result = {
@ -45,7 +45,7 @@ def get_douyin_stream_url(json_data: dict, video_quality: str) -> dict:
"is_live": False,
}
status = json_data.get("status", 4) # 直播状态 2 是正在直播、4 是未开播
status = json_data.get("status", 4)
if status == 2:
stream_url = json_data['stream_url']
@ -73,7 +73,7 @@ def get_douyin_stream_url(json_data: dict, video_quality: str) -> dict:
@trace_error_decorator
def get_tiktok_stream_url(json_data: dict, video_quality: str) -> dict:
async def get_tiktok_stream_url(json_data: dict, video_quality: str) -> dict:
if not json_data:
return {"anchor_name": None, "is_live": False}
@ -129,7 +129,7 @@ def get_tiktok_stream_url(json_data: dict, video_quality: str) -> dict:
@trace_error_decorator
def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict:
async def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict:
if json_data['type'] == 1 and not json_data["is_live"]:
return json_data
live_status = json_data['is_live']
@ -153,31 +153,24 @@ def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict:
result['m3u8_url'] = m3u8_url
if 'flv_url_list' in json_data:
# checks if bitrate in flv_url_list
if 'bitrate' in json_data['flv_url_list'][0]:
flv_url_list = json_data['flv_url_list']
flv_url_list = sorted(flv_url_list, key=lambda x: x['bitrate'], reverse=True)
# uses quality_mapping_bitrate to get the index of the quality
quality_str = str(video_quality).upper()
if quality_str.isdigit():
quality_index_bitrate_value = list(quality_mapping_bit.values())[int(quality_str)]
video_quality, quality_index_bitrate_value = list(quality_mapping_bit.items())[int(quality_str)]
else:
quality_index_bitrate_value = quality_mapping_bit.get(quality_str, 99999)
video_quality = quality_str
# find the value below `quality_index_bitrate_value`, or else use the previous one.
quality_index = next(
(i for i, x in enumerate(flv_url_list) if x['bitrate'] <= quality_index_bitrate_value), None)
if quality_index is None:
# latest quality
quality_index = len(flv_url_list) - 1
flv_url = flv_url_list[quality_index]['url']
result['flv_url'] = flv_url
result['record_url'] = flv_url
else:
# TODO: Old version which not working at 20241128, could be removed if not working confirmed,
# please also clean the quality_mapping mapping
flv_url_list = json_data['flv_url_list'][::-1]
while len(flv_url_list) < 5:
flv_url_list.append(flv_url_list[-1])
@ -189,7 +182,7 @@ def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict:
@trace_error_decorator
def get_huya_stream_url(json_data: dict, video_quality: str) -> dict:
async def get_huya_stream_url(json_data: dict, video_quality: str) -> dict:
game_live_info = json_data['data'][0]['gameLiveInfo']
live_title = game_live_info['introduction']
stream_info_list = json_data['data'][0]['gameStreamInfoList']
@ -308,7 +301,7 @@ async def get_douyu_stream_url(json_data: dict, video_quality: str, cookies: str
@trace_error_decorator
def get_yy_stream_url(json_data: dict) -> dict:
async def get_yy_stream_url(json_data: dict) -> dict:
anchor_name = json_data.get('anchor_name', '')
result = {
"anchor_name": anchor_name,
@ -361,7 +354,7 @@ async def get_bilibili_stream_url(json_data: dict, video_quality: str, proxy_add
@trace_error_decorator
def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:
async def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:
if not json_data['is_live']:
return json_data
stream_list = json_data['stream_list']['resolution']
@ -384,8 +377,8 @@ def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:
}
def get_stream_url(json_data: dict, video_quality: str, url_type: str = 'm3u8', spec: bool = False,
hls_extra_key: str | int = None, flv_extra_key: str | int = None) -> dict:
async def get_stream_url(json_data: dict, video_quality: str, url_type: str = 'm3u8', spec: bool = False,
hls_extra_key: str | int = None, flv_extra_key: str | int = None) -> dict:
if not json_data['is_live']:
return json_data