Compare commits
1205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76b4ba3c5e | |||
| 292d4b585b | |||
| 0dd08bbbb4 | |||
| ac8572fd2d | |||
| b300a4e8d4 | |||
| b3fc995977 | |||
| 7f5a070406 | |||
| 7ccef6068b | |||
| 06aac98996 | |||
| 0f18482ba6 | |||
| ffd7835ab5 | |||
| fb6b103164 | |||
| 2de763eef9 | |||
| 46ab046c34 | |||
| cf54a0e8be | |||
| e73cf46e14 | |||
| 39c3b2f044 | |||
| 7a592a0f15 | |||
| 1fad64cd59 | |||
| 86e5dc4607 | |||
| e98e4766f7 | |||
| 6e59671cf6 | |||
| 86a50367b2 | |||
| 0212c0b85f | |||
| 89d365122c | |||
| 9c0a8350d6 | |||
| 818d21718c | |||
| 0b551c0cd4 | |||
| d8c3b26fa6 | |||
| adde41c533 | |||
| 3925e1dced | |||
| c7d1206dcb | |||
| f1567c64a5 | |||
| d900cc5c53 | |||
| 5c456231b1 | |||
| abde7c0242 | |||
| 762d17c3df | |||
| e006b7f1e1 | |||
| db10f380d1 | |||
| 60f8aa540f | |||
| 7b64c696f7 | |||
| 0367dc21f8 | |||
| 7a172a9051 | |||
| 5bb44c36e1 | |||
| 3ad34de808 | |||
| 4025b2c6a1 | |||
| 6b00cc1a06 | |||
| 80dccdef22 | |||
| b0ec5c527d | |||
| 5d073713b6 | |||
| d93853375e | |||
| 26872eb2c2 | |||
| 119041a425 | |||
| 9c85566da7 | |||
| 575b96d03a | |||
| 823f648955 | |||
| 7203f463f4 | |||
| 3651755984 | |||
| 180fb857ed | |||
| cb09459ad1 | |||
| fd1bdad0e2 | |||
| b2ba3a5922 | |||
| 749023bf02 | |||
| dcc8c7b92a | |||
| f63d591b08 | |||
| 387192610f | |||
| 9aec21f6b5 | |||
| e183a14933 | |||
| 72edf5b555 | |||
| 7fd75bf071 | |||
| d64005df40 | |||
| c523e576c4 | |||
| d1a6e8c99e | |||
| 3e1aab6b29 | |||
| 7a88e7fa34 | |||
| cabb9aed31 | |||
| 61f2c41b76 | |||
| 3249fbb715 | |||
| 2d797adc08 | |||
| c069496b27 | |||
| c1e8087b11 | |||
| 427d52cf9a | |||
| 06d420f743 | |||
| 05a1510b31 | |||
| 1b01a6ece1 | |||
| 3577ef6814 | |||
| 75b7c9ac36 | |||
| 4f63bfd616 | |||
| 4fae733eef | |||
| 36a34f911e | |||
| e4d5e5bf91 | |||
| 547a6121b0 | |||
| bae5e3795e | |||
| 3b935d8fd0 | |||
| 15b83f8b55 | |||
| 56a879e148 | |||
| fc99f1f563 | |||
| 4b257b7b4a | |||
| 172a81b22c | |||
| 5c7ca6b363 | |||
| 93b213b59f | |||
| 3b669521da | |||
| 05f01b2c45 | |||
| f36b898a4d | |||
| fa9b0d9da1 | |||
| 1c2fd4ef84 | |||
| be3b05df68 | |||
| 601a50e430 | |||
| 03d60438e2 | |||
| 9b6720ce80 | |||
| b9e0a0b1ac | |||
| d22fd964c2 | |||
| 188ad540ee | |||
| 12f24674fb | |||
| ac9dfa8e93 | |||
| b5b7db1f32 | |||
| ab7e697f30 | |||
| b086f6d330 | |||
| a73d87229a | |||
| e9fb46b0cd | |||
| 0ca0d9d01e | |||
| 1156b8f100 | |||
| c9820d30ed | |||
| b8fd6a7ec3 | |||
| 2c2a55abab | |||
| f7e9b0a27f | |||
| 6b33089274 | |||
| 229bd598b5 | |||
| 260dd37bd5 | |||
| 54d33c06c7 | |||
| a7358171cf | |||
| 14f563d7c2 | |||
| 44a3e0a97b | |||
| e19474d22f | |||
| 208c8b2b9d | |||
| 0562b4cf6f | |||
| 8ba86fe272 | |||
| fad4594062 | |||
| 74864832ca | |||
| 9c95eba5a1 | |||
| 6f17a0b7af | |||
| f8fed49225 | |||
| 8b438b68cc | |||
| 8f2ec3b197 | |||
| adc2a68a98 | |||
| 39faadc9dc | |||
| c4c7ced948 | |||
| 6fa1c5e214 | |||
| 831ce4da13 | |||
| 24a5bebabe | |||
| 42f761582f | |||
| cb32bf1c4f | |||
| 318b9949f2 | |||
| 221b831bb2 | |||
| cc8609d0aa | |||
| 03f32978c0 | |||
| 8fe7b6551f | |||
| 2919cf28ea | |||
| e131078673 | |||
| e1b445d133 | |||
| 177ca3ccba | |||
| c63a53959c | |||
| e8b13ea8a9 | |||
| 2f2010ce59 | |||
| e6a1407786 | |||
| 3eca8b9c98 | |||
| 0803a916aa | |||
| 675754bc5c | |||
| 2638759b42 | |||
| b4d722b9f2 | |||
| baa30b41de | |||
| 299193e2bd | |||
| fde9dd2a61 | |||
| 5552e8cbd7 | |||
| 2aecc3fa1b | |||
| e2577ea1c7 | |||
| 11346c000e | |||
| ee637e8fec | |||
| cb393c215a | |||
| c4eac8b3c6 | |||
| 852e5e29ca | |||
| 907b3185c2 | |||
| 6478cd2ea4 | |||
| 1616f24cee | |||
| 160718efe2 | |||
| a266b54ad6 | |||
| fde024e7b8 | |||
| 3a600687ea | |||
| 67f38006f8 | |||
| 627bde4b72 | |||
| 742102b541 | |||
| 5251a5ca79 | |||
| ba06ef57a9 | |||
| 0d7adc3382 | |||
| 1c4755f26a | |||
| 93a668de8b | |||
| 1d5b11f7f6 | |||
| b1c7b88b5b | |||
| bedc2be525 | |||
| a321ca5b39 | |||
| 14fbd845c2 | |||
| e4f893a0eb | |||
| a30876c3ff | |||
| 0aaaaab7a0 | |||
| 21ff63c371 | |||
| 3deeca43a1 | |||
| 503ab56a59 | |||
| b711ece829 | |||
| efaa668158 | |||
| a2fd82137c | |||
| efc4805233 | |||
| dc194015c2 | |||
| 5dd332b63d | |||
| 874f02631b | |||
| 24775f4988 | |||
| f255788383 | |||
| ba0997a8db | |||
| 7d4bb28d18 | |||
| d2c69e7733 | |||
| 143a408342 | |||
| 6be68a3656 | |||
| b744ebb3b3 | |||
| fb37f4a1d5 | |||
| 5945a638ff | |||
| e87d7cfaf3 | |||
| 2e66361482 | |||
| 15b054be94 | |||
| 1dfd5000ff | |||
| 4f974122f8 | |||
| dc1782d049 | |||
| 588db95e2a | |||
| 1bce9a89b6 | |||
| d95c04787c | |||
| d99278da1d | |||
| fdc24ec2ee | |||
| 3fd489d1cb | |||
| 058e077422 | |||
| d2b8672c1c | |||
| c8474f734c | |||
| 5626709de5 | |||
| f70a63d575 | |||
| 24fac4b191 | |||
| 99aea27723 | |||
| 94e25e898a | |||
| 4bb0c2d053 | |||
| f48eaccc51 | |||
| e7c05dcfaf | |||
| 82b0015b30 | |||
| 78441730a7 | |||
| 5ea1299030 | |||
| 817d9b3389 | |||
| 25a590e8cd | |||
| 6766e04dd6 | |||
| 28d707604b | |||
| f1f4b1a184 | |||
| f41a608f11 | |||
| 1bc9b42c57 | |||
| 4b37caa63a | |||
| 237db8ae92 | |||
| 9e3f4ac954 | |||
| 8d3b5c851d | |||
| dc662beefe | |||
| 7a27dd8a1b | |||
| af3c4e0ce8 | |||
| e4158d9703 | |||
| 8d230e67a2 | |||
| daf29233e6 | |||
| 15f7a2078b | |||
| 26b4fb21fe | |||
| 617f7e8b5b | |||
| be5a819621 | |||
| c0e71cdea7 | |||
| b89c26ab57 | |||
| 499c3dbdca | |||
| c699c546e4 | |||
| bb04545068 | |||
| 11504321b5 | |||
| f7f3627bb1 | |||
| f4a99beb16 | |||
| 38b0967044 | |||
| 14575f0a06 | |||
| 685e9e4f58 | |||
| 535e0d2553 | |||
| d62a3f0e57 | |||
| ca12763adc | |||
| 2fdf7f1098 | |||
| e5b163b86a | |||
| f8202384a6 | |||
| 35da4ffa3e | |||
| 4762e2fc6c | |||
| 8f8d8e3d0b | |||
| 29623d36e8 | |||
| 443ccbf426 | |||
| 838e5d05d5 | |||
| 7243689215 | |||
| 5eaf5465a5 | |||
| cb492fe3c7 | |||
| 003238ef5e | |||
| 1ad67e223f | |||
| 9556561a77 | |||
| 97d6c2db6b | |||
| d521e5c36a | |||
| c987203f5a | |||
| a96fa7a5c7 | |||
| 5c366e4697 | |||
| 4f592e9c33 | |||
| b5f3f28f41 | |||
| f8ad6655fb | |||
| 25917db07a | |||
| 9b2cf8501f | |||
| 52c1b45feb | |||
| 6032f31aa6 | |||
| 2a58c643d2 | |||
| aafa11b48b | |||
| a5cb069f26 | |||
| 1cad93c276 | |||
| 0116190050 | |||
| cf7290343f | |||
| 9fff4b060e | |||
| 6c58ac5c55 | |||
| cf0460c7f9 | |||
| 779f0c637f | |||
| dac22887cf | |||
| da01376294 | |||
| 74f88b3c50 | |||
| ff93b162ee | |||
| 89f592687e | |||
| 5e6e52d397 | |||
| 20cbbd4f4c | |||
| b637cdabae | |||
| 0f9fd38053 | |||
| 20f0a8a1c4 | |||
| 0e6417c9fa | |||
| cc4bf60092 | |||
| 358665d944 | |||
| d05440d267 | |||
| ca8e0796d9 | |||
| 3949048dde | |||
| bf083c1429 | |||
| 8c15ac7fab | |||
| 487ba2b928 | |||
| 2fc1f39061 | |||
| 9e1adec5e0 | |||
| 43748d439a | |||
| 998017a76d | |||
| 5c9d041423 | |||
| e4644599af | |||
| e6ef6088ff | |||
| b4f446fe42 | |||
| da7abea9aa | |||
| 9faf035413 | |||
| 8b1bd7ac6b | |||
| 7faf3ab04a | |||
| a8014b3942 | |||
| 85b277b872 | |||
| a1efe4caca | |||
| c6111a7fb5 | |||
| 80145cd5a3 | |||
| d39decf918 | |||
| 5517d912bf | |||
| fbbcba95d3 | |||
| 8270d0cc91 | |||
| ddd1a282ea | |||
| 773af2eef9 | |||
| e8028de4d7 | |||
| 595a68b822 | |||
| 18bc6c69d5 | |||
| d56725a43d | |||
| b3aaa9216d | |||
| ea41155c6b | |||
| b337df889e | |||
| 00c92e88c5 | |||
| 8c94187c75 | |||
| cd504e6ee5 | |||
| 6e29b8ad6f | |||
| 0d9cdb98f2 | |||
| c5d9480313 | |||
| 67e32a2c44 | |||
| dadfc09c01 | |||
| 886c88576c | |||
| 4ba33eb3e1 | |||
| 59518ec87e | |||
| 953f077f9d | |||
| cf2f5b2026 | |||
| 135c16c721 | |||
| e94fa791a9 | |||
| 5d9fa06675 | |||
| b294b802a8 | |||
| 7bb504d491 | |||
| b1bd062709 | |||
| 0d3b399b61 | |||
| 0f8e2600e3 | |||
| 3d71e9b509 | |||
| 881f385a61 | |||
| e31851d25e | |||
| aff72323c6 | |||
| a31a396780 | |||
| 8555c5fae1 | |||
| da71354e82 | |||
| 3d0e7f6cb6 | |||
| c7d12cc481 | |||
| 440177a43d | |||
| 953eb6e906 | |||
| fc55b44e4b | |||
| f2a56fcd80 | |||
| 0dbc294836 | |||
| 3b49c1bac0 | |||
| 82f041d050 | |||
| 4f17ed961e | |||
| ba551b48e1 | |||
| 5fc4ad6c4e | |||
| b117b364f2 | |||
| 3603c64fa6 | |||
| d405a400aa | |||
| 54c6f02ebb | |||
| b3f9d04501 | |||
| ab7ef9c9cb | |||
| 0f897589ed | |||
| cea6a2217e | |||
| dc3eceec6a | |||
| a6819ffd1d | |||
| de9ee10abc | |||
| 43989800ba | |||
| 1fd827fa77 | |||
| 3b9766f58c | |||
| 2c8df07753 | |||
| 08421311b9 | |||
| dc68fa2c8b | |||
| ff373621b3 | |||
| 272691aacd | |||
| 46623bc985 | |||
| 3462147195 | |||
| 268fe5efe8 | |||
| 0e0c70f782 | |||
| acf52b9b55 | |||
| 7587fe56d8 | |||
| ab34a95142 | |||
| 9e9952d81f | |||
| 4f2d601f02 | |||
| e722801f80 | |||
| 6cf9204219 | |||
| b719ca5a33 | |||
| 29ae7b9aeb | |||
| 45c13141f9 | |||
| 9079b3e8da | |||
| 0ee40cb636 | |||
| 62105c249f | |||
| a629f209b9 | |||
| ecb8d8991b | |||
| 2e4c0fee77 | |||
| d961278b3d | |||
| db2765aae5 | |||
| 7898af4ceb | |||
| edfd2d0cd9 | |||
| d00ad28efd | |||
| 02b864e41b | |||
| e88ebd748d | |||
| b6954f3bfd | |||
| 27c29bbb4c | |||
| 30842656a7 | |||
| e5248cfaa2 | |||
| c30ba14c1f | |||
| cec22ad10d | |||
| c08c0272b5 | |||
| c52e8a2027 | |||
| 1fd8164756 | |||
| b3b08fecb2 | |||
| 3c16c34386 | |||
| 394d96246b | |||
| fc439cc02a | |||
| 18e6cd429a | |||
| 1b2621cd30 | |||
| 084854d71d | |||
| c2ab0ad641 | |||
| dbc2cda9d4 | |||
| 65fa61a636 | |||
| 04784b4e43 | |||
| 7eb94e9674 | |||
| 0a5550b13d | |||
| 067200be83 | |||
| 2baddc1709 | |||
| 5554595255 | |||
| 65a0ca2f32 | |||
| d4a42a1680 | |||
| af099a9b53 | |||
| 6ebac0e500 | |||
| b25c08e79a | |||
| 8fd47dd658 | |||
| 687255aa31 | |||
| 5c1fbfca03 | |||
| b136f14084 | |||
| d5fe82314e | |||
| 253e72f667 | |||
| aa30227545 | |||
| 06834fefef | |||
| 2946ae1009 | |||
| 4b8f735cb8 | |||
| c230d49d7c | |||
| 20e2cb2d86 | |||
| b70083f3b3 | |||
| 74ef389879 | |||
| d1d0ddf62f | |||
| d78a55adb4 | |||
| 77ecb0a70c | |||
| cb07822aa3 | |||
| defc5f1cf9 | |||
| 6f99ed3955 | |||
| 247a5e12ab | |||
| 855911333a | |||
| 127bfc7d3b | |||
| 7919dd81da | |||
| e3a3aebbf6 | |||
| c05b7c382a | |||
| d7aaa1489c | |||
| aee3360841 | |||
| 256f44a870 | |||
| f631b2ecdc | |||
| a623dd1921 | |||
| b768ad978e | |||
| 6b6776042c | |||
| 4adaeee054 | |||
| a2d9420139 | |||
| 3431a85adf | |||
| 430483c7a1 | |||
| 3ba709fcc3 | |||
| ce1fa42f9d | |||
| 08ac5b6ec3 | |||
| a6f9ceedd8 | |||
| e1da046960 | |||
| 09dfa071dc | |||
| a756026962 | |||
| 75260a960b | |||
| b1f764984f | |||
| 69ee49bee6 | |||
| 2aed2d164b | |||
| 2d011b781e | |||
| 1bf3a26a61 | |||
| c5760b3a40 | |||
| 4de8bf3295 | |||
| 4a9b349c04 | |||
| 87c8f19f19 | |||
| 0ef52c739e | |||
| 0a9a6b949c | |||
| 71f81c5fb0 | |||
| c22068d6b1 | |||
| 012e4a3e63 | |||
| 5d85076ad5 | |||
| 35d7e97258 | |||
| bbc1a86b57 | |||
| 89537abdc4 | |||
| abfc41f382 | |||
| 84ac6ea12a | |||
| cd83d80f2b | |||
| d9c159122f | |||
| 12721eb7dd | |||
| b8a09339cd | |||
| 3634d367c1 | |||
| c1daea0ec7 | |||
| 2bc378a9c3 | |||
| e8196fed7c | |||
| 477702fbb9 | |||
| 2216a271bb | |||
| 91cd7d2f6b | |||
| 4e681d25d9 | |||
| 06cc2891de | |||
| 682432f55a | |||
| 7c4cb5ec58 | |||
| 1df73fdeba | |||
| 21ba8a0593 | |||
| 08ed52eb72 | |||
| 99700e1b95 | |||
| 4e0be95368 | |||
| c8a59c8343 | |||
| 2b2a2ed708 | |||
| a827a2fbcc | |||
| f97f6b8061 | |||
| 61d7bed181 | |||
| 844ea9d77e | |||
| 71479286e9 | |||
| a9337033c1 | |||
| a0e61ee67f | |||
| 28c2ac528d | |||
| 08d3a5d2fe | |||
| 696a36b4a5 | |||
| 5fb4922c6f | |||
| 3738f95871 | |||
| 6797bdec04 | |||
| 764c6d5461 | |||
| 6973182ade | |||
| f62af07381 | |||
| a8da122fb3 | |||
| da842d5a73 | |||
| 46c37c0ae8 | |||
| 4ad71766fc | |||
| 9d60cc8c66 | |||
| 0794a3edf4 | |||
| 4a6243096a | |||
| 10cbdc8e8e | |||
| 4886fc467c | |||
| 8e2827cc39 | |||
| 395d2e4917 | |||
| d31f5229da | |||
| d6622818dc | |||
| ba684d6d3a | |||
| 90c04a4640 | |||
| 06a1e1f166 | |||
| 7bea62adbf | |||
| 31ad366aa9 | |||
| 10f33b0273 | |||
| 8f703f4744 | |||
| eaa1ac8013 | |||
| ca3bb308b3 | |||
| e790f024c2 | |||
| 250e0c75df | |||
| c3532b92f7 | |||
| 0539861dc0 | |||
| deedf2a36c | |||
| cde7375049 | |||
| e89c8dbf76 | |||
| c7fedfbca3 | |||
| b031a55a59 | |||
| f520831025 | |||
| 2f0719a883 | |||
| 34ab99caf1 | |||
| 65f9141764 | |||
| b2f94c0e40 | |||
| ba12d96d23 | |||
| bb807554e2 | |||
| 56d1050bac | |||
| a6e1b23eb0 | |||
| 5f6ab836de | |||
| a9f790e101 | |||
| f888c4b641 | |||
| 65bff1181a | |||
| efb14f0b58 | |||
| 75a4f04cce | |||
| 007fe34363 | |||
| a1d50a6d05 | |||
| d7df2ac60c | |||
| a7e31ef31f | |||
| dcae3daf43 | |||
| 941ee53e7a | |||
| eae0c28e6d | |||
| 7b4e16bb8f | |||
| 21950382b9 | |||
| c72393c970 | |||
| f96d1e9e69 | |||
| 1af9c047fb | |||
| 2d19bfa7fb | |||
| f5f7de64de | |||
| 754e76a61b | |||
| 09505e0988 | |||
| 67e206fa0f | |||
| 608fd873de | |||
| 05a4161fd3 | |||
| 05040351dc | |||
| d75324afc9 | |||
| 38fcd31917 | |||
| 2f33e99006 | |||
| 79d9b8e693 | |||
| 91e3b3b491 | |||
| 816d8a0216 | |||
| e37ccd6ec0 | |||
| f2e2065fd4 | |||
| f27477da26 | |||
| c032a015a4 | |||
| 11e81b035a | |||
| 891a03c038 | |||
| 31aa6c486c | |||
| 1d9133a5e8 | |||
| 3375ca5a8c | |||
| 1596e93cc1 | |||
| 1a540f1cf7 | |||
| 05f5cd1bde | |||
| f0fbd0232c | |||
| 72dd609109 | |||
| 13e94a8b1b | |||
| f911fda34f | |||
| d9e7883fb5 | |||
| 2d396cb589 | |||
| b56031b9f3 | |||
| fd86b141e2 | |||
| 41df562419 | |||
| 427a3e9b08 | |||
| 3001f21f8d | |||
| 48b50a22a4 | |||
| 5e8496bc59 | |||
| 2dbc1153e8 | |||
| b07c146fd9 | |||
| 73a9079ee2 | |||
| cc2edc4d66 | |||
| 9aaeb19418 | |||
| be86ea2982 | |||
| d0fbd260d5 | |||
| b69b19ddce | |||
| b647959ec4 | |||
| 6c0e2e249d | |||
| 8ed5d154b7 | |||
| 524357bfa8 | |||
| 157a86d0f1 | |||
| ca37ca291f | |||
| 93e535d3a1 | |||
| 7343e07fe5 | |||
| a332092769 | |||
| 6be8624373 | |||
| 740c95d557 | |||
| 31607fbb37 | |||
| 122cba2aa7 | |||
| b44a70ff36 | |||
| 1b03f078b9 | |||
| 4c8f8cf64c | |||
| 2696ac5eac | |||
| 6566c91360 | |||
| d623f616fa | |||
| fc8de8aead | |||
| 6480cfcc87 | |||
| e36d424b5f | |||
| f7e7d72688 | |||
| 3474568ce2 | |||
| 89f2dfd78a | |||
| 2c4c56d6d6 | |||
| 087d4153ae | |||
| 86772bd7bd | |||
| 4e2841f0d7 | |||
| 26fe4040bf | |||
| fb7f29de18 | |||
| d18252542d | |||
| 030fcaac15 | |||
| 7ebb043249 | |||
| 598b1c9966 | |||
| cf36aaef2b | |||
| f30af9cd5f | |||
| 5024c52c60 | |||
| e971b62dab | |||
| 4ffec8ad26 | |||
| 69cc5814d8 | |||
| 414a318a0d | |||
| 758e35baba | |||
| 09921a00aa | |||
| 16a3bb2df4 | |||
| 2c684c0231 | |||
| 949a1cce21 | |||
| 0e787f4e9f | |||
| bd31091648 | |||
| 4cd8903abc | |||
| 7b01de8db1 | |||
| f73d8a44df | |||
| 752d65d020 | |||
| c2667f99f4 | |||
| c7e7aa0a61 | |||
| fc79659549 | |||
| e81a6adb95 | |||
| 80aedcd7e2 | |||
| fc28c9237c | |||
| b04200ca68 | |||
| 67fbbcfd12 | |||
| e3dbed1c1a | |||
| 480a6607e2 | |||
| 4a30fee40d | |||
| 7f0fa74467 | |||
| 3d1d27230d | |||
| 9df1506794 | |||
| 2d0844b5db | |||
| 1ee016c997 | |||
| 221389089c | |||
| 59031ee3b8 | |||
| d2e408539e | |||
| a8140cc74b | |||
| d5f080fefb | |||
| 35ff8ec713 | |||
| 764c901cd7 | |||
| f85d45d17f | |||
| 022bd1b8b4 | |||
| 31305af7ff | |||
| dd929d796f | |||
| 11fde02035 | |||
| 3d85014edc | |||
| d125fbc43d | |||
| 427688a0a0 | |||
| 21cc38fcf4 | |||
| ee7bf86e0f | |||
| f8bb7a7ff4 | |||
| 6d3e6d800f | |||
| 208585d3f6 | |||
| bf00dedc7f | |||
| bf43dc00bb | |||
| e64aaebbac | |||
| 1d443d2ff5 | |||
| 6507a9b2ee | |||
| 4cc4d57a78 | |||
| 605bf0e8c3 | |||
| 89e2af6b57 | |||
| b864e9da2a | |||
| f10382a696 | |||
| 297f25cfc2 | |||
| afdef163ea | |||
| 6751560228 | |||
| 2012eb5e11 | |||
| 58068e249a | |||
| 3ddbda9aca | |||
| 1fef8bf266 | |||
| 3c4043199a | |||
| 0220309ea7 | |||
| 497b4f834f | |||
| 715ddbb3b0 | |||
| 0ff038f0a2 | |||
| 7322485a6d | |||
| f77af5f6e4 | |||
| 2324c408ba | |||
| 037cf9e1ee | |||
| 5268553e7f | |||
| 5f7524aca2 | |||
| 86f5221f96 | |||
| ecf85a73ec | |||
| 132ce3ece1 | |||
| 25deca9579 | |||
| 93d15cd969 | |||
| 4b91c9bf66 | |||
| 16adaa64c9 | |||
| 93fe7957fb | |||
| fb96763f65 | |||
| 63342f89d4 | |||
| 47095e6cf8 | |||
| f2062ba19b | |||
| 7f42dcc60f | |||
| 369785c184 | |||
| 58061a7295 | |||
| c6c398179a | |||
| e498e47109 | |||
| 40089e2fd9 | |||
| 045a0419f6 | |||
| 80fb3dc2cb | |||
| fcee64df09 | |||
| 757e0194b9 | |||
| 4625592a83 | |||
| e3b844b5aa | |||
| decaffed86 | |||
| 669c48cc8b | |||
| d1fe24ac92 | |||
| 3fa43a1e08 | |||
| 028a98d2c1 | |||
| 0f70a81db3 | |||
| d014a1dc1d | |||
| e04a152ed0 | |||
| 8a680ae324 | |||
| 75996476a7 | |||
| 620d7b560d | |||
| ab9859ecef | |||
| 37ea50a572 | |||
| 5cb3f04389 | |||
| cedf50eeec | |||
| c5fce647de | |||
| 10a0d6bdba | |||
| 0abe57e930 | |||
| 65c0b486aa | |||
| ae0ecc1b10 | |||
| dfff68b2f4 | |||
| e4c5d51860 | |||
| 8c609bc9ce | |||
| f486f5966f | |||
| e91dd14b31 | |||
| 8a53b60912 | |||
| a4b52b7264 | |||
| 5ae0ef0527 | |||
| dfb1d704ed | |||
| ffd6dac03a | |||
| 8f4895e8a5 | |||
| a9302b8b53 | |||
| c31b0b311b | |||
| bba049c987 | |||
| 21c2acc520 | |||
| 394d23a73a | |||
| 276428878e | |||
| 04db0369d4 | |||
| 4ef7eda593 | |||
| 2242c8d793 | |||
| bef665be36 | |||
| bf9f342c13 | |||
| a994edda04 | |||
| 9f6bca3658 | |||
| 2617a49b78 | |||
| 656bffbbb2 | |||
| 8104e739d5 | |||
| 632968dc81 | |||
| ed69e690b8 | |||
| 20775116f7 | |||
| 446f9bf81f | |||
| 913e80fd55 | |||
| 9a4a01fb0e | |||
| df92df7bd6 | |||
| 78742b8e4c | |||
| 942c400c19 | |||
| 5587dd8bfb | |||
| 8d1fc3f984 | |||
| 318e0d4a24 | |||
| bc00617df7 | |||
| 2c26517172 | |||
| 51648a2a21 | |||
| 12df381495 | |||
| 17ca23d73b | |||
| 41df94115f | |||
| 0250204f14 | |||
| 0419deeec4 | |||
| 9020f68ce1 | |||
| 557c4d065d | |||
| 6cc1bd544a | |||
| aba22b92bc | |||
| 040871459b | |||
| 6263b73d9c | |||
| 1cc433eabc | |||
| 5982cdad90 | |||
| d450169964 | |||
| e823c11b46 | |||
| c328417d29 | |||
| c4192f9f8b | |||
| 5368112d90 | |||
| ed07ed44ae | |||
| 9993dafe54 | |||
| 6bdb5debd2 | |||
| 65403747df | |||
| 594b271383 | |||
| 27f9981142 | |||
| 3d3d879b99 | |||
| 53ed6e5e6e | |||
| d0e4e0f600 | |||
| 311caf3fc3 | |||
| 7c13021def | |||
| c8788d83fb | |||
| 331260cf80 | |||
| 02041fa6f4 | |||
| 79d5a53aea | |||
| f1086a72bf | |||
| 95d001a053 | |||
| c84729a4f4 | |||
| 4951ec9814 | |||
| edba82db37 | |||
| 1ac282b12e | |||
| 588a13377c | |||
| fb1de5a921 | |||
| 71ed840944 | |||
| dab8e15052 | |||
| e47d121985 | |||
| c0b95dbc79 | |||
| 647adc51c8 | |||
| 7668ecf9c9 | |||
| 3769453541 | |||
| 8c2af50170 | |||
| 1e97e4d462 | |||
| 27e7e792b3 | |||
| 7429c07c05 | |||
| 91afaaf8fe | |||
| 333bd2107a | |||
| 25d3d0b731 | |||
| 77addb2283 | |||
| 9651a78b0c | |||
| 9eba31185a | |||
| aea7edf0fa | |||
| 42d4834f63 | |||
| ba78f6a0ff | |||
| feb5b62ad4 | |||
| 5ccba7cf8a | |||
| 848cfc32cc | |||
| 5510e8ebee | |||
| 9c02e99e35 | |||
| 148db8b81a | |||
| e342b7bc71 | |||
| f1c93ae618 | |||
| 4e229ad86b | |||
| e88f079da6 | |||
| 4e8de67aca | |||
| a85488cd20 | |||
| 5a7cca9d1b | |||
| d6c6f3c10c | |||
| 38f52a139e | |||
| 194da8416b | |||
| f3e7bc0573 | |||
| c3fa299acc | |||
| 852460b991 | |||
| 33b67a357f | |||
| 4db31acff9 | |||
| 968e282c90 | |||
| 1731bf7372 | |||
| 0804bed66d | |||
| a0606b5730 | |||
| 9ba6227db4 | |||
| 9bab93262e | |||
| 0f9006c81f | |||
| b3438559cc | |||
| 4df3333b71 | |||
| 5262e50fee | |||
| 547d0ecf58 | |||
| 6e07eab247 | |||
| 96b3d37caf | |||
| 4a3e42e779 | |||
| 67f399dccf | |||
| eb95b025d4 | |||
| de45cfdd8c | |||
| 47966793c0 | |||
| 0c49079c16 | |||
| d82d6b6aef | |||
| 7ae526da8b | |||
| e79d44d9f1 | |||
| fb7de2f966 | |||
| 1eb26bdf08 | |||
| cbd0e71c07 | |||
| 2db1826ed8 | |||
| cf4e64f430 | |||
| 9b39404b9a | |||
| e7e7d96f51 | |||
| 785fa76ac6 | |||
| 6c967c5982 | |||
| affb8c8673 | |||
| 74aa38acd7 | |||
| d4ded281aa | |||
| c597f0de35 | |||
| 3965f90236 | |||
| 8af4417f8f | |||
| 4a5c526ccc | |||
| 320707d44c | |||
| 2690ac299b | |||
| 268b099ca8 | |||
| 33171a58b5 | |||
| 73c1cdb32a | |||
| 13bfe5093e | |||
| 99bed9a9c3 | |||
| 8ef41020d9 | |||
| fca226bdfd | |||
| bca7a26ffd | |||
| 60df855b26 | |||
| 44ed037e73 | |||
| 50ce7572b4 | |||
| 74695428fe | |||
| 139807719c | |||
| 1e2050f106 | |||
| a23f04623e | |||
| 1f30a50f4a | |||
| 098de6b050 | |||
| a0d31a49a0 | |||
| 6bbb968b57 | |||
| 6c6e9ca9f2 | |||
| 89d4ce309d | |||
| b43317c5e1 | |||
| 5917e91447 | |||
| 9753a76905 | |||
| 24b76dbed8 | |||
| dab25a0eeb | |||
| 53beebc774 | |||
| ba003e06ef | |||
| 1d631540ac | |||
| 3ed9d32f68 | |||
| 3ecfd1fdd1 | |||
| 24574d4964 | |||
| 512ab8c6aa | |||
| 029bafdf9c | |||
| 0849c4a447 | |||
| 5238ba5d8f | |||
| 6b1a64652f | |||
| 9961c8c459 | |||
| 18418b6892 | |||
| d4bc7b5a5a | |||
| e2d0f67077 | |||
| 5ef63e738d | |||
| 7bbcb455c0 | |||
| 2ad54cd09d | |||
| 7c9803e135 | |||
| 480999e8e6 | |||
| ef17ec700b | |||
| 4b34b0cfea | |||
| 60fc53306d | |||
| c6188e26af | |||
| 18717d103a | |||
| 6834b64922 | |||
| 56e3063342 | |||
| 781cca0c82 | |||
| eab35890dc | |||
| 967d5deeb7 | |||
| 51ba28bd65 | |||
| a94aeb5c87 | |||
| 13f2783a8e | |||
| 7554f63551 | |||
| 25253cf961 | |||
| 77a5617774 | |||
| 395072239d | |||
| e2f6ecaef6 | |||
| 48e7274d37 | |||
| 9f3aa2cead | |||
| bb50363812 | |||
| 9a38e4dc8a | |||
| c607c95e64 | |||
| 18ae107ce4 | |||
| 3f80b16ffa | |||
| 72aa364aa5 | |||
| 0d5fbcb031 | |||
| a35ea49c99 | |||
| 4811e76860 | |||
| 64a4f259a2 | |||
| bdfd042d70 | |||
| a993420676 | |||
| fb6a901374 | |||
| 25c2267a89 | |||
| 69ea15f73a | |||
| b7ae044e65 | |||
| d8d237f6f2 | |||
| 18e1d03a89 | |||
| 3947f2315d | |||
| cb9e50b2ea | |||
| e620bb9512 | |||
| 8b04fe7633 | |||
| 2cb747651b | |||
| 46897aab4f | |||
| 892787cb1a | |||
| be77e14db9 | |||
| 34af7501fa | |||
| 183ef34422 | |||
| 637936cb9f | |||
| fc59b0ab77 | |||
| d8b312674d | |||
| ebae7229c1 | |||
| 3df8cda110 | |||
| 43cf11aa35 | |||
| 21f0a7e020 | |||
| cb6d2cbd2d | |||
| ce51025e7c | |||
| 5a054e5150 | |||
| 98f003f71a | |||
| b9efcace79 | |||
| 1aaa8de1f9 | |||
| 5e2e190f3e | |||
| 828434058f | |||
| dfbf5fc9fa | |||
| 8029cd3ebb | |||
| e970d7a6aa | |||
| 350e795640 | |||
| f5bda652c4 | |||
| e8189cd0f6 | |||
| 18231fedef | |||
| 935c7231eb | |||
| da2c7db0df | |||
| 83d98ac92d | |||
| b997b12d27 | |||
| 6887e790c8 | |||
| 373a1f72bf | |||
| 0d43b06042 | |||
| ced9868357 | |||
| 23c867f946 | |||
| 42c233c74e | |||
| 1f8e74f3a8 | |||
| d6835f8dd6 | |||
| 86940e96d5 | |||
| 75d90c8e4c | |||
| ecbc0538f6 | |||
| 2a26760911 | |||
| 19b6808602 | |||
| c2202be0f8 | |||
| 9729ae52a3 | |||
| 7cc69f30c4 | |||
| e33706ab25 | |||
| 4018b7e2d5 | |||
| 8425d76198 | |||
| 9bf009c4f8 | |||
| a6bde0943e | |||
| db1ebe2559 | |||
| dbebc4774f | |||
| 0fbc4545d1 | |||
| 5d4bef5478 | |||
| cac3a3e945 | |||
| bca569da42 | |||
| a82303ccd1 | |||
| a2dd2ddd55 | |||
| 5835c4b21d | |||
| d8e6808d77 | |||
| 9e58e31de0 | |||
| 77602aff88 | |||
| 1ffd443d5a | |||
| 1dc5a624a7 | |||
| 3f878d63a5 | |||
| a5882ae162 | |||
| c74028d08d | |||
| 3f13851be5 | |||
| ea446fd4a3 | |||
| 585b5201f1 | |||
| 8d98885cda | |||
| da9418c1b2 | |||
| 9dba930a85 | |||
| 7722cb3ffa | |||
| 3e6819c718 | |||
| 41fb1e5106 | |||
| 64d5ec12e2 | |||
| d409623086 | |||
| 9af28607c9 | |||
| 81a8ebde22 | |||
| 8055b70ab1 | |||
| 05bbf71b6d | |||
| 8bf88f4cb2 | |||
| edf5ee0cc4 | |||
| 86089ec03a | |||
| 29d6783471 | |||
| 236e21efcb | |||
| 30ec203eff | |||
| a6a4cd5667 | |||
| 3a5bbcf2a8 | |||
| 449074e73f | |||
| 2fc97212a7 | |||
| 4519ce26e2 | |||
| 49d9649b8e |
@@ -0,0 +1,371 @@
|
|||||||
|
name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: TestProjects
|
||||||
|
value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj'
|
||||||
|
- name: RestoreBuildProjects
|
||||||
|
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||||
|
|
||||||
|
pr:
|
||||||
|
autoCancel: true
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
batch: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: main_build
|
||||||
|
displayName: Main Build
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
release:
|
||||||
|
BuildConfiguration: Release
|
||||||
|
debug:
|
||||||
|
BuildConfiguration: Debug
|
||||||
|
maxParallel: 2
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
submodules: true
|
||||||
|
persistCredentials: true
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Check out web"
|
||||||
|
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Check out web (PR)"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||||
|
inputs:
|
||||||
|
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||||
|
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Install Node.js'
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
versionSpec: '10.x'
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Build Web UI"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
script: yarn install
|
||||||
|
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: Copy the web UI
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
||||||
|
contents: '**'
|
||||||
|
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
cleanTargetFolder: true # Optional
|
||||||
|
overWrite: true # Optional
|
||||||
|
flattenFolders: false # Optional
|
||||||
|
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: Publish
|
||||||
|
inputs:
|
||||||
|
command: publish
|
||||||
|
publishWebProjects: false
|
||||||
|
projects: '$(RestoreBuildProjects)'
|
||||||
|
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
|
||||||
|
zipAfterPublish: false
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: 'Publish Artifact Naming'
|
||||||
|
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||||
|
artifactName: 'Jellyfin.Naming'
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: 'Publish Artifact Controller'
|
||||||
|
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||||
|
artifactName: 'Jellyfin.Controller'
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: 'Publish Artifact Model'
|
||||||
|
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||||
|
artifactName: 'Jellyfin.Model'
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: 'Publish Artifact Common'
|
||||||
|
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
||||||
|
artifactName: 'Jellyfin.Common'
|
||||||
|
|
||||||
|
- job: main_test
|
||||||
|
displayName: Main Test
|
||||||
|
pool:
|
||||||
|
vmImage: windows-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
submodules: true
|
||||||
|
persistCredentials: false
|
||||||
|
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: Build
|
||||||
|
inputs:
|
||||||
|
command: build
|
||||||
|
publishWebProjects: false
|
||||||
|
projects: '$(TestProjects)'
|
||||||
|
arguments: '--configuration $(BuildConfiguration)'
|
||||||
|
zipAfterPublish: false
|
||||||
|
|
||||||
|
- task: VisualStudioTestPlatformInstaller@1
|
||||||
|
inputs:
|
||||||
|
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
|
||||||
|
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
|
||||||
|
|
||||||
|
- task: VSTest@2
|
||||||
|
inputs:
|
||||||
|
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
|
||||||
|
testAssemblyVer2: | # Required when testSelector == TestAssemblies
|
||||||
|
**\bin\$(BuildConfiguration)\**\*test*.dll
|
||||||
|
!**\obj\**
|
||||||
|
!**\xunit.runner.visualstudio.testadapter.dll
|
||||||
|
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
|
||||||
|
#testPlan: # Required when testSelector == TestPlan
|
||||||
|
#testSuite: # Required when testSelector == TestPlan
|
||||||
|
#testConfiguration: # Required when testSelector == TestPlan
|
||||||
|
#tcmTestRun: '$(test.RunId)' # Optional
|
||||||
|
searchFolder: '$(System.DefaultWorkingDirectory)'
|
||||||
|
#testFiltercriteria: # Optional
|
||||||
|
#runOnlyImpactedTests: False # Optional
|
||||||
|
#runAllTestsAfterXBuilds: '50' # Optional
|
||||||
|
#uiTests: false # Optional
|
||||||
|
#vstestLocationMethod: 'version' # Optional. Options: version, location
|
||||||
|
#vsTestVersion: 'latest' # Optional. Options: latest, 16.0, 15.0, 14.0, toolsInstaller
|
||||||
|
#vstestLocation: # Optional
|
||||||
|
#runSettingsFile: # Optional
|
||||||
|
#overrideTestrunParameters: # Optional
|
||||||
|
#pathtoCustomTestAdapters: # Optional
|
||||||
|
runInParallel: True # Optional
|
||||||
|
runTestsInIsolation: True # Optional
|
||||||
|
codeCoverageEnabled: True # Optional
|
||||||
|
#otherConsoleOptions: # Optional
|
||||||
|
#distributionBatchType: 'basedOnTestCases' # Optional. Options: basedOnTestCases, basedOnExecutionTime, basedOnAssembly
|
||||||
|
#batchingBasedOnAgentsOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customBatchSize
|
||||||
|
#customBatchSizeValue: '10' # Required when distributionBatchType == BasedOnTestCases && BatchingBasedOnAgentsOption == CustomBatchSize
|
||||||
|
#batchingBasedOnExecutionTimeOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customTimeBatchSize
|
||||||
|
#customRunTimePerBatchValue: '60' # Required when distributionBatchType == BasedOnExecutionTime && BatchingBasedOnExecutionTimeOption == CustomTimeBatchSize
|
||||||
|
#dontDistribute: False # Optional
|
||||||
|
#testRunTitle: # Optional
|
||||||
|
#platform: # Optional
|
||||||
|
configuration: 'Debug' # Optional
|
||||||
|
publishRunAttachments: true # Optional
|
||||||
|
#diagnosticsEnabled: false # Optional
|
||||||
|
#collectDumpOn: 'onAbortOnly' # Optional. Options: onAbortOnly, always, never
|
||||||
|
#rerunFailedTests: False # Optional
|
||||||
|
#rerunType: 'basedOnTestFailurePercentage' # Optional. Options: basedOnTestFailurePercentage, basedOnTestFailureCount
|
||||||
|
#rerunFailedThreshold: '30' # Optional
|
||||||
|
#rerunFailedTestCasesMaxLimit: '5' # Optional
|
||||||
|
#rerunMaxAttempts: '3' # Optional
|
||||||
|
|
||||||
|
# - task: PublishTestResults@2
|
||||||
|
# inputs:
|
||||||
|
# testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit, cTest
|
||||||
|
# testResultsFiles: '**/*.trx'
|
||||||
|
# #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
|
||||||
|
# mergeTestResults: true # Optional
|
||||||
|
# #failTaskOnFailedTests: false # Optional
|
||||||
|
# #testRunTitle: # Optional
|
||||||
|
# #buildPlatform: # Optional
|
||||||
|
# #buildConfiguration: # Optional
|
||||||
|
# #publishRunAttachments: true # Optional
|
||||||
|
|
||||||
|
- job: main_build_win
|
||||||
|
displayName: Main Build Windows
|
||||||
|
pool:
|
||||||
|
vmImage: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
release:
|
||||||
|
BuildConfiguration: Release
|
||||||
|
maxParallel: 2
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
submodules: true
|
||||||
|
persistCredentials: true
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Check out web"
|
||||||
|
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Check out web (PR)"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||||
|
inputs:
|
||||||
|
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||||
|
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: 'Install Node.js'
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
versionSpec: '10.x'
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Build Web UI"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
script: yarn install
|
||||||
|
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: Copy the web UI
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
||||||
|
contents: '**'
|
||||||
|
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
cleanTargetFolder: true # Optional
|
||||||
|
overWrite: true # Optional
|
||||||
|
flattenFolders: false # Optional
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: Clone the UX repository
|
||||||
|
inputs:
|
||||||
|
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
||||||
|
|
||||||
|
- task: PowerShell@2
|
||||||
|
displayName: Build the NSIS Installer
|
||||||
|
inputs:
|
||||||
|
targetType: 'filePath' # Optional. Options: filePath, inline
|
||||||
|
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
|
||||||
|
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
||||||
|
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
|
||||||
|
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
|
||||||
|
#failOnStderr: false # Optional
|
||||||
|
#ignoreLASTEXITCODE: false # Optional
|
||||||
|
#pwsh: false # Optional
|
||||||
|
workingDirectory: $(Build.SourcesDirectory) # Optional
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: Copy the NSIS Installer to the artifact directory
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
|
||||||
|
contents: 'jellyfin*.exe'
|
||||||
|
targetFolder: $(System.ArtifactsDirectory)/setup
|
||||||
|
cleanTargetFolder: true # Optional
|
||||||
|
overWrite: true # Optional
|
||||||
|
flattenFolders: true # Optional
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: 'Publish Setup Artifact'
|
||||||
|
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(build.artifactstagingdirectory)/setup'
|
||||||
|
artifactName: 'Jellyfin Server Setup'
|
||||||
|
|
||||||
|
- job: dotnet_compat
|
||||||
|
displayName: Compatibility Check
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
dependsOn: main_build
|
||||||
|
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds)
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
Naming:
|
||||||
|
NugetPackageName: Jellyfin.Naming
|
||||||
|
AssemblyFileName: Emby.Naming.dll
|
||||||
|
Controller:
|
||||||
|
NugetPackageName: Jellyfin.Controller
|
||||||
|
AssemblyFileName: MediaBrowser.Controller.dll
|
||||||
|
Model:
|
||||||
|
NugetPackageName: Jellyfin.Model
|
||||||
|
AssemblyFileName: MediaBrowser.Model.dll
|
||||||
|
Common:
|
||||||
|
NugetPackageName: Jellyfin.Common
|
||||||
|
AssemblyFileName: MediaBrowser.Common.dll
|
||||||
|
maxParallel: 2
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download the New Assembly Build Artifact
|
||||||
|
inputs:
|
||||||
|
source: 'current' # Options: current, specific
|
||||||
|
#preferTriggeringPipeline: false # Optional
|
||||||
|
#tags: # Optional
|
||||||
|
artifact: '$(NugetPackageName)' # Optional
|
||||||
|
#patterns: '**' # Optional
|
||||||
|
path: '$(System.ArtifactsDirectory)/new-artifacts'
|
||||||
|
#project: # Required when source == Specific
|
||||||
|
#pipeline: # Required when source == Specific
|
||||||
|
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||||
|
#runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
|
||||||
|
#runId: # Required when source == Specific && runVersion == Specific
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: Copy New Assembly to new-release folder
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
|
||||||
|
contents: '**/*.dll'
|
||||||
|
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||||
|
cleanTargetFolder: true # Optional
|
||||||
|
overWrite: true # Optional
|
||||||
|
flattenFolders: true # Optional
|
||||||
|
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download the Reference Assembly Build Artifact
|
||||||
|
inputs:
|
||||||
|
source: 'specific' # Options: current, specific
|
||||||
|
#preferTriggeringPipeline: false # Optional
|
||||||
|
#tags: # Optional
|
||||||
|
artifact: '$(NugetPackageName)' # Optional
|
||||||
|
#patterns: '**' # Optional
|
||||||
|
path: '$(System.ArtifactsDirectory)/current-artifacts'
|
||||||
|
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||||
|
pipeline: '$(System.DefinitionId)' # Required when source == Specific
|
||||||
|
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||||
|
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
|
||||||
|
#runId: # Required when source == Specific && runVersion == Specific
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: Copy Reference Assembly to current-release folder
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
|
||||||
|
contents: '**/*.dll'
|
||||||
|
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||||
|
cleanTargetFolder: true # Optional
|
||||||
|
overWrite: true # Optional
|
||||||
|
flattenFolders: true # Optional
|
||||||
|
|
||||||
|
- task: DownloadGitHubRelease@0
|
||||||
|
displayName: Download ABI compatibility check tool from GitHub
|
||||||
|
inputs:
|
||||||
|
connection: Jellyfin Release Download
|
||||||
|
userRepository: EraYaN/dotnet-compatibility
|
||||||
|
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
|
||||||
|
#version: # Required when defaultVersionType != Latest
|
||||||
|
itemPattern: '**-ci.zip' # Optional
|
||||||
|
downloadPath: '$(System.ArtifactsDirectory)'
|
||||||
|
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
displayName: Extract ABI compatibility check tool
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
|
||||||
|
destinationFolder: $(System.ArtifactsDirectory)/tools
|
||||||
|
cleanDestinationFolder: true
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: Execute ABI compatibility check tool
|
||||||
|
inputs:
|
||||||
|
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines'
|
||||||
|
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
||||||
|
#failOnStderr: false # Optional
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Nightly-$(date:yyyyMMdd).$(rev:r)
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: Version
|
||||||
|
value: '1.0.0'
|
||||||
|
|
||||||
|
trigger: none
|
||||||
|
pr: none
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: publish_artifacts_nightly
|
||||||
|
displayName: Publish Artifacts Nightly
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download the Windows Setup Artifact
|
||||||
|
inputs:
|
||||||
|
source: 'specific' # Options: current, specific
|
||||||
|
artifact: 'Jellyfin Server Setup' # Optional
|
||||||
|
path: '$(System.ArtifactsDirectory)/win-installer'
|
||||||
|
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||||
|
pipelineId: 1 # Required when source == Specific
|
||||||
|
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||||
|
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Create Drop directory'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: 'Jellyfin Build Server'
|
||||||
|
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
|
||||||
|
|
||||||
|
- task: CopyFilesOverSSH@0
|
||||||
|
displayName: 'Copy the Windows Setup to the Repo'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: 'Jellyfin Build Server'
|
||||||
|
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
||||||
|
contents: 'jellyfin_*.exe'
|
||||||
|
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Clean up SCP symlink'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: 'Jellyfin Build Server'
|
||||||
|
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: Version
|
||||||
|
value: '1.0.0'
|
||||||
|
- name: UsedRunId
|
||||||
|
value: 0
|
||||||
|
|
||||||
|
trigger: none
|
||||||
|
pr: none
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: publish_artifacts_release
|
||||||
|
displayName: Publish Artifacts Release
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download the Windows Setup Artifact
|
||||||
|
inputs:
|
||||||
|
source: 'specific' # Options: current, specific
|
||||||
|
artifact: 'Jellyfin Server Setup' # Optional
|
||||||
|
path: '$(System.ArtifactsDirectory)/win-installer'
|
||||||
|
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||||
|
pipelineId: 1 # Required when source == Specific
|
||||||
|
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||||
|
runId: $(UsedRunId)
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Create Drop directory'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: 'Jellyfin Build Server'
|
||||||
|
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
|
||||||
|
|
||||||
|
- task: CopyFilesOverSSH@0
|
||||||
|
displayName: 'Copy the Windows Setup to the Repo'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: 'Jellyfin Build Server'
|
||||||
|
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
||||||
|
contents: 'jellyfin_*.exe'
|
||||||
|
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Clean up SCP symlink'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: 'Jellyfin Build Server'
|
||||||
|
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'
|
||||||
+59
-8
@@ -1,8 +1,59 @@
|
|||||||
srpm:
|
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
|
||||||
dnf -y install git
|
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
|
||||||
git submodule update --init --recursive
|
|
||||||
cd deployment/fedora-package-x64; \
|
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
|
||||||
./create_tarball.sh; \
|
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
||||||
rpmbuild -bs pkg-src/jellyfin.spec \
|
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|
||||||
--define "_sourcedir $$PWD/pkg-src/" \
|
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
||||||
--define "_srcrpmdir $(outdir)"
|
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
|
||||||
|
|
||||||
|
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
|
||||||
|
cd deployment/fedora-package-x64; \
|
||||||
|
SOURCE_DIR=../.. \
|
||||||
|
WORKDIR="$${PWD}"; \
|
||||||
|
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
|
||||||
|
pkg_src_dir="$${WORKDIR}/pkg-src"; \
|
||||||
|
GNU_TAR=1; \
|
||||||
|
tar \
|
||||||
|
--transform "s,^\.,jellyfin-$(VERSION)," \
|
||||||
|
--exclude='.git*' \
|
||||||
|
--exclude='**/.git' \
|
||||||
|
--exclude='**/.hg' \
|
||||||
|
--exclude='**/.vs' \
|
||||||
|
--exclude='**/.vscode' \
|
||||||
|
--exclude='deployment' \
|
||||||
|
--exclude='**/bin' \
|
||||||
|
--exclude='**/obj' \
|
||||||
|
--exclude='**/.nuget' \
|
||||||
|
--exclude='*.deb' \
|
||||||
|
--exclude='*.rpm' \
|
||||||
|
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
|
||||||
|
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
|
||||||
|
if [ $${GNU_TAR} -eq 0 ]; then \
|
||||||
|
package_temporary_dir="$$(mktemp -d)"; \
|
||||||
|
mkdir -p "$${package_temporary_dir}/jellyfin"; \
|
||||||
|
tar \
|
||||||
|
--exclude='.git*' \
|
||||||
|
--exclude='**/.git' \
|
||||||
|
--exclude='**/.hg' \
|
||||||
|
--exclude='**/.vs' \
|
||||||
|
--exclude='**/.vscode' \
|
||||||
|
--exclude='deployment' \
|
||||||
|
--exclude='**/bin' \
|
||||||
|
--exclude='**/obj' \
|
||||||
|
--exclude='**/.nuget' \
|
||||||
|
--exclude='*.deb' \
|
||||||
|
--exclude='*.rpm' \
|
||||||
|
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
||||||
|
-C $${SOURCE_DIR} ./; \
|
||||||
|
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
|
||||||
|
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
||||||
|
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
|
||||||
|
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
|
||||||
|
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
|
||||||
|
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
|
||||||
|
rm -rf $${package_temporary_dir}; \
|
||||||
|
fi; \
|
||||||
|
rpmbuild -bs pkg-src/jellyfin.spec \
|
||||||
|
--define "_sourcedir $$PWD/pkg-src/" \
|
||||||
|
--define "_srcrpmdir $(outdir)"
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ README.md
|
|||||||
deployment/*/dist
|
deployment/*/dist
|
||||||
deployment/*/pkg-dist
|
deployment/*/pkg-dist
|
||||||
deployment/collect-dist/
|
deployment/collect-dist/
|
||||||
|
ci/
|
||||||
|
|||||||
+20
-2
@@ -1,12 +1,30 @@
|
|||||||
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: build
|
name: build-debug
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: submodules
|
- name: submodules
|
||||||
image: docker:git
|
image: docker:git
|
||||||
commands:
|
commands:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: microsoft/dotnet:2-sdk
|
image: microsoft/dotnet:2-sdk
|
||||||
commands:
|
commands:
|
||||||
- dotnet publish --configuration release --output /release Jellyfin.Server
|
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: build-release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: submodules
|
||||||
|
image: docker:git
|
||||||
|
commands:
|
||||||
|
- git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
image: microsoft/dotnet:2-sdk
|
||||||
|
commands:
|
||||||
|
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
|
||||||
|
|
||||||
|
|||||||
+75
-9
@@ -15,6 +15,10 @@ insert_final_newline = true
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
max_line_length = null
|
max_line_length = null
|
||||||
|
|
||||||
|
# YAML indentation
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
# XML indentation
|
# XML indentation
|
||||||
[*.{csproj,xml}]
|
[*.{csproj,xml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
@@ -55,15 +59,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
|
|||||||
###############################
|
###############################
|
||||||
# Naming Conventions #
|
# Naming Conventions #
|
||||||
###############################
|
###############################
|
||||||
# Style Definitions
|
# Style Definitions (From Roslyn)
|
||||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
|
||||||
# Use PascalCase for constant fields
|
# Non-private static fields are PascalCase
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
||||||
|
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||||
|
|
||||||
|
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||||
|
|
||||||
|
# Constants are PascalCase
|
||||||
|
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
||||||
|
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
|
||||||
|
|
||||||
|
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
||||||
|
dotnet_naming_symbols.constants.required_modifiers = const
|
||||||
|
|
||||||
|
dotnet_naming_style.constant_style.capitalization = pascal_case
|
||||||
|
|
||||||
|
# Static fields are camelCase and start with s_
|
||||||
|
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
|
||||||
|
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
|
||||||
|
|
||||||
|
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.static_fields.required_modifiers = static
|
||||||
|
|
||||||
|
dotnet_naming_style.static_field_style.capitalization = camel_case
|
||||||
|
dotnet_naming_style.static_field_style.required_prefix = _
|
||||||
|
|
||||||
|
# Instance fields are camelCase and start with _
|
||||||
|
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
||||||
|
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
||||||
|
|
||||||
|
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
||||||
|
|
||||||
|
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||||
|
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||||
|
|
||||||
|
# Locals and parameters are camelCase
|
||||||
|
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
||||||
|
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
||||||
|
|
||||||
|
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
||||||
|
|
||||||
|
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||||
|
|
||||||
|
# Local functions are PascalCase
|
||||||
|
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
||||||
|
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||||
|
|
||||||
|
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
||||||
|
|
||||||
|
# By default, name items with PascalCase
|
||||||
|
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
||||||
|
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
||||||
|
|
||||||
|
dotnet_naming_symbols.all_members.applicable_kinds = *
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# C# Coding Conventions #
|
# C# Coding Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
|
||||||
CONTRIBUTORS.md merge=union
|
CONTRIBUTORS.md merge=union
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ assignees: ''
|
|||||||
- OS: [e.g. Docker, Debian, Windows]
|
- OS: [e.g. Docker, Debian, Windows]
|
||||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||||
- Jellyfin Version: [e.g. 10.0.1]
|
- Jellyfin Version: [e.g. 10.0.1]
|
||||||
|
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
<!-- Add any other context about the problem here. -->
|
<!-- Add any other context about the problem here. -->
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Enhancement request
|
|
||||||
about: Suggest an modification to an existing feature
|
|
||||||
title: ''
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest a new feature
|
|
||||||
title: ''
|
|
||||||
labels: feature
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the feature you'd like**
|
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Media playback issue
|
||||||
|
about: Create a media playback issue report
|
||||||
|
title: ''
|
||||||
|
labels: mediaplayback
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Media Info of the file**
|
||||||
|
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
|
||||||
|
|
||||||
|
**Stats for Nerds Screenshots**
|
||||||
|
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
|
||||||
|
|
||||||
|
**Server System (please complete the following information):**
|
||||||
|
- OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows]
|
||||||
|
- Jellyfin Version: [e.g. 10.0.1]
|
||||||
|
- Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K]
|
||||||
|
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
||||||
|
- Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive]
|
||||||
|
|
||||||
|
**Client System (please complete the following information):**
|
||||||
|
- Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC]
|
||||||
|
- OS: [e.g. iOS, Android, Windows, macOS]
|
||||||
|
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
|
||||||
|
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
|
||||||
|
- Client and Browser Version: [e.g. 10.3.4 and 68.0]
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<!--
|
<!--
|
||||||
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
||||||
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
|
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our documentation.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
**Changes**
|
**Changes**
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 90
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- regression
|
||||||
|
- security
|
||||||
|
- dotnet-3.0-future
|
||||||
|
- roadmap
|
||||||
|
- future
|
||||||
|
- feature
|
||||||
|
- enhancement
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
|
||||||
|
If this issue is safe to close now please do so.
|
||||||
|
If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
+9
-5
@@ -239,11 +239,6 @@ pip-log.txt
|
|||||||
##########
|
##########
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
##########
|
|
||||||
# Visual Studio Code
|
|
||||||
##########
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
#########################
|
#########################
|
||||||
@@ -264,3 +259,12 @@ deployment/**/pkg-dist-tmp/
|
|||||||
deployment/collect-dist/
|
deployment/collect-dist/
|
||||||
|
|
||||||
jellyfin_version.ini
|
jellyfin_version.ini
|
||||||
|
|
||||||
|
ci/
|
||||||
|
|
||||||
|
# Doxygen
|
||||||
|
doc/
|
||||||
|
|
||||||
|
# Deployment artifacts
|
||||||
|
dist
|
||||||
|
*.exe
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
|
|
||||||
path = MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
url = https://github.com/jellyfin/jellyfin-web.git
|
|
||||||
branch = .
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+3
-10
@@ -165,7 +165,7 @@ namespace BDInfo
|
|||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
PlaylistFiles.Add(
|
PlaylistFiles.Add(
|
||||||
file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem));
|
file.Name.ToUpper(), new TSPlaylistFile(this, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ namespace BDInfo
|
|||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
StreamClipFiles.Add(
|
StreamClipFiles.Add(
|
||||||
file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem));
|
file.Name.ToUpper(), new TSStreamClipFile(file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +212,6 @@ namespace BDInfo
|
|||||||
|
|
||||||
public void Scan()
|
public void Scan()
|
||||||
{
|
{
|
||||||
var errorStreamClipFiles = new List<TSStreamClipFile>();
|
|
||||||
foreach (var streamClipFile in StreamClipFiles.Values)
|
foreach (var streamClipFile in StreamClipFiles.Values)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -221,7 +220,6 @@ namespace BDInfo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
errorStreamClipFiles.Add(streamClipFile);
|
|
||||||
if (StreamClipFileScanError != null)
|
if (StreamClipFileScanError != null)
|
||||||
{
|
{
|
||||||
if (StreamClipFileScanError(streamClipFile, ex))
|
if (StreamClipFileScanError(streamClipFile, ex))
|
||||||
@@ -250,7 +248,6 @@ namespace BDInfo
|
|||||||
StreamFiles.Values.CopyTo(streamFiles, 0);
|
StreamFiles.Values.CopyTo(streamFiles, 0);
|
||||||
Array.Sort(streamFiles, CompareStreamFiles);
|
Array.Sort(streamFiles, CompareStreamFiles);
|
||||||
|
|
||||||
var errorPlaylistFiles = new List<TSPlaylistFile>();
|
|
||||||
foreach (var playlistFile in PlaylistFiles.Values)
|
foreach (var playlistFile in PlaylistFiles.Values)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -259,7 +256,6 @@ namespace BDInfo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
errorPlaylistFiles.Add(playlistFile);
|
|
||||||
if (PlaylistFileScanError != null)
|
if (PlaylistFileScanError != null)
|
||||||
{
|
{
|
||||||
if (PlaylistFileScanError(playlistFile, ex))
|
if (PlaylistFileScanError(playlistFile, ex))
|
||||||
@@ -275,7 +271,6 @@ namespace BDInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorStreamFiles = new List<TSStreamFile>();
|
|
||||||
foreach (var streamFile in streamFiles)
|
foreach (var streamFile in streamFiles)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -296,7 +291,6 @@ namespace BDInfo
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
errorStreamFiles.Add(streamFile);
|
|
||||||
if (StreamFileScanError != null)
|
if (StreamFileScanError != null)
|
||||||
{
|
{
|
||||||
if (StreamFileScanError(streamFile, ex))
|
if (StreamFileScanError(streamFile, ex))
|
||||||
@@ -431,7 +425,7 @@ namespace BDInfo
|
|||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else if ((x != null || x.FileInfo != null) && (y == null || y.FileInfo == null))
|
else if ((x != null && x.FileInfo != null) && (y == null || y.FileInfo == null))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -451,6 +445,5 @@ namespace BDInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ namespace BDInfo
|
|||||||
{
|
{
|
||||||
public class TSPlaylistFile
|
public class TSPlaylistFile
|
||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private FileSystemMetadata FileInfo = null;
|
private FileSystemMetadata FileInfo = null;
|
||||||
public string FileType = null;
|
public string FileType = null;
|
||||||
public bool IsInitialized = false;
|
public bool IsInitialized = false;
|
||||||
@@ -64,21 +63,19 @@ namespace BDInfo
|
|||||||
new List<TSGraphicsStream>();
|
new List<TSGraphicsStream>();
|
||||||
|
|
||||||
public TSPlaylistFile(BDROM bdrom,
|
public TSPlaylistFile(BDROM bdrom,
|
||||||
FileSystemMetadata fileInfo, IFileSystem fileSystem)
|
FileSystemMetadata fileInfo)
|
||||||
{
|
{
|
||||||
BDROM = bdrom;
|
BDROM = bdrom;
|
||||||
FileInfo = fileInfo;
|
FileInfo = fileInfo;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
Name = fileInfo.Name.ToUpper();
|
Name = fileInfo.Name.ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TSPlaylistFile(BDROM bdrom,
|
public TSPlaylistFile(BDROM bdrom,
|
||||||
string name,
|
string name,
|
||||||
List<TSStreamClip> clips, IFileSystem fileSystem)
|
List<TSStreamClip> clips)
|
||||||
{
|
{
|
||||||
BDROM = bdrom;
|
BDROM = bdrom;
|
||||||
Name = name;
|
Name = name;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
IsCustom = true;
|
IsCustom = true;
|
||||||
foreach (var clip in clips)
|
foreach (var clip in clips)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ namespace BDInfo
|
|||||||
{
|
{
|
||||||
public class TSStreamClipFile
|
public class TSStreamClipFile
|
||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
public FileSystemMetadata FileInfo = null;
|
public FileSystemMetadata FileInfo = null;
|
||||||
public string FileType = null;
|
public string FileType = null;
|
||||||
public bool IsValid = false;
|
public bool IsValid = false;
|
||||||
@@ -37,10 +36,9 @@ namespace BDInfo
|
|||||||
public Dictionary<ushort, TSStream> Streams =
|
public Dictionary<ushort, TSStream> Streams =
|
||||||
new Dictionary<ushort, TSStream>();
|
new Dictionary<ushort, TSStream>();
|
||||||
|
|
||||||
public TSStreamClipFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
|
public TSStreamClipFile(FileSystemMetadata fileInfo)
|
||||||
{
|
{
|
||||||
FileInfo = fileInfo;
|
FileInfo = fileInfo;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
Name = fileInfo.Name.ToUpper();
|
Name = fileInfo.Name.ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,17 @@
|
|||||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||||
- [WillWill56](https://github.com/WillWill56)
|
- [WillWill56](https://github.com/WillWill56)
|
||||||
|
- [Liggy](https://github.com/Liggy)
|
||||||
|
- [fruhnow](https://github.com/fruhnow)
|
||||||
|
- [Lynxy](https://github.com/Lynxy)
|
||||||
|
- [fasheng](https://github.com/fasheng)
|
||||||
|
- [ploughpuff](https://github.com/ploughpuff)
|
||||||
|
- [pjeanjean](https://github.com/pjeanjean)
|
||||||
|
- [DrPandemic](https://github.com/drpandemic)
|
||||||
|
- [joern-h](https://github.com/joern-h)
|
||||||
|
- [Khinenw](https://github.com/HelloWorld017)
|
||||||
|
- [fhriley](https://github.com/fhriley)
|
||||||
|
- [nevado](https://github.com/nevado)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|||||||
+30
-17
@@ -1,29 +1,42 @@
|
|||||||
ARG DOTNET_VERSION=2
|
ARG DOTNET_VERSION=2.2
|
||||||
|
ARG FFMPEG_VERSION=latest
|
||||||
|
|
||||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
|
FROM node:alpine as web-builder
|
||||||
|
ARG JELLYFIN_WEB_VERSION=v10.4.3
|
||||||
|
RUN apk add curl \
|
||||||
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
|
&& cd jellyfin-web-* \
|
||||||
|
&& yarn install \
|
||||||
|
&& yarn build \
|
||||||
|
&& mv dist /dist
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
RUN bash -c "source deployment/common.build.sh && \
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin"
|
|
||||||
|
|
||||||
FROM jellyfin/ffmpeg as ffmpeg
|
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
||||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
|
|
||||||
# libfontconfig1 is required for Skia
|
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
|
||||||
libfontconfig1 \
|
|
||||||
&& apt-get clean autoclean \
|
|
||||||
&& apt-get autoremove \
|
|
||||||
&& rm -rf /var/lib/{apt,dpkg,cache,log} \
|
|
||||||
&& mkdir -p /cache /config /media \
|
|
||||||
&& chmod 777 /cache /config /media
|
|
||||||
COPY --from=ffmpeg / /
|
COPY --from=ffmpeg / /
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
# Install dependencies:
|
||||||
|
# libfontconfig1: needed for Skia
|
||||||
|
# mesa-va-drivers: needed for VAAPI
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
|
libfontconfig1 mesa-va-drivers \
|
||||||
|
&& apt-get clean autoclean \
|
||||||
|
&& apt-get autoremove \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& mkdir -p /cache /config /media \
|
||||||
|
&& chmod 777 /cache /config /media
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||||
--datadir /config \
|
--datadir /config \
|
||||||
--cachedir /cache \
|
--cachedir /cache \
|
||||||
--ffmpeg /usr/local/bin/ffmpeg \
|
--ffmpeg /usr/local/bin/ffmpeg
|
||||||
--ffprobe /usr/local/bin/ffprobe
|
|
||||||
|
|||||||
+18
-11
@@ -3,12 +3,17 @@
|
|||||||
ARG DOTNET_VERSION=3.0
|
ARG DOTNET_VERSION=3.0
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM node:alpine as web-builder
|
||||||
FROM alpine as qemu_extract
|
ARG JELLYFIN_WEB_VERSION=v10.4.3
|
||||||
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
|
RUN apk add curl \
|
||||||
RUN tar -xzvf qemu-arm-static.tar.gz
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
|
&& cd jellyfin-web-* \
|
||||||
|
&& yarn install \
|
||||||
|
&& yarn build \
|
||||||
|
&& mv dist /dist
|
||||||
|
|
||||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
@@ -17,21 +22,23 @@ RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
|
|||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN bash -c "source deployment/common.build.sh && \
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
|
|
||||||
|
|
||||||
|
|
||||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
|
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||||
COPY --from=qemu_extract qemu-arm-static /usr/bin
|
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
|
||||||
|
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media
|
&& chmod 777 /cache /config /media
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||||
--datadir /config \
|
--datadir /config \
|
||||||
--cachedir /cache \
|
--cachedir /cache \
|
||||||
--ffmpeg /usr/bin/ffmpeg \
|
--ffmpeg /usr/bin/ffmpeg
|
||||||
--ffprobe /usr/bin/ffprobe
|
|
||||||
|
|||||||
+17
-11
@@ -3,13 +3,17 @@
|
|||||||
ARG DOTNET_VERSION=3.0
|
ARG DOTNET_VERSION=3.0
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
FROM node:alpine as web-builder
|
||||||
FROM alpine as qemu_extract
|
ARG JELLYFIN_WEB_VERSION=v10.4.3
|
||||||
COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
|
RUN apk add curl \
|
||||||
RUN tar -xzvf qemu-aarch64-static.tar.gz
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
|
&& cd jellyfin-web-* \
|
||||||
|
&& yarn install \
|
||||||
|
&& yarn build \
|
||||||
|
&& mv dist /dist
|
||||||
|
|
||||||
|
|
||||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
@@ -18,21 +22,23 @@ RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
|
|||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN bash -c "source deployment/common.build.sh && \
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
|
|
||||||
|
|
||||||
|
|
||||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
|
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||||
COPY --from=qemu_extract qemu-aarch64-static /usr/bin
|
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
|
||||||
|
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media
|
&& chmod 777 /cache /config /media
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||||
--datadir /config \
|
--datadir /config \
|
||||||
--cachedir /cache \
|
--cachedir /cache \
|
||||||
--ffmpeg /usr/bin/ffmpeg \
|
--ffmpeg /usr/bin/ffmpeg
|
||||||
--ffprobe /usr/bin/ffprobe
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
namespace DvdLib.Ifo
|
|
||||||
{
|
|
||||||
public enum AudioCodec
|
|
||||||
{
|
|
||||||
AC3 = 0,
|
|
||||||
MPEG1 = 2,
|
|
||||||
MPEG2ext = 3,
|
|
||||||
LPCM = 4,
|
|
||||||
DTS = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ApplicationMode
|
|
||||||
{
|
|
||||||
Unspecified = 0,
|
|
||||||
Karaoke = 1,
|
|
||||||
Surround = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AudioAttributes
|
|
||||||
{
|
|
||||||
public readonly AudioCodec Codec;
|
|
||||||
public readonly bool MultichannelExtensionPresent;
|
|
||||||
public readonly ApplicationMode Mode;
|
|
||||||
public readonly byte QuantDRC;
|
|
||||||
public readonly byte SampleRate;
|
|
||||||
public readonly byte Channels;
|
|
||||||
public readonly ushort LanguageCode;
|
|
||||||
public readonly byte LanguageExtension;
|
|
||||||
public readonly byte CodeExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MultiChannelExtension
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+8
-8
@@ -26,17 +26,17 @@ namespace DvdLib.Ifo
|
|||||||
|
|
||||||
if (vmgPath == null)
|
if (vmgPath == null)
|
||||||
{
|
{
|
||||||
var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase));
|
foreach (var ifo in allFiles)
|
||||||
|
|
||||||
foreach (var ifo in allIfos)
|
|
||||||
{
|
{
|
||||||
var num = ifo.Name.Split('_').ElementAtOrDefault(1);
|
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
|
||||||
var numbersRead = new List<ushort>();
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber))
|
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
||||||
{
|
{
|
||||||
ReadVTS(ifoNumber, ifo.FullName);
|
ReadVTS(ifoNumber, ifo.FullName);
|
||||||
numbersRead.Add(ifoNumber);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ namespace DvdLib.Ifo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadVTS(ushort vtsNum, List<FileSystemMetadata> allFiles)
|
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
||||||
{
|
{
|
||||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
|
||||||
{
|
|
||||||
public class ProgramChainCommandTable
|
|
||||||
{
|
|
||||||
public readonly ushort LastByteAddress;
|
|
||||||
public readonly List<VirtualMachineCommand> PreCommands;
|
|
||||||
public readonly List<VirtualMachineCommand> PostCommands;
|
|
||||||
public readonly List<VirtualMachineCommand> CellCommands;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VirtualMachineCommand
|
|
||||||
{
|
|
||||||
public readonly byte[] Command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,13 +25,10 @@ namespace DvdLib.Ifo
|
|||||||
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
||||||
|
|
||||||
private ushort _nextProgramNumber;
|
private ushort _nextProgramNumber;
|
||||||
public readonly ProgramChain Next;
|
|
||||||
|
|
||||||
private ushort _prevProgramNumber;
|
private ushort _prevProgramNumber;
|
||||||
public readonly ProgramChain Previous;
|
|
||||||
|
|
||||||
private ushort _goupProgramNumber;
|
private ushort _goupProgramNumber;
|
||||||
public readonly ProgramChain Goup; // ?? maybe Group
|
|
||||||
|
|
||||||
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
||||||
public uint ProgramCount { get; private set; }
|
public uint ProgramCount { get; private set; }
|
||||||
@@ -40,7 +37,6 @@ namespace DvdLib.Ifo
|
|||||||
public byte[] Palette { get; private set; } // 16*4 entries
|
public byte[] Palette { get; private set; } // 16*4 entries
|
||||||
|
|
||||||
private ushort _commandTableOffset;
|
private ushort _commandTableOffset;
|
||||||
public readonly ProgramChainCommandTable CommandTable;
|
|
||||||
|
|
||||||
private ushort _programMapOffset;
|
private ushort _programMapOffset;
|
||||||
private ushort _cellPlaybackOffset;
|
private ushort _cellPlaybackOffset;
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
namespace DvdLib.Ifo
|
|
||||||
{
|
|
||||||
public enum VideoCodec
|
|
||||||
{
|
|
||||||
MPEG1 = 0,
|
|
||||||
MPEG2 = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum VideoFormat
|
|
||||||
{
|
|
||||||
NTSC = 0,
|
|
||||||
PAL = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AspectRatio
|
|
||||||
{
|
|
||||||
ar4to3 = 0,
|
|
||||||
ar16to9 = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FilmMode
|
|
||||||
{
|
|
||||||
None = -1,
|
|
||||||
Camera = 0,
|
|
||||||
Film = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VideoAttributes
|
|
||||||
{
|
|
||||||
public readonly VideoCodec Codec;
|
|
||||||
public readonly VideoFormat Format;
|
|
||||||
public readonly AspectRatio Aspect;
|
|
||||||
public readonly bool AutomaticPanScan;
|
|
||||||
public readonly bool AutomaticLetterBox;
|
|
||||||
public readonly bool Line21CCField1;
|
|
||||||
public readonly bool Line21CCField2;
|
|
||||||
public readonly int Width;
|
|
||||||
public readonly int Height;
|
|
||||||
public readonly bool Letterboxed;
|
|
||||||
public readonly FilmMode FilmMode;
|
|
||||||
|
|
||||||
public VideoAttributes()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Main;
|
using Emby.Dlna.Main;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
@@ -108,12 +109,13 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
public class DlnaServerService : IService, IRequiresRequest
|
public class DlnaServerService : IService, IRequiresRequest
|
||||||
{
|
{
|
||||||
private readonly IDlnaManager _dlnaManager;
|
|
||||||
|
|
||||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
private const string XMLContentType = "text/xml; charset=UTF-8";
|
||||||
|
|
||||||
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
private readonly IHttpResultFactory _resultFactory;
|
||||||
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
|
||||||
public IRequest Request { get; set; }
|
public IRequest Request { get; set; }
|
||||||
private IHttpResultFactory _resultFactory;
|
|
||||||
|
|
||||||
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
||||||
|
|
||||||
@@ -121,10 +123,14 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
||||||
|
|
||||||
public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
|
public DlnaServerService(
|
||||||
|
IDlnaManager dlnaManager,
|
||||||
|
IHttpResultFactory httpResultFactory,
|
||||||
|
IServerConfigurationManager configurationManager)
|
||||||
{
|
{
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_resultFactory = httpResultFactory;
|
_resultFactory = httpResultFactory;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetHeader(string name)
|
private string GetHeader(string name)
|
||||||
@@ -136,7 +142,7 @@ namespace Emby.Dlna.Api
|
|||||||
{
|
{
|
||||||
var url = Request.AbsoluteUri;
|
var url = Request.AbsoluteUri;
|
||||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
||||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress);
|
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
|
||||||
|
|
||||||
var cacheLength = TimeSpan.FromDays(1);
|
var cacheLength = TimeSpan.FromDays(1);
|
||||||
var cacheKey = Request.RawUrl.GetMD5();
|
var cacheKey = Request.RawUrl.GetMD5();
|
||||||
@@ -147,21 +153,21 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
public object Get(GetContentDirectory request)
|
public object Get(GetContentDirectory request)
|
||||||
{
|
{
|
||||||
var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary());
|
var xml = ContentDirectory.GetServiceXml();
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetMediaReceiverRegistrar request)
|
public object Get(GetMediaReceiverRegistrar request)
|
||||||
{
|
{
|
||||||
var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary());
|
var xml = MediaReceiverRegistrar.GetServiceXml();
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetConnnectionManager request)
|
public object Get(GetConnnectionManager request)
|
||||||
{
|
{
|
||||||
var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary());
|
var xml = ConnectionManager.GetServiceXml();
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
@@ -193,7 +199,7 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
return service.ProcessControlRequest(new ControlRequest
|
return service.ProcessControlRequest(new ControlRequest
|
||||||
{
|
{
|
||||||
Headers = Request.Headers.ToDictionary(),
|
Headers = Request.Headers,
|
||||||
InputXml = requestStream,
|
InputXml = requestStream,
|
||||||
TargetServerUuId = id,
|
TargetServerUuId = id,
|
||||||
RequestedUrl = Request.AbsoluteUri
|
RequestedUrl = Request.AbsoluteUri
|
||||||
@@ -205,19 +211,32 @@ namespace Emby.Dlna.Api
|
|||||||
var pathInfo = Parse(Request.PathInfo);
|
var pathInfo = Parse(Request.PathInfo);
|
||||||
var first = pathInfo[0];
|
var first = pathInfo[0];
|
||||||
|
|
||||||
|
string baseUrl = _configurationManager.Configuration.BaseUrl;
|
||||||
|
|
||||||
// backwards compatibility
|
// backwards compatibility
|
||||||
// TODO: Work out what this is doing.
|
if (baseUrl.Length == 0)
|
||||||
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
|
{
|
||||||
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||||
string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.Equals(first, baseUrl.Remove(0, 1)))
|
||||||
{
|
{
|
||||||
index++;
|
index++;
|
||||||
|
var second = pathInfo[1];
|
||||||
|
if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathInfo[index];
|
return pathInfo[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> Parse(string pathUri)
|
private static string[] Parse(string pathUri)
|
||||||
{
|
{
|
||||||
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
|
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
|
||||||
|
|
||||||
@@ -231,7 +250,7 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
var args = pathInfo.Split('/');
|
var args = pathInfo.Split('/');
|
||||||
|
|
||||||
return args.Skip(1).ToList();
|
return args.Skip(1).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetIcon request)
|
public object Get(GetIcon request)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration
|
|||||||
public bool EnableServer { get; set; }
|
public bool EnableServer { get; set; }
|
||||||
public bool EnableDebugLog { get; set; }
|
public bool EnableDebugLog { get; set; }
|
||||||
public bool BlastAliveMessages { get; set; }
|
public bool BlastAliveMessages { get; set; }
|
||||||
|
public bool SendOnlyMatchedHost { get; set; }
|
||||||
public int ClientDiscoveryIntervalSeconds { get; set; }
|
public int ClientDiscoveryIntervalSeconds { get; set; }
|
||||||
public int BlastAliveMessageIntervalSeconds { get; set; }
|
public int BlastAliveMessageIntervalSeconds { get; set; }
|
||||||
public string DefaultUserId { get; set; }
|
public string DefaultUserId { get; set; }
|
||||||
@@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration
|
|||||||
EnablePlayTo = true;
|
EnablePlayTo = true;
|
||||||
EnableServer = true;
|
EnableServer = true;
|
||||||
BlastAliveMessages = true;
|
BlastAliveMessages = true;
|
||||||
|
SendOnlyMatchedHost = true;
|
||||||
ClientDiscoveryIntervalSeconds = 60;
|
ClientDiscoveryIntervalSeconds = 60;
|
||||||
BlastAliveMessageIntervalSeconds = 1800;
|
BlastAliveMessageIntervalSeconds = 1800;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ConnectionManager
|
namespace Emby.Dlna.ConnectionManager
|
||||||
@@ -13,18 +11,16 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
private readonly IDlnaManager _dlna;
|
private readonly IDlnaManager _dlna;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
|
||||||
|
|
||||||
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClient)
|
||||||
{
|
{
|
||||||
_dlna = dlna;
|
_dlna = dlna;
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetServiceXml(IDictionary<string, string> headers)
|
public string GetServiceXml()
|
||||||
{
|
{
|
||||||
return new ConnectionManagerXmlBuilder().GetXml();
|
return new ConnectionManagerXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
@@ -34,7 +30,7 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
|
||||||
return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
|
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Emby.Dlna.Service;
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ConnectionManager
|
namespace Emby.Dlna.ConnectionManager
|
||||||
@@ -32,7 +31,8 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
|
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
|
||||||
|
: base(config, logger)
|
||||||
{
|
{
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
|||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
@@ -28,7 +27,6 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IUserViewManager _userViewManager;
|
private readonly IUserViewManager _userViewManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
|
||||||
private readonly ITVSeriesManager _tvSeriesManager;
|
private readonly ITVSeriesManager _tvSeriesManager;
|
||||||
|
|
||||||
public ContentDirectory(IDlnaManager dlna,
|
public ContentDirectory(IDlnaManager dlna,
|
||||||
@@ -38,7 +36,12 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
|
IHttpClient httpClient,
|
||||||
|
ILocalizationManager localization,
|
||||||
|
IMediaSourceManager mediaSourceManager,
|
||||||
|
IUserViewManager userViewManager,
|
||||||
|
IMediaEncoder mediaEncoder,
|
||||||
|
ITVSeriesManager tvSeriesManager)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClient)
|
||||||
{
|
{
|
||||||
_dlna = dlna;
|
_dlna = dlna;
|
||||||
@@ -51,7 +54,6 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_userViewManager = userViewManager;
|
_userViewManager = userViewManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
|
||||||
_tvSeriesManager = tvSeriesManager;
|
_tvSeriesManager = tvSeriesManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetServiceXml(IDictionary<string, string> headers)
|
public string GetServiceXml()
|
||||||
{
|
{
|
||||||
return new ContentDirectoryXmlBuilder().GetXml();
|
return new ContentDirectoryXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
@@ -94,7 +96,6 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
_mediaSourceManager,
|
_mediaSourceManager,
|
||||||
_userViewManager,
|
_userViewManager,
|
||||||
_mediaEncoder,
|
_mediaEncoder,
|
||||||
XmlReaderSettingsFactory,
|
|
||||||
_tvSeriesManager)
|
_tvSeriesManager)
|
||||||
.ProcessControlRequest(request);
|
.ProcessControlRequest(request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ using MediaBrowser.Model.Dlna;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
@@ -51,8 +50,22 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
private readonly DeviceProfile _profile;
|
private readonly DeviceProfile _profile;
|
||||||
|
|
||||||
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
|
public ControlHandler(
|
||||||
: base(config, logger, xmlReaderSettingsFactory)
|
ILogger logger,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
DeviceProfile profile,
|
||||||
|
string serverAddress,
|
||||||
|
string accessToken,
|
||||||
|
IImageProcessor imageProcessor,
|
||||||
|
IUserDataManager userDataManager,
|
||||||
|
User user, int systemUpdateId,
|
||||||
|
IServerConfigurationManager config,
|
||||||
|
ILocalizationManager localization,
|
||||||
|
IMediaSourceManager mediaSourceManager,
|
||||||
|
IUserViewManager userViewManager,
|
||||||
|
IMediaEncoder mediaEncoder,
|
||||||
|
ITVSeriesManager tvSeriesManager)
|
||||||
|
: base(config, logger)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
@@ -260,7 +273,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
||||||
{
|
{
|
||||||
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
|
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
|
||||||
|
|
||||||
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
|
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
|
||||||
}
|
}
|
||||||
@@ -273,10 +286,10 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
|
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
|
||||||
totalCount = childrenResult.TotalRecordCount;
|
totalCount = childrenResult.TotalRecordCount;
|
||||||
|
|
||||||
provided = childrenResult.Items.Length;
|
provided = childrenResult.Items.Count;
|
||||||
|
|
||||||
foreach (var i in childrenResult.Items)
|
foreach (var i in childrenResult.Items)
|
||||||
{
|
{
|
||||||
@@ -296,6 +309,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
//writer.WriteEndDocument();
|
//writer.WriteEndDocument();
|
||||||
}
|
}
|
||||||
@@ -373,7 +387,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
totalCount = childrenResult.TotalRecordCount;
|
totalCount = childrenResult.TotalRecordCount;
|
||||||
|
|
||||||
provided = childrenResult.Items.Length;
|
provided = childrenResult.Items.Count;
|
||||||
|
|
||||||
var dlnaOptions = _config.GetDlnaConfiguration();
|
var dlnaOptions = _config.GetDlnaConfiguration();
|
||||||
|
|
||||||
@@ -664,7 +678,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list.ToArray(),
|
Items = list,
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -742,7 +756,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list.ToArray(),
|
Items = list,
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -847,7 +861,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list.ToArray(),
|
Items = list,
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public class ControlRequest
|
public class ControlRequest
|
||||||
{
|
{
|
||||||
public IDictionary<string, string> Headers { get; set; }
|
public IHeaderDictionary Headers { get; set; }
|
||||||
|
|
||||||
public Stream InputXml { get; set; }
|
public Stream InputXml { get; set; }
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
public ControlRequest()
|
public ControlRequest()
|
||||||
{
|
{
|
||||||
Headers = new Dictionary<string, string>();
|
Headers = new HeaderDictionary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
AddGeneralProperties(item, null, context, writer, filter);
|
AddGeneralProperties(item, null, context, writer, filter);
|
||||||
|
|
||||||
AddSamsungBookmarkInfo(item, user, writer);
|
AddSamsungBookmarkInfo(item, user, writer, streamInfo);
|
||||||
|
|
||||||
// refID?
|
// refID?
|
||||||
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
||||||
@@ -181,19 +181,6 @@ namespace Emby.Dlna.Didl
|
|||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMimeType(string input)
|
|
||||||
{
|
|
||||||
var mime = MimeTypes.GetMimeType(input);
|
|
||||||
|
|
||||||
// TODO: Instead of being hard-coded here, this should probably be moved into all of the existing profiles
|
|
||||||
if (string.Equals(mime, "video/mp2t", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
mime = "video/mpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
return mime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
@@ -384,7 +371,7 @@ namespace Emby.Dlna.Didl
|
|||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||||
@@ -520,7 +507,7 @@ namespace Emby.Dlna.Didl
|
|||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
||||||
@@ -545,17 +532,10 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsIdRoot(string id)
|
public static bool IsIdRoot(string id)
|
||||||
{
|
=> string.IsNullOrWhiteSpace(id)
|
||||||
if (string.IsNullOrWhiteSpace(id)
|
|
||||||
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||||
// Samsung sometimes uses 1 as root
|
// Samsung sometimes uses 1 as root
|
||||||
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
||||||
{
|
{
|
||||||
@@ -601,7 +581,7 @@ namespace Emby.Dlna.Didl
|
|||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer)
|
private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo)
|
||||||
{
|
{
|
||||||
if (!item.SupportsPositionTicksResume || item is Folder)
|
if (!item.SupportsPositionTicksResume || item is Folder)
|
||||||
{
|
{
|
||||||
@@ -625,10 +605,11 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userdata = _userDataManager.GetUserData(user, item);
|
var userdata = _userDataManager.GetUserData(user, item);
|
||||||
|
var playbackPositionTicks = (streamInfo != null && streamInfo.StartPositionTicks > 0) ? streamInfo.StartPositionTicks : userdata.PlaybackPositionTicks;
|
||||||
|
|
||||||
if (userdata.PlaybackPositionTicks > 0)
|
if (playbackPositionTicks > 0)
|
||||||
{
|
{
|
||||||
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
|
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
|
||||||
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
|
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -818,10 +799,9 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
AddCommonFields(item, itemStubType, context, writer, filter);
|
AddCommonFields(item, itemStubType, context, writer, filter);
|
||||||
|
|
||||||
var hasArtists = item as IHasArtist;
|
|
||||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
var hasAlbumArtists = item as IHasAlbumArtist;
|
||||||
|
|
||||||
if (hasArtists != null)
|
if (item is IHasArtist hasArtists)
|
||||||
{
|
{
|
||||||
foreach (var artist in hasArtists.Artists)
|
foreach (var artist in hasArtists.Artists)
|
||||||
{
|
{
|
||||||
@@ -921,8 +901,6 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
|
||||||
|
|
||||||
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
||||||
@@ -931,6 +909,9 @@ namespace Emby.Dlna.Didl
|
|||||||
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
|
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
|
||||||
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
|
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
||||||
@@ -971,7 +952,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||||
"http-get:*:{0}:{1}",
|
"http-get:*:{0}:{1}",
|
||||||
GetMimeType("file." + format),
|
MimeTypes.GetMimeType("file." + format),
|
||||||
contentFeatures
|
contentFeatures
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -1102,7 +1083,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
public static string GetClientId(Guid idValue, StubType? stubType)
|
public static string GetClientId(Guid idValue, StubType? stubType)
|
||||||
{
|
{
|
||||||
var id = idValue.ToString("N");
|
var id = idValue.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (stubType.HasValue)
|
if (stubType.HasValue)
|
||||||
{
|
{
|
||||||
@@ -1116,7 +1097,7 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
||||||
_serverAddress,
|
_serverAddress,
|
||||||
info.ItemId.ToString("N"),
|
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
||||||
info.Type,
|
info.Type,
|
||||||
info.ImageTag,
|
info.ImageTag,
|
||||||
format,
|
format,
|
||||||
|
|||||||
+23
-20
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -15,9 +17,10 @@ using MediaBrowser.Controller.Drawing;
|
|||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Reflection;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
@@ -29,7 +32,7 @@ namespace Emby.Dlna
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IAssemblyInfo _assemblyInfo;
|
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
||||||
|
|
||||||
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
||||||
|
|
||||||
@@ -38,7 +41,8 @@ namespace Emby.Dlna
|
|||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
|
IJsonSerializer jsonSerializer,
|
||||||
|
IServerApplicationHost appHost)
|
||||||
{
|
{
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
@@ -46,7 +50,6 @@ namespace Emby.Dlna
|
|||||||
_logger = loggerFactory.CreateLogger("Dlna");
|
_logger = loggerFactory.CreateLogger("Dlna");
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_assemblyInfo = assemblyInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitProfilesAsync()
|
public async Task InitProfilesAsync()
|
||||||
@@ -203,16 +206,13 @@ namespace Emby.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceProfile GetProfile(IDictionary<string, string> headers)
|
public DeviceProfile GetProfile(IHeaderDictionary headers)
|
||||||
{
|
{
|
||||||
if (headers == null)
|
if (headers == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(headers));
|
throw new ArgumentNullException(nameof(headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to case insensitive
|
|
||||||
headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
||||||
|
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
@@ -228,12 +228,12 @@ namespace Emby.Dlna
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
|
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
|
||||||
{
|
{
|
||||||
return profileInfo.Headers.Any(i => IsMatch(headers, i));
|
return profileInfo.Headers.Any(i => IsMatch(headers, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
|
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
|
||||||
{
|
{
|
||||||
// Handle invalid user setup
|
// Handle invalid user setup
|
||||||
if (string.IsNullOrEmpty(header.Name))
|
if (string.IsNullOrEmpty(header.Name))
|
||||||
@@ -241,14 +241,14 @@ namespace Emby.Dlna
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headers.TryGetValue(header.Name, out string value))
|
if (headers.TryGetValue(header.Name, out StringValues value))
|
||||||
{
|
{
|
||||||
switch (header.Match)
|
switch (header.Match)
|
||||||
{
|
{
|
||||||
case HeaderMatchType.Equals:
|
case HeaderMatchType.Equals:
|
||||||
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
||||||
case HeaderMatchType.Substring:
|
case HeaderMatchType.Substring:
|
||||||
var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
|
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
|
||||||
return isMatch;
|
return isMatch;
|
||||||
case HeaderMatchType.Regex:
|
case HeaderMatchType.Regex:
|
||||||
@@ -301,7 +301,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
profile = ReserializeProfile(tempProfile);
|
profile = ReserializeProfile(tempProfile);
|
||||||
|
|
||||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N");
|
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||||
|
|
||||||
@@ -353,7 +353,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
Info = new DeviceProfileInfo
|
Info = new DeviceProfileInfo
|
||||||
{
|
{
|
||||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
|
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||||
Type = type
|
Type = type
|
||||||
}
|
}
|
||||||
@@ -366,15 +366,18 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
var systemProfilesPath = SystemProfilesPath;
|
var systemProfilesPath = SystemProfilesPath;
|
||||||
|
|
||||||
foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
|
foreach (var name in _assembly.GetManifestResourceNames())
|
||||||
.Where(i => i.StartsWith(namespaceName))
|
|
||||||
.ToList())
|
|
||||||
{
|
{
|
||||||
|
if (!name.StartsWith(namespaceName))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
||||||
|
|
||||||
var path = Path.Combine(systemProfilesPath, filename);
|
var path = Path.Combine(systemProfilesPath, filename);
|
||||||
|
|
||||||
using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
|
using (var stream = _assembly.GetManifestResourceStream(name))
|
||||||
{
|
{
|
||||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||||
|
|
||||||
@@ -491,7 +494,7 @@ namespace Emby.Dlna
|
|||||||
internal string Path { get; set; }
|
internal string Path { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
|
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||||
{
|
{
|
||||||
var profile = GetProfile(headers) ??
|
var profile = GetProfile(headers) ??
|
||||||
GetDefaultProfile();
|
GetDefaultProfile();
|
||||||
@@ -512,7 +515,7 @@ namespace Emby.Dlna
|
|||||||
return new ImageStream
|
return new ImageStream
|
||||||
{
|
{
|
||||||
Format = format,
|
Format = format,
|
||||||
Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
|
Stream = _assembly.GetManifestResourceStream(resource)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -58,4 +59,9 @@
|
|||||||
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
|
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace Emby.Dlna.Eventing
|
|||||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||||
{
|
{
|
||||||
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
||||||
var id = "uuid:" + Guid.NewGuid().ToString("N");
|
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
// Remove logging for now because some devices are sending this very frequently
|
// Remove logging for now because some devices are sending this very frequently
|
||||||
// TODO re-enable with dlna debug logging setting
|
// TODO re-enable with dlna debug logging setting
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public interface IUpnpService
|
public interface IUpnpService
|
||||||
@@ -7,9 +5,8 @@ namespace Emby.Dlna
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the content directory XML.
|
/// Gets the content directory XML.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="headers">The headers.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
string GetServiceXml(IDictionary<string, string> headers);
|
string GetServiceXml();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the control request.
|
/// Processes the control request.
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Net.Sockets;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.PlayTo;
|
using Emby.Dlna.PlayTo;
|
||||||
@@ -20,10 +21,10 @@ using MediaBrowser.Model.Dlna;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
using Rssdp.Infrastructure;
|
using Rssdp.Infrastructure;
|
||||||
|
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||||
|
|
||||||
namespace Emby.Dlna.Main
|
namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
@@ -48,9 +49,8 @@ namespace Emby.Dlna.Main
|
|||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private SsdpDevicePublisher _Publisher;
|
private SsdpDevicePublisher _Publisher;
|
||||||
|
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private readonly IEnvironmentInfo _environmentInfo;
|
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
private ISsdpCommunicationsServer _communicationsServer;
|
private ISsdpCommunicationsServer _communicationsServer;
|
||||||
@@ -76,10 +76,8 @@ namespace Emby.Dlna.Main
|
|||||||
IDeviceDiscovery deviceDiscovery,
|
IDeviceDiscovery deviceDiscovery,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
ISocketFactory socketFactory,
|
ISocketFactory socketFactory,
|
||||||
IEnvironmentInfo environmentInfo,
|
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
IUserViewManager userViewManager,
|
IUserViewManager userViewManager,
|
||||||
IXmlReaderSettingsFactory xmlReaderSettingsFactory,
|
|
||||||
ITVSeriesManager tvSeriesManager)
|
ITVSeriesManager tvSeriesManager)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
@@ -96,11 +94,11 @@ namespace Emby.Dlna.Main
|
|||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_socketFactory = socketFactory;
|
_socketFactory = socketFactory;
|
||||||
_environmentInfo = environmentInfo;
|
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_logger = loggerFactory.CreateLogger("Dlna");
|
_logger = loggerFactory.CreateLogger("Dlna");
|
||||||
|
|
||||||
ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager,
|
ContentDirectory = new ContentDirectory.ContentDirectory(
|
||||||
|
dlnaManager,
|
||||||
userDataManager,
|
userDataManager,
|
||||||
imageProcessor,
|
imageProcessor,
|
||||||
libraryManager,
|
libraryManager,
|
||||||
@@ -112,12 +110,11 @@ namespace Emby.Dlna.Main
|
|||||||
mediaSourceManager,
|
mediaSourceManager,
|
||||||
userViewManager,
|
userViewManager,
|
||||||
mediaEncoder,
|
mediaEncoder,
|
||||||
xmlReaderSettingsFactory,
|
|
||||||
tvSeriesManager);
|
tvSeriesManager);
|
||||||
|
|
||||||
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory);
|
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
|
||||||
|
|
||||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory);
|
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,9 +166,10 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
if (_communicationsServer == null)
|
if (_communicationsServer == null)
|
||||||
{
|
{
|
||||||
var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
|
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
|
||||||
|
OperatingSystem.Id == OperatingSystemId.Linux;
|
||||||
|
|
||||||
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||||
{
|
{
|
||||||
IsShared = true
|
IsShared = true
|
||||||
};
|
};
|
||||||
@@ -229,7 +227,7 @@ namespace Emby.Dlna.Main
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
|
||||||
_Publisher.LogFunction = LogMessage;
|
_Publisher.LogFunction = LogMessage;
|
||||||
_Publisher.SupportPnpRootDevice = false;
|
_Publisher.SupportPnpRootDevice = false;
|
||||||
|
|
||||||
@@ -245,17 +243,17 @@ namespace Emby.Dlna.Main
|
|||||||
|
|
||||||
private async Task RegisterServerEndpoints()
|
private async Task RegisterServerEndpoints()
|
||||||
{
|
{
|
||||||
var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList();
|
var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var udn = CreateUuid(_appHost.SystemId);
|
var udn = CreateUuid(_appHost.SystemId);
|
||||||
|
|
||||||
foreach (var address in addresses)
|
foreach (var address in addresses)
|
||||||
{
|
{
|
||||||
// TODO: Remove this condition on platforms that support it
|
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
//if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
|
{
|
||||||
//{
|
// Not support IPv6 right now
|
||||||
// continue;
|
continue;
|
||||||
//}
|
}
|
||||||
|
|
||||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||||
|
|
||||||
@@ -268,6 +266,8 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
||||||
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
||||||
|
Address = address,
|
||||||
|
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
|
||||||
FriendlyName = "Jellyfin",
|
FriendlyName = "Jellyfin",
|
||||||
Manufacturer = "Jellyfin",
|
Manufacturer = "Jellyfin",
|
||||||
ModelName = "Jellyfin Server",
|
ModelName = "Jellyfin Server",
|
||||||
@@ -308,7 +308,7 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
guid = text.GetMD5();
|
guid = text.GetMD5();
|
||||||
}
|
}
|
||||||
return guid.ToString("N");
|
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
@@ -36,7 +35,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
|
public ControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||||
|
: base(config, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
@@ -10,16 +8,14 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||||||
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
|
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
|
||||||
|
|
||||||
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClient)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetServiceXml(IDictionary<string, string> headers)
|
public string GetServiceXml()
|
||||||
{
|
{
|
||||||
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
@@ -28,7 +24,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||||||
{
|
{
|
||||||
return new ControlHandler(
|
return new ControlHandler(
|
||||||
_config,
|
_config,
|
||||||
Logger, XmlReaderSettingsFactory)
|
Logger)
|
||||||
.ProcessControlRequest(request);
|
.ProcessControlRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
|
||||||
{
|
|
||||||
public class CurrentIdEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+54
-18
@@ -4,6 +4,7 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Server;
|
using Emby.Dlna.Server;
|
||||||
@@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
XElement uPnpResponse;
|
XElement uPnpResponse = null;
|
||||||
|
|
||||||
// Handle different variations sent back by devices
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
uPnpResponse = XElement.Parse(trackString);
|
uPnpResponse = ParseResponse(trackString);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// first try to add a root node with a dlna namesapce
|
_logger.LogError(ex, "Uncaught exception while parsing xml");
|
||||||
try
|
}
|
||||||
{
|
|
||||||
uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
|
if (uPnpResponse == null)
|
||||||
uPnpResponse = uPnpResponse.Descendants().First();
|
{
|
||||||
}
|
_logger.LogError("Failed to parse xml: \n {Xml}", trackString);
|
||||||
catch (Exception ex)
|
return (true, null);
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
|
|
||||||
return (true, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
||||||
@@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return (true, uTrack);
|
return (true, uTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private XElement ParseResponse(string xml)
|
||||||
|
{
|
||||||
|
// Handle different variations sent back by devices
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return XElement.Parse(xml);
|
||||||
|
}
|
||||||
|
catch (XmlException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// first try to add a root node with a dlna namesapce
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
|
||||||
|
.Descendants()
|
||||||
|
.First();
|
||||||
|
}
|
||||||
|
catch (XmlException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// some devices send back invalid xml
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return XElement.Parse(xml.Replace("&", "&"));
|
||||||
|
}
|
||||||
|
catch (XmlException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
||||||
{
|
{
|
||||||
if (container == null)
|
if (container == null)
|
||||||
@@ -1093,6 +1126,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
private void OnPlaybackStart(uBaseObject mediaInfo)
|
private void OnPlaybackStart(uBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
|
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
|
||||||
{
|
{
|
||||||
MediaInfo = mediaInfo
|
MediaInfo = mediaInfo
|
||||||
@@ -1101,8 +1139,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
var mediaUrl = mediaInfo.Url;
|
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
||||||
if (string.IsNullOrWhiteSpace(mediaUrl))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1115,7 +1152,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
private void OnPlaybackStop(uBaseObject mediaInfo)
|
private void OnPlaybackStop(uBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
|
|
||||||
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
||||||
{
|
{
|
||||||
MediaInfo = mediaInfo
|
MediaInfo = mediaInfo
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@@ -17,8 +18,8 @@ using MediaBrowser.Model.Dto;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
@@ -101,9 +102,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
_sessionManager.ReportSessionEnded(_session.Id);
|
_sessionManager.ReportSessionEnded(_session.Id);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Could throw if the session is already gone
|
// Could throw if the session is already gone
|
||||||
|
_logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,20 +113,14 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var info = e.Argument;
|
var info = e.Argument;
|
||||||
|
|
||||||
info.Headers.TryGetValue("NTS", out string nts);
|
if (!_disposed
|
||||||
|
&& info.Headers.TryGetValue("USN", out string usn)
|
||||||
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
|
&& usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1
|
||||||
|
&& (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1
|
||||||
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
|
|| (info.Headers.TryGetValue("NT", out string nt)
|
||||||
|
&& nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)))
|
||||||
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
|
|
||||||
!_disposed)
|
|
||||||
{
|
{
|
||||||
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
|
OnDeviceUnavailable();
|
||||||
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
OnDeviceUnavailable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,22 +607,34 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
Dispose(true);
|
||||||
{
|
GC.SuppressFinalize(this);
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
_device.PlaybackStart -= _device_PlaybackStart;
|
|
||||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
|
||||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
|
||||||
_device.MediaChanged -= _device_MediaChanged;
|
|
||||||
//_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
|
||||||
_device.OnDeviceUnavailable = null;
|
|
||||||
|
|
||||||
_device.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_device.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_device.PlaybackStart -= _device_PlaybackStart;
|
||||||
|
_device.PlaybackProgress -= _device_PlaybackProgress;
|
||||||
|
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||||
|
_device.MediaChanged -= _device_MediaChanged;
|
||||||
|
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||||
|
_device.OnDeviceUnavailable = null;
|
||||||
|
_device = null;
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||||
|
|
||||||
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -847,13 +855,13 @@ namespace Emby.Dlna.PlayTo
|
|||||||
if (index == -1) return request;
|
if (index == -1) return request;
|
||||||
|
|
||||||
var query = url.Substring(index + 1);
|
var query = url.Substring(index + 1);
|
||||||
QueryParamCollection values = MyHttpUtility.ParseQueryString(query);
|
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
|
||||||
|
|
||||||
request.DeviceProfileId = values.Get("DeviceProfileId");
|
request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId");
|
||||||
request.DeviceId = values.Get("DeviceId");
|
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
||||||
request.MediaSourceId = values.Get("MediaSourceId");
|
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
||||||
request.LiveStreamId = values.Get("LiveStreamId");
|
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
||||||
request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase);
|
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
||||||
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
||||||
@@ -867,9 +875,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int? GetIntValue(QueryParamCollection values, string name)
|
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
|
||||||
{
|
{
|
||||||
var value = values.Get(name);
|
var value = values.GetValueOrDefault(name);
|
||||||
|
|
||||||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||||
{
|
{
|
||||||
@@ -879,9 +887,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long GetLongValue(QueryParamCollection values, string name)
|
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
|
||||||
{
|
{
|
||||||
var value = values.Get(name);
|
var value = values.GetValueOrDefault(name);
|
||||||
|
|
||||||
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@@ -14,7 +16,6 @@ using MediaBrowser.Controller.Session;
|
|||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return usn;
|
return usn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return usn.GetMD5().ToString("N");
|
return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
||||||
@@ -156,7 +157,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uuid = location.GetMD5().ToString("N");
|
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
|
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
|
||||||
@@ -172,7 +173,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
||||||
|
|
||||||
string serverAddress;
|
string serverAddress;
|
||||||
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Any))
|
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IPAddress.Any) || info.LocalIpAddress.Equals(IPAddress.IPv6Any))
|
||||||
{
|
{
|
||||||
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
public class PlaylistItemFactory
|
public class PlaylistItemFactory
|
||||||
{
|
{
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
public PlaylistItem Create(Photo item, DeviceProfile profile)
|
public PlaylistItem Create(Photo item, DeviceProfile profile)
|
||||||
{
|
{
|
||||||
var playlistItem = new PlaylistItem
|
var playlistItem = new PlaylistItem
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
|
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
|
||||||
private const string FriendlyName = "Jellyfin";
|
private const string FriendlyName = "Jellyfin";
|
||||||
|
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
@@ -25,7 +27,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XDocument> SendCommandAsync(string baseUrl,
|
public async Task<XDocument> SendCommandAsync(
|
||||||
|
string baseUrl,
|
||||||
DeviceService service,
|
DeviceService service,
|
||||||
string command,
|
string command,
|
||||||
string postData,
|
string postData,
|
||||||
@@ -34,16 +37,21 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var cancellationToken = CancellationToken.None;
|
var cancellationToken = CancellationToken.None;
|
||||||
|
|
||||||
using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest, cancellationToken)
|
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
||||||
|
using (var response = await PostSoapDataAsync(
|
||||||
|
url,
|
||||||
|
$"\"{service.ServiceType}#{command}\"",
|
||||||
|
postData,
|
||||||
|
header,
|
||||||
|
logRequest,
|
||||||
|
cancellationToken)
|
||||||
.ConfigureAwait(false))
|
.ConfigureAwait(false))
|
||||||
|
using (var stream = response.Content)
|
||||||
|
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
using (var stream = response.Content)
|
return XDocument.Parse(
|
||||||
{
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
LoadOptions.PreserveWhitespace);
|
||||||
{
|
|
||||||
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,9 +69,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return baseUrl + serviceUrl;
|
return baseUrl + serviceUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
public async Task SubscribeAsync(
|
||||||
|
string url,
|
||||||
public async Task SubscribeAsync(string url,
|
|
||||||
string ip,
|
string ip,
|
||||||
int port,
|
int port,
|
||||||
string localIp,
|
string localIp,
|
||||||
@@ -76,9 +83,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
UserAgent = USERAGENT,
|
UserAgent = USERAGENT,
|
||||||
LogErrorResponseBody = true,
|
LogErrorResponseBody = true,
|
||||||
BufferContent = false,
|
BufferContent = false,
|
||||||
|
|
||||||
// The periodic requests may keep some devices awake
|
|
||||||
LogRequestAsDebug = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
|
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
|
||||||
@@ -101,47 +105,41 @@ namespace Emby.Dlna.PlayTo
|
|||||||
LogErrorResponseBody = true,
|
LogErrorResponseBody = true,
|
||||||
BufferContent = false,
|
BufferContent = false,
|
||||||
|
|
||||||
// The periodic requests may keep some devices awake
|
|
||||||
LogRequestAsDebug = true,
|
|
||||||
|
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
||||||
|
using (var stream = response.Content)
|
||||||
|
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
using (var stream = response.Content)
|
return XDocument.Parse(
|
||||||
{
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
LoadOptions.PreserveWhitespace);
|
||||||
{
|
|
||||||
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<HttpResponseInfo> PostSoapDataAsync(string url,
|
private Task<HttpResponseInfo> PostSoapDataAsync(
|
||||||
|
string url,
|
||||||
string soapAction,
|
string soapAction,
|
||||||
string postData,
|
string postData,
|
||||||
string header,
|
string header,
|
||||||
bool logRequest,
|
bool logRequest,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!soapAction.StartsWith("\""))
|
if (soapAction[0] != '\"')
|
||||||
soapAction = "\"" + soapAction + "\"";
|
{
|
||||||
|
soapAction = $"\"{soapAction}\"";
|
||||||
|
}
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
var options = new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = url,
|
Url = url,
|
||||||
UserAgent = USERAGENT,
|
UserAgent = USERAGENT,
|
||||||
LogRequest = logRequest || _config.GetDlnaConfiguration().EnableDebugLog,
|
|
||||||
LogErrorResponseBody = true,
|
LogErrorResponseBody = true,
|
||||||
BufferContent = false,
|
BufferContent = false,
|
||||||
|
|
||||||
// The periodic requests may keep some devices awake
|
|
||||||
LogRequestAsDebug = true,
|
|
||||||
|
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,7 +153,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.RequestContentType = "text/xml";
|
options.RequestContentType = "text/xml";
|
||||||
options.AppendCharsetToMimeType = true;
|
|
||||||
options.RequestContent = postData;
|
options.RequestContent = postData;
|
||||||
|
|
||||||
return _httpClient.Post(options);
|
return _httpClient.Post(options);
|
||||||
|
|||||||
@@ -107,12 +107,18 @@ namespace Emby.Dlna.PlayTo
|
|||||||
foreach (var arg in action.ArgumentList)
|
foreach (var arg in action.ArgumentList)
|
||||||
{
|
{
|
||||||
if (arg.Direction == "out")
|
if (arg.Direction == "out")
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg.Name == "InstanceID")
|
if (arg.Name == "InstanceID")
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, "0");
|
stateString += BuildArgumentXml(arg, "0");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, null);
|
stateString += BuildArgumentXml(arg, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
|
return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
|
||||||
@@ -125,11 +131,18 @@ namespace Emby.Dlna.PlayTo
|
|||||||
foreach (var arg in action.ArgumentList)
|
foreach (var arg in action.ArgumentList)
|
||||||
{
|
{
|
||||||
if (arg.Direction == "out")
|
if (arg.Direction == "out")
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg.Name == "InstanceID")
|
if (arg.Name == "InstanceID")
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, "0");
|
stateString += BuildArgumentXml(arg, "0");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
|
stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||||
@@ -142,11 +155,17 @@ namespace Emby.Dlna.PlayTo
|
|||||||
foreach (var arg in action.ArgumentList)
|
foreach (var arg in action.ArgumentList)
|
||||||
{
|
{
|
||||||
if (arg.Name == "InstanceID")
|
if (arg.Name == "InstanceID")
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, "0");
|
stateString += BuildArgumentXml(arg, "0");
|
||||||
|
}
|
||||||
else if (dictionary.ContainsKey(arg.Name))
|
else if (dictionary.ContainsKey(arg.Name))
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, dictionary[arg.Name]);
|
stateString += BuildArgumentXml(arg, dictionary[arg.Name]);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
stateString += BuildArgumentXml(arg, value.ToString());
|
stateString += BuildArgumentXml(arg, value.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
|
||||||
{
|
|
||||||
public class TransportStateEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public TRANSPORTSTATE State { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
|
||||||
{
|
|
||||||
public class uParser
|
|
||||||
{
|
|
||||||
public static IList<uBaseObject> ParseBrowseXml(XDocument doc)
|
|
||||||
{
|
|
||||||
if (doc == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("doc");
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new List<uBaseObject>();
|
|
||||||
|
|
||||||
var document = doc.Document;
|
|
||||||
|
|
||||||
if (document == null)
|
|
||||||
return list;
|
|
||||||
|
|
||||||
var item = (from result in document.Descendants("Result") select result).FirstOrDefault();
|
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
return list;
|
|
||||||
|
|
||||||
var uPnpResponse = XElement.Parse((string)item);
|
|
||||||
|
|
||||||
var uObjects = from container in uPnpResponse.Elements(uPnpNamespaces.containers)
|
|
||||||
select new uParserObject { Element = container };
|
|
||||||
|
|
||||||
var uObjects2 = from container in uPnpResponse.Elements(uPnpNamespaces.items)
|
|
||||||
select new uParserObject { Element = container };
|
|
||||||
|
|
||||||
list.AddRange(uObjects.Concat(uObjects2).Select(CreateObjectFromXML).Where(uObject => uObject != null));
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uBaseObject CreateObjectFromXML(uParserObject uItem)
|
|
||||||
{
|
|
||||||
return UpnpContainer.Create(uItem.Element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
|
||||||
{
|
|
||||||
public class uParserObject
|
|
||||||
{
|
|
||||||
public XElement Element { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ namespace Emby.Dlna.Profiles
|
|||||||
{
|
{
|
||||||
Name = "Dish Hopper-Joey";
|
Name = "Dish Hopper-Joey";
|
||||||
|
|
||||||
ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
|
ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
|
||||||
|
|
||||||
Identification = new DeviceIdentification
|
Identification = new DeviceIdentification
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mp2t:http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ using System.Resources;
|
|||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using System.Xml;
|
|||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Extensions;
|
using MediaBrowser.Controller.Extensions;
|
||||||
using MediaBrowser.Model.Xml;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.Service
|
namespace Emby.Dlna.Service
|
||||||
@@ -18,13 +17,11 @@ namespace Emby.Dlna.Service
|
|||||||
|
|
||||||
protected readonly IServerConfigurationManager Config;
|
protected readonly IServerConfigurationManager Config;
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
|
||||||
|
|
||||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||||
{
|
{
|
||||||
Config = config;
|
Config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||||
@@ -61,11 +58,13 @@ namespace Emby.Dlna.Service
|
|||||||
|
|
||||||
using (var streamReader = new StreamReader(request.InputXml))
|
using (var streamReader = new StreamReader(request.InputXml))
|
||||||
{
|
{
|
||||||
var readerSettings = XmlReaderSettingsFactory.Create(false);
|
var readerSettings = new XmlReaderSettings()
|
||||||
|
{
|
||||||
readerSettings.CheckCharacters = false;
|
ValidationType = ValidationType.None,
|
||||||
readerSettings.IgnoreProcessingInstructions = true;
|
CheckCharacters = false,
|
||||||
readerSettings.IgnoreComments = true;
|
IgnoreProcessingInstructions = true,
|
||||||
|
IgnoreComments = true
|
||||||
|
};
|
||||||
|
|
||||||
using (var reader = XmlReader.Create(streamReader, readerSettings))
|
using (var reader = XmlReader.Create(streamReader, readerSettings))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -15,4 +17,9 @@
|
|||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- We need at least C# 7.1 for the "default literal" feature-->
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+71
-102
@@ -22,42 +22,47 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Drawing
|
namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ImageProcessor
|
/// Class ImageProcessor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ImageProcessor : IImageProcessor, IDisposable
|
public class ImageProcessor : IImageProcessor, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Increment this when there's a change requiring caches to be invalidated
|
||||||
/// The us culture
|
private const string Version = "3";
|
||||||
/// </summary>
|
|
||||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
/// <summary>
|
private static readonly HashSet<string> _transparentImageTypes
|
||||||
/// Gets the list of currently registered image processors
|
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
||||||
/// Image processors are specialized metadata providers that run after the normal ones
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The image enhancers.</value>
|
|
||||||
public IImageEnhancer[] ImageEnhancers { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _logger
|
/// The _logger
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private IImageEncoder _imageEncoder;
|
private IImageEncoder _imageEncoder;
|
||||||
private readonly Func<ILibraryManager> _libraryManager;
|
private readonly Func<ILibraryManager> _libraryManager;
|
||||||
private readonly Func<IMediaEncoder> _mediaEncoder;
|
private readonly Func<IMediaEncoder> _mediaEncoder;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger"></param>
|
||||||
|
/// <param name="appPaths"></param>
|
||||||
|
/// <param name="fileSystem"></param>
|
||||||
|
/// <param name="imageEncoder"></param>
|
||||||
|
/// <param name="libraryManager"></param>
|
||||||
|
/// <param name="mediaEncoder"></param>
|
||||||
public ImageProcessor(
|
public ImageProcessor(
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<ImageProcessor> logger,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IImageEncoder imageEncoder,
|
IImageEncoder imageEncoder,
|
||||||
Func<ILibraryManager> libraryManager,
|
Func<ILibraryManager> libraryManager,
|
||||||
Func<IMediaEncoder> mediaEncoder)
|
Func<IMediaEncoder> mediaEncoder)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ImageProcessor));
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_imageEncoder = imageEncoder;
|
_imageEncoder = imageEncoder;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
@@ -69,20 +74,11 @@ namespace Emby.Drawing
|
|||||||
ImageHelper.ImageProcessor = this;
|
ImageHelper.ImageProcessor = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IImageEncoder ImageEncoder
|
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
|
||||||
{
|
|
||||||
get => _imageEncoder;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
_imageEncoder = value;
|
private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<string> SupportedInputFormats =>
|
public IReadOnlyCollection<string> SupportedInputFormats =>
|
||||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
@@ -115,18 +111,20 @@ namespace Emby.Drawing
|
|||||||
"wbmp"
|
"wbmp"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
|
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
|
||||||
|
|
||||||
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
|
/// <inheritdoc />
|
||||||
|
public IImageEncoder ImageEncoder
|
||||||
private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
|
|
||||||
|
|
||||||
public void AddParts(IEnumerable<IImageEnhancer> enhancers)
|
|
||||||
{
|
{
|
||||||
ImageEnhancers = enhancers.ToArray();
|
get => _imageEncoder;
|
||||||
|
set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
||||||
{
|
{
|
||||||
var file = await ProcessImage(options).ConfigureAwait(false);
|
var file = await ProcessImage(options).ConfigureAwait(false);
|
||||||
@@ -137,15 +135,15 @@ namespace Emby.Drawing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
|
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
|
||||||
=> _imageEncoder.SupportedOutputFormats;
|
=> _imageEncoder.SupportedOutputFormats;
|
||||||
|
|
||||||
private static readonly HashSet<string> TransparentImageTypes
|
/// <inheritdoc />
|
||||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
|
||||||
|
|
||||||
public bool SupportsTransparency(string path)
|
public bool SupportsTransparency(string path)
|
||||||
=> TransparentImageTypes.Contains(Path.GetExtension(path));
|
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
@@ -180,10 +178,16 @@ namespace Emby.Drawing
|
|||||||
|
|
||||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||||
originalImagePath = supportedImageInfo.path;
|
originalImagePath = supportedImageInfo.path;
|
||||||
dateModified = supportedImageInfo.dateModified;
|
|
||||||
bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
|
||||||
|
|
||||||
if (options.Enhancers.Length > 0)
|
if (!File.Exists(originalImagePath))
|
||||||
|
{
|
||||||
|
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
dateModified = supportedImageInfo.dateModified;
|
||||||
|
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||||
|
|
||||||
|
if (options.Enhancers.Count > 0)
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
@@ -265,8 +269,6 @@ namespace Emby.Drawing
|
|||||||
{
|
{
|
||||||
// If it fails for whatever reason, return the original image
|
// If it fails for whatever reason, return the original image
|
||||||
_logger.LogError(ex, "Error encoding image");
|
_logger.LogError(ex, "Error encoding image");
|
||||||
|
|
||||||
// Just spit out the original file if all the options are default
|
|
||||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -275,7 +277,7 @@ namespace Emby.Drawing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageFormat GetOutputFormat(ImageFormat[] clientSupportedFormats, bool requiresTransparency)
|
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
|
||||||
{
|
{
|
||||||
var serverFormats = GetSupportedImageOutputFormats();
|
var serverFormats = GetSupportedImageOutputFormats();
|
||||||
|
|
||||||
@@ -316,11 +318,6 @@ namespace Emby.Drawing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Increment this when there's a change requiring caches to be invalidated
|
|
||||||
/// </summary>
|
|
||||||
private const string Version = "3";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cache file path based on a set of parameters
|
/// Gets the cache file path based on a set of parameters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -368,9 +365,11 @@ namespace Emby.Drawing
|
|||||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
|
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
||||||
=> GetImageDimensions(item, info, true);
|
=> GetImageDimensions(item, info, true);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
|
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
|
||||||
{
|
{
|
||||||
int width = info.Width;
|
int width = info.Width;
|
||||||
@@ -396,26 +395,19 @@ namespace Emby.Drawing
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the size of the image.
|
|
||||||
/// </summary>
|
|
||||||
public ImageDimensions GetImageDimensions(string path)
|
public ImageDimensions GetImageDimensions(string path)
|
||||||
=> _imageEncoder.GetImageSize(path);
|
=> _imageEncoder.GetImageSize(path);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the image cache tag.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="image">The image.</param>
|
|
||||||
/// <returns>Guid.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">item</exception>
|
|
||||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
||||||
{
|
{
|
||||||
var supportedEnhancers = GetSupportedEnhancers(item, image.Type);
|
var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray();
|
||||||
|
|
||||||
return GetImageCacheTag(item, image, supportedEnhancers);
|
return GetImageCacheTag(item, image, supportedEnhancers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -433,31 +425,24 @@ namespace Emby.Drawing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the image cache tag.
|
public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers)
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="image">The image.</param>
|
|
||||||
/// <param name="imageEnhancers">The image enhancers.</param>
|
|
||||||
/// <returns>Guid.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">item</exception>
|
|
||||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers)
|
|
||||||
{
|
{
|
||||||
string originalImagePath = image.Path;
|
string originalImagePath = image.Path;
|
||||||
DateTime dateModified = image.DateModified;
|
DateTime dateModified = image.DateModified;
|
||||||
ImageType imageType = image.Type;
|
ImageType imageType = image.Type;
|
||||||
|
|
||||||
// Optimization
|
// Optimization
|
||||||
if (imageEnhancers.Length == 0)
|
if (imageEnhancers.Count == 0)
|
||||||
{
|
{
|
||||||
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N");
|
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
|
// Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
|
||||||
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
|
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
|
||||||
cacheKeys.Add(originalImagePath + dateModified.Ticks);
|
cacheKeys.Add(originalImagePath + dateModified.Ticks);
|
||||||
|
|
||||||
return string.Join("|", cacheKeys).GetMD5().ToString("N");
|
return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||||
@@ -476,7 +461,7 @@ namespace Emby.Drawing
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N");
|
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
|
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||||
@@ -503,16 +488,10 @@ namespace Emby.Drawing
|
|||||||
return (originalImagePath, dateModified);
|
return (originalImagePath, dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the enhanced image.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="imageType">Type of the image.</param>
|
|
||||||
/// <param name="imageIndex">Index of the image.</param>
|
|
||||||
/// <returns>Task{System.String}.</returns>
|
|
||||||
public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
|
public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
|
||||||
{
|
{
|
||||||
var enhancers = GetSupportedEnhancers(item, imageType);
|
var enhancers = GetSupportedEnhancers(item, imageType).ToArray();
|
||||||
|
|
||||||
ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
|
ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
|
||||||
|
|
||||||
@@ -528,7 +507,7 @@ namespace Emby.Drawing
|
|||||||
bool inputImageSupportsTransparency,
|
bool inputImageSupportsTransparency,
|
||||||
BaseItem item,
|
BaseItem item,
|
||||||
int imageIndex,
|
int imageIndex,
|
||||||
IImageEnhancer[] enhancers,
|
IReadOnlyCollection<IImageEnhancer> enhancers,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var originalImagePath = image.Path;
|
var originalImagePath = image.Path;
|
||||||
@@ -569,6 +548,7 @@ namespace Emby.Drawing
|
|||||||
/// <param name="imageIndex">Index of the image.</param>
|
/// <param name="imageIndex">Index of the image.</param>
|
||||||
/// <param name="supportedEnhancers">The supported enhancers.</param>
|
/// <param name="supportedEnhancers">The supported enhancers.</param>
|
||||||
/// <param name="cacheGuid">The cache unique identifier.</param>
|
/// <param name="cacheGuid">The cache unique identifier.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task<System.String>.</returns>
|
/// <returns>Task<System.String>.</returns>
|
||||||
/// <exception cref="ArgumentNullException">
|
/// <exception cref="ArgumentNullException">
|
||||||
/// originalImagePath
|
/// originalImagePath
|
||||||
@@ -580,9 +560,9 @@ namespace Emby.Drawing
|
|||||||
BaseItem item,
|
BaseItem item,
|
||||||
ImageType imageType,
|
ImageType imageType,
|
||||||
int imageIndex,
|
int imageIndex,
|
||||||
IImageEnhancer[] supportedEnhancers,
|
IReadOnlyCollection<IImageEnhancer> supportedEnhancers,
|
||||||
string cacheGuid,
|
string cacheGuid,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(originalImagePath))
|
if (string.IsNullOrEmpty(originalImagePath))
|
||||||
{
|
{
|
||||||
@@ -676,6 +656,7 @@ namespace Emby.Drawing
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(uniqueName))
|
if (string.IsNullOrEmpty(uniqueName))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(uniqueName));
|
throw new ArgumentNullException(nameof(uniqueName));
|
||||||
@@ -718,6 +699,7 @@ namespace Emby.Drawing
|
|||||||
return Path.Combine(path, prefix, filename);
|
return Path.Combine(path, prefix, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void CreateImageCollage(ImageCollageOptions options)
|
public void CreateImageCollage(ImageCollageOptions options)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath);
|
_logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath);
|
||||||
@@ -727,38 +709,25 @@ namespace Emby.Drawing
|
|||||||
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
|
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType)
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
|
||||||
{
|
{
|
||||||
List<IImageEnhancer> list = null;
|
|
||||||
|
|
||||||
foreach (var i in ImageEnhancers)
|
foreach (var i in ImageEnhancers)
|
||||||
{
|
{
|
||||||
try
|
if (i.Supports(item, imageType))
|
||||||
{
|
{
|
||||||
if (i.Supports(item, imageType))
|
yield return i;
|
||||||
{
|
|
||||||
if (list == null)
|
|
||||||
{
|
|
||||||
list = new List<IImageEnhancer>();
|
|
||||||
}
|
|
||||||
list.Add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error in image enhancer: {0}", i.GetType().Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list == null ? Array.Empty<IImageEnhancer>() : list.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
|
|
||||||
private class LockInfo
|
private class LockInfo
|
||||||
{
|
{
|
||||||
public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
|
public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
|
||||||
public int Count = 1;
|
public int Count = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LockInfo GetLock(string key)
|
private LockInfo GetLock(string key)
|
||||||
{
|
{
|
||||||
lock (_locks)
|
lock (_locks)
|
||||||
@@ -791,7 +760,7 @@ namespace Emby.Drawing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _disposed;
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|||||||
@@ -5,38 +5,42 @@ using MediaBrowser.Model.Drawing;
|
|||||||
|
|
||||||
namespace Emby.Drawing
|
namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A fallback implementation of <see cref="IImageEncoder" />.
|
||||||
|
/// </summary>
|
||||||
public class NullImageEncoder : IImageEncoder
|
public class NullImageEncoder : IImageEncoder
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<string> SupportedInputFormats
|
public IReadOnlyCollection<string> SupportedInputFormats
|
||||||
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
|
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
||||||
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
|
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
|
||||||
|
|
||||||
public void CropWhiteSpace(string inputPath, string outputPath)
|
/// <inheritdoc />
|
||||||
{
|
public string Name => "Null Image Encoder";
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool SupportsImageCollageCreation => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool SupportsImageEncoding => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ImageDimensions GetImageSize(string path)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
|
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void CreateImageCollage(ImageCollageOptions options)
|
public void CreateImageCollage(ImageCollageOptions options)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Null Image Encoder";
|
|
||||||
|
|
||||||
public bool SupportsImageCollageCreation => false;
|
|
||||||
|
|
||||||
public bool SupportsImageEncoding => false;
|
|
||||||
|
|
||||||
public ImageDimensions GetImageSize(string path)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
|
|
||||||
# mstest test results
|
|
||||||
TestResults
|
|
||||||
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Rr]elease/
|
|
||||||
x64/
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
*.ncrunch*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.Publish.xml
|
|
||||||
|
|
||||||
# NuGet Packages Directory
|
|
||||||
packages
|
|
||||||
|
|
||||||
# Windows Azure Build Output
|
|
||||||
csx
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Windows Store app package directory
|
|
||||||
AppPackages/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
[Bb]in
|
|
||||||
[Oo]bj
|
|
||||||
sql
|
|
||||||
TestResults
|
|
||||||
[Tt]est[Rr]esult*
|
|
||||||
*.Cache
|
|
||||||
ClientBin
|
|
||||||
[Ss]tyle[Cc]op.*
|
|
||||||
~$*
|
|
||||||
*.dbmdl
|
|
||||||
Generated_Code #added for RIA/Silverlight projects
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file to a newer
|
|
||||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 15
|
|
||||||
VisualStudioVersion = 15.0.27004.2009
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IsoMounter", "IsoMounter\IsoMounter.csproj", "{B94C929C-6552-4620-9BE5-422DD9A151BA}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {C0E8EAD1-E4D7-44CD-B801-03BD12F30B1B}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using MediaBrowser.Model.Plugins;
|
|
||||||
|
|
||||||
namespace IsoMounter.Configuration
|
|
||||||
{
|
|
||||||
public class PluginConfiguration : BasePluginConfiguration
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="..\..\SharedVersion.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
|
||||||
<ProjectReference Include="..\..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,481 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace IsoMounter
|
|
||||||
{
|
|
||||||
public class LinuxIsoManager : IIsoMounter
|
|
||||||
{
|
|
||||||
[DllImport("libc", SetLastError = true)]
|
|
||||||
static extern uint getuid();
|
|
||||||
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private readonly IEnvironmentInfo EnvironmentInfo;
|
|
||||||
private readonly bool ExecutablesAvailable;
|
|
||||||
private readonly IFileSystem FileSystem;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly string MountCommand;
|
|
||||||
private readonly string MountPointRoot;
|
|
||||||
private readonly IProcessFactory ProcessFactory;
|
|
||||||
private readonly string SudoCommand;
|
|
||||||
private readonly string UmountCommand;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructor(s)
|
|
||||||
|
|
||||||
public LinuxIsoManager(ILogger logger, IFileSystem fileSystem, IEnvironmentInfo environment, IProcessFactory processFactory)
|
|
||||||
{
|
|
||||||
|
|
||||||
EnvironmentInfo = environment;
|
|
||||||
FileSystem = fileSystem;
|
|
||||||
_logger = logger;
|
|
||||||
ProcessFactory = processFactory;
|
|
||||||
|
|
||||||
MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{0}] System PATH is currently set to [{1}].",
|
|
||||||
Name,
|
|
||||||
Environment.GetEnvironmentVariable("PATH") ?? ""
|
|
||||||
);
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{0}] System path separator is [{1}].",
|
|
||||||
Name,
|
|
||||||
Path.PathSeparator
|
|
||||||
);
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{0}] Mount point root is [{1}].",
|
|
||||||
Name,
|
|
||||||
MountPointRoot
|
|
||||||
);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Get the location of the executables we need to support mounting/unmounting ISO images.
|
|
||||||
//
|
|
||||||
|
|
||||||
SudoCommand = GetFullPathForExecutable("sudo");
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] Using version of [sudo] located at [{1}].",
|
|
||||||
Name,
|
|
||||||
SudoCommand
|
|
||||||
);
|
|
||||||
|
|
||||||
MountCommand = GetFullPathForExecutable("mount");
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] Using version of [mount] located at [{1}].",
|
|
||||||
Name,
|
|
||||||
MountCommand
|
|
||||||
);
|
|
||||||
|
|
||||||
UmountCommand = GetFullPathForExecutable("umount");
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] Using version of [umount] located at [{1}].",
|
|
||||||
Name,
|
|
||||||
UmountCommand
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(SudoCommand) && !string.IsNullOrEmpty(MountCommand) && !string.IsNullOrEmpty(UmountCommand))
|
|
||||||
{
|
|
||||||
ExecutablesAvailable = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExecutablesAvailable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Interface Implementation for IIsoMounter
|
|
||||||
|
|
||||||
public bool IsInstalled => true;
|
|
||||||
|
|
||||||
public string Name => "LinuxMount";
|
|
||||||
|
|
||||||
public bool RequiresInstallation => false;
|
|
||||||
|
|
||||||
public bool CanMount(string path)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Linux)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}], Executables Available = [{4}].",
|
|
||||||
Name,
|
|
||||||
path,
|
|
||||||
Path.GetExtension(path),
|
|
||||||
EnvironmentInfo.OperatingSystem,
|
|
||||||
ExecutablesAvailable
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ExecutablesAvailable)
|
|
||||||
{
|
|
||||||
return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Install(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.FromResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<IIsoMount> Mount(string isoPath, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (MountISO(isoPath, out LinuxMount mountedISO))
|
|
||||||
{
|
|
||||||
return Task.FromResult<IIsoMount>(mountedISO);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new IOException(string.Format(
|
|
||||||
"An error occurred trying to mount image [$0].",
|
|
||||||
isoPath
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Interface Implementation for IDisposable
|
|
||||||
|
|
||||||
// Flag: Has Dispose already been called?
|
|
||||||
private bool disposed = false;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
|
|
||||||
// Dispose of unmanaged resources.
|
|
||||||
Dispose(true);
|
|
||||||
|
|
||||||
// Suppress finalization.
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] Disposing [{1}].",
|
|
||||||
Name,
|
|
||||||
disposing
|
|
||||||
);
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
|
|
||||||
//
|
|
||||||
// Free managed objects here.
|
|
||||||
//
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Free any unmanaged objects here.
|
|
||||||
//
|
|
||||||
|
|
||||||
disposed = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private string GetFullPathForExecutable(string name)
|
|
||||||
{
|
|
||||||
|
|
||||||
foreach (string test in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator))
|
|
||||||
{
|
|
||||||
string path = test.Trim();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name)))
|
|
||||||
{
|
|
||||||
return Path.GetFullPath(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private uint GetUID()
|
|
||||||
{
|
|
||||||
|
|
||||||
var uid = getuid();
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{0}] GetUserId() returned [{2}].",
|
|
||||||
Name,
|
|
||||||
uid
|
|
||||||
);
|
|
||||||
|
|
||||||
return uid;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ExecuteCommand(string cmdFilename, string cmdArguments)
|
|
||||||
{
|
|
||||||
|
|
||||||
bool processFailed = false;
|
|
||||||
|
|
||||||
var process = ProcessFactory.Create(
|
|
||||||
new ProcessOptions
|
|
||||||
{
|
|
||||||
CreateNoWindow = true,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
FileName = cmdFilename,
|
|
||||||
Arguments = cmdArguments,
|
|
||||||
IsHidden = true,
|
|
||||||
ErrorDialog = false,
|
|
||||||
EnableRaisingEvents = true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process.Start();
|
|
||||||
|
|
||||||
//StreamReader outputReader = process.StandardOutput.;
|
|
||||||
//StreamReader errorReader = process.StandardError;
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{Name}] Standard output from process is [{Error}].",
|
|
||||||
Name,
|
|
||||||
process.StandardOutput.ReadToEnd()
|
|
||||||
);
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{Name}] Standard error from process is [{Error}].",
|
|
||||||
Name,
|
|
||||||
process.StandardError.ReadToEnd()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
processFailed = true;
|
|
||||||
_logger.LogDebug(ex, "[{Name}] Unhandled exception executing command.", Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!processFailed && process.ExitCode == 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool MountISO(string isoPath, out LinuxMount mountedISO)
|
|
||||||
{
|
|
||||||
|
|
||||||
string cmdArguments;
|
|
||||||
string cmdFilename;
|
|
||||||
string mountPoint = Path.Combine(MountPointRoot, Guid.NewGuid().ToString());
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(isoPath))
|
|
||||||
{
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{Name}] Attempting to mount [{Path}].",
|
|
||||||
Name,
|
|
||||||
isoPath
|
|
||||||
);
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{Name}] ISO will be mounted at [{Path}].",
|
|
||||||
Name,
|
|
||||||
mountPoint
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
throw new ArgumentNullException(nameof(isoPath));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(mountPoint);
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException)
|
|
||||||
{
|
|
||||||
throw new IOException("Unable to create mount point(Permission denied) for " + isoPath);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
throw new IOException("Unable to create mount point for " + isoPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GetUID() == 0)
|
|
||||||
{
|
|
||||||
cmdFilename = MountCommand;
|
|
||||||
cmdArguments = string.Format("\"{0}\" \"{1}\"", isoPath, mountPoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cmdFilename = SudoCommand;
|
|
||||||
cmdArguments = string.Format("\"{0}\" \"{1}\" \"{2}\"", MountCommand, isoPath, mountPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{0}] Mount command [{1}], mount arguments [{2}].",
|
|
||||||
Name,
|
|
||||||
cmdFilename,
|
|
||||||
cmdArguments
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ExecuteCommand(cmdFilename, cmdArguments))
|
|
||||||
{
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] ISO mount completed successfully.",
|
|
||||||
Name
|
|
||||||
);
|
|
||||||
|
|
||||||
mountedISO = new LinuxMount(this, isoPath, mountPoint);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] ISO mount completed with errors.",
|
|
||||||
Name
|
|
||||||
);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.Delete(mountPoint, false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
mountedISO = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return mountedISO != null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnmountISO(LinuxMount mount)
|
|
||||||
{
|
|
||||||
|
|
||||||
string cmdArguments;
|
|
||||||
string cmdFilename;
|
|
||||||
|
|
||||||
if (mount != null)
|
|
||||||
{
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].",
|
|
||||||
Name,
|
|
||||||
mount.IsoPath,
|
|
||||||
mount.MountedPath
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
throw new ArgumentNullException(nameof(mount));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GetUID() == 0)
|
|
||||||
{
|
|
||||||
cmdFilename = UmountCommand;
|
|
||||||
cmdArguments = string.Format("\"{0}\"", mount.MountedPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cmdFilename = SudoCommand;
|
|
||||||
cmdArguments = string.Format("\"{0}\" \"{1}\"", UmountCommand, mount.MountedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
|
||||||
"[{0}] Umount command [{1}], umount arguments [{2}].",
|
|
||||||
Name,
|
|
||||||
cmdFilename,
|
|
||||||
cmdArguments
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ExecuteCommand(cmdFilename, cmdArguments))
|
|
||||||
{
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] ISO unmount completed successfully.",
|
|
||||||
Name
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"[{0}] ISO unmount completed with errors.",
|
|
||||||
Name
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.Delete(mount.MountedPath, false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Internal Methods
|
|
||||||
|
|
||||||
internal void OnUnmount(LinuxMount mount)
|
|
||||||
{
|
|
||||||
|
|
||||||
UnmountISO(mount);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using System;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
|
|
||||||
namespace IsoMounter
|
|
||||||
{
|
|
||||||
internal class LinuxMount : IIsoMount
|
|
||||||
{
|
|
||||||
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private readonly LinuxIsoManager linuxIsoManager;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructor(s)
|
|
||||||
|
|
||||||
internal LinuxMount(LinuxIsoManager isoManager, string isoPath, string mountFolder)
|
|
||||||
{
|
|
||||||
|
|
||||||
linuxIsoManager = isoManager;
|
|
||||||
|
|
||||||
IsoPath = isoPath;
|
|
||||||
MountedPath = mountFolder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Interface Implementation for IDisposable
|
|
||||||
|
|
||||||
// Flag: Has Dispose already been called?
|
|
||||||
private bool disposed = false;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
|
|
||||||
// Dispose of unmanaged resources.
|
|
||||||
Dispose(true);
|
|
||||||
|
|
||||||
// Suppress finalization.
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
|
|
||||||
//
|
|
||||||
// Free managed objects here.
|
|
||||||
//
|
|
||||||
|
|
||||||
linuxIsoManager.OnUnmount(this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Free any unmanaged objects here.
|
|
||||||
//
|
|
||||||
|
|
||||||
disposed = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Interface Implementation for IIsoMount
|
|
||||||
|
|
||||||
public string IsoPath { get; private set; }
|
|
||||||
public string MountedPath { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using IsoMounter.Configuration;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Plugins;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
|
|
||||||
namespace IsoMounter
|
|
||||||
{
|
|
||||||
public class Plugin : BasePlugin<PluginConfiguration>
|
|
||||||
{
|
|
||||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private Guid _id = new Guid("4682DD4C-A675-4F1B-8E7C-79ADF137A8F8");
|
|
||||||
public override Guid Id => _id;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the plugin
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public override string Name => "Iso Mounter";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The description.</value>
|
|
||||||
public override string Description => "Mount and stream ISO contents";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Resources;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("IsoMounter")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
|
||||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 2, June 1991
|
|
||||||
|
|
||||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
License is intended to guarantee your freedom to share and change free
|
|
||||||
software--to make sure the software is free for all its users. This
|
|
||||||
General Public License applies to most of the Free Software
|
|
||||||
Foundation's software and to any other program whose authors commit to
|
|
||||||
using it. (Some other Free Software Foundation software is covered by
|
|
||||||
the GNU Lesser General Public License instead.) You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
this service if you wish), that you receive source code or can get it
|
|
||||||
if you want it, that you can change the software or use pieces of it
|
|
||||||
in new free programs; and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
anyone to deny you these rights or to ask you to surrender the rights.
|
|
||||||
These restrictions translate to certain responsibilities for you if you
|
|
||||||
distribute copies of the software, or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must give the recipients all the rights that
|
|
||||||
you have. You must make sure that they, too, receive or can get the
|
|
||||||
source code. And you must show them these terms so they know their
|
|
||||||
rights.
|
|
||||||
|
|
||||||
We protect your rights with two steps: (1) copyright the software, and
|
|
||||||
(2) offer you this license which gives you legal permission to copy,
|
|
||||||
distribute and/or modify the software.
|
|
||||||
|
|
||||||
Also, for each author's protection and ours, we want to make certain
|
|
||||||
that everyone understands that there is no warranty for this free
|
|
||||||
software. If the software is modified by someone else and passed on, we
|
|
||||||
want its recipients to know that what they have is not the original, so
|
|
||||||
that any problems introduced by others will not reflect on the original
|
|
||||||
authors' reputations.
|
|
||||||
|
|
||||||
Finally, any free program is threatened constantly by software
|
|
||||||
patents. We wish to avoid the danger that redistributors of a free
|
|
||||||
program will individually obtain patent licenses, in effect making the
|
|
||||||
program proprietary. To prevent this, we have made it clear that any
|
|
||||||
patent must be licensed for everyone's free use or not licensed at all.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
|
||||||
a notice placed by the copyright holder saying it may be distributed
|
|
||||||
under the terms of this General Public License. The "Program", below,
|
|
||||||
refers to any such program or work, and a "work based on the Program"
|
|
||||||
means either the Program or any derivative work under copyright law:
|
|
||||||
that is to say, a work containing the Program or a portion of it,
|
|
||||||
either verbatim or with modifications and/or translated into another
|
|
||||||
language. (Hereinafter, translation is included without limitation in
|
|
||||||
the term "modification".) Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running the Program is not restricted, and the output from the Program
|
|
||||||
is covered only if its contents constitute a work based on the
|
|
||||||
Program (independent of having been made by running the Program).
|
|
||||||
Whether that is true depends on what the Program does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Program's
|
|
||||||
source code as you receive it, in any medium, provided that you
|
|
||||||
conspicuously and appropriately publish on each copy an appropriate
|
|
||||||
copyright notice and disclaimer of warranty; keep intact all the
|
|
||||||
notices that refer to this License and to the absence of any warranty;
|
|
||||||
and give any other recipients of the Program a copy of this License
|
|
||||||
along with the Program.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and
|
|
||||||
you may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Program or any portion
|
|
||||||
of it, thus forming a work based on the Program, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) You must cause the modified files to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
b) You must cause any work that you distribute or publish, that in
|
|
||||||
whole or in part contains or is derived from the Program or any
|
|
||||||
part thereof, to be licensed as a whole at no charge to all third
|
|
||||||
parties under the terms of this License.
|
|
||||||
|
|
||||||
c) If the modified program normally reads commands interactively
|
|
||||||
when run, you must cause it, when started running for such
|
|
||||||
interactive use in the most ordinary way, to print or display an
|
|
||||||
announcement including an appropriate copyright notice and a
|
|
||||||
notice that there is no warranty (or else, saying that you provide
|
|
||||||
a warranty) and that users may redistribute the program under
|
|
||||||
these conditions, and telling the user how to view a copy of this
|
|
||||||
License. (Exception: if the Program itself is interactive but
|
|
||||||
does not normally print such an announcement, your work based on
|
|
||||||
the Program is not required to print an announcement.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Program,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Program, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Program.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Program
|
|
||||||
with the Program (or with a work based on the Program) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may copy and distribute the Program (or a work based on it,
|
|
||||||
under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you also do one of the following:
|
|
||||||
|
|
||||||
a) Accompany it with the complete corresponding machine-readable
|
|
||||||
source code, which must be distributed under the terms of Sections
|
|
||||||
1 and 2 above on a medium customarily used for software interchange; or,
|
|
||||||
|
|
||||||
b) Accompany it with a written offer, valid for at least three
|
|
||||||
years, to give any third party, for a charge no more than your
|
|
||||||
cost of physically performing source distribution, a complete
|
|
||||||
machine-readable copy of the corresponding source code, to be
|
|
||||||
distributed under the terms of Sections 1 and 2 above on a medium
|
|
||||||
customarily used for software interchange; or,
|
|
||||||
|
|
||||||
c) Accompany it with the information you received as to the offer
|
|
||||||
to distribute corresponding source code. (This alternative is
|
|
||||||
allowed only for noncommercial distribution and only if you
|
|
||||||
received the program in object code or executable form with such
|
|
||||||
an offer, in accord with Subsection b above.)
|
|
||||||
|
|
||||||
The source code for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For an executable work, complete source
|
|
||||||
code means all the source code for all modules it contains, plus any
|
|
||||||
associated interface definition files, plus the scripts used to
|
|
||||||
control compilation and installation of the executable. However, as a
|
|
||||||
special exception, the source code distributed need not include
|
|
||||||
anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the
|
|
||||||
operating system on which the executable runs, unless that component
|
|
||||||
itself accompanies the executable.
|
|
||||||
|
|
||||||
If distribution of executable or object code is made by offering
|
|
||||||
access to copy from a designated place, then offering equivalent
|
|
||||||
access to copy the source code from the same place counts as
|
|
||||||
distribution of the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
4. You may not copy, modify, sublicense, or distribute the Program
|
|
||||||
except as expressly provided under this License. Any attempt
|
|
||||||
otherwise to copy, modify, sublicense or distribute the Program is
|
|
||||||
void, and will automatically terminate your rights under this License.
|
|
||||||
However, parties who have received copies, or rights, from you under
|
|
||||||
this License will not have their licenses terminated so long as such
|
|
||||||
parties remain in full compliance.
|
|
||||||
|
|
||||||
5. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Program or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Program (or any work based on the
|
|
||||||
Program), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Program or works based on it.
|
|
||||||
|
|
||||||
6. Each time you redistribute the Program (or any work based on the
|
|
||||||
Program), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute or modify the Program subject to
|
|
||||||
these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties to
|
|
||||||
this License.
|
|
||||||
|
|
||||||
7. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Program at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Program by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Program.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under
|
|
||||||
any particular circumstance, the balance of the section is intended to
|
|
||||||
apply and the section as a whole is intended to apply in other
|
|
||||||
circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system, which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
8. If the distribution and/or use of the Program is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Program under this License
|
|
||||||
may add an explicit geographical distribution limitation excluding
|
|
||||||
those countries, so that distribution is permitted only in or among
|
|
||||||
countries not thus excluded. In such case, this License incorporates
|
|
||||||
the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
9. The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Program
|
|
||||||
specifies a version number of this License which applies to it and "any
|
|
||||||
later version", you have the option of following the terms and conditions
|
|
||||||
either of that version or of any later version published by the Free
|
|
||||||
Software Foundation. If the Program does not specify a version number of
|
|
||||||
this License, you may choose any version ever published by the Free Software
|
|
||||||
Foundation.
|
|
||||||
|
|
||||||
10. If you wish to incorporate parts of the Program into other free
|
|
||||||
programs whose distribution conditions are different, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free
|
|
||||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
|
||||||
make exceptions for this. Our decision will be guided by the two goals
|
|
||||||
of preserving the free status of all derivatives of our free software and
|
|
||||||
of promoting the sharing and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
|
||||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
|
||||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
|
||||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
|
||||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
|
||||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
|
||||||
REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
|
||||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
|
||||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
|
||||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
|
||||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|
||||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
{description}
|
|
||||||
Copyright (C) {year} {fullname}
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program is interactive, make it output a short notice like this
|
|
||||||
when it starts in an interactive mode:
|
|
||||||
|
|
||||||
Gnomovision version 69, Copyright (C) year name of author
|
|
||||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, the commands you use may
|
|
||||||
be called something other than `show w' and `show c'; they could even be
|
|
||||||
mouse-clicks or menu items--whatever suits your program.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
|
||||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
|
||||||
|
|
||||||
{signature of Ty Coon}, 1 April 1989
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
This General Public License does not permit incorporating your program into
|
|
||||||
proprietary programs. If your program is a subroutine library, you may
|
|
||||||
consider it more useful to permit linking proprietary applications with the
|
|
||||||
library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# MediaBrowser.IsoMounting.Linux
|
|
||||||
This implements two core interfaces, IIsoManager, and IIsoMount.
|
|
||||||
### IIsoManager
|
|
||||||
The manager class can be used to create a mount, and also determine if the mounter is capable of mounting a given file.
|
|
||||||
### IIsoMount
|
|
||||||
IIsoMount then represents a mount instance, which will be unmounted on disposal.
|
|
||||||
***
|
|
||||||
This Linux version use sudo, mount and umount.
|
|
||||||
|
|
||||||
You need to add this to your sudo file via visudo(change the username):
|
|
||||||
|
|
||||||
Defaults:jsmith !requiretty
|
|
||||||
jsmith ALL=(root) NOPASSWD: /bin/mount
|
|
||||||
jsmith ALL=(root) NOPASSWD: /bin/umount
|
|
||||||
@@ -33,27 +33,29 @@ namespace Emby.Naming.Audio
|
|||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
// Remove whitespace
|
// Remove whitespace
|
||||||
filename = filename.Replace("-", " ");
|
filename = filename.Replace('-', ' ');
|
||||||
filename = filename.Replace(".", " ");
|
filename = filename.Replace('.', ' ');
|
||||||
filename = filename.Replace("(", " ");
|
filename = filename.Replace('(', ' ');
|
||||||
filename = filename.Replace(")", " ");
|
filename = filename.Replace(')', ' ');
|
||||||
filename = Regex.Replace(filename, @"\s+", " ");
|
filename = Regex.Replace(filename, @"\s+", " ");
|
||||||
|
|
||||||
filename = filename.TrimStart();
|
filename = filename.TrimStart();
|
||||||
|
|
||||||
foreach (var prefix in _options.AlbumStackingPrefixes)
|
foreach (var prefix in _options.AlbumStackingPrefixes)
|
||||||
{
|
{
|
||||||
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) == 0)
|
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
|
||||||
{
|
{
|
||||||
var tmp = filename.Substring(prefix.Length);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
|
var tmp = filename.Substring(prefix.Length);
|
||||||
|
|
||||||
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
|
||||||
{
|
|
||||||
result.IsMultiPart = true;
|
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||||
break;
|
{
|
||||||
}
|
result.IsMultiPart = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ namespace Emby.Naming.Audio
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the part.
|
/// Gets or sets the part.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The part.</value>
|
/// <value>The part.</value>
|
||||||
public string Part { get; set; }
|
public string Part { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is multi part.
|
/// Gets or sets a value indicating whether this instance is multi part.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -12,35 +12,56 @@ namespace Emby.Naming.AudioBook
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the container.
|
/// Gets or sets the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The container.</value>
|
/// <value>The container.</value>
|
||||||
public string Container { get; set; }
|
public string Container { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the part number.
|
/// Gets or sets the part number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The part number.</value>
|
/// <value>The part number.</value>
|
||||||
public int? PartNumber { get; set; }
|
public int? PartNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the chapter number.
|
/// Gets or sets the chapter number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The chapter number.</value>
|
/// <value>The chapter number.</value>
|
||||||
public int? ChapterNumber { get; set; }
|
public int? ChapterNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type.
|
/// Gets or sets the type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public int CompareTo(AudioBookFileInfo other)
|
public int CompareTo(AudioBookFileInfo other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(this, other)) return 0;
|
if (ReferenceEquals(this, other))
|
||||||
if (ReferenceEquals(null, other)) return 1;
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber);
|
var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber);
|
||||||
if (chapterNumberComparison != 0) return chapterNumberComparison;
|
if (chapterNumberComparison != 0)
|
||||||
|
{
|
||||||
|
return chapterNumberComparison;
|
||||||
|
}
|
||||||
|
|
||||||
var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber);
|
var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber);
|
||||||
if (partNumberComparison != 0) return partNumberComparison;
|
if (partNumberComparison != 0)
|
||||||
|
{
|
||||||
|
return partNumberComparison;
|
||||||
|
}
|
||||||
|
|
||||||
return string.Compare(Path, other.Path, StringComparison.Ordinal);
|
return string.Compare(Path, other.Path, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -14,14 +15,13 @@ namespace Emby.Naming.AudioBook
|
|||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AudioBookFilePathParserResult Parse(string path, bool IsDirectory)
|
public AudioBookFilePathParserResult Parse(string path)
|
||||||
{
|
{
|
||||||
var result = Parse(path);
|
if (path == null)
|
||||||
return !result.Success ? new AudioBookFilePathParserResult() : result;
|
{
|
||||||
}
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
private AudioBookFilePathParserResult Parse(string path)
|
|
||||||
{
|
|
||||||
var result = new AudioBookFilePathParserResult();
|
var result = new AudioBookFilePathParserResult();
|
||||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||||
foreach (var expression in _options.AudioBookPartsExpressions)
|
foreach (var expression in _options.AudioBookPartsExpressions)
|
||||||
@@ -40,6 +40,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.PartNumber.HasValue)
|
if (!result.PartNumber.HasValue)
|
||||||
{
|
{
|
||||||
var value = match.Groups["part"];
|
var value = match.Groups["part"];
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ namespace Emby.Naming.AudioBook
|
|||||||
public class AudioBookFilePathParserResult
|
public class AudioBookFilePathParserResult
|
||||||
{
|
{
|
||||||
public int? PartNumber { get; set; }
|
public int? PartNumber { get; set; }
|
||||||
|
|
||||||
public int? ChapterNumber { get; set; }
|
public int? ChapterNumber { get; set; }
|
||||||
|
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,33 +7,40 @@ namespace Emby.Naming.AudioBook
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioBookInfo
|
public class AudioBookInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name { get; set; }
|
|
||||||
public int? Year { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the files.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The files.</value>
|
|
||||||
public List<AudioBookFileInfo> Files { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the extras.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The extras.</value>
|
|
||||||
public List<AudioBookFileInfo> Extras { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the alternate versions.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The alternate versions.</value>
|
|
||||||
public List<AudioBookFileInfo> AlternateVersions { get; set; }
|
|
||||||
|
|
||||||
public AudioBookInfo()
|
public AudioBookInfo()
|
||||||
{
|
{
|
||||||
Files = new List<AudioBookFileInfo>();
|
Files = new List<AudioBookFileInfo>();
|
||||||
Extras = new List<AudioBookFileInfo>();
|
Extras = new List<AudioBookFileInfo>();
|
||||||
AlternateVersions = new List<AudioBookFileInfo>();
|
AlternateVersions = new List<AudioBookFileInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the year.
|
||||||
|
/// </summary>
|
||||||
|
public int? Year { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the files.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The files.</value>
|
||||||
|
public List<AudioBookFileInfo> Files { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the extras.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The extras.</value>
|
||||||
|
public List<AudioBookFileInfo> Extras { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alternate versions.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The alternate versions.</value>
|
||||||
|
public List<AudioBookFileInfo> AlternateVersions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<AudioBookInfo> Resolve(List<FileSystemMetadata> files)
|
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
var audioBookResolver = new AudioBookResolver(_options);
|
var audioBookResolver = new AudioBookResolver(_options);
|
||||||
|
|
||||||
|
|||||||
@@ -24,19 +24,21 @@ namespace Emby.Naming.AudioBook
|
|||||||
return Resolve(path, true);
|
return Resolve(path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AudioBookFileInfo Resolve(string path, bool IsDirectory = false)
|
public AudioBookFileInfo Resolve(string path, bool isDirectory = false)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDirectory) // TODO
|
// TODO
|
||||||
|
if (isDirectory)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -45,8 +47,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
|
|
||||||
var container = extension.TrimStart('.');
|
var container = extension.TrimStart('.');
|
||||||
|
|
||||||
var parsingResult = new AudioBookFilePathParser(_options)
|
var parsingResult = new AudioBookFilePathParser(_options).Parse(path);
|
||||||
.Parse(path, IsDirectory);
|
|
||||||
|
|
||||||
return new AudioBookFileInfo
|
return new AudioBookFileInfo
|
||||||
{
|
{
|
||||||
@@ -54,7 +55,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
Container = container,
|
Container = container,
|
||||||
PartNumber = parsingResult.PartNumber,
|
PartNumber = parsingResult.PartNumber,
|
||||||
ChapterNumber = parsingResult.ChapterNumber,
|
ChapterNumber = parsingResult.ChapterNumber,
|
||||||
IsDirectory = IsDirectory
|
IsDirectory = isDirectory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,28 @@ namespace Emby.Naming.Common
|
|||||||
public class EpisodeExpression
|
public class EpisodeExpression
|
||||||
{
|
{
|
||||||
private string _expression;
|
private string _expression;
|
||||||
public string Expression { get => _expression;
|
private Regex _regex;
|
||||||
set { _expression = value; _regex = null; } }
|
|
||||||
|
public string Expression
|
||||||
|
{
|
||||||
|
get => _expression;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_expression = value;
|
||||||
|
_regex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsByDate { get; set; }
|
public bool IsByDate { get; set; }
|
||||||
|
|
||||||
public bool IsOptimistic { get; set; }
|
public bool IsOptimistic { get; set; }
|
||||||
|
|
||||||
public bool IsNamed { get; set; }
|
public bool IsNamed { get; set; }
|
||||||
|
|
||||||
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
|
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
|
||||||
|
|
||||||
public string[] DateTimeFormats { get; set; }
|
public string[] DateTimeFormats { get; set; }
|
||||||
|
|
||||||
private Regex _regex;
|
|
||||||
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
||||||
|
|
||||||
public EpisodeExpression(string expression, bool byDate)
|
public EpisodeExpression(string expression, bool byDate)
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ namespace Emby.Naming.Common
|
|||||||
/// The audio
|
/// The audio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Audio = 0,
|
Audio = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The photo
|
/// The photo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Photo = 1,
|
Photo = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The video
|
/// The video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,19 +8,25 @@ namespace Emby.Naming.Common
|
|||||||
public class NamingOptions
|
public class NamingOptions
|
||||||
{
|
{
|
||||||
public string[] AudioFileExtensions { get; set; }
|
public string[] AudioFileExtensions { get; set; }
|
||||||
|
|
||||||
public string[] AlbumStackingPrefixes { get; set; }
|
public string[] AlbumStackingPrefixes { get; set; }
|
||||||
|
|
||||||
public string[] SubtitleFileExtensions { get; set; }
|
public string[] SubtitleFileExtensions { get; set; }
|
||||||
|
|
||||||
public char[] SubtitleFlagDelimiters { get; set; }
|
public char[] SubtitleFlagDelimiters { get; set; }
|
||||||
|
|
||||||
public string[] SubtitleForcedFlags { get; set; }
|
public string[] SubtitleForcedFlags { get; set; }
|
||||||
|
|
||||||
public string[] SubtitleDefaultFlags { get; set; }
|
public string[] SubtitleDefaultFlags { get; set; }
|
||||||
|
|
||||||
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
||||||
|
|
||||||
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
||||||
|
|
||||||
public string[] EpisodeMultiPartExpressions { get; set; }
|
public string[] EpisodeMultiPartExpressions { get; set; }
|
||||||
|
|
||||||
public string[] VideoFileExtensions { get; set; }
|
public string[] VideoFileExtensions { get; set; }
|
||||||
|
|
||||||
public string[] StubFileExtensions { get; set; }
|
public string[] StubFileExtensions { get; set; }
|
||||||
|
|
||||||
public string[] AudioBookPartsExpressions { get; set; }
|
public string[] AudioBookPartsExpressions { get; set; }
|
||||||
@@ -28,12 +34,14 @@ namespace Emby.Naming.Common
|
|||||||
public StubTypeRule[] StubTypes { get; set; }
|
public StubTypeRule[] StubTypes { get; set; }
|
||||||
|
|
||||||
public char[] VideoFlagDelimiters { get; set; }
|
public char[] VideoFlagDelimiters { get; set; }
|
||||||
|
|
||||||
public Format3DRule[] Format3DRules { get; set; }
|
public Format3DRule[] Format3DRules { get; set; }
|
||||||
|
|
||||||
public string[] VideoFileStackingExpressions { get; set; }
|
public string[] VideoFileStackingExpressions { get; set; }
|
||||||
public string[] CleanDateTimes { get; set; }
|
|
||||||
public string[] CleanStrings { get; set; }
|
|
||||||
|
|
||||||
|
public string[] CleanDateTimes { get; set; }
|
||||||
|
|
||||||
|
public string[] CleanStrings { get; set; }
|
||||||
|
|
||||||
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
||||||
|
|
||||||
@@ -41,7 +49,7 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
public NamingOptions()
|
public NamingOptions()
|
||||||
{
|
{
|
||||||
VideoFileExtensions = new string[]
|
VideoFileExtensions = new[]
|
||||||
{
|
{
|
||||||
".m4v",
|
".m4v",
|
||||||
".3gp",
|
".3gp",
|
||||||
@@ -106,53 +114,53 @@ namespace Emby.Naming.Common
|
|||||||
{
|
{
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "dvd",
|
StubType = "dvd",
|
||||||
Token = "dvd"
|
Token = "dvd"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "hddvd",
|
StubType = "hddvd",
|
||||||
Token = "hddvd"
|
Token = "hddvd"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "bluray"
|
Token = "bluray"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "brrip"
|
Token = "brrip"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "bd25"
|
Token = "bd25"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "bluray",
|
StubType = "bluray",
|
||||||
Token = "bd50"
|
Token = "bd50"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "vhs",
|
StubType = "vhs",
|
||||||
Token = "vhs"
|
Token = "vhs"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "tv",
|
StubType = "tv",
|
||||||
Token = "HDTV"
|
Token = "HDTV"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "tv",
|
StubType = "tv",
|
||||||
Token = "PDTV"
|
Token = "PDTV"
|
||||||
},
|
},
|
||||||
new StubTypeRule
|
new StubTypeRule
|
||||||
{
|
{
|
||||||
StubType = "tv",
|
StubType = "tv",
|
||||||
Token = "DSR"
|
Token = "DSR"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,7 +294,7 @@ namespace Emby.Naming.Common
|
|||||||
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
||||||
new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
|
new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new []
|
DateTimeFormats = new[]
|
||||||
{
|
{
|
||||||
"yyyy.MM.dd",
|
"yyyy.MM.dd",
|
||||||
"yyyy-MM-dd",
|
"yyyy-MM-dd",
|
||||||
@@ -295,7 +303,7 @@ namespace Emby.Naming.Common
|
|||||||
},
|
},
|
||||||
new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
|
new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new []
|
DateTimeFormats = new[]
|
||||||
{
|
{
|
||||||
"dd.MM.yyyy",
|
"dd.MM.yyyy",
|
||||||
"dd-MM-yyyy",
|
"dd-MM-yyyy",
|
||||||
@@ -303,6 +311,14 @@ namespace Emby.Naming.Common
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||||
|
// so we make sure this one gets tested first.
|
||||||
|
// "Foo Bar 889"
|
||||||
|
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
|
||||||
|
{
|
||||||
|
IsNamed = true
|
||||||
|
},
|
||||||
|
|
||||||
new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
|
new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
|
||||||
{
|
{
|
||||||
SupportsAbsoluteEpisodeNumbers = true
|
SupportsAbsoluteEpisodeNumbers = true
|
||||||
@@ -320,37 +336,40 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
// *** End Kodi Standard Naming
|
// *** End Kodi Standard Naming
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$")
|
// [bar] Foo - 1 [baz]
|
||||||
|
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
|
||||||
|
{
|
||||||
|
IsNamed = true
|
||||||
|
},
|
||||||
|
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01.avi"
|
// "01.avi"
|
||||||
new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$")
|
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "1-12 episode title"
|
// "1-12 episode title"
|
||||||
new EpisodeExpression(@"([0-9]+)-([0-9]+)")
|
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
||||||
{
|
|
||||||
},
|
|
||||||
|
|
||||||
// "01 - blah.avi", "01-blah.avi"
|
// "01 - blah.avi", "01-blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
|
||||||
@@ -427,7 +446,7 @@ namespace Emby.Naming.Common
|
|||||||
Token = "_trailer",
|
Token = "_trailer",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
},
|
},
|
||||||
new ExtraRule
|
new ExtraRule
|
||||||
{
|
{
|
||||||
ExtraType = "trailer",
|
ExtraType = "trailer",
|
||||||
RuleType = ExtraRuleType.Suffix,
|
RuleType = ExtraRuleType.Suffix,
|
||||||
@@ -462,7 +481,7 @@ namespace Emby.Naming.Common
|
|||||||
Token = "_sample",
|
Token = "_sample",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
},
|
},
|
||||||
new ExtraRule
|
new ExtraRule
|
||||||
{
|
{
|
||||||
ExtraType = "sample",
|
ExtraType = "sample",
|
||||||
RuleType = ExtraRuleType.Suffix,
|
RuleType = ExtraRuleType.Suffix,
|
||||||
@@ -476,7 +495,6 @@ namespace Emby.Naming.Common
|
|||||||
Token = "theme",
|
Token = "theme",
|
||||||
MediaType = MediaType.Audio
|
MediaType = MediaType.Audio
|
||||||
},
|
},
|
||||||
|
|
||||||
new ExtraRule
|
new ExtraRule
|
||||||
{
|
{
|
||||||
ExtraType = "scene",
|
ExtraType = "scene",
|
||||||
@@ -526,8 +544,8 @@ namespace Emby.Naming.Common
|
|||||||
Token = "-short",
|
Token = "-short",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Format3DRules = new[]
|
Format3DRules = new[]
|
||||||
{
|
{
|
||||||
// Kodi rules:
|
// Kodi rules:
|
||||||
@@ -648,11 +666,9 @@ namespace Emby.Naming.Common
|
|||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
|
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
|
||||||
|
|
||||||
}.Select(i => new EpisodeExpression(i)
|
}.Select(i => new EpisodeExpression(i)
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
VideoFileExtensions = extensions
|
VideoFileExtensions = extensions
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -18,6 +18,19 @@
|
|||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Code analysers-->
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Emby.Naming.Extensions
|
|||||||
{
|
{
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
|
// TODO: @bond remove this when moving to netstandard2.1
|
||||||
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Emby.Naming
|
|
||||||
{
|
|
||||||
internal static class StringExtensions
|
|
||||||
{
|
|
||||||
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
var previousIndex = 0;
|
|
||||||
var index = str.IndexOf(oldValue, comparison);
|
|
||||||
|
|
||||||
while (index != -1)
|
|
||||||
{
|
|
||||||
sb.Append(str.Substring(previousIndex, index - previousIndex));
|
|
||||||
sb.Append(newValue);
|
|
||||||
index += oldValue.Length;
|
|
||||||
|
|
||||||
previousIndex = index;
|
|
||||||
index = str.IndexOf(oldValue, index, comparison);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(str.Substring(previousIndex));
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,16 +7,19 @@ namespace Emby.Naming.Subtitles
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the language.
|
/// Gets or sets the language.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The language.</value>
|
/// <value>The language.</value>
|
||||||
public string Language { get; set; }
|
public string Language { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is default.
|
/// Gets or sets a value indicating whether this instance is default.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
|
||||||
public bool IsDefault { get; set; }
|
public bool IsDefault { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is forced.
|
/// Gets or sets a value indicating whether this instance is forced.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,31 +7,37 @@ namespace Emby.Naming.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the container.
|
/// Gets or sets the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The container.</value>
|
/// <value>The container.</value>
|
||||||
public string Container { get; set; }
|
public string Container { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of the series.
|
/// Gets or sets the name of the series.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name of the series.</value>
|
/// <value>The name of the series.</value>
|
||||||
public string SeriesName { get; set; }
|
public string SeriesName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the format3 d.
|
/// Gets or sets the format3 d.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The format3 d.</value>
|
/// <value>The format3 d.</value>
|
||||||
public string Format3D { get; set; }
|
public string Format3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [is3 d].
|
/// Gets or sets a value indicating whether [is3 d].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
|
||||||
public bool Is3D { get; set; }
|
public bool Is3D { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is stub.
|
/// Gets or sets a value indicating whether this instance is stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
|
||||||
public bool IsStub { get; set; }
|
public bool IsStub { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the stub.
|
/// Gets or sets the type of the stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -39,12 +45,17 @@ namespace Emby.Naming.TV
|
|||||||
public string StubType { get; set; }
|
public string StubType { get; set; }
|
||||||
|
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
public int? EpisodeNumber { get; set; }
|
public int? EpisodeNumber { get; set; }
|
||||||
|
|
||||||
public int? EndingEpsiodeNumber { get; set; }
|
public int? EndingEpsiodeNumber { get; set; }
|
||||||
|
|
||||||
public int? Year { get; set; }
|
public int? Year { get; set; }
|
||||||
|
|
||||||
public int? Month { get; set; }
|
public int? Month { get; set; }
|
||||||
|
|
||||||
public int? Day { get; set; }
|
public int? Day { get; set; }
|
||||||
|
|
||||||
public bool IsByDate { get; set; }
|
public bool IsByDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
@@ -16,38 +15,34 @@ namespace Emby.Naming.TV
|
|||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
||||||
{
|
{
|
||||||
// Added to be able to use regex patterns which require a file extension.
|
// Added to be able to use regex patterns which require a file extension.
|
||||||
// There were no failed tests without this block, but to be safe, we can keep it until
|
// There were no failed tests without this block, but to be safe, we can keep it until
|
||||||
// the regex which require file extensions are modified so that they don't need them.
|
// the regex which require file extensions are modified so that they don't need them.
|
||||||
if (IsDirectory)
|
if (isDirectory)
|
||||||
|
{
|
||||||
path += ".mp4";
|
path += ".mp4";
|
||||||
|
}
|
||||||
|
|
||||||
EpisodePathParserResult result = null;
|
EpisodePathParserResult result = null;
|
||||||
|
|
||||||
foreach (var expression in _options.EpisodeExpressions)
|
foreach (var expression in _options.EpisodeExpressions)
|
||||||
{
|
{
|
||||||
if (supportsAbsoluteNumbers.HasValue)
|
if (supportsAbsoluteNumbers.HasValue
|
||||||
|
&& expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
|
||||||
{
|
{
|
||||||
if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isNamed.HasValue)
|
|
||||||
|
if (isNamed.HasValue && expression.IsNamed != isNamed.Value)
|
||||||
{
|
{
|
||||||
if (expression.IsNamed != isNamed.Value)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isOptimistic.HasValue)
|
|
||||||
|
if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value)
|
||||||
{
|
{
|
||||||
if (expression.IsOptimistic != isOptimistic.Value)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentResult = Parse(path, expression);
|
var currentResult = Parse(path, expression);
|
||||||
@@ -94,7 +89,8 @@ namespace Emby.Naming.TV
|
|||||||
DateTime date;
|
DateTime date;
|
||||||
if (expression.DateTimeFormats.Length > 0)
|
if (expression.DateTimeFormats.Length > 0)
|
||||||
{
|
{
|
||||||
if (DateTime.TryParseExact(match.Groups[0].Value,
|
if (DateTime.TryParseExact(
|
||||||
|
match.Groups[0].Value,
|
||||||
expression.DateTimeFormats,
|
expression.DateTimeFormats,
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
DateTimeStyles.None,
|
DateTimeStyles.None,
|
||||||
@@ -106,15 +102,12 @@ namespace Emby.Naming.TV
|
|||||||
result.Success = true;
|
result.Success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (DateTime.TryParse(match.Groups[0].Value, out date))
|
||||||
{
|
{
|
||||||
if (DateTime.TryParse(match.Groups[0].Value, out date))
|
result.Year = date.Year;
|
||||||
{
|
result.Month = date.Month;
|
||||||
result.Year = date.Year;
|
result.Day = date.Day;
|
||||||
result.Month = date.Month;
|
result.Success = true;
|
||||||
result.Day = date.Day;
|
|
||||||
result.Success = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Only consider success if date successfully parsed?
|
// TODO: Only consider success if date successfully parsed?
|
||||||
@@ -139,7 +132,8 @@ namespace Emby.Naming.TV
|
|||||||
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
||||||
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
||||||
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
||||||
if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
|
if (nextIndex >= name.Length
|
||||||
|
|| "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
|
||||||
{
|
{
|
||||||
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
||||||
{
|
{
|
||||||
@@ -157,6 +151,7 @@ namespace Emby.Naming.TV
|
|||||||
{
|
{
|
||||||
result.SeasonNumber = num;
|
result.SeasonNumber = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
||||||
{
|
{
|
||||||
result.EpisodeNumber = num;
|
result.EpisodeNumber = num;
|
||||||
@@ -168,8 +163,11 @@ namespace Emby.Naming.TV
|
|||||||
// Invalidate match when the season is 200 through 1927 or above 2500
|
// Invalidate match when the season is 200 through 1927 or above 2500
|
||||||
// because it is an error unless the TV show is intentionally using false season numbers.
|
// because it is an error unless the TV show is intentionally using false season numbers.
|
||||||
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
|
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
|
||||||
if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500)
|
if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928)
|
||||||
|
|| result.SeasonNumber > 2500)
|
||||||
|
{
|
||||||
result.Success = false;
|
result.Success = false;
|
||||||
|
}
|
||||||
|
|
||||||
result.IsByDate = expression.IsByDate;
|
result.IsByDate = expression.IsByDate;
|
||||||
}
|
}
|
||||||
@@ -191,13 +189,20 @@ namespace Emby.Naming.TV
|
|||||||
|
|
||||||
private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions)
|
private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions)
|
||||||
{
|
{
|
||||||
var results = expressions
|
foreach (var i in expressions)
|
||||||
.Where(i => i.IsNamed)
|
|
||||||
.Select(i => Parse(path, i))
|
|
||||||
.Where(i => i.Success);
|
|
||||||
|
|
||||||
foreach (var result in results)
|
|
||||||
{
|
{
|
||||||
|
if (!i.IsNamed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = Parse(path, i);
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(info.SeriesName))
|
if (string.IsNullOrEmpty(info.SeriesName))
|
||||||
{
|
{
|
||||||
info.SeriesName = result.SeriesName;
|
info.SeriesName = result.SeriesName;
|
||||||
@@ -208,12 +213,10 @@ namespace Emby.Naming.TV
|
|||||||
info.EndingEpsiodeNumber = result.EndingEpsiodeNumber;
|
info.EndingEpsiodeNumber = result.EndingEpsiodeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.SeriesName))
|
if (!string.IsNullOrEmpty(info.SeriesName)
|
||||||
|
&& (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue))
|
||||||
{
|
{
|
||||||
if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)
|
break;
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,21 @@ namespace Emby.Naming.TV
|
|||||||
public class EpisodePathParserResult
|
public class EpisodePathParserResult
|
||||||
{
|
{
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
public int? EpisodeNumber { get; set; }
|
public int? EpisodeNumber { get; set; }
|
||||||
|
|
||||||
public int? EndingEpsiodeNumber { get; set; }
|
public int? EndingEpsiodeNumber { get; set; }
|
||||||
|
|
||||||
public string SeriesName { get; set; }
|
public string SeriesName { get; set; }
|
||||||
|
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
|
|
||||||
public bool IsByDate { get; set; }
|
public bool IsByDate { get; set; }
|
||||||
|
|
||||||
public int? Year { get; set; }
|
public int? Year { get; set; }
|
||||||
|
|
||||||
public int? Month { get; set; }
|
public int? Month { get; set; }
|
||||||
|
|
||||||
public int? Day { get; set; }
|
public int? Day { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ namespace Emby.Naming.TV
|
|||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeInfo Resolve(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
public EpisodeInfo Resolve(
|
||||||
|
string path,
|
||||||
|
bool isDirectory,
|
||||||
|
bool? isNamed = null,
|
||||||
|
bool? isOptimistic = null,
|
||||||
|
bool? supportsAbsoluteNumbers = null,
|
||||||
|
bool fillExtendedInfo = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
@@ -26,7 +32,7 @@ namespace Emby.Naming.TV
|
|||||||
string container = null;
|
string container = null;
|
||||||
string stubType = null;
|
string stubType = null;
|
||||||
|
|
||||||
if (!IsDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
@@ -52,7 +58,7 @@ namespace Emby.Naming.TV
|
|||||||
var format3DResult = new Format3DParser(_options).Parse(flags);
|
var format3DResult = new Format3DParser(_options).Parse(flags);
|
||||||
|
|
||||||
var parsingResult = new EpisodePathParser(_options)
|
var parsingResult = new EpisodePathParser(_options)
|
||||||
.Parse(path, IsDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
|
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
|
||||||
|
|
||||||
return new EpisodeInfo
|
return new EpisodeInfo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,30 +3,24 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Emby.Naming.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
public class SeasonPathParser
|
public class SeasonPathParser
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _options;
|
|
||||||
|
|
||||||
public SeasonPathParser(NamingOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
||||||
{
|
{
|
||||||
var result = new SeasonPathParserResult();
|
var result = new SeasonPathParserResult();
|
||||||
|
|
||||||
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
|
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
|
||||||
|
|
||||||
result.SeasonNumber = seasonNumberInfo.Item1;
|
result.SeasonNumber = seasonNumberInfo.seasonNumber;
|
||||||
|
|
||||||
if (result.SeasonNumber.HasValue)
|
if (result.SeasonNumber.HasValue)
|
||||||
{
|
{
|
||||||
result.Success = true;
|
result.Success = true;
|
||||||
result.IsSeasonFolder = seasonNumberInfo.Item2;
|
result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -35,7 +29,7 @@ namespace Emby.Naming.TV
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A season folder must contain one of these somewhere in the name
|
/// A season folder must contain one of these somewhere in the name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly string[] SeasonFolderNames =
|
private static readonly string[] _seasonFolderNames =
|
||||||
{
|
{
|
||||||
"season",
|
"season",
|
||||||
"sæson",
|
"sæson",
|
||||||
@@ -54,19 +48,23 @@ namespace Emby.Naming.TV
|
|||||||
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
|
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
|
||||||
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
|
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
private Tuple<int?, bool> GetSeasonNumberFromPath(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
|
||||||
|
string path,
|
||||||
|
bool supportSpecialAliases,
|
||||||
|
bool supportNumericSeasonFolders)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path) ?? string.Empty;
|
||||||
|
|
||||||
if (supportSpecialAliases)
|
if (supportSpecialAliases)
|
||||||
{
|
{
|
||||||
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(0, true);
|
return (0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(0, true);
|
return (0, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +72,7 @@ namespace Emby.Naming.TV
|
|||||||
{
|
{
|
||||||
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(val, true);
|
return (val, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,12 +82,12 @@ namespace Emby.Naming.TV
|
|||||||
|
|
||||||
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(val, true);
|
return (val, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for one of the season folder names
|
// Look for one of the season folder names
|
||||||
foreach (var name in SeasonFolderNames)
|
foreach (var name in _seasonFolderNames)
|
||||||
{
|
{
|
||||||
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -107,10 +105,10 @@ namespace Emby.Naming.TV
|
|||||||
|
|
||||||
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
|
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
|
||||||
return new Tuple<int?, bool>(resultNumber, true);
|
return (resultNumber, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? GetSeasonNumberFromPart(string part)
|
private static int? GetSeasonNumberFromPart(string part)
|
||||||
{
|
{
|
||||||
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -132,7 +130,7 @@ namespace Emby.Naming.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
private Tuple<int?, bool> GetSeasonNumberFromPathSubstring(string path)
|
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path)
|
||||||
{
|
{
|
||||||
var numericStart = -1;
|
var numericStart = -1;
|
||||||
var length = 0;
|
var length = 0;
|
||||||
@@ -174,10 +172,10 @@ namespace Emby.Naming.TV
|
|||||||
|
|
||||||
if (numericStart == -1)
|
if (numericStart == -1)
|
||||||
{
|
{
|
||||||
return new Tuple<int?, bool>(null, isSeasonFolder);
|
return (null, isSeasonFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Tuple<int?, bool>(int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
|
return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user