├── .github ├── dependabot.yml └── workflows │ ├── python-lint.yml │ └── test.yml ├── .gitignore ├── .readthedocs.yaml ├── .ruff.toml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── code_of_conduct.md ├── img │ └── eagle.png ├── index.md ├── overrides │ └── partials │ │ └── nav.html ├── parameter_parsing.md ├── quickstart.md ├── requirements.txt ├── third_party_packages.md └── video_quality_options.md ├── example ├── fetch_douyin_stream.py ├── fetch_rednote_stream.py └── fetch_soop_stream.py ├── mkdocs.yml ├── poetry.toml ├── pyproject.toml ├── requirements.txt ├── setup.py └── streamget ├── __init__.py ├── __version__.py ├── cli.py ├── data.py ├── help.py ├── js ├── crypto-js.min.js ├── haixiu.js ├── liveme.js ├── taobao-sign.js └── x-bogus.js ├── platforms ├── __init__.py ├── acfun │ ├── __init__.py │ └── live_stream.py ├── baidu │ ├── __init__.py │ └── live_stream.py ├── base.py ├── bigo │ ├── __init__.py │ └── live_stream.py ├── bilibili │ ├── __init__.py │ └── live_stream.py ├── blued │ ├── __init__.py │ └── live_stream.py ├── chzzk │ ├── __init__.py │ └── live_stream.py ├── douyin │ ├── __init__.py │ ├── live_stream.py │ └── utils.py ├── douyu │ ├── __init__.py │ └── live_stream.py ├── faceit │ ├── __init__.py │ └── live_stream.py ├── flextv │ ├── __init__.py │ └── live_stream.py ├── haixiu │ ├── __init__.py │ └── live_stream.py ├── huajiao │ ├── __init__.py │ └── live_stream.py ├── huamao │ ├── __init__.py │ └── live_stream.py ├── huya │ ├── __init__.py │ └── live_stream.py ├── inke │ ├── __init__.py │ └── live_stream.py ├── jd │ ├── __init__.py │ └── live_stream.py ├── kuaishou │ ├── __init__.py │ └── live_stream.py ├── kugou │ ├── __init__.py │ └── live_stream.py ├── langlive │ ├── __init__.py │ └── live_stream.py ├── lehai │ ├── __init__.py │ └── live_stream.py ├── liveme │ ├── __init__.py │ └── live_stream.py ├── look │ ├── __init__.py │ └── live_stream.py ├── maoer │ ├── __init__.py │ └── live_stream.py ├── netease │ ├── __init__.py │ └── live_stream.py ├── pandatv │ ├── __init__.py │ └── live_stream.py ├── piaopiao │ ├── __init__.py │ └── live_stream.py ├── popkontv │ ├── __init__.py │ └── live_stream.py ├── qiandurebo │ ├── __init__.py │ └── live_stream.py ├── rednote │ ├── __init__.py │ └── live_stream.py ├── shopee │ ├── __init__.py │ └── live_stream.py ├── showroom │ ├── __init__.py │ └── live_stream.py ├── sixroom │ ├── __init__.py │ └── live_stream.py ├── soop │ ├── __init__.py │ └── live_stream.py ├── taobao │ ├── __init__.py │ └── live_stream.py ├── tiktok │ ├── __init__.py │ └── live_stream.py ├── twitcasting │ ├── __init__.py │ └── live_stream.py ├── twitch │ ├── __init__.py │ └── live_stream.py ├── vvxq │ ├── __init__.py │ └── live_stream.py ├── weibo │ ├── __init__.py │ └── live_stream.py ├── winktv │ ├── __init__.py │ └── live_stream.py ├── yinbo │ ├── __init__.py │ └── live_stream.py ├── yiqilive │ ├── __init__.py │ └── live_stream.py ├── youtube │ ├── __init__.py │ └── live_stream.py ├── yy │ ├── __init__.py │ └── live_stream.py └── zhihu │ ├── __init__.py │ └── live_stream.py ├── requests ├── __init__.py └── async_http.py ├── scripts ├── node_installer.py └── node_setup.py └── utils.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 5 8 | versioning-strategy: "increase" 9 | allow: 10 | - dependency-type: "direct" 11 | - dependency-type: "indirect" -------------------------------------------------------------------------------- /.github/workflows/python-lint.yml: -------------------------------------------------------------------------------- 1 | name: Run Python Lint Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'streamget/**' 9 | - 'requirements.txt' 10 | - '.ruff.toml' 11 | 12 | pull_request: 13 | types: 14 | - opened 15 | - synchronize 16 | paths: 17 | - 'streamget/**' 18 | - 'requirements.txt' 19 | - '.ruff.toml' 20 | 21 | jobs: 22 | lint-python: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | - name: Cache dependencies 29 | uses: actions/cache@v3 30 | with: 31 | path: ~/.cache/pip 32 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 33 | restore-keys: | 34 | ${{ runner.os }}-pip- 35 | 36 | - name: Set up Python 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: "3.10" 40 | 41 | - name: Install dependencies 42 | run: | 43 | python -m pip install --upgrade pip 44 | pip install -r requirements.txt 45 | pip install ruff 46 | 47 | - name: Run ruff lint check 48 | run: ruff check streamget --config .ruff.toml 49 | working-directory: . 50 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.10' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install -r requirements.txt 21 | - name: Run tests 22 | run: | 23 | python -m unittest discover -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # custom 85 | backup_config/ 86 | logs/ 87 | node/ 88 | node-v*.zip 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # poetry 103 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 104 | # This is especially recommended for binary packages to ensure reproducibility, and is more 105 | # commonly ignored for libraries. 106 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 107 | poetry.lock 108 | 109 | # pdm 110 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 111 | #pdm.lock 112 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 113 | # in version control. 114 | # https://pdm.fming.dev/#use-with-ide 115 | .pdm.toml 116 | 117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 118 | __pypackages__/ 119 | 120 | # Celery stuff 121 | celerybeat-schedule 122 | celerybeat.pid 123 | 124 | # SageMath parsed files 125 | *.sage.py 126 | 127 | # Environments 128 | .env 129 | .venv 130 | env/ 131 | venv/ 132 | ENV/ 133 | env.bak/ 134 | venv.bak/ 135 | 136 | # Spyder project settings 137 | .spyderproject 138 | .spyproject 139 | 140 | # Rope project settings 141 | .ropeproject 142 | 143 | # mkdocs documentation 144 | /site 145 | 146 | # mypy 147 | .mypy_cache/ 148 | .dmypy.json 149 | dmypy.json 150 | 151 | # Pyre type checker 152 | .pyre/ 153 | 154 | # pytype static type analyzer 155 | .pytype/ 156 | 157 | # Cython debug symbols 158 | cython_debug/ 159 | 160 | # PyCharm 161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 163 | # and can be added to the global gitignore or merged into this file. For a more nuclear 164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 165 | #.idea/ 166 | 167 | backup_config/ 168 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version, and other tools you might need 8 | build: 9 | os: ubuntu-24.04 10 | tools: 11 | python: "3.13" 12 | 13 | # Build documentation with Mkdocs 14 | mkdocs: 15 | configuration: mkdocs.yml 16 | 17 | # Optionally, but recommended, 18 | # declare the Python requirements required to build your documentation 19 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | python: 21 | install: 22 | - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /.ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 120 2 | 3 | [format] 4 | quote-style = "double" 5 | 6 | 7 | [lint] 8 | preview = false 9 | select = [ 10 | "B", # flake8-bugbear rules 11 | "C4", # flake8-comprehensions 12 | "E", # pycodestyle E rules 13 | "F", # pyflakes rules 14 | "FURB", # refurb rules 15 | "I", # isort rules 16 | "N", # pep8-naming 17 | "PT", # flake8-pytest-style rules 18 | "PLC0208", # iteration-over-set 19 | "PLC0414", # useless-import-alias 20 | "PLE0604", # invalid-all-object 21 | "PLE0605", # invalid-all-format 22 | "PLR0402", # manual-from-import 23 | "PLR1711", # useless-return 24 | "PLR1714", # repeated-equality-comparison 25 | "RUF013", # implicit-optional 26 | "RUF019", # unnecessary-key-check 27 | "RUF100", # unused-noqa 28 | "RUF101", # redirected-noqa 29 | "RUF200", # invalid-pyproject-toml 30 | "RUF022", # unsorted-dunder-all 31 | "S506", # unsafe-yaml-load 32 | "SIM", # flake8-simplify rules 33 | "TRY400", # error-instead-of-exception 34 | "TRY401", # verbose-log-message 35 | "UP", # pyupgrade rules 36 | "W191", # tab-indentation 37 | "W605", # invalid-escape-sequence 38 | ] 39 | 40 | ignore = [ 41 | "E402", # module-import-not-at-top-of-file 42 | "E711", # none-comparison 43 | "E712", # true-false-comparison 44 | "E721", # type-comparison 45 | "E722", # bare-except 46 | "F821", # undefined-name 47 | "F841", # unused-variable 48 | "FURB113", # repeated-append 49 | "FURB152", # math-constant 50 | "UP007", # non-pep604-annotation 51 | "UP032", # f-string 52 | "UP045", # non-pep604-annotation-optional 53 | "B005", # strip-with-multi-characters 54 | "B006", # mutable-argument-default 55 | "B007", # unused-loop-control-variable 56 | "B026", # star-arg-unpacking-after-keyword-arg 57 | "B903", # class-as-data-structure 58 | "B904", # raise-without-from-inside-except 59 | "B905", # zip-without-explicit-strict 60 | "N806", # non-lowercase-variable-in-function 61 | "N815", # mixed-case-variable-in-class-scope 62 | "PT011", # pytest-raises-too-broad 63 | "SIM102", # collapsible-if 64 | "SIM103", # needless-bool 65 | "SIM105", # suppressible-exception 66 | "SIM107", # return-in-try-except-finally 67 | "SIM108", # if-else-block-instead-of-if-exp 68 | "SIM113", # enumerate-for-loop 69 | "SIM117", # multiple-with-statements 70 | "SIM210", # if-expr-with-true-false 71 | ] 72 | 73 | 74 | [lint.per-file-ignores] 75 | "__init__.py" = [ 76 | "F401", # unused-import 77 | "F811", # redefined-while-unused 78 | ] -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | 8 | ## 4.0.5 (22nd May, 2025) 9 | 10 | ### Fixed 11 | 12 | Fix blued and taobao live stream URL fetch. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Hmily 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include streamget/js/*.js -------------------------------------------------------------------------------- /docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | We expect contributors to our projects and online spaces to follow [the Python Software Foundation’s Code of Conduct](https://www.python.org/psf/conduct/). 4 | 5 | The Python community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences great successes and continued growth. When you're working with members of the community, this Code of Conduct will help steer your interactions and keep Python a positive, successful, and growing community. 6 | 7 | ## Our Community 8 | 9 | Members of the Python community are **open, considerate, and respectful**. Behaviours that reinforce these values contribute to a positive environment, and include: 10 | 11 | * **Being open.** Members of the community are open to collaboration, whether it's on PEPs, patches, problems, or otherwise. 12 | * **Focusing on what is best for the community.** We're respectful of the processes set forth in the community, and we work within them. 13 | * **Acknowledging time and effort.** We're respectful of the volunteer efforts that permeate the Python community. We're thoughtful when addressing the efforts of others, keeping in mind that often times the labor was completed simply for the good of the community. 14 | * **Being respectful of differing viewpoints and experiences.** We're receptive to constructive comments and criticism, as the experiences and skill sets of other members contribute to the whole of our efforts. 15 | * **Showing empathy towards other community members.** We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. 16 | * **Being considerate.** Members of the community are considerate of their peers -- other Python users. 17 | * **Being respectful.** We're respectful of others, their positions, their skills, their commitments, and their efforts. 18 | * **Gracefully accepting constructive criticism.** When we disagree, we are courteous in raising our issues. 19 | * **Using welcoming and inclusive language.** We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate and everyone can make a difference. 20 | 21 | ## Our Standards 22 | 23 | Every member of our community has the right to have their identity respected. The Python community is dedicated to providing a positive experience for everyone, regardless of age, gender identity and expression, sexual orientation, disability, physical appearance, body size, ethnicity, nationality, race, or religion (or lack thereof), education, or socio-economic status. 24 | 25 | ## Inappropriate Behavior 26 | 27 | Examples of unacceptable behavior by participants include: 28 | 29 | * Harassment of any participants in any form 30 | * Deliberate intimidation, stalking, or following 31 | * Logging or taking screenshots of online activity for harassment purposes 32 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 33 | * Violent threats or language directed against another person 34 | * Incitement of violence or harassment towards any individual, including encouraging a person to commit suicide or to engage in self-harm 35 | * Creating additional online accounts in order to harass another person or circumvent a ban 36 | * Sexual language and imagery in online communities or in any conference venue, including talks 37 | * Insults, put downs, or jokes that are based upon stereotypes, that are exclusionary, or that hold others up for ridicule 38 | * Excessive swearing 39 | * Unwelcome sexual attention or advances 40 | * Unwelcome physical contact, including simulated physical contact (eg, textual descriptions like "hug" or "backrub") without consent or after a request to stop 41 | * Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others 42 | * Sustained disruption of online community discussions, in-person presentations, or other in-person events 43 | * Continued one-on-one communication after requests to cease 44 | * Other conduct that is inappropriate for a professional audience including people of many different backgrounds 45 | 46 | Community members asked to stop any inappropriate behavior are expected to comply immediately. 47 | 48 | ## Enforcement 49 | 50 | We take Code of Conduct violations seriously, and will act to ensure our spaces are welcoming, inclusive, and professional environments to communicate in. 51 | 52 | you may [make a report to the Python Software Foundation](https://www.python.org/psf/conduct/reporting/). -------------------------------------------------------------------------------- /docs/img/eagle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/streamget/2e44fccbb6cfb99077e728c34bb5c96f618bc0bf/docs/img/eagle.png -------------------------------------------------------------------------------- /docs/overrides/partials/nav.html: -------------------------------------------------------------------------------- 1 | {% import "partials/nav-item.html" as item with context %} 2 | 3 | 4 | {% set class = "md-nav md-nav--primary" %} 5 | {% if "navigation.tabs" in features %} 6 | {% set class = class ~ " md-nav--lifted" %} 7 | {% endif %} 8 | {% if "toc.integrate" in features %} 9 | {% set class = class ~ " md-nav--integrated" %} 10 | {% endif %} 11 | 12 | 13 | 48 | -------------------------------------------------------------------------------- /docs/parameter_parsing.md: -------------------------------------------------------------------------------- 1 | # Parameter Parsing 2 | 3 | ## Overview 4 | 5 | This guide provides a comprehensive introduction to using the `StreamGet` library to fetch and process live streaming data from various platforms. It covers the essential request parameters, methods, and attributes you need to know to get started. 6 | 7 | ## Instantiating an Object 8 | 9 | To begin, you need to instantiate an object for the specific live streaming platform you are interested in. For example, to work with Douyin Live, you would use: 10 | 11 | ```python 12 | >>> from streamget import DouyinLiveStream 13 | >>> live = DouyinLiveStream() 14 | ``` 15 | 16 | You can also pass additional parameters during instantiation, such as cookies or proxy settings, which will be discussed later in this guide. 17 | 18 | ## fetch_web_stream_data Method 19 | 20 | The `fetch_web_stream_data` method is used to fetch data from a live streaming webpage. It has two main parameters: 21 | 22 | - **url**: The URL of the live streaming webpage. 23 | - **process_data**: A boolean parameter that determines whether the fetched data should be processed or returned in its raw form. 24 | 25 | ### Example Usage 26 | 27 | ```python 28 | >>> url = "https://example.com/live" 29 | >>> data = asyncio.run(live.fetch_web_stream_data(url, process_data=True)) 30 | ``` 31 | 32 | ### Parameters 33 | 34 | - **url**: The URL of the live streaming webpage. 35 | - **process_data**: If `True`, the data will be processed and returned in a structured format. If `False`, the raw data from the official API will be returned. 36 | 37 | ### Return Value 38 | 39 | The method returns a dictionary containing the processed data. If `process_data` is `True`, the dictionary might look like this: 40 | 41 | ```json 42 | { 43 | "anchor_name": "xxxxx", 44 | "is_live": True, 45 | "title": "xxxx", 46 | ... 47 | } 48 | ``` 49 | 50 | If `process_data` is `False`, the raw data from the official API will be returned. 51 | 52 | ## fetch_stream_url Method 53 | 54 | The `fetch_stream_url` method is used to fetch the streaming URL from the processed data obtained from `fetch_web_stream_data`. It has two main parameters: 55 | 56 | - **data**: The processed data returned by `fetch_web_stream_data`. 57 | - **video_quality**: The desired video quality of the stream. 58 | 59 | ### Example Usage 60 | 61 | ```python 62 | >>> stream_obj = asyncio.run(live.fetch_stream_url(data, video_quality="OD")) 63 | ``` 64 | 65 | ### Parameters 66 | 67 | - **data**: The processed data returned by `fetch_web_stream_data`. 68 | - **video_quality**: The desired video quality of the stream (e.g., "OD" for original definition). please refer to the [Video Quality Options](https://streamget.readthedocs.io/video_quality_options/). 69 | 70 | ### Return Value 71 | 72 | The method returns a `Stream` object containing the streaming URL and other relevant information. 73 | 74 | ## Stream Object 75 | 76 | The `Stream` object returned by `fetch_stream_url` has the following attributes: 77 | 78 | - **platform**: The name of the live streaming platform. 79 | - **anchor_name**: The name of the live stream anchor. 80 | - **is_live**: A boolean indicating whether the stream is live. 81 | - **title**: The title of the live stream. 82 | - **quality**: The quality of the stream. 83 | - **m3u8_url**: The URL of the stream in M3U8 format. 84 | - **flv_url**: The URL of the stream in FLV format. 85 | - **record_url**: The URL for recording the stream. 86 | - **new_cookies**: Any new cookies obtained during the request. 87 | - **new_token**: Any new token obtained during the request. 88 | - **extra**: Any additional information. 89 | 90 | ### Example Stream Object 91 | 92 | ```python 93 | StreamData( 94 | platform='抖音', 95 | anchor_name='Jack', 96 | is_live=True, 97 | title='Hello everyone ~', 98 | quality='OD', 99 | m3u8_url='https://example.com/xxxx.m3u8', 100 | flv_url='https://example.com/xxxx.flv', 101 | record_url='https://example.com/xxxx.flv', 102 | new_cookies=None, 103 | new_token=None, 104 | extra=None 105 | ) 106 | ``` 107 | 108 | ## Converting to JSON 109 | 110 | The `Stream` object provides a `.to_json()` method that converts the object's attributes to a JSON string. 111 | 112 | ### Example Usage 113 | 114 | ```python 115 | >>> json_str = stream_obj.to_json() 116 | >>> print(json_str) 117 | '{"platform": "抖音", "anchor_name": "Jack", "is_live": True, "flv_url": "https://example.com/xxxx.flv", "m3u8_url": "https://example.com/xxxx.m3u8" ...}' 118 | ``` 119 | 120 | ## Using Cookies 121 | 122 | To include additional cookies in the outgoing request, you can pass a string of cookies during object instantiation: 123 | 124 | ```python 125 | >>> cookies = 'key1=value1;key2=value2;' 126 | >>> live = DouyinLiveStream(cookies=cookies) 127 | ``` 128 | 129 | The final `Stream` object returned will also contain a `new_cookies` attribute if any new cookies are obtained during the request. 130 | 131 | ## Using Proxy 132 | 133 | For platforms that require proxy access, you can pass a proxy URL during object instantiation: 134 | 135 | ```python 136 | >>> proxy = "http://127.0.0.1:7890" 137 | >>> live = DouyinLiveStream(proxy=proxy) 138 | ``` 139 | 140 | 141 | 142 | ## Troubleshooting 143 | 144 | If you encounter issues with parsing URLs, it might be due to network issues or an invalid URL: 145 | 146 | In such cases, please check the following: 147 | 148 | 1. **URL Validity**: Ensure that the URL is correct and accessible. 149 | 2. **Network Connection**: Verify that your network connection is stable. 150 | 3. **Retry the Request**: Sometimes, retrying the request can resolve transient issues. 151 | 152 | If the problem persists, you may need to manually configure your environment or seek further assistance. 153 | 154 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # QuickStart 2 | 3 | First, start by importing StreamGet: 4 | 5 | ```python 6 | >>> import asyncio 7 | >>> import streamget 8 | ``` 9 | 10 | Now, let’s try to get a webpage. 11 | 12 | ```python 13 | >>> live = streamget.DouyinLiveStream() 14 | >>> data = asyncio.run(live.fetch_web_stream_data(url)) 15 | >>> data 16 | {'anchor_name': 'xxxxx', 'is_live': False} 17 | or 18 | {'anchor_name': 'xxxxx', 'is_live': True, 'title': 'xxxx', ...} 19 | ``` 20 | 21 | Similarly, to get the official API raw data: 22 | 23 | ```python 24 | >>> data = asyncio.run(live.fetch_web_stream_data(url, process_data=False)) 25 | >>> data 26 | The official API raw data will be returned 27 | ``` 28 | 29 | Or, you can also directly obtain live streaming source data. 30 | 31 | ```python 32 | >>> import asyncio 33 | >>> from streamget import DouyinLiveStream 34 | >>> live = DouyinLiveStream() 35 | >>> data = asyncio.run(live.fetch_web_stream_data(url, process_data=True)) 36 | 37 | >>> stream_obj = asyncio.run(live.fetch_stream_url(data, "OD")) 38 | StreamData(platform='xxxx', anchor_name='xxxx', is_live=True, m3u8_url="xxx"...) 39 | 40 | ``` 41 | 42 | Note: that `process_data` params must be True in this case. 43 | 44 | ## Install NodeJS 45 | 46 | Some live streaming platforms require Node.js dependencies to obtain data 47 | 48 | You can install Node.js using built-in commands 49 | 50 | ```python 51 | streamget install-node 52 | ``` 53 | 54 | You can also view installation nodejs help info 55 | 56 | ```python 57 | streamget install-node -h 58 | ``` 59 | 60 | If the installation cannot be successful, please manually download and configure the environment variables. 61 | 62 | ## JSON Response Content 63 | 64 | Use `to_json` method will be encoded as JSON. 65 | 66 | ```python 67 | >>> stream_obj = asyncio.run(live.fetch_stream_url(data, "OD")) 68 | StreamData(platform='xxxx', anchor_name='xxxx', is_live=True, m3u8_url="xxx"...) 69 | >>> json_str = stream_obj.to_json() 70 | '{"anchor_name": "xxxx", "is_live": True, "flv_url": "...", "m3u8_url": "..."}' 71 | ``` 72 | 73 | ## Use Cookies 74 | 75 | To include additional cookies in the outgoing request, use the `cookies` keyword argument: 76 | 77 | ```python 78 | >>> cookies = 'key1=value1;key2=value2;' # string 79 | >>> live = streamget.DouyinLiveStream(cookies=cookies) 80 | ``` 81 | 82 | By initiating the request in this way, the final `Stream` object returned also contains a new_comkie attribute 83 | 84 | ## Use Proxy 85 | 86 | For platforms that require proxy access, you can use the proxy parameter when instantiating objects 87 | 88 | ```python 89 | >>> proxy = 'http://127.0.0.1:7890' 90 | >>> live = streamget.DouyinLiveStream(proxy=proxy) 91 | ``` 92 | 93 | ## Supported Platforms 94 | 95 | The currently supported platforms are as follows: 96 | 97 | ```markdown 98 | 抖音 -> DouyinLiveStream 99 | TikTok -> TikTokLiveStream 100 | 快手 -> KwaiLiveStream 101 | 虎牙 -> HuyaLiveStream 102 | 斗鱼 -> DouyuLiveStream 103 | YY -> YYLiveStream 104 | B站 -> BilibiliLiveStream 105 | 小红书 -> RedNoteLiveStream 106 | Bigo -> BigoLiveStream 107 | Blued -> BluedLiveStream 108 | SOOP -> SoopLiveStream 109 | 网易CC -> NeteaseLiveStream 110 | 千度热播 -> QiandureboLiveStream 111 | PandaTV -> PandaLiveStream 112 | 猫耳FM -> MaoerLiveStream 113 | Look -> LookLiveStream 114 | WinkTV -> WinkTVLiveStream 115 | FlexTV -> FlexTVLiveStream 116 | PopkonTV -> PopkonTVLiveStream 117 | TwitCasting -> TwitCastingLiveStream 118 | 百度直播 -> BaiduLiveStream 119 | 微博直播 -> WeiboLiveStream 120 | 酷狗直播 -> KugouLiveStream 121 | TwitchTV -> TwitchLiveStream 122 | LiveMe -> LiveMeLiveStream 123 | 花椒直播 -> HuajiaoLiveStream 124 | ShowRoom -> ShowRoomLiveStream 125 | Acfun -> AcfunLiveStream 126 | 映客直播 -> InkeLiveStream 127 | 音播直播 -> YinboLiveStream 128 | 知乎直播 -> ZhihuLiveStream 129 | CHZZK -> ChzzkLiveStream 130 | 嗨秀直播 -> HaixiuLiveStream 131 | VV星球直播 -> VVXQLiveStream 132 | 17Live -> YiqiLiveStream 133 | 浪Live -> LangLiveStream 134 | 飘飘直播 -> PiaopaioLiveStream 135 | 六间房直播 -> SixRoomLiveStream 136 | 乐嗨直播 -> LehaiLiveStream 137 | 花猫直播 -> HuamaoLiveStream 138 | Shopee -> ShopeeLiveStream 139 | Youtube -> YoutubeLiveStream 140 | 淘宝 -> TaobaoLiveStream 141 | 京东 -> JDLiveStream 142 | Faceit -> FaceitLiveStream 143 | ``` 144 | 145 | You can use the command line to view the supported live streaming platforms: 146 | 147 | ```bash 148 | streamget -h 149 | ``` 150 | 151 | Will return the help info, it inclued return a list of supported platforms. 152 | 153 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Documentation 2 | mkdocs==1.6.1 3 | mkautodoc==0.2.0 4 | mkdocs-material==9.6.14 5 | -------------------------------------------------------------------------------- /docs/third_party_packages.md: -------------------------------------------------------------------------------- 1 | # Third Party Packages 2 | 3 | As StreamGet usage grows, there is an expanding community of developers building tools and libraries that integrate with StreamGet, or depend on StreamGet. Here are some of them. 4 | 5 | ## Plugins 6 | 7 | ### StreamCap 8 | 9 | [GitHub](https://github.com/ihmily/StreamCap/) - [Documentation](https://github.com/ihmily/StreamCap/) 10 | 11 | 一个多平台直播流自动录制工具 · 基于FFmpeg · 支持监控/定时/转码 -------------------------------------------------------------------------------- /docs/video_quality_options.md: -------------------------------------------------------------------------------- 1 | # Video Quality Options 2 | 3 | When using the `fetch_stream_url` method, the `video_quality` parameter allows you to specify the desired video quality of the stream. The supported video qualities and their corresponding values are listed below: 4 | 5 | ## Supported Video Qualities 6 | 7 | | Quality Index | Quality Name | Description | 8 | | ------------- | ------------ | ------------------------------ | 9 | | 0 | OD | Original Definition (最高画质) | 10 | | 1 | UHD | Ultra High Definition (超高清) | 11 | | 2 | HD | High Definition (高清) | 12 | | 3 | SD | Standard Definition (标清) | 13 | | 4 | LD | Low Definition (流畅) | 14 | 15 | ## Usage 16 | 17 | You can specify the video quality using either the quality name (e.g., "OD", "UHD") or the corresponding index (e.g., 0, 1, 2, 3, 4). 18 | 19 | If the `video_quality` parameter is not provided or set to `None`, the default quality will be **OD (Original Definition)**, which is the highest available quality. For example: 20 | 21 | ```python 22 | # Using the default quality (OD) 23 | stream_obj = asyncio.run(live.fetch_stream_url(data)) 24 | 25 | # Explicitly setting the quality to OD 26 | stream_obj = asyncio.run(live.fetch_stream_url(data, video_quality="OD")) 27 | 28 | # Using quality index for OD 29 | stream_obj = asyncio.run(live.fetch_stream_url(data, video_quality=0)) 30 | ``` 31 | 32 | --- 33 | 34 | ### Important Notes 35 | 36 | - **Default Quality**: If the `video_quality` parameter is omitted or set to `None`, the method will automatically select the highest available quality, which is **OD (Original Definition)**. 37 | - **Fallback Behavior**: If the live broadcast room does not support the selected video quality, the method will automatically fall back to the highest available quality in descending order (e.g., if "UHD" is not supported, it will try "HD", then "SD", and so on). 38 | -------------------------------------------------------------------------------- /example/fetch_douyin_stream.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from streamget import DouyinLiveStream 4 | 5 | 6 | async def main(): 7 | # URL of the Douyin live stream 8 | url = "https://live.douyin.com/991562466558" 9 | 10 | # Initialize the DouyinLiveStream object 11 | douyin_stream = DouyinLiveStream() 12 | 13 | try: 14 | # Fetch the live stream data from the provided URL 15 | data = await douyin_stream.fetch_web_stream_data(url) 16 | 17 | # Fetch the stream URL 18 | stream_data = await douyin_stream.fetch_stream_url(data, "OD") 19 | print(stream_data) 20 | 21 | # Convert to json string 22 | json_str = stream_data.to_json() 23 | print(json_str) 24 | except Exception as e: 25 | print(f"An error occurred: {e}") 26 | 27 | 28 | if __name__ == "__main__": 29 | asyncio.run(main()) 30 | -------------------------------------------------------------------------------- /example/fetch_rednote_stream.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from streamget import RedNoteLiveStream 4 | 5 | 6 | async def main(): 7 | # URL of the RedNote live stream 8 | url = "https://www.xiaohongshu.com/user/profile/5ac7123ce8ac2b1d1503e920" 9 | 10 | # Initialize the RedNote object 11 | rednote_stream = RedNoteLiveStream() 12 | 13 | try: 14 | # Fetch the live stream data from the provided URL 15 | data = await rednote_stream.fetch_app_stream_data(url) 16 | 17 | # Fetch the stream URL and convert it to JSON format 18 | stream_data = await rednote_stream.fetch_stream_url(data) 19 | print(stream_data) 20 | 21 | # Convert to json string 22 | json_str = stream_data.to_json() 23 | print(json_str) 24 | except Exception as e: 25 | print(f"An error occurred: {e}") 26 | 27 | 28 | if __name__ == "__main__": 29 | asyncio.run(main()) 30 | -------------------------------------------------------------------------------- /example/fetch_soop_stream.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from streamget import SoopLiveStream 4 | 5 | 6 | async def main(): 7 | # URL of the SOOP Live 8 | url = "https://play.sooplive.co.kr/alswl2208/281343812" 9 | 10 | # Initialize the SoopLiveStream object 11 | soop_stream = SoopLiveStream( 12 | # Use proxy, Ensure that your agent can access it normally 13 | proxy_addr='http://127.0.0.1:7890' 14 | ) 15 | 16 | try: 17 | # Fetch the live stream data from the provided URL 18 | data = await soop_stream.fetch_web_stream_data(url) 19 | 20 | # Fetch the stream data object 21 | stream_data = await soop_stream.fetch_stream_url(data, "OD") 22 | print(stream_data) 23 | 24 | # Convert object to json string 25 | json_str = stream_data.to_json() 26 | print(json_str) 27 | except Exception as e: 28 | print(f"An error occurred: {e}") 29 | 30 | 31 | # Run the asynchronous main function 32 | if __name__ == "__main__": 33 | asyncio.run(main()) 34 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: StreamGet 2 | site_description: A Multi-Platform Live Stream Parser Library for Python. 3 | site_url: https://streamget.readthedocs.io/ 4 | 5 | theme: 6 | name: 'material' 7 | custom_dir: 'docs/overrides' 8 | palette: 9 | - scheme: 'default' 10 | media: '(prefers-color-scheme: light)' 11 | toggle: 12 | icon: 'material/lightbulb' 13 | name: "Switch to dark mode" 14 | - scheme: 'slate' 15 | media: '(prefers-color-scheme: dark)' 16 | primary: 'blue' 17 | toggle: 18 | icon: 'material/lightbulb-outline' 19 | name: 'Switch to light mode' 20 | 21 | repo_name: ihmily/streamget 22 | repo_url: https://github.com/ihmily/streamget/ 23 | edit_uri: "" 24 | 25 | nav: 26 | - Introduction: 'index.md' 27 | - QuickStart: 'quickstart.md' 28 | - Guides: 29 | - Parameter Parsing: 'parameter_parsing.md' 30 | - Video Quality Options: 'video_quality_options.md' 31 | - Community: 32 | - Third Party Packages: 'third_party_packages.md' 33 | - Code of Conduct: 'code_of_conduct.md' 34 | 35 | markdown_extensions: 36 | - admonition 37 | - codehilite: 38 | css_class: highlight 39 | - mkautodoc -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | create = true 4 | prefer-active-python = true -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "streamget" 3 | version = "4.0.5" 4 | description = "A Multi-Platform Live Stream Parser Library." 5 | authors = [{ name = "Hmily" }] 6 | license = {text = "MIT"} 7 | readme = "README.md" 8 | url='https://github.com/ihmily/streamget' 9 | keywords = ["live", "stream"] 10 | requires-python = ">=3.10,<4.0" 11 | 12 | dependencies = [ 13 | "requests>=2.31.0", 14 | "loguru>=0.7.3", 15 | "pycryptodome>=3.20.0", 16 | "distro>=1.9.0", 17 | "tqdm>=4.67.1", 18 | "httpx[http2]>=0.28.1", 19 | "PyExecJS>=1.5.1" 20 | ] 21 | 22 | [project.urls] 23 | Changelog = "https://github.com/ihmily/streamget/blob/main/CHANGELOG.md" 24 | Documentation = "https://streamget.readthedocs.io" 25 | Homepage = "https://github.com/ihmily/streamget" 26 | Source = "https://github.com/ihmily/streamget" 27 | 28 | [build-system] 29 | requires = ["poetry-core>=1.0.0"] 30 | build-backend = "poetry.core.masonry.api" 31 | 32 | [project.scripts] 33 | streamget = "streamget.cli:main" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.31.0 2 | loguru>=0.7.3 3 | pycryptodome>=3.20.0 4 | distro>=1.9.0 5 | tqdm>=4.67.1 6 | httpx[http2]>=0.28.1 7 | PyExecJS>=1.5.1 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open('README.md', encoding='utf-8') as f: 4 | readme = f.read() 5 | 6 | setup( 7 | name='streamget', 8 | version='4.0.5', 9 | author='Hmily', 10 | description='A Multi-Platform Live Stream Parser Library.', 11 | long_description=readme, 12 | long_description_content_type='text/markdown', 13 | url='https://github.com/ihmily/streamget', 14 | project_urls={ 15 | "Documentation": "https://streamget.readthedocs.io", 16 | "Source": "https://github.com/ihmily/streamget" 17 | }, 18 | include_package_data=True, 19 | package_data={ 20 | 'streamget': ['js/*.js'], 21 | }, 22 | packages=find_packages(), 23 | install_requires=[ 24 | 'requests>=2.31.0', 25 | 'loguru>=0.7.3', 26 | 'pycryptodome>=3.20.0', 27 | 'distro>=1.9.0', 28 | 'tqdm>=4.67.1', 29 | 'httpx[http2]>=0.28.1', 30 | 'PyExecJS>=1.5.1', 31 | ], 32 | classifiers=[ 33 | 'Development Status :: 3 - Alpha', 34 | 'Intended Audience :: Developers', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3 :: Only', 37 | 'Programming Language :: Python :: 3.10', 38 | 'Programming Language :: Python :: 3.11', 39 | 'Programming Language :: Python :: 3.12', 40 | 'Programming Language :: Python :: 3.13', 41 | ], 42 | entry_points={ 43 | 'console_scripts': [ 44 | 'streamget=streamget.cli:main' 45 | ] 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /streamget/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from pathlib import Path 4 | 5 | from .__version__ import __description__, __title__, __version__ 6 | 7 | current_file_path = Path(__file__).resolve() 8 | current_dir = current_file_path.parent 9 | JS_SCRIPT_PATH = current_dir / 'js' 10 | 11 | execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] 12 | node_execute_dir = Path(execute_dir) / 'node' 13 | current_env_path = os.environ.get('PATH') 14 | os.environ['PATH'] = str(node_execute_dir) + os.pathsep + current_env_path 15 | 16 | from .data import StreamData 17 | from .platforms.acfun.live_stream import AcfunLiveStream 18 | from .platforms.baidu.live_stream import BaiduLiveStream 19 | from .platforms.bigo.live_stream import BigoLiveStream 20 | from .platforms.bilibili.live_stream import BilibiliLiveStream 21 | from .platforms.blued.live_stream import BluedLiveStream 22 | from .platforms.chzzk.live_stream import ChzzkLiveStream 23 | from .platforms.douyin.live_stream import DouyinLiveStream 24 | from .platforms.douyu.live_stream import DouyuLiveStream 25 | from .platforms.faceit.live_stream import FaceitLiveStream 26 | from .platforms.flextv.live_stream import FlexTVLiveStream 27 | from .platforms.haixiu.live_stream import HaixiuLiveStream 28 | from .platforms.huajiao.live_stream import HuajiaoLiveStream 29 | from .platforms.huamao.live_stream import HuamaoLiveStream 30 | from .platforms.huya.live_stream import HuyaLiveStream 31 | from .platforms.inke.live_stream import InkeLiveStream 32 | from .platforms.jd.live_stream import JDLiveStream 33 | from .platforms.kuaishou.live_stream import KwaiLiveStream 34 | from .platforms.kugou.live_stream import KugouLiveStream 35 | from .platforms.langlive.live_stream import LangLiveStream 36 | from .platforms.lehai.live_stream import LehaiLiveStream 37 | from .platforms.liveme.live_stream import LiveMeLiveStream 38 | from .platforms.look.live_stream import LookLiveStream 39 | from .platforms.maoer.live_stream import MaoerLiveStream 40 | from .platforms.netease.live_stream import NeteaseLiveStream 41 | from .platforms.pandatv.live_stream import PandaLiveStream 42 | from .platforms.piaopiao.live_stream import PiaopaioLiveStream 43 | from .platforms.popkontv.live_stream import PopkonTVLiveStream 44 | from .platforms.qiandurebo.live_stream import QiandureboLiveStream 45 | from .platforms.rednote.live_stream import RedNoteLiveStream 46 | from .platforms.shopee.live_stream import ShopeeLiveStream 47 | from .platforms.showroom.live_stream import ShowRoomLiveStream 48 | from .platforms.sixroom.live_stream import SixRoomLiveStream 49 | from .platforms.soop.live_stream import SoopLiveStream 50 | from .platforms.taobao.live_stream import TaobaoLiveStream 51 | from .platforms.tiktok.live_stream import TikTokLiveStream 52 | from .platforms.twitcasting.live_stream import TwitCastingLiveStream 53 | from .platforms.twitch.live_stream import TwitchLiveStream 54 | from .platforms.vvxq.live_stream import VVXQLiveStream 55 | from .platforms.weibo.live_stream import WeiboLiveStream 56 | from .platforms.winktv.live_stream import WinkTVLiveStream 57 | from .platforms.yinbo.live_stream import YinboLiveStream 58 | from .platforms.yiqilive.live_stream import YiqiLiveStream 59 | from .platforms.youtube.live_stream import YoutubeLiveStream 60 | from .platforms.yy.live_stream import YYLiveStream 61 | from .platforms.zhihu.live_stream import ZhihuLiveStream 62 | 63 | __all__ = [ 64 | "AcfunLiveStream", 65 | "BaiduLiveStream", 66 | "BigoLiveStream", 67 | "BilibiliLiveStream", 68 | "BluedLiveStream", 69 | "ChzzkLiveStream", 70 | "DouyinLiveStream", 71 | "DouyuLiveStream", 72 | "FaceitLiveStream", 73 | "FlexTVLiveStream", 74 | "HaixiuLiveStream", 75 | "HuajiaoLiveStream", 76 | "HuamaoLiveStream", 77 | "HuyaLiveStream", 78 | "InkeLiveStream", 79 | "JDLiveStream", 80 | "KugouLiveStream", 81 | "KwaiLiveStream", 82 | "LangLiveStream", 83 | "LehaiLiveStream", 84 | "LiveMeLiveStream", 85 | "LookLiveStream", 86 | "MaoerLiveStream", 87 | "NeteaseLiveStream", 88 | "PandaLiveStream", 89 | "PiaopaioLiveStream", 90 | "PopkonTVLiveStream", 91 | "QiandureboLiveStream", 92 | "RedNoteLiveStream", 93 | "ShopeeLiveStream", 94 | "ShowRoomLiveStream", 95 | "SixRoomLiveStream", 96 | "SoopLiveStream", 97 | "StreamData", 98 | "TaobaoLiveStream", 99 | "TikTokLiveStream", 100 | "TwitCastingLiveStream", 101 | "TwitchLiveStream", 102 | "VVXQLiveStream", 103 | "WeiboLiveStream", 104 | "WinkTVLiveStream", 105 | "YYLiveStream", 106 | "YinboLiveStream", 107 | "YiqiLiveStream", 108 | "YoutubeLiveStream", 109 | "ZhihuLiveStream", 110 | "__description__", 111 | "__title__", 112 | "__version__", 113 | ] 114 | 115 | __locals = locals() 116 | for __name in __all__: 117 | if not __name.startswith("__"): 118 | __locals[__name].__module__ = "streamget" 119 | 120 | # from .scripts.node_setup import check_node 121 | # check_node() 122 | -------------------------------------------------------------------------------- /streamget/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = "streamget" 2 | __description__ = "A Multi-Platform Live Stream Parser Library." 3 | __version__ = "4.0.5" 4 | __author__ = "Hmily" 5 | __license__ = "MIT" 6 | __copyright__ = "Copyright Hmily" 7 | __url__ = "https://github.com/ihmily/streamget" 8 | -------------------------------------------------------------------------------- /streamget/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import platform 3 | import sys 4 | from pathlib import Path 5 | 6 | from .help import show_welcome_help 7 | from .scripts.node_installer import install_node 8 | 9 | 10 | def main(): 11 | """Main command entry (supports subcommands like streamget install-node)""" 12 | if is_main_help_request(): 13 | show_welcome_help() 14 | sys.exit(0) 15 | 16 | parser = argparse.ArgumentParser( 17 | prog='streamget', 18 | add_help=False 19 | ) 20 | 21 | # Manually add help option 22 | parser.add_argument( 23 | '-h', '--help', 24 | action='help', 25 | default=argparse.SUPPRESS, 26 | help='Show help message' 27 | ) 28 | 29 | subparsers = parser.add_subparsers( 30 | title="Available Commands", 31 | dest="command", 32 | required=True, 33 | help="Node.js runtime installation" 34 | ) 35 | 36 | # install-node subcommand 37 | node_parser = subparsers.add_parser( 38 | 'install-node', 39 | description='Install specific Node.js version to custom path', 40 | formatter_class=argparse.RawTextHelpFormatter, 41 | epilog='''Example usage: 42 | streamget install-node # Install default version 43 | streamget install-node --version 20.0.0 # Specify version 44 | streamget install-node --path ./node_dir # Custom install path 45 | 46 | Version reference: https://nodejs.org/dist/ 47 | ''', 48 | add_help=False 49 | ) 50 | node_parser.add_argument( 51 | '--version', 52 | default='22.14.0', 53 | help='Node.js version (default: %(default)s)' 54 | ) 55 | node_parser.add_argument( 56 | '--path', 57 | type=Path, 58 | default=None, 59 | help='Custom installation path (default: ~/.streamget_node)' 60 | ) 61 | node_parser.add_argument( 62 | '-h', '--help', 63 | action='help', 64 | help='Show this help message' 65 | ) 66 | node_parser.set_defaults(func=handle_install_node) 67 | 68 | args = parser.parse_args() 69 | if hasattr(args, 'func'): 70 | args.func(args) 71 | else: 72 | show_welcome_help() 73 | 74 | 75 | def is_main_help_request(): 76 | """Check if it's a global help request (not subcommand level)""" 77 | # Case 1: streamget -h/--help 78 | if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'): 79 | return True 80 | # Case 2: streamget (no arguments) 81 | if len(sys.argv) == 1: 82 | return True 83 | return False 84 | 85 | 86 | def handle_install_node(args): 87 | """Handle install-node subcommand""" 88 | try: 89 | # Parameter validation 90 | if args.path and not args.path.parent.exists(): 91 | raise ValueError(f"Path {args.path.parent} does not exist") 92 | 93 | if args.path and not args.path.parent.is_dir(): 94 | raise ValueError(f"{args.path.parent} is not a valid directory") 95 | 96 | # Version format validation 97 | if not all(c.isdigit() or c == '.' for c in args.version): 98 | raise ValueError("Invalid version format, should be like 20.0.0") 99 | 100 | # Execute installation 101 | install_node( 102 | version=args.version, 103 | install_path=args.path.expanduser() if args.path else None 104 | ) 105 | 106 | print("✅ Node.js installed successfully!\n") 107 | 108 | except Exception as e: 109 | print(f"❌ Installation failed: {str(e)}\n") 110 | print("💡 Try adding --help for usage\n") 111 | sys.exit(1) 112 | 113 | 114 | def get_bin_path(version, custom_path): 115 | """Generate Node.js binary path""" 116 | system = platform.system().lower() 117 | base_dir = custom_path or Path.home() / ".streamget_node" 118 | return base_dir / f"node-v{version}-{system}-x64" / ("bin" if system != 'windows' else "") 119 | -------------------------------------------------------------------------------- /streamget/data.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class StreamData: 7 | """ 8 | Represents metadata and URLs associated with a streaming session. 9 | 10 | This class encapsulates essential information about a stream, including platform details, 11 | streamer information, stream status, and URLs for different stream formats. 12 | It also provides a method to convert the object to a JSON string. 13 | 14 | Attributes: 15 | platform (str): The streaming platform (e.g., "Twitch", "SOOP", "TikTok"). 16 | anchor_name (str): The name of the streamer. 17 | is_live (bool): Indicates whether the stream is currently live. 18 | title (str): The title of the stream. 19 | quality (str): The quality of the stream (e.g., "OD", "BD", "UHD", "HD"). 20 | m3u8_url (str): The URL for the m3u8 stream format. 21 | flv_url (str): The URL for the FLV stream format. 22 | record_url (str): The URL for recording the stream. 23 | new_cookies (str): Updated cookies required for accessing the stream. 24 | new_token (str): Updated token required for accessing the stream. 25 | extra (dict): Additional metadata or custom fields. 26 | 27 | Example: 28 | >>> stream_data = StreamData(platform="Twitch", anchor_name="StreamerName", is_live=True, title="Live Title") 29 | >>> json_data = stream_data.to_json() 30 | >>> print(json_data) 31 | JSON representation of the stream data 32 | 33 | Note: 34 | The `extra` attribute can be used to store any additional metadata that is not explicitly defined in the class. 35 | """ 36 | platform: str = None 37 | anchor_name: str = None 38 | is_live: bool = None 39 | title: str = None 40 | quality: str = None 41 | m3u8_url: str = None 42 | flv_url: str = None 43 | record_url: str = None 44 | new_cookies: str = None 45 | new_token: str = None 46 | extra: dict = None 47 | 48 | def to_json(self) -> str: 49 | """ 50 | Converts the StreamData object to a JSON string. 51 | 52 | This method serializes the object's attributes into a JSON format, making it easy to 53 | transmit or store the stream data. 54 | 55 | Returns: 56 | str: A JSON representation of the StreamData object. 57 | 58 | Example: 59 | >>> stream_data = StreamData(platform="Twitch", anchor_name="StreamerName") 60 | >>> json_data = stream_data.to_json() 61 | >>> print(json_data) 62 | { 63 | "platform": "Twitch", 64 | "anchor_name": "StreamerName", 65 | ... 66 | } 67 | """ 68 | return json.dumps(self.__dict__, ensure_ascii=False, indent=4) 69 | 70 | 71 | def wrap_stream(data: dict) -> StreamData: 72 | """ 73 | Wraps a dictionary into a StreamData object with default values for missing fields. 74 | 75 | This function ensures that all required and optional fields are present in the input dictionary. 76 | If a field is missing, it is set to `None`. 77 | 78 | Args: 79 | data (dict): A dictionary containing stream data. 80 | 81 | Returns: 82 | StreamData: An instance of StreamData with default values for missing fields. 83 | 84 | Raises: 85 | TypeError: If the input is not a dictionary. 86 | 87 | Example: 88 | >>> json_data = {"platform": "Bilibili", "anchor_name": "StreamerName"} 89 | >>> stream_data = wrap_stream(json_data) 90 | >>> print(stream_data) 91 | StreamData(platform='Bilibili', anchor_name='StreamerName', ...) 92 | 93 | Note: 94 | The function assumes that the input dictionary contains valid data types for each field. 95 | """ 96 | if not isinstance(data, dict): 97 | raise TypeError("Input must be a dictionary") 98 | 99 | required_fields = ["platform", "anchor_name", "is_live", "title", "quality", "m3u8_url", "flv_url", "record_url"] 100 | optional_fields = ["new_cookies", "new_token"] 101 | 102 | for field in required_fields + optional_fields: 103 | if field not in data: 104 | data[field] = None 105 | 106 | return StreamData(**data) 107 | -------------------------------------------------------------------------------- /streamget/help.py: -------------------------------------------------------------------------------- 1 | from . import __all__, __version__ 2 | 3 | 4 | def show_welcome_help(): 5 | """ 6 | Print help information for the streamget package. 7 | """ 8 | print("Welcome to streamget!") 9 | print(f"Version: {__version__}") 10 | print("Description: A Multi-Platform Live Stream Parser Library.") 11 | 12 | print("\nCommand Line Tools:") 13 | print(" streamget [-h] [-help] -- help info") 14 | print(" Install Node.js runtime:") 15 | print(" streamget install-node [--version] [--path] [--help]") 16 | print(" Example:") 17 | print(" streamget install-node") 18 | print(" streamget install-node --version 20.0.0") 19 | print(" streamget install-node --version 20.0.0 --path ./node") 20 | 21 | print("\nSupported Platforms:") 22 | print(__all__[4:]) 23 | print("\nUsage:") 24 | print(" import asyncio") 25 | print(" from streamget import DouyinLiveStream") 26 | print(" stream = DouyinLiveStream()") 27 | print(" data = asyncio.run(stream.fetch_web_stream_data('https://live.douyin.com/xxxxxx'))") 28 | print(" stream_obj = asyncio.run(stream.fetch_stream_url(data))") 29 | print(" stream_json_str = stream_obj.to_json()") 30 | print("\nFor more information, visit the GitHub repository: https://github.com/ihmily/streamget\n") 31 | 32 | 33 | if __name__ == '__main__': 34 | show_welcome_help() 35 | -------------------------------------------------------------------------------- /streamget/js/taobao-sign.js: -------------------------------------------------------------------------------- 1 | function sign(e) { 2 | function t(e, t) { 3 | return e << t | e >>> 32 - t 4 | } 5 | function o(e, t) { 6 | var o, n, r, i, a; 7 | return r = 2147483648 & e, 8 | i = 2147483648 & t, 9 | a = (1073741823 & e) + (1073741823 & t), 10 | (o = 1073741824 & e) & (n = 1073741824 & t) ? 2147483648 ^ a ^ r ^ i : o | n ? 1073741824 & a ? 3221225472 ^ a ^ r ^ i : 1073741824 ^ a ^ r ^ i : a ^ r ^ i 11 | } 12 | function n(e, n, r, i, a, s, u) { 13 | return o(t(e = o(e, o(o(function(e, t, o) { 14 | return e & t | ~e & o 15 | }(n, r, i), a), u)), s), n) 16 | } 17 | function r(e, n, r, i, a, s, u) { 18 | return o(t(e = o(e, o(o(function(e, t, o) { 19 | return e & o | t & ~o 20 | }(n, r, i), a), u)), s), n) 21 | } 22 | function i(e, n, r, i, a, s, u) { 23 | return o(t(e = o(e, o(o(function(e, t, o) { 24 | return e ^ t ^ o 25 | }(n, r, i), a), u)), s), n) 26 | } 27 | function a(e, n, r, i, a, s, u) { 28 | return o(t(e = o(e, o(o(function(e, t, o) { 29 | return t ^ (e | ~o) 30 | }(n, r, i), a), u)), s), n) 31 | } 32 | function s(e) { 33 | var t, o = "", n = ""; 34 | for (t = 0; 3 >= t; t++) 35 | o += (n = "0" + (e >>> 8 * t & 255).toString(16)).substr(n.length - 2, 2); 36 | return o 37 | } 38 | var u, l, d, c, p, f, h, m, y, g; 39 | for (g = function(e) { 40 | for (var t = e.length, o = t + 8, n = 16 * ((o - o % 64) / 64 + 1), r = Array(n - 1), i = 0, a = 0; t > a; ) 41 | i = a % 4 * 8, 42 | r[(a - a % 4) / 4] |= e.charCodeAt(a) << i, 43 | a++; 44 | return i = a % 4 * 8, 45 | r[(a - a % 4) / 4] |= 128 << i, 46 | r[n - 2] = t << 3, 47 | r[n - 1] = t >>> 29, 48 | r 49 | }(e = function(e) { 50 | var t = String.fromCharCode; 51 | e = e.replace(/\r\n/g, "\n"); 52 | for (var o, n = "", r = 0; r < e.length; r++) 53 | 128 > (o = e.charCodeAt(r)) ? n += t(o) : o > 127 && 2048 > o ? (n += t(o >> 6 | 192), 54 | n += t(63 & o | 128)) : (n += t(o >> 12 | 224), 55 | n += t(o >> 6 & 63 | 128), 56 | n += t(63 & o | 128)); 57 | return n 58 | }(e)), 59 | f = 1732584193, 60 | h = 4023233417, 61 | m = 2562383102, 62 | y = 271733878, 63 | u = 0; u < g.length; u += 16) 64 | l = f, 65 | d = h, 66 | c = m, 67 | p = y, 68 | h = a(h = a(h = a(h = a(h = i(h = i(h = i(h = i(h = r(h = r(h = r(h = r(h = n(h = n(h = n(h = n(h, m = n(m, y = n(y, f = n(f, h, m, y, g[u + 0], 7, 3614090360), h, m, g[u + 1], 12, 3905402710), f, h, g[u + 2], 17, 606105819), y, f, g[u + 3], 22, 3250441966), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 4], 7, 4118548399), h, m, g[u + 5], 12, 1200080426), f, h, g[u + 6], 17, 2821735955), y, f, g[u + 7], 22, 4249261313), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 8], 7, 1770035416), h, m, g[u + 9], 12, 2336552879), f, h, g[u + 10], 17, 4294925233), y, f, g[u + 11], 22, 2304563134), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 12], 7, 1804603682), h, m, g[u + 13], 12, 4254626195), f, h, g[u + 14], 17, 2792965006), y, f, g[u + 15], 22, 1236535329), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 1], 5, 4129170786), h, m, g[u + 6], 9, 3225465664), f, h, g[u + 11], 14, 643717713), y, f, g[u + 0], 20, 3921069994), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 5], 5, 3593408605), h, m, g[u + 10], 9, 38016083), f, h, g[u + 15], 14, 3634488961), y, f, g[u + 4], 20, 3889429448), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 9], 5, 568446438), h, m, g[u + 14], 9, 3275163606), f, h, g[u + 3], 14, 4107603335), y, f, g[u + 8], 20, 1163531501), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 13], 5, 2850285829), h, m, g[u + 2], 9, 4243563512), f, h, g[u + 7], 14, 1735328473), y, f, g[u + 12], 20, 2368359562), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 5], 4, 4294588738), h, m, g[u + 8], 11, 2272392833), f, h, g[u + 11], 16, 1839030562), y, f, g[u + 14], 23, 4259657740), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 1], 4, 2763975236), h, m, g[u + 4], 11, 1272893353), f, h, g[u + 7], 16, 4139469664), y, f, g[u + 10], 23, 3200236656), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 13], 4, 681279174), h, m, g[u + 0], 11, 3936430074), f, h, g[u + 3], 16, 3572445317), y, f, g[u + 6], 23, 76029189), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 9], 4, 3654602809), h, m, g[u + 12], 11, 3873151461), f, h, g[u + 15], 16, 530742520), y, f, g[u + 2], 23, 3299628645), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 0], 6, 4096336452), h, m, g[u + 7], 10, 1126891415), f, h, g[u + 14], 15, 2878612391), y, f, g[u + 5], 21, 4237533241), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 12], 6, 1700485571), h, m, g[u + 3], 10, 2399980690), f, h, g[u + 10], 15, 4293915773), y, f, g[u + 1], 21, 2240044497), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 8], 6, 1873313359), h, m, g[u + 15], 10, 4264355552), f, h, g[u + 6], 15, 2734768916), y, f, g[u + 13], 21, 1309151649), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 4], 6, 4149444226), h, m, g[u + 11], 10, 3174756917), f, h, g[u + 2], 15, 718787259), y, f, g[u + 9], 21, 3951481745), 69 | f = o(f, l), 70 | h = o(h, d), 71 | m = o(m, c), 72 | y = o(y, p); 73 | return (s(f) + s(h) + s(m) + s(y)).toLowerCase() 74 | } 75 | 76 | // 正确sign值:05748e8359cd3e6deaab02d15caafc11 77 | // var sg =sign('5655b7041ca049730330701082886efd&1719411639403&12574478&{"componentKey":"wp_pc_shop_basic_info","params":"{\\"memberId\\":\\"b2b-22133374292418351a\\"}"}') 78 | // console.log(sg) -------------------------------------------------------------------------------- /streamget/platforms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/streamget/2e44fccbb6cfb99077e728c34bb5c96f618bc0bf/streamget/platforms/__init__.py -------------------------------------------------------------------------------- /streamget/platforms/acfun/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/streamget/2e44fccbb6cfb99077e728c34bb5c96f618bc0bf/streamget/platforms/acfun/__init__.py -------------------------------------------------------------------------------- /streamget/platforms/acfun/live_stream.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib.parse 3 | from operator import itemgetter 4 | 5 | from ... import utils 6 | from ...data import StreamData, wrap_stream 7 | from ...requests.async_http import async_req 8 | from ..base import BaseLiveStream 9 | 10 | 11 | class AcfunLiveStream(BaseLiveStream): 12 | """ 13 | A class for fetching and processing Acfun live stream information. 14 | """ 15 | def __init__(self, proxy_addr: str | None = None, cookies: str | None = None): 16 | super().__init__(proxy_addr, cookies) 17 | self.pc_headers = self._get_pc_headers() 18 | 19 | async def _get_acfun_sign_params(self) -> tuple: 20 | did = f'web_{utils.generate_random_string(16)}' 21 | headers = { 22 | 'referer': 'https://live.acfun.cn/', 23 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', 24 | 'cookie': f'_did={did};', 25 | } 26 | data = { 27 | 'sid': 'acfun.api.visitor', 28 | } 29 | api = 'https://id.app.acfun.cn/rest/app/visitor/login' 30 | json_str = await async_req(api, data=data, proxy_addr=self.proxy_addr, headers=headers) 31 | json_data = json.loads(json_str) 32 | user_id = json_data["userId"] 33 | visitor_st = json_data["acfun.api.visitor_st"] 34 | return user_id, did, visitor_st 35 | 36 | async def fetch_web_stream_data(self, url: str, process_data: bool = True) -> dict: 37 | """ 38 | Fetches web stream data for a live room. 39 | 40 | Args: 41 | url (str): The room URL. 42 | process_data (bool): Whether to process the data. Defaults to True. 43 | 44 | Returns: 45 | dict: A dictionary containing anchor name, live status, room URL, and title. 46 | """ 47 | author_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] 48 | user_info_api = f'https://live.acfun.cn/rest/pc-direct/user/userInfo?userId={author_id}' 49 | json_str = await async_req(user_info_api, proxy_addr=self.proxy_addr, headers=self.pc_headers) 50 | json_data = json.loads(json_str) 51 | anchor_name = json_data['profile']['name'] 52 | status = 'liveId' in json_data['profile'] 53 | result = {"anchor_name": anchor_name, "is_live": False} 54 | if status: 55 | result["is_live"] = True 56 | user_id, did, visitor_st = await self._get_acfun_sign_params() 57 | params = { 58 | 'subBiz': 'mainApp', 59 | 'kpn': 'ACFUN_APP', 60 | 'kpf': 'PC_WEB', 61 | 'userId': user_id, 62 | 'did': did, 63 | 'acfun.api.visitor_st': visitor_st, 64 | } 65 | 66 | data = { 67 | 'authorId': author_id, 68 | 'pullStreamType': 'FLV', 69 | } 70 | play_api = f'https://api.kuaishouzt.com/rest/zt/live/web/startPlay?{urllib.parse.urlencode(params)}' 71 | json_str = await async_req(play_api, data=data, proxy_addr=self.proxy_addr, headers=self.pc_headers) 72 | json_data = json.loads(json_str) 73 | live_title = json_data['data']['caption'] 74 | videoPlayRes = json_data['data']['videoPlayRes'] 75 | play_url_list = json.loads(videoPlayRes)['liveAdaptiveManifest'][0]['adaptationSet']['representation'] 76 | play_url_list = sorted(play_url_list, key=itemgetter('bitrate'), reverse=True) 77 | result |= {'play_url_list': play_url_list, 'title': live_title} 78 | return result 79 | 80 | async def fetch_stream_url(self, json_data: dict, video_quality: str | int | None = None) -> StreamData: 81 | """ 82 | Fetches the stream URL for a live room and wraps it into a StreamData object. 83 | """ 84 | data = await self.get_stream_url( 85 | json_data, video_quality, url_type='flv', flv_extra_key='url', platform='Acfun') 86 | return wrap_stream(data) 87 | -------------------------------------------------------------------------------- /streamget/platforms/baidu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/streamget/2e44fccbb6cfb99077e728c34bb5c96f618bc0bf/streamget/platforms/baidu/__init__.py -------------------------------------------------------------------------------- /streamget/platforms/baidu/live_stream.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import re 4 | import time 5 | import urllib.parse 6 | 7 | from ...data import StreamData, wrap_stream 8 | from ...requests.async_http import async_req 9 | from ..base import BaseLiveStream 10 | 11 | 12 | class BaiduLiveStream(BaseLiveStream): 13 | """ 14 | A class for fetching and processing Baidu live stream information. 15 | """ 16 | def __init__(self, proxy_addr: str | None = None, cookies: str | None = None): 17 | super().__init__(proxy_addr, cookies) 18 | self.pc_headers = self._get_pc_headers() 19 | 20 | async def fetch_web_stream_data(self, url: str, process_data: bool = True) -> dict: 21 | """ 22 | Fetches web stream data for a live room. 23 | 24 | Args: 25 | url (str): The room URL. 26 | process_data (bool): Whether to process the data. Defaults to True. 27 | 28 | Returns: 29 | dict: A dictionary containing anchor name, live status, room URL, and title. 30 | """ 31 | uid = random.choice([ 32 | 'h5-683e85bdf741bf2492586f7ca39bf465', 33 | 'h5-c7c6dc14064a136be4215b452fab9eea', 34 | 'h5-4581281f80bb8968bd9a9dfba6050d3a' 35 | ]) 36 | room_id = re.search('room_id=(.*?)&', url).group(1) 37 | params = { 38 | 'cmd': '371', 39 | 'action': 'star', 40 | 'service': 'bdbox', 41 | 'osname': 'baiduboxapp', 42 | 'data': '{"data":{"room_id":"' + room_id + '","device_id":"h5-683e85bdf741bf2492586f7ca39bf465",' 43 | '"source_type":0,"osname":"baiduboxapp"},"replay_slice":0,' 44 | '"nid":"","schemeParams":{"src_pre":"pc","src_suf":"other",' 45 | '"bd_vid":"","share_uid":"","share_cuk":"","share_ecid":"",' 46 | '"zb_tag":"","shareTaskInfo":"{\\"room_id\\":\\"9175031377\\"}",' 47 | '"share_from":"","ext_params":"","nid":""}}', 48 | 'ua': '360_740_ANDROID_0', 49 | 'bd_vid': '', 50 | 'uid': uid, 51 | '_': str(int(time.time() * 1000)), 52 | } 53 | app_api = f'https://mbd.baidu.com/searchbox?{urllib.parse.urlencode(params)}' 54 | json_str = await async_req(url=app_api, proxy_addr=self.proxy_addr, headers=self.pc_headers) 55 | json_data = json.loads(json_str) 56 | if not process_data: 57 | return json_data 58 | key = list(json_data['data'].keys())[0] 59 | data = json_data['data'][key] 60 | anchor_name = data['host']['name'] 61 | result = {"anchor_name": anchor_name, "is_live": False} 62 | if data['status'] == "0": 63 | result["is_live"] = True 64 | live_title = data['video']['title'] 65 | play_url_list = data['video']['url_clarity_list'] 66 | url_list = [] 67 | prefix = 'https://hls.liveshow.bdstatic.com/live/' 68 | if play_url_list: 69 | for i in play_url_list: 70 | url_list.append( 71 | prefix + i['urls']['flv'].rsplit('.', maxsplit=1)[0].rsplit('/', maxsplit=1)[1] + '.m3u8') 72 | else: 73 | play_url_list = data['video']['url_list'] 74 | for i in play_url_list: 75 | url_list.append(prefix + i['urls'][0]['hls'].rsplit('?', maxsplit=1)[0].rsplit('/', maxsplit=1)[1]) 76 | 77 | if url_list: 78 | result |= {"is_live": True, "title": live_title, 'play_url_list': url_list} 79 | return result 80 | 81 | async def fetch_stream_url(self, json_data: dict, video_quality: str | int | None = None) -> StreamData: 82 | """ 83 | Fetches the stream URL for a live room and wraps it into a StreamData object. 84 | """ 85 | data = await self.get_stream_url(json_data, video_quality, platform='百度') 86 | return wrap_stream(data) 87 | -------------------------------------------------------------------------------- /streamget/platforms/bigo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/streamget/2e44fccbb6cfb99077e728c34bb5c96f618bc0bf/streamget/platforms/bigo/__init__.py -------------------------------------------------------------------------------- /streamget/platforms/bigo/live_stream.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | 4 | from ...data import StreamData, wrap_stream 5 | from ...requests.async_http import async_req 6 | from ..base import BaseLiveStream 7 | 8 | 9 | class BigoLiveStream(BaseLiveStream): 10 | """ 11 | A class for fetching and processing Bigo live stream information. 12 | """ 13 | def __init__(self, proxy_addr: str | None = None, cookies: str | None = None): 14 | super().__init__(proxy_addr, cookies) 15 | self.pc_headers = self._get_pc_headers() 16 | 17 | def _get_pc_headers(self) -> dict: 18 | return { 19 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', 20 | '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', 21 | 'cookie': self.cookies or '', 22 | } 23 | 24 | async def fetch_web_stream_data(self, url: str, process_data: bool = True) -> dict: 25 | """ 26 | Fetches web stream data for a live room. 27 | 28 | Args: 29 | url (str): The room URL. 30 | process_data (bool): Whether to process the data. Defaults to True. 31 | 32 | Returns: 33 | dict: A dictionary containing anchor name, live status, room URL, and title. 34 | """ 35 | if 'bigo.tv' not in url: 36 | html_str = await async_req(url, proxy_addr=self.proxy_addr, headers=self.pc_headers) 37 | web_url = re.search( 38 | '', 39 | html_str).group(1) 40 | room_id = web_url.split('&h=')[-1] 41 | else: 42 | if '&h=' in url: 43 | room_id = url.split('&h=')[-1] 44 | else: 45 | room_id = re.search('www.bigo.tv/cn/(\\w+)', url).group(1) 46 | 47 | data = {'siteId': room_id} # roomId 48 | url2 = 'https://ta.bigo.tv/official_website/studio/getInternalStudioInfo' 49 | json_str = await async_req(url=url2, proxy_addr=self.proxy_addr, headers=self.pc_headers, data=data) 50 | json_data = json.loads(json_str) 51 | if not process_data: 52 | return json_data 53 | anchor_name = json_data['data']['nick_name'] 54 | live_status = json_data['data']['alive'] 55 | result = {"anchor_name": anchor_name, "is_live": False} 56 | 57 | if live_status == 1: 58 | live_title = json_data['data']['roomTopic'] 59 | m3u8_url = json_data['data']['hls_src'] 60 | result['m3u8_url'] = m3u8_url 61 | result['record_url'] = m3u8_url 62 | result |= {"title": live_title, "is_live": True, "m3u8_url": m3u8_url, 'record_url': m3u8_url} 63 | elif result['anchor_name'] == '': 64 | html_str = await async_req(url=f'https://www.bigo.tv/cn/{room_id}', 65 | proxy_addr=self.proxy_addr, headers=self.pc_headers) 66 | result['anchor_name'] = re.search('