├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENCE ├── README.md ├── default.nix └── src ├── bin └── rustymedia.rs ├── cache.rs ├── config.rs ├── devices.rs ├── dlna.rs ├── dlna ├── connection.xml ├── content.xml ├── discovery.rs ├── root.xml ├── server.rs └── types.rs ├── error.rs ├── ffmpeg.rs ├── lib.rs ├── local.rs ├── root.rs └── xml.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | perf.data perf.data.old 4 | result 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.6.10" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "aho-corasick" 16 | version = "1.1.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 19 | dependencies = [ 20 | "memchr", 21 | ] 22 | 23 | [[package]] 24 | name = "arrayvec" 25 | version = "0.4.10" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" 28 | dependencies = [ 29 | "nodrop", 30 | ] 31 | 32 | [[package]] 33 | name = "autocfg" 34 | version = "0.1.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" 37 | 38 | [[package]] 39 | name = "backtrace" 40 | version = "0.3.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" 43 | dependencies = [ 44 | "autocfg", 45 | "backtrace-sys", 46 | "cfg-if", 47 | "libc", 48 | "rustc-demangle", 49 | "winapi 0.3.9", 50 | ] 51 | 52 | [[package]] 53 | name = "backtrace-sys" 54 | version = "0.1.28" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" 57 | dependencies = [ 58 | "cc", 59 | "libc", 60 | ] 61 | 62 | [[package]] 63 | name = "base64" 64 | version = "0.9.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" 67 | dependencies = [ 68 | "byteorder", 69 | "safemem", 70 | ] 71 | 72 | [[package]] 73 | name = "bitflags" 74 | version = "0.7.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 77 | 78 | [[package]] 79 | name = "bitflags" 80 | version = "0.9.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "1.0.4" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 89 | 90 | [[package]] 91 | name = "byteorder" 92 | version = "1.3.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" 95 | 96 | [[package]] 97 | name = "bytes" 98 | version = "0.4.12" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 101 | dependencies = [ 102 | "byteorder", 103 | "iovec", 104 | ] 105 | 106 | [[package]] 107 | name = "cc" 108 | version = "1.0.34" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "30f813bf45048a18eda9190fd3c6b78644146056740c43172a5a3699118588fd" 111 | 112 | [[package]] 113 | name = "cfg-if" 114 | version = "0.1.7" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" 117 | 118 | [[package]] 119 | name = "cloudabi" 120 | version = "0.0.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 123 | dependencies = [ 124 | "bitflags 1.0.4", 125 | ] 126 | 127 | [[package]] 128 | name = "crossbeam-deque" 129 | version = "0.7.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" 132 | dependencies = [ 133 | "crossbeam-epoch", 134 | "crossbeam-utils", 135 | ] 136 | 137 | [[package]] 138 | name = "crossbeam-epoch" 139 | version = "0.7.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" 142 | dependencies = [ 143 | "arrayvec", 144 | "cfg-if", 145 | "crossbeam-utils", 146 | "lazy_static", 147 | "memoffset", 148 | "scopeguard", 149 | ] 150 | 151 | [[package]] 152 | name = "crossbeam-queue" 153 | version = "0.1.2" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" 156 | dependencies = [ 157 | "crossbeam-utils", 158 | ] 159 | 160 | [[package]] 161 | name = "crossbeam-utils" 162 | version = "0.6.5" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" 165 | dependencies = [ 166 | "cfg-if", 167 | "lazy_static", 168 | ] 169 | 170 | [[package]] 171 | name = "docopt" 172 | version = "1.0.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "db2906c2579b5b7207fc1e328796a9a8835dc44e22dbe8e460b1d636f9a7b225" 175 | dependencies = [ 176 | "lazy_static", 177 | "regex 1.10.4", 178 | "serde", 179 | "serde_derive", 180 | "strsim", 181 | ] 182 | 183 | [[package]] 184 | name = "env_logger" 185 | version = "0.4.3" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" 188 | dependencies = [ 189 | "log 0.3.9", 190 | "regex 0.2.11", 191 | ] 192 | 193 | [[package]] 194 | name = "error-chain" 195 | version = "0.11.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" 198 | dependencies = [ 199 | "backtrace", 200 | ] 201 | 202 | [[package]] 203 | name = "fnv" 204 | version = "1.0.6" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 207 | 208 | [[package]] 209 | name = "fuchsia-cprng" 210 | version = "0.1.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 213 | 214 | [[package]] 215 | name = "fuchsia-zircon" 216 | version = "0.3.3" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 219 | dependencies = [ 220 | "bitflags 1.0.4", 221 | "fuchsia-zircon-sys", 222 | ] 223 | 224 | [[package]] 225 | name = "fuchsia-zircon-sys" 226 | version = "0.3.3" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 229 | 230 | [[package]] 231 | name = "futures" 232 | version = "0.1.26" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981" 235 | 236 | [[package]] 237 | name = "futures-cpupool" 238 | version = "0.1.8" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 241 | dependencies = [ 242 | "futures", 243 | "num_cpus", 244 | ] 245 | 246 | [[package]] 247 | name = "glob" 248 | version = "0.3.1" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 251 | 252 | [[package]] 253 | name = "httparse" 254 | version = "1.3.3" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" 257 | 258 | [[package]] 259 | name = "hyper" 260 | version = "0.11.27" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "34a590ca09d341e94cddf8e5af0bbccde205d5fbc2fa3c09dd67c7f85cea59d7" 263 | dependencies = [ 264 | "base64", 265 | "bytes", 266 | "futures", 267 | "futures-cpupool", 268 | "httparse", 269 | "iovec", 270 | "language-tags", 271 | "log 0.4.6", 272 | "mime", 273 | "net2", 274 | "percent-encoding", 275 | "relay", 276 | "time", 277 | "tokio-core", 278 | "tokio-io", 279 | "tokio-proto", 280 | "tokio-service", 281 | "unicase", 282 | "want", 283 | ] 284 | 285 | [[package]] 286 | name = "iovec" 287 | version = "0.1.2" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 290 | dependencies = [ 291 | "libc", 292 | "winapi 0.2.8", 293 | ] 294 | 295 | [[package]] 296 | name = "ipnetwork" 297 | version = "0.20.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" 300 | dependencies = [ 301 | "serde", 302 | ] 303 | 304 | [[package]] 305 | name = "itoa" 306 | version = "0.4.3" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" 309 | 310 | [[package]] 311 | name = "kernel32-sys" 312 | version = "0.2.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 315 | dependencies = [ 316 | "winapi 0.2.8", 317 | "winapi-build", 318 | ] 319 | 320 | [[package]] 321 | name = "language-tags" 322 | version = "0.2.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 325 | 326 | [[package]] 327 | name = "lazy_static" 328 | version = "1.3.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 331 | 332 | [[package]] 333 | name = "lazycell" 334 | version = "1.2.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 337 | 338 | [[package]] 339 | name = "libc" 340 | version = "0.2.154" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 343 | 344 | [[package]] 345 | name = "linked-hash-map" 346 | version = "0.4.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" 349 | 350 | [[package]] 351 | name = "lock_api" 352 | version = "0.1.5" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" 355 | dependencies = [ 356 | "owning_ref", 357 | "scopeguard", 358 | ] 359 | 360 | [[package]] 361 | name = "log" 362 | version = "0.3.9" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 365 | dependencies = [ 366 | "log 0.4.6", 367 | ] 368 | 369 | [[package]] 370 | name = "log" 371 | version = "0.4.6" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 374 | dependencies = [ 375 | "cfg-if", 376 | ] 377 | 378 | [[package]] 379 | name = "lru-cache" 380 | version = "0.1.1" 381 | source = "git+https://github.com/kevincox/lru-cache.git?branch=entry-api#f4aa466217611e7094ac88906196240239146fdf" 382 | dependencies = [ 383 | "linked-hash-map", 384 | ] 385 | 386 | [[package]] 387 | name = "memchr" 388 | version = "2.7.2" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 391 | 392 | [[package]] 393 | name = "memoffset" 394 | version = "0.2.1" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 397 | 398 | [[package]] 399 | name = "mime" 400 | version = "0.3.13" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425" 403 | dependencies = [ 404 | "unicase", 405 | ] 406 | 407 | [[package]] 408 | name = "mio" 409 | version = "0.6.16" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" 412 | dependencies = [ 413 | "fuchsia-zircon", 414 | "fuchsia-zircon-sys", 415 | "iovec", 416 | "kernel32-sys", 417 | "lazycell", 418 | "libc", 419 | "log 0.4.6", 420 | "miow", 421 | "net2", 422 | "slab 0.4.2", 423 | "winapi 0.2.8", 424 | ] 425 | 426 | [[package]] 427 | name = "mio-uds" 428 | version = "0.6.7" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" 431 | dependencies = [ 432 | "iovec", 433 | "libc", 434 | "mio", 435 | ] 436 | 437 | [[package]] 438 | name = "miow" 439 | version = "0.2.1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 442 | dependencies = [ 443 | "kernel32-sys", 444 | "net2", 445 | "winapi 0.2.8", 446 | "ws2_32-sys", 447 | ] 448 | 449 | [[package]] 450 | name = "net2" 451 | version = "0.2.33" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 454 | dependencies = [ 455 | "cfg-if", 456 | "libc", 457 | "winapi 0.3.9", 458 | ] 459 | 460 | [[package]] 461 | name = "nix" 462 | version = "0.9.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" 465 | dependencies = [ 466 | "bitflags 0.9.1", 467 | "cfg-if", 468 | "libc", 469 | "void", 470 | ] 471 | 472 | [[package]] 473 | name = "nix" 474 | version = "0.11.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" 477 | dependencies = [ 478 | "bitflags 1.0.4", 479 | "cc", 480 | "cfg-if", 481 | "libc", 482 | "void", 483 | ] 484 | 485 | [[package]] 486 | name = "no-std-net" 487 | version = "0.6.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" 490 | 491 | [[package]] 492 | name = "nodrop" 493 | version = "0.1.13" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" 496 | 497 | [[package]] 498 | name = "num_cpus" 499 | version = "1.10.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" 502 | dependencies = [ 503 | "libc", 504 | ] 505 | 506 | [[package]] 507 | name = "os_pipe" 508 | version = "0.6.2" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "fe033225d563042c3eeb22ffd1d2ea1aefcc48e7e37151a064c9e0bae64b253f" 511 | dependencies = [ 512 | "nix 0.11.0", 513 | "winapi 0.3.9", 514 | ] 515 | 516 | [[package]] 517 | name = "owning_ref" 518 | version = "0.4.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" 521 | dependencies = [ 522 | "stable_deref_trait", 523 | ] 524 | 525 | [[package]] 526 | name = "parking_lot" 527 | version = "0.7.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" 530 | dependencies = [ 531 | "lock_api", 532 | "parking_lot_core", 533 | ] 534 | 535 | [[package]] 536 | name = "parking_lot_core" 537 | version = "0.4.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" 540 | dependencies = [ 541 | "libc", 542 | "rand 0.6.5", 543 | "rustc_version", 544 | "smallvec 0.6.10", 545 | "winapi 0.3.9", 546 | ] 547 | 548 | [[package]] 549 | name = "percent-encoding" 550 | version = "1.0.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 553 | 554 | [[package]] 555 | name = "pnet" 556 | version = "0.34.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "130c5b738eeda2dc5796fe2671e49027e6935e817ab51b930a36ec9e6a206a64" 559 | dependencies = [ 560 | "ipnetwork", 561 | "pnet_base", 562 | "pnet_datalink", 563 | "pnet_packet", 564 | "pnet_sys", 565 | "pnet_transport", 566 | ] 567 | 568 | [[package]] 569 | name = "pnet_base" 570 | version = "0.34.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c" 573 | dependencies = [ 574 | "no-std-net", 575 | ] 576 | 577 | [[package]] 578 | name = "pnet_datalink" 579 | version = "0.34.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "ad5854abf0067ebbd3967f7d45ebc8976ff577ff0c7bd101c4973ae3c70f98fe" 582 | dependencies = [ 583 | "ipnetwork", 584 | "libc", 585 | "pnet_base", 586 | "pnet_sys", 587 | "winapi 0.3.9", 588 | ] 589 | 590 | [[package]] 591 | name = "pnet_macros" 592 | version = "0.34.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804" 595 | dependencies = [ 596 | "proc-macro2 1.0.82", 597 | "quote 1.0.36", 598 | "regex 1.10.4", 599 | "syn 2.0.62", 600 | ] 601 | 602 | [[package]] 603 | name = "pnet_macros_support" 604 | version = "0.34.0" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56" 607 | dependencies = [ 608 | "pnet_base", 609 | ] 610 | 611 | [[package]] 612 | name = "pnet_packet" 613 | version = "0.34.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba" 616 | dependencies = [ 617 | "glob", 618 | "pnet_base", 619 | "pnet_macros", 620 | "pnet_macros_support", 621 | ] 622 | 623 | [[package]] 624 | name = "pnet_sys" 625 | version = "0.34.0" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "417c0becd1b573f6d544f73671070b039051e5ad819cc64aa96377b536128d00" 628 | dependencies = [ 629 | "libc", 630 | "winapi 0.3.9", 631 | ] 632 | 633 | [[package]] 634 | name = "pnet_transport" 635 | version = "0.34.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "2637e14d7de974ee2f74393afccbc8704f3e54e6eb31488715e72481d1662cc3" 638 | dependencies = [ 639 | "libc", 640 | "pnet_base", 641 | "pnet_packet", 642 | "pnet_sys", 643 | ] 644 | 645 | [[package]] 646 | name = "proc-macro2" 647 | version = "0.4.27" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" 650 | dependencies = [ 651 | "unicode-xid", 652 | ] 653 | 654 | [[package]] 655 | name = "proc-macro2" 656 | version = "1.0.82" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 659 | dependencies = [ 660 | "unicode-ident", 661 | ] 662 | 663 | [[package]] 664 | name = "quote" 665 | version = "0.6.11" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" 668 | dependencies = [ 669 | "proc-macro2 0.4.27", 670 | ] 671 | 672 | [[package]] 673 | name = "quote" 674 | version = "1.0.36" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 677 | dependencies = [ 678 | "proc-macro2 1.0.82", 679 | ] 680 | 681 | [[package]] 682 | name = "rand" 683 | version = "0.3.23" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 686 | dependencies = [ 687 | "libc", 688 | "rand 0.4.6", 689 | ] 690 | 691 | [[package]] 692 | name = "rand" 693 | version = "0.4.6" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 696 | dependencies = [ 697 | "fuchsia-cprng", 698 | "libc", 699 | "rand_core 0.3.1", 700 | "rdrand", 701 | "winapi 0.3.9", 702 | ] 703 | 704 | [[package]] 705 | name = "rand" 706 | version = "0.6.5" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 709 | dependencies = [ 710 | "autocfg", 711 | "libc", 712 | "rand_chacha", 713 | "rand_core 0.4.0", 714 | "rand_hc", 715 | "rand_isaac", 716 | "rand_jitter", 717 | "rand_os", 718 | "rand_pcg", 719 | "rand_xorshift", 720 | "winapi 0.3.9", 721 | ] 722 | 723 | [[package]] 724 | name = "rand_chacha" 725 | version = "0.1.1" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 728 | dependencies = [ 729 | "autocfg", 730 | "rand_core 0.3.1", 731 | ] 732 | 733 | [[package]] 734 | name = "rand_core" 735 | version = "0.3.1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 738 | dependencies = [ 739 | "rand_core 0.4.0", 740 | ] 741 | 742 | [[package]] 743 | name = "rand_core" 744 | version = "0.4.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" 747 | 748 | [[package]] 749 | name = "rand_hc" 750 | version = "0.1.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 753 | dependencies = [ 754 | "rand_core 0.3.1", 755 | ] 756 | 757 | [[package]] 758 | name = "rand_isaac" 759 | version = "0.1.1" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 762 | dependencies = [ 763 | "rand_core 0.3.1", 764 | ] 765 | 766 | [[package]] 767 | name = "rand_jitter" 768 | version = "0.1.3" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" 771 | dependencies = [ 772 | "libc", 773 | "rand_core 0.4.0", 774 | "winapi 0.3.9", 775 | ] 776 | 777 | [[package]] 778 | name = "rand_os" 779 | version = "0.1.3" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 782 | dependencies = [ 783 | "cloudabi", 784 | "fuchsia-cprng", 785 | "libc", 786 | "rand_core 0.4.0", 787 | "rdrand", 788 | "winapi 0.3.9", 789 | ] 790 | 791 | [[package]] 792 | name = "rand_pcg" 793 | version = "0.1.2" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 796 | dependencies = [ 797 | "autocfg", 798 | "rand_core 0.4.0", 799 | ] 800 | 801 | [[package]] 802 | name = "rand_xorshift" 803 | version = "0.1.1" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 806 | dependencies = [ 807 | "rand_core 0.3.1", 808 | ] 809 | 810 | [[package]] 811 | name = "rdrand" 812 | version = "0.4.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 815 | dependencies = [ 816 | "rand_core 0.3.1", 817 | ] 818 | 819 | [[package]] 820 | name = "redox_syscall" 821 | version = "0.1.52" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "d32b3053e5ced86e4bc0411fec997389532bf56b000e66cb4884eeeb41413d69" 824 | 825 | [[package]] 826 | name = "regex" 827 | version = "0.2.11" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" 830 | dependencies = [ 831 | "aho-corasick 0.6.10", 832 | "memchr", 833 | "regex-syntax 0.5.6", 834 | "thread_local", 835 | "utf8-ranges", 836 | ] 837 | 838 | [[package]] 839 | name = "regex" 840 | version = "1.10.4" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 843 | dependencies = [ 844 | "aho-corasick 1.1.3", 845 | "memchr", 846 | "regex-automata", 847 | "regex-syntax 0.8.3", 848 | ] 849 | 850 | [[package]] 851 | name = "regex-automata" 852 | version = "0.4.6" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 855 | dependencies = [ 856 | "aho-corasick 1.1.3", 857 | "memchr", 858 | "regex-syntax 0.8.3", 859 | ] 860 | 861 | [[package]] 862 | name = "regex-syntax" 863 | version = "0.5.6" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" 866 | dependencies = [ 867 | "ucd-util", 868 | ] 869 | 870 | [[package]] 871 | name = "regex-syntax" 872 | version = "0.8.3" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 875 | 876 | [[package]] 877 | name = "relay" 878 | version = "0.1.1" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" 881 | dependencies = [ 882 | "futures", 883 | ] 884 | 885 | [[package]] 886 | name = "rustc-demangle" 887 | version = "0.1.13" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" 890 | 891 | [[package]] 892 | name = "rustc_version" 893 | version = "0.2.3" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 896 | dependencies = [ 897 | "semver", 898 | ] 899 | 900 | [[package]] 901 | name = "rustymedia" 902 | version = "0.1.0" 903 | dependencies = [ 904 | "bytes", 905 | "docopt", 906 | "env_logger", 907 | "error-chain", 908 | "futures", 909 | "futures-cpupool", 910 | "hyper", 911 | "lazy_static", 912 | "lru-cache", 913 | "nix 0.9.0", 914 | "os_pipe", 915 | "percent-encoding", 916 | "pnet", 917 | "regex 0.2.11", 918 | "serde", 919 | "serde-xml-rs", 920 | "serde_derive", 921 | "serde_json", 922 | "serde_urlencoded_field", 923 | "smallvec 0.6.10", 924 | "tokio-core", 925 | "tokio-file-unix", 926 | "tokio-io", 927 | ] 928 | 929 | [[package]] 930 | name = "ryu" 931 | version = "1.0.3" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" 934 | 935 | [[package]] 936 | name = "safemem" 937 | version = "0.3.0" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" 940 | 941 | [[package]] 942 | name = "scoped-tls" 943 | version = "0.1.2" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" 946 | 947 | [[package]] 948 | name = "scopeguard" 949 | version = "0.3.3" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 952 | 953 | [[package]] 954 | name = "semver" 955 | version = "0.9.0" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 958 | dependencies = [ 959 | "semver-parser", 960 | ] 961 | 962 | [[package]] 963 | name = "semver-parser" 964 | version = "0.7.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 967 | 968 | [[package]] 969 | name = "serde" 970 | version = "1.0.106" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" 973 | 974 | [[package]] 975 | name = "serde-xml-rs" 976 | version = "0.2.1" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "0c06881f4313eec67d4ecfcd8e14339f6042cfc0de4b1bd3ceae74c29d597f68" 979 | dependencies = [ 980 | "log 0.3.9", 981 | "serde", 982 | "xml-rs", 983 | ] 984 | 985 | [[package]] 986 | name = "serde_derive" 987 | version = "1.0.90" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79" 990 | dependencies = [ 991 | "proc-macro2 0.4.27", 992 | "quote 0.6.11", 993 | "syn 0.15.30", 994 | ] 995 | 996 | [[package]] 997 | name = "serde_json" 998 | version = "1.0.50" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867" 1001 | dependencies = [ 1002 | "itoa", 1003 | "ryu", 1004 | "serde", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "serde_urlencoded_field" 1009 | version = "0.1.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "0f88895fff99f4655004d0c28bf28abae600d8da5da6ca4b1a200df17ea9bf6d" 1012 | dependencies = [ 1013 | "percent-encoding", 1014 | "serde", 1015 | "serde_derive", 1016 | "serde_json", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "slab" 1021 | version = "0.3.0" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" 1024 | 1025 | [[package]] 1026 | name = "slab" 1027 | version = "0.4.2" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1030 | 1031 | [[package]] 1032 | name = "smallvec" 1033 | version = "0.2.1" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" 1036 | 1037 | [[package]] 1038 | name = "smallvec" 1039 | version = "0.6.10" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" 1042 | 1043 | [[package]] 1044 | name = "stable_deref_trait" 1045 | version = "1.1.1" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 1048 | 1049 | [[package]] 1050 | name = "strsim" 1051 | version = "0.7.0" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 1054 | 1055 | [[package]] 1056 | name = "syn" 1057 | version = "0.15.30" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" 1060 | dependencies = [ 1061 | "proc-macro2 0.4.27", 1062 | "quote 0.6.11", 1063 | "unicode-xid", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "syn" 1068 | version = "2.0.62" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "9f660c3bfcefb88c538776b6685a0c472e3128b51e74d48793dc2a488196e8eb" 1071 | dependencies = [ 1072 | "proc-macro2 1.0.82", 1073 | "quote 1.0.36", 1074 | "unicode-ident", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "take" 1079 | version = "0.1.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" 1082 | 1083 | [[package]] 1084 | name = "thread_local" 1085 | version = "0.3.6" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 1088 | dependencies = [ 1089 | "lazy_static", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "time" 1094 | version = "0.1.42" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 1097 | dependencies = [ 1098 | "libc", 1099 | "redox_syscall", 1100 | "winapi 0.3.9", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "tokio" 1105 | version = "0.1.18" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "65641e515a437b308ab131a82ce3042ff9795bef5d6c5a9be4eb24195c417fd9" 1108 | dependencies = [ 1109 | "bytes", 1110 | "futures", 1111 | "mio", 1112 | "num_cpus", 1113 | "tokio-codec", 1114 | "tokio-current-thread", 1115 | "tokio-executor", 1116 | "tokio-fs", 1117 | "tokio-io", 1118 | "tokio-reactor", 1119 | "tokio-sync", 1120 | "tokio-tcp", 1121 | "tokio-threadpool", 1122 | "tokio-timer", 1123 | "tokio-trace-core", 1124 | "tokio-udp", 1125 | "tokio-uds", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "tokio-codec" 1130 | version = "0.1.1" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" 1133 | dependencies = [ 1134 | "bytes", 1135 | "futures", 1136 | "tokio-io", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "tokio-core" 1141 | version = "0.1.17" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" 1144 | dependencies = [ 1145 | "bytes", 1146 | "futures", 1147 | "iovec", 1148 | "log 0.4.6", 1149 | "mio", 1150 | "scoped-tls", 1151 | "tokio", 1152 | "tokio-executor", 1153 | "tokio-io", 1154 | "tokio-reactor", 1155 | "tokio-timer", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "tokio-current-thread" 1160 | version = "0.1.6" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" 1163 | dependencies = [ 1164 | "futures", 1165 | "tokio-executor", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "tokio-executor" 1170 | version = "0.1.7" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e" 1173 | dependencies = [ 1174 | "crossbeam-utils", 1175 | "futures", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "tokio-file-unix" 1180 | version = "0.4.2" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "3ee72fc88747c60118694e54ccebaf4fc9dde6f47f79a13f2fb99b71f58af038" 1183 | dependencies = [ 1184 | "bytes", 1185 | "libc", 1186 | "mio", 1187 | "tokio-core", 1188 | "tokio-io", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "tokio-fs" 1193 | version = "0.1.6" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" 1196 | dependencies = [ 1197 | "futures", 1198 | "tokio-io", 1199 | "tokio-threadpool", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "tokio-io" 1204 | version = "0.1.13" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" 1207 | dependencies = [ 1208 | "bytes", 1209 | "futures", 1210 | "log 0.4.6", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "tokio-proto" 1215 | version = "0.1.1" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" 1218 | dependencies = [ 1219 | "futures", 1220 | "log 0.3.9", 1221 | "net2", 1222 | "rand 0.3.23", 1223 | "slab 0.3.0", 1224 | "smallvec 0.2.1", 1225 | "take", 1226 | "tokio-core", 1227 | "tokio-io", 1228 | "tokio-service", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "tokio-reactor" 1233 | version = "0.1.9" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" 1236 | dependencies = [ 1237 | "crossbeam-utils", 1238 | "futures", 1239 | "lazy_static", 1240 | "log 0.4.6", 1241 | "mio", 1242 | "num_cpus", 1243 | "parking_lot", 1244 | "slab 0.4.2", 1245 | "tokio-executor", 1246 | "tokio-io", 1247 | "tokio-sync", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "tokio-service" 1252 | version = "0.1.0" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" 1255 | dependencies = [ 1256 | "futures", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "tokio-sync" 1261 | version = "0.1.4" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "fda385df506bf7546e70872767f71e81640f1f251bdf2fd8eb81a0eaec5fe022" 1264 | dependencies = [ 1265 | "fnv", 1266 | "futures", 1267 | ] 1268 | 1269 | [[package]] 1270 | name = "tokio-tcp" 1271 | version = "0.1.3" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" 1274 | dependencies = [ 1275 | "bytes", 1276 | "futures", 1277 | "iovec", 1278 | "mio", 1279 | "tokio-io", 1280 | "tokio-reactor", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "tokio-threadpool" 1285 | version = "0.1.13" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "ec5759cf26cf9659555f36c431b515e3d05f66831741c85b4b5d5dfb9cf1323c" 1288 | dependencies = [ 1289 | "crossbeam-deque", 1290 | "crossbeam-queue", 1291 | "crossbeam-utils", 1292 | "futures", 1293 | "log 0.4.6", 1294 | "num_cpus", 1295 | "rand 0.6.5", 1296 | "slab 0.4.2", 1297 | "tokio-executor", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "tokio-timer" 1302 | version = "0.2.10" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" 1305 | dependencies = [ 1306 | "crossbeam-utils", 1307 | "futures", 1308 | "slab 0.4.2", 1309 | "tokio-executor", 1310 | ] 1311 | 1312 | [[package]] 1313 | name = "tokio-trace-core" 1314 | version = "0.1.0" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "350c9edade9830dc185ae48ba45667a445ab59f6167ef6d0254ec9d2430d9dd3" 1317 | dependencies = [ 1318 | "lazy_static", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "tokio-udp" 1323 | version = "0.1.3" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" 1326 | dependencies = [ 1327 | "bytes", 1328 | "futures", 1329 | "log 0.4.6", 1330 | "mio", 1331 | "tokio-codec", 1332 | "tokio-io", 1333 | "tokio-reactor", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "tokio-uds" 1338 | version = "0.2.5" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" 1341 | dependencies = [ 1342 | "bytes", 1343 | "futures", 1344 | "iovec", 1345 | "libc", 1346 | "log 0.4.6", 1347 | "mio", 1348 | "mio-uds", 1349 | "tokio-codec", 1350 | "tokio-io", 1351 | "tokio-reactor", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "try-lock" 1356 | version = "0.1.0" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" 1359 | 1360 | [[package]] 1361 | name = "ucd-util" 1362 | version = "0.1.3" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 1365 | 1366 | [[package]] 1367 | name = "unicase" 1368 | version = "2.3.0" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "41d17211f887da8e4a70a45b9536f26fc5de166b81e2d5d80de4a17fd22553bd" 1371 | dependencies = [ 1372 | "version_check", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "unicode-ident" 1377 | version = "1.0.12" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1380 | 1381 | [[package]] 1382 | name = "unicode-xid" 1383 | version = "0.1.0" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1386 | 1387 | [[package]] 1388 | name = "utf8-ranges" 1389 | version = "1.0.2" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" 1392 | 1393 | [[package]] 1394 | name = "version_check" 1395 | version = "0.1.5" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 1398 | 1399 | [[package]] 1400 | name = "void" 1401 | version = "1.0.2" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1404 | 1405 | [[package]] 1406 | name = "want" 1407 | version = "0.0.4" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "a05d9d966753fa4b5c8db73fcab5eed4549cfe0e1e4e66911e5564a0085c35d1" 1410 | dependencies = [ 1411 | "futures", 1412 | "log 0.4.6", 1413 | "try-lock", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "winapi" 1418 | version = "0.2.8" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1421 | 1422 | [[package]] 1423 | name = "winapi" 1424 | version = "0.3.9" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1427 | dependencies = [ 1428 | "winapi-i686-pc-windows-gnu", 1429 | "winapi-x86_64-pc-windows-gnu", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "winapi-build" 1434 | version = "0.1.1" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1437 | 1438 | [[package]] 1439 | name = "winapi-i686-pc-windows-gnu" 1440 | version = "0.4.0" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1443 | 1444 | [[package]] 1445 | name = "winapi-x86_64-pc-windows-gnu" 1446 | version = "0.4.0" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1449 | 1450 | [[package]] 1451 | name = "ws2_32-sys" 1452 | version = "0.2.1" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1455 | dependencies = [ 1456 | "winapi 0.2.8", 1457 | "winapi-build", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "xml-rs" 1462 | version = "0.3.6" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "7ec6c39eaa68382c8e31e35239402c0a9489d4141a8ceb0c716099a0b515b562" 1465 | dependencies = [ 1466 | "bitflags 0.7.0", 1467 | ] 1468 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Kevin Cox "] 3 | name = "rustymedia" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | bytes = "0.4" 9 | docopt = "1.0" 10 | env_logger = "0.4" 11 | error-chain = "0.11" 12 | futures = "0.1" 13 | futures-cpupool = "0.1" 14 | hyper = "0.11" 15 | lazy_static = "1.0.0" 16 | lru-cache = "=0.1.1" 17 | nix = "0.9" 18 | os_pipe = "0.6" 19 | percent-encoding = "1.0" 20 | pnet = "0.34.0" 21 | regex = "0.2" 22 | serde = "1.0" 23 | serde-xml-rs = "0.2" 24 | serde_derive = "1.0" 25 | serde_json = "1.0" 26 | serde_urlencoded_field = "0.1.0" 27 | smallvec = "0.6.10" 28 | tokio-core = "0.1" 29 | tokio-file-unix = "0.4" 30 | tokio-io = "0.1" 31 | 32 | [patch.crates-io] 33 | lru-cache = { git = "https://github.com/kevincox/lru-cache.git", branch = "entry-api" } 34 | 35 | [profile.release] 36 | debug = true 37 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright the authors. Including Kevin Cox 2017-2018 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rustymedia 2 | 3 | Rustymedia is a media server. It mimics the DLNA protocol and should work with most DLNA clients. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | # Serve two directories. 9 | cargo run -- --local 'My Videos'=~/Videos --local 'Other Stuff'=/mnt/usb/vids 10 | 11 | # See all options. 12 | cargo run -- --help 13 | ``` 14 | 15 | ## Transcoding 16 | 17 | The server automatically transcodes to formats that the client supports (if required). Right now only a [couple of clients](src/devices.rs) are recognized. Other clients get a "safe" profile which is likely to work. 18 | 19 | Recent transcodes are cached as anonymous files in /tmp, kill the server to clear the cache. 20 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with import {}; let 2 | in rec { 3 | out = rustPlatform.buildRustPackage { 4 | name = "rustymedia"; 5 | meta = { 6 | description = "RustyMedia Server"; 7 | homepage = https://kevincox.ca; 8 | }; 9 | 10 | cargoSha256 = null; 11 | src = builtins.filterSource (name: type: 12 | (lib.hasPrefix (toString ./src) name) || 13 | (lib.hasPrefix (toString ./Cargo) name)) ./.; 14 | 15 | FFMPEG_BINARY = "${ffmpeg}/bin/ffmpeg"; 16 | FFPROBE_BINARY = "${ffmpeg}/bin/ffprobe"; 17 | 18 | doCheck = false; 19 | 20 | # Work around https://github.com/NixOS/nixpkgs/pull/34034 21 | postUnpack = '' 22 | eval "$cargoDepsHook" 23 | unpackFile "$cargoDeps" 24 | cargoDepsCopy=$(stripHash $(basename $cargoDeps)) 25 | chmod -R +w "$cargoDepsCopy" 26 | mkdir -p .cargo 27 | cat >.cargo/config <<-EOF 28 | [source.crates-io] 29 | registry = 'https://github.com/rust-lang/crates.io-index' 30 | replace-with = 'vendored-sources' 31 | 32 | [source."https://github.com/kevincox/lru-cache.git"] 33 | git = "https://github.com/kevincox/lru-cache.git" 34 | branch = "entry-api" 35 | replace-with = "vendored-sources" 36 | 37 | [source.vendored-sources] 38 | directory = '$(pwd)/$cargoDepsCopy' 39 | EOF 40 | unset cargoDepsCopy 41 | export RUST_LOG=warn 42 | ''; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/rustymedia.rs: -------------------------------------------------------------------------------- 1 | extern crate docopt; 2 | extern crate env_logger; 3 | extern crate futures_cpupool; 4 | extern crate hyper; 5 | extern crate pnet; 6 | extern crate rustymedia; 7 | #[macro_use] extern crate serde_derive; 8 | extern crate tokio_core; 9 | 10 | use std::sync::{Arc, Mutex}; 11 | 12 | const USAGE: &str = " 13 | Usage: 14 | rustymedia [options] 15 | rustymedia --help 16 | 17 | Folder Configuration: 18 | -l --local= ... Map a local path to be served. 19 | The argument should be in the form = where 20 | everything until the first `=` is treated as the name and the rest as 21 | the path. 22 | 23 | Server Options: 24 | -b --bind= Serving socket bind address. [default: [::]:4950] 25 | -n --name= Set the server name. [default: RustyMedia] 26 | --uuid= Server UUID. [default: 06289e13-a832-4d76-be0b-00151d449864] 27 | 28 | Other Options: 29 | -h --help Show this help. 30 | "; 31 | 32 | #[derive(Deserialize)] 33 | struct Args { 34 | flag_bind: std::net::SocketAddr, 35 | flag_local: Vec, 36 | flag_name: String, 37 | flag_uuid: String, 38 | } 39 | 40 | fn find_public_addr(bind: std::net::SocketAddr) -> std::net::SocketAddr { 41 | if !bind.ip().is_unspecified() { return bind } 42 | 43 | for interface in pnet::datalink::interfaces() { 44 | if interface.is_loopback() { continue } 45 | 46 | for ipnetwork in interface.ips { 47 | return std::net::SocketAddr::new(ipnetwork.ip(), bind.port()); 48 | } 49 | } 50 | 51 | panic!("Could not find public address! Please pass --bind=:") 52 | } 53 | 54 | fn result_main() -> rustymedia::Result<()> { 55 | let args: Args = docopt::Docopt::new(USAGE) 56 | .and_then(|d| d.deserialize()) 57 | .unwrap_or_else(|e| e.exit()); 58 | 59 | let mut root = rustymedia::root::Root::new(); 60 | 61 | for mapping in args.flag_local { 62 | let i = mapping.find('=').expect("No `=` found in --local mapping"); 63 | 64 | root.add(rustymedia::local::Object::new_root( 65 | mapping[..i].to_string(), mapping[i+1..].to_string())?); 66 | } 67 | 68 | if root.is_empty() { 69 | panic!("No folders configured."); 70 | } 71 | let root = Arc::new(root); 72 | 73 | let addr = find_public_addr(args.flag_bind); 74 | 75 | let handle: Arc>> = 76 | Arc::new(std::sync::Mutex::new(None)); 77 | 78 | let service_handle = handle.clone(); 79 | let service = rustymedia::dlna::server::ServerFactory::new( 80 | rustymedia::dlna::server::ServerArgs { 81 | uri: format!("http://{}", addr), 82 | root: root.clone(), 83 | remote: move || service_handle.lock().unwrap().as_ref().unwrap().clone(), 84 | name: args.flag_name, 85 | uuid: args.flag_uuid, 86 | }); 87 | 88 | let server = hyper::server::Http::new() 89 | .bind(&args.flag_bind, service).unwrap(); 90 | 91 | *handle.lock().unwrap() = Some(server.handle().remote().clone()); 92 | 93 | eprintln!("Listening on http://{}/", addr); 94 | rustymedia::dlna::discovery::schedule_presence_broadcasts(server.handle(), addr); 95 | server.run().unwrap(); 96 | eprintln!("Done."); 97 | 98 | Ok(()) 99 | } 100 | 101 | fn main() { 102 | env_logger::init().expect("Failed to init env_logger"); 103 | result_main().unwrap() 104 | } 105 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use lru_cache; 2 | use smallvec; 3 | use std; 4 | 5 | #[derive(Debug)] 6 | struct Entry { 7 | format: crate::ffmpeg::Format, 8 | media: std::sync::Arc, 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct TranscodeCache { 13 | values: lru_cache::LruCache< 14 | String, 15 | smallvec::SmallVec<[Entry; 1]>>, 16 | } 17 | 18 | impl TranscodeCache { 19 | pub fn new() -> Self { 20 | TranscodeCache { 21 | values: lru_cache::LruCache::new(10), 22 | } 23 | } 24 | 25 | pub fn get(&mut self, 26 | exec: &crate::Executors, 27 | item: &Box, 28 | format: &crate::ffmpeg::Format, 29 | device: &crate::ffmpeg::Device, 30 | ) -> crate::Result> 31 | { 32 | if format.compatible_with(device) { return item.body(&exec) } 33 | 34 | eprintln!("Cache size: {}", self.values.len()); 35 | match self.values.entry(item.id().to_owned()) { 36 | lru_cache::Entry::Occupied(mut e) => { 37 | for e in e.get_mut().iter_mut() { 38 | eprintln!("Transcode available: {:?}", e.format); 39 | if e.format.compatible_with(device) { 40 | eprintln!("Transcode cache hit!"); 41 | return Ok(e.media.clone()) 42 | } 43 | } 44 | let transcoded_format = format.transcode_for(device); 45 | let media = item.transcoded_body(&exec, &format, &transcoded_format)?; 46 | e.get_mut().push(Entry{format: transcoded_format, media: media.clone()}); 47 | Ok(media) 48 | } 49 | lru_cache::Entry::Vacant(e) => { 50 | eprintln!("Transcode cache miss!"); 51 | let transcoded_format = format.transcode_for(device); 52 | let media = item.transcoded_body(exec, &format, &transcoded_format)?; 53 | e.insert(smallvec::SmallVec::from_buf( 54 | [Entry{format: transcoded_format, media: media.clone()}])); 55 | Ok(media) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] // Until this module can be constexpr. 2 | 3 | #[inline] pub fn FFMPEG_BINARY() -> &'static str { 4 | option_env!("FFMPEG_BINARY").unwrap_or("ffmpeg") 5 | } 6 | #[inline] pub fn FFPROBE_BINARY() -> &'static str { 7 | option_env!("FFPROBE_BINARY").unwrap_or("ffprobe") 8 | } 9 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | use crate::ffmpeg::*; 2 | use hyper; 3 | use regex; 4 | 5 | const ALL: Device = Device { 6 | container: &[], 7 | video: &[], 8 | audio: &[], 9 | }; 10 | 11 | const CHROMECAST: Device = Device { 12 | container: &[ContainerFormat::MKV], 13 | video: &[VideoFormat::H264, VideoFormat::VP8], 14 | audio: &[ 15 | AudioFormat::Opus, 16 | AudioFormat::Vorbis, 17 | AudioFormat::AAC, 18 | AudioFormat::FLAC, 19 | AudioFormat::MP3, 20 | ], 21 | }; 22 | 23 | const CHROMECAST_ULTRA: Device = Device { 24 | container: &[ContainerFormat::MKV], 25 | video: &[VideoFormat::H264, VideoFormat::HEVC, VideoFormat::VP8], 26 | audio: &[ 27 | // AudioFormat::AAC, // Fails to play. 28 | AudioFormat::Opus, 29 | AudioFormat::Vorbis, 30 | AudioFormat::FLAC, 31 | AudioFormat::MP3, 32 | ], 33 | }; 34 | 35 | const SAFE: Device = Device { 36 | container: &[ContainerFormat::MKV], 37 | video: &[VideoFormat::H264], 38 | audio: &[ 39 | AudioFormat::MP3, 40 | AudioFormat::AAC, 41 | ], 42 | }; 43 | 44 | const WEIRD: Device = Device { 45 | container: &[ContainerFormat::MOV], 46 | video: &[VideoFormat::HEVC], 47 | audio: &[AudioFormat::MP3], 48 | }; 49 | 50 | const DEVICES: &[Device] = &[ 51 | CHROMECAST_ULTRA, 52 | CHROMECAST, 53 | ALL, 54 | WEIRD, 55 | SAFE, 56 | ]; 57 | 58 | lazy_static! { 59 | static ref UA_TO_DEVICE: regex::RegexSet = regex::RegexSet::new(&[ 60 | " aarch64\\).* CrKey/", 61 | " CrKey/", 62 | "^VLC/", 63 | "^TestWeird/", 64 | "", 65 | ]).unwrap(); 66 | } 67 | 68 | pub fn identify(req: &hyper::Request) -> &'static Device { 69 | let useragent = match req.headers().get::() { 70 | Some(ua) => ua, 71 | None => return &SAFE, 72 | }; 73 | 74 | for i in UA_TO_DEVICE.matches(useragent) { 75 | return &DEVICES[i] 76 | } 77 | unreachable!() 78 | } 79 | 80 | #[test] 81 | fn test_useragents() { 82 | assert_eq!(DEVICES.len(), UA_TO_DEVICE.len()); 83 | 84 | let mut req = hyper::Request::new(hyper::Method::Get, "/".parse().unwrap()); 85 | 86 | req.headers_mut().set(hyper::header::UserAgent::new("Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.120 Safari/537.36 CrKey/1.32.124602")); 87 | assert_eq!(identify(&req), &CHROMECAST); 88 | 89 | req.headers_mut().set(hyper::header::UserAgent::new("Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.120 Safari/537.36 CrKey/1.32.124602")); 90 | assert_eq!(identify(&req), &CHROMECAST_ULTRA); 91 | } 92 | -------------------------------------------------------------------------------- /src/dlna.rs: -------------------------------------------------------------------------------- 1 | use futures::{Future, Stream}; 2 | use hyper; 3 | use percent_encoding; 4 | use serde; 5 | use serde_xml_rs; 6 | use std; 7 | 8 | use crate::error::ResultExt; 9 | 10 | pub mod discovery; 11 | pub mod server; 12 | pub mod types; 13 | 14 | const UDN: &str = "uuid:06289e13-a832-4d76-be0b-00151d439863"; 15 | 16 | #[derive(Debug)] 17 | struct Request { 18 | req: hyper::Request, 19 | path_offset: usize, 20 | } 21 | 22 | impl Request { 23 | fn new(req: hyper::Request) -> Self { 24 | Request { 25 | path_offset: if req.path().starts_with('/') { 1 } else { 0 }, 26 | req: req, 27 | } 28 | } 29 | 30 | fn path(&self) -> &str { &self.req.path()[self.path_offset..] } 31 | 32 | fn decoded_path(&self) -> crate::Result { 33 | percent_encoding::percent_decode(self.path().as_bytes()) 34 | .decode_utf8() 35 | .chain_err(|| "Error percent-decoding path to utf8") 36 | .map(|s| s.to_string()) 37 | } 38 | 39 | fn pop(&mut self) -> &str { 40 | let next_chunk_start = self.path_offset; 41 | let next_chunk_end = match self.path().find('/') { 42 | Some(i) => { 43 | self.path_offset += i + 1; 44 | next_chunk_start + i 45 | } 46 | None => { 47 | self.path_offset = self.req.path().len(); 48 | self.path_offset 49 | } 50 | }; 51 | 52 | let next_chunk = &self.req.path()[next_chunk_start..next_chunk_end]; 53 | // eprintln!("Pop {:?} from {:?}", next_chunk, self.path()); 54 | return next_chunk 55 | } 56 | 57 | fn body_vec(self) -> Box, Error=crate::error::Error>> { 58 | Box::new(self.req.body() 59 | .then(|r| r.chain_err(|| "Parsing request body.")) 60 | .fold(Vec::new(), |mut v, chunk| { 61 | v.extend(chunk); 62 | Ok::<_,crate::error::Error>(v) 63 | })) 64 | } 65 | 66 | fn body_str_lossy(self) -> Box> { 67 | Box::new(self.req.body() 68 | .then(|r| r.chain_err(|| "Parsing request body.")) 69 | .fold(String::new(), |mut s, chunk| { 70 | s += &String::from_utf8_lossy(&chunk); 71 | Ok::<_,crate::error::Error>(s) 72 | })) 73 | } 74 | 75 | fn to_xml + std::fmt::Debug>(self) 76 | -> Box, Error=crate::error::Error>> 77 | { 78 | Box::new(self.body_vec() 79 | .and_then(|v| { 80 | eprintln!("Parsing xml: {}", String::from_utf8_lossy(&v)); 81 | serde_xml_rs::deserialize(&v[..]) 82 | .chain_err(|| 83 | format!("Error parsing xml:\n{}", String::from_utf8_lossy(&v))) 84 | }) 85 | .inspect(|xml| eprintln!("Request: {:#?}", xml))) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/dlna/connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 0 6 | 7 | 8 | 9 | GetCurrentConnectionInfo 10 | 11 | 12 | ConnectionID 13 | in 14 | A_ARG_TYPE_ConnectionID 15 | 16 | 17 | RcsID 18 | out 19 | A_ARG_TYPE_RcsID 20 | 21 | 22 | AVTransportID 23 | out 24 | A_ARG_TYPE_AVTransportID 25 | 26 | 27 | ProtocolInfo 28 | out 29 | A_ARG_TYPE_ProtocolInfo 30 | 31 | 32 | PeerConnectionManager 33 | out 34 | A_ARG_TYPE_ConnectionManager 35 | 36 | 37 | PeerConnectionID 38 | out 39 | A_ARG_TYPE_ConnectionID 40 | 41 | 42 | Direction 43 | out 44 | A_ARG_TYPE_Direction 45 | 46 | 47 | Status 48 | out 49 | A_ARG_TYPE_ConnectionStatus 50 | 51 | 52 | 53 | 54 | ConnectionComplete 55 | 56 | 57 | ConnectionID 58 | in 59 | A_ARG_TYPE_ConnectionID 60 | 61 | 62 | 63 | 64 | PrepareForConnection 65 | 66 | 67 | RemoteProtocolInfo 68 | in 69 | A_ARG_TYPE_ProtocolInfo 70 | 71 | 72 | PeerConnectionManager 73 | in 74 | A_ARG_TYPE_ConnectionManager 75 | 76 | 77 | PeerConnectionID 78 | in 79 | A_ARG_TYPE_ConnectionID 80 | 81 | 82 | Direction 83 | in 84 | A_ARG_TYPE_Direction 85 | 86 | 87 | ConnectionID 88 | out 89 | A_ARG_TYPE_ConnectionID 90 | 91 | 92 | AVTransportID 93 | out 94 | A_ARG_TYPE_AVTransportID 95 | 96 | 97 | RcsID 98 | out 99 | A_ARG_TYPE_RcsID 100 | 101 | 102 | 103 | 104 | GetProtocolInfo 105 | 106 | 107 | Source 108 | out 109 | SourceProtocolInfo 110 | 111 | 112 | Sink 113 | out 114 | SinkProtocolInfo 115 | 116 | 117 | 118 | 119 | GetCurrentConnectionIDs 120 | 121 | 122 | ConnectionIDs 123 | out 124 | CurrentConnectionIDs 125 | 126 | 127 | 128 | 129 | 130 | 131 | A_ARG_TYPE_ProtocolInfo 132 | string 133 | 134 | 135 | A_ARG_TYPE_ConnectionStatus 136 | string 137 | 138 | OK 139 | ContentFormatMismatch 140 | InsufficientBandwidth 141 | UnreliableChannel 142 | Unknown 143 | 144 | 145 | 146 | SinkProtocolInfo 147 | string 148 | 149 | 150 | A_ARG_TYPE_AVTransportID 151 | i4 152 | 153 | 154 | A_ARG_TYPE_RcsID 155 | i4 156 | 157 | 158 | A_ARG_TYPE_ConnectionID 159 | i4 160 | 161 | 162 | A_ARG_TYPE_ConnectionManager 163 | string 164 | 165 | 166 | SourceProtocolInfo 167 | string 168 | 169 | 170 | A_ARG_TYPE_Direction 171 | string 172 | 173 | Input 174 | Output 175 | 176 | 177 | 178 | CurrentConnectionIDs 179 | string 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/dlna/content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 0 6 | 7 | 8 | 9 | GetSystemUpdateID 10 | 11 | 12 | Id 13 | out 14 | SystemUpdateID 15 | 16 | 17 | 18 | 19 | Search 20 | 21 | 22 | ContainerID 23 | in 24 | A_ARG_TYPE_ObjectID 25 | 26 | 27 | SearchCriteria 28 | in 29 | A_ARG_TYPE_SearchCriteria 30 | 31 | 32 | Filter 33 | in 34 | A_ARG_TYPE_Filter 35 | 36 | 37 | StartingIndex 38 | in 39 | A_ARG_TYPE_Index 40 | 41 | 42 | RequestedCount 43 | in 44 | A_ARG_TYPE_Count 45 | 46 | 47 | SortCriteria 48 | in 49 | A_ARG_TYPE_SortCriteria 50 | 51 | 52 | Result 53 | out 54 | A_ARG_TYPE_Result 55 | 56 | 57 | NumberReturned 58 | out 59 | A_ARG_TYPE_Count 60 | 61 | 62 | TotalMatches 63 | out 64 | A_ARG_TYPE_Count 65 | 66 | 67 | UpdateID 68 | out 69 | A_ARG_TYPE_UpdateID 70 | 71 | 72 | 73 | 74 | GetSearchCapabilities 75 | 76 | 77 | SearchCaps 78 | out 79 | SearchCapabilities 80 | 81 | 82 | 83 | 84 | GetSortCapabilities 85 | 86 | 87 | SortCaps 88 | out 89 | SortCapabilities 90 | 91 | 92 | 93 | 94 | Browse 95 | 96 | 97 | ObjectID 98 | in 99 | A_ARG_TYPE_ObjectID 100 | 101 | 102 | BrowseFlag 103 | in 104 | A_ARG_TYPE_BrowseFlag 105 | 106 | 107 | Filter 108 | in 109 | A_ARG_TYPE_Filter 110 | 111 | 112 | StartingIndex 113 | in 114 | A_ARG_TYPE_Index 115 | 116 | 117 | RequestedCount 118 | in 119 | A_ARG_TYPE_Count 120 | 121 | 122 | SortCriteria 123 | in 124 | A_ARG_TYPE_SortCriteria 125 | 126 | 127 | Result 128 | out 129 | A_ARG_TYPE_Result 130 | 131 | 132 | NumberReturned 133 | out 134 | A_ARG_TYPE_Count 135 | 136 | 137 | TotalMatches 138 | out 139 | A_ARG_TYPE_Count 140 | 141 | 142 | UpdateID 143 | out 144 | A_ARG_TYPE_UpdateID 145 | 146 | 147 | 148 | 149 | 150 | 151 | A_ARG_TYPE_SortCriteria 152 | string 153 | 154 | 155 | A_ARG_TYPE_TransferLength 156 | string 157 | 158 | 159 | TransferIDs 160 | string 161 | 162 | 163 | A_ARG_TYPE_UpdateID 164 | ui4 165 | 166 | 167 | A_ARG_TYPE_SearchCriteria 168 | string 169 | 170 | 171 | A_ARG_TYPE_Filter 172 | string 173 | 174 | 175 | ContainerUpdateIDs 176 | string 177 | 178 | 179 | A_ARG_TYPE_Result 180 | string 181 | 182 | 183 | A_ARG_TYPE_Index 184 | ui4 185 | 186 | 187 | A_ARG_TYPE_TransferID 188 | ui4 189 | 190 | 191 | A_ARG_TYPE_TagValueList 192 | string 193 | 194 | 195 | A_ARG_TYPE_URI 196 | uri 197 | 198 | 199 | A_ARG_TYPE_ObjectID 200 | string 201 | 202 | 203 | SortCapabilities 204 | string 205 | 206 | 207 | SearchCapabilities 208 | string 209 | 210 | 211 | A_ARG_TYPE_Count 212 | ui4 213 | 214 | 215 | A_ARG_TYPE_BrowseFlag 216 | string 217 | 218 | BrowseMetadata 219 | BrowseDirectChildren 220 | 221 | 222 | 223 | SystemUpdateID 224 | ui4 225 | 226 | 227 | A_ARG_TYPE_TransferStatus 228 | string 229 | 230 | COMPLETED 231 | ERROR 232 | IN_PROGRESS 233 | STOPPED 234 | 235 | 236 | 237 | A_ARG_TYPE_TransferTotal 238 | string 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /src/dlna/discovery.rs: -------------------------------------------------------------------------------- 1 | use futures::{Future, IntoFuture, Stream}; 2 | use std; 3 | use tokio_core; 4 | 5 | use crate::dlna; 6 | use crate::error::ResultExt; 7 | 8 | pub fn schedule_presence_broadcasts( 9 | handle: tokio_core::reactor::Handle, 10 | addr: std::net::SocketAddr) 11 | { 12 | let socket = std::net::UdpSocket::bind("[::]:0").unwrap(); 13 | socket.connect("239.255.255.250:1900").unwrap(); 14 | let socket = std::rc::Rc::new(socket); 15 | 16 | let make_msg = |nt, usn: &str| format!("\ 17 | NOTIFY * HTTP/1.1\r\n\ 18 | HOST: 239.255.255.250:1900\r\n\ 19 | NT: {}\r\n\ 20 | NTS: ssdp:alive\r\n\ 21 | LOCATION: http://{}/root.xml\r\n\ 22 | USN: {}\r\n\ 23 | CACHE-CONTROL: max-age=1800\r\n\ 24 | SERVER: somesystem, DLNADOC/1.50 UPnP/1.0, rustmedia/1.0\r\n\ 25 | \r\n", 26 | nt, 27 | addr, 28 | usn).into_bytes(); 29 | 30 | let make_dup = |nt| make_msg(nt, format!("{}::{}", dlna::UDN, nt).as_str()); 31 | 32 | let msg_root = make_dup("upnp:rootdevice"); 33 | let msg_mediaserver = make_dup("urn:schemas-upnp-org:device:MediaServer:1"); 34 | let msg_contentdir = make_dup("urn:schemas-upnp-org:service:ContentDirectory:1"); 35 | let msg_connectionmanager = make_dup("urn:schemas-upnp-org:service:ConnectionManager:1"); 36 | let msg_uuid = make_msg(dlna::UDN, dlna::UDN); 37 | 38 | let broadcast_message = move |desc, data: &[u8]| { 39 | socket.send(data) 40 | .map(|bytes_written| if bytes_written != data.len() { 41 | eprintln!("W: sending of {} truncated.", desc); }) 42 | .chain_err(|| format!("Error sending {}", desc)) 43 | }; 44 | 45 | let broadcast_presence = move || -> crate::error::Result<()> { 46 | // eprintln!("Broadcasting presence."); 47 | // eprintln!("{}", String::from_utf8_lossy(&msg_uuid)); 48 | 49 | // Spec recommends sending each packet 3 times. One seems fine for now. 50 | for _ in 0..1 { 51 | broadcast_message("uuid", &msg_uuid)?; 52 | broadcast_message("root", &msg_root)?; 53 | broadcast_message("mediaserver", &msg_mediaserver)?; 54 | broadcast_message("connectionmanager", &msg_connectionmanager)?; 55 | broadcast_message("contentdir", &msg_contentdir)?; 56 | } 57 | 58 | Ok(()) 59 | }; 60 | 61 | handle.spawn(tokio_core::reactor::Interval::new_at( 62 | std::time::Instant::now(), 63 | std::time::Duration::from_secs(10), 64 | &handle).unwrap() 65 | .for_each(move |_| 66 | broadcast_presence() 67 | .or_else(|e: crate::error::Error| { 68 | eprintln!("Error broadcasting presence: {:?}", e); 69 | Ok(()) 70 | }) 71 | .into_future()) 72 | .map_err(|e| { eprintln!("Error at end of forever: {:?}", e); })); 73 | } 74 | -------------------------------------------------------------------------------- /src/dlna/root.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 4 | 5 | urn:schemas-upnp-org:device:MediaServer:1 6 | {name} 7 | Kevin Cox 8 | https://kevincox.ca 9 | model 10 | rustymedia 11 | 1 12 | https://kevincox/ca 13 | 1 14 | uuid:{uuid} 15 | DMS-1.50 16 | / 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | urn:schemas-upnp-org:service:ContentDirectory:1 29 | urn:upnp-org:serviceId:ContentDirectory 30 | /content/control 31 | /events/content 32 | /content/desc.xml 33 | 34 | 35 | urn:schemas-upnp-org:service:ConnectionManager:1 36 | urn:upnp-org:serviceId:ConnectionManager 37 | /connection/control 38 | /events/connection 39 | /connection/desc.xml 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/dlna/server.rs: -------------------------------------------------------------------------------- 1 | use bytes; 2 | use error_chain::ChainedError; 3 | use futures; 4 | use futures::{Future, Sink, Stream}; 5 | use futures_cpupool; 6 | use hyper; 7 | use percent_encoding; 8 | use serde; 9 | use std; 10 | use tokio_core; 11 | 12 | use crate::Object; 13 | use crate::dlna; 14 | use crate::error::{ResultExt}; 15 | 16 | const CONNECTION_XML: &str = include_str!("connection.xml"); 17 | const CONTENT_XML: &str = include_str!("content.xml"); 18 | 19 | header! { (Soapaction, "Soapaction") => [String] } 20 | 21 | pub struct ServerArgs { 22 | pub uri: String, 23 | pub remote: F, 24 | pub root: std::sync::Arc, 25 | pub name: String, 26 | pub uuid: String, 27 | } 28 | 29 | #[derive(Debug)] 30 | struct Shared { 31 | transcode_cache: std::sync::Mutex, 32 | } 33 | 34 | pub struct ServerFactory { 35 | uri: String, 36 | remote: F, 37 | root: std::sync::Arc, 38 | shared: std::sync::Arc, 39 | root_xml: bytes::Bytes, 40 | 41 | cpupool: std::sync::Arc, 42 | } 43 | 44 | impl ServerFactory { 45 | pub fn new(args: ServerArgs) -> Self { 46 | ServerFactory { 47 | uri: args.uri, 48 | remote: args.remote, 49 | root: args.root, 50 | shared: std::sync::Arc::new(Shared { 51 | transcode_cache: std::sync::Mutex::new(crate::cache::TranscodeCache::new()), 52 | }), 53 | root_xml: format!(include_str!("root.xml"), 54 | name=args.name, 55 | uuid=args.uuid 56 | ).into(), 57 | 58 | cpupool: std::sync::Arc::new(futures_cpupool::CpuPool::new(8)), 59 | } 60 | } 61 | } 62 | 63 | impl tokio_core::reactor::Remote> hyper::server::NewService for ServerFactory { 64 | type Request = hyper::Request; 65 | type Response = hyper::Response; 66 | type Error = hyper::Error; 67 | type Instance = ServerRef; 68 | 69 | fn new_service(&self) -> Result { 70 | Ok(ServerRef(std::sync::Arc::new(Server::new(self)))) 71 | } 72 | } 73 | 74 | #[derive(Debug)] 75 | pub struct Server { 76 | uri: String, 77 | root: std::sync::Arc, 78 | shared: std::sync::Arc, 79 | root_xml: bytes::Bytes, 80 | 81 | exec: crate::Executors, 82 | } 83 | 84 | impl Server { 85 | fn new< 86 | F: Fn() -> tokio_core::reactor::Remote> 87 | (factory: &ServerFactory) -> Self 88 | { 89 | Server { 90 | uri: factory.uri.clone(), 91 | root: factory.root.clone(), 92 | shared: factory.shared.clone(), 93 | root_xml: factory.root_xml.clone(), 94 | exec: crate::Executors { 95 | handle: (factory.remote)().handle().unwrap(), 96 | cpupool: factory.cpupool.clone(), 97 | }, 98 | } 99 | } 100 | } 101 | 102 | impl ServerRef { 103 | fn call_root(&self, mut req: dlna::Request) -> BoxedResponse { 104 | match req.pop() { 105 | "root.xml" => { 106 | if *req.req.method() != hyper::Method::Get { 107 | return call_method_not_allowed(req) 108 | } 109 | 110 | respond_ok( 111 | hyper::Response::new() 112 | .with_status(hyper::StatusCode::Ok) 113 | .with_body(self.0.root_xml.clone())) 114 | } 115 | "connection" => self.call_connection(req), 116 | "content" => self.call_content(req), 117 | "files" => self.call_files(req), 118 | "video" => self.call_video(req), 119 | _ => call_not_found(req), 120 | } 121 | } 122 | 123 | fn call_connection(&self, mut req: dlna::Request) -> BoxedResponse { 124 | match req.pop() { 125 | "desc.xml" => { 126 | respond_ok(hyper::Response::new().with_body(CONNECTION_XML)) 127 | } 128 | _ => call_not_found(req), 129 | } 130 | } 131 | 132 | fn call_content(&self, mut req: dlna::Request) -> BoxedResponse { 133 | match req.pop() { 134 | "control" => self.call_content_soap(req), 135 | "desc.xml" => respond_ok(hyper::Response::new().with_body(CONTENT_XML)), 136 | _ => call_not_found(req), 137 | } 138 | } 139 | 140 | fn call_content_soap(&self, req: dlna::Request) -> BoxedResponse { 141 | let action = match req.req.headers().get::() { 142 | Some(action) => { 143 | let action = action.trim_matches('"'); 144 | if !action.starts_with("urn:schemas-upnp-org:service:ContentDirectory:1#") { 145 | return respond_soap_fault(&format!("Unknown action namespace: {:?}", action)) 146 | } 147 | &action[48..] 148 | } 149 | None => return respond_soap_fault("No Soapaction header."), 150 | }.to_string(); // TODO: Fix this last lifetime fix. 151 | 152 | match &action[..] { 153 | "Browse" => { 154 | let this = self.clone(); 155 | Box::new(req.to_xml().and_then(move |x| this.call_dlna_browse(x.body))) 156 | } 157 | other => respond_soap_fault(&format!("Unknown action {:?}", other)), 158 | } 159 | } 160 | 161 | fn call_files(&self, req: dlna::Request) -> BoxedResponse { 162 | let path = match req.decoded_path() { 163 | Ok(p) => p, 164 | Err(e) => return respond_err(e), 165 | }; 166 | let item = match self.0.root.lookup(&path) { 167 | Ok(path) => path, 168 | Err(e) => return respond_err(e), 169 | }; 170 | 171 | let server = self.0.clone(); 172 | 173 | let r = item.body(&server.exec) 174 | .and_then(move |media| { 175 | let mut response = hyper::Response::new() 176 | .with_header(hyper::header::ContentType::octet_stream()); 177 | 178 | let content = media.read_all() 179 | .map(|c| Ok(c.into())) 180 | .map_err(|e| e.into()); 181 | 182 | let (sender, body) = hyper::Body::pair(); 183 | server.exec.spawn( 184 | sender.send_all(content) 185 | .map(|_| ()) 186 | .then(|r| r.chain_err(|| "Error sending body.")))?; 187 | 188 | eprintln!("Response: {:?}", response); 189 | response.set_body(body); 190 | Ok(response) 191 | }); 192 | 193 | Box::new(futures::future::result(r)) 194 | } 195 | 196 | fn call_video(&self, req: dlna::Request) -> BoxedResponse { 197 | let path = match req.decoded_path() { 198 | Ok(p) => p, 199 | Err(e) => return respond_err(e), 200 | }; 201 | let item = match self.0.root.lookup(&path) { 202 | Ok(path) => path, 203 | Err(e) => return respond_err(e), 204 | }; 205 | 206 | let server = self.0.clone(); 207 | let server2 = self.0.clone(); 208 | 209 | let device = crate::devices::identify(&req.req); 210 | 211 | let r = item.format(&server.exec) 212 | .and_then(move |format| { 213 | let mut cache = server.shared.transcode_cache.lock().unwrap(); 214 | cache.get(&server.exec, &item, &format, &device) 215 | }) 216 | .and_then(move |media| { 217 | let mut response = hyper::Response::new() 218 | .with_header(hyper::header::AcceptRanges(vec![ 219 | hyper::header::RangeUnit::Bytes, 220 | ])) 221 | .with_header(hyper::header::ContentType::octet_stream()); 222 | 223 | let size = media.size(); 224 | 225 | let range = req.req.headers().get::() 226 | .and_then(|range| match *range { 227 | hyper::header::Range::Bytes(ref spec) => Some(spec), 228 | _ => None, 229 | }) 230 | .and_then(|spec| spec.first()) 231 | .and_then(|range| match *range { 232 | hyper::header::ByteRangeSpec::FromTo(start, end) => { 233 | if start < size.available { 234 | Some((start, end.min(size.available-1))) 235 | } else { 236 | None 237 | } 238 | }, 239 | hyper::header::ByteRangeSpec::AllFrom(start) => { 240 | if start < size.available { 241 | Some((start, size.available-1)) 242 | } else { 243 | None 244 | } 245 | }, 246 | hyper::header::ByteRangeSpec::Last(_) => { 247 | None 248 | } 249 | }); 250 | 251 | let content = match range { 252 | Some((start, end)) => { 253 | response.set_status(hyper::StatusCode::PartialContent); 254 | response.headers_mut().set(hyper::header::ContentRange( 255 | hyper::header::ContentRangeSpec::Bytes{ 256 | range: Some((start, end)), 257 | instance_length: size.total, 258 | })); 259 | response.headers_mut().set(hyper::header::ContentLength(end+1-start)); 260 | media.read_range(start, end+1) 261 | }, 262 | None => { 263 | if let Some(size) = size.total { 264 | response.headers_mut().set(hyper::header::ContentLength(size)); 265 | } 266 | media.read_all() // No range. 267 | } 268 | }; 269 | 270 | let content = content 271 | .map(|c| Ok(c.into())) 272 | .map_err(|e| e.into()); 273 | 274 | let (sender, body) = hyper::Body::pair(); 275 | server2.exec.spawn( 276 | sender.send_all(content) 277 | .map(|_| ()) 278 | .then(|r| r.chain_err(|| "Error sending body.")))?; 279 | 280 | eprintln!("Response: {:?}", response); 281 | response.set_body(body); 282 | Ok(response) 283 | }); 284 | 285 | Box::new(r) 286 | } 287 | 288 | fn call_dlna_browse(self, body: dlna::types::Body) -> crate::Result { 289 | let object = self.0.root.lookup(&body.browse.object_id)?; 290 | 291 | let mut containers = Vec::new(); 292 | let mut items = Vec::new(); 293 | let mut support = Vec::new(); 294 | 295 | for entry in object.children()? { 296 | match entry.file_type() { 297 | crate::Type::Directory => containers.push(entry), 298 | crate::Type::Image | crate::Type::Subtitles => support.push(entry), 299 | crate::Type::Video => items.push(entry), 300 | crate::Type::Other => continue, 301 | } 302 | } 303 | 304 | containers.sort_by(|l, r| crate::human_order(l.id(), r.id())); 305 | items.sort_by(|l, r| crate::human_order(l.id(), r.id())); 306 | support.sort_by(|l, r| crate::human_order(l.id(), r.id())); 307 | 308 | respond_soap(dlna::types::BodyBrowseResponse { 309 | browse_response: dlna::types::BrowseResponse { 310 | number_returned: 1, 311 | total_matches: 1, 312 | update_id: 1, 313 | result: dlna::types::Result(dlna::types::DidlLite { 314 | xmlns: "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/", 315 | xmlns_dc: "http://purl.org/dc/elements/1.1/", 316 | xmlns_upnp: "urn:schemas-upnp-org:metadata-1-0/upnp/", 317 | containers: containers.into_iter().map(|entry| { 318 | dlna::types::Container { 319 | parent_id: entry.parent_id().to_string(), 320 | id: entry.id().to_string(), 321 | title: entry.title(), 322 | restricted: true, 323 | class: entry.dlna_class(), 324 | _start_body: crate::xml::Body(()), 325 | } 326 | }).collect(), 327 | items: items.into_iter().map(|entry| { 328 | let path = percent_encoding::percent_encode( 329 | entry.id().as_bytes(), 330 | percent_encoding::DEFAULT_ENCODE_SET); 331 | let url = format!("{}/video/{}", self.0.uri, path); 332 | 333 | let mut item = dlna::types::Item { 334 | parent_id: entry.parent_id().to_string(), 335 | id: entry.id().to_string(), 336 | title: entry.title(), 337 | restricted: true, 338 | class: entry.dlna_class(), 339 | res: vec![ 340 | dlna::types::Res { 341 | protocol_info: "http-get:*:video/x-matroska:*".to_string(), 342 | uri: crate::xml::Body(url), 343 | }, 344 | ], 345 | }; 346 | 347 | let prefix = entry.prefix(); 348 | let start = support 349 | .binary_search_by_key(&prefix, |e| e.id()) 350 | .unwrap_or_else(|e| e); 351 | for support in &support[start..] { 352 | if !support.id().starts_with(prefix) { 353 | break 354 | } 355 | 356 | let path = percent_encoding::percent_encode( 357 | support.id().as_bytes(), 358 | percent_encoding::DEFAULT_ENCODE_SET); 359 | 360 | match support.file_type() { 361 | crate::Type::Image => { 362 | item.res.push(dlna::types::Res { 363 | protocol_info: "http-get:*:image/jpeg:*".to_string(), 364 | uri: crate::xml::Body(format!("{}/files/{}", self.0.uri, path)), 365 | }); 366 | } 367 | crate::Type::Subtitles => { 368 | } 369 | 370 | crate::Type::Directory => unreachable!(), 371 | crate::Type::Video => unreachable!(), 372 | crate::Type::Other => unreachable!(), 373 | } 374 | } 375 | 376 | item 377 | }).collect(), 378 | }), 379 | }, 380 | }) 381 | } 382 | } 383 | 384 | fn respond_ok(res: hyper::Response) -> BoxedResponse { 385 | Box::new(futures::future::ok(res)) 386 | } 387 | 388 | fn respond_err(e: crate::error::Error) -> BoxedResponse { 389 | Box::new(futures::future::err(e)) 390 | } 391 | 392 | fn respond_soap 393 | (body: T) -> crate::error::Result 394 | { 395 | eprintln!("Responding with: {:#?}", body); 396 | let mut buf = Vec::new(); 397 | crate::xml::serialize(&mut buf, dlna::types::Envelope{body}) 398 | .chain_err(|| "Error serializing XML.")?; 399 | // eprintln!("Emitting xml: {}", String::from_utf8_lossy(&buf)); 400 | Ok(hyper::Response::new() 401 | .with_header(hyper::header::ContentType::xml()) 402 | .with_body(buf)) 403 | } 404 | 405 | fn respond_soap_fault(msg: &str) -> BoxedResponse { 406 | eprintln!("Reporting fault via soap: {:?}", msg); 407 | Box::new(futures::future::result(respond_soap(dlna::types::BodyFault { 408 | fault: dlna::types::Fault { 409 | faultcode: "SOAP-ENV:Client", 410 | faultstring: msg, 411 | }, 412 | }))) 413 | } 414 | 415 | fn call_not_found(req: dlna::Request) -> BoxedResponse { 416 | eprintln!("404 {:?}", req.req); 417 | Box::new(req.body_str_lossy() 418 | .and_then(move |_body| { 419 | // eprintln!("404 body\n{}\n404 End\n", _body); 420 | Ok(hyper::Response::new() 421 | .with_status(hyper::StatusCode::NotFound)) 422 | })) 423 | } 424 | 425 | fn call_method_not_allowed(req: dlna::Request) -> BoxedResponse { 426 | eprintln!("405 {:?}", req.req); 427 | respond_ok( 428 | hyper::Response::new() 429 | .with_status(hyper::StatusCode::MethodNotAllowed)) 430 | } 431 | 432 | type BoxedResponse = Box>; 433 | 434 | #[derive(Clone,Debug)] 435 | pub struct ServerRef(std::sync::Arc); 436 | 437 | impl hyper::server::Service for ServerRef { 438 | type Request = hyper::Request; 439 | type Response = hyper::Response; 440 | type Error = hyper::Error; 441 | type Future = Box>; 442 | 443 | fn call(&self, req: Self::Request) -> Self::Future { 444 | // eprintln!("Request: {:#?}", req); 445 | let req = dlna::Request::new(req); 446 | Box::new(self.call_root(req).or_else(|e| { 447 | eprintln!("{}", e.display_chain()); 448 | Ok(hyper::Response::new() 449 | .with_status(hyper::StatusCode::InternalServerError) 450 | .with_body("Internal Error")) 451 | })) 452 | } 453 | } 454 | 455 | -------------------------------------------------------------------------------- /src/dlna/types.rs: -------------------------------------------------------------------------------- 1 | use serde; 2 | use serde::ser::Error; 3 | use std; 4 | 5 | #[derive(Debug,Deserialize,Serialize)] 6 | #[serde(rename_all="PascalCase")] 7 | pub struct Envelope { 8 | pub body: B, 9 | } 10 | 11 | #[derive(Debug,Deserialize)] 12 | #[serde(rename_all="PascalCase")] 13 | pub struct Body { 14 | pub browse: Browse, 15 | } 16 | 17 | #[derive(Debug,Serialize)] 18 | #[serde(rename="Body",rename_all="PascalCase")] 19 | pub struct BodyFault<'a> { 20 | pub fault: Fault<'a>, 21 | } 22 | 23 | #[derive(Debug,Serialize)] 24 | pub struct Fault<'a> { 25 | pub faultcode: &'a str, 26 | pub faultstring: &'a str, 27 | } 28 | 29 | #[derive(Debug,Serialize)] 30 | #[serde(rename="Body",rename_all="PascalCase")] 31 | pub struct BodyBrowseResponse { 32 | pub browse_response: BrowseResponse, 33 | } 34 | 35 | #[derive(Debug,Deserialize)] 36 | #[serde(rename_all="PascalCase")] 37 | pub struct Browse { 38 | #[serde(rename="ObjectID")] 39 | pub object_id: String, // TODO: Make an i64 40 | pub browse_flag: String, 41 | pub filter: String, 42 | pub starting_index: u64, 43 | pub requested_count: u64, 44 | pub sort_criteria: String, 45 | } 46 | 47 | #[derive(Debug,Serialize)] 48 | #[serde(rename_all="PascalCase")] 49 | pub struct BrowseResponse { 50 | pub result: Result, 51 | pub number_returned: u64, 52 | pub total_matches: u64, 53 | pub update_id: u64, 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct Result(pub DidlLite); 58 | 59 | impl serde::Serialize for Result { 60 | fn serialize(&self, serializer: S) 61 | -> std::result::Result 62 | { 63 | let mut buf = Vec::new(); 64 | if let Err(e) = crate::xml::serialize(&mut buf, &self.0) { 65 | return Err(S::Error::custom(format!("{:?}", e))) 66 | } 67 | let s = String::from_utf8(buf).unwrap(); 68 | serializer.serialize_newtype_struct("Result", &s) 69 | } 70 | } 71 | 72 | #[derive(Debug,Serialize)] 73 | #[serde(rename="DIDL-Lite")] 74 | pub struct DidlLite { 75 | #[serde(rename="xmlns")] 76 | pub xmlns: &'static str, 77 | #[serde(rename="xmlns:dc")] 78 | pub xmlns_dc: &'static str, 79 | #[serde(rename="xmlns:upnp")] 80 | pub xmlns_upnp: &'static str, 81 | pub containers: Vec, 82 | pub items: Vec, 83 | } 84 | 85 | #[derive(Debug,Serialize)] 86 | #[serde(rename="container",rename_all="camelCase")] 87 | pub struct Container { 88 | pub id: String, 89 | #[serde(rename="parentID")] 90 | pub parent_id: String, 91 | pub restricted: bool, 92 | 93 | pub _start_body: crate::xml::Body<()>, 94 | #[serde(rename="dc:title")] 95 | pub title: String, 96 | #[serde(rename="upnp:class")] 97 | pub class: &'static str, 98 | 99 | // #[serde(rename="albumArtURI")] 100 | // pub album_art_uri: Vec, 101 | } 102 | 103 | #[derive(Debug,Serialize)] 104 | #[serde(rename="item",rename_all="camelCase")] 105 | pub struct Item { 106 | pub id: String, 107 | #[serde(rename="parentID")] 108 | pub parent_id: String, 109 | pub restricted: bool, 110 | 111 | pub res: Vec, 112 | 113 | #[serde(rename="dc:title")] 114 | pub title: String, 115 | #[serde(rename="upnp:class")] 116 | pub class: &'static str, 117 | } 118 | 119 | #[derive(Debug,Serialize)] 120 | #[serde(rename="res",rename_all="camelCase")] 121 | pub struct Res { 122 | // pub size: u64, 123 | 124 | // Resolution in XXXxYYYY format. 125 | // pub resolution: String, 126 | 127 | pub protocol_info: String, 128 | pub uri: crate::xml::Body, 129 | } 130 | 131 | #[derive(Debug,Serialize)] 132 | pub struct AlbumArtUri { 133 | #[serde(rename="profileID")] 134 | pub profile_id: String, 135 | pub uri: crate::xml::Body, 136 | } 137 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | 3 | use futures; 4 | use std; 5 | 6 | error_chain!{ 7 | errors { 8 | Invalid(msg: String) 9 | ExecuteError 10 | NotADirectory(path: std::path::PathBuf) { display("Not a directory: {:?}", path) } 11 | NotAFile(path: String) { display("Not a file: {:?}", path) } 12 | NotFound(msg: String) { display("Not found: {}", msg) } 13 | Other(msg: String) 14 | Unimplemented(msg: &'static str) 15 | } 16 | 17 | foreign_links { 18 | Hyper(::hyper::Error); 19 | Io(::std::io::Error); 20 | Json(::serde_json::Error); 21 | KXml(crate::xml::Error); 22 | Nix(::nix::Error); 23 | Utf8Error(::std::str::Utf8Error); 24 | Xml(::serde_xml_rs::Error); 25 | } 26 | } 27 | 28 | impl Into> for Error { 29 | fn into(self) -> futures::sync::mpsc::SendError { 30 | panic!("Can't convert following into futures::sync::mpsc::SendError: {:?}", self) 31 | } 32 | } 33 | 34 | impl From> for Error { 35 | fn from(err: futures::sync::mpsc::SendError) -> Self { 36 | ErrorKind::Other(format!("SendError: {:?}", err)).into() 37 | } 38 | } 39 | 40 | impl From> for Error { 41 | fn from(_: futures::future::ExecuteError) -> Self { 42 | ErrorKind::ExecuteError.into() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ffmpeg.rs: -------------------------------------------------------------------------------- 1 | use futures; 2 | use futures::stream::Stream; 3 | use nix; 4 | use os_pipe::IntoStdio; 5 | use os_pipe; 6 | use serde_json; 7 | use std; 8 | use std::io::{Write}; 9 | use std::os::unix::fs::FileExt; 10 | use std::os::unix::io::FromRawFd; 11 | 12 | use crate::error::ResultExt; 13 | 14 | fn start_cmd(cmd: &'static str) -> std::process::Command { 15 | let mut cmd = std::process::Command::new(cmd); 16 | cmd.stdin(std::process::Stdio::null()); 17 | cmd 18 | } 19 | 20 | fn start_ffmpeg() -> std::process::Command { 21 | let mut cmd = start_cmd(crate::config::FFMPEG_BINARY()); 22 | cmd.arg("-nostdin"); 23 | cmd 24 | } 25 | 26 | fn start_ffprobe() -> std::process::Command { 27 | start_cmd(crate::config::FFPROBE_BINARY()) 28 | } 29 | 30 | pub enum Input<'a> { 31 | Uri(&'a std::path::Path), 32 | Stream(crate::ByteStream), 33 | } 34 | 35 | fn add_input(input: Input, exec: &crate::Executors, cmd: &mut std::process::Command) -> crate::Result<()> { 36 | cmd.args(&["-err_detect", "ignore_err"]); 37 | 38 | match input { 39 | Input::Uri(uri) => { cmd.arg("-i").arg(uri); } 40 | Input::Stream(content) => { 41 | let (read, mut write) = os_pipe::pipe()?; 42 | exec.spawn( 43 | content.for_each(move |chunk| { 44 | write.write_all(&chunk) 45 | .chain_err(|| "Error writing to ffmpeg.") 46 | }))?; 47 | 48 | cmd.arg("-i").arg("pipe:0"); 49 | cmd.stdin(read.into_stdio()); 50 | } 51 | }; 52 | 53 | Ok(()) 54 | } 55 | 56 | #[derive(Clone,Debug,PartialEq)] 57 | pub enum ContainerFormat { 58 | MKV, 59 | MOV, 60 | MP4, 61 | MPEGTS, 62 | WAV, 63 | WEBM, 64 | 65 | Other(String), 66 | } 67 | 68 | impl ContainerFormat { 69 | fn ffmpeg_encoder_and_flags(&self) -> &'static [&'static str] { 70 | match *self { 71 | ContainerFormat::MKV => &["matroska"], 72 | ContainerFormat::MPEGTS => &["mpegts"], 73 | ContainerFormat::MOV => &["mov", "-movflags", "+frag_keyframe"], 74 | ContainerFormat::MP4 => &["ismv", "-movflags", "+frag_keyframe"], 75 | ContainerFormat::WAV => 76 | unreachable!("WAV shouldn't be used because ffmpeg creates invalid WAV files."), 77 | ContainerFormat::WEBM => &["webm"], 78 | ContainerFormat::Other(ref s) => 79 | unreachable!("Unknown codec {:?} should never be used as a target.", s), 80 | } 81 | } 82 | } 83 | 84 | #[derive(Clone,Debug,PartialEq)] 85 | pub enum AudioFormat { 86 | AAC, 87 | FLAC, 88 | MP3, 89 | Opus, 90 | Vorbis, 91 | 92 | Other(String), 93 | } 94 | 95 | impl AudioFormat { 96 | fn ffmpeg_id(&self) -> &'static [&'static str] { 97 | match *self { 98 | AudioFormat::AAC => &["aac"], 99 | AudioFormat::FLAC => &["flac"], 100 | AudioFormat::MP3 => &["mp3"], 101 | AudioFormat::Opus => &["opus", "-strict", "-2"], 102 | AudioFormat::Vorbis => &["libvorbis"], 103 | AudioFormat::Other(ref s) => 104 | unreachable!("Unknown codec {:?} should never be used as a target.", s), 105 | } 106 | } 107 | } 108 | 109 | #[derive(Clone,Debug,PartialEq)] 110 | pub enum VideoFormat { 111 | H264, 112 | HEVC, 113 | VP8, 114 | Other(String), 115 | } 116 | 117 | impl VideoFormat { 118 | fn ffmpeg_encoder_and_flags(&self) -> &'static [&'static str] { 119 | match *self { 120 | VideoFormat::H264 => 121 | &["h264", "-preset", "ultrafast", "-bsf:v", "h264_mp4toannexb"], 122 | VideoFormat::HEVC => 123 | &["libx265", "-preset", "ultrafast"], 124 | VideoFormat::VP8 => &["vp8"], 125 | VideoFormat::Other(ref s) => 126 | unreachable!("Unknown codec {:?} should never be used as a target.", s), 127 | } 128 | } 129 | } 130 | 131 | #[derive(Debug)] 132 | pub struct Format { 133 | container: ContainerFormat, 134 | audio: Option, 135 | video: Option, 136 | } 137 | 138 | impl Format { 139 | pub fn compatible_with(&self, device: &Device) -> bool { 140 | // Empty container is a hack to indicate that everything is supported. 141 | return device.container.is_empty() 142 | || (device.container.contains(&self.container) 143 | && self.video.as_ref().map(|f| device.video.contains(&f)).unwrap_or(true) 144 | && self.audio.as_ref().map(|f| device.audio.contains(&f)).unwrap_or(true)); 145 | } 146 | 147 | pub fn transcode_for(&self, device: &Device) -> Format { 148 | // Warning: Devices may have empty supported arrays to indicate they will take anything. 149 | let video = self.video.as_ref() 150 | .and_then(|f| if device.video.contains(f) { Some(f) } else { device.video.first() }); 151 | let audio = self.audio.as_ref() 152 | .and_then(|f| if device.audio.contains(f) { Some(f) } else { device.audio.first() }); 153 | 154 | Format { 155 | container: device.container.first().cloned().unwrap_or(ContainerFormat::MKV), 156 | video: video.cloned(), 157 | audio: audio.cloned(), 158 | } 159 | } 160 | } 161 | 162 | #[derive(Debug,PartialEq)] 163 | pub struct Device { 164 | pub container: &'static [ContainerFormat], 165 | pub audio: &'static [AudioFormat], 166 | pub video: &'static [VideoFormat], 167 | } 168 | 169 | #[derive(Deserialize)] 170 | struct Ffprobe { 171 | format: FfprobeFormat, 172 | streams: Vec, 173 | } 174 | 175 | #[derive(Deserialize)] 176 | struct FfprobeFormat { 177 | format_name: String, 178 | } 179 | 180 | #[derive(Deserialize)] 181 | struct FfprobeStream { 182 | codec_type: String, 183 | codec_name: String, 184 | } 185 | 186 | pub fn format(input: Input, exec: &crate::Executors) -> crate::Future { 187 | let mut cmd = start_ffprobe(); 188 | if let Err(e) = add_input(input, exec, &mut cmd) { 189 | return Box::new(futures::future::err(e)) 190 | } 191 | 192 | cmd.stdout(std::process::Stdio::piped()); 193 | cmd.stderr(std::process::Stdio::null()); 194 | 195 | cmd.arg("-of").arg("json"); 196 | cmd.arg("-show_streams"); 197 | cmd.arg("-show_entries").arg("format=format_name"); 198 | 199 | // eprintln!("Executing: {:?}", cmd); 200 | 201 | let mut child = match cmd.spawn().chain_err(|| "Error executing ffprobe") { 202 | Ok(child) => child, 203 | Err(e) => return Box::new(futures::future::err(e)) 204 | }; 205 | 206 | Box::new(futures::future::lazy(move || { 207 | let out = serde_json::from_reader(child.stdout.take().unwrap()) 208 | .chain_err(|| format!("Error parsing output of: {:?}", cmd)); 209 | 210 | let out = match child.try_wait()? { 211 | Some(status) if !status.success() => { 212 | out.chain_err(|| format!("ffprobe exited: {:?}", status)) 213 | } 214 | _ => out, 215 | }; 216 | 217 | let Ffprobe{ 218 | format: FfprobeFormat{format_name}, 219 | streams, 220 | } = out?; 221 | 222 | let container = match format_name.as_ref() { 223 | "matroska" | "matroska,webm" => ContainerFormat::MKV, 224 | "mov" | "mov,mp4,m4a,3gp,3g2,mj2" => ContainerFormat::MOV, 225 | "mpegts" => ContainerFormat::MPEGTS, 226 | "wav" => ContainerFormat::WAV, 227 | _ => { 228 | eprintln!("Unknown container format: {:?}", format_name); 229 | ContainerFormat::Other(format_name) 230 | } 231 | }; 232 | 233 | let mut format = Format { 234 | container, 235 | audio: None, 236 | video: None, 237 | }; 238 | 239 | for stream in streams.into_iter().rev() { 240 | let FfprobeStream{codec_type, codec_name, ..} = stream; 241 | 242 | println!("{} {}", codec_type, codec_name); 243 | match (codec_type.as_ref(), codec_name.as_ref()) { 244 | ("video", "h264") => 245 | format.video = Some(VideoFormat::H264), 246 | ("video", "hevc") => 247 | format.video = Some(VideoFormat::HEVC), 248 | ("video", codec) => 249 | format.video = Some(VideoFormat::Other(codec.to_string())), 250 | ("audio", "aac") => 251 | format.audio = Some(AudioFormat::AAC), 252 | ("audio", codec) => 253 | format.audio = Some(AudioFormat::Other(codec.to_string())), 254 | ("subtitle", _) => {}, 255 | other => eprintln!("Ignoring unknown stream {:?}", other), 256 | } 257 | } 258 | 259 | eprintln!("{:?}", format); 260 | Ok(format) 261 | })) 262 | } 263 | 264 | #[derive(Debug)] 265 | struct Media { 266 | file: std::sync::Arc 267 | } 268 | 269 | #[derive(Debug)] 270 | struct MediaFile { 271 | file: std::fs::File, 272 | progress: std::sync::Mutex, 273 | } 274 | 275 | #[derive(Debug)] 276 | struct MediaProgress { 277 | size: u64, 278 | complete: bool, 279 | blocked: Vec, 280 | } 281 | 282 | impl crate::Media for Media { 283 | fn size(&self) -> crate::MediaSize { 284 | let progress = self.file.progress.lock().unwrap(); 285 | crate::MediaSize { 286 | available: progress.size, 287 | total: if progress.complete { Some(progress.size) } else { None }, 288 | } 289 | } 290 | 291 | fn read_range(&self, start: u64, end: u64) -> crate::ByteStream { 292 | Box::new(MediaStream{file: self.file.clone(), offset: start, end: end}) 293 | } 294 | } 295 | 296 | struct MediaStream { 297 | file: std::sync::Arc, 298 | offset: u64, 299 | end: u64, 300 | } 301 | 302 | impl MediaStream { 303 | fn read(&mut self, buf: &mut Vec) -> crate::Result { 304 | let len = self.file.file.read_at(buf, self.offset) 305 | .chain_err(|| "Error reading in follower.")?; 306 | // eprintln!("STREAM read {}-{} size {}", self.offset, self.offset+len as u64, len); 307 | unsafe { buf.set_len(len); } 308 | return Ok(len as i64) 309 | } 310 | } 311 | 312 | impl futures::Stream for MediaStream { 313 | type Item = Vec; 314 | type Error = crate::Error; 315 | 316 | fn poll(&mut self) -> futures::Poll, crate::Error> { 317 | let buf_size = crate::CHUNK_SIZE.min((self.end - self.offset) as usize); 318 | if buf_size == 0 { return Ok(futures::Async::Ready(None)) } 319 | 320 | let mut buf = Vec::with_capacity(buf_size); 321 | unsafe { buf.set_len(buf_size); } 322 | 323 | match self.read(&mut buf) { 324 | Ok(0) => { 325 | let size = { 326 | let mut progress = self.file.progress.lock().unwrap(); 327 | if !progress.complete { 328 | progress.blocked.push(futures::task::current()); 329 | return Ok(futures::Async::NotReady) 330 | } 331 | progress.size.min(self.end) 332 | }; 333 | 334 | if size > self.offset { 335 | unsafe { buf.set_len(buf_size); } 336 | let len = self.read(&mut buf)?; 337 | if len != 0 { 338 | return Err(crate::ErrorKind::Other( 339 | "Read EOF when expecting content".to_string()).into()) 340 | } 341 | return Ok(futures::Async::Ready(Some(buf))) 342 | } 343 | 344 | Ok(futures::Async::Ready(None)) 345 | }, 346 | Ok(len) => { 347 | // eprintln!("READ: {}/{} ({})", len, buf_size, len as f64 / buf_size as f64); 348 | self.offset += len as u64; 349 | Ok(futures::Async::Ready(Some(buf))) 350 | } 351 | Err(e) => { 352 | return Err(e.into()) 353 | } 354 | } 355 | } 356 | } 357 | 358 | pub fn transcode(source: &Format, target: &Format, input: Input, exec: &crate::Executors) 359 | -> crate::Result> { 360 | let fd = nix::fcntl::open( 361 | "/tmp", 362 | { use nix::fcntl::*; O_APPEND | O_CLOEXEC | O_TMPFILE | O_RDWR }, 363 | { use nix::sys::stat::*; S_IRUSR | S_IWUSR })?; 364 | let file = unsafe { std::fs::File::from_raw_fd(fd) }; 365 | 366 | let mut cmd = start_ffmpeg(); 367 | // cmd.stderr(std::process::Stdio::null()); 368 | add_input(input, exec, &mut cmd)?; 369 | 370 | if let Some(ref f) = target.video { 371 | cmd.arg("-c:v").args(if target.video == source.video { 372 | &["copy"] 373 | } else { 374 | f.ffmpeg_encoder_and_flags() 375 | }); 376 | } 377 | if let Some(ref f) = target.audio { 378 | cmd.arg("-c:a").args(if target.audio == source.audio { 379 | &["copy"] 380 | } else { 381 | f.ffmpeg_id() 382 | }); 383 | } 384 | cmd.arg("-f").args(target.container.ffmpeg_encoder_and_flags()); 385 | 386 | cmd.arg("-y"); // "Overwrite" output files. 387 | // Note: `pipe:` is always treated as unseekable so use /dev/stdout. 388 | cmd.arg("/dev/stdout"); 389 | 390 | cmd.stdout(file.try_clone()?); 391 | 392 | eprintln!("Executing: {:?}", cmd); 393 | 394 | let mut child = cmd.spawn().chain_err(|| "Error executing ffmpeg")?; 395 | 396 | let media_file = std::sync::Arc::new(MediaFile{ 397 | file: file.try_clone()?, 398 | progress: std::sync::Mutex::new(MediaProgress{ 399 | size: 0, 400 | complete: false, 401 | blocked: Vec::new(), 402 | }), 403 | }); 404 | 405 | let media_file_thread = media_file.clone(); 406 | std::thread::spawn(move || { 407 | loop { 408 | std::thread::sleep(std::time::Duration::from_secs(1)); 409 | 410 | match child.try_wait() { 411 | Ok(Some(_)) => break, 412 | Ok(None) => {}, 413 | Err(e) => eprintln!("Error waiting for ffmpeg: {:?}", e), 414 | } 415 | 416 | let metadata = file.metadata(); 417 | let mut progress = media_file_thread.progress.lock().unwrap(); 418 | match metadata { 419 | Ok(metadata) => progress.size = metadata.len(), 420 | Err(e) => eprintln!("Error reading transcoded file size: {:?}", e), 421 | } 422 | 423 | for task in progress.blocked.drain(..) { 424 | task.notify(); 425 | } 426 | } 427 | 428 | eprintln!("Transcoding complete."); 429 | let metadata = file.metadata(); 430 | let mut progress = media_file_thread.progress.lock().unwrap(); 431 | match metadata { 432 | Ok(metadata) => progress.size = metadata.len(), 433 | Err(e) => eprintln!("Error reading transcoded file size: {:?}", e), 434 | } 435 | progress.complete = true; 436 | for task in progress.blocked.drain(..) { 437 | task.notify(); 438 | } 439 | }); 440 | 441 | Ok(std::sync::Arc::new(Media{file: media_file})) 442 | } 443 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit="512"] 2 | 3 | extern crate bytes; 4 | extern crate futures; 5 | extern crate futures_cpupool; 6 | #[macro_use] extern crate error_chain; 7 | #[macro_use] extern crate hyper; 8 | #[macro_use] extern crate lazy_static; 9 | extern crate lru_cache; 10 | extern crate nix; 11 | extern crate os_pipe; 12 | extern crate percent_encoding; 13 | extern crate regex; 14 | #[macro_use] extern crate serde_derive; 15 | extern crate serde; 16 | extern crate serde_json; 17 | extern crate serde_xml_rs; 18 | extern crate smallvec; 19 | extern crate tokio_core; 20 | extern crate tokio_file_unix; 21 | extern crate tokio_io; 22 | 23 | use error_chain::ChainedError; 24 | use futures::future::{Executor}; 25 | 26 | mod cache; 27 | mod config; 28 | mod devices; 29 | pub mod dlna; 30 | mod error; 31 | mod ffmpeg; 32 | pub mod local; 33 | pub mod root; 34 | mod xml; 35 | 36 | pub use crate::error::{Error,ErrorKind,Result}; 37 | 38 | pub type Future = Box + Send>; 39 | pub type ByteStream = Box, Error=Error> + Send>; 40 | 41 | pub const CHUNK_SIZE: usize = 256 * 1024; 42 | 43 | struct ReadStream(T); 44 | 45 | impl futures::Stream for ReadStream { 46 | type Item = Vec; 47 | type Error = Error; 48 | 49 | fn poll(&mut self) -> futures::Poll, Error> { 50 | let mut buf = Vec::with_capacity(CHUNK_SIZE); 51 | unsafe { buf.set_len(CHUNK_SIZE); } 52 | let len = self.0.read(&mut buf)?; 53 | unsafe { buf.set_len(len); } 54 | // eprintln!("READ: {}/{} ({})", len, buf_size, len as f64 / buf_size as f64); 55 | 56 | if len == 0 { 57 | Ok(futures::Async::Ready(None)) 58 | } else { 59 | Ok(futures::Async::Ready(Some(buf))) 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug)] 65 | pub struct Executors { 66 | handle: tokio_core::reactor::Handle, 67 | cpupool: std::sync::Arc, 68 | } 69 | 70 | impl Executors { 71 | fn spawn< 72 | F: 'static + futures::future::Future + Send> 73 | (&self, f: F) -> Result<()> 74 | { 75 | self.cpupool.execute( 76 | f.map_err(|e| { eprintln!("Error in spawned future: {}", e.display_chain()); })) 77 | .map_err(|e| e.into()) 78 | } 79 | } 80 | 81 | #[derive(PartialEq)] 82 | pub enum Type { 83 | Directory, 84 | Image, 85 | Subtitles, 86 | Video, 87 | Other, 88 | } 89 | 90 | pub trait Object: Send + Sync + std::fmt::Debug { 91 | fn id(&self) -> &str; 92 | fn parent_id(&self) -> &str; 93 | fn file_type(&self) -> Type; 94 | 95 | fn prefix(&self) -> &str { 96 | let mut prefix = self.id(); 97 | if let Some(i) = prefix.rfind('.') { 98 | if !prefix[i..].contains('/') { 99 | prefix = &prefix[..i]; 100 | } 101 | } 102 | prefix 103 | } 104 | 105 | fn dlna_class(&self) -> &'static str { 106 | match self.file_type() { 107 | Type::Directory => "object.container.storageFolder", 108 | Type::Image => "object.item.imageItem.photo", 109 | Type::Subtitles => "object.item", 110 | Type::Video => "object.item.videoItem", 111 | Type::Other => "object.item", 112 | } 113 | } 114 | 115 | fn title(&self) -> String; 116 | 117 | fn is_dir(&self) -> bool; 118 | fn lookup(&self, id: &str) -> Result>; 119 | 120 | fn children(&self) -> Result>>; 121 | 122 | fn ffmpeg_input(&self, exec: &Executors) -> Result { 123 | Ok(crate::ffmpeg::Input::Stream(self.body(exec)?.read_all())) 124 | } 125 | 126 | fn format(&self, exec: &Executors) -> Future { 127 | let ffmpeg_input = match self.ffmpeg_input(exec) { 128 | Ok(input) => input, 129 | Err(e) => return Box::new(futures::future::err(e)), 130 | }; 131 | crate::ffmpeg::format(ffmpeg_input, exec) 132 | } 133 | 134 | fn body(&self, _exec: &Executors) -> Result> { 135 | Err(ErrorKind::NotAFile(self.id().to_string()).into()) 136 | } 137 | 138 | fn transcoded_body( 139 | &self, exec: &Executors, 140 | source: &crate::ffmpeg::Format, 141 | target: &crate::ffmpeg::Format 142 | ) -> Result> { 143 | crate::ffmpeg::transcode(source, target, self.ffmpeg_input(exec)?, exec) 144 | } 145 | } 146 | 147 | pub struct MediaSize { 148 | available: u64, 149 | total: Option, 150 | } 151 | 152 | pub trait Media: Send + Sync + std::fmt::Debug { 153 | fn size(&self) -> MediaSize; 154 | 155 | fn read_range(&self, start: u64, end: u64) -> ByteStream; 156 | 157 | fn read_all(&self) -> ByteStream { 158 | self.read_range(0, u64::max_value()) 159 | } 160 | } 161 | 162 | #[derive(Debug,Eq,PartialEq,PartialOrd)] 163 | struct Chunk<'a>(&'a str); 164 | 165 | impl<'a> Ord for Chunk<'a> { 166 | fn cmp(&self, that: &Self) -> std::cmp::Ordering { 167 | if self.0.chars().next().unwrap_or('0').is_digit(10) { 168 | (self.0.len(), self.0).cmp(&(that.0.len(), that.0)) 169 | } else { 170 | self.0.cmp(that.0) 171 | } 172 | } 173 | } 174 | 175 | #[derive(Debug)] 176 | struct ChunkIter<'a>(&'a str); 177 | 178 | impl<'a> Iterator for ChunkIter<'a> { 179 | type Item = Chunk<'a>; 180 | 181 | fn next(&mut self) -> Option { 182 | self.0.chars().next() 183 | .map(|first| { 184 | let (head, tail) = self.0.find(|c: char| c.is_digit(10) != first.is_digit(10)) 185 | .map(|i| self.0.split_at(i)) 186 | .unwrap_or((self.0, "")); 187 | self.0 = tail; 188 | Chunk(head.trim_start_matches('0')) 189 | }) 190 | } 191 | } 192 | 193 | fn human_order(l: &str, r: &str) -> std::cmp::Ordering { 194 | let lchunks = ChunkIter(l.rsplit('/').next().unwrap_or(r)); 195 | let rchunks = ChunkIter(r.rsplit('/').next().unwrap_or(r)); 196 | lchunks.cmp(rchunks) 197 | } 198 | 199 | #[test] 200 | fn test_human_order() { 201 | use std::cmp::Ordering::*; 202 | 203 | assert_eq!(human_order("foo", "bar"), Greater); 204 | assert_eq!(human_order("bar", "foo"), Less); 205 | assert_eq!(human_order("bar", "bar"), Equal); 206 | assert_eq!(human_order("bar", "bar 10"), Less); 207 | assert_eq!(human_order("bar 2", "bar 10"), Less); 208 | assert_eq!(human_order("bar 20 59", "bar 20 8"), Greater); 209 | assert_eq!(human_order("bar 07", "bar 02"), Greater); 210 | assert_eq!(human_order("bar 07", "bar 2"), Greater); 211 | assert_eq!(human_order("bar 7", "bar 02"), Greater); 212 | } 213 | -------------------------------------------------------------------------------- /src/local.rs: -------------------------------------------------------------------------------- 1 | use futures; 2 | use futures::Future; 3 | use std; 4 | use std::io::{Read, Seek}; 5 | use std::sync::Arc; 6 | use std::os::unix::ffi::{OsStrExt, OsStringExt}; 7 | 8 | use crate::error::{ResultExt}; 9 | 10 | #[derive(Debug)] 11 | pub struct Root { 12 | title: String, 13 | path: std::path::PathBuf, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct Object { 18 | root: Arc, 19 | path: std::path::PathBuf, 20 | id: String, 21 | } 22 | 23 | impl Object { 24 | pub fn new(root: Arc, path: std::path::PathBuf) -> crate::Result { 25 | let relpath = &path_remove_prefix(&path, &root.path); 26 | let relpath = relpath.to_string_lossy(); 27 | let id = format!("{}{}", root.title, relpath); 28 | Ok(Object { 29 | root: root.clone(), 30 | path: path, 31 | id: id 32 | }) 33 | } 34 | 35 | pub fn new_boxed(root: Arc, path: std::path::PathBuf) -> crate::Result> { 36 | let r = Self::new(root, path)?; 37 | Ok(Box::new(r)) 38 | } 39 | 40 | pub fn new_root> 41 | (name: String, path: P) -> crate::Result 42 | { 43 | let path = path.into(); 44 | let root = Arc::new(Root { 45 | title: name.clone(), 46 | path: path.clone(), 47 | }); 48 | 49 | Ok(Object { 50 | root: root, 51 | path: path, 52 | id: name, 53 | }) 54 | } 55 | } 56 | 57 | impl crate::Object for Object { 58 | fn id(&self) -> &str { &self.id } 59 | fn parent_id(&self) -> &str { 60 | match self.id.rfind('/') { 61 | Some(i) => &self.id[0..i], 62 | None => { 63 | eprintln!("Can't find parent ID"); 64 | "0" 65 | } 66 | } 67 | } 68 | 69 | fn file_type(&self) -> crate::Type { 70 | if self.is_dir() { return crate::Type::Directory } 71 | 72 | match self.path.extension().and_then(std::ffi::OsStr::to_str) { 73 | Some("avi") => crate::Type::Video, 74 | Some("jpeg") => crate::Type::Image, 75 | Some("jpg") => crate::Type::Image, 76 | Some("m4v") => crate::Type::Video, 77 | Some("mkv") => crate::Type::Video, 78 | Some("mp4") => crate::Type::Video, 79 | Some("png") => crate::Type::Image, 80 | Some("srt") => crate::Type::Subtitles, 81 | _ => crate::Type::Other, 82 | } 83 | } 84 | 85 | fn title(&self) -> String { 86 | self.path.file_name() 87 | .map(|t| t.to_string_lossy().to_string()) 88 | .unwrap_or_else(|| "".to_string()) 89 | } 90 | 91 | fn is_dir(&self) -> bool { self.path.is_dir() } 92 | 93 | fn lookup(&self, id: &str) -> crate::Result> { 94 | debug_assert_eq!(self.path, self.root.path); 95 | 96 | let mut base = self.path.clone(); 97 | let safepath = std::path::Path::new(id) 98 | .iter() 99 | .filter(|c| c != &"..") 100 | .map(|osstr| std::path::Path::new(osstr)); 101 | base.extend(safepath); 102 | 103 | eprintln!("Lookup: {:?}", base); 104 | 105 | Self::new_boxed(self.root.clone(), base) 106 | } 107 | 108 | fn children(&self) -> crate::error::Result>> { 109 | self.path.read_dir() 110 | .chain_err(|| "Getting children of local directory.")? 111 | .map(|result| result 112 | .chain_err(|| "Reading next direntry") 113 | .and_then(|entry| { 114 | Self::new_boxed(self.root.clone(), entry.path()) 115 | })) 116 | .collect() 117 | } 118 | 119 | fn ffmpeg_input(&self, _exec: &crate::Executors) -> crate::Result { 120 | Ok(crate::ffmpeg::Input::Uri(&self.path)) 121 | } 122 | 123 | fn body(&self, _exec: &crate::Executors) -> crate::Result> { 124 | Ok(std::sync::Arc::new(Media{path: self.path.clone()})) 125 | } 126 | } 127 | 128 | #[derive(Debug)] 129 | struct Media { 130 | path: std::path::PathBuf, 131 | } 132 | 133 | impl crate::Media for Media { 134 | fn size(&self) -> crate::MediaSize { 135 | let s = self.path.metadata().map(|m| m.len()).unwrap_or(0); 136 | crate::MediaSize { 137 | available: s, 138 | total: Some(s), 139 | } 140 | } 141 | 142 | fn read_range(&self, start: u64, end: u64) -> crate::ByteStream { 143 | let mut file = match std::fs::File::open(&self.path) { 144 | Ok(f) => f, 145 | Err(e) => { 146 | let e = crate::Error::with_chain(e, format!("Error opening {:?}", self.path)); 147 | return Box::new(futures::future::err(e).into_stream()) 148 | } 149 | }; 150 | if let Err(e) = file.seek(std::io::SeekFrom::Start(start)) { 151 | let e = crate::Error::with_chain(e, format!("Error seeking {:?}", self.path)); 152 | return Box::new(futures::future::err(e).into_stream()) 153 | } 154 | Box::new(crate::ReadStream(file.take(end))) 155 | } 156 | } 157 | 158 | fn path_remove_prefix(full: &std::path::Path, prefix: &std::path::Path) -> std::path::PathBuf { 159 | osstr_remove_prefix(full.as_os_str(), prefix.as_os_str()).into() 160 | } 161 | 162 | fn osstr_remove_prefix(full: &std::ffi::OsStr, prefix: &std::ffi::OsStr) -> std::ffi::OsString { 163 | std::ffi::OsString::from_vec(full.as_bytes()[prefix.as_bytes().len()..].to_vec()) 164 | } 165 | -------------------------------------------------------------------------------- /src/root.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::sync::Arc; 3 | 4 | #[derive(Debug)] 5 | pub struct Root { 6 | items: std::collections::HashMap> 7 | } 8 | 9 | impl Root { 10 | pub fn new() -> Root { 11 | Root { 12 | items: std::collections::HashMap::new(), 13 | } 14 | } 15 | 16 | pub fn is_empty(&self) -> bool { self.items.is_empty() } 17 | 18 | pub fn add(&mut self, object: T) { 19 | self.add_boxed(Box::new(object)) 20 | } 21 | 22 | pub fn add_boxed(&mut self, object: Box) { 23 | let name = object.id().to_string(); 24 | 25 | debug_assert!(name != "0"); 26 | debug_assert!(name != "-1"); 27 | 28 | self.items.insert(name, object); 29 | } 30 | } 31 | 32 | impl crate::Object for Arc { 33 | fn id(&self) -> &str { "0" } 34 | fn parent_id(&self) -> &str { "-1" } 35 | fn file_type(&self) -> crate::Type { crate::Type::Directory } 36 | 37 | fn title(&self) -> String { 38 | "Rusty Media".to_string() 39 | } 40 | 41 | fn is_dir(&self) -> bool { true } 42 | 43 | fn lookup(&self, id: &str) -> crate::Result> { 44 | debug_assert!(id != "-1"); 45 | 46 | if id == "0" { 47 | return Ok(Box::new(self.clone())) 48 | } 49 | 50 | let (first, suffix) = match id.find('/') { 51 | Some(i) => (&id[..i], &id[i+1..]), 52 | None => (id, ""), 53 | }; 54 | 55 | match self.items.get(first) { 56 | Some(obj) => obj.lookup(suffix), 57 | None => return Err( 58 | crate::ErrorKind::NotFound(format!( 59 | "{:?} not found looking for {:?}", first, id)).into()) 60 | } 61 | } 62 | 63 | fn children(&self) -> crate::Result>> { 64 | self.items.values() 65 | .map(|v| v.lookup("")) 66 | .collect() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/xml.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use serde; 3 | use std; 4 | 5 | #[allow(deprecated)] 6 | mod error { 7 | use serde; 8 | use std; 9 | 10 | error_chain!{ 11 | errors { 12 | Unsupported(method: &'static str) 13 | } 14 | 15 | foreign_links { 16 | Io(::std::io::Error); 17 | Xml(::serde_xml_rs::Error); 18 | } 19 | } 20 | 21 | impl serde::ser::Error for Error { 22 | fn custom(msg: T) -> Self { 23 | msg.to_string().into() 24 | } 25 | } 26 | } 27 | pub use crate::xml::error::{Error, ErrorKind, Result}; 28 | 29 | #[derive(Debug,Serialize)] 30 | #[serde(rename="||KXML body node||")] 31 | pub struct Body(pub T); 32 | 33 | pub fn serialize(out: W, val: S) -> Result<()> { 34 | val.serialize(&mut Serializer::new(out)) 35 | } 36 | 37 | fn check_valid_name(_name: &str) -> Result<()> { Ok(()) } 38 | fn check_valid_attr(_name: &str) -> Result<()> { Ok(()) } 39 | 40 | pub struct Serializer { 41 | writer: W, 42 | } 43 | 44 | impl Serializer 45 | { 46 | pub fn new(writer: W) -> Self { 47 | Self { writer: writer } 48 | } 49 | } 50 | 51 | impl<'a, W: Write> serde::ser::Serializer for &'a mut Serializer { 52 | type Ok = (); 53 | type Error = Error; 54 | 55 | type SerializeSeq = serde::ser::Impossible; 56 | type SerializeTuple = serde::ser::Impossible; 57 | type SerializeTupleStruct = serde::ser::Impossible; 58 | type SerializeTupleVariant = serde::ser::Impossible; 59 | type SerializeMap = serde::ser::Impossible; 60 | type SerializeStruct = Struct<'a, W>; 61 | type SerializeStructVariant = serde::ser::Impossible; 62 | 63 | fn serialize_bool(self, v: bool) -> Result { 64 | write!(self.writer, "{}", v)?; Ok(()) 65 | } 66 | 67 | fn serialize_i8(self, v: i8) -> Result { 68 | write!(self.writer, "{}", v)?; Ok(()) 69 | } 70 | 71 | fn serialize_i16(self, v: i16) -> Result { 72 | write!(self.writer, "{}", v)?; Ok(()) 73 | } 74 | 75 | fn serialize_i32(self, v: i32) -> Result { 76 | write!(self.writer, "{}", v)?; Ok(()) 77 | } 78 | 79 | fn serialize_i64(self, v: i64) -> Result { 80 | write!(self.writer, "{}", v)?; Ok(()) 81 | } 82 | 83 | fn serialize_u8(self, v: u8) -> Result { 84 | write!(self.writer, "{}", v)?; Ok(()) 85 | } 86 | 87 | fn serialize_u16(self, v: u16) -> Result { 88 | write!(self.writer, "{}", v)?; Ok(()) 89 | } 90 | 91 | fn serialize_u32(self, v: u32) -> Result { 92 | write!(self.writer, "{}", v)?; Ok(()) 93 | } 94 | 95 | fn serialize_u64(self, v: u64) -> Result { 96 | write!(self.writer, "{}", v)?; Ok(()) 97 | } 98 | 99 | fn serialize_f32(self, v: f32) -> Result { 100 | write!(self.writer, "{}", v)?; Ok(()) 101 | } 102 | 103 | fn serialize_f64(self, v: f64) -> Result { 104 | write!(self.writer, "{}", v)?; Ok(()) 105 | } 106 | 107 | fn serialize_char(self, c: char) -> Result { 108 | match c { 109 | '"' => write!(self.writer, """)?, 110 | '<' => write!(self.writer, "<")?, 111 | '&' => write!(self.writer, "&")?, 112 | c => write!(self.writer, "{}", c)?, 113 | } 114 | Ok(()) 115 | } 116 | 117 | fn serialize_str(self, value: &str) -> Result { 118 | for c in value.chars() { 119 | self.serialize_char(c)? 120 | } 121 | Ok(()) 122 | } 123 | 124 | fn serialize_bytes(self, _value: &[u8]) -> Result { 125 | Err(ErrorKind::Unsupported("serialize_bytes").into()) 126 | } 127 | 128 | fn serialize_none(self) -> Result { 129 | Ok(()) 130 | } 131 | 132 | fn serialize_some(self, value: &T) -> Result { 133 | value.serialize(self) 134 | } 135 | 136 | fn serialize_unit(self) -> Result { 137 | Ok(()) 138 | } 139 | 140 | fn serialize_unit_struct(self, name: &'static str) -> Result { 141 | check_valid_name(name)?; 142 | write!(self.writer, "<{}/>", name)?; 143 | Ok(()) 144 | } 145 | 146 | fn serialize_unit_variant(self, 147 | _name: &'static str, _variant_index: u32, _variant: &'static str) 148 | -> Result 149 | { 150 | Err(ErrorKind::Unsupported("serialize_unit_variant").into()) 151 | } 152 | 153 | fn serialize_newtype_struct 154 | (self, _name: &'static str, value: &T) 155 | -> Result 156 | { 157 | value.serialize(&mut *self)?; 158 | Ok(()) 159 | } 160 | 161 | fn serialize_newtype_variant 162 | (self, name: &'static str, _variant_index: u32, variant: &'static str, value: &T) 163 | -> Result 164 | { 165 | check_valid_name(name)?; 166 | write!(self.writer, "<{}>", variant)?; 167 | value.serialize(&mut *self)?; 168 | write!(self.writer, "", variant)?; 169 | Ok(()) 170 | } 171 | 172 | fn serialize_seq(self, _len: Option) -> Result { 173 | Err(ErrorKind::Unsupported("serialize_seq").into()) 174 | } 175 | 176 | fn serialize_tuple(self, _len: usize) -> Result { 177 | Err(ErrorKind::Unsupported("serialize_tuple").into()) 178 | } 179 | 180 | fn serialize_tuple_struct(self, _name: &'static str, _len: usize) 181 | -> Result 182 | { 183 | Err(ErrorKind::Unsupported("serialize_tuple_struct").into()) 184 | } 185 | 186 | fn serialize_tuple_variant(self, 187 | _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) 188 | -> Result 189 | { 190 | Err(ErrorKind::Unsupported("serialize_tuple_variant").into()) 191 | } 192 | 193 | fn serialize_map(self, _len: Option) -> Result { 194 | Err(ErrorKind::Unsupported("serialize_map").into()) 195 | } 196 | 197 | fn serialize_struct(self, name: &'static str, _len: usize) 198 | -> Result 199 | { 200 | check_valid_name(name)?; 201 | write!(self.writer, "<{}", name)?; 202 | Ok(Struct{ 203 | parent: self, 204 | name: name, 205 | body: false, 206 | }) 207 | } 208 | 209 | fn serialize_struct_variant(self, 210 | _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) 211 | -> Result 212 | { 213 | Err(ErrorKind::Unsupported("serialize_struct_variant").into()) 214 | } 215 | } 216 | 217 | pub struct Seq<'a, 'b: 'a, W: 'b> { 218 | parent: SubSerializer<'a, 'b, W>, 219 | name: &'static str, 220 | } 221 | 222 | impl<'a, 'b, W: Write> serde::ser::SerializeSeq for Seq<'a, 'b, W> { 223 | type Ok = (); 224 | type Error = Error; 225 | 226 | fn serialize_element(&mut self, value: &T) -> Result<()> { 227 | value.serialize(SeqSubSerializer { 228 | name: self.name, 229 | parent: self, 230 | })?; 231 | Ok(()) 232 | } 233 | 234 | fn end(self) -> Result<()> { 235 | Ok(()) 236 | } 237 | } 238 | 239 | pub struct SeqSubSerializer<'a, 'b: 'a, 'c: 'b, W: 'c> { 240 | parent: &'a mut Seq<'b, 'c, W>, 241 | name: &'static str, 242 | } 243 | 244 | impl<'a, 'b, 'c, W: Write> SeqSubSerializer<'a, 'b, 'c, W> { 245 | fn wrapped)->Result<()>>(&mut self, f: F) -> Result<()> { 246 | check_valid_name(self.name)?; 247 | 248 | write!(self.parent.parent.parent.parent.writer, "<{}>", self.name)?; 249 | let r = f(self.root()); 250 | write!(self.parent.parent.parent.parent.writer, "", self.name)?; 251 | r 252 | } 253 | 254 | fn root(&mut self) -> &mut Serializer { 255 | self.parent.parent.parent.parent 256 | } 257 | 258 | fn writer(&mut self) -> &mut W { 259 | &mut self.root().writer 260 | } 261 | } 262 | 263 | impl<'a, 'b, 'c, W: Write> serde::ser::Serializer for SeqSubSerializer<'a, 'b, 'c, W> { 264 | type Ok = (); 265 | type Error = Error; 266 | 267 | type SerializeSeq = Seq<'a, 'b, W>; 268 | type SerializeTuple = serde::ser::Impossible; 269 | type SerializeTupleStruct = serde::ser::Impossible; 270 | type SerializeTupleVariant = serde::ser::Impossible; 271 | type SerializeMap = serde::ser::Impossible; 272 | type SerializeStruct = Struct<'a, W>; 273 | type SerializeStructVariant = serde::ser::Impossible; 274 | 275 | fn serialize_bool(mut self, v: bool) -> Result { 276 | self.wrapped(|p| p.serialize_bool(v)) 277 | } 278 | 279 | fn serialize_i8(mut self, v: i8) -> Result { 280 | self.wrapped(|p| p.serialize_i8(v)) 281 | } 282 | 283 | fn serialize_i16(mut self, v: i16) -> Result { 284 | self.wrapped(|p| p.serialize_i16(v)) 285 | } 286 | 287 | fn serialize_i32(mut self, v: i32) -> Result { 288 | self.wrapped(|p| p.serialize_i32(v)) 289 | } 290 | 291 | fn serialize_i64(mut self, v: i64) -> Result { 292 | self.wrapped(|p| p.serialize_i64(v)) 293 | } 294 | 295 | fn serialize_u8(mut self, v: u8) -> Result { 296 | self.wrapped(|p| p.serialize_u8(v)) 297 | } 298 | 299 | fn serialize_u16(mut self, v: u16) -> Result { 300 | self.wrapped(|p| p.serialize_u16(v)) 301 | } 302 | 303 | fn serialize_u32(mut self, v: u32) -> Result { 304 | self.wrapped(|p| p.serialize_u32(v)) 305 | } 306 | 307 | fn serialize_u64(mut self, v: u64) -> Result { 308 | self.wrapped(|p| p.serialize_u64(v)) 309 | } 310 | 311 | fn serialize_f32(mut self, v: f32) -> Result { 312 | self.wrapped(|p| p.serialize_f32(v)) 313 | } 314 | 315 | fn serialize_f64(mut self, v: f64) -> Result { 316 | self.wrapped(|p| p.serialize_f64(v)) 317 | } 318 | 319 | fn serialize_char(mut self, c: char) -> Result { 320 | self.wrapped(|p| p.serialize_char(c)) 321 | } 322 | 323 | fn serialize_str(mut self, value: &str) -> Result { 324 | self.wrapped(|p| p.serialize_str(value)) 325 | } 326 | 327 | fn serialize_bytes(mut self, value: &[u8]) -> Result { 328 | self.wrapped(|p| p.serialize_bytes(value)) 329 | } 330 | 331 | fn serialize_none(self) -> Result { 332 | Ok(()) 333 | } 334 | 335 | fn serialize_some(self, _value: &T) -> Result { 336 | Err(ErrorKind::Unsupported("serialize_some inside sequence").into()) 337 | } 338 | 339 | fn serialize_unit(self) -> Result { 340 | Ok(()) 341 | } 342 | 343 | fn serialize_unit_struct(mut self, name: &'static str) -> Result { 344 | self.root().serialize_unit_struct(name) 345 | } 346 | 347 | fn serialize_unit_variant(mut self, 348 | name: &'static str, variant_index: u32, variant: &'static str) 349 | -> Result 350 | { 351 | self.root().serialize_unit_variant(name, variant_index, variant) 352 | } 353 | 354 | fn serialize_newtype_struct 355 | (mut self, name: &'static str, value: &T) 356 | -> Result 357 | { 358 | self.root().serialize_newtype_struct(name, value) 359 | } 360 | 361 | fn serialize_newtype_variant 362 | (mut self, name: &'static str, variant_index: u32, variant: &'static str, value: &T) 363 | -> Result 364 | { 365 | self.root().serialize_newtype_variant(name, variant_index, variant, value) 366 | } 367 | 368 | fn serialize_seq(self, _len: Option) -> Result { 369 | Err(ErrorKind::Unsupported("serialize_seq").into()) 370 | } 371 | 372 | fn serialize_tuple(mut self, len: usize) -> Result { 373 | self.root().serialize_tuple(len) 374 | } 375 | 376 | fn serialize_tuple_struct(mut self, 377 | name: &'static str, len: usize) 378 | -> Result 379 | { 380 | self.root().serialize_tuple_struct(name, len) 381 | } 382 | 383 | fn serialize_tuple_variant(mut self, 384 | name: &'static str, 385 | variant_index: u32, variant: &'static str, 386 | len: usize) 387 | -> Result 388 | { 389 | self.root().serialize_tuple_variant(name, variant_index, variant, len) 390 | } 391 | 392 | fn serialize_map(mut self, len: Option) -> Result { 393 | self.root().serialize_map(len) 394 | } 395 | 396 | fn serialize_struct(mut self, name: &'static str, _len: usize) 397 | -> Result 398 | { 399 | check_valid_name(name)?; 400 | write!(self.writer(), "<{}", name)?; 401 | Ok(Struct { 402 | parent: self.parent.parent.parent.parent, 403 | name: name, 404 | body: false, 405 | }) 406 | } 407 | 408 | fn serialize_struct_variant(mut self, 409 | name: &'static str, variant_index: u32, variant: &'static str, len: usize) 410 | -> Result 411 | { 412 | self.root().serialize_struct_variant(name, variant_index, variant, len) 413 | } 414 | } 415 | 416 | pub struct Struct<'a, W: 'a> 417 | { 418 | parent: &'a mut Serializer, 419 | name: &'static str, 420 | body: bool, 421 | } 422 | 423 | impl<'a, W: std::io::Write> Struct<'a, W> { 424 | fn enter_body(&mut self) -> Result<()> { 425 | if !self.body { 426 | self.body = true; 427 | write!(self.parent.writer, ">")?; 428 | } 429 | Ok(()) 430 | } 431 | } 432 | 433 | impl<'a, W: Write> serde::ser::SerializeStruct for Struct<'a, W> { 434 | type Ok = (); 435 | type Error = Error; 436 | 437 | fn serialize_field 438 | (&mut self, key: &'static str, value: &T) -> Result<()> 439 | { 440 | value.serialize(SubSerializer{parent: self, name: key})?; 441 | Ok(()) 442 | } 443 | 444 | fn end(self) -> Result { 445 | if self.body { 446 | write!(self.parent.writer, "", self.name)?; 447 | } else { 448 | write!(self.parent.writer, "/>")?; 449 | } 450 | Ok(()) 451 | } 452 | } 453 | 454 | pub struct SubSerializer<'a, 'b: 'a, W: 'b> { 455 | parent: &'a mut Struct<'b, W>, 456 | name: &'static str, 457 | } 458 | 459 | impl<'a, 'b, W: Write> SubSerializer<'a, 'b, W> { 460 | fn attr)->Result<()>>(&mut self, f: F) -> Result<()> { 461 | check_valid_attr(self.name)?; 462 | 463 | if self.parent.body { return self.child(f) } 464 | 465 | write!(self.parent.parent.writer, " {}=\"", self.name)?; 466 | let r = f(self.parent.parent); 467 | write!(self.parent.parent.writer, "\"")?; 468 | r 469 | } 470 | 471 | fn child)->Result<()>>(&mut self, f: F) -> Result<()> { 472 | check_valid_name(self.name)?; 473 | 474 | self.parent.enter_body()?; 475 | write!(self.parent.parent.writer, "<{}>", self.name)?; 476 | let r = f(self.parent.parent); 477 | write!(self.parent.parent.writer, "", self.name)?; 478 | r 479 | } 480 | } 481 | 482 | impl<'a, 'b, W: Write> serde::ser::Serializer for SubSerializer<'a, 'b, W> { 483 | type Ok = (); 484 | type Error = Error; 485 | 486 | type SerializeSeq = Seq<'a, 'b, W>; 487 | type SerializeTuple = serde::ser::Impossible; 488 | type SerializeTupleStruct = serde::ser::Impossible; 489 | type SerializeTupleVariant = serde::ser::Impossible; 490 | type SerializeMap = serde::ser::Impossible; 491 | type SerializeStruct = Struct<'a, W>; 492 | type SerializeStructVariant = serde::ser::Impossible; 493 | 494 | fn serialize_bool(mut self, v: bool) -> Result { 495 | self.attr(|p| p.serialize_bool(v)) 496 | } 497 | 498 | fn serialize_i8(mut self, v: i8) -> Result { 499 | self.attr(|p| p.serialize_i8(v)) 500 | } 501 | 502 | fn serialize_i16(mut self, v: i16) -> Result { 503 | self.attr(|p| p.serialize_i16(v)) 504 | } 505 | 506 | fn serialize_i32(mut self, v: i32) -> Result { 507 | self.attr(|p| p.serialize_i32(v)) 508 | } 509 | 510 | fn serialize_i64(mut self, v: i64) -> Result { 511 | self.attr(|p| p.serialize_i64(v)) 512 | } 513 | 514 | fn serialize_u8(mut self, v: u8) -> Result { 515 | self.attr(|p| p.serialize_u8(v)) 516 | } 517 | 518 | fn serialize_u16(mut self, v: u16) -> Result { 519 | self.attr(|p| p.serialize_u16(v)) 520 | } 521 | 522 | fn serialize_u32(mut self, v: u32) -> Result { 523 | self.attr(|p| p.serialize_u32(v)) 524 | } 525 | 526 | fn serialize_u64(mut self, v: u64) -> Result { 527 | self.attr(|p| p.serialize_u64(v)) 528 | } 529 | 530 | fn serialize_f32(mut self, v: f32) -> Result { 531 | self.attr(|p| p.serialize_f32(v)) 532 | } 533 | 534 | fn serialize_f64(mut self, v: f64) -> Result { 535 | self.attr(|p| p.serialize_f64(v)) 536 | } 537 | 538 | fn serialize_char(mut self, c: char) -> Result { 539 | self.attr(|p| p.serialize_char(c)) 540 | } 541 | 542 | fn serialize_str(mut self, value: &str) -> Result { 543 | self.attr(|p| p.serialize_str(value)) 544 | } 545 | 546 | fn serialize_bytes(mut self, value: &[u8]) -> Result { 547 | self.child(|p| p.serialize_bytes(value)) 548 | } 549 | 550 | fn serialize_none(self) -> Result { 551 | Ok(()) 552 | } 553 | 554 | fn serialize_some(mut self, value: &T) -> Result { 555 | self.attr(|p| p.serialize_some(value)) 556 | } 557 | 558 | fn serialize_unit(self) -> Result { 559 | Ok(()) 560 | } 561 | 562 | fn serialize_unit_struct(mut self, name: &'static str) -> Result { 563 | self.child(|p| p.serialize_unit_struct(name)) 564 | } 565 | 566 | fn serialize_unit_variant(mut self, 567 | name: &'static str, variant_index: u32, variant: &'static str) 568 | -> Result 569 | { 570 | self.attr(|p| p.serialize_unit_variant(name, variant_index, variant)) 571 | } 572 | 573 | fn serialize_newtype_struct 574 | (mut self, name: &'static str, value: &T) 575 | -> Result 576 | { 577 | if !self.parent.body && name == "||KXML body node||" { 578 | self.parent.enter_body()?; 579 | value.serialize(&mut *self.parent.parent) 580 | } else { 581 | self.child(|p| value.serialize(p)) 582 | } 583 | } 584 | 585 | fn serialize_newtype_variant 586 | (mut self, name: &'static str, variant_index: u32, variant: &'static str, value: &T) 587 | -> Result 588 | { 589 | self.attr(|p| p.serialize_newtype_variant(name, variant_index, variant, value)) 590 | } 591 | 592 | fn serialize_seq(self, _len: Option) -> Result { 593 | self.parent.enter_body()?; 594 | Ok(Seq{ 595 | name: self.name, 596 | parent: self, 597 | }) 598 | } 599 | 600 | fn serialize_tuple(self, len: usize) -> Result { 601 | self.parent.enter_body()?; 602 | self.parent.parent.serialize_tuple(len) 603 | } 604 | 605 | fn serialize_tuple_struct(self, 606 | name: &'static str, len: usize) 607 | -> Result 608 | { 609 | self.parent.enter_body()?; 610 | self.parent.parent.serialize_tuple_struct(name, len) 611 | } 612 | 613 | fn serialize_tuple_variant(self, 614 | name: &'static str, 615 | variant_index: u32, variant: &'static str, 616 | len: usize) 617 | -> Result 618 | { 619 | self.parent.enter_body()?; 620 | self.parent.parent.serialize_tuple_variant(name, variant_index, variant, len) 621 | } 622 | 623 | fn serialize_map(self, len: Option) -> Result { 624 | self.parent.enter_body()?; 625 | self.parent.parent.serialize_map(len) 626 | } 627 | 628 | fn serialize_struct(self, name: &'static str, len: usize) 629 | -> Result 630 | { 631 | self.parent.enter_body()?; 632 | self.parent.parent.serialize_struct(name, len) 633 | } 634 | 635 | fn serialize_struct_variant(self, 636 | name: &'static str, variant_index: u32, variant: &'static str, len: usize) 637 | -> Result 638 | { 639 | self.parent.enter_body()?; 640 | self.parent.parent.serialize_struct_variant(name, variant_index, variant, len) 641 | } 642 | } 643 | 644 | #[cfg(test)] 645 | mod tests { 646 | use super::*; 647 | 648 | #[derive(Serialize)] 649 | struct Simple { 650 | a: String, 651 | b: i64, 652 | val: Body, 653 | } 654 | 655 | fn to_xml(obj: T) -> String { 656 | let mut buf = Vec::new(); 657 | serialize(&mut buf, obj).unwrap(); 658 | String::from_utf8(buf).unwrap() 659 | } 660 | 661 | #[test] 662 | fn test_simple() { 663 | let obj = Simple{ 664 | a: "foo".to_string(), 665 | b: 17, 666 | val: Body("contents".to_string()), 667 | }; 668 | assert_eq!(to_xml(obj), r#"contents"#); 669 | } 670 | 671 | #[derive(Serialize)] 672 | struct NewType(String); 673 | 674 | #[derive(Serialize)] 675 | struct NoAttr { 676 | new_type: NewType, 677 | } 678 | 679 | #[test] 680 | fn test_new_type() { 681 | let obj = NoAttr{ 682 | new_type: NewType("foobar".to_string()), 683 | }; 684 | assert_eq!(to_xml(obj), r#"foobar"#); 685 | } 686 | 687 | #[derive(Serialize)] 688 | struct Seq { 689 | a: Vec, 690 | n: Vec, 691 | } 692 | 693 | #[test] 694 | fn test_seq() { 695 | let obj = Seq{ 696 | a: vec![], 697 | n: vec![1, 2, 3], 698 | }; 699 | assert_eq!(to_xml(obj), r#"123"#); 700 | } 701 | 702 | #[derive(Serialize)] 703 | struct SeqNT { 704 | a: Vec>, 705 | n: Vec>, 706 | } 707 | 708 | #[test] 709 | fn test_seq_type() { 710 | let obj = SeqNT{ 711 | a: vec![], 712 | n: vec![ 713 | Simple{a: "foo".to_string(), b: 42, val: Body(3)}, 714 | Simple{a: "bar".to_string(), b: 2, val: Body(14)}, 715 | ], 716 | }; 717 | assert_eq!(to_xml(obj), r#"314"#); 718 | } 719 | 720 | #[derive(Serialize)] 721 | struct Complex { 722 | a: String, 723 | b: Simple, 724 | c: String, 725 | } 726 | 727 | #[test] 728 | fn test_complex() { 729 | let obj = Complex{ 730 | a: "a b c".to_string(), 731 | b: Simple{ 732 | a: r#""#.to_string(), 733 | b: 42, 734 | val: Body(Simple{ 735 | a: "such nesting".to_string(), 736 | b: 9001, 737 | val: Body(3918), 738 | }), 739 | }, 740 | c: "finally".to_string(), 741 | }; 742 | assert_eq!(to_xml(obj), r#"3918finally"#); 743 | } 744 | } 745 | --------------------------------------------------------------------------------