├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── example_config.toml ├── kaeru ├── .gitignore ├── Cargo.toml ├── src │ └── lib.rs └── test │ ├── test.mp3 │ ├── test4.mp3 │ └── test5.aac └── src ├── api.rs ├── broadcast.rs ├── config.rs ├── main.rs ├── prebuffer.rs ├── queue.rs ├── radio.rs ├── tc_queue.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | config.toml 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: c 3 | services: 4 | - docker 5 | before_install: 6 | - docker pull calycosa/kawa-build 7 | script: 8 | - docker run calycosa/kawa-build /bin/sh -c "cd /build/; ./build-kawa.sh $TRAVIS_COMMIT" 9 | before_deploy: 10 | - mkdir ./kawa 11 | - docker run -v $TRAVIS_BUILD_DIR/kawa:/artifacts calycosa/kawa-build /bin/sh -c "cd /build/; ./build-kawa.sh $TRAVIS_TAG release; mv kawa /artifacts/" 12 | # TODO add in config, usage, licensing info 13 | - strip ./kawa/kawa 14 | - tar -zcvf kawa-$TRAVIS_TAG.tar.gz ./kawa 15 | deploy: 16 | provider: releases 17 | api_key: 18 | secure: FFKtd12oNz94pwXXo17rj/pWbrVgX8jBDlw+SuntrrNNp79LZJe6iJXXxacXBLMpKAVkVilYzykwc6tHYDRy/Oi+56IRuPqxQeNnrnR8LQs+3+c8krHVbyjdSdVVza+RaSsEEH1qsj0cV1Tg15v7raY5GwXn/gSbf0uGgO0sD28Xd+AuWZAwXC9HpVbW1+4KZM7Ykhr3dXJo1eZ4E2oO2hmiF94bU5fB07sc2M9erFer+tdsGlCMFug9p0gX+Uvg1jqd9rA6ue7TRkNCQlfM8ZJAuD+G+YBH3Zd+fWXD2GByc2TZaIk4WV4oR117z2Xv12B/97QIXC/4OQAaJAFABRKPYgGwT4kEMwhT5gnqngXRVyPIm8+MJsnTM84YNr6iOVNsyASNuLdljt3Xsp0P2RO7dH68dwPNqaGLQ0fXzH7yUiz1xMDvJEZ32vKNofpMaQ8ywGUSVEiMYlAJIIntBgF9LcUfmyTT9lE4/FHvFHtV1EyjZQt7LnUK1uPwK9C/THi99BJnA+sw4aoQ4pE5dKPnMhTw632EZPvh+WzOduCxEYJP3M2pr66MvC5WVIYOvIM8UlYrgs0bBCpChtNu0ps1leAqqn0zemA4bjKOgA+kbvwQPZHiZytcazbb1zQ8uTo3x0pa6MpkrJ+CjnThoTSe+Mn1aICUjCS4BD/3Ox4= 19 | file_glob: true 20 | file: kawa-*.tar.gz 21 | skip_cleanup: true 22 | on: 23 | tags: true 24 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "aho-corasick" 10 | version = "0.5.3" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "aho-corasick" 18 | version = "0.7.3" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "amy" 26 | version = "0.8.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "arrayvec" 35 | version = "0.4.10" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | dependencies = [ 38 | "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", 39 | ] 40 | 41 | [[package]] 42 | name = "ascii" 43 | version = "0.7.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | 46 | [[package]] 47 | name = "atty" 48 | version = "0.2.11" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | dependencies = [ 51 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [[package]] 57 | name = "autocfg" 58 | version = "0.1.4" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "backtrace" 63 | version = "0.3.30" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | dependencies = [ 66 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 71 | ] 72 | 73 | [[package]] 74 | name = "backtrace-sys" 75 | version = "0.1.28" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 80 | ] 81 | 82 | [[package]] 83 | name = "base64" 84 | version = "0.10.1" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | dependencies = [ 87 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 88 | ] 89 | 90 | [[package]] 91 | name = "bitflags" 92 | version = "0.4.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | 95 | [[package]] 96 | name = "bitflags" 97 | version = "1.1.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | 100 | [[package]] 101 | name = "brotli-sys" 102 | version = "0.2.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | dependencies = [ 105 | "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "brotli2" 111 | version = "0.2.2" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "brotli-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "buf_redux" 120 | version = "0.1.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | 123 | [[package]] 124 | name = "build_const" 125 | version = "0.2.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | 128 | [[package]] 129 | name = "byteorder" 130 | version = "1.3.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | 133 | [[package]] 134 | name = "bytes" 135 | version = "0.4.12" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 141 | ] 142 | 143 | [[package]] 144 | name = "cc" 145 | version = "1.0.37" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | 148 | [[package]] 149 | name = "cfg-if" 150 | version = "0.1.9" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | 153 | [[package]] 154 | name = "chrono" 155 | version = "0.2.25" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | dependencies = [ 158 | "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "chunked_transfer" 164 | version = "0.3.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | 167 | [[package]] 168 | name = "cloudabi" 169 | version = "0.0.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "cookie" 177 | version = "0.12.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | ] 183 | 184 | [[package]] 185 | name = "cookie_store" 186 | version = "0.7.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | dependencies = [ 189 | "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 195 | "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 196 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 197 | "try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 199 | ] 200 | 201 | [[package]] 202 | name = "core-foundation" 203 | version = "0.6.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | dependencies = [ 206 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 208 | ] 209 | 210 | [[package]] 211 | name = "core-foundation-sys" 212 | version = "0.6.2" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | 215 | [[package]] 216 | name = "crc" 217 | version = "1.8.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | dependencies = [ 220 | "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 221 | ] 222 | 223 | [[package]] 224 | name = "crc32fast" 225 | version = "1.2.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | dependencies = [ 228 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 229 | ] 230 | 231 | [[package]] 232 | name = "crossbeam-deque" 233 | version = "0.7.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | dependencies = [ 236 | "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 237 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 238 | ] 239 | 240 | [[package]] 241 | name = "crossbeam-epoch" 242 | version = "0.7.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | dependencies = [ 245 | "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 246 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 247 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 250 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 251 | ] 252 | 253 | [[package]] 254 | name = "crossbeam-queue" 255 | version = "0.1.2" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | dependencies = [ 258 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "crossbeam-utils" 263 | version = "0.6.5" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | dependencies = [ 266 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 268 | ] 269 | 270 | [[package]] 271 | name = "dtoa" 272 | version = "0.4.4" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | 275 | [[package]] 276 | name = "either" 277 | version = "1.5.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | 280 | [[package]] 281 | name = "encoding" 282 | version = "0.2.33" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | dependencies = [ 285 | "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 290 | ] 291 | 292 | [[package]] 293 | name = "encoding-index-japanese" 294 | version = "1.20141219.5" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | dependencies = [ 297 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 298 | ] 299 | 300 | [[package]] 301 | name = "encoding-index-korean" 302 | version = "1.20141219.5" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | dependencies = [ 305 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "encoding-index-simpchinese" 310 | version = "1.20141219.5" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | dependencies = [ 313 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "encoding-index-singlebyte" 318 | version = "1.20141219.5" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | dependencies = [ 321 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 322 | ] 323 | 324 | [[package]] 325 | name = "encoding-index-tradchinese" 326 | version = "1.20141219.5" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | dependencies = [ 329 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 330 | ] 331 | 332 | [[package]] 333 | name = "encoding_index_tests" 334 | version = "0.1.4" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | 337 | [[package]] 338 | name = "encoding_rs" 339 | version = "0.8.17" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | dependencies = [ 342 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 343 | ] 344 | 345 | [[package]] 346 | name = "env_logger" 347 | version = "0.3.5" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | dependencies = [ 350 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", 352 | ] 353 | 354 | [[package]] 355 | name = "env_logger" 356 | version = "0.5.13" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 362 | "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 363 | "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 364 | ] 365 | 366 | [[package]] 367 | name = "error-chain" 368 | version = "0.10.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | dependencies = [ 371 | "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", 372 | ] 373 | 374 | [[package]] 375 | name = "error-chain" 376 | version = "0.12.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | dependencies = [ 379 | "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", 380 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 381 | ] 382 | 383 | [[package]] 384 | name = "failure" 385 | version = "0.1.5" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | dependencies = [ 388 | "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 390 | ] 391 | 392 | [[package]] 393 | name = "failure_derive" 394 | version = "0.1.5" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | dependencies = [ 397 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 398 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", 400 | "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", 401 | ] 402 | 403 | [[package]] 404 | name = "ffmpeg-sys" 405 | version = "4.1.3" 406 | source = "git+https://github.com/Luminarys/rust-ffmpeg-sys.git#a2d3ed4ddc9cf1486b536a758b6198830407f1e9" 407 | dependencies = [ 408 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 409 | ] 410 | 411 | [[package]] 412 | name = "filetime" 413 | version = "0.1.15" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | dependencies = [ 416 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 417 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 418 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 419 | ] 420 | 421 | [[package]] 422 | name = "flate2" 423 | version = "0.2.20" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | dependencies = [ 426 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 427 | "miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 428 | ] 429 | 430 | [[package]] 431 | name = "flate2" 432 | version = "1.0.7" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | dependencies = [ 435 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 437 | "miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 438 | ] 439 | 440 | [[package]] 441 | name = "fnv" 442 | version = "1.0.6" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | 445 | [[package]] 446 | name = "foreign-types" 447 | version = "0.3.2" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | dependencies = [ 450 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 451 | ] 452 | 453 | [[package]] 454 | name = "foreign-types-shared" 455 | version = "0.1.1" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | 458 | [[package]] 459 | name = "fuchsia-cprng" 460 | version = "0.1.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | 463 | [[package]] 464 | name = "fuchsia-zircon" 465 | version = "0.3.3" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | dependencies = [ 468 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 469 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 470 | ] 471 | 472 | [[package]] 473 | name = "fuchsia-zircon-sys" 474 | version = "0.3.3" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | 477 | [[package]] 478 | name = "futures" 479 | version = "0.1.27" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | 482 | [[package]] 483 | name = "futures-cpupool" 484 | version = "0.1.8" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | dependencies = [ 487 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 488 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 489 | ] 490 | 491 | [[package]] 492 | name = "gcc" 493 | version = "0.3.55" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | 496 | [[package]] 497 | name = "h2" 498 | version = "0.1.23" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | dependencies = [ 501 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 502 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 503 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 504 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 506 | "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 508 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 509 | "string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 510 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 511 | ] 512 | 513 | [[package]] 514 | name = "http" 515 | version = "0.1.17" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | dependencies = [ 518 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 519 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 521 | ] 522 | 523 | [[package]] 524 | name = "http-body" 525 | version = "0.1.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | dependencies = [ 528 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 529 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 530 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 531 | "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 532 | ] 533 | 534 | [[package]] 535 | name = "httparse" 536 | version = "1.3.3" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | 539 | [[package]] 540 | name = "humantime" 541 | version = "1.2.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | dependencies = [ 544 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 545 | ] 546 | 547 | [[package]] 548 | name = "hyper" 549 | version = "0.12.29" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | dependencies = [ 552 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 553 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 554 | "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 555 | "h2 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", 556 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 557 | "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 558 | "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 559 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 562 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 563 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 564 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 565 | "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", 566 | "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 567 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 568 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 569 | "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 570 | "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 571 | "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 572 | "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 573 | "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 574 | ] 575 | 576 | [[package]] 577 | name = "hyper-tls" 578 | version = "0.3.2" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | dependencies = [ 581 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 582 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 583 | "hyper 0.12.29 (registry+https://github.com/rust-lang/crates.io-index)", 584 | "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 585 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 586 | ] 587 | 588 | [[package]] 589 | name = "idna" 590 | version = "0.1.5" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | dependencies = [ 593 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 594 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 595 | "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 596 | ] 597 | 598 | [[package]] 599 | name = "indexmap" 600 | version = "1.0.2" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | 603 | [[package]] 604 | name = "iovec" 605 | version = "0.1.2" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | dependencies = [ 608 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 609 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 610 | ] 611 | 612 | [[package]] 613 | name = "itoa" 614 | version = "0.4.4" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | 617 | [[package]] 618 | name = "kaeru" 619 | version = "0.1.0" 620 | dependencies = [ 621 | "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 622 | "ffmpeg-sys 4.1.3 (git+https://github.com/Luminarys/rust-ffmpeg-sys.git)", 623 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 624 | ] 625 | 626 | [[package]] 627 | name = "kawa" 628 | version = "0.1.0" 629 | dependencies = [ 630 | "amy 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 631 | "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", 632 | "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 633 | "kaeru 0.1.0", 634 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 635 | "reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", 636 | "rouille 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 637 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 638 | "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 639 | "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 640 | "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 641 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 642 | ] 643 | 644 | [[package]] 645 | name = "kernel32-sys" 646 | version = "0.2.2" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | dependencies = [ 649 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 650 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 651 | ] 652 | 653 | [[package]] 654 | name = "lazy_static" 655 | version = "1.3.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | 658 | [[package]] 659 | name = "libc" 660 | version = "0.2.58" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | 663 | [[package]] 664 | name = "lock_api" 665 | version = "0.1.5" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | dependencies = [ 668 | "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 669 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 670 | ] 671 | 672 | [[package]] 673 | name = "log" 674 | version = "0.3.9" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | dependencies = [ 677 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 678 | ] 679 | 680 | [[package]] 681 | name = "log" 682 | version = "0.4.6" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | dependencies = [ 685 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 686 | ] 687 | 688 | [[package]] 689 | name = "matches" 690 | version = "0.1.8" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | 693 | [[package]] 694 | name = "memchr" 695 | version = "0.1.11" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | dependencies = [ 698 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 699 | ] 700 | 701 | [[package]] 702 | name = "memchr" 703 | version = "2.2.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | 706 | [[package]] 707 | name = "memoffset" 708 | version = "0.2.1" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | 711 | [[package]] 712 | name = "mime" 713 | version = "0.1.3" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | dependencies = [ 716 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 717 | "serde 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", 718 | ] 719 | 720 | [[package]] 721 | name = "mime" 722 | version = "0.3.13" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | dependencies = [ 725 | "unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 726 | ] 727 | 728 | [[package]] 729 | name = "mime_guess" 730 | version = "1.5.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | dependencies = [ 733 | "mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 734 | "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 735 | "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 736 | ] 737 | 738 | [[package]] 739 | name = "mime_guess" 740 | version = "2.0.0-alpha.6" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | dependencies = [ 743 | "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", 744 | "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 745 | "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 746 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 747 | ] 748 | 749 | [[package]] 750 | name = "miniz-sys" 751 | version = "0.1.12" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | dependencies = [ 754 | "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", 755 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 756 | ] 757 | 758 | [[package]] 759 | name = "miniz_oxide" 760 | version = "0.2.1" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | dependencies = [ 763 | "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 764 | ] 765 | 766 | [[package]] 767 | name = "miniz_oxide_c_api" 768 | version = "0.2.1" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | dependencies = [ 771 | "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", 772 | "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 773 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 774 | "miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 775 | ] 776 | 777 | [[package]] 778 | name = "mio" 779 | version = "0.6.19" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | dependencies = [ 782 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 783 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 784 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 785 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 786 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 787 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 788 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 789 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 790 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 791 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 792 | ] 793 | 794 | [[package]] 795 | name = "miow" 796 | version = "0.2.1" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | dependencies = [ 799 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 800 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 801 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 802 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 803 | ] 804 | 805 | [[package]] 806 | name = "multipart" 807 | version = "0.5.1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | dependencies = [ 810 | "buf_redux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 811 | "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 812 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 813 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 814 | "mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 815 | "mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 816 | "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", 817 | "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 818 | ] 819 | 820 | [[package]] 821 | name = "native-tls" 822 | version = "0.2.3" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | dependencies = [ 825 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 826 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 827 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 828 | "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", 829 | "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 830 | "openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)", 831 | "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 832 | "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 833 | "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 834 | "tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 835 | ] 836 | 837 | [[package]] 838 | name = "net2" 839 | version = "0.2.33" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | dependencies = [ 842 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 843 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 844 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 845 | ] 846 | 847 | [[package]] 848 | name = "nix" 849 | version = "0.6.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | dependencies = [ 852 | "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 853 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 854 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 855 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 856 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 857 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 858 | ] 859 | 860 | [[package]] 861 | name = "nodrop" 862 | version = "0.1.13" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | 865 | [[package]] 866 | name = "num" 867 | version = "0.1.42" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | dependencies = [ 870 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 871 | "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 872 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 873 | ] 874 | 875 | [[package]] 876 | name = "num-integer" 877 | version = "0.1.41" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | dependencies = [ 880 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 881 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 882 | ] 883 | 884 | [[package]] 885 | name = "num-iter" 886 | version = "0.1.39" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | dependencies = [ 889 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 890 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 891 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 892 | ] 893 | 894 | [[package]] 895 | name = "num-traits" 896 | version = "0.2.8" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | dependencies = [ 899 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 900 | ] 901 | 902 | [[package]] 903 | name = "num_cpus" 904 | version = "1.10.1" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | dependencies = [ 907 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 908 | ] 909 | 910 | [[package]] 911 | name = "numtoa" 912 | version = "0.1.0" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | 915 | [[package]] 916 | name = "openssl" 917 | version = "0.10.23" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | dependencies = [ 920 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 921 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 922 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 923 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 924 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 925 | "openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)", 926 | ] 927 | 928 | [[package]] 929 | name = "openssl-probe" 930 | version = "0.1.2" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | 933 | [[package]] 934 | name = "openssl-sys" 935 | version = "0.9.47" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | dependencies = [ 938 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 939 | "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", 940 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 941 | "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 942 | "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 943 | ] 944 | 945 | [[package]] 946 | name = "owning_ref" 947 | version = "0.4.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | dependencies = [ 950 | "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 951 | ] 952 | 953 | [[package]] 954 | name = "parking_lot" 955 | version = "0.7.1" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | dependencies = [ 958 | "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 959 | "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 960 | ] 961 | 962 | [[package]] 963 | name = "parking_lot_core" 964 | version = "0.4.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | dependencies = [ 967 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 968 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 969 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 970 | "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 971 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 972 | ] 973 | 974 | [[package]] 975 | name = "percent-encoding" 976 | version = "1.0.1" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | 979 | [[package]] 980 | name = "phf" 981 | version = "0.7.24" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | dependencies = [ 984 | "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 985 | ] 986 | 987 | [[package]] 988 | name = "phf_codegen" 989 | version = "0.7.24" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | dependencies = [ 992 | "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 993 | "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 994 | ] 995 | 996 | [[package]] 997 | name = "phf_generator" 998 | version = "0.7.24" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | dependencies = [ 1001 | "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", 1002 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "phf_shared" 1007 | version = "0.7.24" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | dependencies = [ 1010 | "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 1011 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "pkg-config" 1016 | version = "0.3.14" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | 1019 | [[package]] 1020 | name = "proc-macro2" 1021 | version = "0.4.30" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | dependencies = [ 1024 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "publicsuffix" 1029 | version = "1.5.2" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | dependencies = [ 1032 | "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", 1033 | "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1034 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1035 | "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1036 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "quick-error" 1041 | version = "1.2.2" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | 1044 | [[package]] 1045 | name = "quote" 1046 | version = "0.6.12" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | dependencies = [ 1049 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "rand" 1054 | version = "0.3.23" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | dependencies = [ 1057 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1058 | "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "rand" 1063 | version = "0.4.6" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | dependencies = [ 1066 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1067 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1068 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1069 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1070 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "rand" 1075 | version = "0.6.5" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | dependencies = [ 1078 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1079 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1080 | "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1081 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1082 | "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1083 | "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1084 | "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1085 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 1086 | "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 1087 | "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1088 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "rand_chacha" 1093 | version = "0.1.1" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | dependencies = [ 1096 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1097 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "rand_core" 1102 | version = "0.3.1" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | dependencies = [ 1105 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "rand_core" 1110 | version = "0.4.0" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | 1113 | [[package]] 1114 | name = "rand_hc" 1115 | version = "0.1.0" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | dependencies = [ 1118 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "rand_isaac" 1123 | version = "0.1.1" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | dependencies = [ 1126 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "rand_jitter" 1131 | version = "0.1.4" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | dependencies = [ 1134 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1135 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1136 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "rand_os" 1141 | version = "0.1.3" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | dependencies = [ 1144 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 1145 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1146 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1147 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1148 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1149 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "rand_pcg" 1154 | version = "0.1.2" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | dependencies = [ 1157 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1158 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "rand_xorshift" 1163 | version = "0.1.1" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | dependencies = [ 1166 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "rdrand" 1171 | version = "0.4.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | dependencies = [ 1174 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "redox_syscall" 1179 | version = "0.1.54" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | 1182 | [[package]] 1183 | name = "redox_termios" 1184 | version = "0.1.1" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | dependencies = [ 1187 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "regex" 1192 | version = "0.1.80" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | dependencies = [ 1195 | "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 1196 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 1197 | "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 1198 | "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 1199 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "regex" 1204 | version = "1.1.7" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | dependencies = [ 1207 | "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 1208 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1209 | "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", 1210 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 1211 | "utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "regex-syntax" 1216 | version = "0.3.9" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | 1219 | [[package]] 1220 | name = "regex-syntax" 1221 | version = "0.6.7" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | dependencies = [ 1224 | "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "remove_dir_all" 1229 | version = "0.5.2" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | dependencies = [ 1232 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "reqwest" 1237 | version = "0.9.18" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | dependencies = [ 1240 | "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 1241 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1242 | "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 1243 | "cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 1244 | "encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)", 1245 | "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 1246 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1247 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 1248 | "hyper 0.12.29 (registry+https://github.com/rust-lang/crates.io-index)", 1249 | "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 1250 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1251 | "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", 1252 | "mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", 1253 | "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 1254 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 1255 | "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 1256 | "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 1257 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 1258 | "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", 1259 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1260 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1261 | "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 1262 | "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 1263 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1264 | "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "rouille" 1269 | version = "1.0.3" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | dependencies = [ 1272 | "brotli2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 1273 | "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 1274 | "filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 1275 | "flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", 1276 | "multipart 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 1277 | "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", 1278 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 1279 | "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1280 | "term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 1281 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 1282 | "tiny_http 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 1283 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "rustc-demangle" 1288 | version = "0.1.15" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | 1291 | [[package]] 1292 | name = "rustc-serialize" 1293 | version = "0.3.24" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | 1296 | [[package]] 1297 | name = "rustc_version" 1298 | version = "0.1.7" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | dependencies = [ 1301 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "rustc_version" 1306 | version = "0.2.3" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | dependencies = [ 1309 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 1310 | ] 1311 | 1312 | [[package]] 1313 | name = "ryu" 1314 | version = "0.2.8" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | 1317 | [[package]] 1318 | name = "schannel" 1319 | version = "0.1.15" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | dependencies = [ 1322 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1323 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "scopeguard" 1328 | version = "0.3.3" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | 1331 | [[package]] 1332 | name = "security-framework" 1333 | version = "0.3.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | dependencies = [ 1336 | "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 1337 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 1338 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1339 | "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "security-framework-sys" 1344 | version = "0.3.1" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | dependencies = [ 1347 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "semver" 1352 | version = "0.1.20" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | 1355 | [[package]] 1356 | name = "semver" 1357 | version = "0.9.0" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | dependencies = [ 1360 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "semver-parser" 1365 | version = "0.7.0" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | 1368 | [[package]] 1369 | name = "serde" 1370 | version = "0.6.15" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | dependencies = [ 1373 | "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "serde" 1378 | version = "1.0.92" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | dependencies = [ 1381 | "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "serde_derive" 1386 | version = "1.0.92" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | dependencies = [ 1389 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 1390 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 1391 | "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "serde_json" 1396 | version = "1.0.39" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | dependencies = [ 1399 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 1400 | "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 1401 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "serde_urlencoded" 1406 | version = "0.5.5" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | dependencies = [ 1409 | "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 1410 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 1411 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 1412 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "sha1" 1417 | version = "0.2.0" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | 1420 | [[package]] 1421 | name = "siphasher" 1422 | version = "0.2.3" 1423 | source = "registry+https://github.com/rust-lang/crates.io-index" 1424 | 1425 | [[package]] 1426 | name = "slab" 1427 | version = "0.4.2" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | 1430 | [[package]] 1431 | name = "smallvec" 1432 | version = "0.6.10" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | 1435 | [[package]] 1436 | name = "stable_deref_trait" 1437 | version = "1.1.1" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | 1440 | [[package]] 1441 | name = "string" 1442 | version = "0.2.0" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | dependencies = [ 1445 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "syn" 1450 | version = "0.15.35" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | dependencies = [ 1453 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 1454 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 1455 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1456 | ] 1457 | 1458 | [[package]] 1459 | name = "synstructure" 1460 | version = "0.10.2" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | dependencies = [ 1463 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 1464 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 1465 | "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", 1466 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1467 | ] 1468 | 1469 | [[package]] 1470 | name = "tempdir" 1471 | version = "0.3.7" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | dependencies = [ 1474 | "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1475 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1476 | ] 1477 | 1478 | [[package]] 1479 | name = "tempfile" 1480 | version = "3.0.8" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | dependencies = [ 1483 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 1484 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1485 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1486 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 1487 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1488 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "term" 1493 | version = "0.2.14" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | dependencies = [ 1496 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 1497 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 1498 | ] 1499 | 1500 | [[package]] 1501 | name = "termcolor" 1502 | version = "1.0.5" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | dependencies = [ 1505 | "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "termion" 1510 | version = "1.5.3" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | dependencies = [ 1513 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1514 | "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1515 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 1516 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1517 | ] 1518 | 1519 | [[package]] 1520 | name = "thread-id" 1521 | version = "2.0.0" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | dependencies = [ 1524 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 1525 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "thread_local" 1530 | version = "0.2.7" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | dependencies = [ 1533 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "thread_local" 1538 | version = "0.3.6" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | dependencies = [ 1541 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1542 | ] 1543 | 1544 | [[package]] 1545 | name = "time" 1546 | version = "0.1.42" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | dependencies = [ 1549 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1550 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 1551 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1552 | ] 1553 | 1554 | [[package]] 1555 | name = "tiny_http" 1556 | version = "0.5.9" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | dependencies = [ 1559 | "ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 1560 | "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 1561 | "chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1562 | "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 1563 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 1564 | "url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "tokio" 1569 | version = "0.1.21" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | dependencies = [ 1572 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1573 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1574 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 1575 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 1576 | "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 1577 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1578 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1579 | "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 1580 | "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 1581 | "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 1582 | "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 1583 | "tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "tokio-buf" 1588 | version = "0.1.1" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | dependencies = [ 1591 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1592 | "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1593 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1594 | ] 1595 | 1596 | [[package]] 1597 | name = "tokio-current-thread" 1598 | version = "0.1.6" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | dependencies = [ 1601 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1602 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1603 | ] 1604 | 1605 | [[package]] 1606 | name = "tokio-executor" 1607 | version = "0.1.7" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | dependencies = [ 1610 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1611 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "tokio-io" 1616 | version = "0.1.12" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | dependencies = [ 1619 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1620 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1621 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1622 | ] 1623 | 1624 | [[package]] 1625 | name = "tokio-reactor" 1626 | version = "0.1.9" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | dependencies = [ 1629 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1630 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1631 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1632 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1633 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 1634 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 1635 | "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 1636 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1637 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1638 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1639 | "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 1640 | ] 1641 | 1642 | [[package]] 1643 | name = "tokio-sync" 1644 | version = "0.1.6" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | dependencies = [ 1647 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 1648 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "tokio-tcp" 1653 | version = "0.1.3" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | dependencies = [ 1656 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1657 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1658 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 1659 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 1660 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1661 | "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "tokio-threadpool" 1666 | version = "0.1.14" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | dependencies = [ 1669 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 1670 | "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 1671 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1672 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1673 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1674 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 1675 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1676 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1677 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "tokio-timer" 1682 | version = "0.2.11" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | dependencies = [ 1685 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1686 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1687 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1688 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "tokio-trace-core" 1693 | version = "0.2.0" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | dependencies = [ 1696 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1697 | ] 1698 | 1699 | [[package]] 1700 | name = "toml" 1701 | version = "0.4.10" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | dependencies = [ 1704 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 1705 | ] 1706 | 1707 | [[package]] 1708 | name = "try-lock" 1709 | version = "0.2.2" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | 1712 | [[package]] 1713 | name = "try_from" 1714 | version = "0.3.2" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | dependencies = [ 1717 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "ucd-util" 1722 | version = "0.1.3" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | 1725 | [[package]] 1726 | name = "unicase" 1727 | version = "1.4.2" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | dependencies = [ 1730 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "unicase" 1735 | version = "2.4.0" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | dependencies = [ 1738 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "unicode-bidi" 1743 | version = "0.3.4" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | dependencies = [ 1746 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "unicode-normalization" 1751 | version = "0.1.8" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | dependencies = [ 1754 | "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 1755 | ] 1756 | 1757 | [[package]] 1758 | name = "unicode-xid" 1759 | version = "0.1.0" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | 1762 | [[package]] 1763 | name = "url" 1764 | version = "0.2.38" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | dependencies = [ 1767 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1768 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 1769 | "uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "url" 1774 | version = "1.7.2" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | dependencies = [ 1777 | "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1778 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1779 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "utf8-ranges" 1784 | version = "0.1.3" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | 1787 | [[package]] 1788 | name = "utf8-ranges" 1789 | version = "1.0.3" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | 1792 | [[package]] 1793 | name = "uuid" 1794 | version = "0.1.18" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | dependencies = [ 1797 | "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", 1798 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 1799 | ] 1800 | 1801 | [[package]] 1802 | name = "uuid" 1803 | version = "0.7.4" 1804 | source = "registry+https://github.com/rust-lang/crates.io-index" 1805 | dependencies = [ 1806 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1807 | ] 1808 | 1809 | [[package]] 1810 | name = "vcpkg" 1811 | version = "0.2.6" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | 1814 | [[package]] 1815 | name = "version_check" 1816 | version = "0.1.5" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | 1819 | [[package]] 1820 | name = "void" 1821 | version = "1.0.2" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | 1824 | [[package]] 1825 | name = "want" 1826 | version = "0.0.6" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | dependencies = [ 1829 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1830 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1831 | "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "winapi" 1836 | version = "0.2.8" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | 1839 | [[package]] 1840 | name = "winapi" 1841 | version = "0.3.7" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | dependencies = [ 1844 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1845 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1846 | ] 1847 | 1848 | [[package]] 1849 | name = "winapi-build" 1850 | version = "0.1.1" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | 1853 | [[package]] 1854 | name = "winapi-i686-pc-windows-gnu" 1855 | version = "0.4.0" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | 1858 | [[package]] 1859 | name = "winapi-util" 1860 | version = "0.1.2" 1861 | source = "registry+https://github.com/rust-lang/crates.io-index" 1862 | dependencies = [ 1863 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "winapi-x86_64-pc-windows-gnu" 1868 | version = "0.4.0" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | 1871 | [[package]] 1872 | name = "wincolor" 1873 | version = "1.0.1" 1874 | source = "registry+https://github.com/rust-lang/crates.io-index" 1875 | dependencies = [ 1876 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 1877 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 1878 | ] 1879 | 1880 | [[package]] 1881 | name = "ws2_32-sys" 1882 | version = "0.2.1" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | dependencies = [ 1885 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 1886 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1887 | ] 1888 | 1889 | [metadata] 1890 | "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" 1891 | "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 1892 | "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" 1893 | "checksum amy 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc72e85a2f1cd8ac533de69490edc55d5e8896b684d7421cc1bb013c602053c" 1894 | "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" 1895 | "checksum ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50" 1896 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 1897 | "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" 1898 | "checksum backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" 1899 | "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" 1900 | "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 1901 | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" 1902 | "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" 1903 | "checksum brotli-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cb50f54b2e0c671b7ef1637a76237ebacbb293be179440d5d65ca288e42116bb" 1904 | "checksum brotli2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ea9d0bbab1235017a09226b079ed733bca4bf9ecb6b6102bd01aac79ea082dca" 1905 | "checksum buf_redux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b115bd9935c68b58f80ff867e1c46942c4aed79e78bcc8c2bc22d50f52bb9099" 1906 | "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" 1907 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 1908 | "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 1909 | "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" 1910 | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 1911 | "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" 1912 | "checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" 1913 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 1914 | "checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" 1915 | "checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" 1916 | "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 1917 | "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 1918 | "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" 1919 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 1920 | "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" 1921 | "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" 1922 | "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" 1923 | "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" 1924 | "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" 1925 | "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" 1926 | "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 1927 | "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 1928 | "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 1929 | "checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 1930 | "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 1931 | "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 1932 | "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 1933 | "checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed" 1934 | "checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" 1935 | "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" 1936 | "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" 1937 | "checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" 1938 | "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" 1939 | "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" 1940 | "checksum ffmpeg-sys 4.1.3 (git+https://github.com/Luminarys/rust-ffmpeg-sys.git)" = "" 1941 | "checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" 1942 | "checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423" 1943 | "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" 1944 | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 1945 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 1946 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 1947 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 1948 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 1949 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 1950 | "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" 1951 | "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 1952 | "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 1953 | "checksum h2 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "1e42e3daed5a7e17b12a0c23b5b2fbff23a925a570938ebee4baca1a9a1a2240" 1954 | "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" 1955 | "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" 1956 | "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" 1957 | "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" 1958 | "checksum hyper 0.12.29 (registry+https://github.com/rust-lang/crates.io-index)" = "e2cd6adf83b3347d36e271f030621a8cf95fd1fd0760546b9fc5a24a0f1447c7" 1959 | "checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" 1960 | "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 1961 | "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" 1962 | "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 1963 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 1964 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 1965 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 1966 | "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" 1967 | "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" 1968 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 1969 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 1970 | "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1971 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 1972 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 1973 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 1974 | "checksum mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ec0c2f4d901bf1d4a2192a40b4b570ae3b19c51243e549defc1de741940aa787" 1975 | "checksum mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425" 1976 | "checksum mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "422acd80644209a8c8c66a20514840d8c092eb1eab2898ca7c548cc1d64c8998" 1977 | "checksum mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30de2e4613efcba1ec63d8133f344076952090c122992a903359be5a4f99c3ed" 1978 | "checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" 1979 | "checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e" 1980 | "checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab" 1981 | "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" 1982 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 1983 | "checksum multipart 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b68c9a0c757bd65893af529f7af6e7a71442e57ca6d9db1fa69b79e2f05f6b49" 1984 | "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" 1985 | "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 1986 | "checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2" 1987 | "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" 1988 | "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 1989 | "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 1990 | "checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e" 1991 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 1992 | "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 1993 | "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 1994 | "checksum openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)" = "97c140cbb82f3b3468193dd14c1b88def39f341f68257f8a7fe8ed9ed3f628a5" 1995 | "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 1996 | "checksum openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)" = "75bdd6dbbb4958d38e47a1d2348847ad1eb4dc205dc5d37473ae504391865acc" 1997 | "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" 1998 | "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" 1999 | "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" 2000 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 2001 | "checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" 2002 | "checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" 2003 | "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" 2004 | "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" 2005 | "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" 2006 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 2007 | "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d" 2008 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 2009 | "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" 2010 | "checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 2011 | "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 2012 | "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 2013 | "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 2014 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 2015 | "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" 2016 | "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 2017 | "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 2018 | "checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 2019 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 2020 | "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 2021 | "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 2022 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 2023 | "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" 2024 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 2025 | "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 2026 | "checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd" 2027 | "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 2028 | "checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" 2029 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 2030 | "checksum reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "00eb63f212df0e358b427f0f40aa13aaea010b470be642ad422bcbca2feff2e4" 2031 | "checksum rouille 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d6415f261a8775bef50e9fcfb14ed73209ce637f753f9d1c8c6122559e559001" 2032 | "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" 2033 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 2034 | "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" 2035 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 2036 | "checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" 2037 | "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" 2038 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 2039 | "checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" 2040 | "checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" 2041 | "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" 2042 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 2043 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 2044 | "checksum serde 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97b18e9e53de541f11e497357d6c5eaeb39f0cb9c8734e274abe4935f6991fa" 2045 | "checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" 2046 | "checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" 2047 | "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" 2048 | "checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" 2049 | "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" 2050 | "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" 2051 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 2052 | "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" 2053 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 2054 | "checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995" 2055 | "checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" 2056 | "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" 2057 | "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 2058 | "checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef" 2059 | "checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281" 2060 | "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" 2061 | "checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330" 2062 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 2063 | "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 2064 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 2065 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 2066 | "checksum tiny_http 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2f4d55c9a213880d1f0c89ded183f209c6e45b912ca6c7df6f93c163773572e1" 2067 | "checksum tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2ffcf4bcfc641413fa0f1427bf8f91dfc78f56a6559cbf50e04837ae442a87" 2068 | "checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" 2069 | "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" 2070 | "checksum tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e" 2071 | "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" 2072 | "checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" 2073 | "checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" 2074 | "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" 2075 | "checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" 2076 | "checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" 2077 | "checksum tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9c8a256d6956f7cb5e2bdfe8b1e8022f1a09206c6c2b1ba00f3b746b260c613" 2078 | "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" 2079 | "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 2080 | "checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" 2081 | "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 2082 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 2083 | "checksum unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84e5511b2a947f3ae965dcb29b13b7b1691b6e7332cf5dbc1744138d5acb7f6" 2084 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 2085 | "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" 2086 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 2087 | "checksum url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)" = "cbaa8377a162d88e7d15db0cf110c8523453edcbc5bc66d2b6fffccffa34a068" 2088 | "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 2089 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 2090 | "checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" 2091 | "checksum uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "78c590b5bd79ed10aad8fb75f078a59d8db445af6c743e55c4a53227fc01c13f" 2092 | "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" 2093 | "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" 2094 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 2095 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 2096 | "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" 2097 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 2098 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 2099 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 2100 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2101 | "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 2102 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2103 | "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" 2104 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 2105 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kawa" 3 | version = "0.1.0" 4 | authors = ["Luminarys ", "ParadoxSpiral "] 5 | 6 | [features] 7 | default = [] 8 | nightly = [] 9 | 10 | [dependencies] 11 | kaeru = { path = "kaeru" } 12 | toml = "0.4" 13 | log = "0.4" 14 | env_logger = "0.5" 15 | serde = "1.0" 16 | serde_json = "1.0" 17 | serde_derive = "1.0" 18 | reqwest = "0.9" 19 | rouille = "1.0.2" 20 | httparse = "1.2.3" 21 | url = "1.5" 22 | 23 | [dependencies.amy] 24 | version = "0.8.1" 25 | default-features = false 26 | features = ["no_timerfd"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Luminarys 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kawa 2 | 3 | kawa is an ffmpeg-based radio streamer. 4 | 5 | ## Compiling 6 | 7 | Install the dependencies: 8 | 9 | - ffmpeg 10 | - rustc (stable) 11 | - cargo (stable) 12 | 13 | ``` 14 | $ cargo build --release 15 | # cargo install 16 | ``` 17 | 18 | ## Usage 19 | 20 | Start by copying example_config.toml to the location of your choice and reading 21 | through it. Batteries are not included - kawa needs to be paired with your own 22 | software to find songs to stream. You will have to provide an external API that 23 | kawa can query for songs to play and notify as new songs being played. 24 | 25 | ## API 26 | 27 | Kawa provides an HTTP API for management the queue. Kawa will play songs from 28 | the queue until it is exhausted, then request new tracks from 29 | `[queue].random_song_api`. 30 | 31 | ### GET /np 32 | 33 | **Response** 34 | 35 | ```json 36 | { track blob } 37 | ``` 38 | 39 | ### GET /listeners 40 | 41 | **Response** 42 | 43 | ```json 44 | [ 45 | { 46 | "mount": "stream256.opus", 47 | "path": "/stream256.opus?user=minus", 48 | "headers": [ 49 | { 50 | "name": "User-Agent", 51 | "value": "Music Player Daemon 0.20.9" 52 | }, 53 | ... 54 | ] 55 | }, 56 | ... 57 | ] 58 | ``` 59 | 60 | ### GET /queue 61 | 62 | **Response** 63 | 64 | ```json 65 | [ 66 | { track blob }, 67 | ... 68 | ] 69 | ``` 70 | 71 | ### POST /queue/head 72 | 73 | Inserts a track at the top of the queue. 74 | 75 | **Request** 76 | 77 | ```json 78 | { track blob } 79 | ``` 80 | 81 | Note: track blob is an arbitrary JSON blob that Kawa will hold on to for you. At 82 | a minimum it must include "path", the path to the audio source on the 83 | filesystem. Additionally, a unique "queue_id" field will be added whenever 84 | the blob is retrieved and can be used for manipulation of the queue. 85 | 86 | **Response** 87 | 88 | ```json 89 | { 90 | "success": true, 91 | "reason": null 92 | } 93 | ``` 94 | 95 | ### POST /queue/tail 96 | 97 | Inserts a track at the bottom of the queue. See `/queue/head`. 98 | 99 | ### DELETE /queue/head 100 | 101 | Unqueues the track at the top of the queue. 102 | 103 | **Response** 104 | 105 | ```json 106 | { 107 | "success": true, 108 | "reason": null 109 | } 110 | ``` 111 | 112 | ### DELETE /queue/tail 113 | 114 | Unqueues the track at the bottom of the queue. See `/queue/tail`. 115 | 116 | ### POST /queue/clear 117 | 118 | Removes all tracks from the queue. 119 | 120 | **Response** 121 | 122 | ```json 123 | { 124 | "success": true, 125 | "reason": null 126 | } 127 | ``` 128 | 129 | ### POST /skip 130 | 131 | Immediately skips to the next track in the queue. 132 | 133 | **Response** 134 | 135 | ```json 136 | { 137 | "success": true, 138 | "reason": null 139 | } 140 | ``` 141 | -------------------------------------------------------------------------------- /example_config.toml: -------------------------------------------------------------------------------- 1 | [api] 2 | # 3 | # The HTTP port the Kawa API listens on. Kawa will listen on localhost. 4 | port=4040 5 | 6 | [queue] 7 | # 8 | # An HTTP GET is sent to this URL when Kawa's queue is empty and it needs a new 9 | # random track to play. The expected response is an arbitrary JSON blob that 10 | # Kawa stores in its queue. At a minimum, it must include the "path" property: 11 | # 12 | # { 13 | # "path": "/path/to/audio/file" 14 | # } 15 | # 16 | # The path is the path to an audio file on the filesystem you want Kawa to play. 17 | random_song_api="http://localhost:8012/api/random" 18 | # 19 | # An HTTP POST is issued to this URL when Kawa starts playing a track. The body 20 | # will be identical to the JSON blob in the queue. 21 | np="http://localhost:8012/api/np" 22 | # 23 | # When no tracks are available for whatever reason (such as external service 24 | # outages), this track will be played. 25 | fallback="/tmp/in.flac" 26 | # Length of buffer to maintain in KiB 27 | buffer_len=4096 28 | 29 | [radio] 30 | # 31 | # The port to stream actual audio on. Kawa will listen on localhost. 32 | port=8001 33 | # Name of the stream. 34 | name="my radio" 35 | 36 | # 37 | # A list of streams to make available at [radio.port]/(mount) follows. The 38 | # following properties are available: 39 | # 40 | # mount: the HTTP address to serve the stream from 41 | # container: the container format to use (ogg, flac, aac, or mp3) 42 | # codec: the audio codec to use (opus, vorbis, flac, aac, do not specify for mp3 streams) 43 | # bitrate: the desired bitrate of the stream in Kb/s, if not specified an appropriate 44 | # bitrate will be automatically selected based on the container/codec 45 | [[streams]] 46 | mount="stream128.mp3" 47 | container="mp3" 48 | bitrate=128 49 | 50 | [[streams]] 51 | mount="stream192.mp3" 52 | container="mp3" 53 | bitrate=192 54 | 55 | [[streams]] 56 | mount="stream128.aac" 57 | container="aac" 58 | bitrate=128 59 | 60 | [[streams]] 61 | mount="stream128.opus" 62 | container="ogg" 63 | codec="opus" 64 | bitrate=128 65 | 66 | [[streams]] 67 | mount="stream192.opus" 68 | container="ogg" 69 | codec="opus" 70 | bitrate=192 71 | 72 | [[streams]] 73 | mount="stream256.opus" 74 | container="ogg" 75 | codec="opus" 76 | bitrate=256 77 | 78 | [[streams]] 79 | mount="stream.flac" 80 | container="flac" 81 | -------------------------------------------------------------------------------- /kaeru/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.ogg 5 | *.swp 6 | -------------------------------------------------------------------------------- /kaeru/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kaeru" 3 | version = "0.1.0" 4 | authors = ["Luminarys "] 5 | 6 | [dependencies.libc] 7 | version = "0.2" 8 | 9 | [dependencies.error-chain] 10 | version = "0.10" 11 | 12 | [dependencies.ffmpeg-sys] 13 | version = "4.1.3" 14 | git = "https://github.com/Luminarys/rust-ffmpeg-sys.git" 15 | -------------------------------------------------------------------------------- /kaeru/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate error_chain; 3 | extern crate ffmpeg_sys as sys; 4 | extern crate libc; 5 | 6 | pub use sys::AVCodecID; 7 | 8 | use std::ffi::{CString, CStr}; 9 | use std::io::{self, Read, Write}; 10 | use std::{slice, ptr, mem, time}; 11 | use libc::{c_char, c_int, c_void}; 12 | 13 | error_chain! { 14 | errors { 15 | FFmpeg(reason: &'static str, code: c_int) { 16 | description("ffmpeg error") 17 | display("{}, ffmpeg error: {}", reason, get_error(*code)) 18 | } 19 | Allocation { 20 | description("Failed to allocate a necessary structure") 21 | display("Allocation failed(OOM)") 22 | } 23 | } 24 | } 25 | 26 | macro_rules! str_conv { 27 | ($s:expr) => {{ 28 | $s.as_ptr() as *const i8 29 | }} 30 | } 31 | 32 | macro_rules! ck_null { 33 | ($s:expr) => { 34 | if $s.is_null() { 35 | return Err(ErrorKind::Allocation.into()); 36 | } 37 | } 38 | } 39 | 40 | const FFMPEG_BUFFER_SIZE: usize = 4096; 41 | 42 | pub struct Graph { 43 | #[allow(dead_code)] // The graph needs to be kept as context for the filters 44 | graph: GraphP, 45 | #[allow(dead_code)] 46 | splitter: *mut sys::AVFilterContext, // We don't actually need this, but it's nice to have 47 | in_frame: *mut sys::AVFrame, 48 | out_frame: *mut sys::AVFrame, 49 | input: GraphInput, 50 | outputs: Vec, 51 | } 52 | 53 | pub struct GraphBuilder { 54 | graph: GraphP, 55 | input: GraphInput, 56 | outputs: Vec, 57 | } 58 | 59 | struct GraphOutput { 60 | output: Output, 61 | ctx: *mut sys::AVFilterContext, 62 | } 63 | 64 | struct GraphInput { 65 | input: Input, 66 | ctx: *mut sys::AVFilterContext, 67 | } 68 | 69 | pub struct Input { 70 | ctx: *mut sys::AVFormatContext, 71 | codec_ctx: *mut sys::AVCodecContext, 72 | stream: *mut sys::AVStream, 73 | _opaque: Opaque, 74 | } 75 | 76 | pub struct Output { 77 | ctx: *mut sys::AVFormatContext, 78 | codec_ctx: *mut sys::AVCodecContext, 79 | stream: *mut sys::AVStream, 80 | _opaque: Opaque, 81 | header_signal: fn(*mut c_void), 82 | packet_signal: fn(*mut c_void, f64), 83 | body_signal: fn(*mut c_void), 84 | } 85 | 86 | #[derive(Debug, Clone)] 87 | pub struct Metadata { 88 | pub title: Option, 89 | pub album: Option, 90 | pub artist: Option, 91 | pub genre: Option, 92 | pub date: Option, 93 | pub track: Option, 94 | } 95 | 96 | struct Opaque { 97 | ptr: *mut c_void, 98 | cleanup: fn(*mut c_void), 99 | } 100 | 101 | struct GraphP { 102 | ptr: *mut sys::AVFilterGraph, 103 | } 104 | 105 | pub trait Sink : Write { 106 | fn header_written(&mut self) { } 107 | fn packet_written(&mut self, _: f64) { } 108 | fn body_written(&mut self) { } 109 | } 110 | 111 | impl Graph { 112 | pub fn run(mut self) -> Result<()> { 113 | unsafe { 114 | // Write header 115 | for o in self.outputs.iter() { 116 | match sys::avformat_write_header(o.output.ctx, ptr::null_mut()) { 117 | 0 => { 118 | (o.output.header_signal)(o.output._opaque.ptr); 119 | } 120 | e => return Err(ErrorKind::FFmpeg("failed to write header", e).into()), 121 | } 122 | } 123 | 124 | let mut res = self.execute_tc(); 125 | if res.is_ok() { 126 | res = self.try_flush(); 127 | for o in self.outputs.iter() { 128 | (o.output.body_signal)(o.output._opaque.ptr); 129 | sys::av_write_trailer(o.output.ctx); 130 | } 131 | } else { 132 | for o in self.outputs.iter() { 133 | (o.output.body_signal)(o.output._opaque.ptr); 134 | } 135 | 136 | if self.try_flush().is_err() { 137 | // TODO: ? 138 | } 139 | 140 | for o in self.outputs.iter() { 141 | sys::av_write_trailer(o.output.ctx); 142 | } 143 | } 144 | res 145 | } 146 | } 147 | 148 | unsafe fn execute_tc(&mut self) -> Result<()> { 149 | self.input.input.read_frames(self.in_frame, || { 150 | (*self.in_frame).pts = sys::av_frame_get_best_effort_timestamp(self.in_frame); 151 | let pres = self.process_frame(self.in_frame); 152 | sys::av_frame_unref(self.in_frame); 153 | pres 154 | })?; 155 | 156 | Ok(()) 157 | } 158 | 159 | unsafe fn process_frame(&self, frame: *mut sys::AVFrame) -> Result<()> { 160 | // Push the frame into the graph source 161 | match sys::av_buffersrc_add_frame_flags(self.input.ctx, frame, sys::AV_BUFFERSRC_FLAG_KEEP_REF as i32) { 162 | 0 => { } 163 | e => return Err(ErrorKind::FFmpeg("failed to add frame to graph source", e).into()), 164 | } 165 | 166 | // Pull out frames from each graph sink, sends them into the codecs for encoding, then 167 | // writes out the received packets 168 | for output in self.outputs.iter() { 169 | loop { 170 | match sys::av_buffersink_get_frame(output.ctx, self.out_frame) { 171 | 0 => { } 172 | e if e == sys::AVERROR(libc::EAGAIN) => { break } 173 | e if e == sys::AVERROR_EOF => { break } 174 | e => return Err(ErrorKind::FFmpeg("failed to get frame from graph sink", e).into()), 175 | } 176 | 177 | { let r = output.output.write_frame(self.out_frame); sys::av_frame_unref(self.out_frame); r}?; 178 | } 179 | sys::av_frame_unref(self.out_frame); 180 | } 181 | Ok(()) 182 | } 183 | 184 | unsafe fn try_flush(&self) -> Result<()> { 185 | let mut res = self.input.input.flush_frames(self.in_frame, || { 186 | (*self.in_frame).pts = sys::av_frame_get_best_effort_timestamp(self.in_frame); 187 | let pres = self.process_frame(self.in_frame); 188 | sys::av_frame_unref(self.in_frame); 189 | pres 190 | }); 191 | 192 | // Flush everything 193 | res = res.and(self.process_frame(ptr::null_mut())); 194 | for o in self.outputs.iter() { 195 | // If the codec needs flushing, do so 196 | if ((*(*o.output.codec_ctx).codec).capabilities as u32 & sys::AV_CODEC_CAP_DELAY) != 0 { 197 | res = res.and(o.output.write_frame(ptr::null_mut())); 198 | } 199 | } 200 | res 201 | } 202 | } 203 | 204 | impl Drop for Graph { 205 | fn drop(&mut self) { 206 | unsafe { 207 | sys::av_frame_free(&mut self.in_frame); 208 | sys::av_frame_free(&mut self.out_frame); 209 | } 210 | } 211 | } 212 | 213 | unsafe impl Send for Graph { } 214 | 215 | impl GraphBuilder { 216 | pub fn new(input: Input) -> Result { 217 | unsafe { 218 | let graph = sys::avfilter_graph_alloc(); 219 | ck_null!(graph); 220 | let buffersrc = sys::avfilter_get_by_name(str_conv!("abuffer\0")); 221 | ck_null!(buffersrc); 222 | let buffersrc_ctx = sys::avfilter_graph_alloc_filter(graph, buffersrc, str_conv!("in\0")); 223 | ck_null!(buffersrc_ctx); 224 | let time_base = (*input.stream).time_base; 225 | let sample_fmt = CStr::from_ptr(sys::av_get_sample_fmt_name((*input.codec_ctx).sample_fmt)) 226 | .to_str().chain_err(|| "failed to parse format!")?; 227 | let args = format!("time_base={}/{}:sample_rate={}:sample_fmt={}:channel_layout=0x{:X}\0", 228 | time_base.num, time_base.den, (*input.codec_ctx).sample_rate, 229 | sample_fmt, (*input.codec_ctx).channel_layout); 230 | 231 | match sys::avfilter_init_str(buffersrc_ctx, str_conv!(args)) { 232 | 0 => { } 233 | e => return Err(ErrorKind::FFmpeg("failed to initialize buffersrc", e).into()), 234 | } 235 | 236 | Ok(GraphBuilder { 237 | input: GraphInput { 238 | input, 239 | ctx: buffersrc_ctx, 240 | }, 241 | outputs: Vec::new(), 242 | graph: GraphP { ptr: graph }, 243 | }) 244 | } 245 | } 246 | 247 | pub fn add_output(&mut self, output: Output) -> Result<&mut Self> { 248 | unsafe { 249 | // Configure the encoder based on the decoder, then initialize it 250 | let ref input = self.input.input; 251 | if (*output.codec_ctx).codec_id == sys::AVCodecID::AV_CODEC_ID_OPUS { 252 | // OPUS only supports 48kHz sample rates 253 | (*output.codec_ctx).sample_rate = 48000; 254 | } else if (*output.codec_ctx).codec_id == sys::AVCodecID::AV_CODEC_ID_AAC { 255 | (*output.codec_ctx).sample_rate = 48000; 256 | } else if (*output.codec_ctx).codec_id == sys::AVCodecID::AV_CODEC_ID_MP3 { 257 | // MP3 can't handle 192 kHz, so encode at 44.1 258 | (*output.codec_ctx).sample_rate = 44100; 259 | } else { 260 | (*output.codec_ctx).sample_rate = (*input.codec_ctx).sample_rate; 261 | } 262 | if (*output.codec_ctx).bit_rate == 0 { 263 | (*output.codec_ctx).bit_rate = (*input.codec_ctx).bit_rate; 264 | } 265 | (*output.codec_ctx).channel_layout = (*input.codec_ctx).channel_layout; 266 | (*output.codec_ctx).channels = sys::av_get_channel_layout_nb_channels((*input.codec_ctx).channel_layout); 267 | let time_base = sys::AVRational { 268 | num: 1, 269 | den: (*output.codec_ctx).sample_rate, 270 | }; 271 | (*output.codec_ctx).time_base = time_base; 272 | (*output.stream).time_base = time_base; 273 | 274 | sys::av_dict_copy(&mut (*output.ctx).metadata, (*self.input.input.ctx).metadata, 0); 275 | 276 | match sys::avcodec_open2(output.codec_ctx, (*output.codec_ctx).codec, ptr::null_mut()) { 277 | 0 => { } 278 | e => return Err(ErrorKind::FFmpeg("failed to open audio decoder", e).into()), 279 | } 280 | match sys::avcodec_parameters_from_context((*output.stream).codecpar, output.codec_ctx) { 281 | 0 => { }, 282 | e => return Err(ErrorKind::FFmpeg("failed to configure output stream", e).into()), 283 | } 284 | 285 | // Create and configure the sink filter 286 | let buffersink = sys::avfilter_get_by_name(str_conv!("abuffersink\0")); 287 | ck_null!(buffersink); 288 | let id = format!("out{}\0", self.outputs.len()); 289 | let buffersink_ctx = sys::avfilter_graph_alloc_filter(self.graph.ptr, buffersink, str_conv!(id)); 290 | ck_null!(buffersink_ctx); 291 | 292 | match sys::av_opt_set_bin(buffersink_ctx as *mut c_void, str_conv!("sample_rates\0"), mem::transmute(&(*output.codec_ctx).sample_rate), 293 | mem::size_of_val(&(*output.codec_ctx).sample_rate) as c_int, sys::AV_OPT_SEARCH_CHILDREN as i32) { 294 | 0 => { } 295 | e => return Err(ErrorKind::FFmpeg("failed to configure buffersink sample_rates", e).into()), 296 | } 297 | match sys::av_opt_set_bin(buffersink_ctx as *mut c_void, str_conv!("sample_fmts\0"), mem::transmute(&(*output.codec_ctx).sample_fmt), 298 | mem::size_of_val(&(*output.codec_ctx).sample_fmt) as c_int, sys::AV_OPT_SEARCH_CHILDREN as i32) { 299 | 0 => { } 300 | e => return Err(ErrorKind::FFmpeg("failed to configure buffersink sample_fmts", e).into()), 301 | } 302 | match sys::av_opt_set_bin(buffersink_ctx as *mut c_void, str_conv!("channel_layouts\0"), mem::transmute(&(*output.codec_ctx).channel_layout), 303 | mem::size_of_val(&(*output.codec_ctx).channel_layout) as c_int, sys::AV_OPT_SEARCH_CHILDREN as i32) { 304 | 0 => { } 305 | e => return Err(ErrorKind::FFmpeg("failed to configure buffersink channel_layouts", e).into()), 306 | } 307 | match sys::avfilter_init_str(buffersink_ctx, ptr::null()) { 308 | 0 => { } 309 | e => return Err(ErrorKind::FFmpeg("failed to initialize buffersink", e).into()), 310 | } 311 | self.outputs.push(GraphOutput { 312 | output, 313 | ctx: buffersink_ctx, 314 | }); 315 | } 316 | Ok(self) 317 | } 318 | 319 | pub fn build(self) -> Result { 320 | unsafe { 321 | // Create the audio split filter and wire it up 322 | let asplit = sys::avfilter_get_by_name(str_conv!("asplit\0")); 323 | ck_null!(asplit); 324 | let asplit_ctx = sys::avfilter_graph_alloc_filter(self.graph.ptr, asplit, str_conv!("splitter\0")); 325 | ck_null!(asplit_ctx); 326 | match sys::av_opt_set_int(asplit_ctx as *mut c_void, str_conv!("outputs\0"), self.outputs.len() as i64, sys::AV_OPT_SEARCH_CHILDREN as i32) { 327 | 0 => { } 328 | e => return Err(ErrorKind::FFmpeg("failed to configure asplit", e).into()), 329 | } 330 | match sys::avfilter_init_str(asplit_ctx, ptr::null()) { 331 | 0 => { } 332 | e => return Err(ErrorKind::FFmpeg("failed to initialize asplit", e).into()), 333 | } 334 | match sys::avfilter_link(self.input.ctx, 0, asplit_ctx, 0) { 335 | 0 => { } 336 | e => return Err(ErrorKind::FFmpeg("failed to link input to asplit", e).into()), 337 | } 338 | 339 | for (i, output) in self.outputs.iter().enumerate() { 340 | match sys::avfilter_link(asplit_ctx, i as u32, output.ctx, 0) { 341 | 0 => { } 342 | e => return Err(ErrorKind::FFmpeg("failed to link output to asplit", e).into()), 343 | } 344 | } 345 | 346 | // validate the graph 347 | match sys::avfilter_graph_config(self.graph.ptr, ptr::null_mut()) { 348 | 0 => { } 349 | e => return Err(ErrorKind::FFmpeg("failed to configure the filtergraph", e).into()), 350 | } 351 | 352 | // Align frame sizes on the buffersinks 353 | for o in self.outputs.iter() { 354 | sys::av_buffersink_set_frame_size(o.ctx, (*o.output.codec_ctx).frame_size as u32); 355 | } 356 | 357 | Ok(Graph { 358 | graph: self.graph, 359 | input: self.input, 360 | in_frame: sys::av_frame_alloc(), 361 | out_frame: sys::av_frame_alloc(), 362 | outputs: self.outputs, 363 | splitter: asplit_ctx, 364 | }) 365 | } 366 | } 367 | } 368 | 369 | unsafe impl Send for GraphBuilder { } 370 | 371 | impl Input { 372 | pub fn new(t: T, container: &str) -> Result { 373 | unsafe { 374 | // Cache page size used here 375 | let buffer = sys::av_malloc(FFMPEG_BUFFER_SIZE) as *mut u8; 376 | ck_null!(buffer); 377 | let opaque = Opaque::new(t); 378 | let io_ctx = sys::avio_alloc_context(buffer, FFMPEG_BUFFER_SIZE as c_int, 0, opaque.ptr, Some(read_cb::), None, None); 379 | ck_null!(io_ctx); 380 | 381 | let mut ps = sys::avformat_alloc_context(); 382 | if ps.is_null() { 383 | return Err(ErrorKind::Allocation.into()); 384 | } 385 | (*ps).pb = io_ctx; 386 | let cstr = CString::new(container).unwrap(); 387 | let format = sys::av_find_input_format(cstr.as_ptr()); 388 | if format.is_null() { 389 | bail!("Could not derive format from container!"); 390 | } 391 | let ctx = match sys::avformat_open_input(&mut ps, ptr::null(), format, ptr::null_mut()) { 392 | 0 => ps, 393 | e => return Err(ErrorKind::FFmpeg("failed to open input context", e).into()), 394 | }; 395 | match sys::avformat_find_stream_info(ctx, ptr::null_mut()) { 396 | 0 => { }, 397 | e => return Err(ErrorKind::FFmpeg("failed to get stream info", e).into()), 398 | } 399 | let mut codec = ptr::null_mut(); 400 | let stream_idx = match sys::av_find_best_stream(ctx, sys::AVMediaType_AVMEDIA_TYPE_AUDIO, -1, -1, &mut codec, 0) { 401 | s if s >= 0 => s as usize, 402 | e => return Err(ErrorKind::FFmpeg("failed to get audio stream from input", e).into()), 403 | }; 404 | if codec.is_null() { 405 | bail!("Failed to find a suitable codec!"); 406 | } 407 | 408 | let codec_ctx = sys::avcodec_alloc_context3(codec); 409 | ck_null!(codec_ctx); 410 | let stream = *(*ctx).streams.offset(stream_idx as isize); 411 | match sys::av_opt_set_int(codec_ctx as *mut c_void, str_conv!("refcounted_frames\0"), 1, 0) { 412 | 0 => { }, 413 | e => return Err(ErrorKind::FFmpeg("failed to configure codec", e).into()), 414 | } 415 | match sys::avcodec_parameters_to_context(codec_ctx, (*stream).codecpar) { 416 | 0 => { }, 417 | e => return Err(ErrorKind::FFmpeg("failed to configure output stream", e).into()), 418 | } 419 | match sys::avcodec_open2(codec_ctx, codec, ptr::null_mut()) { 420 | 0 => { } 421 | e => return Err(ErrorKind::FFmpeg("failed to open audio decoder", e).into()), 422 | } 423 | 424 | Ok(Input { 425 | ctx, 426 | codec_ctx, 427 | stream, 428 | _opaque: opaque, 429 | }) 430 | } 431 | } 432 | 433 | pub fn duration(&self) -> time::Duration { 434 | unsafe { 435 | let s = sys::av_q2d((*self.stream).time_base); 436 | let dur = s * (*self.stream).duration as f64; 437 | time::Duration::from_millis((dur * 1000.) as u64) 438 | } 439 | } 440 | 441 | pub fn metadata(&self) -> Metadata { 442 | unsafe { 443 | Metadata { 444 | title: self.get_metadata_val("title\0"), 445 | album: self.get_metadata_val("album\0"), 446 | artist: self.get_metadata_val("artist\0"), 447 | genre: self.get_metadata_val("genre\0"), 448 | date: self.get_metadata_val("date\0"), 449 | track: self.get_metadata_val("track\0"), 450 | } 451 | } 452 | } 453 | 454 | unsafe fn get_metadata_val(&self, opt: &str) -> Option { 455 | let entry = sys::av_dict_get((*self.ctx).metadata, str_conv!(opt), ptr::null(), 0); 456 | if entry.is_null() { 457 | None 458 | } else { 459 | let len = libc::strlen((*entry).value) + 1; 460 | let mut val = vec![0u8; len]; 461 | let mptr = val.as_mut_ptr() as *mut c_char; 462 | libc::strcpy(mptr, (*entry).value as *const c_char); 463 | val.pop(); 464 | String::from_utf8(val).ok() 465 | } 466 | } 467 | 468 | unsafe fn read_frames Result<()>>(&self, frame: *mut sys::AVFrame, mut f: F) -> Result<()> { 469 | let mut packet: sys::AVPacket = mem::uninitialized(); 470 | packet.data = ptr::null_mut(); 471 | packet.size = 0; 472 | 473 | 'outer: loop { 474 | loop { 475 | match sys::av_read_frame(self.ctx, &mut packet) { 476 | 0 => { } 477 | e if e == sys::AVERROR_EOF => { break 'outer; } 478 | e => { return Err(ErrorKind::FFmpeg("failed to read frame", e).into()); } 479 | } 480 | let stream_idx = (&packet).stream_index as isize; 481 | let stream = *(*self.ctx).streams.offset(stream_idx); 482 | if stream == self.stream { 483 | break; 484 | } else { 485 | sys::av_packet_unref(&mut packet); 486 | } 487 | } 488 | 489 | match { let r = sys::avcodec_send_packet(self.codec_ctx, &packet); sys::av_packet_unref(&mut packet); r} { 490 | 0 => { } 491 | e if e == sys::AVERROR_EOF => { break 'outer; } 492 | e => { return Err(ErrorKind::FFmpeg("failed to decode packet", e).into()); } 493 | } 494 | 495 | loop { 496 | // Try to get a frame, if not try to read packets and decode them 497 | match sys::avcodec_receive_frame(self.codec_ctx, frame) { 498 | 0 => { f()?; }, 499 | e if e == sys::AVERROR(libc::EAGAIN) => { break; } 500 | e if e == sys::AVERROR_EOF => { break 'outer; } 501 | e => { return Err(ErrorKind::FFmpeg("failed to receive frame", e).into()); } 502 | } 503 | } 504 | } 505 | 506 | Ok(()) 507 | } 508 | 509 | unsafe fn flush_frames Result<()>>(&self, frame: *mut sys::AVFrame, mut f: F) -> Result<()> { 510 | if ((*(*self.codec_ctx).codec).capabilities as u32 & sys::AV_CODEC_CAP_DELAY) == 0 { 511 | return Ok(()); 512 | } 513 | 514 | let mut packet: sys::AVPacket = mem::uninitialized(); 515 | packet.data = ptr::null_mut(); 516 | packet.size = 0; 517 | 518 | let mut res = match sys::avcodec_send_packet(self.codec_ctx, &packet) { 519 | 0 => Ok(()), 520 | e => Err(ErrorKind::FFmpeg("failed to handle EOF packet", e).into()), 521 | }; 522 | 523 | loop { 524 | // Try to get a frame, if not try to read packets and decode them 525 | let r = match sys::avcodec_receive_frame(self.codec_ctx, frame) { 526 | 0 => f(), 527 | e if e == sys::AVERROR(libc::EAGAIN) => break, 528 | e if e == sys::AVERROR_EOF => break, 529 | e => Err(ErrorKind::FFmpeg("failed to receive frame", e).into()), 530 | }; 531 | res = res.and(r); 532 | } 533 | res 534 | } 535 | } 536 | 537 | impl Drop for Input { 538 | fn drop(&mut self) { 539 | unsafe { 540 | sys::av_free((*(*self.ctx).pb).buffer as *mut c_void); 541 | sys::av_free((*self.ctx).pb as *mut c_void); 542 | sys::avformat_close_input(&mut self.ctx); 543 | sys::avcodec_free_context(&mut self.codec_ctx); 544 | } 545 | } 546 | } 547 | 548 | impl Output { 549 | pub fn new_writer(t: T, container: &str, codec_id: sys::AVCodecID::Type, bit_rate: Option) -> Result { 550 | struct SW(T); 551 | impl Write for SW { 552 | fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } 553 | fn flush(&mut self) -> io::Result<()> { self.0.flush() } 554 | } 555 | impl Sink for SW { }; 556 | Output::new(SW(t), container, codec_id, bit_rate) 557 | } 558 | 559 | pub fn new(t: T, container: &str, codec_id: sys::AVCodecID::Type, bit_rate: Option) -> Result { 560 | unsafe { 561 | let buffer = sys::av_malloc(4096) as *mut u8; 562 | ck_null!(buffer); 563 | let opaque = Opaque::new(t); 564 | let io_ctx = sys::avio_alloc_context(buffer, 4096, 1, opaque.ptr, None, Some(write_cb::), None); 565 | ck_null!(io_ctx); 566 | 567 | let mut ctx = ptr::null_mut(); 568 | let cstr = CString::new(container).unwrap(); 569 | match sys::avformat_alloc_output_context2(&mut ctx, ptr::null_mut(), cstr.as_ptr(), ptr::null()) { 570 | 0 => { }, 571 | e => return Err(ErrorKind::FFmpeg("failed to open output context", e).into()), 572 | }; 573 | (*ctx).pb = io_ctx; 574 | 575 | let codec = sys::avcodec_find_encoder(codec_id); 576 | if codec.is_null() { 577 | bail!("invalid codec provided!"); 578 | } 579 | let codec_ctx = sys::avcodec_alloc_context3(codec); 580 | ck_null!(codec_ctx); 581 | if let Some(br) = bit_rate { 582 | (*codec_ctx).bit_rate = br as i64 * 1000; 583 | } else { 584 | (*codec_ctx).bit_rate = 0; 585 | } 586 | (*codec_ctx).sample_fmt = *(*codec).sample_fmts; 587 | if container == "ogg" { 588 | // Set page size to a small duration(0.05s), to minimize skip loss 589 | sys::av_opt_set_int((*ctx).priv_data as *mut c_void, str_conv!("page_duration\0"), 50000, 0); 590 | } 591 | let stream = sys::avformat_new_stream(ctx, codec); 592 | ck_null!(stream); 593 | 594 | Ok(Output { 595 | ctx, 596 | _opaque: opaque, 597 | codec_ctx, 598 | stream, 599 | header_signal: sink_header_written::, 600 | packet_signal: sink_packet_written::, 601 | body_signal: sink_body_written::, 602 | }) 603 | } 604 | } 605 | 606 | unsafe fn write_frame(&self, frame: *mut sys::AVFrame) -> Result<()> { 607 | let mut out_pkt: sys::AVPacket = mem::uninitialized(); 608 | out_pkt.data = ptr::null_mut(); 609 | out_pkt.size = 0; 610 | sys::av_init_packet(&mut out_pkt); 611 | match sys::avcodec_send_frame(self.codec_ctx, frame) { 612 | 0 => { } 613 | e => return Err(ErrorKind::FFmpeg("failed to send frame to encoder", e).into()), 614 | } 615 | loop { 616 | match sys::avcodec_receive_packet(self.codec_ctx, &mut out_pkt) { 617 | 0 => { } 618 | e if e == sys::AVERROR(libc::EAGAIN) => { break } 619 | e if e == sys::AVERROR_EOF => { break } 620 | e => return Err(ErrorKind::FFmpeg("failed to get packet from encoder", e).into()), 621 | } 622 | 623 | sys::av_packet_rescale_ts(&mut out_pkt, (*self.codec_ctx).time_base, (*self.stream).time_base); 624 | out_pkt.stream_index = 0; 625 | let s = sys::av_q2d((*self.stream).time_base); 626 | let pts = s * out_pkt.pts as f64; 627 | 628 | match { let r = sys::av_write_frame(self.ctx, &mut out_pkt); sys::av_packet_unref(&mut out_pkt); r } { 629 | 0 => { } 630 | e => return Err(ErrorKind::FFmpeg("failed to write packet", e).into()), 631 | } 632 | sys::avio_flush((*self.ctx).pb); 633 | (self.packet_signal)(self._opaque.ptr, pts); 634 | sys::av_packet_unref(&mut out_pkt); 635 | } 636 | Ok(()) 637 | } 638 | 639 | unsafe fn flush_queue(&self) { 640 | let mut out_pkt: sys::AVPacket = mem::uninitialized(); 641 | out_pkt.data = ptr::null_mut(); 642 | out_pkt.size = 0; 643 | sys::av_init_packet(&mut out_pkt); 644 | sys::avcodec_send_frame(self.codec_ctx, ptr::null()); 645 | loop { 646 | match sys::avcodec_receive_packet(self.codec_ctx, &mut out_pkt) { 647 | 0 => { } 648 | _ => break 649 | } 650 | } 651 | } 652 | } 653 | 654 | impl Drop for Output { 655 | fn drop(&mut self) { 656 | unsafe { 657 | self.flush_queue(); 658 | sys::av_free((*(*self.ctx).pb).buffer as *mut c_void); 659 | sys::av_free((*self.ctx).pb as *mut c_void); 660 | sys::avformat_free_context(self.ctx); 661 | sys::avcodec_free_context(&mut self.codec_ctx); 662 | } 663 | } 664 | } 665 | 666 | unsafe extern fn read_cb(opaque: *mut c_void, buf: *mut u8, len: c_int) -> c_int { 667 | let reader = &mut *(opaque as *mut T); 668 | let s = slice::from_raw_parts_mut(buf, len as usize); 669 | match reader.read(s) { 670 | Ok(a) => a as c_int, 671 | Err(e) => { 672 | if e.kind() == io::ErrorKind::WouldBlock { 673 | 0 674 | } else { 675 | sys::AVERROR_EXIT 676 | } 677 | } 678 | } 679 | } 680 | 681 | unsafe extern fn write_cb(opaque: *mut c_void, buf: *mut u8, len: c_int) -> c_int { 682 | let writer = &mut *(opaque as *mut T); 683 | let s = slice::from_raw_parts(buf, len as usize); 684 | match writer.write(s) { 685 | Ok(a) => a as c_int, 686 | Err(e) => { 687 | if e.kind() == io::ErrorKind::WouldBlock { 688 | 0 689 | } else { 690 | sys::AVERROR_EXIT 691 | } 692 | } 693 | } 694 | } 695 | 696 | fn sink_header_written(opaque: *mut c_void) { 697 | unsafe { 698 | let s = &mut *(opaque as *mut T); 699 | s.header_written(); 700 | } 701 | } 702 | 703 | fn sink_packet_written(opaque: *mut c_void, pts: f64) { 704 | unsafe { 705 | let s = &mut *(opaque as *mut T); 706 | s.packet_written(pts); 707 | } 708 | } 709 | 710 | fn sink_body_written(opaque: *mut c_void) { 711 | unsafe { 712 | let s = &mut *(opaque as *mut T); 713 | s.body_written(); 714 | } 715 | } 716 | 717 | impl Drop for GraphP { 718 | fn drop(&mut self) { 719 | unsafe { 720 | sys::avfilter_graph_free(&mut self.ptr); 721 | } 722 | } 723 | } 724 | 725 | impl Opaque { 726 | fn new(t: T) -> Opaque { 727 | Opaque { 728 | ptr: Box::into_raw(Box::new(t)) as *mut c_void, 729 | cleanup: cleanup_opaque::, 730 | } 731 | } 732 | } 733 | 734 | impl Drop for Opaque { 735 | fn drop(&mut self) { 736 | (self.cleanup)(self.ptr); 737 | } 738 | } 739 | 740 | fn cleanup_opaque(opaque: *mut c_void) { 741 | unsafe { 742 | let b = Box::from_raw(opaque as *mut T); 743 | drop(b); 744 | } 745 | } 746 | 747 | fn get_error(code: c_int) -> String { 748 | let len = 200; 749 | let mut raw = vec![0u8; len]; 750 | unsafe { 751 | // To satisfy the CString invariant(buffer ending with a \0 and containing a single \0), 752 | // we create a large buffer to get the error string, then copy in the exact size later 753 | let ptr = raw.as_mut_ptr() as *mut c_char; 754 | sys::av_strerror(code, ptr, len); 755 | let len = libc::strlen(ptr) + 1; 756 | 757 | let mut msg = vec![0u8; len]; 758 | let mptr = msg.as_mut_ptr() as *mut c_char; 759 | libc::strcpy(mptr, ptr); 760 | // Pop the null byte 761 | msg.pop(); 762 | String::from_utf8(msg).unwrap_or("improper error".to_owned()) 763 | } 764 | } 765 | 766 | pub fn init() { 767 | unsafe { 768 | sys::avfilter_register_all(); 769 | } 770 | } 771 | 772 | #[cfg(test)] 773 | mod tests { 774 | use super::{GraphBuilder, Input, Output, init, Result}; 775 | use std::fs::File; 776 | 777 | #[test] 778 | fn test_instantiate_input() { 779 | init(); 780 | let f = File::open("test/test.mp3").unwrap(); 781 | if let Err(e) = Input::new(f, "mp3") { 782 | panic!("Failure: {}", e); 783 | } 784 | } 785 | 786 | #[test] 787 | fn test_instantiate_output() { 788 | init(); 789 | let d = vec![0u8; 1024 * 16]; 790 | Output::new_writer(d, "ogg", super::sys::AVCodecID::AV_CODEC_ID_VORBIS, None).unwrap(); 791 | } 792 | 793 | #[test] 794 | fn test_run_graph() { 795 | init(); 796 | run_graph().unwrap(); 797 | } 798 | 799 | fn run_graph() -> Result<()> { 800 | let fin = File::open("test/test.mp3").unwrap(); 801 | let fout1 = File::create("test/test.ogg").unwrap(); 802 | let fout2 = File::create("test/test2.ogg").unwrap(); 803 | let fout3 = File::create("test/test3.ogg").unwrap(); 804 | let fout4 = File::create("test/test4.mp3").unwrap(); 805 | let fout5 = File::create("test/test5.aac").unwrap(); 806 | 807 | let i = Input::new(fin, "mp3")?; 808 | let o1 = Output::new_writer(fout1, "ogg", super::AVCodecID::AV_CODEC_ID_OPUS, None)?; 809 | let o2 = Output::new_writer(fout2, "ogg", super::AVCodecID::AV_CODEC_ID_VORBIS, None)?; 810 | let o3 = Output::new_writer(fout3, "ogg", super::AVCodecID::AV_CODEC_ID_FLAC, None)?; 811 | let o4 = Output::new_writer(fout4, "mp3", super::AVCodecID::AV_CODEC_ID_MP3, None)?; 812 | let o5 = Output::new_writer(fout5, "adts", super::AVCodecID::AV_CODEC_ID_AAC, None)?; 813 | let mut gb = GraphBuilder::new(i)?; 814 | gb.add_output(o1)?; 815 | gb.add_output(o2)?; 816 | gb.add_output(o3)?; 817 | gb.add_output(o4)?; 818 | gb.add_output(o5)?; 819 | gb.build()?.run() 820 | } 821 | 822 | #[test] 823 | fn test_metadata() { 824 | init(); 825 | let fin = File::open("/tmp/in.flac").unwrap(); 826 | let i = Input::new(fin, "flac").unwrap(); 827 | let md = i.metadata(); 828 | println!("{:?}", md); 829 | assert!(md.title.is_some()); 830 | } 831 | } 832 | -------------------------------------------------------------------------------- /kaeru/test/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luminarys/kawa/31761b7f8f42eaf294319b0d512019e0e39becc9/kaeru/test/test.mp3 -------------------------------------------------------------------------------- /kaeru/test/test4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luminarys/kawa/31761b7f8f42eaf294319b0d512019e0e39becc9/kaeru/test/test4.mp3 -------------------------------------------------------------------------------- /kaeru/test/test5.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luminarys/kawa/31761b7f8f42eaf294319b0d512019e0e39becc9/kaeru/test/test5.aac -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::sync::mpsc::Sender; 3 | use std::collections::HashMap; 4 | use std::thread; 5 | use std::path::Path; 6 | use serde_json as serde; 7 | use rouille; 8 | 9 | use queue::{Queue, NewQueueEntry}; 10 | use config::ApiConfig; 11 | 12 | pub type Listeners = Arc>>; 13 | type SQueue = Arc>; 14 | type ApiChan = Arc>>; 15 | 16 | struct Server { 17 | queue: SQueue, 18 | listeners: Listeners, 19 | chan: ApiChan, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub enum QueuePos { 24 | Head, 25 | Tail, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub enum ApiMessage { 30 | Skip, 31 | Remove(QueuePos), 32 | Insert(QueuePos, NewQueueEntry), 33 | Clear, 34 | } 35 | 36 | #[derive(Serialize)] 37 | pub struct Resp { 38 | pub success: bool, 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub reason: Option, 41 | } 42 | 43 | #[derive(Serialize)] 44 | pub struct Listener { 45 | pub mount: String, 46 | pub path: String, 47 | pub headers: Vec
, 48 | } 49 | 50 | #[derive(Serialize)] 51 | pub struct Header { 52 | pub name: String, 53 | pub value: String, 54 | } 55 | 56 | impl Server { 57 | fn handle_request(&self, req: &rouille::Request) -> rouille::Response { 58 | router!(req, 59 | (GET) (/np) => { 60 | debug!("Handling now playing req"); 61 | let q = self.queue.lock().unwrap(); 62 | rouille::Response::from_data( 63 | "application/json", 64 | serde::to_string(&q.np().entry().serialize()).unwrap()) 65 | }, 66 | 67 | (GET) (/listeners) => { 68 | debug!("Handling listeners req"); 69 | let l = self.listeners.lock().unwrap(); 70 | rouille::Response::from_data( 71 | "application/json", 72 | serde::to_string::>(&l.iter().map(|(_, v)| v).collect()).unwrap()) 73 | }, 74 | 75 | (GET) (/queue) => { 76 | debug!("Handling queue disp req"); 77 | let q = self.queue.lock().unwrap(); 78 | rouille::Response::from_data( 79 | "application/json", 80 | serde::to_string(&q.entries().iter().map(|e| e.serialize()).collect::>()).unwrap()) 81 | }, 82 | 83 | (POST) (/queue/head) => { 84 | match serde::from_reader(req.data().unwrap()).map(|d| NewQueueEntry::deserialize(d)) { 85 | Ok(Some(qe)) => { 86 | debug!("Handling queue head insert"); 87 | if Path::new(&qe.path).exists() { 88 | self.chan.lock().unwrap().send(ApiMessage::Insert(QueuePos::Head, qe)).unwrap(); 89 | rouille::Response::from_data( 90 | "application/json", 91 | serde::to_string(&Resp::success()).unwrap()) 92 | } else { 93 | rouille::Response::from_data( 94 | "application/json", 95 | serde::to_string(&Resp::failure("file does not exist")).unwrap() 96 | ).with_status_code(400) 97 | } 98 | } 99 | Ok(None) => { 100 | rouille::Response::from_data( 101 | "application/json", 102 | serde::to_string(&Resp::failure("blob must contain path!")).unwrap() 103 | ).with_status_code(400) 104 | } 105 | Err(_) => { 106 | rouille::Response::from_data( 107 | "application/json", 108 | serde::to_string(&Resp::failure("malformed json sent")).unwrap() 109 | ).with_status_code(400) 110 | } 111 | } 112 | }, 113 | 114 | (DELETE) (/queue/head) => { 115 | debug!("Handling queue head remove"); 116 | self.chan.lock().unwrap().send(ApiMessage::Remove(QueuePos::Head)).unwrap(); 117 | rouille::Response::from_data( 118 | "application/json", 119 | serde::to_string(&Resp::success()).unwrap()) 120 | }, 121 | 122 | (POST) (/queue/tail) => { 123 | debug!("Handling queue tail insert"); 124 | match serde::from_reader(req.data().unwrap()).map(|d| NewQueueEntry::deserialize(d)) { 125 | Ok(Some(qe)) => { 126 | debug!("Handling queue head insert"); 127 | if Path::new(&qe.path).exists() { 128 | self.chan.lock().unwrap().send(ApiMessage::Insert(QueuePos::Tail, qe)).unwrap(); 129 | rouille::Response::from_data( 130 | "application/json", 131 | serde::to_string(&Resp::success()).unwrap()) 132 | } else { 133 | rouille::Response::from_data( 134 | "application/json", 135 | serde::to_string(&Resp::failure("file does not exist")).unwrap() 136 | ).with_status_code(400) 137 | } 138 | } 139 | Ok(None) => { 140 | rouille::Response::from_data( 141 | "application/json", 142 | serde::to_string(&Resp::failure("blob must contain path!")).unwrap() 143 | ).with_status_code(400) 144 | } 145 | Err(_) => { 146 | rouille::Response::from_data( 147 | "application/json", 148 | serde::to_string(&Resp::failure("malformed json sent")).unwrap() 149 | ).with_status_code(400) 150 | } 151 | } 152 | }, 153 | 154 | (DELETE) (/queue/tail) => { 155 | debug!("Handling queue tail remove"); 156 | self.chan.lock().unwrap().send(ApiMessage::Remove(QueuePos::Tail)).unwrap(); 157 | rouille::Response::from_data( 158 | "application/json", 159 | serde::to_string(&Resp::success()).unwrap()) 160 | }, 161 | 162 | (POST) (/skip) => { 163 | debug!("Handling queue skip"); 164 | self.chan.lock().unwrap().send(ApiMessage::Skip).unwrap(); 165 | rouille::Response::from_data( 166 | "application/json", 167 | serde::to_string(&Resp::success()).unwrap()) 168 | }, 169 | 170 | (POST) (/queue/clear) => { 171 | debug!("Handling queue clear"); 172 | self.chan.lock().unwrap().send(ApiMessage::Clear).unwrap(); 173 | rouille::Response::from_data( 174 | "application/json", 175 | serde::to_string(&Resp::success()).unwrap()) 176 | }, 177 | 178 | _ => rouille::Response::empty_404() 179 | ) 180 | } 181 | } 182 | 183 | impl Resp { 184 | fn success() -> Resp { 185 | Resp { 186 | success: true, 187 | reason: None, 188 | } 189 | } 190 | 191 | fn failure(reason: &str) -> Resp { 192 | Resp { 193 | success: false, 194 | reason: Some(String::from(reason)), 195 | } 196 | 197 | } 198 | } 199 | 200 | 201 | pub fn start_api(config: ApiConfig, queue: Arc>, listeners: Listeners, updates: Sender) { 202 | thread::spawn(move || { 203 | info!("Starting API"); 204 | let chan = Arc::new(Mutex::new(updates)); 205 | let serv = Server { 206 | queue: queue, 207 | chan: chan, 208 | listeners, 209 | }; 210 | rouille::start_server(("127.0.0.1", config.port), move |request| { 211 | serv.handle_request(request) 212 | }); 213 | }); 214 | } 215 | -------------------------------------------------------------------------------- /src/broadcast.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet, VecDeque}; 2 | use std::net::{TcpStream, TcpListener, Ipv4Addr}; 3 | use std::{time, thread, cmp}; 4 | use std::io::{self, Read, Write}; 5 | 6 | use {amy, httparse}; 7 | use url::Url; 8 | 9 | use api; 10 | use config::{Config, StreamConfig, Container}; 11 | 12 | const CLIENT_BUFFER_LEN: usize = 16384; 13 | // Number of frames to buffer by 14 | const BACK_BUFFER_LEN: usize = 256; 15 | // Seconds of inactivity until client timeout 16 | const CLIENT_TIMEOUT: u64 = 10; 17 | 18 | const CHUNK_SIZE: usize = 1024; 19 | static CHUNK_HEADER: &'static str = "400\r\n"; 20 | static CHUNK_FOOTER: &'static str = "\r\n"; 21 | 22 | pub struct Broadcaster { 23 | poll: amy::Poller, 24 | reg: amy::Registrar, 25 | data: amy::Receiver, 26 | /// Map of amy ID -> incoming client 27 | incoming: HashMap, 28 | /// Map from amy ID -> client 29 | clients: HashMap, 30 | /// Vec of mount names, idx is mount id 31 | streams: Vec, 32 | /// vec where idx: mount id , val: set of clients attached to mount id 33 | client_mounts: Vec>, 34 | listener: TcpListener, 35 | listeners: api::Listeners, 36 | lid: usize, 37 | tid: usize, 38 | name: String, 39 | } 40 | 41 | #[derive(Clone, Debug)] 42 | pub struct Buffer { 43 | mount: usize, 44 | data: BufferData 45 | } 46 | 47 | #[derive(Clone, Debug)] 48 | pub enum BufferData { 49 | Header(Vec), 50 | Frame { data: Vec, pts: f64 }, 51 | Trailer(Vec), 52 | } 53 | 54 | struct Client { 55 | conn: TcpStream, 56 | buffer: VecDeque, 57 | last_action: time::Instant, 58 | agent: Agent, 59 | chunker: Chunker, 60 | } 61 | 62 | #[derive(PartialEq)] 63 | enum Agent { 64 | MPV, 65 | MPD, 66 | Other, 67 | } 68 | 69 | struct Incoming { 70 | last_action: time::Instant, 71 | conn: TcpStream, 72 | buf: [u8; 1024], 73 | len: usize, 74 | } 75 | 76 | struct Stream { 77 | config: StreamConfig, 78 | header: Vec, 79 | buffer: VecDeque>, 80 | } 81 | 82 | enum Chunker { 83 | Header(&'static str), 84 | Body(usize), 85 | Footer(&'static str), 86 | } 87 | 88 | // Write 89 | enum WR { 90 | Ok, 91 | Inc(usize), 92 | Blocked, 93 | Err, 94 | } 95 | 96 | pub fn start(cfg: &Config, listeners: api::Listeners) -> amy::Sender { 97 | let (mut b, tx) = Broadcaster::new(cfg, listeners).unwrap(); 98 | thread::spawn(move || b.run()); 99 | tx 100 | } 101 | 102 | impl Broadcaster { 103 | pub fn new(cfg: &Config, listeners: api::Listeners) -> io::Result<(Broadcaster, amy::Sender)> { 104 | let poll = amy::Poller::new()?; 105 | let mut reg = poll.get_registrar()?; 106 | let listener = TcpListener::bind((Ipv4Addr::new(0, 0, 0, 0), cfg.radio.port))?; 107 | listener.set_nonblocking(true)?; 108 | let lid = reg.register(&listener, amy::Event::Read)?; 109 | let tid = reg.set_interval(5000)?; 110 | let (tx, rx) = reg.channel()?; 111 | let mut streams = Vec::new(); 112 | for config in cfg.streams.iter().cloned() { 113 | streams.push(Stream { config, header: Vec::new(), buffer: VecDeque::with_capacity(BACK_BUFFER_LEN) }) 114 | } 115 | 116 | Ok((Broadcaster { 117 | poll, 118 | reg, 119 | data: rx, 120 | incoming: HashMap::new(), 121 | clients: HashMap::new(), 122 | streams, 123 | client_mounts: vec![HashSet::new(); cfg.streams.len()], 124 | listener, 125 | listeners, 126 | lid, 127 | tid, 128 | name: cfg.radio.name.clone(), 129 | }, tx)) 130 | } 131 | 132 | pub fn run(&mut self) { 133 | debug!("starting broadcaster"); 134 | loop { 135 | for n in self.poll.wait(15).unwrap() { 136 | if n.id == self.lid { 137 | self.accept_client(); 138 | } else if n.id == self.tid{ 139 | self.reap(); 140 | } else if n.id == self.data.get_id() { 141 | self.process_buffer(); 142 | } else if self.incoming.contains_key(&n.id) { 143 | self.process_incoming(n.id); 144 | } else if self.clients.contains_key(&n.id) { 145 | self.process_client(n.id); 146 | } else { 147 | warn!("Received amy event for bad id: {}", n.id); 148 | } 149 | } 150 | } 151 | } 152 | 153 | fn reap(&mut self) { 154 | let mut ids = Vec::new(); 155 | for (id, inc) in self.incoming.iter() { 156 | if inc.last_action.elapsed() > time::Duration::from_secs(CLIENT_TIMEOUT) { 157 | ids.push(*id); 158 | } 159 | } 160 | for id in ids.iter() { 161 | self.remove_incoming(id); 162 | } 163 | ids.clear(); 164 | 165 | for (id, client) in self.clients.iter() { 166 | if client.last_action.elapsed() > time::Duration::from_secs(CLIENT_TIMEOUT) { 167 | ids.push(*id); 168 | } 169 | } 170 | for id in ids.iter() { 171 | self.remove_client(id); 172 | } 173 | } 174 | 175 | fn accept_client(&mut self) { 176 | loop { 177 | match self.listener.accept() { 178 | Ok((conn, ip)) => { 179 | debug!("Accepted new connection from {:?}!", ip); 180 | let pid = self.reg.register(&conn, amy::Event::Read).unwrap(); 181 | self.incoming.insert(pid, Incoming::new(conn)); 182 | } 183 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 184 | break; 185 | } 186 | _ => { unimplemented!(); } 187 | } 188 | } 189 | 190 | } 191 | 192 | fn process_buffer(&mut self) { 193 | while let Ok(buf) = self.data.try_recv() { 194 | for id in self.client_mounts[buf.mount].clone() { 195 | if { 196 | let client = self.clients.get_mut(&id).unwrap(); 197 | if buf.data.is_data() || client.agent == Agent::MPD { 198 | client.send_data(buf.data.frame()) 199 | } else { 200 | Ok(()) 201 | } 202 | }.is_err() { 203 | self.remove_client(&id); 204 | } 205 | } 206 | { 207 | let ref mut sb = self.streams[buf.mount].buffer; 208 | sb.push_back(buf.data.frame().to_vec()); 209 | while sb.len() > BACK_BUFFER_LEN { 210 | sb.pop_front(); 211 | } 212 | } 213 | match buf.data { 214 | BufferData::Header(h) => { 215 | self.streams[buf.mount].header = h; 216 | } 217 | _ => { } 218 | } 219 | } 220 | } 221 | 222 | fn process_incoming(&mut self, id: usize) { 223 | match self.incoming.get_mut(&id).unwrap().process() { 224 | Ok(Some((path, agent, headers))) => { 225 | // Need this 226 | let ub = Url::parse("http://localhost/").unwrap(); 227 | let url = if let Ok(u) = ub.join(&path) { 228 | u 229 | } else { 230 | self.remove_incoming(&id); 231 | return; 232 | }; 233 | let mount = url.path(); 234 | 235 | let inc = self.incoming.remove(&id).unwrap(); 236 | for (mid, stream) in self.streams.iter().enumerate() { 237 | if mount.ends_with(&stream.config.mount) { 238 | debug!("Adding a client to stream {}", stream.config.mount); 239 | // Swap to write only mode 240 | self.reg.reregister(id, &inc.conn, amy::Event::Write).unwrap(); 241 | let mut client = Client::new(inc.conn, agent); 242 | // Send header, and buffered data 243 | if client.write_resp(&self.name, &stream.config) 244 | .and_then(|_| client.send_data(&stream.header)) 245 | .and_then(|_| { 246 | // TODO: Consider edge case where the header is double sent 247 | for buf in stream.buffer.iter() { 248 | client.send_data(buf)? 249 | } 250 | Ok(()) 251 | }) 252 | .is_ok() 253 | { 254 | self.client_mounts[mid].insert(id); 255 | self.clients.insert(id, client); 256 | self.listeners.lock().unwrap().insert(id, api::Listener { 257 | mount: stream.config.mount.clone(), 258 | path: path.clone(), 259 | headers, 260 | }); 261 | } else { 262 | debug!("Failed to write data to client"); 263 | } 264 | return; 265 | } 266 | } 267 | debug!("Client specified unknown path: {}", mount); 268 | } 269 | Ok(None) => { }, 270 | Err(()) => self.remove_incoming(&id), 271 | } 272 | } 273 | 274 | fn process_client(&mut self, id: usize) { 275 | match self.clients.get_mut(&id).unwrap().flush_buffer() { 276 | Err(()) => self.remove_client(&id), 277 | _ => { } 278 | } 279 | } 280 | 281 | fn remove_client(&mut self, id: &usize) { 282 | let client = self.clients.remove(id).unwrap(); 283 | self.reg.deregister(&client.conn).unwrap(); 284 | self.listeners.lock().unwrap().remove(id); 285 | // Remove from client_mounts map too 286 | for m in self.client_mounts.iter_mut() { 287 | m.remove(id); 288 | } 289 | } 290 | 291 | fn remove_incoming(&mut self, id: &usize) { 292 | let inc = self.incoming.remove(id).unwrap(); 293 | self.reg.deregister(&inc.conn).unwrap(); 294 | } 295 | } 296 | 297 | impl Buffer { 298 | pub fn new(mount: usize, data: BufferData) -> Buffer { 299 | Buffer { mount, data } 300 | } 301 | } 302 | 303 | impl BufferData { 304 | pub fn is_data(&self) -> bool { 305 | match *self { 306 | BufferData::Frame { .. } => true, 307 | _ => false, 308 | } 309 | } 310 | 311 | pub fn frame(&self) -> &[u8] { 312 | match *self { 313 | BufferData::Header(ref f) 314 | | BufferData::Frame { data: ref f, .. } 315 | | BufferData::Trailer(ref f) => f, 316 | } 317 | } 318 | } 319 | 320 | impl Incoming { 321 | fn new(conn: TcpStream) -> Incoming { 322 | conn.set_nonblocking(true).unwrap(); 323 | Incoming { 324 | last_action: time::Instant::now(), 325 | conn, 326 | buf: [0; 1024], 327 | len: 0, 328 | } 329 | } 330 | 331 | fn process(&mut self) -> Result)>, ()> { 332 | self.last_action = time::Instant::now(); 333 | if self.read().is_ok() { 334 | let mut headers = [httparse::EMPTY_HEADER; 16]; 335 | let mut req = httparse::Request::new(&mut headers); 336 | match req.parse(&self.buf[..self.len]) { 337 | Ok(httparse::Status::Complete(_)) => { 338 | if let Some(p) = req.path { 339 | let mut agent = Agent::Other; 340 | let ah: Vec = req.headers.into_iter() 341 | .filter(|h| *h != &httparse::EMPTY_HEADER) 342 | .map(|h| api::Header { 343 | name: h.name.to_owned(), 344 | value: String::from_utf8(h.value.to_vec()).unwrap_or("".to_owned()) 345 | }) 346 | .map(|h| { 347 | if h.name == "User-Agent" { 348 | if h.value.starts_with("mpv") { 349 | agent = Agent::MPV; 350 | } else if h.value.starts_with("mpd") || h.value.starts_with("Music Player Daemon") { 351 | agent = Agent::MPD; 352 | } 353 | } 354 | h 355 | }) 356 | .collect(); 357 | Ok(Some((p.to_owned(), agent, ah))) 358 | } else { 359 | Err(()) 360 | } 361 | } 362 | Ok(httparse::Status::Partial) => Ok(None), 363 | Err(_) => Err(()), 364 | } 365 | } else { 366 | Err(()) 367 | } 368 | } 369 | 370 | fn read(&mut self) -> Result<(), ()> { 371 | loop { 372 | match self.conn.read(&mut self.buf[self.len..]) { 373 | Ok(0) => return Err(()), 374 | Ok(a) => self.len += a, 375 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(()), 376 | Err(_) => return Err(()), 377 | } 378 | } 379 | } 380 | } 381 | 382 | impl Client { 383 | fn new(conn: TcpStream, agent: Agent) -> Client { 384 | Client { 385 | conn, 386 | buffer: VecDeque::with_capacity(CLIENT_BUFFER_LEN), 387 | last_action: time::Instant::now(), 388 | chunker: Chunker::new(), 389 | agent, 390 | } 391 | } 392 | 393 | fn write_resp(&mut self, name: &str, config: &StreamConfig) -> Result<(), ()> { 394 | let lines = vec![ 395 | format!("HTTP/1.1 200 OK"), 396 | format!("Server: {}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")), 397 | format!("Content-Type: {}", if let Container::MP3 = config.container { 398 | "audio/mpeg" 399 | } else if let Container::FLAC = config.container { 400 | "audio/flac" 401 | } else if let Container::AAC = config.container { 402 | "audio/aac" 403 | } else { 404 | "audio/ogg" 405 | }), 406 | format!("Transfer-Encoding: chunked"), 407 | format!("Connection: keep-alive"), 408 | format!("Cache-Control: no-cache"), 409 | format!("x-audiocast-name: {}", name), 410 | match config.bitrate { 411 | Some(bitrate) => format!("x-audiocast-bitrate: {}", bitrate), 412 | None => format!("x-audiocast-bitrate: 0"), 413 | }, 414 | format!("icy-name: {}", name), 415 | match config.bitrate { 416 | Some(bitrate) => format!("icy-br: {}", bitrate), 417 | None => format!("icy-br: 0"), 418 | }, 419 | ]; 420 | let data = lines.join("\r\n") + "\r\n\r\n"; 421 | match self.conn.write(data.as_bytes()) { 422 | Ok(0) => Err(()), 423 | Ok(a) if a == data.as_bytes().len() => { Ok(() )} 424 | Ok(_) => unreachable!(), 425 | Err(_) => Err(()) 426 | } 427 | } 428 | 429 | fn send_data(&mut self, data: &[u8]) -> Result<(), ()> { 430 | if data.len() == 0 { 431 | return Ok(()); 432 | } 433 | 434 | // Attempt to flush buffer first 435 | match self.flush_buffer() { 436 | Ok(true) => { }, 437 | Ok(false) => { 438 | self.buffer.extend(data.iter()); 439 | while self.buffer.len() > CLIENT_BUFFER_LEN { 440 | self.buffer.pop_front(); 441 | } 442 | return Ok(()) 443 | }, 444 | Err(()) => return Err(()), 445 | } 446 | 447 | match self.chunker.write(&mut self.conn, data) { 448 | Ok(Some(0)) => Err(()), 449 | // Complete write, do nothing 450 | Ok(Some(a)) if a == data.len() => Ok(()), 451 | // Incomplete write, append to buf 452 | Ok(Some(a)) => { 453 | self.buffer.extend(data[0..a].iter()); 454 | while self.buffer.len() > CLIENT_BUFFER_LEN { 455 | self.buffer.pop_front(); 456 | } 457 | Ok(()) 458 | } 459 | Ok(None) => { 460 | self.buffer.extend(data.iter()); 461 | while self.buffer.len() > CLIENT_BUFFER_LEN { 462 | self.buffer.pop_front(); 463 | } 464 | Ok(()) 465 | } 466 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { 467 | self.buffer.extend(data.iter()); 468 | while self.buffer.len() > CLIENT_BUFFER_LEN { 469 | self.buffer.pop_front(); 470 | } 471 | Ok(()) 472 | } 473 | Err(_) => Err(()), 474 | } 475 | } 476 | 477 | fn flush_buffer(&mut self) -> Result { 478 | self.last_action = time::Instant::now(); 479 | 480 | if self.buffer.is_empty() { 481 | return Ok(true); 482 | } 483 | loop { 484 | match self.write_buffer() { 485 | WR::Ok => { 486 | self.buffer.clear(); 487 | return Ok(true); 488 | } 489 | WR::Inc(a) => { 490 | for _ in 0..a { 491 | self.buffer.pop_front(); 492 | } 493 | } 494 | WR::Blocked => return Ok(false), 495 | WR::Err => return Err(()), 496 | } 497 | } 498 | } 499 | 500 | fn write_buffer(&mut self) -> WR { 501 | let (head, tail) = self.buffer.as_slices(); 502 | match self.chunker.write(&mut self.conn, head) { 503 | Ok(Some(0)) => WR::Err, 504 | Ok(Some(a)) if a == head.len() => { 505 | match self.chunker.write(&mut self.conn, tail) { 506 | Ok(Some(0)) => WR::Err, 507 | Ok(Some(i)) if i == tail.len() => WR::Ok, 508 | Ok(Some(i)) => WR::Inc(i + a), 509 | Ok(None) => WR::Inc(a), 510 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => WR::Inc(a), 511 | Err(_) => WR::Err, 512 | } 513 | }, 514 | Ok(Some(a)) => WR::Inc(a), 515 | Ok(None) => WR::Inc(0), 516 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => WR::Blocked, 517 | Err(_) => WR::Err, 518 | } 519 | } 520 | } 521 | 522 | impl Chunker { 523 | fn new() -> Chunker { 524 | Chunker::Header(CHUNK_HEADER) 525 | } 526 | 527 | fn write(&mut self, conn: &mut T, data: &[u8]) -> io::Result> { 528 | match *self { 529 | Chunker::Header(s) => { 530 | let amnt = conn.write(s.as_bytes())?; 531 | if amnt == s.len() { 532 | *self = Chunker::Body(0); 533 | self.write(conn, data) 534 | } else { 535 | *self = Chunker::Header(&s[amnt..]); 536 | Ok(None) 537 | } 538 | } 539 | Chunker::Body(i) => { 540 | let amnt = conn.write(&data[..cmp::min(CHUNK_SIZE - i, data.len())])?; 541 | if i + amnt == CHUNK_SIZE { 542 | *self = Chunker::Footer(CHUNK_FOOTER); 543 | // Continue writing, and add on the current amount 544 | // written to the result. 545 | // We ignore errors here for now, since they should 546 | // be reported later anyways. TODO: think more about it 547 | match self.write(conn, &data[amnt..]) { 548 | Ok(r) => { 549 | Ok(Some(match r { 550 | Some(a) => a + amnt, 551 | None => amnt 552 | })) 553 | } 554 | Err(_) => Ok(Some(amnt)) 555 | } 556 | } else { 557 | *self = Chunker::Body(i + amnt); 558 | Ok(Some(amnt)) 559 | } 560 | } 561 | Chunker::Footer(s) => { 562 | let amnt = conn.write(s.as_bytes())?; 563 | if amnt == s.len() { 564 | *self = Chunker::Header(CHUNK_HEADER); 565 | self.write(conn, data) 566 | } else { 567 | *self = Chunker::Footer(&s[amnt..]); 568 | Ok(None) 569 | } 570 | } 571 | } 572 | } 573 | } 574 | 575 | #[test] 576 | fn test_footer_transition() { 577 | use std::io::Cursor; 578 | let mut c = Chunker::Footer("hello world"); 579 | let mut d = [0u8; 5]; 580 | let mut v = Cursor::new(&mut d[..]); 581 | c.write(&mut v, &[]).unwrap(); 582 | assert_eq!(v.into_inner(), b"hello"); 583 | if let Chunker::Footer(" world") = c { 584 | } else { unreachable!() }; 585 | } 586 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use toml; 2 | use kaeru::AVCodecID; 3 | 4 | use std::sync::Arc; 5 | use std::fs::File; 6 | use std::io::Read; 7 | 8 | #[derive(Clone)] 9 | pub struct Config { 10 | pub api: ApiConfig, 11 | pub radio: RadioConfig, 12 | pub streams: Vec, 13 | pub queue: QueueConfig, 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct StreamConfig { 18 | pub mount: String, 19 | pub bitrate: Option, 20 | pub container: Container, 21 | pub codec: AVCodecID::Type, 22 | } 23 | 24 | #[derive(Clone, Deserialize)] 25 | #[serde(deny_unknown_fields)] 26 | pub struct RadioConfig { 27 | pub port: u16, 28 | pub name: String, 29 | } 30 | 31 | #[derive(Clone, Deserialize)] 32 | #[serde(deny_unknown_fields)] 33 | pub struct ApiConfig { 34 | pub port: u16, 35 | } 36 | 37 | #[derive(Clone)] 38 | pub struct QueueConfig { 39 | pub random: String, 40 | pub np: String, 41 | pub fallback: (Arc>, String), 42 | pub buffer_len: usize, 43 | } 44 | 45 | #[derive(Clone)] 46 | pub enum Container { 47 | Ogg, 48 | MP3, 49 | AAC, 50 | FLAC, 51 | } 52 | 53 | // Some unfortunate code duplication because you can't derive Deserialize for newtypes in this case 54 | #[derive(Deserialize)] 55 | #[serde(deny_unknown_fields)] 56 | struct InternalConfig { 57 | pub api: ApiConfig, 58 | pub radio: RadioConfig, 59 | pub streams: Vec, 60 | pub queue: InternalQueueConfig, 61 | } 62 | 63 | #[derive(Deserialize)] 64 | #[serde(deny_unknown_fields)] 65 | struct InternalStreamConfig { 66 | pub mount: String, 67 | pub bitrate: Option, 68 | pub container: String, 69 | pub codec: Option, 70 | } 71 | 72 | #[derive(Deserialize)] 73 | #[serde(deny_unknown_fields)] 74 | struct InternalQueueConfig { 75 | #[serde(rename = "random_song_api")] 76 | pub random: String, 77 | pub np: String, 78 | pub fallback: String, 79 | pub buffer_len: usize, 80 | } 81 | 82 | impl InternalConfig { 83 | fn into_config(self) -> Result { 84 | // TODO: Should be alloca'ed, but w/e 85 | let mut streams = Vec::with_capacity(self.streams.len()); 86 | for s in self.streams { 87 | let container = match &*s.container { 88 | "ogg" => Container::Ogg, 89 | "mp3" => Container::MP3, 90 | "aac" => Container::AAC, 91 | "flac" => Container::FLAC, 92 | _ => return Err(format!("Currently, only ogg, mp3, aac, and flac are supported as containers.")), 93 | }; 94 | let codec = if let Some(c) = s.codec { 95 | match &*c { 96 | "opus" => AVCodecID::AV_CODEC_ID_OPUS, 97 | "vorbis" => AVCodecID::AV_CODEC_ID_VORBIS, 98 | "flac" => AVCodecID::AV_CODEC_ID_FLAC, 99 | "mp3" => AVCodecID::AV_CODEC_ID_MP3, 100 | "aac" => AVCodecID::AV_CODEC_ID_AAC, 101 | _ => return Err(format!("Currently, only opus, vorbis, flac, aac, and mp3 are \ 102 | supported as codecs.")), 103 | } 104 | } else { 105 | // Default to OPUS for Ogg, and MP3 for MP3 106 | match container { 107 | Container::Ogg => AVCodecID::AV_CODEC_ID_OPUS, 108 | Container::MP3 => AVCodecID::AV_CODEC_ID_MP3, 109 | Container::AAC => AVCodecID::AV_CODEC_ID_AAC, 110 | Container::FLAC => AVCodecID::AV_CODEC_ID_FLAC, 111 | } 112 | }; 113 | 114 | streams.push(StreamConfig { 115 | mount: s.mount, 116 | bitrate: s.bitrate.map(|b| b as i64), 117 | container: container, 118 | codec: codec, 119 | }) 120 | } 121 | 122 | let mut buffer = Vec::new(); 123 | File::open(&self.queue.fallback).expect("Queue fallback must be present and a vaild file").read_to_end(&mut buffer).expect("IO ERROR!"); 124 | let fbp = self.queue.fallback.split('.').last().expect("Queue fallback must have a container extension"); 125 | if fbp != "ogg" && fbp != "mp3" && fbp != "flac" { 126 | panic!("Fallback must be mp3 or ogg or flac"); 127 | } 128 | Ok(Config { 129 | api: self.api, 130 | radio: self.radio, 131 | streams: streams, 132 | queue: QueueConfig { 133 | random: self.queue.random, 134 | np: self.queue.np, 135 | fallback: (Arc::new(buffer), fbp.to_owned()), 136 | buffer_len: self.queue.buffer_len, 137 | }, 138 | }) 139 | } 140 | } 141 | 142 | pub fn parse_config(input: &str) -> Result { 143 | let parsed: Result = toml::de::from_str(input); 144 | if let Err(e) = parsed { 145 | Err(format!("{}", e)) 146 | } else { 147 | parsed.unwrap().into_config() 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "nightly", feature(alloc_system))] 2 | #[cfg(feature = "nightly")] 3 | extern crate alloc_system; 4 | 5 | #[macro_use] 6 | extern crate log; 7 | extern crate env_logger; 8 | extern crate toml; 9 | extern crate serde; 10 | extern crate serde_json; 11 | extern crate reqwest; 12 | #[macro_use] 13 | extern crate serde_derive; 14 | #[macro_use] 15 | extern crate rouille; 16 | extern crate amy; 17 | extern crate httparse; 18 | extern crate url; 19 | 20 | extern crate kaeru; 21 | 22 | mod radio; 23 | mod config; 24 | mod api; 25 | mod queue; 26 | mod util; 27 | mod tc_queue; 28 | mod prebuffer; 29 | mod broadcast; 30 | 31 | use std::env; 32 | use std::sync::{Arc, Mutex, mpsc}; 33 | use std::io::{Read}; 34 | use std::collections::HashMap; 35 | 36 | fn main() { 37 | // Wow this is dumb 38 | if std::env::var("RUST_LOG").is_err() { 39 | std::env::set_var("RUST_LOG", "info"); 40 | } 41 | env_logger::init(); 42 | 43 | #[cfg(feature = "nightly")] 44 | info!("Using system alloc"); 45 | 46 | info!("Initializing ffmpeg"); 47 | kaeru::init(); 48 | 49 | let path = env::args().nth(1).unwrap_or("config.toml".to_owned()); 50 | let mut s = String::new(); 51 | if let Ok(mut f) = std::fs::File::open(&path) { 52 | if f.read_to_string(&mut s).is_err() { 53 | error!("Config file could not be read!"); 54 | return; 55 | } 56 | } else { 57 | error!("A config file path must be passed as argv[1] or must exist as ./config.toml"); 58 | return; 59 | } 60 | 61 | info!("Initializing config"); 62 | let config = match config::parse_config(&s) { 63 | Ok(c) => c, 64 | Err(e) => { 65 | error!("Failed to parse config: {}", e); 66 | return; 67 | } 68 | }; 69 | 70 | info!("Starting"); 71 | let queue = Arc::new(Mutex::new(queue::Queue::new(config.clone()))); 72 | let listeners = Arc::new(Mutex::new(HashMap::new())); 73 | let (tx, rx) = mpsc::channel(); 74 | let btx = broadcast::start(&config, listeners.clone()); 75 | api::start_api(config.api.clone(), queue.clone(), listeners, tx); 76 | radio::start_streams(config.clone(), queue, rx, btx); 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use super::*; 82 | use super::kaeru::{Input, Output, GraphBuilder}; 83 | use std::{thread, io}; 84 | use std::fs::File; 85 | 86 | #[ignore] 87 | #[test] 88 | fn test_tc() { 89 | #[cfg(feature = "nightly")] 90 | info!(LOG, "Using system alloc"); 91 | kaeru::init(); 92 | tc(); 93 | thread::sleep_ms(30000); 94 | } 95 | 96 | struct Dum(usize); 97 | 98 | impl io::Write for Dum { 99 | fn write(&mut self, buf: &[u8]) -> io::Result { 100 | self.0 += buf.len(); 101 | return Ok(buf.len()); 102 | if self.0 < 4096 * 32 { 103 | Ok(buf.len()) 104 | } else { 105 | Err(io::Error::new(io::ErrorKind::Other, "oh no!")) 106 | } 107 | } 108 | 109 | fn flush(&mut self) -> io::Result<()> { Ok(()) } 110 | } 111 | 112 | fn tc() -> kaeru::Result<()> { 113 | let fin = File::open("/tmp/in.mp3").unwrap(); 114 | let i = Input::new(fin, "mp3")?; 115 | 116 | let o1 = Output::new_writer(Dum(0), "mp3", kaeru::AVCodecID::AV_CODEC_ID_MP3, Some(192))?; 117 | let o2 = Output::new_writer(Dum(0), "ogg", kaeru::AVCodecID::AV_CODEC_ID_OPUS, Some(192))?; 118 | let o3 = Output::new_writer(Dum(0), "adts", kaeru::AVCodecID::AV_CODEC_ID_AAC, Some(192))?; 119 | let o4 = Output::new_writer(Dum(0), "ogg", kaeru::AVCodecID::AV_CODEC_ID_FLAC, None)?; 120 | let mut gb = GraphBuilder::new(i)?; 121 | gb.add_output(o1)?.add_output(o2)?.add_output(o3)?.add_output(o4)?; 122 | let g = gb.build()?; 123 | let gt = thread::spawn(move || g.run().unwrap()); 124 | gt.join(); 125 | Ok(()) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/prebuffer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use kaeru::Metadata; 3 | use tc_queue; 4 | 5 | pub struct PreBuffer { 6 | pub buffer: tc_queue::QR, 7 | pub metadata: Arc, 8 | } 9 | 10 | impl PreBuffer { 11 | pub fn new(buffer: tc_queue::QR, md: Arc) -> PreBuffer { 12 | PreBuffer { 13 | buffer, 14 | metadata: md, 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/queue.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, fs, thread, sync}; 2 | use std::io::{self, Read, BufReader}; 3 | use std::collections::VecDeque; 4 | use config::{Config, Container}; 5 | use reqwest; 6 | use prebuffer::PreBuffer; 7 | use serde_json as serde; 8 | use serde_json::Map; 9 | use serde_json::Value as JSON; 10 | use tc_queue; 11 | use kaeru; 12 | 13 | pub struct Queue { 14 | entries: VecDeque, 15 | next: QueueBuffer, 16 | np: QueueBuffer, 17 | counter: u64, 18 | last_id: u64, 19 | cfg: Config, 20 | } 21 | 22 | #[derive(Clone, Debug, Deserialize, Default, PartialEq)] 23 | pub struct NewQueueEntry { 24 | pub data: Map, 25 | pub path: String, 26 | } 27 | 28 | #[derive(Clone, Debug, Default, Serialize, PartialEq)] 29 | pub struct QueueEntry { 30 | pub id: u64, 31 | pub data: Map, 32 | pub path: String, 33 | } 34 | 35 | #[derive(Default)] 36 | pub struct QueueBuffer { 37 | entry: QueueEntry, 38 | bufs: Vec, 39 | } 40 | 41 | impl Queue { 42 | pub fn new(cfg: Config) -> Queue { 43 | let mut q = Queue { 44 | np: Default::default(), 45 | next: Default::default(), 46 | entries: VecDeque::new(), 47 | cfg: cfg, 48 | counter: 0, 49 | last_id: 0, 50 | }; 51 | q.start_next_tc(); 52 | q 53 | } 54 | 55 | pub fn np(&self) -> &QueueBuffer { 56 | &self.np 57 | } 58 | 59 | pub fn entries(&self) -> &VecDeque { 60 | &self.entries 61 | } 62 | 63 | pub fn push(&mut self, nqe: NewQueueEntry) { 64 | debug!("Inserting {:?} into queue tail!", nqe); 65 | let qe = self.queue_entry_from_new(nqe); 66 | self.entries.push_back(qe); 67 | if self.entries.len() == 1 { 68 | self.start_next_tc(); 69 | } 70 | } 71 | 72 | pub fn push_head(&mut self, nqe: NewQueueEntry) { 73 | debug!("Inserting {:?} into queue head!", nqe); 74 | let qe = self.queue_entry_from_new(nqe); 75 | self.entries.push_front(qe); 76 | self.start_next_tc(); 77 | } 78 | 79 | pub fn pop(&mut self) { 80 | let entry = self.entries.pop_back(); 81 | debug!("Removing {:?} from queue tail!", entry); 82 | if self.entries.is_empty() { 83 | self.start_next_tc(); 84 | } 85 | } 86 | 87 | pub fn pop_head(&mut self) { 88 | let res = self.entries.pop_front(); 89 | debug!("Removing {:?} from queue head!", res); 90 | self.start_next_tc(); 91 | } 92 | 93 | pub fn clear(&mut self) { 94 | debug!("Clearing queue!"); 95 | if !self.entries.is_empty() { 96 | self.entries.clear(); 97 | self.start_next_tc(); 98 | } 99 | } 100 | 101 | pub fn get_next_tc(&mut self) -> Vec { 102 | debug!("Extracting current pre-transcode!"); 103 | // Swap next into np, then clear next and extract np buffers 104 | mem::swap(&mut self.next, &mut self.np); 105 | self.next = Default::default(); 106 | // Pop queue head if its the same as np, and start next transcode 107 | if self.entries.front().map(|e| *e == self.np.entry).unwrap_or(false) { 108 | self.entries.pop_front(); 109 | } 110 | mem::replace(&mut self.np.bufs, Vec::new()) 111 | } 112 | 113 | pub fn start_next_tc(&mut self) { 114 | debug!("Beginning next pre-transcode!"); 115 | let mut tries = 0; 116 | loop { 117 | if tries == 5 { 118 | use std::borrow::Borrow; 119 | let buf = { 120 | let b: &Vec = self.cfg.queue.fallback.0.borrow(); 121 | io::Cursor::new(b.clone()) 122 | }; 123 | // TODO: Make this less retarded - Rust can't deal with two levels of dereference 124 | let ct = &self.cfg.queue.fallback.1.clone(); 125 | warn!("Using fallback"); 126 | let tc = self.initiate_transcode(buf, ct).unwrap(); 127 | self.next = QueueBuffer { 128 | bufs: tc, 129 | entry: self.queue_entry_from_new(NewQueueEntry { data: Map::new(), path: "fallback".to_owned() }), 130 | }; 131 | return; 132 | } 133 | tries += 1; 134 | if let Some(qe) = self.next_buffer() { 135 | match fs::File::open(&qe.path) { 136 | Ok(f) => { 137 | let ext = if let Some(e) = qe.path.split('.').last() { e } else { continue }; 138 | match self.initiate_transcode(f, ext) { 139 | Ok(tc) => { 140 | self.next = QueueBuffer { 141 | bufs: tc, 142 | entry: qe.clone(), 143 | }; 144 | return; 145 | }, 146 | Err(e) => { 147 | warn!("Failed to start transcode: {}", e); 148 | continue; 149 | } 150 | } 151 | } 152 | Err(e) => { 153 | warn!("Failed to open queue entry {:?}: {}", qe, e); 154 | continue; 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | fn next_buffer(&mut self) -> Option { 162 | self.next_queue_buffer().or_else(|| self.random_buffer()) 163 | } 164 | 165 | fn next_queue_buffer(&mut self) -> Option { 166 | let e = self.entries.front().cloned(); 167 | if let Some(ref er) = e { 168 | info!("Using queue entry {:?}", er); 169 | } 170 | e 171 | } 172 | 173 | fn random_buffer(&mut self) -> Option { 174 | let mut body = String::new(); 175 | let res = reqwest::get(&self.cfg.queue.random.clone()) 176 | .ok() 177 | .and_then(|mut r| r.read_to_string(&mut body).ok()) 178 | .and_then(|_| serde::from_str(&body).ok()) 179 | .and_then(|v| NewQueueEntry::deserialize(v)) 180 | .map(|v| self.queue_entry_from_new(v)); 181 | if res.is_some() { 182 | info!("Using random entry {:?}", res.as_ref().unwrap()); 183 | } 184 | res 185 | } 186 | 187 | fn initiate_transcode(&mut self, s: T, container: &str) -> kaeru::Result> { 188 | let mut prebufs = Vec::new(); 189 | let input = kaeru::Input::new( 190 | BufReader::with_capacity(self.cfg.queue.buffer_len * 1024, s), container)?; 191 | let metadata = sync::Arc::new(input.metadata()); 192 | let mut gb = kaeru::GraphBuilder::new(input)?; 193 | for s in self.cfg.streams.iter() { 194 | let (tx, rx) = tc_queue::new(); 195 | let ct = match s.container { 196 | Container::Ogg => "ogg", 197 | Container::MP3 => "mp3", 198 | Container::AAC => "adts", 199 | Container::FLAC => "flac", 200 | }; 201 | let output = kaeru::Output::new(tx, ct, s.codec, s.bitrate)?; 202 | gb.add_output(output)?; 203 | prebufs.push(PreBuffer::new(rx, metadata.clone())); 204 | } 205 | let g = gb.build()?; 206 | thread::spawn(move || { 207 | debug!("Starting transcode"); 208 | match g.run() { 209 | Ok(()) => { } 210 | Err(e) => { debug!("transcode completed with err: {}", e) } 211 | } 212 | debug!("Completed transcode"); 213 | }); 214 | self.counter += 1; 215 | Ok(prebufs) 216 | } 217 | 218 | fn queue_entry_from_new(&mut self, nqe: NewQueueEntry) -> QueueEntry { 219 | self.last_id += 1; 220 | QueueEntry { id: self.last_id, data: nqe.data, path: nqe.path } 221 | } 222 | } 223 | 224 | impl NewQueueEntry { 225 | pub fn deserialize(json: JSON) -> Option { 226 | match json { 227 | JSON::Object(o) => { 228 | match o.get("path").cloned() { 229 | Some(JSON::String(p)) => Some(NewQueueEntry { data: o, path: p }), 230 | _ => None, 231 | } 232 | } 233 | _ => None 234 | } 235 | } 236 | } 237 | 238 | impl QueueEntry { 239 | pub fn serialize(&self) -> JSON { 240 | let mut obj = self.data.clone(); 241 | obj.insert("queue_id".to_string(), JSON::Number(self.id.into())); 242 | JSON::Object(obj) 243 | } 244 | } 245 | 246 | impl QueueBuffer { 247 | pub fn entry(&self) -> &QueueEntry { 248 | &self.entry 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/radio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::sync::atomic::Ordering; 3 | use std::sync::mpsc::{self, Receiver, Sender}; 4 | use std::{thread, time}; 5 | 6 | use reqwest; 7 | 8 | use queue::{Queue, QueueEntry}; 9 | use api::{ApiMessage, QueuePos}; 10 | use config::Config; 11 | use prebuffer::PreBuffer; 12 | use broadcast::{Buffer, BufferData}; 13 | use tc_queue::BufferRes; 14 | use amy; 15 | 16 | struct RadioConn { 17 | tx: Sender, 18 | } 19 | 20 | const SYNC_AHEAD: u64 = 1; 21 | const MAX_FALL_BEHIND: u64 = 2; 22 | 23 | struct Syncer { 24 | last_pts: f64, 25 | init_pts: Option, 26 | start: time::Instant, 27 | } 28 | 29 | impl Syncer { 30 | fn new() -> Syncer { 31 | Syncer { 32 | last_pts: 0., 33 | init_pts: Some(0.), 34 | start: time::Instant::now(), 35 | } 36 | } 37 | 38 | fn update(&mut self, pts: f64) { 39 | if self.init_pts.is_none() { 40 | self.init_pts = Some(pts); 41 | } 42 | self.last_pts = pts; 43 | } 44 | 45 | fn new_song(&mut self) { 46 | self.start = time::Instant::now(); 47 | self.init_pts = None; 48 | self.last_pts = 0.; 49 | } 50 | 51 | fn done(&mut self) { 52 | if let Some(dur) = time::Duration::from_millis(((self.last_pts - self.init_pts.unwrap_or(0.)) * 1000.) as u64) 53 | .checked_sub(time::Instant::now() - self.start) { 54 | thread::sleep(dur); 55 | } 56 | } 57 | 58 | fn should_skip(&mut self) -> bool { 59 | if let Some(dur) = (time::Instant::now() - self.start) 60 | .checked_sub(time::Duration::from_millis(((self.last_pts - self.init_pts.unwrap_or(0.)) * 1000.) as u64)) { 61 | dur > time::Duration::from_secs(MAX_FALL_BEHIND) 62 | } else { 63 | false 64 | } 65 | } 66 | 67 | fn sync(&mut self) { 68 | if let Some(dur) = time::Duration::from_millis(((self.last_pts - self.init_pts.unwrap_or(0.)) * 1000.) as u64) 69 | .checked_sub(time::Duration::from_secs(SYNC_AHEAD) + (time::Instant::now() - self.start)) { 70 | thread::sleep(dur); 71 | } 72 | } 73 | } 74 | 75 | impl RadioConn { 76 | fn new( 77 | mid: usize, 78 | btx: amy::Sender, 79 | ) -> RadioConn { 80 | let (tx, rx) = mpsc::channel(); 81 | 82 | thread::spawn(move || { 83 | play(rx, mid, btx); 84 | }); 85 | RadioConn { 86 | tx: tx, 87 | } 88 | } 89 | 90 | fn replace_buffer(&mut self, buffer: PreBuffer) { 91 | self.tx.send(buffer).unwrap(); 92 | } 93 | } 94 | 95 | pub fn play(buffer_rec: Receiver, mid: usize, btx: amy::Sender) { 96 | debug!("Awaiting initial buffer"); 97 | let mut pb = buffer_rec.recv().unwrap(); 98 | let mut syncer = Syncer::new(); 99 | loop { 100 | match pb.buffer.next_buf() { 101 | BufferRes::Data(BufferData::Frame { data, pts } ) => { 102 | syncer.update(pts); 103 | btx.send(Buffer::new(mid, BufferData::Frame { data, pts })).unwrap(); 104 | syncer.sync(); 105 | } 106 | BufferRes::Data(b @ BufferData::Header(_) ) => { 107 | syncer.new_song(); 108 | btx.send(Buffer::new(mid, b)).unwrap(); 109 | } 110 | BufferRes::Data(b @ BufferData::Trailer(_) ) => { 111 | btx.send(Buffer::new(mid, b)).unwrap(); 112 | } 113 | BufferRes::Timeout => { 114 | if syncer.should_skip() { 115 | debug!("Buffer recv timeout, skipping!"); 116 | pb.buffer.done.store(true, Ordering::Release); 117 | pb = buffer_rec.recv().unwrap(); 118 | syncer.done(); 119 | debug!("Received next buffer, moving on!"); 120 | } 121 | } 122 | BufferRes::Done => { 123 | pb.buffer.done.store(true, Ordering::Release); 124 | debug!("Buffer drained, waiting for next!"); 125 | pb = buffer_rec.recv().unwrap(); 126 | debug!("Received next buffer, syncing for remaining time!"); 127 | syncer.done(); 128 | debug!("Sync complete, resuming!"); 129 | } 130 | } 131 | } 132 | } 133 | 134 | pub fn start_streams(cfg: Config, 135 | queue: Arc>, 136 | updates: Receiver, 137 | btx: amy::Sender, 138 | ) { 139 | let mut rconns: Vec<_> = cfg.streams.iter().enumerate() 140 | .map(|(id, _)| { 141 | RadioConn::new(id, 142 | btx.try_clone().unwrap(), 143 | ) 144 | }) 145 | .collect(); 146 | 147 | loop { 148 | debug!("Extracting next buffer"); 149 | let prebuffers = queue.lock().unwrap().get_next_tc(); 150 | 151 | debug!("Dispatching new buffers"); 152 | // The order is guarenteed to be correct because we always iterate by the config 153 | // ordering. 154 | let tokens: Vec<_> = rconns.iter_mut().zip(prebuffers.into_iter()) 155 | .map(|(rconn, pb)| { 156 | let tok = pb.buffer.done.clone(); 157 | rconn.replace_buffer(pb); 158 | tok 159 | }).collect(); 160 | 161 | debug!("Broadcasting np"); 162 | let np = queue.lock().unwrap().np().entry().clone(); 163 | if let Err(e) = broadcast_np(&cfg.queue.np, np) { 164 | warn!("Failed to broadcast np: {}", e); 165 | } 166 | 167 | queue.lock().unwrap().start_next_tc(); 168 | debug!("Entering main loop"); 169 | 170 | // Song activity loop - ensures that the song is properly transcoding and handles any sort 171 | // of API message that gets received in the meanwhile 172 | loop { 173 | // If any prebuffer completes, just move on to next song. We want to minimize downtime 174 | // even if it means some songs get cut off early 175 | if tokens.iter().any(|tok| tok.load(Ordering::Acquire)) { 176 | break; 177 | } else { 178 | if let Ok(msg) = updates.try_recv() { 179 | // Keep all these operations local just incase 180 | // anything complex might need to happen in the future. 181 | debug!("Received API message {:?}", msg); 182 | match msg { 183 | ApiMessage::Skip => { 184 | for token in tokens { 185 | token.store(true, Ordering::Release); 186 | } 187 | break; 188 | } 189 | ApiMessage::Clear => { 190 | queue.lock().unwrap().clear(); 191 | } 192 | ApiMessage::Insert(QueuePos::Head, qe) => { 193 | queue.lock().unwrap().push_head(qe); 194 | } 195 | ApiMessage::Insert(QueuePos::Tail, qe) => { 196 | queue.lock().unwrap().push(qe); 197 | } 198 | ApiMessage::Remove(QueuePos::Head) => { 199 | queue.lock().unwrap().pop_head(); 200 | } 201 | ApiMessage::Remove(QueuePos::Tail) => { 202 | queue.lock().unwrap().pop(); 203 | } 204 | } 205 | } else { 206 | thread::sleep(time::Duration::from_millis(20)); 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | fn broadcast_np(url: &str, song: QueueEntry) -> Result<(), reqwest::Error> { 214 | let client = reqwest::Client::new(); 215 | client.post(url) 216 | .json(&song.serialize()) 217 | .send()?; 218 | Ok(()) 219 | } 220 | -------------------------------------------------------------------------------- /src/tc_queue.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{atomic, mpsc, Arc}; 2 | use std::{mem, io, time}; 3 | 4 | use kaeru::Sink; 5 | use broadcast::BufferData; 6 | 7 | pub struct QW { 8 | queue: mpsc::SyncSender, 9 | buf: io::Cursor>, 10 | writing_header: bool, 11 | writing_trailer: bool, 12 | done: Arc, 13 | } 14 | 15 | pub struct QR { 16 | pub done: Arc, 17 | queue: mpsc::Receiver, 18 | } 19 | 20 | pub enum BufferRes { 21 | Data(BufferData), 22 | Timeout, 23 | Done, 24 | } 25 | 26 | pub fn new() -> (QW, QR) { 27 | let (tx, rx) = mpsc::sync_channel(15); 28 | let done = Arc::new(atomic::AtomicBool::new(false)); 29 | ( 30 | QW::new(tx, done.clone()), 31 | QR { queue: rx, done } 32 | ) 33 | } 34 | 35 | impl QW { 36 | fn new(q: mpsc::SyncSender, done: Arc) -> QW { 37 | QW { 38 | queue: q, 39 | buf: io::Cursor::new(Vec::with_capacity(1024)), 40 | writing_header: true, 41 | writing_trailer: false, 42 | done, 43 | } 44 | } 45 | 46 | fn done(&self) -> bool { 47 | self.done.load(atomic::Ordering::Acquire) 48 | } 49 | } 50 | 51 | impl io::Write for QW { 52 | fn write(&mut self, buf: &[u8]) -> io::Result { 53 | if self.writing_trailer { 54 | self.buf.write(&buf) 55 | } else if self.done() { 56 | return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "Canceled!")); 57 | } else { 58 | self.buf.write(&buf) 59 | } 60 | } 61 | 62 | fn flush(&mut self) -> io::Result<()> { 63 | self.buf.flush() 64 | } 65 | } 66 | 67 | impl Sink for QW { 68 | fn header_written(&mut self) { 69 | self.writing_header = false; 70 | let nb = io::Cursor::new(Vec::with_capacity(1024)); 71 | let ob = mem::replace(&mut self.buf, nb); 72 | if self.queue.send(BufferData::Header(ob.into_inner())).is_err() { 73 | self.done.store(true, atomic::Ordering::Release); 74 | } 75 | } 76 | 77 | fn packet_written(&mut self, pts: f64) { 78 | if self.writing_trailer { 79 | return; 80 | } 81 | 82 | let nb = io::Cursor::new(Vec::with_capacity(1024)); 83 | let ob = mem::replace(&mut self.buf, nb); 84 | let bd = BufferData::Frame { 85 | data: ob.into_inner(), 86 | pts, 87 | }; 88 | if self.queue.send(bd).is_err() { 89 | self.done.store(true, atomic::Ordering::Release); 90 | } 91 | } 92 | 93 | fn body_written(&mut self) { 94 | self.writing_trailer = true; 95 | } 96 | } 97 | 98 | impl Drop for QW { 99 | fn drop(&mut self) { 100 | if self.writing_trailer { 101 | let nb = io::Cursor::new(Vec::with_capacity(0)); 102 | let ob = mem::replace(&mut self.buf, nb); 103 | if self.queue.send(BufferData::Trailer(ob.into_inner())).is_err() { } 104 | } 105 | self.done.store(true, atomic::Ordering::Release); 106 | } 107 | } 108 | 109 | impl QR { 110 | pub fn next_buf(&self) -> BufferRes { 111 | match self.queue.recv_timeout(time::Duration::from_millis(10)) { 112 | Ok(b) => BufferRes::Data(b), 113 | Err(mpsc::RecvTimeoutError::Timeout) => BufferRes::Timeout, 114 | Err(mpsc::RecvTimeoutError::Disconnected) => BufferRes::Done, 115 | } 116 | } 117 | } 118 | 119 | impl Drop for QR { 120 | fn drop(&mut self) { 121 | self.done.store(true, atomic::Ordering::Release); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luminarys/kawa/31761b7f8f42eaf294319b0d512019e0e39becc9/src/util.rs --------------------------------------------------------------------------------