├── .github ├── Dockerfile └── workflows │ └── CI.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── pyproject.toml ├── src ├── downloader.rs ├── downloader │ ├── hls.rs │ ├── httpflv.rs │ └── util.rs ├── error.rs ├── flv_parser.rs ├── flv_writer.rs ├── lib.rs ├── login.rs ├── main.rs └── uploader.rs └── tests ├── test_download.py └── test_upload.py /.github/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux2010_x86_64 2 | 3 | RUN yum install -y openssl openssl-devel 4 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | linux: 9 | runs-on: ubuntu-latest 10 | # container: quay.io/pypa/manylinux2010_x86_64 11 | steps: 12 | - uses: actions/checkout@v2 13 | # - name: Install rust 14 | # run: curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 15 | # - name: Installing custom dependency 16 | # run: yum install -y openssl openssl-devel 17 | - uses: messense/maturin-action@v1 18 | with: 19 | manylinux: auto 20 | command: build 21 | container: ghcr.io/biliup/manylinux2010 22 | args: --release -o dist 23 | # - name: Build 24 | # run: | 25 | # source $HOME/.cargo/env 26 | # pip3 install maturin 27 | # maturin build --release --manylinux 2010 -o dist 28 | 29 | - name: Upload wheels 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: wheels 33 | path: dist 34 | 35 | windows: 36 | runs-on: windows-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: messense/maturin-action@v1 40 | with: 41 | command: build 42 | args: --release --no-sdist -o dist 43 | - name: Upload wheels 44 | uses: actions/upload-artifact@v2 45 | with: 46 | name: wheels 47 | path: dist 48 | 49 | macos: 50 | runs-on: macos-latest 51 | steps: 52 | - uses: actions/checkout@v2 53 | - uses: messense/maturin-action@v1 54 | with: 55 | command: build 56 | args: --release --no-sdist -o dist --universal2 57 | - name: Upload wheels 58 | uses: actions/upload-artifact@v2 59 | with: 60 | name: wheels 61 | path: dist 62 | 63 | release: 64 | name: Release 65 | runs-on: ubuntu-latest 66 | if: "startsWith(github.ref, 'refs/tags/')" 67 | needs: [ macos, windows, linux ] 68 | steps: 69 | - uses: actions/download-artifact@v2 70 | with: 71 | name: wheels 72 | - name: Publish to PyPI 73 | uses: messense/maturin-action@v1 74 | env: 75 | MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 76 | with: 77 | command: upload 78 | args: --skip-existing * -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | .pytest_cache/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | .venv/ 14 | env/ 15 | .env/ 16 | bin/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | include/ 27 | man/ 28 | venv/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | pip-selfcheck.json 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | 49 | # Mr Developer 50 | .mr.developer.cfg 51 | .project 52 | .pydevproject 53 | 54 | # Rope 55 | .ropeproject 56 | 57 | # Django stuff: 58 | *.log 59 | *.pot 60 | 61 | .DS_Store 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyCharm 67 | .idea/ 68 | 69 | # VSCode 70 | .vscode/ 71 | 72 | # Pyenv 73 | .python-version 74 | cookies.json 75 | *.mp4 76 | *.log.* 77 | *.flv 78 | *.ts 79 | *flv.json 80 | *.part 81 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "adler32" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 16 | 17 | [[package]] 18 | name = "ansi_term" 19 | version = "0.12.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 22 | dependencies = [ 23 | "winapi", 24 | ] 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.58" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" 31 | 32 | [[package]] 33 | name = "async-compression" 34 | version = "0.3.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "345fd392ab01f746c717b1357165b76f0b67a60192007b234058c9045fdcf695" 37 | dependencies = [ 38 | "flate2", 39 | "futures-core", 40 | "memchr", 41 | "pin-project-lite", 42 | "tokio", 43 | ] 44 | 45 | [[package]] 46 | name = "async-trait" 47 | version = "0.1.56" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" 50 | dependencies = [ 51 | "proc-macro2", 52 | "quote", 53 | "syn", 54 | ] 55 | 56 | [[package]] 57 | name = "atty" 58 | version = "0.2.14" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 61 | dependencies = [ 62 | "hermit-abi", 63 | "libc", 64 | "winapi", 65 | ] 66 | 67 | [[package]] 68 | name = "autocfg" 69 | version = "0.1.8" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" 72 | dependencies = [ 73 | "autocfg 1.1.0", 74 | ] 75 | 76 | [[package]] 77 | name = "autocfg" 78 | version = "1.1.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 81 | 82 | [[package]] 83 | name = "base-x" 84 | version = "0.2.11" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 87 | 88 | [[package]] 89 | name = "base64" 90 | version = "0.13.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 93 | 94 | [[package]] 95 | name = "base64ct" 96 | version = "1.1.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "e6b4d9b1225d28d360ec6a231d65af1fd99a2a095154c8040689617290569c5c" 99 | 100 | [[package]] 101 | name = "biliup" 102 | version = "0.1.10" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "cf37c383a27bb1f3e5da204b2537e0314de25c5bc1d044946df25298b86f06c1" 105 | dependencies = [ 106 | "anyhow", 107 | "base64", 108 | "bytes", 109 | "clap", 110 | "cookie 0.15.1", 111 | "cookie_store 0.15.1", 112 | "dialoguer", 113 | "futures", 114 | "glob", 115 | "image", 116 | "indicatif", 117 | "md-5", 118 | "qrcode", 119 | "rand", 120 | "reqwest", 121 | "reqwest-middleware", 122 | "reqwest-retry", 123 | "reqwest_cookie_store", 124 | "rsa", 125 | "serde", 126 | "serde_json", 127 | "serde_urlencoded", 128 | "serde_yaml", 129 | "thiserror", 130 | "tokio", 131 | "tracing", 132 | "tracing-subscriber", 133 | "typed-builder", 134 | "url", 135 | ] 136 | 137 | [[package]] 138 | name = "bitflags" 139 | version = "1.3.2" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 142 | 143 | [[package]] 144 | name = "block-buffer" 145 | version = "0.9.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 148 | dependencies = [ 149 | "generic-array", 150 | ] 151 | 152 | [[package]] 153 | name = "bumpalo" 154 | version = "3.10.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 157 | 158 | [[package]] 159 | name = "bytemuck" 160 | version = "1.10.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a" 163 | 164 | [[package]] 165 | name = "byteorder" 166 | version = "1.4.3" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 169 | 170 | [[package]] 171 | name = "bytes" 172 | version = "1.1.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 175 | 176 | [[package]] 177 | name = "cc" 178 | version = "1.0.73" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 181 | 182 | [[package]] 183 | name = "cfg-if" 184 | version = "1.0.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 187 | 188 | [[package]] 189 | name = "checked_int_cast" 190 | version = "1.0.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" 193 | 194 | [[package]] 195 | name = "chrono" 196 | version = "0.4.19" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 199 | dependencies = [ 200 | "libc", 201 | "num-integer", 202 | "num-traits", 203 | "time 0.1.44", 204 | "winapi", 205 | ] 206 | 207 | [[package]] 208 | name = "clap" 209 | version = "3.2.8" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" 212 | dependencies = [ 213 | "atty", 214 | "bitflags", 215 | "clap_derive", 216 | "clap_lex", 217 | "indexmap", 218 | "once_cell", 219 | "strsim", 220 | "termcolor", 221 | "textwrap", 222 | ] 223 | 224 | [[package]] 225 | name = "clap_derive" 226 | version = "3.2.7" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" 229 | dependencies = [ 230 | "heck", 231 | "proc-macro-error", 232 | "proc-macro2", 233 | "quote", 234 | "syn", 235 | ] 236 | 237 | [[package]] 238 | name = "clap_lex" 239 | version = "0.2.4" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 242 | dependencies = [ 243 | "os_str_bytes", 244 | ] 245 | 246 | [[package]] 247 | name = "color_quant" 248 | version = "1.1.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 251 | 252 | [[package]] 253 | name = "console" 254 | version = "0.15.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 257 | dependencies = [ 258 | "encode_unicode", 259 | "libc", 260 | "once_cell", 261 | "regex", 262 | "terminal_size", 263 | "unicode-width", 264 | "winapi", 265 | ] 266 | 267 | [[package]] 268 | name = "const-oid" 269 | version = "0.6.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" 272 | 273 | [[package]] 274 | name = "const_fn" 275 | version = "0.4.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" 278 | 279 | [[package]] 280 | name = "cookie" 281 | version = "0.15.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" 284 | dependencies = [ 285 | "percent-encoding", 286 | "time 0.2.27", 287 | "version_check", 288 | ] 289 | 290 | [[package]] 291 | name = "cookie" 292 | version = "0.16.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" 295 | dependencies = [ 296 | "percent-encoding", 297 | "time 0.3.11", 298 | "version_check", 299 | ] 300 | 301 | [[package]] 302 | name = "cookie_store" 303 | version = "0.15.1" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "b3f7034c0932dc36f5bd8ec37368d971346809435824f277cb3b8299fc56167c" 306 | dependencies = [ 307 | "cookie 0.15.1", 308 | "idna", 309 | "log", 310 | "publicsuffix", 311 | "serde", 312 | "serde_json", 313 | "time 0.2.27", 314 | "url", 315 | ] 316 | 317 | [[package]] 318 | name = "cookie_store" 319 | version = "0.16.1" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" 322 | dependencies = [ 323 | "cookie 0.16.0", 324 | "idna", 325 | "log", 326 | "publicsuffix", 327 | "serde", 328 | "serde_json", 329 | "time 0.3.11", 330 | "url", 331 | ] 332 | 333 | [[package]] 334 | name = "core-foundation" 335 | version = "0.9.3" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 338 | dependencies = [ 339 | "core-foundation-sys", 340 | "libc", 341 | ] 342 | 343 | [[package]] 344 | name = "core-foundation-sys" 345 | version = "0.8.3" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 348 | 349 | [[package]] 350 | name = "crc32fast" 351 | version = "1.3.2" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 354 | dependencies = [ 355 | "cfg-if", 356 | ] 357 | 358 | [[package]] 359 | name = "crossbeam-channel" 360 | version = "0.5.5" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" 363 | dependencies = [ 364 | "cfg-if", 365 | "crossbeam-utils", 366 | ] 367 | 368 | [[package]] 369 | name = "crossbeam-deque" 370 | version = "0.8.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 373 | dependencies = [ 374 | "cfg-if", 375 | "crossbeam-epoch", 376 | "crossbeam-utils", 377 | ] 378 | 379 | [[package]] 380 | name = "crossbeam-epoch" 381 | version = "0.9.9" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" 384 | dependencies = [ 385 | "autocfg 1.1.0", 386 | "cfg-if", 387 | "crossbeam-utils", 388 | "memoffset", 389 | "once_cell", 390 | "scopeguard", 391 | ] 392 | 393 | [[package]] 394 | name = "crossbeam-utils" 395 | version = "0.8.10" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" 398 | dependencies = [ 399 | "cfg-if", 400 | "once_cell", 401 | ] 402 | 403 | [[package]] 404 | name = "crypto-bigint" 405 | version = "0.2.11" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" 408 | dependencies = [ 409 | "generic-array", 410 | "rand_core", 411 | "subtle", 412 | ] 413 | 414 | [[package]] 415 | name = "deflate" 416 | version = "0.8.6" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 419 | dependencies = [ 420 | "adler32", 421 | "byteorder", 422 | ] 423 | 424 | [[package]] 425 | name = "der" 426 | version = "0.4.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" 429 | dependencies = [ 430 | "const-oid", 431 | "crypto-bigint", 432 | ] 433 | 434 | [[package]] 435 | name = "dialoguer" 436 | version = "0.9.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb" 439 | dependencies = [ 440 | "console", 441 | "lazy_static", 442 | "tempfile", 443 | "zeroize", 444 | ] 445 | 446 | [[package]] 447 | name = "digest" 448 | version = "0.9.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 451 | dependencies = [ 452 | "generic-array", 453 | ] 454 | 455 | [[package]] 456 | name = "discard" 457 | version = "1.0.4" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 460 | 461 | [[package]] 462 | name = "either" 463 | version = "1.7.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" 466 | 467 | [[package]] 468 | name = "encode_unicode" 469 | version = "0.3.6" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 472 | 473 | [[package]] 474 | name = "encoding_rs" 475 | version = "0.8.31" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 478 | dependencies = [ 479 | "cfg-if", 480 | ] 481 | 482 | [[package]] 483 | name = "fastrand" 484 | version = "1.7.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 487 | dependencies = [ 488 | "instant", 489 | ] 490 | 491 | [[package]] 492 | name = "flate2" 493 | version = "1.0.24" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" 496 | dependencies = [ 497 | "crc32fast", 498 | "miniz_oxide 0.5.3", 499 | ] 500 | 501 | [[package]] 502 | name = "fnv" 503 | version = "1.0.7" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 506 | 507 | [[package]] 508 | name = "foreign-types" 509 | version = "0.3.2" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 512 | dependencies = [ 513 | "foreign-types-shared", 514 | ] 515 | 516 | [[package]] 517 | name = "foreign-types-shared" 518 | version = "0.1.1" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 521 | 522 | [[package]] 523 | name = "form_urlencoded" 524 | version = "1.0.1" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 527 | dependencies = [ 528 | "matches", 529 | "percent-encoding", 530 | ] 531 | 532 | [[package]] 533 | name = "futures" 534 | version = "0.3.21" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 537 | dependencies = [ 538 | "futures-channel", 539 | "futures-core", 540 | "futures-executor", 541 | "futures-io", 542 | "futures-sink", 543 | "futures-task", 544 | "futures-util", 545 | ] 546 | 547 | [[package]] 548 | name = "futures-channel" 549 | version = "0.3.21" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 552 | dependencies = [ 553 | "futures-core", 554 | "futures-sink", 555 | ] 556 | 557 | [[package]] 558 | name = "futures-core" 559 | version = "0.3.21" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 562 | 563 | [[package]] 564 | name = "futures-executor" 565 | version = "0.3.21" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 568 | dependencies = [ 569 | "futures-core", 570 | "futures-task", 571 | "futures-util", 572 | ] 573 | 574 | [[package]] 575 | name = "futures-io" 576 | version = "0.3.21" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 579 | 580 | [[package]] 581 | name = "futures-macro" 582 | version = "0.3.21" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 585 | dependencies = [ 586 | "proc-macro2", 587 | "quote", 588 | "syn", 589 | ] 590 | 591 | [[package]] 592 | name = "futures-sink" 593 | version = "0.3.21" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 596 | 597 | [[package]] 598 | name = "futures-task" 599 | version = "0.3.21" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 602 | 603 | [[package]] 604 | name = "futures-util" 605 | version = "0.3.21" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 608 | dependencies = [ 609 | "futures-channel", 610 | "futures-core", 611 | "futures-io", 612 | "futures-macro", 613 | "futures-sink", 614 | "futures-task", 615 | "memchr", 616 | "pin-project-lite", 617 | "pin-utils", 618 | "slab", 619 | ] 620 | 621 | [[package]] 622 | name = "generic-array" 623 | version = "0.14.5" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 626 | dependencies = [ 627 | "typenum", 628 | "version_check", 629 | ] 630 | 631 | [[package]] 632 | name = "getrandom" 633 | version = "0.2.7" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 636 | dependencies = [ 637 | "cfg-if", 638 | "libc", 639 | "wasi 0.11.0+wasi-snapshot-preview1", 640 | ] 641 | 642 | [[package]] 643 | name = "gif" 644 | version = "0.11.4" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" 647 | dependencies = [ 648 | "color_quant", 649 | "weezl", 650 | ] 651 | 652 | [[package]] 653 | name = "glob" 654 | version = "0.3.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 657 | 658 | [[package]] 659 | name = "h2" 660 | version = "0.3.13" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 663 | dependencies = [ 664 | "bytes", 665 | "fnv", 666 | "futures-core", 667 | "futures-sink", 668 | "futures-util", 669 | "http", 670 | "indexmap", 671 | "slab", 672 | "tokio", 673 | "tokio-util", 674 | "tracing", 675 | ] 676 | 677 | [[package]] 678 | name = "hashbrown" 679 | version = "0.11.2" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 682 | 683 | [[package]] 684 | name = "hashbrown" 685 | version = "0.12.2" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" 688 | 689 | [[package]] 690 | name = "heck" 691 | version = "0.4.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 694 | 695 | [[package]] 696 | name = "hermit-abi" 697 | version = "0.1.19" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 700 | dependencies = [ 701 | "libc", 702 | ] 703 | 704 | [[package]] 705 | name = "http" 706 | version = "0.2.8" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 709 | dependencies = [ 710 | "bytes", 711 | "fnv", 712 | "itoa", 713 | ] 714 | 715 | [[package]] 716 | name = "http-body" 717 | version = "0.4.5" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 720 | dependencies = [ 721 | "bytes", 722 | "http", 723 | "pin-project-lite", 724 | ] 725 | 726 | [[package]] 727 | name = "httparse" 728 | version = "1.7.1" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 731 | 732 | [[package]] 733 | name = "httpdate" 734 | version = "1.0.2" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 737 | 738 | [[package]] 739 | name = "hyper" 740 | version = "0.14.20" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" 743 | dependencies = [ 744 | "bytes", 745 | "futures-channel", 746 | "futures-core", 747 | "futures-util", 748 | "h2", 749 | "http", 750 | "http-body", 751 | "httparse", 752 | "httpdate", 753 | "itoa", 754 | "pin-project-lite", 755 | "socket2", 756 | "tokio", 757 | "tower-service", 758 | "tracing", 759 | "want", 760 | ] 761 | 762 | [[package]] 763 | name = "hyper-rustls" 764 | version = "0.23.0" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" 767 | dependencies = [ 768 | "http", 769 | "hyper", 770 | "rustls", 771 | "tokio", 772 | "tokio-rustls", 773 | ] 774 | 775 | [[package]] 776 | name = "hyper-tls" 777 | version = "0.5.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 780 | dependencies = [ 781 | "bytes", 782 | "hyper", 783 | "native-tls", 784 | "tokio", 785 | "tokio-native-tls", 786 | ] 787 | 788 | [[package]] 789 | name = "idna" 790 | version = "0.2.3" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 793 | dependencies = [ 794 | "matches", 795 | "unicode-bidi", 796 | "unicode-normalization", 797 | ] 798 | 799 | [[package]] 800 | name = "image" 801 | version = "0.23.14" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 804 | dependencies = [ 805 | "bytemuck", 806 | "byteorder", 807 | "color_quant", 808 | "gif", 809 | "jpeg-decoder", 810 | "num-iter", 811 | "num-rational", 812 | "num-traits", 813 | "png", 814 | "scoped_threadpool", 815 | "tiff", 816 | ] 817 | 818 | [[package]] 819 | name = "indexmap" 820 | version = "1.9.1" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 823 | dependencies = [ 824 | "autocfg 1.1.0", 825 | "hashbrown 0.12.2", 826 | ] 827 | 828 | [[package]] 829 | name = "indicatif" 830 | version = "0.17.0-rc.11" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "4017d0ce94b8e91e29d2c78ed891e57e5ec3dc4371820a9d96abab4af09eb8ad" 833 | dependencies = [ 834 | "console", 835 | "number_prefix", 836 | "unicode-width", 837 | ] 838 | 839 | [[package]] 840 | name = "indoc" 841 | version = "1.0.6" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" 844 | 845 | [[package]] 846 | name = "instant" 847 | version = "0.1.12" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 850 | dependencies = [ 851 | "cfg-if", 852 | ] 853 | 854 | [[package]] 855 | name = "ipnet" 856 | version = "2.5.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 859 | 860 | [[package]] 861 | name = "itoa" 862 | version = "1.0.2" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 865 | 866 | [[package]] 867 | name = "jpeg-decoder" 868 | version = "0.1.22" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 871 | dependencies = [ 872 | "rayon", 873 | ] 874 | 875 | [[package]] 876 | name = "js-sys" 877 | version = "0.3.58" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" 880 | dependencies = [ 881 | "wasm-bindgen", 882 | ] 883 | 884 | [[package]] 885 | name = "lazy_static" 886 | version = "1.4.0" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 889 | dependencies = [ 890 | "spin", 891 | ] 892 | 893 | [[package]] 894 | name = "libc" 895 | version = "0.2.126" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 898 | 899 | [[package]] 900 | name = "libm" 901 | version = "0.2.2" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" 904 | 905 | [[package]] 906 | name = "linked-hash-map" 907 | version = "0.5.6" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 910 | 911 | [[package]] 912 | name = "lock_api" 913 | version = "0.4.7" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 916 | dependencies = [ 917 | "autocfg 1.1.0", 918 | "scopeguard", 919 | ] 920 | 921 | [[package]] 922 | name = "log" 923 | version = "0.4.17" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 926 | dependencies = [ 927 | "cfg-if", 928 | ] 929 | 930 | [[package]] 931 | name = "m3u8-rs" 932 | version = "4.0.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "c27f4a86278e7d10f93c8c97f0191f85a071a45fa4245c261539465729c6d947" 935 | dependencies = [ 936 | "nom", 937 | ] 938 | 939 | [[package]] 940 | name = "matches" 941 | version = "0.1.9" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 944 | 945 | [[package]] 946 | name = "md-5" 947 | version = "0.9.1" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" 950 | dependencies = [ 951 | "block-buffer", 952 | "digest", 953 | "opaque-debug", 954 | ] 955 | 956 | [[package]] 957 | name = "memchr" 958 | version = "2.5.0" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 961 | 962 | [[package]] 963 | name = "memoffset" 964 | version = "0.6.5" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 967 | dependencies = [ 968 | "autocfg 1.1.0", 969 | ] 970 | 971 | [[package]] 972 | name = "mime" 973 | version = "0.3.16" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 976 | 977 | [[package]] 978 | name = "mime_guess" 979 | version = "2.0.4" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 982 | dependencies = [ 983 | "mime", 984 | "unicase", 985 | ] 986 | 987 | [[package]] 988 | name = "minimal-lexical" 989 | version = "0.2.1" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 992 | 993 | [[package]] 994 | name = "miniz_oxide" 995 | version = "0.3.7" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 998 | dependencies = [ 999 | "adler32", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "miniz_oxide" 1004 | version = "0.4.4" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 1007 | dependencies = [ 1008 | "adler", 1009 | "autocfg 1.1.0", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "miniz_oxide" 1014 | version = "0.5.3" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 1017 | dependencies = [ 1018 | "adler", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "mio" 1023 | version = "0.8.4" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 1026 | dependencies = [ 1027 | "libc", 1028 | "log", 1029 | "wasi 0.11.0+wasi-snapshot-preview1", 1030 | "windows-sys", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "native-tls" 1035 | version = "0.2.10" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" 1038 | dependencies = [ 1039 | "lazy_static", 1040 | "libc", 1041 | "log", 1042 | "openssl", 1043 | "openssl-probe", 1044 | "openssl-sys", 1045 | "schannel", 1046 | "security-framework", 1047 | "security-framework-sys", 1048 | "tempfile", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "nom" 1053 | version = "7.1.1" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 1056 | dependencies = [ 1057 | "memchr", 1058 | "minimal-lexical", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "num-bigint-dig" 1063 | version = "0.7.0" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480" 1066 | dependencies = [ 1067 | "autocfg 0.1.8", 1068 | "byteorder", 1069 | "lazy_static", 1070 | "libm", 1071 | "num-integer", 1072 | "num-iter", 1073 | "num-traits", 1074 | "rand", 1075 | "smallvec", 1076 | "zeroize", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "num-integer" 1081 | version = "0.1.45" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 1084 | dependencies = [ 1085 | "autocfg 1.1.0", 1086 | "num-traits", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "num-iter" 1091 | version = "0.1.43" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 1094 | dependencies = [ 1095 | "autocfg 1.1.0", 1096 | "num-integer", 1097 | "num-traits", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "num-rational" 1102 | version = "0.3.2" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 1105 | dependencies = [ 1106 | "autocfg 1.1.0", 1107 | "num-integer", 1108 | "num-traits", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "num-traits" 1113 | version = "0.2.15" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 1116 | dependencies = [ 1117 | "autocfg 1.1.0", 1118 | "libm", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "num_cpus" 1123 | version = "1.13.1" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 1126 | dependencies = [ 1127 | "hermit-abi", 1128 | "libc", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "num_threads" 1133 | version = "0.1.6" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 1136 | dependencies = [ 1137 | "libc", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "number_prefix" 1142 | version = "0.4.0" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 1145 | 1146 | [[package]] 1147 | name = "once_cell" 1148 | version = "1.13.0" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 1151 | 1152 | [[package]] 1153 | name = "opaque-debug" 1154 | version = "0.3.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1157 | 1158 | [[package]] 1159 | name = "openssl" 1160 | version = "0.10.41" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" 1163 | dependencies = [ 1164 | "bitflags", 1165 | "cfg-if", 1166 | "foreign-types", 1167 | "libc", 1168 | "once_cell", 1169 | "openssl-macros", 1170 | "openssl-sys", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "openssl-macros" 1175 | version = "0.1.0" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 1178 | dependencies = [ 1179 | "proc-macro2", 1180 | "quote", 1181 | "syn", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "openssl-probe" 1186 | version = "0.1.5" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1189 | 1190 | [[package]] 1191 | name = "openssl-sys" 1192 | version = "0.9.75" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" 1195 | dependencies = [ 1196 | "autocfg 1.1.0", 1197 | "cc", 1198 | "libc", 1199 | "pkg-config", 1200 | "vcpkg", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "os_str_bytes" 1205 | version = "6.1.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" 1208 | 1209 | [[package]] 1210 | name = "parking_lot" 1211 | version = "0.12.1" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1214 | dependencies = [ 1215 | "lock_api", 1216 | "parking_lot_core", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "parking_lot_core" 1221 | version = "0.9.3" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 1224 | dependencies = [ 1225 | "cfg-if", 1226 | "libc", 1227 | "redox_syscall", 1228 | "smallvec", 1229 | "windows-sys", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "pem-rfc7468" 1234 | version = "0.2.4" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "84e93a3b1cc0510b03020f33f21e62acdde3dcaef432edc95bea377fbd4c2cd4" 1237 | dependencies = [ 1238 | "base64ct", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "percent-encoding" 1243 | version = "2.1.0" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1246 | 1247 | [[package]] 1248 | name = "pin-project-lite" 1249 | version = "0.2.9" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1252 | 1253 | [[package]] 1254 | name = "pin-utils" 1255 | version = "0.1.0" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1258 | 1259 | [[package]] 1260 | name = "pkcs1" 1261 | version = "0.2.4" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "116bee8279d783c0cf370efa1a94632f2108e5ef0bb32df31f051647810a4e2c" 1264 | dependencies = [ 1265 | "der", 1266 | "pem-rfc7468", 1267 | "zeroize", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "pkcs8" 1272 | version = "0.7.6" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" 1275 | dependencies = [ 1276 | "der", 1277 | "pem-rfc7468", 1278 | "pkcs1", 1279 | "spki", 1280 | "zeroize", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "pkg-config" 1285 | version = "0.3.25" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 1288 | 1289 | [[package]] 1290 | name = "png" 1291 | version = "0.16.8" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" 1294 | dependencies = [ 1295 | "bitflags", 1296 | "crc32fast", 1297 | "deflate", 1298 | "miniz_oxide 0.3.7", 1299 | ] 1300 | 1301 | [[package]] 1302 | name = "ppv-lite86" 1303 | version = "0.2.16" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 1306 | 1307 | [[package]] 1308 | name = "proc-macro-error" 1309 | version = "1.0.4" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1312 | dependencies = [ 1313 | "proc-macro-error-attr", 1314 | "proc-macro2", 1315 | "quote", 1316 | "syn", 1317 | "version_check", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "proc-macro-error-attr" 1322 | version = "1.0.4" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1325 | dependencies = [ 1326 | "proc-macro2", 1327 | "quote", 1328 | "version_check", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "proc-macro-hack" 1333 | version = "0.5.19" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 1336 | 1337 | [[package]] 1338 | name = "proc-macro2" 1339 | version = "1.0.40" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 1342 | dependencies = [ 1343 | "unicode-ident", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "psl-types" 1348 | version = "2.0.10" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "e8eda7c62d9ecaafdf8b62374c006de0adf61666ae96a96ba74a37134aa4e470" 1351 | 1352 | [[package]] 1353 | name = "publicsuffix" 1354 | version = "2.1.1" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "292972edad6bbecc137ab84c5e36421a4a6c979ea31d3cc73540dd04315b33e1" 1357 | dependencies = [ 1358 | "byteorder", 1359 | "hashbrown 0.11.2", 1360 | "idna", 1361 | "psl-types", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "pyo3" 1366 | version = "0.16.5" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" 1369 | dependencies = [ 1370 | "cfg-if", 1371 | "indoc", 1372 | "libc", 1373 | "parking_lot", 1374 | "pyo3-build-config", 1375 | "pyo3-ffi", 1376 | "pyo3-macros", 1377 | "unindent", 1378 | ] 1379 | 1380 | [[package]] 1381 | name = "pyo3-build-config" 1382 | version = "0.16.5" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" 1385 | dependencies = [ 1386 | "once_cell", 1387 | "target-lexicon", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "pyo3-ffi" 1392 | version = "0.16.5" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" 1395 | dependencies = [ 1396 | "libc", 1397 | "pyo3-build-config", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "pyo3-macros" 1402 | version = "0.16.5" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" 1405 | dependencies = [ 1406 | "proc-macro2", 1407 | "pyo3-macros-backend", 1408 | "quote", 1409 | "syn", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "pyo3-macros-backend" 1414 | version = "0.16.5" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" 1417 | dependencies = [ 1418 | "proc-macro2", 1419 | "quote", 1420 | "syn", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "qrcode" 1425 | version = "0.12.0" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" 1428 | dependencies = [ 1429 | "checked_int_cast", 1430 | "image", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "quote" 1435 | version = "1.0.20" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 1438 | dependencies = [ 1439 | "proc-macro2", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "rand" 1444 | version = "0.8.5" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1447 | dependencies = [ 1448 | "libc", 1449 | "rand_chacha", 1450 | "rand_core", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "rand_chacha" 1455 | version = "0.3.1" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1458 | dependencies = [ 1459 | "ppv-lite86", 1460 | "rand_core", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "rand_core" 1465 | version = "0.6.3" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1468 | dependencies = [ 1469 | "getrandom", 1470 | ] 1471 | 1472 | [[package]] 1473 | name = "rayon" 1474 | version = "1.5.3" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" 1477 | dependencies = [ 1478 | "autocfg 1.1.0", 1479 | "crossbeam-deque", 1480 | "either", 1481 | "rayon-core", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "rayon-core" 1486 | version = "1.9.3" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" 1489 | dependencies = [ 1490 | "crossbeam-channel", 1491 | "crossbeam-deque", 1492 | "crossbeam-utils", 1493 | "num_cpus", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "redox_syscall" 1498 | version = "0.2.13" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 1501 | dependencies = [ 1502 | "bitflags", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "regex" 1507 | version = "1.6.0" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 1510 | dependencies = [ 1511 | "regex-syntax", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "regex-syntax" 1516 | version = "0.6.27" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 1519 | 1520 | [[package]] 1521 | name = "remove_dir_all" 1522 | version = "0.5.3" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1525 | dependencies = [ 1526 | "winapi", 1527 | ] 1528 | 1529 | [[package]] 1530 | name = "reqwest" 1531 | version = "0.11.11" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" 1534 | dependencies = [ 1535 | "async-compression", 1536 | "base64", 1537 | "bytes", 1538 | "cookie 0.16.0", 1539 | "cookie_store 0.16.1", 1540 | "encoding_rs", 1541 | "futures-core", 1542 | "futures-util", 1543 | "h2", 1544 | "http", 1545 | "http-body", 1546 | "hyper", 1547 | "hyper-rustls", 1548 | "hyper-tls", 1549 | "ipnet", 1550 | "js-sys", 1551 | "lazy_static", 1552 | "log", 1553 | "mime", 1554 | "mime_guess", 1555 | "native-tls", 1556 | "percent-encoding", 1557 | "pin-project-lite", 1558 | "proc-macro-hack", 1559 | "rustls", 1560 | "rustls-pemfile", 1561 | "serde", 1562 | "serde_json", 1563 | "serde_urlencoded", 1564 | "tokio", 1565 | "tokio-native-tls", 1566 | "tokio-rustls", 1567 | "tokio-util", 1568 | "tower-service", 1569 | "url", 1570 | "wasm-bindgen", 1571 | "wasm-bindgen-futures", 1572 | "web-sys", 1573 | "webpki-roots", 1574 | "winreg", 1575 | ] 1576 | 1577 | [[package]] 1578 | name = "reqwest-middleware" 1579 | version = "0.1.6" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "69539cea4148dce683bec9dc95be3f0397a9bb2c248a49c8296a9d21659a8cdd" 1582 | dependencies = [ 1583 | "anyhow", 1584 | "async-trait", 1585 | "futures", 1586 | "http", 1587 | "reqwest", 1588 | "serde", 1589 | "task-local-extensions", 1590 | "thiserror", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "reqwest-retry" 1595 | version = "0.1.5" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "ce246a729eaa6aff5e215aee42845bf5fed9893cc6cd51aeeb712f34e04dd9f3" 1598 | dependencies = [ 1599 | "anyhow", 1600 | "async-trait", 1601 | "chrono", 1602 | "futures", 1603 | "http", 1604 | "hyper", 1605 | "reqwest", 1606 | "reqwest-middleware", 1607 | "retry-policies", 1608 | "task-local-extensions", 1609 | "tokio", 1610 | "tracing", 1611 | ] 1612 | 1613 | [[package]] 1614 | name = "reqwest_cookie_store" 1615 | version = "0.2.0" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "48cbf3a2aeeeaa6d10a76db86068c3560320f6265c3a0e2642e0538e11c2d143" 1618 | dependencies = [ 1619 | "bytes", 1620 | "cookie 0.15.1", 1621 | "cookie_store 0.15.1", 1622 | "reqwest", 1623 | "url", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "retry-policies" 1628 | version = "0.1.1" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "47f9e19b18c6cdd796cc70aea8a9ea5ee7b813be611c6589e3624fcdbfd05f9d" 1631 | dependencies = [ 1632 | "anyhow", 1633 | "chrono", 1634 | "rand", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "ring" 1639 | version = "0.16.20" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1642 | dependencies = [ 1643 | "cc", 1644 | "libc", 1645 | "once_cell", 1646 | "spin", 1647 | "untrusted", 1648 | "web-sys", 1649 | "winapi", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "rsa" 1654 | version = "0.5.0" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d" 1657 | dependencies = [ 1658 | "byteorder", 1659 | "digest", 1660 | "lazy_static", 1661 | "num-bigint-dig", 1662 | "num-integer", 1663 | "num-iter", 1664 | "num-traits", 1665 | "pkcs1", 1666 | "pkcs8", 1667 | "rand", 1668 | "subtle", 1669 | "zeroize", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "rustc_version" 1674 | version = "0.2.3" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1677 | dependencies = [ 1678 | "semver", 1679 | ] 1680 | 1681 | [[package]] 1682 | name = "rustls" 1683 | version = "0.20.6" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" 1686 | dependencies = [ 1687 | "log", 1688 | "ring", 1689 | "sct", 1690 | "webpki", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "rustls-pemfile" 1695 | version = "1.0.0" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" 1698 | dependencies = [ 1699 | "base64", 1700 | ] 1701 | 1702 | [[package]] 1703 | name = "ryu" 1704 | version = "1.0.10" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 1707 | 1708 | [[package]] 1709 | name = "schannel" 1710 | version = "0.1.20" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 1713 | dependencies = [ 1714 | "lazy_static", 1715 | "windows-sys", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "scoped_threadpool" 1720 | version = "0.1.9" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 1723 | 1724 | [[package]] 1725 | name = "scopeguard" 1726 | version = "1.1.0" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1729 | 1730 | [[package]] 1731 | name = "sct" 1732 | version = "0.7.0" 1733 | source = "registry+https://github.com/rust-lang/crates.io-index" 1734 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 1735 | dependencies = [ 1736 | "ring", 1737 | "untrusted", 1738 | ] 1739 | 1740 | [[package]] 1741 | name = "security-framework" 1742 | version = "2.6.1" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 1745 | dependencies = [ 1746 | "bitflags", 1747 | "core-foundation", 1748 | "core-foundation-sys", 1749 | "libc", 1750 | "security-framework-sys", 1751 | ] 1752 | 1753 | [[package]] 1754 | name = "security-framework-sys" 1755 | version = "2.6.1" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1758 | dependencies = [ 1759 | "core-foundation-sys", 1760 | "libc", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "semver" 1765 | version = "0.9.0" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1768 | dependencies = [ 1769 | "semver-parser", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "semver-parser" 1774 | version = "0.7.0" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1777 | 1778 | [[package]] 1779 | name = "serde" 1780 | version = "1.0.138" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" 1783 | dependencies = [ 1784 | "serde_derive", 1785 | ] 1786 | 1787 | [[package]] 1788 | name = "serde_derive" 1789 | version = "1.0.138" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" 1792 | dependencies = [ 1793 | "proc-macro2", 1794 | "quote", 1795 | "syn", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "serde_json" 1800 | version = "1.0.82" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" 1803 | dependencies = [ 1804 | "itoa", 1805 | "ryu", 1806 | "serde", 1807 | ] 1808 | 1809 | [[package]] 1810 | name = "serde_urlencoded" 1811 | version = "0.7.1" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1814 | dependencies = [ 1815 | "form_urlencoded", 1816 | "itoa", 1817 | "ryu", 1818 | "serde", 1819 | ] 1820 | 1821 | [[package]] 1822 | name = "serde_yaml" 1823 | version = "0.8.25" 1824 | source = "registry+https://github.com/rust-lang/crates.io-index" 1825 | checksum = "1ec0091e1f5aa338283ce049bd9dfefd55e1f168ac233e85c1ffe0038fb48cbe" 1826 | dependencies = [ 1827 | "indexmap", 1828 | "ryu", 1829 | "serde", 1830 | "yaml-rust", 1831 | ] 1832 | 1833 | [[package]] 1834 | name = "sha1" 1835 | version = "0.6.1" 1836 | source = "registry+https://github.com/rust-lang/crates.io-index" 1837 | checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" 1838 | dependencies = [ 1839 | "sha1_smol", 1840 | ] 1841 | 1842 | [[package]] 1843 | name = "sha1_smol" 1844 | version = "1.0.0" 1845 | source = "registry+https://github.com/rust-lang/crates.io-index" 1846 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 1847 | 1848 | [[package]] 1849 | name = "sharded-slab" 1850 | version = "0.1.4" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1853 | dependencies = [ 1854 | "lazy_static", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "signal-hook-registry" 1859 | version = "1.4.0" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1862 | dependencies = [ 1863 | "libc", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "slab" 1868 | version = "0.4.6" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" 1871 | 1872 | [[package]] 1873 | name = "smallvec" 1874 | version = "1.9.0" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 1877 | 1878 | [[package]] 1879 | name = "socket2" 1880 | version = "0.4.4" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1883 | dependencies = [ 1884 | "libc", 1885 | "winapi", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "spin" 1890 | version = "0.5.2" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1893 | 1894 | [[package]] 1895 | name = "spki" 1896 | version = "0.4.1" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" 1899 | dependencies = [ 1900 | "der", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "standback" 1905 | version = "0.2.17" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" 1908 | dependencies = [ 1909 | "version_check", 1910 | ] 1911 | 1912 | [[package]] 1913 | name = "stdweb" 1914 | version = "0.4.20" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 1917 | dependencies = [ 1918 | "discard", 1919 | "rustc_version", 1920 | "stdweb-derive", 1921 | "stdweb-internal-macros", 1922 | "stdweb-internal-runtime", 1923 | "wasm-bindgen", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "stdweb-derive" 1928 | version = "0.5.3" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 1931 | dependencies = [ 1932 | "proc-macro2", 1933 | "quote", 1934 | "serde", 1935 | "serde_derive", 1936 | "syn", 1937 | ] 1938 | 1939 | [[package]] 1940 | name = "stdweb-internal-macros" 1941 | version = "0.2.9" 1942 | source = "registry+https://github.com/rust-lang/crates.io-index" 1943 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 1944 | dependencies = [ 1945 | "base-x", 1946 | "proc-macro2", 1947 | "quote", 1948 | "serde", 1949 | "serde_derive", 1950 | "serde_json", 1951 | "sha1", 1952 | "syn", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "stdweb-internal-runtime" 1957 | version = "0.1.5" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 1960 | 1961 | [[package]] 1962 | name = "stream-gears" 1963 | version = "0.1.11" 1964 | dependencies = [ 1965 | "anyhow", 1966 | "biliup", 1967 | "byteorder", 1968 | "bytes", 1969 | "chrono", 1970 | "futures", 1971 | "m3u8-rs", 1972 | "nom", 1973 | "pyo3", 1974 | "reqwest", 1975 | "serde", 1976 | "serde_json", 1977 | "thiserror", 1978 | "tokio", 1979 | "tracing", 1980 | "tracing-appender", 1981 | "tracing-subscriber", 1982 | "url", 1983 | ] 1984 | 1985 | [[package]] 1986 | name = "strsim" 1987 | version = "0.10.0" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1990 | 1991 | [[package]] 1992 | name = "subtle" 1993 | version = "2.4.1" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1996 | 1997 | [[package]] 1998 | name = "syn" 1999 | version = "1.0.98" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 2002 | dependencies = [ 2003 | "proc-macro2", 2004 | "quote", 2005 | "unicode-ident", 2006 | ] 2007 | 2008 | [[package]] 2009 | name = "synstructure" 2010 | version = "0.12.6" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 2013 | dependencies = [ 2014 | "proc-macro2", 2015 | "quote", 2016 | "syn", 2017 | "unicode-xid", 2018 | ] 2019 | 2020 | [[package]] 2021 | name = "target-lexicon" 2022 | version = "0.12.4" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" 2025 | 2026 | [[package]] 2027 | name = "task-local-extensions" 2028 | version = "0.1.1" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "36794203e10c86e5998179e260869d156e0674f02d5451b4a3fb9fd86d02aaab" 2031 | dependencies = [ 2032 | "tokio", 2033 | ] 2034 | 2035 | [[package]] 2036 | name = "tempfile" 2037 | version = "3.3.0" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 2040 | dependencies = [ 2041 | "cfg-if", 2042 | "fastrand", 2043 | "libc", 2044 | "redox_syscall", 2045 | "remove_dir_all", 2046 | "winapi", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "termcolor" 2051 | version = "1.1.3" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 2054 | dependencies = [ 2055 | "winapi-util", 2056 | ] 2057 | 2058 | [[package]] 2059 | name = "terminal_size" 2060 | version = "0.1.17" 2061 | source = "registry+https://github.com/rust-lang/crates.io-index" 2062 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 2063 | dependencies = [ 2064 | "libc", 2065 | "winapi", 2066 | ] 2067 | 2068 | [[package]] 2069 | name = "textwrap" 2070 | version = "0.15.0" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 2073 | 2074 | [[package]] 2075 | name = "thiserror" 2076 | version = "1.0.31" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 2079 | dependencies = [ 2080 | "thiserror-impl", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "thiserror-impl" 2085 | version = "1.0.31" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 2088 | dependencies = [ 2089 | "proc-macro2", 2090 | "quote", 2091 | "syn", 2092 | ] 2093 | 2094 | [[package]] 2095 | name = "thread_local" 2096 | version = "1.1.4" 2097 | source = "registry+https://github.com/rust-lang/crates.io-index" 2098 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 2099 | dependencies = [ 2100 | "once_cell", 2101 | ] 2102 | 2103 | [[package]] 2104 | name = "tiff" 2105 | version = "0.6.1" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" 2108 | dependencies = [ 2109 | "jpeg-decoder", 2110 | "miniz_oxide 0.4.4", 2111 | "weezl", 2112 | ] 2113 | 2114 | [[package]] 2115 | name = "time" 2116 | version = "0.1.44" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 2119 | dependencies = [ 2120 | "libc", 2121 | "wasi 0.10.0+wasi-snapshot-preview1", 2122 | "winapi", 2123 | ] 2124 | 2125 | [[package]] 2126 | name = "time" 2127 | version = "0.2.27" 2128 | source = "registry+https://github.com/rust-lang/crates.io-index" 2129 | checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" 2130 | dependencies = [ 2131 | "const_fn", 2132 | "libc", 2133 | "standback", 2134 | "stdweb", 2135 | "time-macros 0.1.1", 2136 | "version_check", 2137 | "winapi", 2138 | ] 2139 | 2140 | [[package]] 2141 | name = "time" 2142 | version = "0.3.11" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" 2145 | dependencies = [ 2146 | "itoa", 2147 | "libc", 2148 | "num_threads", 2149 | "time-macros 0.2.4", 2150 | ] 2151 | 2152 | [[package]] 2153 | name = "time-macros" 2154 | version = "0.1.1" 2155 | source = "registry+https://github.com/rust-lang/crates.io-index" 2156 | checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" 2157 | dependencies = [ 2158 | "proc-macro-hack", 2159 | "time-macros-impl", 2160 | ] 2161 | 2162 | [[package]] 2163 | name = "time-macros" 2164 | version = "0.2.4" 2165 | source = "registry+https://github.com/rust-lang/crates.io-index" 2166 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 2167 | 2168 | [[package]] 2169 | name = "time-macros-impl" 2170 | version = "0.1.2" 2171 | source = "registry+https://github.com/rust-lang/crates.io-index" 2172 | checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" 2173 | dependencies = [ 2174 | "proc-macro-hack", 2175 | "proc-macro2", 2176 | "quote", 2177 | "standback", 2178 | "syn", 2179 | ] 2180 | 2181 | [[package]] 2182 | name = "tinyvec" 2183 | version = "1.6.0" 2184 | source = "registry+https://github.com/rust-lang/crates.io-index" 2185 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 2186 | dependencies = [ 2187 | "tinyvec_macros", 2188 | ] 2189 | 2190 | [[package]] 2191 | name = "tinyvec_macros" 2192 | version = "0.1.0" 2193 | source = "registry+https://github.com/rust-lang/crates.io-index" 2194 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 2195 | 2196 | [[package]] 2197 | name = "tokio" 2198 | version = "1.19.2" 2199 | source = "registry+https://github.com/rust-lang/crates.io-index" 2200 | checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" 2201 | dependencies = [ 2202 | "bytes", 2203 | "libc", 2204 | "memchr", 2205 | "mio", 2206 | "num_cpus", 2207 | "once_cell", 2208 | "parking_lot", 2209 | "pin-project-lite", 2210 | "signal-hook-registry", 2211 | "socket2", 2212 | "tokio-macros", 2213 | "winapi", 2214 | ] 2215 | 2216 | [[package]] 2217 | name = "tokio-macros" 2218 | version = "1.8.0" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 2221 | dependencies = [ 2222 | "proc-macro2", 2223 | "quote", 2224 | "syn", 2225 | ] 2226 | 2227 | [[package]] 2228 | name = "tokio-native-tls" 2229 | version = "0.3.0" 2230 | source = "registry+https://github.com/rust-lang/crates.io-index" 2231 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 2232 | dependencies = [ 2233 | "native-tls", 2234 | "tokio", 2235 | ] 2236 | 2237 | [[package]] 2238 | name = "tokio-rustls" 2239 | version = "0.23.4" 2240 | source = "registry+https://github.com/rust-lang/crates.io-index" 2241 | checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 2242 | dependencies = [ 2243 | "rustls", 2244 | "tokio", 2245 | "webpki", 2246 | ] 2247 | 2248 | [[package]] 2249 | name = "tokio-util" 2250 | version = "0.7.3" 2251 | source = "registry+https://github.com/rust-lang/crates.io-index" 2252 | checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 2253 | dependencies = [ 2254 | "bytes", 2255 | "futures-core", 2256 | "futures-sink", 2257 | "pin-project-lite", 2258 | "tokio", 2259 | "tracing", 2260 | ] 2261 | 2262 | [[package]] 2263 | name = "tower-service" 2264 | version = "0.3.2" 2265 | source = "registry+https://github.com/rust-lang/crates.io-index" 2266 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 2267 | 2268 | [[package]] 2269 | name = "tracing" 2270 | version = "0.1.35" 2271 | source = "registry+https://github.com/rust-lang/crates.io-index" 2272 | checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" 2273 | dependencies = [ 2274 | "cfg-if", 2275 | "pin-project-lite", 2276 | "tracing-attributes", 2277 | "tracing-core", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "tracing-appender" 2282 | version = "0.2.2" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" 2285 | dependencies = [ 2286 | "crossbeam-channel", 2287 | "time 0.3.11", 2288 | "tracing-subscriber", 2289 | ] 2290 | 2291 | [[package]] 2292 | name = "tracing-attributes" 2293 | version = "0.1.22" 2294 | source = "registry+https://github.com/rust-lang/crates.io-index" 2295 | checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" 2296 | dependencies = [ 2297 | "proc-macro2", 2298 | "quote", 2299 | "syn", 2300 | ] 2301 | 2302 | [[package]] 2303 | name = "tracing-core" 2304 | version = "0.1.28" 2305 | source = "registry+https://github.com/rust-lang/crates.io-index" 2306 | checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" 2307 | dependencies = [ 2308 | "once_cell", 2309 | "valuable", 2310 | ] 2311 | 2312 | [[package]] 2313 | name = "tracing-log" 2314 | version = "0.1.3" 2315 | source = "registry+https://github.com/rust-lang/crates.io-index" 2316 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 2317 | dependencies = [ 2318 | "lazy_static", 2319 | "log", 2320 | "tracing-core", 2321 | ] 2322 | 2323 | [[package]] 2324 | name = "tracing-subscriber" 2325 | version = "0.3.14" 2326 | source = "registry+https://github.com/rust-lang/crates.io-index" 2327 | checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" 2328 | dependencies = [ 2329 | "ansi_term", 2330 | "sharded-slab", 2331 | "smallvec", 2332 | "thread_local", 2333 | "tracing-core", 2334 | "tracing-log", 2335 | ] 2336 | 2337 | [[package]] 2338 | name = "try-lock" 2339 | version = "0.2.3" 2340 | source = "registry+https://github.com/rust-lang/crates.io-index" 2341 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 2342 | 2343 | [[package]] 2344 | name = "typed-builder" 2345 | version = "0.9.1" 2346 | source = "registry+https://github.com/rust-lang/crates.io-index" 2347 | checksum = "a46ee5bd706ff79131be9c94e7edcb82b703c487766a114434e5790361cf08c5" 2348 | dependencies = [ 2349 | "proc-macro2", 2350 | "quote", 2351 | "syn", 2352 | ] 2353 | 2354 | [[package]] 2355 | name = "typenum" 2356 | version = "1.15.0" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 2359 | 2360 | [[package]] 2361 | name = "unicase" 2362 | version = "2.6.0" 2363 | source = "registry+https://github.com/rust-lang/crates.io-index" 2364 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 2365 | dependencies = [ 2366 | "version_check", 2367 | ] 2368 | 2369 | [[package]] 2370 | name = "unicode-bidi" 2371 | version = "0.3.8" 2372 | source = "registry+https://github.com/rust-lang/crates.io-index" 2373 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 2374 | 2375 | [[package]] 2376 | name = "unicode-ident" 2377 | version = "1.0.1" 2378 | source = "registry+https://github.com/rust-lang/crates.io-index" 2379 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 2380 | 2381 | [[package]] 2382 | name = "unicode-normalization" 2383 | version = "0.1.21" 2384 | source = "registry+https://github.com/rust-lang/crates.io-index" 2385 | checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" 2386 | dependencies = [ 2387 | "tinyvec", 2388 | ] 2389 | 2390 | [[package]] 2391 | name = "unicode-width" 2392 | version = "0.1.9" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 2395 | 2396 | [[package]] 2397 | name = "unicode-xid" 2398 | version = "0.2.3" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" 2401 | 2402 | [[package]] 2403 | name = "unindent" 2404 | version = "0.1.9" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44" 2407 | 2408 | [[package]] 2409 | name = "untrusted" 2410 | version = "0.7.1" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 2413 | 2414 | [[package]] 2415 | name = "url" 2416 | version = "2.2.2" 2417 | source = "registry+https://github.com/rust-lang/crates.io-index" 2418 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 2419 | dependencies = [ 2420 | "form_urlencoded", 2421 | "idna", 2422 | "matches", 2423 | "percent-encoding", 2424 | ] 2425 | 2426 | [[package]] 2427 | name = "valuable" 2428 | version = "0.1.0" 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" 2430 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2431 | 2432 | [[package]] 2433 | name = "vcpkg" 2434 | version = "0.2.15" 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" 2436 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2437 | 2438 | [[package]] 2439 | name = "version_check" 2440 | version = "0.9.4" 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" 2442 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2443 | 2444 | [[package]] 2445 | name = "want" 2446 | version = "0.3.0" 2447 | source = "registry+https://github.com/rust-lang/crates.io-index" 2448 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 2449 | dependencies = [ 2450 | "log", 2451 | "try-lock", 2452 | ] 2453 | 2454 | [[package]] 2455 | name = "wasi" 2456 | version = "0.10.0+wasi-snapshot-preview1" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 2459 | 2460 | [[package]] 2461 | name = "wasi" 2462 | version = "0.11.0+wasi-snapshot-preview1" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2465 | 2466 | [[package]] 2467 | name = "wasm-bindgen" 2468 | version = "0.2.81" 2469 | source = "registry+https://github.com/rust-lang/crates.io-index" 2470 | checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" 2471 | dependencies = [ 2472 | "cfg-if", 2473 | "wasm-bindgen-macro", 2474 | ] 2475 | 2476 | [[package]] 2477 | name = "wasm-bindgen-backend" 2478 | version = "0.2.81" 2479 | source = "registry+https://github.com/rust-lang/crates.io-index" 2480 | checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" 2481 | dependencies = [ 2482 | "bumpalo", 2483 | "lazy_static", 2484 | "log", 2485 | "proc-macro2", 2486 | "quote", 2487 | "syn", 2488 | "wasm-bindgen-shared", 2489 | ] 2490 | 2491 | [[package]] 2492 | name = "wasm-bindgen-futures" 2493 | version = "0.4.31" 2494 | source = "registry+https://github.com/rust-lang/crates.io-index" 2495 | checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" 2496 | dependencies = [ 2497 | "cfg-if", 2498 | "js-sys", 2499 | "wasm-bindgen", 2500 | "web-sys", 2501 | ] 2502 | 2503 | [[package]] 2504 | name = "wasm-bindgen-macro" 2505 | version = "0.2.81" 2506 | source = "registry+https://github.com/rust-lang/crates.io-index" 2507 | checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" 2508 | dependencies = [ 2509 | "quote", 2510 | "wasm-bindgen-macro-support", 2511 | ] 2512 | 2513 | [[package]] 2514 | name = "wasm-bindgen-macro-support" 2515 | version = "0.2.81" 2516 | source = "registry+https://github.com/rust-lang/crates.io-index" 2517 | checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" 2518 | dependencies = [ 2519 | "proc-macro2", 2520 | "quote", 2521 | "syn", 2522 | "wasm-bindgen-backend", 2523 | "wasm-bindgen-shared", 2524 | ] 2525 | 2526 | [[package]] 2527 | name = "wasm-bindgen-shared" 2528 | version = "0.2.81" 2529 | source = "registry+https://github.com/rust-lang/crates.io-index" 2530 | checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" 2531 | 2532 | [[package]] 2533 | name = "web-sys" 2534 | version = "0.3.58" 2535 | source = "registry+https://github.com/rust-lang/crates.io-index" 2536 | checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" 2537 | dependencies = [ 2538 | "js-sys", 2539 | "wasm-bindgen", 2540 | ] 2541 | 2542 | [[package]] 2543 | name = "webpki" 2544 | version = "0.22.0" 2545 | source = "registry+https://github.com/rust-lang/crates.io-index" 2546 | checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 2547 | dependencies = [ 2548 | "ring", 2549 | "untrusted", 2550 | ] 2551 | 2552 | [[package]] 2553 | name = "webpki-roots" 2554 | version = "0.22.4" 2555 | source = "registry+https://github.com/rust-lang/crates.io-index" 2556 | checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" 2557 | dependencies = [ 2558 | "webpki", 2559 | ] 2560 | 2561 | [[package]] 2562 | name = "weezl" 2563 | version = "0.1.6" 2564 | source = "registry+https://github.com/rust-lang/crates.io-index" 2565 | checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4" 2566 | 2567 | [[package]] 2568 | name = "winapi" 2569 | version = "0.3.9" 2570 | source = "registry+https://github.com/rust-lang/crates.io-index" 2571 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2572 | dependencies = [ 2573 | "winapi-i686-pc-windows-gnu", 2574 | "winapi-x86_64-pc-windows-gnu", 2575 | ] 2576 | 2577 | [[package]] 2578 | name = "winapi-i686-pc-windows-gnu" 2579 | version = "0.4.0" 2580 | source = "registry+https://github.com/rust-lang/crates.io-index" 2581 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2582 | 2583 | [[package]] 2584 | name = "winapi-util" 2585 | version = "0.1.5" 2586 | source = "registry+https://github.com/rust-lang/crates.io-index" 2587 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2588 | dependencies = [ 2589 | "winapi", 2590 | ] 2591 | 2592 | [[package]] 2593 | name = "winapi-x86_64-pc-windows-gnu" 2594 | version = "0.4.0" 2595 | source = "registry+https://github.com/rust-lang/crates.io-index" 2596 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2597 | 2598 | [[package]] 2599 | name = "windows-sys" 2600 | version = "0.36.1" 2601 | source = "registry+https://github.com/rust-lang/crates.io-index" 2602 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 2603 | dependencies = [ 2604 | "windows_aarch64_msvc", 2605 | "windows_i686_gnu", 2606 | "windows_i686_msvc", 2607 | "windows_x86_64_gnu", 2608 | "windows_x86_64_msvc", 2609 | ] 2610 | 2611 | [[package]] 2612 | name = "windows_aarch64_msvc" 2613 | version = "0.36.1" 2614 | source = "registry+https://github.com/rust-lang/crates.io-index" 2615 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 2616 | 2617 | [[package]] 2618 | name = "windows_i686_gnu" 2619 | version = "0.36.1" 2620 | source = "registry+https://github.com/rust-lang/crates.io-index" 2621 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 2622 | 2623 | [[package]] 2624 | name = "windows_i686_msvc" 2625 | version = "0.36.1" 2626 | source = "registry+https://github.com/rust-lang/crates.io-index" 2627 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 2628 | 2629 | [[package]] 2630 | name = "windows_x86_64_gnu" 2631 | version = "0.36.1" 2632 | source = "registry+https://github.com/rust-lang/crates.io-index" 2633 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 2634 | 2635 | [[package]] 2636 | name = "windows_x86_64_msvc" 2637 | version = "0.36.1" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 2640 | 2641 | [[package]] 2642 | name = "winreg" 2643 | version = "0.10.1" 2644 | source = "registry+https://github.com/rust-lang/crates.io-index" 2645 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 2646 | dependencies = [ 2647 | "winapi", 2648 | ] 2649 | 2650 | [[package]] 2651 | name = "yaml-rust" 2652 | version = "0.4.5" 2653 | source = "registry+https://github.com/rust-lang/crates.io-index" 2654 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2655 | dependencies = [ 2656 | "linked-hash-map", 2657 | ] 2658 | 2659 | [[package]] 2660 | name = "zeroize" 2661 | version = "1.4.3" 2662 | source = "registry+https://github.com/rust-lang/crates.io-index" 2663 | checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" 2664 | dependencies = [ 2665 | "zeroize_derive", 2666 | ] 2667 | 2668 | [[package]] 2669 | name = "zeroize_derive" 2670 | version = "1.3.2" 2671 | source = "registry+https://github.com/rust-lang/crates.io-index" 2672 | checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" 2673 | dependencies = [ 2674 | "proc-macro2", 2675 | "quote", 2676 | "syn", 2677 | "synstructure", 2678 | ] 2679 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stream-gears" 3 | version = "0.1.11" 4 | edition = "2021" 5 | 6 | [profile.release] 7 | lto = true 8 | codegen-units = 1 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [lib] 11 | name = "stream_gears" 12 | crate-type = ["cdylib", "lib"] 13 | 14 | [dependencies] 15 | pyo3 = { version = "0.16.3", features = ["extension-module"] } 16 | biliup = "0.1.10" 17 | reqwest = { version = "*", features = ["blocking", "deflate", "gzip"] } 18 | url = "*" 19 | m3u8-rs = "4.0.0" 20 | nom = "7" 21 | serde = "1" 22 | serde_json = "1.0" 23 | chrono = "0.4" 24 | bytes = "1.1.0" 25 | byteorder = "1.4.3" 26 | anyhow = "1.0" 27 | thiserror = "1.0" 28 | tokio = { version = "1", features = ["full"] } 29 | tracing = "0.1" 30 | tracing-subscriber = "0.3" 31 | tracing-appender = "0.2" 32 | futures = "0.3.21" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 biliup 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stream-gears 2 | 通过 PyO3 导出**上传** B 站与**下载** FLV、HLS 流的函数供Python调用, 3 | 支持时间或文件大小分段,同时解决拉取FLV流花屏的问题。 4 | 5 | ## Dev 6 | 1. Install the latest [Rust compiler](https://www.rust-lang.org/tools/install) 7 | 2. Install [maturin](https://maturin.rs/): `$ pip3 install maturin` 8 | ```shell 9 | $ python -m venv .env 10 | $ source .env/bin/activate 11 | $ maturin develop 12 | ``` 13 | 14 | ## Credits 15 | 感谢 [Genteure](https://github.com/Genteure) 提供帮助 -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=0.12,<0.13"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "stream-gears" 7 | requires-python = ">=3.7" 8 | classifiers = [ 9 | "Programming Language :: Rust", 10 | "Programming Language :: Python :: Implementation :: CPython", 11 | "Programming Language :: Python :: Implementation :: PyPy", 12 | ] 13 | 14 | -------------------------------------------------------------------------------- /src/downloader.rs: -------------------------------------------------------------------------------- 1 | use crate::downloader::httpflv::Connection; 2 | use crate::flv_parser::header; 3 | use nom::Err; 4 | use reqwest::blocking::Response; 5 | use reqwest::header::{ 6 | HeaderMap, HeaderName, HeaderValue, ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, USER_AGENT, 7 | }; 8 | use std::collections::HashMap; 9 | 10 | use std::io::Read; 11 | use std::str::FromStr; 12 | use std::thread::sleep; 13 | use std::time::Duration; 14 | use util::Segment; 15 | 16 | mod hls; 17 | pub mod httpflv; 18 | pub mod util; 19 | 20 | pub fn download( 21 | url: &str, 22 | headers: HeaderMap, 23 | file_name: &str, 24 | segment: Segment, 25 | ) -> anyhow::Result<()> { 26 | let mut response = get_response(url, &headers)?; 27 | let buf = &mut [0u8; 9]; 28 | response.read_exact(buf)?; 29 | // let out = File::create(format!("{}.flv", file_name)).expect("Unable to create file."); 30 | // let mut writer = BufWriter::new(out); 31 | // let mut buf = [0u8; 8 * 1024]; 32 | // response.copy_to(&mut writer)?; 33 | // io::copy(&mut resp, &mut out).expect("Unable to copy the content."); 34 | match header(buf) { 35 | Ok((_i, header)) => { 36 | println!("header: {header:#?}"); 37 | println!("{}", response.status()); 38 | let connection = Connection::new(response); 39 | println!("Downloading {}...", url); 40 | httpflv::download(connection, file_name, segment); 41 | } 42 | Err(Err::Incomplete(needed)) => { 43 | println!("needed: {needed:?}") 44 | } 45 | Err(e) => { 46 | println!("{e}"); 47 | hls::download(url, &headers, file_name, segment)?; 48 | } 49 | } 50 | Ok(()) 51 | } 52 | 53 | pub fn construct_headers(hash_map: HashMap) -> HeaderMap { 54 | let mut headers = HeaderMap::new(); 55 | for (key, value) in hash_map.iter() { 56 | headers.insert( 57 | HeaderName::from_str(key).unwrap(), 58 | HeaderValue::from_str(value).unwrap(), 59 | ); 60 | } 61 | headers 62 | } 63 | 64 | pub fn get_response(url: &str, headers: &HeaderMap) -> reqwest::Result { 65 | let resp = retry(|| { 66 | reqwest::blocking::Client::new() 67 | .get(url) 68 | .header(ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") 69 | .header(ACCEPT_ENCODING, "gzip, deflate") 70 | .header(ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3") 71 | .header(USER_AGENT, "Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Iceweasel/38.2.1") 72 | .headers(headers.clone()) 73 | .send() 74 | })?; 75 | resp.error_for_status_ref()?; 76 | Ok(resp) 77 | } 78 | 79 | fn retry(mut f: impl FnMut() -> Result) -> Result { 80 | let mut retries = 0; 81 | let mut wait = 1; 82 | loop { 83 | match f() { 84 | Err(e) if retries < 3 => { 85 | retries += 1; 86 | println!( 87 | "Retry attempt #{}. Sleeping {wait}s before the next attempt. {e}", 88 | retries, 89 | ); 90 | sleep(Duration::from_secs(wait)); 91 | wait *= 2; 92 | } 93 | res => break res, 94 | } 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use crate::downloader::download; 101 | use crate::downloader::util::Segment; 102 | use anyhow::Result; 103 | use reqwest::header::{HeaderMap, HeaderValue, REFERER}; 104 | 105 | #[test] 106 | fn it_works() -> Result<()> { 107 | tracing_subscriber::fmt::init(); 108 | 109 | let mut headers = HeaderMap::new(); 110 | headers.insert( 111 | REFERER, 112 | HeaderValue::from_static("https://live.bilibili.com"), 113 | ); 114 | download( 115 | "", 116 | headers, 117 | "testdouyu%Y-%m-%dT%H_%M_%S", 118 | // Segment::Size(20 * 1024 * 1024, 0), 119 | Segment::Time(std::time::Duration::from_secs(6000), Default::default()), 120 | )?; 121 | Ok(()) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/downloader/hls.rs: -------------------------------------------------------------------------------- 1 | use crate::downloader::util::format_filename; 2 | use crate::error::Result; 3 | use crate::Segment; 4 | use m3u8_rs::Playlist; 5 | use reqwest::header::HeaderMap; 6 | use std::fs::File; 7 | use std::io::{BufWriter, Write}; 8 | use std::time::Duration; 9 | use tracing::{debug, error, info, warn}; 10 | use url::Url; 11 | 12 | pub fn download( 13 | url: &str, 14 | headers: &HeaderMap, 15 | file_name: &str, 16 | mut splitting: Segment, 17 | ) -> Result<()> { 18 | println!("Downloading {}...", url); 19 | let resp = super::get_response(url, headers)?; 20 | println!("{}", resp.status()); 21 | // let mut resp = resp.bytes_stream(); 22 | let bytes = resp.bytes()?; 23 | let mut ts_file = TsFile::new(file_name); 24 | 25 | let mut media_url = Url::parse(url)?; 26 | let mut pl = match m3u8_rs::parse_playlist(&bytes) { 27 | Ok((_i, Playlist::MasterPlaylist(pl))) => { 28 | println!("Master playlist:\n{:#?}", pl); 29 | media_url = media_url.join(&pl.variants[0].uri)?; 30 | println!("media url: {media_url}"); 31 | let resp = super::get_response(media_url.as_str(), headers)?; 32 | let bs = resp.bytes()?; 33 | // println!("{:?}", bs); 34 | if let Ok((_, pl)) = m3u8_rs::parse_media_playlist(&bs) { 35 | pl 36 | } else { 37 | let mut file = File::create("test.fmp4")?; 38 | file.write_all(&bs)?; 39 | panic!("Unable to parse the content.") 40 | } 41 | } 42 | Ok((_i, Playlist::MediaPlaylist(pl))) => { 43 | println!("Media playlist:\n{:#?}", pl); 44 | println!("index {}", pl.media_sequence); 45 | pl 46 | } 47 | Err(e) => panic!("Parsing error: \n{}", e), 48 | }; 49 | let mut previous_last_segment = 0; 50 | loop { 51 | if pl.segments.is_empty() { 52 | println!("Segments array is empty - stream finished"); 53 | break; 54 | } 55 | let mut seq = pl.media_sequence; 56 | for segment in &pl.segments { 57 | if seq > previous_last_segment { 58 | if (previous_last_segment > 0) && (seq > (previous_last_segment + 1)) { 59 | warn!("SEGMENT INFO SKIPPED"); 60 | } 61 | debug!("Yield segment"); 62 | if segment.discontinuity { 63 | warn!("#EXT-X-DISCONTINUITY"); 64 | ts_file = TsFile::new(file_name); 65 | splitting = Segment::from_seg(splitting); 66 | } 67 | let length = download_to_file( 68 | media_url.join(&segment.uri)?, 69 | headers, 70 | &mut ts_file.buf_writer, 71 | )?; 72 | if splitting.needed_delta(length, Duration::from_secs(segment.duration as u64)) { 73 | ts_file = TsFile::new(file_name); 74 | info!("{} splitting.{splitting:?}", ts_file.name); 75 | } 76 | previous_last_segment = seq; 77 | } 78 | seq += 1; 79 | } 80 | let resp = super::get_response(media_url.as_str(), headers)?; 81 | let bs = resp.bytes()?; 82 | if let Ok((_, playlist)) = m3u8_rs::parse_media_playlist(&bs) { 83 | pl = playlist; 84 | } 85 | } 86 | println!("Done..."); 87 | Ok(()) 88 | } 89 | 90 | fn download_to_file(url: Url, headers: &HeaderMap, out: &mut impl Write) -> reqwest::Result { 91 | debug!("url: {url}"); 92 | let mut response = super::get_response(url.as_str(), headers)?; 93 | // let mut out = File::options() 94 | // .append(true) 95 | // .open(format!("{file_name}.ts"))?; 96 | let length = response.copy_to(out)?; 97 | Ok(length) 98 | } 99 | 100 | pub struct TsFile { 101 | pub buf_writer: BufWriter, 102 | pub name: String, 103 | } 104 | 105 | impl TsFile { 106 | pub fn new(file_name: &str) -> Self { 107 | let file_name = format_filename(file_name); 108 | let out = File::create(format!("{file_name}.ts.part")).expect("Unable to create ts file."); 109 | let buf_writer = BufWriter::new(out); 110 | Self { 111 | buf_writer, 112 | name: file_name, 113 | } 114 | } 115 | } 116 | 117 | impl Drop for TsFile { 118 | fn drop(&mut self) { 119 | std::fs::rename( 120 | format!("{}.ts.part", self.name), 121 | format!("{}.ts", self.name), 122 | ) 123 | .unwrap_or_else(|e| error!("{e}")) 124 | } 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | 130 | use anyhow::Result; 131 | use reqwest::Url; 132 | 133 | #[test] 134 | fn test_url() -> Result<()> { 135 | let url = Url::parse("h://host.path/to/remote/resource.m3u8")?; 136 | let scheme = url.scheme(); 137 | let new_url = url.join("http://path.host/remote/resource.ts")?; 138 | println!("{url}, {scheme}"); 139 | println!("{new_url}, {scheme}"); 140 | Ok(()) 141 | } 142 | 143 | #[test] 144 | fn it_works() -> Result<()> { 145 | // download( 146 | // "test.ts")?; 147 | Ok(()) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/downloader/httpflv.rs: -------------------------------------------------------------------------------- 1 | use crate::downloader::util::Segment; 2 | use crate::flv_parser::{ 3 | aac_audio_packet_header, avc_video_packet_header, script_data, tag_data, tag_header, 4 | AACPacketType, AVCPacketType, CodecId, FrameType, SoundFormat, TagData, TagHeader, 5 | }; 6 | use crate::flv_writer::{FlvFile, FlvTag, TagDataHeader}; 7 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 8 | use nom::{Err, IResult}; 9 | use std::io::{ErrorKind, Read}; 10 | use std::time::Duration; 11 | use tracing::{info, warn}; 12 | 13 | pub fn download(connection: Connection, file_name: &str, segment: Segment) { 14 | match parse_flv(connection, file_name, segment) { 15 | Ok(_) => { 16 | info!("Done... {file_name}"); 17 | } 18 | Err(e) => { 19 | warn!("{e}") 20 | } 21 | } 22 | } 23 | 24 | fn parse_flv( 25 | mut connection: Connection, 26 | file_name: &str, 27 | mut segment: Segment, 28 | ) -> core::result::Result<(), crate::error::Error> { 29 | let mut flv_tags_cache: Vec<(TagHeader, Bytes, Bytes)> = Vec::new(); 30 | 31 | let _previous_tag_size = connection.read_frame(4)?; 32 | // let mut rdr = Cursor::new(previous_tag_size); 33 | // println!("{}", rdr.read_u32::().unwrap()); 34 | // let file = std::fs::File::create(format!("{file_name}_flv.json"))?; 35 | // let mut writer = BufWriter::new(file); 36 | // flv_writer::to_json(&mut writer, &header)?; 37 | 38 | let mut out = FlvFile::new(file_name)?; 39 | let mut downloaded_size = 9 + 4; 40 | let mut on_meta_data = None; 41 | let mut aac_sequence_header = None; 42 | let mut h264_sequence_header: Option<(TagHeader, Bytes, Bytes)> = None; 43 | let mut prev_timestamp = 0; 44 | let mut create_new = false; 45 | loop { 46 | let tag_header_bytes = connection.read_frame(11)?; 47 | if tag_header_bytes.is_empty() { 48 | // let mut rdr = Cursor::new(tag_header_bytes); 49 | // println!("{}", rdr.read_u32::().unwrap()); 50 | break; 51 | } 52 | 53 | let (_, tag_header) = map_parse_err(tag_header(&tag_header_bytes), "tag header")?; 54 | // write_tag_header(&mut out, &tag_header)?; 55 | 56 | let bytes = connection.read_frame(tag_header.data_size as usize)?; 57 | let previous_tag_size = connection.read_frame(4)?; 58 | // out.write(&bytes)?; 59 | let (i, flv_tag_data) = map_parse_err( 60 | tag_data(tag_header.tag_type, tag_header.data_size as usize)(&bytes), 61 | "tag data", 62 | )?; 63 | let flv_tag = match flv_tag_data { 64 | TagData::Audio(audio_data) => { 65 | let packet_type = if audio_data.sound_format == SoundFormat::AAC { 66 | let (_, packet_header) = aac_audio_packet_header(audio_data.sound_data) 67 | .expect("Error in parsing aac audio packet header."); 68 | if packet_header.packet_type == AACPacketType::SequenceHeader { 69 | if aac_sequence_header.is_some() { 70 | warn!("Unexpected aac sequence header tag. {tag_header:?}"); 71 | // panic!("Unexpected aac_sequence_header tag."); 72 | // create_new = true; 73 | } 74 | aac_sequence_header = 75 | Some((tag_header, bytes.clone(), previous_tag_size.clone())) 76 | } 77 | Some(packet_header.packet_type) 78 | } else { 79 | None 80 | }; 81 | 82 | FlvTag { 83 | header: tag_header, 84 | data: TagDataHeader::Audio { 85 | sound_format: audio_data.sound_format, 86 | sound_rate: audio_data.sound_rate, 87 | sound_size: audio_data.sound_size, 88 | sound_type: audio_data.sound_type, 89 | packet_type, 90 | }, 91 | } 92 | } 93 | TagData::Video(video_data) => { 94 | let (packet_type, composition_time) = if CodecId::H264 == video_data.codec_id { 95 | let (_, avc_video_header) = avc_video_packet_header(video_data.video_data) 96 | .expect("Error in parsing avc video packet header."); 97 | if avc_video_header.packet_type == AVCPacketType::SequenceHeader { 98 | h264_sequence_header = match h264_sequence_header { 99 | None => Some((tag_header, bytes.clone(), previous_tag_size.clone())), 100 | Some((_, binary_data, _)) => { 101 | warn!("Unexpected h264 sequence header tag. {tag_header:?}"); 102 | // panic!("Unexpected h264 sequence header tag."); 103 | if bytes != binary_data { 104 | create_new = true; 105 | warn!("Different h264 sequence header tag. {tag_header:?}"); 106 | } 107 | Some((tag_header, bytes.clone(), previous_tag_size.clone())) 108 | } 109 | }; 110 | } 111 | ( 112 | Some(avc_video_header.packet_type), 113 | Some(avc_video_header.composition_time), 114 | ) 115 | } else { 116 | (None, None) 117 | }; 118 | 119 | FlvTag { 120 | header: tag_header, 121 | data: TagDataHeader::Video { 122 | frame_type: video_data.frame_type, 123 | codec_id: video_data.codec_id, 124 | packet_type, 125 | composition_time, 126 | }, 127 | } 128 | } 129 | TagData::Script => { 130 | let (_, tag_data) = script_data(i).expect("Error in parsing script tag."); 131 | if on_meta_data.is_some() { 132 | warn!("Unexpected script tag. {tag_header:?}"); 133 | // create_new = true; 134 | 135 | // panic!("Unexpected script tag."); 136 | } 137 | on_meta_data = Some((tag_header, bytes.clone(), previous_tag_size.clone())); 138 | 139 | let flv_tag = FlvTag { 140 | header: tag_header, 141 | data: TagDataHeader::Script(tag_data), 142 | }; 143 | flv_tag 144 | } 145 | }; 146 | match &flv_tag { 147 | FlvTag { 148 | data: 149 | TagDataHeader::Video { 150 | frame_type: FrameType::Key, 151 | .. 152 | }, 153 | .. 154 | } => { 155 | if segment.needed( 156 | downloaded_size, 157 | Duration::from_millis(flv_tag.header.timestamp as u64), 158 | ) { 159 | // let new_file_name = format_filename(file_name); 160 | downloaded_size = 9 + 4; 161 | out = FlvFile::new(file_name)?; 162 | let on_meta_data = on_meta_data.as_ref().expect("on_meta_data does not exist"); 163 | // onMetaData 164 | out.write_tag(&on_meta_data.0, &on_meta_data.1, &on_meta_data.2)?; 165 | // AACSequenceHeader 166 | let aac_sequence_header = aac_sequence_header 167 | .as_ref() 168 | .expect("aac_sequence_header does not exist"); 169 | out.write_tag( 170 | &aac_sequence_header.0, 171 | &aac_sequence_header.1, 172 | &aac_sequence_header.2, 173 | )?; 174 | // H264SequenceHeader 175 | let h264_sequence_header = h264_sequence_header 176 | .as_ref() 177 | .expect("h264_sequence_header does not exist"); 178 | out.write_tag( 179 | &h264_sequence_header.0, 180 | &h264_sequence_header.1, 181 | &h264_sequence_header.2, 182 | )?; 183 | info!("{} splitting.{segment:?}", out.name); 184 | } 185 | 186 | for (tag_header, flv_tag_data, previous_tag_size_bytes) in &flv_tags_cache { 187 | if tag_header.timestamp < prev_timestamp { 188 | warn!("Non-monotonous DTS in output stream; previous: {prev_timestamp}, current: {};", tag_header.timestamp); 189 | } 190 | out.write_tag(tag_header, flv_tag_data, previous_tag_size_bytes)?; 191 | // out.write_tag_header( tag_header)?; 192 | // out.write(flv_tag_data)?; 193 | // out.write(previous_tag_size_bytes)?; 194 | downloaded_size += (11 + tag_header.data_size + 4) as u64; 195 | prev_timestamp = tag_header.timestamp 196 | // println!("{downloaded_size}"); 197 | } 198 | flv_tags_cache.clear(); 199 | if create_new { 200 | // let new_file_name = format_filename(file_name); 201 | out = FlvFile::new(file_name)?; 202 | // let on_meta_data = on_meta_data.as_ref().unwrap(); 203 | // flv_tags_cache.push(on_meta_data) 204 | // onMetaData 205 | let on_meta_data = on_meta_data.as_ref().expect("on_meta_data does not exist"); 206 | out.write_tag(&on_meta_data.0, &on_meta_data.1, &on_meta_data.2)?; 207 | // AACSequenceHeader 208 | let aac_sequence_header = aac_sequence_header 209 | .as_ref() 210 | .expect("aac_sequence_header does not exist"); 211 | out.write_tag( 212 | &aac_sequence_header.0, 213 | &aac_sequence_header.1, 214 | &aac_sequence_header.2, 215 | )?; 216 | create_new = false; 217 | info!("{} splitting.", out.name); 218 | } 219 | flv_tags_cache.push((tag_header, bytes.clone(), previous_tag_size.clone())); 220 | } 221 | _ => { 222 | flv_tags_cache.push((tag_header, bytes.clone(), previous_tag_size.clone())); 223 | } 224 | } 225 | // flv_writer::to_json(&mut writer, &flv_tag)?; 226 | } 227 | Ok(()) 228 | } 229 | 230 | // fn is_splitting( 231 | // flv_tag: FlvTag, 232 | // segment: &Segment, 233 | // first_tag_time: &mut u32, 234 | // downloaded_size: &mut u64, 235 | // ) -> bool { 236 | // match segment { 237 | // Segment::Time(duration, _) => { 238 | // if duration 239 | // <= &Duration::from_millis((flv_tag.header.timestamp - *first_tag_time) as u64) 240 | // { 241 | // *first_tag_time = flv_tag.header.timestamp; 242 | // true 243 | // } else { 244 | // false 245 | // } 246 | // } 247 | // Segment::Size(file_size, _) => { 248 | // if *downloaded_size >= *file_size { 249 | // *downloaded_size = 9 + 4; 250 | // true 251 | // } else { 252 | // false 253 | // } 254 | // } 255 | // } 256 | // } 257 | 258 | pub fn map_parse_err<'a, T>( 259 | i_result: IResult<&'a [u8], T>, 260 | msg: &str, 261 | ) -> core::result::Result<(&'a [u8], T), crate::error::Error> { 262 | match i_result { 263 | Ok((i, res)) => Ok((i, res)), 264 | Err(nom::Err::Incomplete(needed)) => { 265 | Err(crate::error::Error::NomIncomplete(msg.to_string(), needed)) 266 | } 267 | Err(Err::Error(e)) => { 268 | panic!("parse {msg} err: {e:?}") 269 | } 270 | Err(Err::Failure(f)) => { 271 | panic!("{msg} Failure: {f:?}") 272 | } 273 | } 274 | } 275 | 276 | pub struct Connection { 277 | resp: T, 278 | buffer: BytesMut, 279 | } 280 | 281 | impl Connection { 282 | pub fn new(resp: T) -> Connection { 283 | Connection { 284 | resp, 285 | buffer: BytesMut::with_capacity(8 * 1024), 286 | } 287 | } 288 | 289 | pub fn read_frame(&mut self, chunk_size: usize) -> std::io::Result { 290 | let mut buf = [0u8; 8 * 1024]; 291 | loop { 292 | if chunk_size <= self.buffer.len() { 293 | let bytes = Bytes::copy_from_slice(&self.buffer[..chunk_size]); 294 | self.buffer.advance(chunk_size as usize); 295 | return Ok(bytes); 296 | } 297 | // BytesMut::with_capacity(0).deref_mut() 298 | // tokio::fs::File::open("").read() 299 | let n = match self.resp.read(&mut buf) { 300 | Ok(n) => n, 301 | Err(e) if e.kind() == ErrorKind::Interrupted => continue, 302 | Err(e) => return Err(e), 303 | }; 304 | 305 | if n == 0 { 306 | return Ok(self.buffer.split().freeze()); 307 | } 308 | self.buffer.put_slice(&buf[..n]); 309 | } 310 | } 311 | } 312 | 313 | #[cfg(test)] 314 | mod tests { 315 | 316 | use anyhow::Result; 317 | use bytes::{Buf, BufMut, BytesMut}; 318 | 319 | #[test] 320 | fn byte_it_works() -> Result<()> { 321 | let mut bb = bytes::BytesMut::with_capacity(10); 322 | println!("chunk {:?}", bb.chunk()); 323 | println!("capacity {}", bb.capacity()); 324 | bb.put(&b"hello"[..]); 325 | println!("chunk {:?}", bb.chunk()); 326 | println!("remaining {}", bb.remaining()); 327 | bb.advance(5); 328 | println!("capacity {}", bb.capacity()); 329 | println!("chunk {:?}", bb.chunk()); 330 | println!("remaining {}", bb.remaining()); 331 | bb.put(&b"hello"[..]); 332 | bb.put(&b"hello"[..]); 333 | println!("chunk {:?}", bb.chunk()); 334 | println!("capacity {}", bb.capacity()); 335 | println!("remaining {}", bb.remaining()); 336 | 337 | let mut buf = BytesMut::with_capacity(11); 338 | buf.put(&b"hello world"[..]); 339 | 340 | let other = buf.split(); 341 | // buf.advance_mut() 342 | 343 | assert!(buf.is_empty()); 344 | assert_eq!(0, buf.capacity()); 345 | assert_eq!(11, other.capacity()); 346 | assert_eq!(other, b"hello world"[..]); 347 | 348 | Ok(()) 349 | } 350 | 351 | #[test] 352 | fn it_works() -> Result<()> { 353 | // download( 354 | // "test.flv")?; 355 | Ok(()) 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/downloader/util.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Local}; 2 | use std::time::Duration; 3 | 4 | #[derive(Debug)] 5 | pub enum Segment { 6 | Time(Duration, Duration), 7 | Size(u64, u64), 8 | } 9 | 10 | impl Segment { 11 | pub fn from_seg(mut seg: Segment) -> Self { 12 | match &mut seg { 13 | Segment::Time(_, old) => { 14 | *old = Duration::ZERO; 15 | seg 16 | } 17 | Segment::Size(_, old) => { 18 | *old = 0; 19 | seg 20 | } 21 | } 22 | } 23 | 24 | pub fn needed(&mut self, actual_size: u64, actual_time: Duration) -> bool { 25 | match self { 26 | Segment::Time(expected, start_time) if *expected <= actual_time - *start_time => { 27 | *start_time = actual_time; 28 | true 29 | } 30 | Segment::Size(expected, _) if *expected <= actual_size => true, 31 | Segment::Time(_, _) => false, 32 | Segment::Size(_, _) => false, 33 | } 34 | } 35 | 36 | pub fn needed_delta(&mut self, size: u64, delta: Duration) -> bool { 37 | match self { 38 | Segment::Time(expected, previous) if *expected <= *previous + delta => { 39 | *previous = delta; 40 | true 41 | } 42 | Segment::Size(expected, previous) if *expected <= *previous + size => { 43 | *previous = 0; 44 | true 45 | } 46 | Segment::Time(_, previous) => { 47 | *previous += delta; 48 | false 49 | } 50 | Segment::Size(_, previous) => { 51 | *previous += size; 52 | false 53 | } 54 | } 55 | } 56 | } 57 | 58 | pub fn format_filename(file_name: &str) -> String { 59 | let local: DateTime = Local::now(); 60 | // let time_str = local.format("%Y-%m-%dT%H_%M_%S"); 61 | let time_str = local.format(file_name); 62 | // format!("{file_name}{time_str}") 63 | time_str.to_string() 64 | } 65 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use nom::Needed; 2 | use std::io; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum Error { 7 | #[error(transparent)] 8 | IOError(#[from] io::Error), 9 | 10 | #[error(transparent)] 11 | ReqwestError(#[from] reqwest::Error), 12 | 13 | #[error(transparent)] 14 | UrlParseError(#[from] url::ParseError), 15 | 16 | #[error("Parsing {0} requires {1:?} bytes/chars.")] 17 | NomIncomplete(String, Needed), 18 | } 19 | 20 | pub type Result = core::result::Result; 21 | -------------------------------------------------------------------------------- /src/flv_parser.rs: -------------------------------------------------------------------------------- 1 | // source: https://github.com/rust-av/flavors/blob/master/src/parser.rs 2 | use nom::bits::bits; 3 | use nom::bits::streaming::take; 4 | use nom::bytes::streaming::tag; 5 | use nom::combinator::{flat_map, map, map_res}; 6 | use nom::error::{Error, ErrorKind}; 7 | use nom::multi::{length_data, many0, many_m_n}; 8 | use nom::number::streaming::{be_f64, be_i16, be_i24, be_u16, be_u24, be_u32, be_u8}; 9 | use nom::sequence::{pair, terminated, tuple}; 10 | use nom::{Err, IResult, Needed}; 11 | use serde::Serialize; 12 | use std::str::from_utf8; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Serialize)] 15 | pub struct Header { 16 | pub version: u8, 17 | pub audio: bool, 18 | pub video: bool, 19 | pub offset: u32, 20 | } 21 | 22 | pub fn header(input: &[u8]) -> IResult<&[u8], Header> { 23 | map( 24 | tuple((tag("FLV"), be_u8, be_u8, be_u32)), 25 | |(_, version, flags, offset)| Header { 26 | version, 27 | audio: flags & 4 == 4, 28 | video: flags & 1 == 1, 29 | offset, 30 | }, 31 | )(input) 32 | } 33 | 34 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 35 | pub enum TagType { 36 | Audio = 8, 37 | Video = 9, 38 | Script = 18, 39 | } 40 | 41 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 42 | pub struct TagHeader { 43 | pub tag_type: TagType, 44 | pub data_size: u32, 45 | pub timestamp: u32, 46 | pub stream_id: u32, 47 | } 48 | 49 | #[derive(Clone, Debug, PartialEq, Eq)] 50 | pub enum TagData<'a> { 51 | Audio(AudioData<'a>), 52 | Video(VideoData<'a>), 53 | Script, 54 | } 55 | 56 | #[derive(Debug, PartialEq, Eq)] 57 | pub struct Tag<'a> { 58 | pub header: TagHeader, 59 | pub data: TagData<'a>, 60 | } 61 | 62 | fn tag_type(input: &[u8]) -> IResult<&[u8], TagType> { 63 | map_res(be_u8, |tag_type| { 64 | Ok(match tag_type { 65 | 8 => TagType::Audio, 66 | 9 => TagType::Video, 67 | 18 => TagType::Script, 68 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 69 | }) 70 | })(input) 71 | } 72 | 73 | pub fn tag_header(input: &[u8]) -> IResult<&[u8], TagHeader> { 74 | map( 75 | tuple((tag_type, be_u24, be_u24, be_u8, be_u24)), 76 | |(tag_type, data_size, timestamp, timestamp_extended, stream_id)| TagHeader { 77 | tag_type, 78 | data_size, 79 | timestamp: (u32::from(timestamp_extended) << 24) + timestamp, 80 | stream_id, 81 | }, 82 | )(input) 83 | } 84 | 85 | pub fn complete_tag(input: &[u8]) -> IResult<&[u8], Tag> { 86 | flat_map(pair(tag_type, be_u24), |(tag_type, data_size)| { 87 | map( 88 | tuple(( 89 | be_u24, 90 | be_u8, 91 | be_u24, 92 | tag_data(tag_type, data_size as usize), 93 | )), 94 | move |(timestamp, timestamp_extended, stream_id, data)| Tag { 95 | header: TagHeader { 96 | tag_type, 97 | data_size, 98 | timestamp: (u32::from(timestamp_extended) << 24) + timestamp, 99 | stream_id, 100 | }, 101 | data, 102 | }, 103 | ) 104 | })(input) 105 | } 106 | 107 | pub fn tag_data(tag_type: TagType, size: usize) -> impl Fn(&[u8]) -> IResult<&[u8], TagData> { 108 | move |input| match tag_type { 109 | TagType::Video => map(|i| video_data(i, size), TagData::Video)(input), 110 | TagType::Audio => map(|i| audio_data(i, size), TagData::Audio)(input), 111 | TagType::Script => Ok((input, TagData::Script)), 112 | } 113 | } 114 | 115 | #[allow(non_camel_case_types)] 116 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 117 | pub enum SoundFormat { 118 | PCM_NE, // native endianness... 119 | ADPCM, 120 | MP3, 121 | PCM_LE, 122 | NELLYMOSER_16KHZ_MONO, 123 | NELLYMOSER_8KHZ_MONO, 124 | NELLYMOSER, 125 | PCM_ALAW, 126 | PCM_ULAW, 127 | AAC, 128 | SPEEX, 129 | MP3_8KHZ, 130 | DEVICE_SPECIFIC, 131 | } 132 | 133 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 134 | pub enum SoundRate { 135 | _5_5KHZ, 136 | _11KHZ, 137 | _22KHZ, 138 | _44KHZ, 139 | } 140 | 141 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 142 | pub enum SoundSize { 143 | Snd8bit, 144 | Snd16bit, 145 | } 146 | 147 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 148 | pub enum SoundType { 149 | SndMono, 150 | SndStereo, 151 | } 152 | 153 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 154 | pub enum AACPacketType { 155 | SequenceHeader, 156 | Raw, 157 | } 158 | 159 | #[derive(Clone, Debug, PartialEq, Eq)] 160 | pub struct AACAudioPacketHeader { 161 | pub packet_type: AACPacketType, 162 | } 163 | 164 | pub fn aac_audio_packet_header(input: &[u8]) -> IResult<&[u8], AACAudioPacketHeader> { 165 | map_res(be_u8, |packet_type| { 166 | Ok(AACAudioPacketHeader { 167 | packet_type: match packet_type { 168 | 0 => AACPacketType::SequenceHeader, 169 | 1 => AACPacketType::Raw, 170 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 171 | }, 172 | }) 173 | })(input) 174 | } 175 | 176 | #[derive(Debug, PartialEq, Eq)] 177 | pub struct AACAudioPacket<'a> { 178 | pub packet_type: AACPacketType, 179 | pub aac_data: &'a [u8], 180 | } 181 | 182 | pub fn aac_audio_packet(input: &[u8], size: usize) -> IResult<&[u8], AACAudioPacket> { 183 | if input.len() < size { 184 | return Err(Err::Incomplete(Needed::new(size))); 185 | } 186 | 187 | if size < 1 { 188 | return Err(Err::Incomplete(Needed::new(1))); 189 | } 190 | 191 | be_u8(input).and_then(|(_, packet_type)| { 192 | Ok(( 193 | &input[size..], 194 | AACAudioPacket { 195 | packet_type: match packet_type { 196 | 0 => AACPacketType::SequenceHeader, 197 | 1 => AACPacketType::Raw, 198 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 199 | }, 200 | aac_data: &input[1..size], 201 | }, 202 | )) 203 | }) 204 | } 205 | 206 | #[derive(Clone, Debug, PartialEq, Eq)] 207 | pub struct AudioData<'a> { 208 | pub sound_format: SoundFormat, 209 | pub sound_rate: SoundRate, 210 | pub sound_size: SoundSize, 211 | pub sound_type: SoundType, 212 | pub sound_data: &'a [u8], 213 | } 214 | 215 | pub fn audio_data(input: &[u8], size: usize) -> IResult<&[u8], AudioData> { 216 | if input.len() < size { 217 | return Err(Err::Incomplete(Needed::new(size))); 218 | } 219 | 220 | if size < 1 { 221 | return Err(Err::Incomplete(Needed::new(1))); 222 | } 223 | 224 | let take_bits = tuple((take(4usize), take(2usize), take(1usize), take(1usize))); 225 | bits::<_, _, Error<_>, _, _>(take_bits)(input).and_then( 226 | |(_, (sformat, srate, ssize, stype))| { 227 | let sformat = match sformat { 228 | 0 => SoundFormat::PCM_NE, 229 | 1 => SoundFormat::ADPCM, 230 | 2 => SoundFormat::MP3, 231 | 3 => SoundFormat::PCM_LE, 232 | 4 => SoundFormat::NELLYMOSER_16KHZ_MONO, 233 | 5 => SoundFormat::NELLYMOSER_8KHZ_MONO, 234 | 6 => SoundFormat::NELLYMOSER, 235 | 7 => SoundFormat::PCM_ALAW, 236 | 8 => SoundFormat::PCM_ULAW, 237 | 10 => SoundFormat::AAC, 238 | 11 => SoundFormat::SPEEX, 239 | 14 => SoundFormat::MP3_8KHZ, 240 | 15 => SoundFormat::DEVICE_SPECIFIC, 241 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 242 | }; 243 | let srate = match srate { 244 | 0 => SoundRate::_5_5KHZ, 245 | 1 => SoundRate::_11KHZ, 246 | 2 => SoundRate::_22KHZ, 247 | 3 => SoundRate::_44KHZ, 248 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 249 | }; 250 | let ssize = match ssize { 251 | 0 => SoundSize::Snd8bit, 252 | 1 => SoundSize::Snd16bit, 253 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 254 | }; 255 | let stype = match stype { 256 | 0 => SoundType::SndMono, 257 | 1 => SoundType::SndStereo, 258 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 259 | }; 260 | 261 | Ok(( 262 | &input[size..], 263 | AudioData { 264 | sound_format: sformat, 265 | sound_rate: srate, 266 | sound_size: ssize, 267 | sound_type: stype, 268 | sound_data: &input[1..size], 269 | }, 270 | )) 271 | }, 272 | ) 273 | } 274 | 275 | #[derive(Debug, PartialEq, Eq)] 276 | pub struct AudioDataHeader { 277 | pub sound_format: SoundFormat, 278 | pub sound_rate: SoundRate, 279 | pub sound_size: SoundSize, 280 | pub sound_type: SoundType, 281 | } 282 | 283 | pub fn audio_data_header(input: &[u8]) -> IResult<&[u8], AudioDataHeader> { 284 | if input.is_empty() { 285 | return Err(Err::Incomplete(Needed::new(1))); 286 | } 287 | 288 | let take_bits = tuple((take(4usize), take(2usize), take(1usize), take(1usize))); 289 | map_res( 290 | bits::<_, _, Error<_>, _, _>(take_bits), 291 | |(sformat, srate, ssize, stype)| { 292 | let sformat = match sformat { 293 | 0 => SoundFormat::PCM_NE, 294 | 1 => SoundFormat::ADPCM, 295 | 2 => SoundFormat::MP3, 296 | 3 => SoundFormat::PCM_LE, 297 | 4 => SoundFormat::NELLYMOSER_16KHZ_MONO, 298 | 5 => SoundFormat::NELLYMOSER_8KHZ_MONO, 299 | 6 => SoundFormat::NELLYMOSER, 300 | 7 => SoundFormat::PCM_ALAW, 301 | 8 => SoundFormat::PCM_ULAW, 302 | 10 => SoundFormat::AAC, 303 | 11 => SoundFormat::SPEEX, 304 | 14 => SoundFormat::MP3_8KHZ, 305 | 15 => SoundFormat::DEVICE_SPECIFIC, 306 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 307 | }; 308 | let srate = match srate { 309 | 0 => SoundRate::_5_5KHZ, 310 | 1 => SoundRate::_11KHZ, 311 | 2 => SoundRate::_22KHZ, 312 | 3 => SoundRate::_44KHZ, 313 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 314 | }; 315 | let ssize = match ssize { 316 | 0 => SoundSize::Snd8bit, 317 | 1 => SoundSize::Snd16bit, 318 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 319 | }; 320 | let stype = match stype { 321 | 0 => SoundType::SndMono, 322 | 1 => SoundType::SndStereo, 323 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 324 | }; 325 | 326 | Ok(AudioDataHeader { 327 | sound_format: sformat, 328 | sound_rate: srate, 329 | sound_size: ssize, 330 | sound_type: stype, 331 | }) 332 | }, 333 | )(input) 334 | } 335 | 336 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 337 | pub enum FrameType { 338 | Key, 339 | Inter, 340 | DisposableInter, 341 | Generated, 342 | Command, 343 | } 344 | 345 | #[allow(non_camel_case_types)] 346 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 347 | pub enum CodecId { 348 | JPEG, 349 | SORENSON_H263, 350 | SCREEN, 351 | VP6, 352 | VP6A, 353 | SCREEN2, 354 | H264, 355 | // Not in FLV standard 356 | H263, 357 | MPEG4Part2, // MPEG-4 Part 2 358 | } 359 | 360 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] 361 | pub enum AVCPacketType { 362 | SequenceHeader, 363 | NALU, 364 | EndOfSequence, 365 | } 366 | 367 | #[derive(Clone, Debug, PartialEq, Eq)] 368 | pub struct AVCVideoPacketHeader { 369 | pub packet_type: AVCPacketType, 370 | pub composition_time: i32, 371 | } 372 | 373 | fn packet_type(input: &[u8]) -> IResult<&[u8], AVCPacketType> { 374 | map_res(be_u8, |packet_type| { 375 | Ok(match packet_type { 376 | 0 => AVCPacketType::SequenceHeader, 377 | 1 => AVCPacketType::NALU, 378 | 2 => AVCPacketType::EndOfSequence, 379 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 380 | }) 381 | })(input) 382 | } 383 | 384 | pub fn avc_video_packet_header(input: &[u8]) -> IResult<&[u8], AVCVideoPacketHeader> { 385 | map( 386 | pair(packet_type, be_i24), 387 | |(packet_type, composition_time)| AVCVideoPacketHeader { 388 | packet_type, 389 | composition_time, 390 | }, 391 | )(input) 392 | } 393 | 394 | #[derive(Debug, PartialEq, Eq)] 395 | pub struct AVCVideoPacket<'a> { 396 | pub packet_type: AVCPacketType, 397 | pub composition_time: i32, 398 | pub avc_data: &'a [u8], 399 | } 400 | 401 | pub fn avc_video_packet(input: &[u8], size: usize) -> IResult<&[u8], AVCVideoPacket> { 402 | if input.len() < size { 403 | return Err(Err::Incomplete(Needed::new(size))); 404 | } 405 | 406 | if size < 4 { 407 | return Err(Err::Incomplete(Needed::new(4))); 408 | } 409 | pair(packet_type, be_i24)(input).map(|(_, (packet_type, composition_time))| { 410 | ( 411 | &input[size..], 412 | AVCVideoPacket { 413 | packet_type, 414 | composition_time, 415 | avc_data: &input[4..size], 416 | }, 417 | ) 418 | }) 419 | } 420 | 421 | #[derive(Clone, Debug, PartialEq, Eq)] 422 | pub struct VideoData<'a> { 423 | pub frame_type: FrameType, 424 | pub codec_id: CodecId, 425 | pub video_data: &'a [u8], 426 | } 427 | 428 | pub fn video_data(input: &[u8], size: usize) -> IResult<&[u8], VideoData> { 429 | if input.len() < size { 430 | return Err(Err::Incomplete(Needed::new(size))); 431 | } 432 | 433 | if size < 1 { 434 | return Err(Err::Incomplete(Needed::new(1))); 435 | } 436 | 437 | let take_bits = pair(take(4usize), take(4usize)); 438 | bits::<_, _, Error<_>, _, _>(take_bits)(input).and_then(|(_, (frame_type, codec_id))| { 439 | let frame_type = match frame_type { 440 | 1 => FrameType::Key, 441 | 2 => FrameType::Inter, 442 | 3 => FrameType::DisposableInter, 443 | 4 => FrameType::Generated, 444 | 5 => FrameType::Command, 445 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 446 | }; 447 | let codec_id = match codec_id { 448 | 1 => CodecId::JPEG, 449 | 2 => CodecId::SORENSON_H263, 450 | 3 => CodecId::SCREEN, 451 | 4 => CodecId::VP6, 452 | 5 => CodecId::VP6A, 453 | 6 => CodecId::SCREEN2, 454 | 7 => CodecId::H264, 455 | 8 => CodecId::H263, 456 | 9 => CodecId::MPEG4Part2, 457 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 458 | }; 459 | 460 | Ok(( 461 | &input[size..], 462 | VideoData { 463 | frame_type, 464 | codec_id, 465 | video_data: &input[1..size], 466 | }, 467 | )) 468 | }) 469 | } 470 | 471 | #[derive(Clone, Debug, PartialEq, Eq)] 472 | pub struct VideoDataHeader { 473 | pub frame_type: FrameType, 474 | pub codec_id: CodecId, 475 | } 476 | 477 | pub fn video_data_header(input: &[u8]) -> IResult<&[u8], VideoDataHeader> { 478 | if input.is_empty() { 479 | return Err(Err::Incomplete(Needed::new(1))); 480 | } 481 | 482 | let take_bits = pair(take(4usize), take(4usize)); 483 | map_res( 484 | bits::<_, _, Error<_>, _, _>(take_bits), 485 | |(frame_type, codec_id)| { 486 | let frame_type = match frame_type { 487 | 1 => FrameType::Key, 488 | 2 => FrameType::Inter, 489 | 3 => FrameType::DisposableInter, 490 | 4 => FrameType::Generated, 491 | 5 => FrameType::Command, 492 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 493 | }; 494 | let codec_id = match codec_id { 495 | 1 => CodecId::JPEG, 496 | 2 => CodecId::SORENSON_H263, 497 | 3 => CodecId::SCREEN, 498 | 4 => CodecId::VP6, 499 | 5 => CodecId::VP6A, 500 | 6 => CodecId::SCREEN2, 501 | 7 => CodecId::H264, 502 | 8 => CodecId::H263, 503 | 9 => CodecId::MPEG4Part2, 504 | _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), 505 | }; 506 | 507 | Ok(VideoDataHeader { 508 | frame_type, 509 | codec_id, 510 | }) 511 | }, 512 | )(input) 513 | } 514 | 515 | #[derive(Debug, PartialEq, Serialize)] 516 | pub struct ScriptData<'a> { 517 | pub name: &'a str, 518 | pub arguments: ScriptDataValue<'a>, 519 | } 520 | 521 | #[derive(Debug, PartialEq, Serialize)] 522 | pub enum ScriptDataValue<'a> { 523 | Number(f64), 524 | Boolean(bool), 525 | String(&'a str), 526 | Object(Vec>), 527 | MovieClip(&'a str), 528 | Null, 529 | Undefined, 530 | Reference(u16), 531 | ECMAArray(Vec>), 532 | StrictArray(Vec>), 533 | Date(ScriptDataDate), 534 | LongString(&'a str), 535 | } 536 | 537 | #[derive(Debug, PartialEq, Serialize)] 538 | pub struct ScriptDataObject<'a> { 539 | pub name: &'a str, 540 | pub data: ScriptDataValue<'a>, 541 | } 542 | 543 | #[derive(Clone, Debug, PartialEq, Serialize)] 544 | pub struct ScriptDataDate { 545 | pub date_time: f64, 546 | pub local_date_time_offset: i16, // SI16 547 | } 548 | 549 | #[allow(non_upper_case_globals)] 550 | static script_data_name_tag: &[u8] = &[2]; 551 | 552 | pub fn script_data(input: &[u8]) -> IResult<&[u8], ScriptData> { 553 | // Must start with a string, i.e. 2 554 | map( 555 | tuple(( 556 | tag(script_data_name_tag), 557 | script_data_string, 558 | script_data_value, 559 | )), 560 | |(_, name, arguments)| ScriptData { name, arguments }, 561 | )(input) 562 | } 563 | 564 | pub fn script_data_value(input: &[u8]) -> IResult<&[u8], ScriptDataValue> { 565 | be_u8(input).and_then(|v| match v { 566 | (i, 0) => map(be_f64, ScriptDataValue::Number)(i), 567 | (i, 1) => map(be_u8, |n| ScriptDataValue::Boolean(n != 0))(i), 568 | (i, 2) => map(script_data_string, ScriptDataValue::String)(i), 569 | (i, 3) => map(script_data_objects, ScriptDataValue::Object)(i), 570 | (i, 4) => map(script_data_string, ScriptDataValue::MovieClip)(i), 571 | (i, 5) => Ok((i, ScriptDataValue::Null)), // to remove 572 | (i, 6) => Ok((i, ScriptDataValue::Undefined)), // to remove 573 | (i, 7) => map(be_u16, ScriptDataValue::Reference)(i), 574 | (i, 8) => map(script_data_ecma_array, ScriptDataValue::ECMAArray)(i), 575 | (i, 10) => map(script_data_strict_array, ScriptDataValue::StrictArray)(i), 576 | (i, 11) => map(script_data_date, ScriptDataValue::Date)(i), 577 | (i, 12) => map(script_data_long_string, ScriptDataValue::LongString)(i), 578 | _ => Err(Err::Error(Error::new(input, ErrorKind::Alt))), 579 | }) 580 | } 581 | 582 | pub fn script_data_objects(input: &[u8]) -> IResult<&[u8], Vec> { 583 | terminated(many0(script_data_object), script_data_object_end)(input) 584 | } 585 | 586 | pub fn script_data_object(input: &[u8]) -> IResult<&[u8], ScriptDataObject> { 587 | map( 588 | pair(script_data_string, script_data_value), 589 | |(name, data)| ScriptDataObject { name, data }, 590 | )(input) 591 | } 592 | 593 | #[allow(non_upper_case_globals)] 594 | static script_data_object_end_terminator: &[u8] = &[0, 0, 9]; 595 | 596 | pub fn script_data_object_end(input: &[u8]) -> IResult<&[u8], &[u8]> { 597 | tag(script_data_object_end_terminator)(input) 598 | } 599 | 600 | pub fn script_data_string(input: &[u8]) -> IResult<&[u8], &str> { 601 | map_res(length_data(be_u16), from_utf8)(input) 602 | } 603 | 604 | pub fn script_data_long_string(input: &[u8]) -> IResult<&[u8], &str> { 605 | map_res(length_data(be_u32), from_utf8)(input) 606 | } 607 | 608 | pub fn script_data_date(input: &[u8]) -> IResult<&[u8], ScriptDataDate> { 609 | map( 610 | pair(be_f64, be_i16), 611 | |(date_time, local_date_time_offset)| ScriptDataDate { 612 | date_time, 613 | local_date_time_offset, 614 | }, 615 | )(input) 616 | } 617 | 618 | pub fn script_data_ecma_array(input: &[u8]) -> IResult<&[u8], Vec> { 619 | map(pair(be_u32, script_data_objects), |(_, data_objects)| { 620 | data_objects 621 | })(input) 622 | } 623 | 624 | pub fn script_data_strict_array(input: &[u8]) -> IResult<&[u8], Vec> { 625 | flat_map(be_u32, |o| many_m_n(1, o as usize, script_data_value))(input) 626 | } 627 | -------------------------------------------------------------------------------- /src/flv_writer.rs: -------------------------------------------------------------------------------- 1 | use crate::downloader::util; 2 | use crate::flv_parser::{ 3 | AACPacketType, AVCPacketType, CodecId, FrameType, ScriptData, SoundFormat, SoundRate, 4 | SoundSize, SoundType, TagHeader, 5 | }; 6 | use byteorder::{BigEndian, WriteBytesExt}; 7 | use serde::Serialize; 8 | use std::fs::File; 9 | use std::io::{BufWriter, Write}; 10 | use tracing::error; 11 | 12 | const FLV_HEADER: [u8; 9] = [ 13 | 0x46, // 'F' 14 | 0x4c, //'L' 15 | 0x56, //'V' 16 | 0x01, //version 17 | 0x05, //00000101 audio tag and video tag 18 | 0x00, 0x00, 0x00, 0x09, //flv header size 19 | ]; // 9 20 | 21 | pub struct FlvFile { 22 | pub buf_writer: BufWriter, 23 | pub name: String, 24 | } 25 | 26 | impl FlvFile { 27 | pub fn new(file_name: &str) -> std::io::Result { 28 | let file_name = util::format_filename(file_name); 29 | let out = 30 | File::create(format!("{file_name}.flv.part")).expect("Unable to create flv file."); 31 | let mut buf_writer = BufWriter::new(out); 32 | buf_writer.write_all(&FLV_HEADER)?; 33 | Self::write_previous_tag_size(&mut buf_writer, 0)?; 34 | Ok(Self { 35 | buf_writer, 36 | name: file_name, 37 | }) 38 | } 39 | 40 | pub fn write_tag( 41 | &mut self, 42 | tag_header: &TagHeader, 43 | body: &[u8], 44 | previous_tag_size: &[u8], 45 | ) -> std::io::Result { 46 | self.write_tag_header(tag_header)?; 47 | self.buf_writer.write_all(body)?; 48 | self.buf_writer.write(previous_tag_size) 49 | } 50 | 51 | pub fn write_tag_header(&mut self, tag_header: &TagHeader) -> std::io::Result<()> { 52 | self.buf_writer.write_u8(tag_header.tag_type as u8)?; 53 | self.buf_writer 54 | .write_u24::(tag_header.data_size)?; 55 | self.buf_writer 56 | .write_u24::(tag_header.timestamp & 0xffffff)?; 57 | let timestamp_ext = (tag_header.timestamp >> 24 & 0xff) as u8; 58 | self.buf_writer.write_u8(timestamp_ext)?; 59 | self.buf_writer.write_u24::(tag_header.stream_id) 60 | } 61 | 62 | pub fn write_previous_tag_size( 63 | writer: &mut impl Write, 64 | previous_tag_size: u32, 65 | ) -> std::io::Result { 66 | writer.write(&previous_tag_size.to_be_bytes()) 67 | } 68 | } 69 | 70 | impl Drop for FlvFile { 71 | fn drop(&mut self) { 72 | std::fs::rename( 73 | format!("{}.flv.part", self.name), 74 | format!("{}.flv", self.name), 75 | ) 76 | .unwrap_or_else(|e| error!("{e}")) 77 | } 78 | } 79 | 80 | // pub fn create_flv_file(file_name: &str) -> std::io::Result { 81 | // 82 | // } 83 | 84 | #[derive(Debug, PartialEq, Serialize)] 85 | pub struct FlvTag<'a> { 86 | pub header: TagHeader, 87 | pub data: TagDataHeader<'a>, 88 | } 89 | 90 | pub fn to_json(mut writer: impl Write, t: &T) -> std::io::Result { 91 | serde_json::to_writer(&mut writer, t)?; 92 | writer.write("\n".as_ref()) 93 | } 94 | 95 | #[derive(Debug, PartialEq, Serialize)] 96 | pub enum TagDataHeader<'a> { 97 | Audio { 98 | sound_format: SoundFormat, 99 | sound_rate: SoundRate, 100 | sound_size: SoundSize, 101 | sound_type: SoundType, 102 | packet_type: Option, 103 | }, 104 | Video { 105 | frame_type: FrameType, 106 | codec_id: CodecId, 107 | packet_type: Option, 108 | composition_time: Option, 109 | }, 110 | Script(ScriptData<'a>), 111 | } 112 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod downloader; 2 | pub mod error; 3 | pub mod flv_parser; 4 | pub mod flv_writer; 5 | mod uploader; 6 | mod login; 7 | 8 | use crate::downloader::construct_headers; 9 | use crate::uploader::UploadLine; 10 | 11 | use pyo3::prelude::*; 12 | 13 | use downloader::util::Segment; 14 | use std::collections::HashMap; 15 | use std::path::PathBuf; 16 | use std::time::Duration; 17 | 18 | use tracing_subscriber::layer::SubscriberExt; 19 | 20 | #[derive(FromPyObject)] 21 | pub enum PySegment { 22 | Time { 23 | #[pyo3(attribute("time"))] 24 | time: u64, 25 | }, 26 | Size { 27 | #[pyo3(attribute("size"))] 28 | size: u64, 29 | }, 30 | } 31 | 32 | #[pyfunction] 33 | fn download( 34 | py: Python<'_>, 35 | url: &str, 36 | header_map: HashMap, 37 | file_name: &str, 38 | segment: PySegment, 39 | ) -> PyResult<()> { 40 | py.allow_threads(|| { 41 | let map = construct_headers(header_map); 42 | // 输出到控制台中 43 | let formatting_layer = tracing_subscriber::FmtSubscriber::builder() 44 | // will be written to stdout. 45 | // builds the subscriber. 46 | .finish(); 47 | let file_appender = tracing_appender::rolling::never("", "download.log"); 48 | let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 49 | let file_layer = tracing_subscriber::fmt::layer() 50 | .with_ansi(false) 51 | .with_writer(non_blocking); 52 | 53 | let collector = formatting_layer.with(file_layer); 54 | let segment = match segment { 55 | PySegment::Time { time } => { 56 | Segment::Time(Duration::from_secs(time), Duration::default()) 57 | } 58 | PySegment::Size { size } => Segment::Size(size, 0), 59 | }; 60 | tracing::subscriber::with_default(collector, || -> PyResult<()> { 61 | match downloader::download(url, map, file_name, segment) { 62 | Ok(res) => Ok(res), 63 | // Ok(_) => { }, 64 | Err(err) => { 65 | return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( 66 | "{}, {}", 67 | err.root_cause(), 68 | err 69 | ))); 70 | } 71 | } 72 | }) 73 | }) 74 | } 75 | #[pyfunction] 76 | fn login_by_cookies()->PyResult{ 77 | let rt = tokio::runtime::Runtime::new().unwrap(); 78 | let result =rt.block_on(async { 79 | login::login_by_cookies().await 80 | }); 81 | match result{ 82 | Ok(_) => Ok(true), 83 | Err(err) => { 84 | return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( 85 | "{}, {}", 86 | err.root_cause(), 87 | err 88 | ))); 89 | } 90 | } 91 | 92 | 93 | } 94 | #[pyfunction] 95 | fn send_sms(country_code:u32,phone:u64) -> PyResult { 96 | let rt = tokio::runtime::Runtime::new().unwrap(); 97 | let result= rt.block_on(async { 98 | login::send_sms(country_code,phone).await 99 | }); 100 | match result{ 101 | Ok(res)=>{ 102 | Ok(res.to_string()) 103 | } 104 | Err(err)=>{ 105 | Err(pyo3::exceptions::PyRuntimeError::new_err(format!("{}",err))) 106 | } 107 | } 108 | } 109 | #[pyfunction] 110 | fn login_by_sms(code:u32, ret:String) -> PyResult{ 111 | let rt = tokio::runtime::Runtime::new().unwrap(); 112 | let result= rt.block_on(async { 113 | login::login_by_sms(code,serde_json::from_str(&ret).unwrap()).await 114 | }); 115 | match result 116 | { 117 | Ok(_)=>Ok(true), 118 | Err(_)=>Ok(false), 119 | } 120 | } 121 | #[pyfunction] 122 | fn get_qrcode()->PyResult{ 123 | let rt = tokio::runtime::Runtime::new().unwrap(); 124 | let result= rt.block_on(async { 125 | login::get_qrcode().await 126 | }); 127 | match result{ 128 | Ok(res)=>{ 129 | Ok(res.to_string()) 130 | } 131 | Err(err)=>{ 132 | Err(pyo3::exceptions::PyRuntimeError::new_err(format!("{}",err))) 133 | } 134 | } 135 | } 136 | #[pyfunction] 137 | fn login_by_qrcode(ret:String) -> PyResult{ 138 | let rt = tokio::runtime::Runtime::new().unwrap(); 139 | let result= rt.block_on(async { 140 | login::login_by_qrcode(serde_json::from_str(&ret).unwrap()).await 141 | }); 142 | match result 143 | { 144 | Ok(_)=>Ok(true), 145 | Err(err)=>{ 146 | Err(pyo3::exceptions::PyRuntimeError::new_err(format!("{}",err))) 147 | }, 148 | } 149 | 150 | } 151 | 152 | #[pyfunction] 153 | fn upload( 154 | py: Python<'_>, 155 | video_path: Vec, 156 | cookie_file: PathBuf, 157 | title: String, 158 | tid: u16, 159 | tag: String, 160 | copyright: u8, 161 | source: String, 162 | desc: String, 163 | dynamic: String, 164 | cover: String, 165 | dtime: Option, 166 | line: Option, 167 | limit: usize, 168 | ) -> PyResult<()> { 169 | py.allow_threads(|| { 170 | let rt = tokio::runtime::Builder::new_current_thread() 171 | .enable_all() 172 | .build()?; 173 | // 输出到控制台中 174 | let formatting_layer = tracing_subscriber::FmtSubscriber::builder() 175 | // will be written to stdout. 176 | // builds the subscriber. 177 | .finish(); 178 | let file_appender = tracing_appender::rolling::never("", "upload.log"); 179 | let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 180 | let file_layer = tracing_subscriber::fmt::layer() 181 | .with_ansi(false) 182 | .with_writer(non_blocking); 183 | 184 | let collector = formatting_layer.with(file_layer); 185 | 186 | tracing::subscriber::with_default(collector, || -> PyResult<()> { 187 | match rt.block_on(uploader::upload( 188 | video_path, 189 | cookie_file, 190 | line, 191 | limit, 192 | title, 193 | tid, 194 | tag, 195 | copyright, 196 | source, 197 | desc, 198 | dynamic, 199 | cover, 200 | dtime, 201 | )) { 202 | Ok(_res) => Ok(()), 203 | // Ok(_) => { }, 204 | Err(err) => { 205 | return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( 206 | "{}, {}", 207 | err.root_cause(), 208 | err 209 | ))); 210 | } 211 | } 212 | }) 213 | }) 214 | } 215 | 216 | /// A Python module implemented in Rust. 217 | #[pymodule] 218 | fn stream_gears(_py: Python, m: &PyModule) -> PyResult<()> { 219 | // let file_appender = tracing_appender::rolling::daily("", "upload.log"); 220 | // let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 221 | // tracing_subscriber::fmt() 222 | // .with_writer(non_blocking) 223 | // .init(); 224 | m.add_function(wrap_pyfunction!(upload, m)?)?; 225 | m.add_function(wrap_pyfunction!(download, m)?)?; 226 | m.add_function(wrap_pyfunction!(login_by_cookies, m)?)?; 227 | m.add_function(wrap_pyfunction!(send_sms, m)?)?; 228 | m.add_function(wrap_pyfunction!(login_by_qrcode, m)?)?; 229 | m.add_function(wrap_pyfunction!(get_qrcode, m)?)?; 230 | // m.add_function(wrap_pyfunction(login_by_sms, m)?)?; 231 | m.add_class::()?; 232 | Ok(()) 233 | } 234 | -------------------------------------------------------------------------------- /src/login.rs: -------------------------------------------------------------------------------- 1 | 2 | use anyhow::{Result}; 3 | use biliup::client::Client; 4 | use biliup::{client,}; 5 | 6 | pub async fn login_by_cookies()->Result{ 7 | let login_info= Client::new().login_by_cookies(std::fs::File::open("cookies.json")?).await?; 8 | Ok(login_info) 9 | } 10 | pub async fn send_sms(country_code: u32, phone: u64) -> Result { 11 | let ret = Client::new().send_sms(phone, country_code).await?; 12 | Ok(ret) 13 | } 14 | pub async fn login_by_sms(code: u32, res: serde_json::Value) -> Result { 15 | let info = Client::new().login_by_sms(code, res).await?; 16 | let file = std::fs::File::create("cookies.json")?; 17 | serde_json::to_writer_pretty(&file, &info)?; 18 | Ok(true) 19 | } 20 | pub async fn get_qrcode() -> Result { 21 | let qrcode = Client::new().get_qrcode().await?; 22 | Ok(qrcode) 23 | } 24 | pub async fn login_by_qrcode(res: serde_json::Value) -> Result { 25 | let info = Client::new().login_by_qrcode(res).await?; 26 | let file = std::fs::File::create("cookies.json")?; 27 | serde_json::to_writer_pretty(&file, &info)?; 28 | Ok(true) 29 | } 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 2 | use std::env; 3 | use std::io::{BufReader, BufWriter, ErrorKind, Read}; 4 | use std::time::Duration; 5 | use stream_gears::downloader::httpflv::{download, map_parse_err, Connection}; 6 | use stream_gears::downloader::util::Segment; 7 | use stream_gears::error::Error; 8 | use stream_gears::flv_parser::{ 9 | aac_audio_packet_header, avc_video_packet_header, header, script_data, tag_data, tag_header, 10 | CodecId, SoundFormat, TagData, 11 | }; 12 | use stream_gears::flv_writer::{self, FlvTag, TagDataHeader}; 13 | 14 | fn main() -> Result<(), Error> { 15 | tracing_subscriber::fmt::init(); 16 | 17 | let args: Vec = env::args().collect(); 18 | let file_name = &args[1]; 19 | let flv_file = std::fs::File::open(file_name)?; 20 | let buf_reader = BufReader::new(flv_file); 21 | let mut connection = Connection::new(buf_reader); 22 | connection.read_frame(9)?; 23 | download( 24 | connection, 25 | &(file_name.to_owned() + "new%H_%M_%S%.f"), 26 | Segment::Time(Duration::from_secs(60 * 60 * 24), Default::default()), 27 | ); 28 | // Ok(result) 29 | // generate_json()?; 30 | Ok(()) 31 | } 32 | 33 | fn generate_json() -> Result<(), Error> { 34 | let args: Vec = env::args().collect(); 35 | let file_name = &args[1]; 36 | let flv_file = std::fs::File::open(file_name)?; 37 | let buf_reader = BufReader::new(flv_file); 38 | let mut reader = Reader::new(buf_reader); 39 | 40 | let mut script_tag_count = 0; 41 | let mut audio_tag_count = 0; 42 | let mut video_tag_count = 0; 43 | let mut tag_count = 0; 44 | let _err_count = 0; 45 | let flv_header = reader.read_frame(9)?; 46 | let file = std::fs::File::create(format!("{file_name}.json"))?; 47 | let mut writer = BufWriter::new(file); 48 | // Vec::clear() 49 | let (_, header) = map_parse_err(header(&flv_header), "flv header")?; 50 | flv_writer::to_json(&mut writer, &header)?; 51 | loop { 52 | let _previous_tag_size = reader.read_frame(4)?; 53 | 54 | let t_header = reader.read_frame(11)?; 55 | if t_header.is_empty() { 56 | break; 57 | } 58 | let tag_header = match map_parse_err(tag_header(&t_header), "tag header") { 59 | Ok((_, tag_header)) => tag_header, 60 | Err(e) => { 61 | println!("{e}"); 62 | break; 63 | } 64 | }; 65 | tag_count += 1; 66 | let bytes = reader.read_frame(tag_header.data_size as usize)?; 67 | let (i, flv_tag_data) = match map_parse_err( 68 | tag_data(tag_header.tag_type, tag_header.data_size as usize)(&bytes), 69 | "tag data", 70 | ) { 71 | Ok((i, flv_tag_data)) => (i, flv_tag_data), 72 | Err(e) => { 73 | println!("{e}"); 74 | break; 75 | } 76 | }; 77 | 78 | let flv_tag = match flv_tag_data { 79 | TagData::Audio(audio_data) => { 80 | audio_tag_count += 1; 81 | 82 | let packet_type = if audio_data.sound_format == SoundFormat::AAC { 83 | let (_, packet_header) = 84 | aac_audio_packet_header(audio_data.sound_data).unwrap(); 85 | Some(packet_header.packet_type) 86 | } else { 87 | None 88 | }; 89 | 90 | FlvTag { 91 | header: tag_header, 92 | data: TagDataHeader::Audio { 93 | sound_format: audio_data.sound_format, 94 | sound_rate: audio_data.sound_rate, 95 | sound_size: audio_data.sound_size, 96 | sound_type: audio_data.sound_type, 97 | packet_type, 98 | }, 99 | } 100 | } 101 | TagData::Video(video_data) => { 102 | video_tag_count += 1; 103 | 104 | let (packet_type, composition_time) = if CodecId::H264 == video_data.codec_id { 105 | let (_, avc_video_header) = 106 | avc_video_packet_header(video_data.video_data).unwrap(); 107 | ( 108 | Some(avc_video_header.packet_type), 109 | Some(avc_video_header.composition_time), 110 | ) 111 | } else { 112 | (None, None) 113 | }; 114 | 115 | FlvTag { 116 | header: tag_header, 117 | data: TagDataHeader::Video { 118 | frame_type: video_data.frame_type, 119 | codec_id: video_data.codec_id, 120 | packet_type, 121 | composition_time, 122 | }, 123 | } 124 | } 125 | TagData::Script => { 126 | script_tag_count += 1; 127 | 128 | let (_, tag_data) = script_data(i).unwrap(); 129 | let flv_tag = FlvTag { 130 | header: tag_header, 131 | data: TagDataHeader::Script(tag_data), 132 | }; 133 | flv_tag 134 | } 135 | }; 136 | flv_writer::to_json(&mut writer, &flv_tag)?; 137 | } 138 | println!("tag count: {tag_count}"); 139 | println!("audio tag count: {audio_tag_count}"); 140 | println!("script tag count: {script_tag_count}"); 141 | println!("video tag count: {video_tag_count}"); 142 | Ok(()) 143 | } 144 | 145 | pub struct Reader { 146 | read: T, 147 | buffer: BytesMut, 148 | } 149 | 150 | impl Reader { 151 | fn new(read: T) -> Reader { 152 | Reader { 153 | read, 154 | buffer: BytesMut::with_capacity(8 * 1024), 155 | } 156 | } 157 | 158 | fn read_frame(&mut self, chunk_size: usize) -> std::io::Result { 159 | let mut buf = [0u8; 8 * 1024]; 160 | loop { 161 | if chunk_size <= self.buffer.len() { 162 | let bytes = Bytes::copy_from_slice(&self.buffer[..chunk_size]); 163 | self.buffer.advance(chunk_size as usize); 164 | return Ok(bytes); 165 | } 166 | // BytesMut::with_capacity(0).deref_mut() 167 | // tokio::fs::File::open("").read() 168 | // self.read_buf. 169 | let n = match self.read.read(&mut buf) { 170 | Ok(n) => n, 171 | Err(e) if e.kind() == ErrorKind::Interrupted => continue, 172 | Err(e) => return Err(e), 173 | }; 174 | if n == 0 { 175 | return Ok(self.buffer.split().freeze()); 176 | } 177 | self.buffer.put_slice(&buf[..n]); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/uploader.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use biliup::client::Client; 3 | use biliup::line::{self, Probe}; 4 | use biliup::video::{BiliBili, Studio}; 5 | use biliup::VideoFile; 6 | use futures::StreamExt; 7 | use pyo3::pyclass; 8 | use serde_json::Value; 9 | use std::path::PathBuf; 10 | use std::time::Instant; 11 | use tracing::info; 12 | 13 | #[pyclass] 14 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] 15 | pub enum UploadLine { 16 | Bda2, 17 | Ws, 18 | Qn, 19 | Kodo, 20 | Cos, 21 | CosInternal, 22 | } 23 | 24 | pub async fn upload( 25 | video_path: Vec, 26 | cookie_file: PathBuf, 27 | line: Option, 28 | limit: usize, 29 | title: String, 30 | tid: u16, 31 | tag: String, 32 | copyright: u8, 33 | source: String, 34 | desc: String, 35 | dynamic: String, 36 | cover: String, 37 | dtime: Option, 38 | ) -> Result { 39 | let client: Client = Default::default(); 40 | let file = std::fs::File::options() 41 | .read(true) 42 | .write(true) 43 | .open(&cookie_file); 44 | let login_info = client 45 | .login_by_cookies(file.with_context(|| cookie_file.to_str().unwrap().to_string())?) 46 | .await?; 47 | let mut videos = Vec::new(); 48 | let line = match line { 49 | Some(UploadLine::Kodo) => line::kodo(), 50 | Some(UploadLine::Bda2) => line::bda2(), 51 | Some(UploadLine::Ws) => line::ws(), 52 | Some(UploadLine::Qn) => line::qn(), 53 | Some(UploadLine::Cos) => line::cos(), 54 | Some(UploadLine::CosInternal) => line::cos_internal(), 55 | None => Probe::probe().await.unwrap_or_default(), 56 | }; 57 | // let line = line::kodo(); 58 | for video_path in video_path { 59 | println!("{:?}", video_path.canonicalize()?.to_str()); 60 | info!("{line:?}"); 61 | let video_file = VideoFile::new(&video_path)?; 62 | let total_size = video_file.total_size; 63 | let file_name = video_file.file_name.clone(); 64 | let uploader = line.to_uploader(video_file); 65 | 66 | let instant = Instant::now(); 67 | 68 | let video = uploader 69 | .upload(&client, limit, |vs| { 70 | vs.map(|vs| { 71 | let chunk = vs?; 72 | let len = chunk.len(); 73 | Ok((chunk, len)) 74 | }) 75 | }) 76 | .await?; 77 | let t = instant.elapsed().as_millis(); 78 | info!( 79 | "Upload completed: {file_name} => cost {:.2}s, {:.2} MB/s.", 80 | t as f64 / 1000., 81 | total_size as f64 / 1000. / t as f64 82 | ); 83 | videos.push(video); 84 | } 85 | let mut studio: Studio = Studio::builder() 86 | .desc(desc) 87 | .dtime(dtime) 88 | .copyright(copyright) 89 | .cover(cover) 90 | .dynamic(dynamic) 91 | .source(source) 92 | .tag(tag) 93 | .tid(tid) 94 | .title(title) 95 | .videos(videos) 96 | .build(); 97 | if !studio.cover.is_empty() { 98 | let url = BiliBili::new(&login_info, &client) 99 | .cover_up( 100 | &std::fs::read(&studio.cover) 101 | .with_context(|| format!("cover: {}", studio.cover))?, 102 | ) 103 | .await?; 104 | println!("{url}"); 105 | studio.cover = url; 106 | } 107 | Ok(studio.submit(&login_info).await?) 108 | // Ok(videos) 109 | } 110 | -------------------------------------------------------------------------------- /tests/test_download.py: -------------------------------------------------------------------------------- 1 | import stream_gears 2 | 3 | 4 | class Segment: 5 | pass 6 | 7 | 8 | if __name__ == '__main__': 9 | segment = Segment() 10 | # segment.time = 60 11 | # segment.size = 6000 * 1024 * 1024 12 | segment.size = 60 * 1024 * 1024 13 | stream_gears.download( 14 | "", 15 | {"referer": "https://live.bilibili.com"}, 16 | # {}, 17 | "new_test%Y-%m-%dT%H_%M_%S", 18 | segment 19 | ) 20 | -------------------------------------------------------------------------------- /tests/test_upload.py: -------------------------------------------------------------------------------- 1 | import stream_gears 2 | 3 | if __name__ == '__main__': 4 | stream_gears.upload( 5 | ["examples/test.mp4"], 6 | "cookies.json", 7 | "title", 8 | 171, 9 | "tag", 10 | 1, 11 | "source", 12 | "desc", 13 | "dynamic", 14 | "", 15 | None, 16 | stream_gears.UploadLine.Bda2, 17 | 3, 18 | ) 19 | --------------------------------------------------------------------------------