├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── issue.yml ├── release-body.md └── workflows │ ├── main.yml │ └── preview-build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── streamlink.appdata.xml ├── streamlink.desktop └── streamlink.svg ├── build-docker.sh ├── build.sh ├── config.yml ├── deploy.sh ├── get-dependencies.sh └── requirements.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 128 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ bastimeyer ] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: "Main Streamlink repository" 4 | about: "Please go here for any issues or questions related to Streamlink itself." 5 | url: https://github.com/streamlink/streamlink 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.yml: -------------------------------------------------------------------------------- 1 | name: Issue 2 | description: An issue with one of Streamlink's AppImages 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ## Thanks for reporting an issue with Streamlink's AppImages! 8 | 9 | **Issues and questions related to Streamlink itself DO NOT BELONG here and will be closed immediately.** 10 | - type: dropdown 11 | attributes: 12 | label: AppImage architecture 13 | options: 14 | - aarch64 15 | - i686 16 | - x86_64 17 | validations: 18 | required: true 19 | - type: input 20 | attributes: 21 | label: AppImage version 22 | description: | 23 | The exact file name of the AppImage. 24 | - type: textarea 25 | attributes: 26 | label: Description 27 | description: | 28 | Explain the issue as thoroughly as you can and list the steps which lead to the problem. 29 | Please also provide details about your operating system and its configuration if you can. 30 | validations: 31 | required: true 32 | -------------------------------------------------------------------------------- /.github/release-body.md: -------------------------------------------------------------------------------- 1 | ## 📝 Changelog 2 | 3 | - [streamlink/streamlink](https://github.com/streamlink/streamlink/releases) (release changelog) 4 | - [streamlink/streamlink-appimage](https://github.com/streamlink/streamlink-appimage/blob/master/CHANGELOG.md) (packaging changelog) 5 | 6 | ## ⚙️ Instructions 7 | 8 | Please see the [README.md](https://github.com/streamlink/streamlink-appimage#how-to) on how to set up and use AppImages. 9 | 10 | Further information can be found in Streamlink's [install docs](https://streamlink.github.io/install.html) and [Command-Line Interface usage guide](https://streamlink.github.io/cli.html). 11 | 12 | ## ❤️ Support 13 | 14 | If you think that Streamlink is useful and if you want to keep the project alive, then please consider supporting its maintainers by sending a small and optionally recurring tip via the [available options](https://streamlink.github.io/support.html). 15 | Your support is very much appreciated, thank you! 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy 2 | on: 3 | push: {} 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | strategy: 9 | matrix: 10 | include: 11 | - arch: aarch64 12 | runs-on: ubuntu-24.04-arm 13 | bundle: "" 14 | - arch: x86_64 15 | runs-on: ubuntu-24.04 16 | bundle: "" 17 | - arch: aarch64 18 | runs-on: ubuntu-24.04-arm 19 | bundle: ffmpeg 20 | - arch: x86_64 21 | runs-on: ubuntu-24.04 22 | bundle: ffmpeg 23 | runs-on: ${{ matrix.runs-on }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-python@v5 27 | with: 28 | python-version: "3.12" 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install -U --upgrade-strategy=eager -r requirements.txt 32 | - name: appimage 33 | run: | 34 | SOURCE_DATE_EPOCH=$(git show -s --format=%ct) ./build.sh --bundle=${{ matrix.bundle }} 35 | - name: Get file name 36 | id: vars 37 | run: | 38 | echo "file_name=$(cd dist && ls *.*)" >> $GITHUB_OUTPUT 39 | - uses: actions/upload-artifact@v4 40 | with: 41 | name: ${{ steps.vars.outputs.file_name }} 42 | path: dist/*.AppImage 43 | deploy: 44 | name: Deploy 45 | needs: 46 | - build 47 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') 48 | runs-on: ubuntu-latest 49 | permissions: 50 | contents: write 51 | steps: 52 | - uses: actions/checkout@v4 53 | - run: mkdir -p dist 54 | - uses: actions/download-artifact@v4 55 | with: 56 | path: dist 57 | - run: ./deploy.sh dist/**/*.AppImage 58 | env: 59 | RELEASES_API_KEY: ${{ secrets.GITHUB_TOKEN }} 60 | -------------------------------------------------------------------------------- /.github/workflows/preview-build.yml: -------------------------------------------------------------------------------- 1 | name: Preview build 2 | run-name: "Preview build - ${{ inputs.ref }}" 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | ref: 8 | description: A git ref on the Streamlink git repo 9 | default: master 10 | required: true 11 | type: string 12 | 13 | jobs: 14 | build: 15 | name: "${{ inputs.ref }} (${{ matrix.arch }})" 16 | runs-on: ${{ matrix.runs-on }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | include: 21 | - arch: aarch64 22 | runs-on: ubuntu-24.04-arm 23 | bundle: ffmpeg 24 | - arch: x86_64 25 | runs-on: ubuntu-24.04 26 | bundle: ffmpeg 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | - uses: actions/setup-python@v5 32 | with: 33 | python-version: "3.12" 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install -U --upgrade-strategy=eager -r requirements.txt 37 | - name: Build 38 | run: | 39 | ./build.sh --arch "${{ matrix.arch }}" --gitref "${{ inputs.ref }}" --bundle "${{ matrix.bundle }}" 40 | - name: Get file name 41 | id: vars 42 | run: | 43 | echo "file_name=$(cd dist && ls *.*)" >> $GITHUB_OUTPUT 44 | - uses: actions/upload-artifact@v4 45 | with: 46 | name: ${{ steps.vars.outputs.file_name }} 47 | path: dist/* 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS and editors 2 | .DS_Store 3 | ._* 4 | Thumbs.db 5 | Desktop.ini 6 | *.bak 7 | .cache 8 | .project 9 | .settings 10 | .tmproj 11 | nbproject 12 | *.sublime-project 13 | *.sublime-workspace 14 | .idea 15 | 16 | cache 17 | dist 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog - streamlink/streamlink-appimage 2 | ==== 3 | 4 | ## 7.3.0-1 (2025-04-26) 5 | 6 | - Updated Streamlink to 7.3.0, updated its dependencies 7 | - Updated build images, with Python 3.13.3 8 | 9 | ## 7.2.0-1 (2025-04-04) 10 | 11 | - Updated Streamlink to 7.2.0, updated its dependencies 12 | - Added optional `zstandard` dependency 13 | 14 | ## 7.1.3-1 (2025-02-14) 15 | 16 | - Updated Streamlink to 7.1.3, updated its dependencies 17 | - Updated build images, with Python 3.13.2 18 | - Added secondary AppImages with FFmpeg being bundled (x86\_64 and aarch64) 19 | - Added preview builds triggered from commits to Streamlink's master branch 20 | - Removed nightly builds 21 | - Refactored build scripts and replaced positional build parameters with getopt parameters 22 | - Changed AppImage file name format for builds of untagged commits 23 | 24 | ## 7.1.2-1 (2025-01-08) 25 | 26 | - Updated Streamlink to 7.1.2 27 | 28 | ## 7.1.1-1 (2024-12-28) 29 | 30 | - Updated Streamlink to 7.1.1 31 | 32 | ## 7.1.0-1 (2024-12-28) 33 | 34 | - Updated Streamlink to 7.1.0, updated its dependencies 35 | - Updated build images, with Python 3.12.8 36 | - Fixed missing license files after the manylinux\_2\_28 switch 37 | 38 | ## 7.0.0-1 (2024-11-04) 39 | 40 | - Updated Streamlink to 7.0.0, updated its dependencies 41 | - Switched from EOL manylinux2014 to manylinux\_2\_28 (glibc 2.28+ is now required) 42 | - Dropped i686 ("32 bit") builds (unsupported on manylinux\_2\_28) 43 | - Updated build images, with Python 3.12.7 44 | 45 | ## 6.11.0-1 (2024-10-01) 46 | 47 | - Updated Streamlink to 6.11.0, updated its dependencies 48 | - Updated build images, with Python 3.12.6 49 | 50 | ## 6.10.0-1 (2024-09-06) 51 | 52 | - Updated Streamlink to 6.10.0, updated its dependencies 53 | 54 | ## 6.9.0-1 (2024-08-12) 55 | 56 | - Updated Streamlink to 6.9.0, updated its dependencies 57 | - Updated build images, with Python 3.12.5 58 | 59 | ## 6.8.3-1 (2024-07-11) 60 | 61 | - Updated Streamlink to 6.8.3, updated its dependencies 62 | 63 | ## 6.8.2-2 (2024-07-04) 64 | 65 | - Updated build images 66 | 67 | ## 6.8.2-1 (2024-07-04) 68 | 69 | - Updated Streamlink to 6.8.2, updated its dependencies 70 | 71 | ## 6.8.1-1 (2024-06-18) 72 | 73 | - Updated Streamlink to 6.8.1 74 | 75 | ## 6.8.0-1 (2024-06-17) 76 | 77 | - Updated Streamlink to 6.8.0, updated its dependencies 78 | - Updated build images, with Python 3.12.4 79 | - Made AppImages use the bundled cert file from `certifi` (unless `SSL_CERT_FILE` env var is set to a different path) 80 | - Fixed rpath patch issue in build script 81 | 82 | ## 6.7.4-1 (2024-05-12) 83 | 84 | - Updated Streamlink to 6.7.4, updated its dependencies 85 | 86 | ## 6.7.3-1 (2024-04-14) 87 | 88 | - Updated Streamlink to 6.7.3, updated its dependencies 89 | - Updated build images, with Python 3.12.3 90 | 91 | ## 6.7.1-1 (2024-03-19) 92 | 93 | - Updated Streamlink to 6.7.1, updated its dependencies 94 | 95 | ## 6.7.0-1 (2024-03-09) 96 | 97 | - Updated Streamlink to 6.7.0, updated its dependencies 98 | 99 | ## 6.6.2-1 (2024-02-20) 100 | 101 | - Updated Streamlink to 6.6.2 102 | 103 | ## 6.6.1-1 (2024-02-17) 104 | 105 | - Updated Streamlink to 6.6.1 106 | 107 | ## 6.6.0-1 (2024-02-16) 108 | 109 | - Updated Streamlink to 6.6.0, updated its dependencies 110 | - Updated build images, with Python 3.12.2 111 | 112 | ## 6.5.1-1 (2024-01-16) 113 | 114 | - Updated Streamlink to 6.5.1, updated its dependencies 115 | - Switched from Python 3.11 to Python 3.12 116 | 117 | ## 6.5.0-1 (2023-12-16) 118 | 119 | - Updated Streamlink to 6.5.0, updated its dependencies 120 | - Updated build images, with Python 3.11.7 121 | - Added brotli dependency 122 | 123 | ## 6.4.2-1 (2023-11-28) 124 | 125 | - Updated Streamlink to 6.4.2 126 | 127 | ## 6.4.1-1 (2023-11-22) 128 | 129 | - Updated Streamlink to 6.4.1 130 | 131 | ## 6.4.0-1 (2023-11-21) 132 | 133 | - Updated Streamlink to 6.4.0, updated its dependencies 134 | 135 | ## 6.3.1-1 (2023-10-26) 136 | 137 | - Updated Streamlink to 6.3.1 138 | 139 | ## 6.3.0-1 (2023-10-25) 140 | 141 | - Updated Streamlink to 6.3.0, updated its dependencies 142 | - Updated build images, with Python 3.11.6 143 | 144 | ## 6.2.1-1 (2023-10-03) 145 | 146 | - Updated Streamlink to 6.2.1, updated its dependencies 147 | - Updated build images, with Python 3.11.5 and OpenSSL 3.0.11 148 | - Switched build-config format from JSON to YML 149 | 150 | ## 6.2.0-1 (2023-09-14) 151 | 152 | - Updated Streamlink to 6.2.0, updated its dependencies 153 | 154 | ## 6.1.0-2 (2023-08-28) 155 | 156 | - Updated build images, with Python 3.11.5 157 | 158 | ## 6.1.0-1 (2023-08-16) 159 | 160 | - Updated Streamlink to 6.1.0, updated its dependencies 161 | - Added param to get-dependencies script for optional dependency overrides 162 | 163 | ## 6.0.1-1 (2023-08-02) 164 | 165 | - Updated Streamlink to 6.0.1, updated its dependencies 166 | 167 | ## 6.0.0-1 (2023-07-20) 168 | 169 | - Updated Streamlink to 6.0.0, updated its dependencies 170 | - Updated build images, with Python 3.11.4 171 | 172 | ## 5.5.1-2 (2023-05-22) 173 | 174 | - Updated dependencies ([urllib3 2.x](https://urllib3.readthedocs.io/en/2.0.2/v2-migration-guide.html#what-are-the-important-changes), [requests 2.31.0](https://github.com/psf/requests/releases/tag/v2.31.0)) 175 | 176 | ## 5.5.1-1 (2023-05-08) 177 | 178 | - Updated Streamlink to 5.5.1, updated its dependencies 179 | 180 | ## 5.5.0-1 (2023-05-05) 181 | 182 | - Updated Streamlink to 5.5.0, updated its dependencies 183 | 184 | ## 5.4.0-1 (2023-04-12) 185 | 186 | - Updated Streamlink to 5.4.0, updated its dependencies 187 | - Updated build images, with Python 3.11.3 188 | 189 | ## 5.3.1-1 (2023-02-25) 190 | 191 | - Updated Streamlink to 5.3.1 192 | 193 | ## 5.3.0-1 (2023-02-18) 194 | 195 | - Updated Streamlink to 5.3.0, updated its dependencies 196 | - Updated build images, with Python 3.11.2 197 | 198 | ## 5.2.1-1 (2023-01-23) 199 | 200 | - Updated Streamlink to 5.2.1, updated its dependencies 201 | 202 | ## 5.1.2-2 (2022-12-14) 203 | 204 | - Switched from Python 3.10 to Python 3.11 205 | - Updated build images, with Python 3.11.1 206 | - Added back compiled Python bytecode of stdlib and site-packages 207 | 208 | ## 5.1.2-1 (2022-12-03) 209 | 210 | - Rewritten the build config and scripts in order to be more consistent with the window-builds repository 211 | - Switched to building Streamlink from git instead of PyPI 212 | - Updated Streamlink to 5.1.2, updated its dependencies 213 | - Updated build images, with new SquashFS and appimagetool versions 214 | - Added nightly builds 215 | - Added changelog 216 | 217 | ## 5.1.1-3 (2022-11-23) 218 | 219 | - Updated Streamlink to 5.1.1 220 | - Removed bytecode from AppImages due to non-deterministic builds 221 | - Renamed `build` dir to `dist` 222 | 223 | ## 5.1.0-1 (2022-11-14) 224 | 225 | - Updated Streamlink to 5.0.0, updated its dependencies 226 | - Updated build images, with Python 3.10.8 227 | - Fixed outdated Python version number in AppImage's appdata 228 | 229 | ## 5.0.0-1 (2022-09-16) 230 | 231 | - Updated Streamlink to 5.0.0, updated its dependencies 232 | - Updated build images, with Python 3.10.7 233 | 234 | ## 4.3.0-1 (2022-08-15) 235 | 236 | - Updated Streamlink to 4.3.0, updated its dependencies 237 | - Updated build images, with Python 3.10.6 238 | 239 | ## 4.2.0-1 (2022-07-09) 240 | 241 | - Updated Streamlink to 4.2.0, updated its dependencies 242 | - Updated build images, with Python 3.10.5 243 | 244 | ## 4.1.0-2 (2022-06-01) 245 | 246 | - Updated dependencies (fixed lxml) 247 | 248 | ## 4.1.0-1 (2022-05-30) 249 | 250 | - Updated Streamlink to 4.1.0, updated its dependencies 251 | 252 | ## 4.0.0-1 (2022-05-01) 253 | 254 | - Updated Streamlink to 4.0.0 255 | 256 | ## 3.2.0-2 (2022-04-05) 257 | 258 | - Updated dependencies 259 | - Updated build images, with Python 3.10.4 260 | 261 | ## 3.2.0-1 (2022-03-05) 262 | 263 | - Updated Streamlink to 3.2.0, updated its dependencies 264 | - Updated build images, with Python 3.10.2 265 | 266 | ## 3.1.1-2 (2022-02-14) 267 | 268 | - Updated dependencies (fixed charset-normalizer) 269 | 270 | ## 3.1.1-1 (2022-01-25) 271 | 272 | - Updated Streamlink to 3.1.1, updated its dependencies 273 | 274 | ## 3.1.0-1 (2022-01-22) 275 | 276 | - Updated Streamlink to 3.1.0, updated its dependencies 277 | - Updated build images, with Python 3.10.1 278 | 279 | ## 3.0.3-1 (2021-11-28) 280 | 281 | - Updated Streamlink to 3.0.3 282 | 283 | ## 3.0.2-1 (2021-11-25) 284 | 285 | - Updated Streamlink to 3.0.2, updated its dependencies 286 | 287 | ## 3.0.1-1 (2021-11-17) 288 | 289 | - Updated Streamlink to 3.0.1, updated its dependencies 290 | - Updated build images 291 | 292 | ## 2.4.0-1 (2021-09-07) 293 | 294 | - Updated Streamlink to 2.4.0, updated its dependencies 295 | 296 | ## 2.3.0-1 (2021-07-26) 297 | 298 | - Updated Streamlink to 2.3.0, updated its dependencies 299 | 300 | ## 2.2.0-1 (2021-06-19) 301 | 302 | - Updated Streamlink to 2.2.0, updated its dependencies 303 | 304 | ## 2.1.2-1 (2021-05-20) 305 | 306 | - Moved build dependencies to new docker images stored on GitHub's new container registry 307 | - Updated Streamlink to 2.1.2, updated its dependencies 308 | 309 | ## 2.1.1-1 (2021-03-25) 310 | 311 | - Updated Streamlink to 2.1.1 312 | 313 | ## 2.1.0-1 (2021-03-23) 314 | 315 | - Updated Streamlink to 2.1.0, updated its dependencies 316 | 317 | ## 2.0.0-1 (2021-03-07) 318 | 319 | - Initial release, based on Streamlink 2.0.0 and Python 3.9 320 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2025, Sebastian Meyer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Streamlink AppImage 2 | ==== 3 | 4 | Linux [AppImage][appimage] build config for [Streamlink][streamlink] 5 | 6 | ### Contents 7 | 8 | - [a Python environment](https://github.com/streamlink/appimage-buildenv) 9 | - [Streamlink and its dependencies](https://github.com/streamlink/streamlink) 10 | - [FFmpeg, for muxing streams (optional)](https://github.com/streamlink/FFmpeg-Builds) 11 | 12 | ### Supported architectures 13 | 14 | - x86\_64 15 | - aarch64 16 | 17 | ### How to 18 | 19 | 1. Verify that the system is running on at least [glibc][glibc-wikipedia] [2.28 (Aug 2018)][glibc-release-distro-mapping] (see `ld.so --version`) 20 | 21 | 2. [Download the AppImage file matching the system's CPU architecture][releases] (see `uname --machine`) 22 | 23 | 3. Set the executable flag via a file browser or `chmod +x filename` from a command-line shell 24 | 25 | ```bash 26 | # AppImage file names include the release version, 27 | # the Python version, platform name and CPU architecture 28 | chmod +x streamlink-7.0.0-1-cp312-cp312-manylinux_2_28_x86_64.AppImage 29 | ``` 30 | 31 | 4. Run the AppImage with any command-line parameters supported by Streamlink 32 | 33 | ```bash 34 | ./streamlink-7.0.0-1-cp312-cp312-manylinux_2_28_x86_64.AppImage --loglevel=debug 35 | ``` 36 | 37 | ### What are AppImages 38 | 39 | AppImages are portable applications which are independent of the Linux distribution in use and its package management. Just set the executable flag on the AppImage file and run it. 40 | 41 | The only requirement is having [FUSE][appimage-fuse] installed for being able to mount the contents of the AppImage's SquashFS, which is done automatically. Also, only glibc-based systems are currently supported. 42 | 43 | Note: Check out [AppImageLauncher][appimagelauncher], which automates the setup and system integration of AppImages. AppImageLauncher may also be available via your distro's package management. 44 | 45 | Additional information, like for example how to inspect the AppImage contents or how to extract the contents if [FUSE][appimage-fuse] is not available on your system, can be found in the [AppImage documentation][appimage-documentation]. 46 | 47 | ### About 48 | 49 | These AppImages are built using the [`streamlink/appimage-buildenv-*`][streamlink-appimage-buildenv] docker images, which are based on the [`pypa/manylinux`][manylinux] project and the [`manylinux_2_28`][manylinux_2_28] platform, which is based on AlmaLinux 8. The pre-built Python install and its needed runtime libraries are copied from the docker image (see the manylinux build files) into the AppImages, in addition to the main Python application code, namely Streamlink and its dependencies, which are pulled from GitHub and PyPI. Streamlink's AppImages optionally bundle third-party software, like [Streamlink's own FFmpeg builds][ffmpeg-builds]. 50 | 51 | ### Build 52 | 53 | Requirements: `git`, `jq`, `yq`, `docker` 54 | Supported architectures: `x86_64`, `aarch64` 55 | Optionally bundled software: `ffmpeg` 56 | 57 | ```bash 58 | # Build 59 | ./build.sh [--arch=$ARCH] [--gitrepo=$GITREPO] [--gitref=$GITREF] [--bundle=...] 60 | 61 | # Get new list of Python dependencies (for updating config.yml) 62 | ./get-dependencies.sh [--arch=$ARCH] [--gitrepo=$GITREPO] [--gitref=$GITREF] [depspec...] 63 | ``` 64 | 65 | The AppImages are reproducible when `SOURCE_DATE_EPOCH` is set: 66 | 67 | ```bash 68 | export SOURCE_DATE_EPOCH=$(git show -s --format=%ct) 69 | ``` 70 | 71 | 72 | [appimage]: https://appimage.org/ 73 | [appimage-documentation]: https://docs.appimage.org/user-guide/run-appimages.html 74 | [appimage-fuse]: https://docs.appimage.org/user-guide/troubleshooting/fuse.html 75 | [streamlink]: https://github.com/streamlink/streamlink 76 | [streamlink-appimage-buildenv]: https://github.com/streamlink/appimage-buildenv 77 | [ffmpeg-builds]: https://github.com/streamlink/FFmpeg-Builds 78 | [releases]: https://github.com/streamlink/streamlink-appimage/releases 79 | [appimagelauncher]: https://github.com/TheAssassin/AppImageLauncher 80 | [manylinux]: https://github.com/pypa/manylinux 81 | [manylinux_2_28]: https://github.com/pypa/manylinux#manylinux_2_28-almalinux-8-based 82 | [glibc-wikipedia]: https://en.wikipedia.org/wiki/Glibc 83 | [glibc-release-distro-mapping]: https://sourceware.org/glibc/wiki/Release#Distribution_Branch_Mapping 84 | -------------------------------------------------------------------------------- /app/streamlink.appdata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.github.streamlink.streamlink 4 | BSD-2-Clause 5 | BSD-2-Clause 6 | Streamlink 7 | A command-line utility that extracts streams from various services and pipes them into a video player of choice. 8 | 9 |

Streamlink is a command-line utility which pipes video streams from various services into a video player, such as VLC. The main purpose of Streamlink is to avoid resource-heavy and unoptimized websites, while still allowing the user to enjoy various streamed content.

10 |
11 | streamlink.desktop 12 | https://streamlink.github.io/ 13 | 14 | python3.10 15 | streamlink 16 | 17 |
18 | -------------------------------------------------------------------------------- /app/streamlink.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Streamlink 4 | Exec=streamlink 5 | Comment=A command-line utility that extracts streams from various services and pipes them into a video player of choice. 6 | Icon=streamlink 7 | Categories=AudioVideo;Development;Network; 8 | Terminal=true 9 | -------------------------------------------------------------------------------- /app/streamlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | image/svg+xml 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /build-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | ABI="${1}" 5 | ENTRY="${2}" 6 | 7 | PIP_ARGS=( 8 | --disable-pip-version-check 9 | --root-user-action=ignore 10 | --isolated 11 | --no-cache-dir 12 | --no-deps 13 | ) 14 | 15 | 16 | # ---- 17 | 18 | 19 | SELF=$(basename -- "$(readlink -f -- "${0}")") 20 | log() { 21 | echo "[${SELF}]" "${@}" 22 | } 23 | err() { 24 | log >&2 "$@" 25 | exit 1 26 | } 27 | 28 | [[ -f /.dockerenv ]] || err "This script is supposed to be run from build.sh inside a docker container" 29 | 30 | declare -A excludelist 31 | while read -r lib; do 32 | excludelist["${lib}"]="${lib}" 33 | done <<< "$(sed -e '/#.*/d; /^[[:space:]]*|[[:space:]]*$/d; /^$/d' /usr/local/share/appimage/excludelist)" 34 | 35 | libraries=() 36 | 37 | 38 | # ---- 39 | 40 | 41 | # based on niess/python-appimage (GPLv3) 42 | # https://github.com/niess/python-appimage/blob/d0d64c3316ced7660476d50b5c049f3939213519/python_appimage/appimage/relocate.py 43 | 44 | DEST=out.AppImage 45 | 46 | PYTHON="/opt/python/${ABI}/bin/python" 47 | VERSION=$("${PYTHON}" -B -c 'import sys; print("{}.{}".format(*sys.version_info[:2]))') 48 | PYTHON_X_Y="python${VERSION}" 49 | 50 | APPDIR=AppDir 51 | APPDIR_BIN="${APPDIR}/usr/bin" 52 | APPDIR_LIB="${APPDIR}/usr/lib" 53 | 54 | HOST_PREFIX=$("${PYTHON}" -c 'import sys; print(sys.prefix)') 55 | HOST_BIN="${HOST_PREFIX}/bin" 56 | HOST_INC="${HOST_PREFIX}/include/${PYTHON_X_Y}" 57 | HOST_LIB="${HOST_PREFIX}/lib" 58 | HOST_PKG="${HOST_LIB}/${PYTHON_X_Y}" 59 | 60 | PYTHON_PREFIX="${APPDIR}/opt/${PYTHON_X_Y}" 61 | PYTHON_BIN="${PYTHON_PREFIX}/bin" 62 | PYTHON_INC="${PYTHON_PREFIX}/include/${PYTHON_X_Y}" 63 | PYTHON_LIB="${PYTHON_PREFIX}/lib" 64 | PYTHON_PKG="${PYTHON_LIB}/${PYTHON_X_Y}" 65 | 66 | 67 | patch_binary() { 68 | local path="${1}" 69 | local libdir="${2}" 70 | local recursive="${3:-false}" 71 | 72 | local newrpath rpath relpath deps 73 | rpath=$(patchelf --print-rpath "${path}") 74 | relpath="$(realpath --relative-to="$(dirname -- "${path}")" "${libdir}")" 75 | if [[ "${relpath}" == "." ]]; then newrpath="\$ORIGIN"; else newrpath="\$ORIGIN/${relpath}"; fi 76 | 77 | mapfile -t deps < <(ldd "${path}" 2>/dev/null | grep -E ' => \S+' | sed -E 's/.+ => (.+) \(0x.+/\1/') 78 | 79 | if [[ "${rpath}" != "${newrpath}" ]]; then 80 | log "Patching RPATH: ${path} (\"${rpath}\" -> \"${newrpath}\")" 81 | patchelf --set-rpath "${newrpath}" "${path}" 82 | fi 83 | 84 | for dep in "${deps[@]}"; do 85 | local name 86 | name=$(basename "${dep}") 87 | [[ -n "${excludelist[${name}]}" ]] && continue 88 | local target="${libdir}/${name}" 89 | if ! [[ -f "${target}" ]]; then 90 | log "Bundling library: ${dep} (${target})" 91 | libraries+=("${dep}") 92 | install -Dm777 "${dep}" "${target}" 93 | if [[ "${recursive}" == true ]]; then 94 | patch_binary "${target}" "${libdir}" true 95 | fi 96 | fi 97 | done 98 | } 99 | 100 | 101 | setup_python() { 102 | log "Setting up python install" 103 | 104 | local file 105 | install -Dm777 "${HOST_BIN}/${PYTHON_X_Y}" "${PYTHON_BIN}/${PYTHON_X_Y}" 106 | 107 | mkdir -p "${PYTHON_PKG}" "${PYTHON_INC}" 108 | cp -aT "${HOST_PKG}" "${PYTHON_PKG}" 109 | cp -aT "${HOST_INC}" "${PYTHON_INC}" 110 | rm -rf \ 111 | "${PYTHON_LIB}/lib/${PYTHON_X_Y}.a" \ 112 | "${PYTHON_PKG}/"{test,dist-packages,config-*-linux-*} \ 113 | || true 114 | 115 | mkdir -p "${APPDIR_BIN}" 116 | ln -rs "${PYTHON_BIN}/${PYTHON_X_Y}" "${APPDIR_BIN}/${PYTHON_X_Y}" 117 | ln -sfT "${PYTHON_X_Y}" "${APPDIR_BIN}/python" 118 | ln -sfT "${PYTHON_X_Y}" "${APPDIR_BIN}/python3" 119 | 120 | mkdir -p "${APPDIR_LIB}" 121 | patch_binary "${PYTHON_BIN}/${PYTHON_X_Y}" "${APPDIR_LIB}" false 122 | while read -r file; do 123 | patch_binary "${file}" "${APPDIR_LIB}" false 124 | done <<< "$(find "${PYTHON_PKG}/lib-dynload" -type f -name '*.so' -print)" 125 | while read -r file; do 126 | patch_binary "${file}" "${APPDIR_LIB}" true 127 | done <<< "$(find "${APPDIR_LIB}" -type f -name 'lib*.so*' -print)" 128 | } 129 | 130 | 131 | install_application() { 132 | export PYTHONHASHSEED=0 133 | 134 | log "Installing dependencies" 135 | "${PYTHON_BIN}/${PYTHON_X_Y}" -B -m pip install \ 136 | "${PIP_ARGS[@]}" \ 137 | --no-compile \ 138 | --require-hashes \ 139 | -r requirements.txt 140 | 141 | log "Installing application" 142 | # fix git permission issue when getting version string via versioningit 143 | git config --global --add safe.directory /app/source.git 144 | "${PYTHON_BIN}/${PYTHON_X_Y}" -B -m pip install \ 145 | --verbose \ 146 | "${PIP_ARGS[@]}" \ 147 | --no-compile \ 148 | /app/source.git 149 | } 150 | 151 | get_version() { 152 | log "Reading version string" 153 | "${PYTHON_BIN}/${PYTHON_X_Y}" -Bsc "from importlib.metadata import version;print(version('${ENTRY}'))" \ 154 | | tee version.txt 155 | } 156 | 157 | cleanup() { 158 | log "Removing unneeded dependencies" 159 | "${PYTHON_BIN}/${PYTHON_X_Y}" -B -m pip uninstall \ 160 | -y \ 161 | -r <("${HOST_BIN}/${PYTHON_X_Y}" -B -m pip list --format=freeze) 162 | } 163 | 164 | build_bytecode() { 165 | log "Building bytecode" 166 | "${PYTHON_BIN}/${PYTHON_X_Y}" -B -m compileall -q -j1 -f -r9999 -x 'lib2to3|test' "${PYTHON_LIB}" 167 | } 168 | 169 | 170 | copy_licenses() { 171 | log "Finding library licenses" 172 | declare -A packages 173 | local package 174 | for library in "${libraries[@]}"; do 175 | package=$(dnf repoquery --installed --file "$(readlink -f -- "${library}")") 176 | if [[ -z "${package}" ]]; then 177 | log "Could not find package for library ${library}" 178 | continue 179 | fi 180 | packages["${package}"]="${library}" 181 | done 182 | 183 | for package in "${!packages[@]}"; do 184 | if ! find_licenses "${package}" >/dev/null; then 185 | log "Could not find license files for package ${package}" 186 | for dependency in $(dnf repoquery --installed --requires --resolve "${package}"); do 187 | if [[ -z "${packages["${dependency}"]}" ]]; then 188 | # ignore dependencies with files in the excludelist 189 | for depfile in $(dnf repoquery --installed --list "${dependency}"); do 190 | [[ "${excludelist["$(basename "${depfile}")"]}" ]] && continue 2 191 | done 192 | echo "Attempting to find licenses in dependency ${dependency}" 193 | packages["${dependency}"]="${packages["${package}"]}" 194 | fi 195 | done 196 | fi 197 | done 198 | 199 | log "Re-installing packages without suppressing license files:" "${!packages[@]}" 200 | dnf reinstall -y -v --setopt=timeout=5 --setopt=retries=3 --setopt=tsflags= "${!packages[@]}" 201 | 202 | for package in "${!packages[@]}"; do 203 | log "Copying license files for package ${package}" 204 | for file in $(find_licenses "${package}" || true); do 205 | install -vDm644 "${file}" "${APPDIR}${file}" 206 | done 207 | done 208 | } 209 | 210 | find_licenses() { 211 | dnf repoquery --installed --list "${1}" \ 212 | | grep -Ei '^/usr/share/(doc|licenses)/.*(copying|licen[cs]e|readme|terms).*' 213 | } 214 | 215 | 216 | build_appimage() { 217 | log "Building appimage" 218 | [ "${SOURCE_DATE_EPOCH}" ] && mtime="@${SOURCE_DATE_EPOCH}" || mtime=now 219 | find "${APPDIR}" -exec touch --no-dereference "--date=${mtime}" '{}' '+' 220 | /usr/local/bin/mksquashfs "${APPDIR}" AppDir.sqfs -comp zstd -root-owned -noappend -b 128k 221 | cat /usr/local/share/appimage/runtime AppDir.sqfs > "${DEST}" 222 | chmod +x "${DEST}" 223 | } 224 | 225 | 226 | build() { 227 | setup_python 228 | copy_licenses 229 | install_application 230 | get_version 231 | cleanup 232 | build_bytecode 233 | build_appimage 234 | 235 | log "Successfully built appimage" 236 | } 237 | 238 | build 239 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2016 3 | 4 | set -euo pipefail 5 | 6 | ARCH="$(uname -m)" 7 | GITREPO="" 8 | GITREF="" 9 | BUNDLES=() 10 | 11 | ROOT=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(readlink -f "${0}")") 12 | CONFIG="${ROOT}/config.yml" 13 | DIR_APP="${ROOT}/app" 14 | DIR_CACHE="${ROOT}/cache" 15 | DIR_DIST="${ROOT}/dist" 16 | SCRIPT_DOCKER="${ROOT}/build-docker.sh" 17 | 18 | declare -A DEPS=( 19 | [git]=git 20 | [jq]=jq 21 | [yq]=yq 22 | [docker]=docker 23 | ) 24 | 25 | _OPTS=$(getopt --name "$0" --long 'help,arch:,gitrepo:,gitref:,bundle:' --options 'help,a:,b:' -- "$@") 26 | eval set -- "${_OPTS}" 27 | unset _OPTS 28 | 29 | 30 | # ---- 31 | 32 | 33 | SELF=$(basename -- "$(readlink -f -- "${0}")") 34 | log() { 35 | echo "[${SELF}]" "$@" 36 | } 37 | err() { 38 | log >&2 "$@" 39 | exit 1 40 | } 41 | 42 | print_help() { 43 | echo "Usage: ${0} [options]" 44 | echo 45 | echo "Options:" 46 | echo " -a, --arch Target architecture" 47 | echo " --gitrepo Source" 48 | echo " --gitref Git branch/tag/commit" 49 | echo " -b, --bundle Comma-separated list of bundled software" 50 | exit 0 51 | } 52 | 53 | in_array() { 54 | local item elem 55 | item="${1}" 56 | shift 57 | for elem in "${@}"; do 58 | [[ "${elem}" == "${item}" ]] && return 0 59 | done 60 | return 1 61 | } 62 | 63 | 64 | # ---- 65 | 66 | 67 | while true; do 68 | case "${1}" in 69 | -h | --help) 70 | print_help 71 | ;; 72 | -a | --arch) 73 | ARCH="${2}" 74 | shift 2 75 | ;; 76 | --gitrepo) 77 | GITREPO="${2}" 78 | shift 2 79 | ;; 80 | --gitref) 81 | GITREF="${2}" 82 | shift 2 83 | ;; 84 | -b | --bundle) 85 | IFS=',' read -r -a BUNDLES <<< "${2}" 86 | shift 2 87 | ;; 88 | --) 89 | shift 90 | break 91 | ;; 92 | *) 93 | err "Invalid option: ${1}" 94 | ;; 95 | esac 96 | done 97 | 98 | 99 | # ---- 100 | 101 | 102 | for dep in "${!DEPS[@]}"; do 103 | command -v "${dep}" >/dev/null 2>&1 || err "Missing dependency: ${DEPS["${dep}"]}" 104 | done 105 | 106 | [[ -f "${CONFIG}" ]] \ 107 | || err "Missing config file: ${CONFIG}" 108 | CONFIGJSON=$(cat "${CONFIG}") 109 | 110 | yq -e --arg a "${ARCH}" '.builds[$a]' >/dev/null <<< "${CONFIGJSON}" \ 111 | || err "Unsupported arch" 112 | 113 | mapfile -t bundles < \ 114 | <(yq -r --arg a "${ARCH}" '.builds[$a].bundles | keys[]' <<< "${CONFIGJSON}") 115 | for bundle in "${BUNDLES[@]}"; do 116 | in_array "${bundle}" "${bundles[@]}" || err "Invalid bundle name: ${bundle}" 117 | done 118 | 119 | read -r appname apprel appentry \ 120 | < <(yq -r '.app | "\(.name) \(.rel) \(.entry)"' <<< "${CONFIGJSON}") 121 | read -r gitrepo gitref \ 122 | < <(yq -r '.git | "\(.repo) \(.ref)"' <<< "${CONFIGJSON}") 123 | read -r image tag abi \ 124 | < <(yq -r --arg a "${ARCH}" '.builds[$a] | "\(.image) \(.tag) \(.abi)"' <<< "${CONFIGJSON}") 125 | 126 | gitrepo="${GITREPO:-${gitrepo}}" 127 | gitref="${GITREF:-${gitref}}" 128 | 129 | 130 | # ---- 131 | 132 | 133 | # shellcheck disable=SC2064 134 | TEMP=$(mktemp -d) && trap "rm -rf -- '${TEMP}'" EXIT || exit 255 135 | cd "${TEMP}" 136 | 137 | DIR_APPDIR="${TEMP}/AppDir" 138 | DIR_BUNDLES="${TEMP}/bundles" 139 | 140 | mkdir -p \ 141 | "${DIR_CACHE}" \ 142 | "${DIR_DIST}" \ 143 | "${DIR_APPDIR}" \ 144 | "${DIR_BUNDLES}" 145 | 146 | 147 | get_docker_image() { 148 | log "Getting docker image" 149 | [[ -n "$(docker image ls -q "${image}")" ]] \ 150 | || docker image pull "${image}" 151 | } 152 | 153 | 154 | get_sources() { 155 | log "Getting sources" 156 | mkdir -p "${TEMP}/source.git" 157 | pushd "${TEMP}/source.git" 158 | 159 | # TODO: re-investigate and optimize this 160 | git clone --depth 1 "${gitrepo}" . 161 | git fetch origin --depth 300 "${gitref}:branch" 162 | git ls-remote --tags --sort=version:refname 2>&- \ 163 | | awk "END{printf \"+%s:%s\\n\",\$2,\$2}" \ 164 | | git fetch origin --depth=300 165 | git -c advice.detachedHead=false checkout --force branch 166 | git fetch origin --depth=300 --update-shallow 167 | 168 | log "Commit information" 169 | git describe --tags --long --dirty 170 | git --no-pager log -1 --pretty=full 171 | 172 | popd 173 | } 174 | 175 | 176 | get_bundles() { 177 | local bundle 178 | for bundle in "${bundles[@]}"; do 179 | in_array "${bundle}" "${BUNDLES[@]}" || continue 180 | local filename url sha256 181 | read -r filename url sha256 \ 182 | < <(yq -r --arg a "${ARCH}" --arg b "${bundle}" '.builds[$a].bundles[$b] | "\(.filename) \(.url) \(.sha256)"' <<< "${CONFIGJSON}") 183 | if ! [[ -f "${DIR_CACHE}/${filename}" ]]; then 184 | log "Downloading bundle: ${bundle}" 185 | curl -SLo "${DIR_CACHE}/${filename}" "${url}" 186 | fi 187 | log "Checking bundle: ${bundle}" 188 | sha256sum -c - <<< "${sha256} ${DIR_CACHE}/${filename}" 189 | done 190 | } 191 | 192 | 193 | prepare_bundles() { 194 | log "Preparing bundles" 195 | local bundle 196 | for bundle in "${bundles[@]}"; do 197 | in_array "${bundle}" "${BUNDLES[@]}" || continue 198 | log "Preparing bundle: ${bundle}" 199 | local type filename sourcedir 200 | read -r type filename sourcedir \ 201 | < <(yq -r --arg a "${ARCH}" --arg b "${bundle}" '.builds[$a].bundles[$b] | "\(.type) \(.filename) \(.sourcedir)"' <<< "${CONFIGJSON}") 202 | case "${type}" in 203 | tar) 204 | mkdir -p "${DIR_BUNDLES}/${bundle}" 205 | tar -C "${DIR_BUNDLES}/${bundle}" -xvf "${DIR_CACHE}/${filename}" 206 | sourcedir="${DIR_BUNDLES}/${bundle}/${sourcedir}" 207 | ;; 208 | zip) 209 | mkdir -p "${DIR_BUNDLES}/${bundle}" 210 | unzip "${DIR_CACHE}/${filename}" -d "${DIR_BUNDLES}/${bundle}" 211 | sourcedir="${DIR_BUNDLES}/${bundle}/${sourcedir}" 212 | ;; 213 | *) 214 | sourcedir="${DIR_CACHE}" 215 | ;; 216 | esac 217 | while read -r from to; do 218 | install -vDT "${sourcedir}/${from}" "${DIR_APPDIR}/${to}" 219 | done < <(yq -r --arg a "${ARCH}" --arg b "${bundle}" '.builds[$a].bundles[$b].files[] | "\(.from) \(.to)"' <<< "${CONFIGJSON}") 220 | done 221 | } 222 | 223 | 224 | prepare_tempdir() { 225 | log "Copying container build files" 226 | cp -vt "${TEMP}" "${SCRIPT_DOCKER}" 227 | 228 | log "Building requirements.txt" 229 | yq -r --arg a "${ARCH}" '.builds[$a].dependencies | to_entries | .[] | "\(.key)==\(.value)"' <<< "${CONFIGJSON}" \ 230 | > "${TEMP}/requirements.txt" 231 | 232 | log "Installing AppDir files" 233 | install -Dm644 -t "${DIR_APPDIR}/usr/share/applications/" "${DIR_APP}/${appname}.desktop" 234 | install -Dm644 -t "${DIR_APPDIR}/usr/share/icons/hicolor/scalable/apps/" "${DIR_APP}/${appname}.svg" 235 | install -Dm644 -t "${DIR_APPDIR}/usr/share/metainfo/" "${DIR_APP}/${appname}.appdata.xml" 236 | ln -sr "${DIR_APPDIR}/usr/share/applications/${appname}.desktop" "${DIR_APPDIR}/${appname}.desktop" 237 | ln -sr "${DIR_APPDIR}/usr/share/icons/hicolor/scalable/apps/${appname}.svg" "${DIR_APPDIR}/${appname}.svg" 238 | ln -sr "${DIR_APPDIR}/usr/share/icons/hicolor/scalable/apps/${appname}.svg" "${DIR_APPDIR}/.DirIcon" 239 | cat > "${DIR_APPDIR}/AppRun" <=1.0.9 # optional urllib3 dependency 30 | - zstandard>=0.18.0 # optional urllib3 dependency 31 | dependencies: 32 | attrs: 25.3.0 --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 33 | Brotli: 1.1.0 --hash=sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f 34 | certifi: 2025.4.26 --hash=sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3 35 | charset-normalizer: 3.4.2 --hash=sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf 36 | h11: 0.16.0 --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 37 | idna: 3.10 --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 38 | isodate: 0.7.2 --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 39 | lxml: 5.4.0 --hash=sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff 40 | outcome: 1.3.0.post0 --hash=sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b 41 | pycountry: 24.6.1 --hash=sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f 42 | pycryptodome: 3.23.0 --hash=sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490 43 | PySocks: 1.7.1 --hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 44 | requests: 2.32.3 --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 45 | sniffio: 1.3.1 --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 46 | sortedcontainers: 2.4.0 --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 47 | trio: 0.30.0 --hash=sha256:3bf4f06b8decf8d3cf00af85f40a89824669e2d033bb32469d34840edcfc22a5 48 | trio-websocket: 0.12.2 --hash=sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6 49 | urllib3: 2.4.0 --hash=sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813 50 | websocket-client: 1.8.0 --hash=sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526 51 | wsproto: 1.2.0 --hash=sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736 52 | zstandard: 0.23.0 --hash=sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2 53 | x86_64: 54 | image: ghcr.io/streamlink/appimage-buildenv-x86_64@sha256:47575371b63ff20642267f808557d03eb93b4689fb1ed200bb4152bbed869376 55 | tag: manylinux_2_28_x86_64 56 | abi: cp313-cp313 57 | bundles: 58 | ffmpeg: 59 | filename: ffmpeg-n7.1-153-gaeb8631048-linux64-gpl-7.1.tar.xz 60 | url: https://github.com/streamlink/FFmpeg-Builds/releases/download/20250121-1/ffmpeg-n7.1-153-gaeb8631048-linux64-gpl-7.1.tar.xz 61 | sha256: e50ae66b991e3f6afa1fae57fb3e8f6cfc0a9a2b82f68790bf08c3fd165e8a1a 62 | type: tar 63 | sourcedir: ffmpeg-n7.1-153-gaeb8631048-linux64-gpl-7.1 64 | files: 65 | - from: bin/ffmpeg 66 | to: usr/bin/ffmpeg 67 | - from: LICENSE.txt 68 | to: usr/share/licenses/ffmpeg.txt 69 | - from: BUILDINFO.txt 70 | to: usr/share/ffmpeg/buildinfo.txt 71 | dependency_override: 72 | # https://github.com/urllib3/urllib3/blob/2.4.0/pyproject.toml#L44 73 | - brotli>=1.0.9 # optional urllib3 dependency 74 | - zstandard>=0.18.0 # optional urllib3 dependency 75 | dependencies: 76 | attrs: 25.3.0 --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 77 | Brotli: 1.1.0 --hash=sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0 78 | certifi: 2025.4.26 --hash=sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3 79 | charset-normalizer: 3.4.2 --hash=sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c 80 | h11: 0.16.0 --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 81 | idna: 3.10 --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 82 | isodate: 0.7.2 --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 83 | lxml: 5.4.0 --hash=sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982 84 | outcome: 1.3.0.post0 --hash=sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b 85 | pycountry: 24.6.1 --hash=sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f 86 | pycryptodome: 3.23.0 --hash=sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575 87 | PySocks: 1.7.1 --hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 88 | requests: 2.32.3 --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 89 | sniffio: 1.3.1 --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 90 | sortedcontainers: 2.4.0 --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 91 | trio: 0.30.0 --hash=sha256:3bf4f06b8decf8d3cf00af85f40a89824669e2d033bb32469d34840edcfc22a5 92 | trio-websocket: 0.12.2 --hash=sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6 93 | urllib3: 2.4.0 --hash=sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813 94 | websocket-client: 1.8.0 --hash=sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526 95 | wsproto: 1.2.0 --hash=sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736 96 | zstandard: 0.23.0 --hash=sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed 97 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | [[ "${CI}" ]] && [[ "${GITHUB_REPOSITORY}" ]] && [[ "${GITHUB_REF}" =~ ^refs/tags/ ]] && [[ "${RELEASES_API_KEY}" ]] || exit 1 5 | 6 | ROOT=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(readlink -f "${0}")") 7 | FILES=("${@}") 8 | 9 | declare -A DEPS=( 10 | [curl]=curl 11 | [jq]=jq 12 | ) 13 | 14 | # ---- 15 | 16 | SELF=$(basename "$(readlink -f "${0}")") 17 | log() { 18 | echo "[${SELF}] $@" 19 | } 20 | err() { 21 | log >&2 "$@" 22 | exit 1 23 | } 24 | 25 | for dep in "${!DEPS[@]}"; do 26 | command -v "${dep}" 2>&1 >/dev/null || err "Missing dependency: ${DEPS["${dep}"]}" 27 | done 28 | 29 | [[ $# == 0 ]] && err "Missing file(s)" 30 | 31 | # ---- 32 | 33 | TAG="${GITHUB_REF/#refs\/tags\//}" 34 | CURL_OPTIONS=( 35 | -H "Accept: application/vnd.github.v3+json" 36 | -H "User-Agent: ${GITHUB_REPOSITORY}" 37 | -H "Authorization: token ${RELEASES_API_KEY}" 38 | ) 39 | GH_API="https://api.github.com/repos/${GITHUB_REPOSITORY}" 40 | GH_UPLOAD="https://uploads.github.com/repos/${GITHUB_REPOSITORY}" 41 | BODY="${ROOT}/.github/release-body.md" 42 | 43 | 44 | get_release_id() { 45 | curl -fsSL \ 46 | -X GET \ 47 | "${CURL_OPTIONS[@]}" \ 48 | "${GH_API}/releases/tags/${TAG}" \ 49 | | jq -re ".id" 50 | } 51 | 52 | create_release() { 53 | local data="$(jq -cnR \ 54 | --arg tag_name "${TAG}" \ 55 | --arg name "${GITHUB_REPOSITORY} ${TAG}" \ 56 | --arg body "$(cat "${BODY}")" \ 57 | '{ 58 | "tag_name": $tag_name, 59 | "name": $name, 60 | "body": $body 61 | }' 62 | )" 63 | curl -fsSL \ 64 | -X POST \ 65 | "${CURL_OPTIONS[@]}" \ 66 | -d "${data}" \ 67 | "${GH_API}/releases" \ 68 | | jq -re ".id" 69 | } 70 | 71 | upload_assets() { 72 | local release_id="${1}" 73 | for path in "${FILES[@]}"; do 74 | local file=$(basename "${path}") 75 | log "Uploading ${file}" 76 | sha256sum "${path}" 77 | curl -fsSL \ 78 | -X POST \ 79 | "${CURL_OPTIONS[@]}" \ 80 | -H "Content-Type: application/octet-stream" \ 81 | --data-binary "@${path}" \ 82 | "${GH_UPLOAD}/releases/${release_id}/assets?name=${file}" \ 83 | >/dev/null 84 | done 85 | } 86 | 87 | deploy() { 88 | log "Getting release ID for tag ${TAG}" 89 | local release_id=$(get_release_id 2>/dev/null || true) 90 | 91 | if [[ -z "${release_id}" ]]; then 92 | log "Creating new release for tag ${TAG}" 93 | local release_id=$(create_release) 94 | fi 95 | 96 | if [[ -z "${release_id}" ]]; then 97 | err "Missing release ID" 98 | fi 99 | 100 | log "Uploading assets to release ${release_id}" 101 | upload_assets "${release_id}" 102 | 103 | log "Done" 104 | } 105 | 106 | deploy 107 | -------------------------------------------------------------------------------- /get-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck disable=SC2016 3 | 4 | set -euo pipefail 5 | 6 | ARCH="$(uname -m)" 7 | GITREPO="" 8 | GITREF="" 9 | OPT_DEPSPEC=() 10 | 11 | ROOT=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$(readlink -f "${0}")") 12 | CONFIG="${ROOT}/config.yml" 13 | 14 | declare -A DEPS=( 15 | [jq]=jq 16 | [yq]=yq 17 | [docker]=docker 18 | ) 19 | 20 | _OPTS=$(getopt --name "$0" --long 'help,arch:,gitrepo:,gitref:' --options 'help,a:' -- "$@") 21 | eval set -- "${_OPTS}" 22 | unset _OPTS 23 | 24 | 25 | # ---- 26 | 27 | 28 | SELF=$(basename -- "$(readlink -f -- "${0}")") 29 | log() { 30 | echo "[${SELF}]" "$@" 31 | } 32 | err() { 33 | log >&2 "$@" 34 | exit 1 35 | } 36 | 37 | print_help() { 38 | echo "Usage: ${0} [options] [depspec]" 39 | echo 40 | echo "Options:" 41 | echo " -a, --arch Target architecture" 42 | echo " --gitrepo Source" 43 | echo " --gitref Git branch/tag/commit" 44 | exit 0 45 | } 46 | 47 | 48 | # ---- 49 | 50 | 51 | while true; do 52 | case "${1}" in 53 | -h | --help) 54 | print_help 55 | ;; 56 | -a | --arch) 57 | ARCH="${2}" 58 | shift 2 59 | ;; 60 | --gitrepo) 61 | GITREPO="${2}" 62 | shift 2 63 | ;; 64 | --gitref) 65 | GITREF="${2}" 66 | shift 2 67 | ;; 68 | --) 69 | shift 70 | break 71 | ;; 72 | *) 73 | err "Invalid option: ${1}" 74 | ;; 75 | esac 76 | done 77 | 78 | OPT_DEPSPEC+=("${@}") 79 | 80 | 81 | # ---- 82 | 83 | 84 | for dep in "${!DEPS[@]}"; do 85 | command -v "${dep}" >/dev/null 2>&1 || err "Missing dependency: ${DEPS["${dep}"]}" 86 | done 87 | 88 | CONFIGJSON=$(cat "${CONFIG}") 89 | 90 | yq -e --arg a "${ARCH}" '.builds[$a]' >/dev/null <<< "${CONFIGJSON}" \ 91 | || err "Unsupported arch" 92 | 93 | read -r gitrepo gitref \ 94 | < <(yq -r '.git | "\(.repo) \(.ref)"' <<< "${CONFIGJSON}") 95 | read -r image abi \ 96 | < <(yq -r --arg a "${ARCH}" '.builds[$a] | "\(.image) \(.abi)"' <<< "${CONFIGJSON}") 97 | 98 | # shellcheck disable=SC2207 99 | dependency_override=($(yq -r --arg a "${ARCH}" '.builds[$a].dependency_override[]' <<< "${CONFIGJSON}")) 100 | 101 | gitrepo="${GITREPO:-${gitrepo}}" 102 | gitref="${GITREF:-${gitref}}" 103 | 104 | 105 | # ---- 106 | 107 | 108 | get_docker_image() { 109 | log "Getting docker image" 110 | [[ -n "$(docker image ls -q "${image}")" ]] \ 111 | || docker image pull "${image}" 112 | } 113 | 114 | 115 | get_deps() { 116 | log "Finding dependencies (${ARCH} / ${abi}) for ${gitrepo}@${gitref}" 117 | local deps script 118 | deps=("git+${gitrepo}@${gitref}" "${dependency_override[@]}" "${OPT_DEPSPEC[@]}") 119 | script=$(cat <[^=]+)="; "\(.hash):") 140 | | { 141 | key: .metadata.name, 142 | value: "\(.metadata.version) --hash=\(.download_info.archive_info.hash)" 143 | } 144 | ] 145 | | sort_by(.key | ascii_upcase) 146 | | from_entries 147 | | {"dependencies": .} 148 | ' \ 149 | "\${REPORT}" 150 | EOF 151 | ) 152 | 153 | docker run \ 154 | --interactive \ 155 | --rm \ 156 | "${image}" \ 157 | /usr/bin/bash /dev/stdin "${deps[@]}" <<< "${script}" 158 | } 159 | 160 | 161 | get_docker_image 162 | get_deps 163 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | yq >=3.0.0 2 | --------------------------------------------------------------------------------